对齐

1

因为中文字体设计的缘故,中文的引号、括号、书名号等标点符号,它们的左半部分,例如左引号,在字符图形空间是靠右侧的。

左引号

按照中文排版惯例,段落首行缩进两个字符的宽度。但是,像左引号这样的标点符号,当它出现在段落之首时,所体现的段落缩进在视觉上就会大于两个字符的宽度。这样的标点符号出现在段落中任何一行的行首,都会导致类似的问题。

引号未与段落首行的左边界对齐

在意这样的问题,可能会被嘲笑或被不以为然国。不过,我不仅在意,而且用了大概一天的时间把它解决了。

解决方法很简单,就是在排版软件完成了段落分行之后,写个程序去检查引号、括号、书名号等标点符号的左半部分是不是出现在了段落中每一行的开始,然后对符合这一情况的文本行里的字符位置进行微调。当然,前提是这个排版软件支持这样的程序。大多数是不支持的,不过,TeX 可以。基于 TeX 的 ConTeXt MkIV 也可以。所以,我才有机会对这个问题的解决作一些记录。

既然出现在行首的标点会导致缩进宽度在视觉上增大,消除这个增量的最简单的办法是,将这一行文本整体向左偏移,直到标点与段落左边界对齐为止,段落首行的左引号算是与段落左边界近似齐平。

以左引号开头的文本行整体向左偏移了 0.35 倍字符宽度

但是,将一行文本向做移动,肯定会导致文本的右侧出现空缺,这在上面的图里可以看得出来。有一些拆东墙补西墙的意味。此时,由于段落分行工作已经结束,因此没有办法再从下一行里取字符去填补这一行右侧的微小空缺;即使有办法,也会产生连锁反应,从而波及整个段落再度重新分行,这样会让问题变得非常复杂,甚至无解。

需要换个角度去思考。一行文本向左移动而引起的右侧空缺,是因为最右侧的字符被「坑」了。民主的做法是,这一行文本里的每个字符都应该承担一点空缺。可以想象为,将这行文本最右侧的空缺打碎,将碎片插入到这一行文本的每个字符的后面,从而使得该行文本整体向右有所膨胀。由于每个字符后面所分担的空缺碎片很小,足以欺骗我的眼睛。

下面给出一个具有一般性的例子以及完整的试验代码:

\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 里的代码是不需要的。


[1] zhfonts:ConTeXt MkIV 中文支持的 Hacking


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

毛线的 · 10月10日

从排版的角度,为何不做成一种特殊的标点压缩,使得在任意的标点模式(全角、半角、开明等)下,特定标点(如左半开标点类)出现在行首时,总是压缩。xeCJK 宏包文档的 3.5.2 节,就提供了类似的功能。

回复

0

一个原因是,每个标点的参数可以在 zhfonts 模块的 lua 脚本里改。另一个原因是懒。

garfileo 作者 · 10月10日
载入中...