寻找标点符号及其包围盒

LiYanrui posted @ Jun 18, 2009 04:09:16 AM in Dream of TeX with tags luatex tex , 4795 阅读

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

中文标点符号的判断

我将中文标点分成了三组:left_puncts、right_puncts 和 other_puncts,对应构造了三个 lua 表:

local left_puncts = {
   [0x2018] = true, -- ‘
   [0x201C] = true, -- “
   [0x3008] = true, -- 〈
   [0x300A] = true, -- 《
   [0x300C] = true, -- 「
   [0x300E] = true, -- 『
   [0x3010] = true, -- 【
   [0x3014] = true, -- 〔
   [0x3016] = true, -- 〖
   [0xFF08] = true, -- (
   [0xFF3B] = true, -- [
   [0xFF5B] = true, -- {
}

local right_puncts = {
   [0x2019] = true, -- ’
   [0x201D] = true, -- ”
   [0x3009] = true, -- 〉
   [0x300B] = true, -- 》
   [0x300D] = true, -- 」
   [0x300F] = true, -- 』
   [0x3011] = true, -- 】
   [0x3015] = true, -- 〕
   [0x3017] = true, -- 〗
   [0xFF09] = true, -- )
   [0xFF3D] = true, -- ]
   [0xFF5D] = true, -- }
}

local other_puncts = {
   [0x2014] = true, -- —
   [0x2026] = true, -- …
   [0x2500] = true, -- ─
   [0x3001] = true, -- 、
   [0x3002] = true, -- 。
   [0xFF01] = true, -- !
   [0xFF05] = true, -- %
   [0xFF0C] = true, -- ,
   [0xFF0E] = true, -- .
   [0xFF1A] = true, -- :
   [0xFF1B] = true, -- ;
   [0xFF1F] = true, -- ?
}

然后,在结点遍历过程中进行判断:

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 ***')
         set_inter_glue (t.font, inter_glue)
         insert_node_after(head, t,
                           make_glue_node (inter_glue_width, inter_glue_stretch, inter_glue_shrink))
      elseif is_cjk_puncts (t) then
         texio.write_nl ('*** CJK Punct ***')
   end
   return true
end

对于中文 glyph 结点的判断,正确的次序应当是先判断中文字符结点,然后再判断标点字符结点。

获取标点字符的包围盒信息

不管是 LuaTeX 计算出了 glyph 的包围盒信息,还是 fontforge 计算的,总之 LuaTeX 将一款字体载入内存并为之构建 Lua 表时,是含有每个 glyph 包围盒信息的。我要做的就是如何找到它。

过 程很有些曲折,由于不了解 LuaTeX 的 OTF & TTF 字体载入机制,所以只好玩笨方法,用 print (">>>>>>>>>>>>>") 语句并配合一个 tex 文档示例,把 luatex-fonts-merged.lua 文件里所有与 OTF & TTF 字体处理有关的函数跟踪了一遍。事实证明,也只有这样才能基本上找得到。这是因为 Hans 在写字体载入脚本时,不知是出于啥心理,把 glyphs 表的名字改成了 descriptions,直至跟踪到 otf.copy_to_tfm () 函数时,看到:

        for u, i in next, indices do
            characters[u] = { } -- we need this because for instance we add protruding info
            descriptions[u] = glyphs[i]
        end

这 个 descriptions 表,在 LuaTeX 手册里是从未出现过,不用笨方法跟踪,实在很难知道。固然可以在最后打印出 fonts 表的所有键值,也是很难知道 descriptions 是 glyphs 表(这个表在 LuaTeX 手册里倒是有介绍),除非是对 fonts 表进行深层次的序列化。

知道 descriptions 表里包含着每个 glyph 的包围盒信息也是没用的,因为在 define_font () 回调函数的实现中,也就是 define.read () 函数,"fontdata.cache" 的值被设置为 "no",这样 descriptions 表的引用是不存在的,因此就无法访问它。将 "fontdata.cache" 的值设为 "yes" 就可以解决这一问题。现在说起来倒是很简单,昨晚被这事搞得头有点大,幸好今早瞟了一眼 LuaTeX 手册,又碰巧看到了 fonts 表结构中的这个键值的解释,而且这个键值在 LuaTeX 那里是默认设置为 "yes" 的,现在它之所以是 "no",估计是 Hans 的想法,是为了提高 ConTeXt MkIV 的运行效率。我已经发信去问了,不过他没搭理我 :(

将 f4zhcn.pre_linebreak_filter () 函数改写一下,就可以打印出标点符号的包围盒信息了,如下:

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 ***')
         set_inter_glue (t.font, inter_glue)
         insert_node_after(head, t,
                           make_glue_node (inter_glue_width, inter_glue_stretch, inter_glue_shrink))
      elseif is_cjk_puncts (t) then
         texio.write_nl ('*** CJK Punct ***')
         for i, k in ipairs(font.fonts[t.font].descriptions[t.char].boundingbox) do
            texio.write_nl (k)
         end
         texio.write_nl ('width = ' .. font.fonts[t.font].descriptions[t.char].width)
      end
   end
   return true
end

对于 simsun.ttc 的中文逗号,输出的包围盒信息与字符的宽度值如下:

*** CJK Punct ***
28
-11
73
59
width = 256

要验证一下,只需要用 fontforge 打开 simsun.ttc 字体,找到逗号字符,测量一下就可以了。从下图也可以大致看出来。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter