寻找标点符号及其包围盒
为解决“CJK 字符结点判定”中提到的标点间距压缩问题(详情参考这里),需要从 glyph 结点中筛选出是中文标点符号的结点,并且获得包围盒(boundingbox)信息。下面是我有些笨拙的探险。
中文标点符号的判断
我将中文标点分成了三组:left_puncts、right_puncts 和 other_puncts,对应构造了三个 lua 表:
[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, -- ?
}
然后,在结点遍历过程中进行判断:
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 () 函数时,看到:
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 () 函数改写一下,就可以打印出标点符号的包围盒信息了,如下:
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 的中文逗号,输出的包围盒信息与字符的宽度值如下:
28
-11
73
59
width = 256
要验证一下,只需要用 fontforge 打开 simsun.ttc 字体,找到逗号字符,测量一下就可以了。从下图也可以大致看出来。