标点在左右边界处的伸出

先把要解决的问题交代一下。所谓“标点在左右边界处的伸出 (protruding)”,可以通过下面的示例来理解。

第一个示例是标点在版面左侧文本边界处未有伸出的示例。

第二个示例是标点在版面的左侧文本边界处伸出的示例,伸出的目的是让文字在左边界处垂向对齐,让版面更齐整。

同理,为了排版的美观,对于标点出现在文本右侧边界处也需要伸出处理。本文要讲述的是怎样控制 LuaTeX 来处理这类问题。

Posted by LiYanrui Jun 30, 2009 04:56:35 PM


如何创建 LuaTeX 结点

LuaTeX 的诸多结点(Node)类型中,目前我仅关心 glyph、glue、kern、penalty 和 rule。LuaTeX Reference 在第 8 章中粗略介绍了这些结点的数据结构,但是要真正理解它们,需要从 Knuth 的 The TeXbook 中获得一些 TeX 常识。我目前的目标仅仅是学会使用上述的那 5 种结点来解决几个事关中文处理的问题,因此我也许可以在不了解 LuaTeX 的情况下完成我的目的。本文以 glyph 结点的创建为例,来说明如何在自己写的外部扩展中创建那些结点并插入到文档中。

Posted by LiYanrui Jun 25, 2009 06:48:19 AM


Evince 终于可以支持批注了

这其实已经不是什么新闻了,只是因为它需要 >=0.11.0 版本的 poppler 的支持。而 Gentoo Portage 里把 poppler 的包拆出了问题,即使解决 >=poppler-0.11.0 的屏蔽,也是没法安装成功的,因为 poppler-utils 包的问题……总之是需要做一些手脚,才能把全套的 poppler-0.11.1 装上。

从 evince 的 git 仓库里编译了最新版本(2.27.3)。完毕后,兴冲冲地打开一个 pdf 文件,然后就是寻找添加批注的功能。事实证明,是找不到的。

我又窜到 Windows 里,用一个盗版的 acrobat 在一份 pdf 文档里添加了一个批注,然后再窜回 gentoo,用这个号称是开始支持批注的 evince 打开了这份 pdf,结果看到了这个:

现在可以耐心地等了,估计到 gnome 2.28 的时候,这个功能不会再跳票。

Posted by LiYanrui Jun 21, 2009 05:43:14 AM


背景

TeX 相关软件中文字体嵌入一个存在已久的问题水落石出,兼谈不拘小节的中文字体设计中 Wang Yue 谈到了这样的问题:

中文字体设计不拘小节也让我也想到了另一个问题,用先前,中文用户使用 XeTeX,需要频繁地切换中英文字体,后来 XeTeX 开发者不得不提供了一个机制来让字体切换变得不那么折腾。而我和 ConTeXt 开发者交流中文排版问题,还要煞费苦心地讲怎么切换,需要编程实现复杂的虚拟字体机制来实现。这个都归罪于中文字体普遍地缺乏高质量的英文部分,仔细看看 simsun 或者 simhei 的英文部分,就可以看出有多么夸张了。

如果说这个问题的原因是中国的字体公司,向来没有很好的英文字体设计基础,同时对这个问题也不加以重视,那么中文标点的设计,就没有丝毫的可以开罪的地方了,这个问题直接导致用户和开发者都非常为难。我们知道,高质量的中文排版,标点并不是占据一个中文字符的位置,而要比中文字符略小。 同时,标点之间需要存在压缩,比如逗号后紧紧跟随的关门引号,需要使用类似 kerning 的特性把两个 glyph 的距离减小。另外,类似破折号和省略号, 其实应该放在一个 glyph 中而不应该分开。而现在所有的中文字体的糟糕程度,竟然到所有的标点符号都占用一个中文字符距离的程度。本来这个问题如果中文 字体设计得当,使用默认的排版算法,就基本上能够解决一般的中文的排版问题,而现在糟糕的设计就使得排版软件的设计难上加难。首先我们需要重新定义一系列 的新算法和新规则,然后需要手工赋值去确定标点的大小和两个标点连在一起时候的压缩程度。更麻烦的是,不同字体中的相同的 glyph,比如逗号或者句号, 往往会在这个 box 的不同的位置,大小也会千差万别。调好了中易宋体的冒号和开门引号,把相同的数值使用到中易的隶书中,顿时两个符号就会挤在一起,这就 使得如果不针对每一个字体仔细调整,高质量的中文排版就几乎不可能。

我是受了 Wang Yue 几篇文章忽悠才开始使用 ConTeXt MkIV(下面简称 MkIV)的,迄今为止一年又半载。期间,看见了 MkIV 的效率以及 CJK 文字支持的诸多进步。比如,现在利用 MkIV 的 fallback 字体机制,已经很好地解决了中西文字体混合的问题,也就是说用设计比较专业的英文字体去替换中文字体所包含的英文字符部分。此外 Wolfgang Schuster 实现了 simplefonts 模块,提供了类似 XeLaTeX 的 fontspec 宏包那样的功能,简化了 ConTeXt 字体配置过程。但是,对于中文标点符号的处理,迄今也未有解决。虽然 Hans 多次许诺将来会解决这个问题,但是我们不知道他说的将来是什么时候了。

为了让 ConTeXt 对中文支持的更好一些,我想自己动手解决这个问题。假如将来 Hans 真的兑现了他所说的,提供了很好的中文支持,大不了我就扔掉这块工作。

一开始,我不知道该从哪里入手,在 ConTeXt 的 base 目录里 grep 了好长时间,发现有关中文断行以及标点处理的代码集中在 font-otf.lua 文件里,当时我对这个文件进行了一些 hack,只得到到了轻微的改善,不过我却受到了鼓舞,打消了对 ConTeXt 的畏惧。后来 Hans 对 CJK 文字的处理进行了调整,原先在 font-otf.lua 文件里的 CJK 文字处理部分的代码被重新改写了,并且放在了 scrp-ini.lua 和 scrp-cjk.lua 文件中,我对这两个文件继续 hack,工作成果见 http://bbs.ctex.org/viewthread.php?tid=48562。在此期间,把学习 MkIV 过程中胡乱写的一些笔记整理了一下,挂在了 http://bbs.ctex.org/viewthread.php?tid=45237。我不是一个有耐心的人,做什么事情都是三分钟热度,再加上 MkIV 层出不穷的 bug,所以有一段时间对 MkIV 的喜欢的热度也退却了很多。

在 hack MkIV 的时候,我感觉 MkIV 对中文标点的处理方式很脏,太依赖具体的字体。像 scrp-cjk.lua 文件中的许多参数,对 AdobeSongStd-Light.otf 字体是适合的,但是换成 simsun.ttc 就不行了,具体见 http://bbs.ctex.org/viewthread.php?tid=47559。仅仅是因为这个看似很小的问题,我还几次都想回到 XeTeX + xeCJK 的环境里。如果 OpenOffice.org 在排版方面不是那么废柴,我甚至都可以放弃 TeX。

再后来,看到 Wang Yue 频率较高的宣扬利用 bbox 来解决中文标点间距压缩问题,说这样就可以做到不依赖具体的字体。一开始,我对此是不以为然的,而且我也没法以之为然,因为那时我只是比较浅薄地知道 scrp-cjk.lua,其它的我都不知道。直到后来,孙文昌(CTeX 论坛的 mytex)老师给出了一个示例(见 http://bbs.ctex.org/viewthread.php?tid=49757)之时,我才大致明白所谓 bbox 的解决方式是怎么一回事。另外,Wang Yue 精简 ConTeXt Minimals,作了一个 mini luatex 包,我从他那里学会了如何生成 luatex 格式文件,如何在 tex 文件里加载自己写的 lua 程序。

我对 LuaTeX 和 MkIV 的认识就是这样私有似无地累积起来了,直到有一天我感觉可以从 LuaTeX 的层面上来实现对中文的单独处理,这样我就可以摆脱 Hans 的那套目前有些残废的中文支持方式,不必再担心他每一次升级 beta 版本而让我没法再用中文。虽然事实上还是有隐患,因为 Hans 为 LuaTeX 分离出来的字体处理部分,也就是 luatex-fonts-merged.lua 文件,它也是不稳定的,但是至少要比 MkIV 稳定。这段时间,我一直在折腾这件事情,随着最后一个有关标点边界对齐问题得到了解决,现在整套方案终于有了一个大概的眉目。

现在,打算正式开始解决这个问题。在此过程中,我会再重新整理一下思路,以连载的形式记录整个过程,希望能够对喜欢 LuaTeX与 MkIV 的同学有所帮助。

Posted by LiYanrui Jun 19, 2009 08:03:39 PM


走弯路了!

在“寻找标点符号及其包围盒”中,发现在 luatex-fonts-merged.lua 中需要将 define.read () 函数里的 "fontdata.cache" 的值被设置为 "yes" 才可以访问到字符包围盒信息,便发信给 Hans 询问为啥默认要设置为 "no"。结果 Hans 的回答让我很沮丧,原来通过我说的那种途径获得包围盒信息是绕了一个大圈子。

事实上,每个字体的信息都被存放在一个全局的表 fonts.ids 里,而且这个表在“断行”中我已经用过了,只是当时没理解这个表的作用。等后来稍微明白了 LuaTeX 处理字体的机制后,又把那个表给忘记了。

正确的做法是这样的:

local fontdata = fonts.ids

function f4zhcn.pre_linebreak_filter (head, groupcode)
   for t in node.traverse(head) do
      if is_cjk_ideo (t) then
         texio.write_nl ('*** CJK Ideo ***')
      elseif is_cjk_puncts (t) then
         texio.write_nl ('*** CJK Punct ***')
         for k in pairs(fontdata[t.font]) do
            texio.write_nl (k)
         end
      end
   end
   return true
end

Posted by LiYanrui Jun 18, 2009 04:17:39 PM


寻找标点符号及其包围盒

为解决“CJK 字符结点判定”中提到的标点间距压缩问题(详情参考这里),需要从 glyph 结点中筛选出是中文标点符号的结点,并且获得包围盒(boundingbox)信息。下面是我有些笨拙的探险。

Posted by LiYanrui Jun 18, 2009 04:09:16 AM


CJK 字符结点判定

在上一篇“断行”的末尾处,我提到了应该仅在中文字符之后插入 glue。查阅 luatex 手册,发现 glyph 结点含有 char 成员——体现为 glyph 的十进制编码。因此只需要将 CJK 的 Unicode 区域范围由 unicode 编码换算为十进制(直接用 16 进制也可以),就可以判断 glyph 结点是表示 CJK 字符。

Posted by LiYanrui Jun 15, 2009 06:24:15 AM


断行

在那篇“使用 luatex mini 包处理中文” 的文章中,我制造了一个不会断行的中文排版示例。中文断行问题的解决方法,以我的智慧只能想到两种。第一种方法是自己提出一个断行算法并程序实现。第二种 方法就是利用 LuaTeX 提供的断行算法。前者适合勤劳而且又懂 TeX 的人,而且 LuaTeX 也提供了相应的支持。很不幸,我又懒,又不懂 TeX,所以只好尝试第二种方法。

Posted by LiYanrui Jun 14, 2009 05:02:18 PM