随 Adobe Acrobat/Reader 8 发布的中文字体中,出现了一款叫做 Adobe Heiti Std 的,初步使用之下,感觉非常满意。
[hei.pdf](http://www.newsmth.org/att.php?p.460.248713.1.442.538.pdf) 是用 XeTeX 生成的一个样例 PDF。下面的图片分别是 Linotype FontExplorer X 下查看到它的一些属性,和几个简繁差异较大的字的写法示例。另外这个字体一共包含 30591 个字形。
Random notes & thoughts.
有朋友问到 xcp.py 究竟怎么用,是我不对,说了半天连个例子都没举出来,光看两句介绍当然无法理解。我自己记性也不好,怕以后忘了,现在赶紧写下来:
\documentclass{article}
\usepackage{fontspec}
% 定义英文字体,更换为你希望使用的
\setromanfont{Minion Pro}
% 定义中文字体,可将 SimSun 更换为你希望使用的字体
\newfontinstance{\zhfont}{SimSun}
\newcommand{\zh}[1]{{\zhfont #1}}
% 设置中文断行,必备
\XeTeXlinebreaklocale “zh”
\XeTeXlinebreakskip = 0pt plus 1pt
\begin{document}
TeX 提供了一套功能强大并且十分灵活的排版语言,它多达 900
多条指令,并且 TeX 有宏功能,用户可以不断地定义自己适用的
新命令来扩展 TeX 系统的功能。许多人利用 TeX 提供的宏定义
功能对 TeX 进行了二次开发,其中比较著名的有美国数学学会推
荐的非常适合于数学家使用的 AMS-TeX 以及适合于一般文章、报
告、书籍的 LaTeX 系统。
\end{document}
将此文件存为 foo.tex,对它
python xcp.py foo.tex > foo.out.tex
xelatex foo.out.tex
mv foo.out.pdf foo.pdf
就可以得到你需要的 PDF 啦。如果你有兴趣,还可以看看 foo.out.tex 是什么样子的:
\documentclass{article}
\usepackage{fontspec}
% 定义英文字体,更换为你希望使用的
\setromanfont{Minion Pro}
% 定义中文字体,可将 SimSun 更换为你希望使用的字体
\newfontinstance{\zhfont}{SimSun}
\newcommand{\zh}[1]{{\zhfont #1}}
% 设置中文断行,必备
\XeTeXlinebreaklocale “zh”
\XeTeXlinebreakskip = 0pt plus 1pt
\begin{document}
TeX \zh{提供了一套功能强大并且十分灵活的排版语言,它多达} 900
\zh{多条指令,并且} TeX \zh{有宏功能,用户可以不断地定义自己适用的%
新命令来扩展} TeX \zh{系统的功能。许多人利用} TeX \zh{提供的宏定义%
功能对} TeX \zh{进行了二次开发,其中比较著名的有美国数学学会推%
荐的非常适合于数学家使用的} AMS-TeX \zh{以及适合于一般文章、报%
告、书籍的} LaTeX \zh{系统。}
\end{document}
可见导言区都没有修改,只是对正文区进行了预处理。
我们可以打开 PDF 看看,效果如图:
可见中文使用了宋体,英文使用了 Minion Pro,正是我们需要的效果。
#### 两个功能
1. 使用 Python 来对 XeTeX 文档进行处理,进行自动的中文与英文字体的切换。
2. 消除两行连续的中文之间多余的空格。
#### 如何获取
从下面的地址下载:
[http://xcp.googlecode.com/svn/trunk/xcp.py](http://xcp.googlecode.com/svn/trunk/xcp.py)
可以到 [http://code.google.com/p/xcp/](http://code.google.com/p/xcp/) 查看相关信息和提交 Issues。
#### 使用方法
现在这个版本应该可以处理任何包含中文的 XeTeX 文档了。使用方法很简单,在文档的导言区加上:
\usepackage{fontspec}
\setromanfont{Garamond Premier Pro} % 此处指定英文字体
\newfontinstance\zhfont{SimSun} % 此处指定中文字体
\newcommand{\zh}[1]{{\zhfont #1}}
然后调用:
python xcp.py foo.tex > bar.tex
编译得到的 bar.tex 即可。
#### 几点说明
1. 默认接受 UTF-8 格式的文档,如果你使用了其他编码,可以修改 xcp.py 第 5 行的 encoding=’utf-8′ 为你使用的编码,在 [http://docs.python.org/lib/standard-encodings.html](http://docs.python.org/lib/standard-encodings.html) 可以找到 Python 支持的所有编码。
2. 默认在 `\begin{document}` 之后才开始处理,到 `\end{document}` 结束处理,所以记住把 `\title{}` 之类放在这中间,其中的汉字才能被处理。如果你用的不是 LaTeX 而是 ConTeXt,可以相应的改成 `\starttext` 和 `\stoptext`。
3. 需要 Python 2.4。并建议把文件格式转换为 Unix 的。
4. 如果需要避免它处理部分代码,则可以使用 `@{}` 标记,这样 `{}` 中间的内容都不会被处理。比如 `@{中文}` 将被转换为 `{中文}`,而不是 `{\zh{中文}}`。
自从第一部分贴出以来,受到了很多朋友的鼓励,令我感到非常高兴: 看来尽管我拙于表达,对于 TeX 系统的构建感兴趣的朋友还是不少的。不过有一点需要说明:撰写此文的主要目的并非是“构建一个最小的 TeX 系统”,只不过正巧 web2c 系统符合我们讲解的需要而已,我所希望证明的是,TeX 的构建看似复杂,其实也不过是有许多细小的“砖石”一块块搭成,并无什么神秘的地方,尽管大家的兴趣多数是在 TeX 的基础上撰写宏包,但其实参与到 TeX 系统的开发中去也并非那么困难。
web2c 只是用 YACC 写的一套简单的分析程序,由于无法作复杂的分析,所以类似 autotools 的那套“由 A 生成 B,B 生成 C,C 再生成 D”这样化学反应链一般的恐怖过程便被设计出来。
一开始,因为没法直接从 Pascal 代码中分析出其中的变量与函数,所以不得不在一些称为 .defines 的文件中专门说明这些,比如我们如果要用 web2c 转换 etex.p,就得把 common.defines, texmf .defines 和 etexdir/etex.defines 再加上 etex.p 四个文件连接起来送给 web2c 这个程序的标准输入。
然后编写 web2c 的人发现,就算这样还是不好直接通过 web2c 生成的方式搞定所有 C 程序里需要的定义,还是另外把一些内容写在 .h 头文件里,让生成的 .c 程序 #include 它就好了。于是就有一个叫做 texmf.h 的头文件被 C 程序包含,后来这个包含继续复杂化,变成现在这样,C 程序包含 texd.h, texd.h 除了自己的一部分自动生成的内容之外包含 texmfmp.h 和 texcoerce.h,而 texcoerce.h 的内容则是把 web2c 生成的一部分内容再加上一个通用的 coerce.h 连接起来得到的……实在太麻烦也太恶心了,好在我们不需要细究这个,毕竟我们只希望知道,如果要给 TeX 增加代码,对 web 文件的改动,如果增加了变量、函数、过程,则应该在 .defines 文件里增加内容,如果有额外的 C 程序需要链接在一起,则应该找一个被 texd.h 包含的 .h 文件,往里面添加增加全局变量或者函数声明。
分析 web2c/convert 这个 shell 脚本我们可以发现,这个脚本完成了完整的从 Pascal 转换到 C 的过程,一个 tex.p 被分成了三个部分:
texd.h (被其他 .c 文件包含的头文件),事实上,这个文件主要是从对应 .defines 文件里得来,比如 web2c/texmf.defines 就定义了全局变量和函数原型 (prototypes)。对此的分析可以让我们得出结论:如果我们希望把部分模块单独用 C 语言写好,最后再和原来的 TeX 程序链接起来,是可以做到的,只需要在对应的 .h 文件中增加相应的函数声明,并在 Makefile.in 文件中提供正确的链接选项即可。事实上,虽然 eTeX, pdfTeX, Omega 这些 TeX 的扩展都还是用纯 web 语言编写的,但较新的一些扩展,如 XeTeX 和 LuaTeX,都体现了尽量把代码提取出来用 C/C++ 实现的思路。
texini.c: 这是原来的程序中标记了 INITEX 那一部分的代码, 读过 TeXBook 的朋友知道,TeX 可以采用一种特殊的方式把大量的宏定义转换成一种便于以后快速载入的文件 (很像现在的 C 编译器提供的预编译头文件 PCH 的功能),这种文件就称为 format 文件,而用于生成——在这里叫做 dump——这种 format 文件的程序就是initex。以前 Knuth 的设计是,如果我们把标记了 INITEX 的那部分代码编译进去,生成的程序就叫做 initex,专门用于 dump format,如果不编译进去,就是我们日常运行的 tex,只能载入 format 而不能 \dump。熟悉 C 语言的朋友可以想象成,web2c 里把 `#ifdef INITEX` 和 `#endif` 之间的那部分代码提取出来,形成了 texini.c。
顺便说一下,现在的 tex 程序往往都把这个 texini.c 默认就编译进去,不过一定要指定命令行参数 -initialize (-ini) 才会启用这部分功能,事实上平时我们用于生成 format 文件的 fmtutil 这个脚本就是调用 tex -ini 来创建格式的。如果用 tetex/TeXLive,可以打开 kpsewhich fmtutil.cnf 文件查看不同的格式对应的源文件 (如果用 MikTeX,你可以使用 MikTeX Options->Formats 查看),比如我这里 Plain TeX 格式对应的源文件的内容就是:
\input plain
\dump
\endinput
你同样可以用 etex -ini 启动 TeX 系统,输入上面的内容,手动地生成格式。
tex0.c, tex1.c, tex2.c: 在前面我们提到过,原来的 Pascal 代码足足有 3 万多行,自然,转换出的 C 代码也有 3 万多行,设计 web2c 的时候生怕当时的 C 编译器无法一口啃下这么长的代码,就专门开发了一个叫做 splitup 的程序,把完整的程序切分为三个差不多长度的文件——嗯,叫人很汗的想法。
得到这些文件之后就简单了,只要用 C 编译器编译并链接起来就得到我们平时运行的 TeX 程序。
最后是一幅图,把前边叙述的内容作一小结。下次我们将讨论 TeX 系统中的文件搜索与支持它的核心:Kpathsea。
这个东东首发在水木社区的 TeX 版,反响还不错,就贴来大家一同讨论吧 🙂
谈谈 TeX 系统的构建过程,不过这个其实挺没意思的,如果只是用 TeX,而不打算修改 TeX 的代码的人完全不需要了解这个,我也不会涉及什么 TeXBook 里的内容,不知道有人有兴趣不?先贴一小段,要是大家有兴趣我就继续贴。呵呵,也算是一个开发了几十年的软件如何演化的一点掌故。
偶尔,你可能会突发奇想:平时自己用的这些 tex, latex, pdftex 是怎么来的?——是怎么写出来的,又是怎么编译出来的?
愿意究根问底的话,可以先自己动手试一试,目前可以工作的最小的 TeX 系统是 web2c,我们常用的发行版中的二进制程序,都是根据 web2c 中提供的代码,基本不作修改,或者只作很小修改编译而来的,所以,你可以先从 http://www.tug.org/ftp/tex/web2c.tar.gz 下载一份 web2c 的代码,解压开来,听我一步步的讲。
众所周知,Knuth 最早是在一台 DEC PDP-11 上用一种叫做 SAIL 的语言编写的 TeX,尔后他和一些合作者,开始在 Stanford 大学实验一种叫做 literate programming (文学编程) 的新式编程方法,他们设计了一套叫做 WEB 的系统,用 Pascal 语言来编写程序,用 TeX 来编写文档,将两者交织为一张“网”。
经过几年的不断修改,WEB 系统和 TeX 系统相互的影响下逐渐成熟,到 1982 年的时候,两者都基本上稳定下来,此时的 TeX 系统,核心就是一个 tex.web 文件。OK, 看看你刚刚解压出来的 web2c 目录下是不是有这么一个文件?那就是 Knuth 亲手写的,原封不动。
WEB 系统中有两个独立运行的程序,一个叫做 tangle (扰乱?) 一个叫 weave (编织?),运行 tangle tex.web 将生成一个叫做 tex.p 的 Pascal 程序,这个程序可以用任何现代的 Pascal 编译器编译;而运行 weave tex.web 将生成 tex.tex,你可以用自己系统里已经安装的 tex 来 tex tex.tex 得到这个程序的文档:tex.dvi。
我相信很多人看到这里就会觉得相当无聊,那不妨认为你现在用的 TeX 系统就是这么来的句号。如果你坚持到现在还在继续看,那就可以接下去看更完整一点的故事。
Knuth 当时写着写着,居然写了一个三万多行代码的文件出来 (灌水王啊~) 这已经很恐怖了,可是后来大家要接着在他后边修改就很累了,他自己也得做 bug fix 不是?所以就给 WEB 系统添加了一个功能:合并主程序和 change file 的功能。
其实这个 change file,和我们现在常用的 diff 出来的 patch 结构也差不多,无非是原来代码的上下文,和要修改成的代码。比如在 tex.ch 里,你可以看到:
@x
原代码
@y
新代码
@z
这就是一个完整的修改单元。tangle 和 weave 都同时接受主程序和 change file 两个参数,将两者归并起来再行生成 Pascal 程序和 tex 文档,也就是说,实际的编译过程是这样的:
tangle tex.web tex.ch 得到 tex.p
weave tex.web tex.ch 得到 tex.tex
可是后来,Knuth 又说,TeX 这个程序应该 freeze 下来,大家不要再改了啊,想改,就换个名字,不要再叫做 tex 了。所以后来陆陆续续就出现 etex, omega, pdftex 等等增强版本,可是大家做这些增强版本的时候,也不好意思直接在 Knuth 的代码上改啊 (恩,大牛写的代码只能拜),就纷纷创建自己的 change file。渐渐的 change file 就多了起来,比如 XeTeX, 要归并的 change file 有四五个之多,可是 tangle
和 weave 都只支持一个 change file 啊,于是就有人写了一个叫做 tie 的程序,专门做这种归并工作 (和 Larry Wall 写的 patch 一样),你可以指定一个主文件和 9 个以内的 change file,按顺序一个一个归并起来。
所以我们看 etex 的编译过程 (在 etexdir/etex.mk 这个 Makefile 里可以看到),就是先:
tie -m etex.web tex.web etex.ch etex.fix
得到 etex.web,然后 tangle etex.web 得到 etex.p 的。
可是 Knuth 开发 TeX 的那个时代 C 还没有获得大众 (OK,如果计算机科学家也算作大众的话) 的欢迎,也许因为 Knuth 本人和 Nicklaus Wirth 关系也比较铁,WEB 就优先选用了 Pascal 语言来编写 TeX,然而到了 90 年代,Pascal 渐渐退出了大家的视野,尤其在 Unix 系统下,C 更是金科玉律一般没有任何其他语言能替代,考虑到普及的原因,也考虑到方便与其他 C 语言编写的模块一同链接的原因,一班人开始考虑把 TeX 的代码转换为 C 的可能性,这就产生了 web2c。(web2c 一位长期的维护者 Karl Berry 就是现任 TUG 主席,也是 TeXLive 的主要维护者之一。)
最直接的办法就是把 tangle 后的 Pascal 代码转换为 C 代码,web2c 也正是如此设计的,毕竟 Knuth 当时为了保证尽可能的方便移植,几乎没有用到只有 Pascal 语言才有的特性,所涵盖的功能现代的 C 编译器
都已具备。所以他们采取的方法就是用 lex 和 yacc 写出了一个把 tangle 输出转换为 C 代码的程序。
那么,究竟应该如何调用 web2c 来生成 C 代码呢?生成出来的代码又该如何编译呢?且听下回分解。