因为中文字体设计的缘故,中文的引号、括号、书名号等标点符号,它们的左半部分,例如左引号,在字符图形空间是靠右侧的。
按照中文排版惯例,段落首行缩进两个字符的宽度。但是,像左引号这样的标点符号,当它出现在段落之首时,所体现的段落缩进在视觉上就会大于两个字符的宽度。这样的标点符号出现在段落中任何一行的行首,都会导致类似的问题。
在意这样的问题,可能会被嘲笑或被不以为然国。不过,我不仅在意,而且用了大概一天的时间把它解决了。
解决方法很简单,就是在排版软件完成了段落分行之后,写个程序去检查引号、括号、书名号等标点符号的左半部分是不是出现在了段落中每一行的开始,然后对符合这一情况的文本行里的字符位置进行微调。当然,前提是这个排版软件支持这样的程序。大多数是不支持的,不过,TeX 可以。基于 TeX 的 ConTeXt MkIV 也可以。所以,我才有机会对这个问题的解决作一些记录。
既然出现在行首的标点会导致缩进宽度在视觉上增大,消除这个增量的最简单的办法是,将这一行文本整体向左偏移,直到标点与段落左边界对齐为止,段落首行的左引号算是与段落左边界近似齐平。
但是,将一行文本向做移动,肯定会导致文本的右侧出现空缺,这在上面的图里可以看得出来。有一些拆东墙补西墙的意味。此时,由于段落分行工作已经结束,因此没有办法再从下一行里取字符去填补这一行右侧的微小空缺;即使有办法,也会产生连锁反应,从而波及整个段落再度重新分行,这样会让问题变得非常复杂,甚至无解。
需要换个角度去思考。一行文本向左移动而引起的右侧空缺,是因为最右侧的字符被「坑」了。民主的做法是,这一行文本里的每个字符都应该承担一点空缺。可以想象为,将这行文本最右侧的空缺打碎,将碎片插入到这一行文本的每个字符的后面,从而使得该行文本整体向右有所膨胀。由于每个字符后面所分担的空缺碎片很小,足以欺骗我的眼睛。
下面给出一个具有一般性的例子以及完整的试验代码:
\usemodule[zhfonts]
\setuppapersize[A5][A5]
\setupindenting[first,always,2em]
\setupinterlinespace[line=1.5em]
\startluacode
zhfonts = zhfonts or {}
local hlist = nodes.nodecodes.hlist
local glyph = nodes.nodecodes.glyph
local insert_before = node.insert_before
local insert_after = node.insert_after
local new_kern = nodes.pool.kern
local fonthashes = fonts.hashes
local fontdata = fonthashes.identifiers
local quaddata = fonthashes.quads
local left_puncs = {
[0x2018] = 0.35, -- ‘
[0x201C] = 0.35, -- “
[0x3008] = 0.35, -- 〈
[0x300A] = 0.35, -- 《
[0x300C] = 0.35, -- 「
[0x300E] = 0.35, -- 『
[0x3010] = 0.35, -- 【
[0x3014] = 0.35, -- 〔
[0x3016] = 0.35, -- 〖
[0xFF08] = 0.35, -- (
[0xFF3B] = 0.35, -- [
[0xFF5B] = 0.35 -- {
}
local function is_left_punc(n)
if left_puncs[n.char] then return true end
return false
end
local function quad_multiple(font, r)
local quad = quaddata[font]
return r * quad
end
function zhfonts.align_left_puncs(head)
local it = head
while it do
if it.id == hlist then
local e = it.head
local neg_kern = nil
local hit = nil
while e do
if e.id == glyph then
if is_left_punc(e) then
hit = e
end
break
end
e = e.next
end
if hit ~= nil then
-- 文本行整体向左偏移
neg_kern = -left_puncs[hit.char] * quad_multiple(hit.font, 1)
insert_before(head, hit, new_kern(neg_kern))
-- 统计字符个数
local w = 0
local x = hit
while x do
if x.id == glyph then w = w + 1 end
x = x.next
end
if w == 0 then w = 1 end
-- 将 neg_kern 分摊出去
x = it.head -- 重新遍历
av_neg_kern = -neg_kern/w
local i = 0
while x do
if x.id == glyph then
i = i + 1
-- 最后一个字符之后不插入 kern
if i < w then
insert_after(head, x, new_kern(av_neg_kern))
end
end
x = x.next
end
end
end
it = it.next
end
return head, done
end
nodes.tasks.appendaction("finalizers", "after", "zhfonts.align_left_puncs")
\stopluacode
\starttext
\quotation{很遗憾,}最高执政官说,\quotation{如果没有高级文明的培植,他们还要在亚光速和三维时空中被禁锢两千年,至少还需一千年时间才能掌握和使用湮灭能量,两千年后才能通过多维时空进行通讯,至于通过超空间跃迁进行宇宙航行,可能是五千年后的事了,至少要一万年,他们才具备加入银河系碳基文明大家庭的起码条件。}
参议员说:\quotation{文明的这种孤独进化,是银河系太古时代才有的事。如果那古老的记载正确,我那太古的祖先生活在一个海洋行星的深海中。在那黑暗世界中的无数个王朝后,一个庞大的探险计划开始了,他们发射了第一个外空飞船,那是一个透明浮力小球,经过漫长的路程浮上海面。当时正是深夜,小球中的先祖第一次看到了星空……你们能够想象,那对他们是怎样的壮丽和神秘啊!}
最高执政官说:\quotation{那是一个让人想往的时代,一粒灰尘样的行星对先祖都是一个无限广阔的世界,在那绿色的海洋和紫色的草原上,先祖敬畏地面对群星……这感觉我们已丢失千万年了。}
\quotation{可我现在又找回了它!}参议员指着地球的影像说,她那蓝色的晶莹球体上浮动着雪白的云纹,他觉得她真像一种来自他祖先星球海洋中的一种美丽的珍珠,\quotation{看这个小小的世界,她上面的生命体在过着自己的生活,做着自己的梦,对我们的存在,对银河系中的战争和毁灭全然不知,宇宙对他们来说,是希望和梦想的无限源泉,这真象一首来自太古时代的歌谣。}
他真的吟唱了起来,他们三人的智能场合为一体,荡漾着玫瑰色的波纹。那从遥远得无法想象的太古时代传下来的歌谣听起来悠远、神秘、苍凉,通过超空间,它传遍了整个银河系,在这团由上千亿颗恒星组成的星云中,数不清的生命感到了一种久已消失的温馨和宁静。
\quotation{宇宙的最不可理解之处在于它是可以理解的。}最高执政官说。
\quotation{宇宙的最可理解之处在于它是不可理解的。}参议员说
\stoptext
现在,我已经将上述代码中处理标点符号与段落左边界对齐的代码合并到 zhfonts 模块 [1] 了,所以上例里 \startluacode ... \stopluacode
里的代码是不需要的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。