我很喜欢卡片世界里的这种很静态的时光机。它只负责提供时光地图之类的信息,至于如何在时光里穿越,取决于我自己。
很多人给这种机器取了很庸俗的名字,诸如待办事项,Todo List 之类。他们似乎并未想到这种机器的真正力量。他们的注意力过多地投放在如何通过这台机器让自己也变成机器。
那台时光机是用 ConTeXt 的列表排版功能实现的,理论上,也可以使用 ConTeXt 的表格排版功能实现。在表达能力上,表格比列表更强大,所以基于表格实现的时光机也就更强大。
这个玩笑,我是很认真开的。
列表时光机的不足
使用 \ok
和 \no
作为列表项符号尚可,但是用于表达事情完成状态,则不够合理,因为这样做,体现的是,我们关心事情的结果大于关心事情本身。
事情是否完成,其状态应在事情的尾部予以标记,而不应在其首部。
倘若将 \ok
和 \no
放到每件事情的尾部,让列表继续发挥它原有的用途,结果是每件事情的进展状态无法取得形式上的一致性,即令它们在页面里位于同一列,而不是参差不齐。虽然更应当关心事情本身,但并不意味着结果不重要。
与列表相比,表格更适合制作时光机。事实上,完全可以将列表视为由两列数据构成的表格,一列数据是列表项符号,另一列数据是列表项的内容。换言之,列表是表格的特例。
我早该想到这一点,但是却刚刚想到。要谢谢时光机的副作用。
表格
ConTeXt 有多种表格,最强大的一种叫 xtable,也叫终极表格。
我胸有成竹地写出了新的 card.tex:
\environment card-env
\starttext
\timestamp{2021 年 05 月 01 日}
\startxtable
\startxrow
\startxcell \stopxcell
\startxcell 成为天才 \stopxcell
\startxcell \no \stopxcell
\stopxrow
\stopxtable
\stoptext
却被 TeX 编译器无情地抨击了,它指责我,
You can't use '\lower' in internal vertical mode
倘若我去掉上述代码里的 \no
,TeX 就不再说什么了,很顺利地生成了 card.pdf。
那就看看 \no
的定义吧……在 card-env.tex 里有
\def\cangjie#1{%
\lower.2ex\hbox{\externalfigure[#1][width=\bodyfontsize]}}
\def\ok{\cangjie{ok}}
\def\no{\cangjie{no}}
大概是 \cangjie
的定义里使用了 \lower
,从而冒犯了 TeX 编译器。但是作为用户,我难道不是有权不知道什么东西叫 vertical mode 么?更何况是 internal vertical mode。TeX 就是如此不讲道理。它觉得我既然使用了它,就应该了解它的一切。然而我又不是 Knuth,我也不需要写《计算机程序设计艺术》。
我太急于推卸自己的责任了。不会,可以看书啊。Knuth 为 TeX 写了《The TeX book》,此书的第 13 章写道:
大意是,假如我随便输入任何一个字符,便可令 TeX 由竖向模式转化为水平模式。
ConTeXt 有一个命令 \strut
,它表示一个没有宽度,但是有着一个字符高度的字符。我试着用它,帮助 TeX 从竖向模式自动切换为水平模式,从而让它不再指责 \no
的定义:
\environment card-env
\starttext
\timestamp{2021 年 05 月 01 日}
\startxtable
\startxrow
\startxcell \stopxcell
\startxcell 成为天才 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\stopxtable
\stoptext
排版结果如下:
这是一行三列的表格。第一列没有内容,第二列是待做之事,第三列是事情的进展状态。结构是合理的,但排版结果,丑陋至极,接下来,要想方设法令其美观。
舒展
首先,令表格横跨页面的横向空间,可通过为各单元格分配合适的宽度予以实现:
\startxtable
\startxrow
\startxcell[width=.0655\textwidth] \stopxcell
\startxcell[width=.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth]\strut\no\stopxcell
\stopxrow
\stopxtable
其中,\textwidth
是正文区域的宽度,一般情况下该宽度与版心宽度 \makeupwidth
相等,只有在正文需要分列排版时,二者不同。至于各个单元格的宽度为何如此分配,暂且不表,但是这些单元格的宽度之和恰好是 \textwdith
。
为了观察表格经上述修改后,是否横跨页面,我给出新的 card.tex:
\environment card-env
\showframe
\starttext
\timestamp{2021 年 05 月 01 日}
我今天做什么?
\startxtable
\startxrow
\startxcell[width=.0655\textwidth] \stopxcell
\startxcell[width=0.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth]\strut\no\stopxcell
\stopxrow
\stopxtable
\stoptext
排版结果如下:
在上述代码里,表格的第一个单元格的宽度能将第二个单元格里的内容向右推到恰好与正文段落首行缩进的位置对齐。换言之,.0655\textwidth
这个宽度参数,是我肉眼观察的结果。
再加一行
表格单元格的宽度只需要设置第一行,为了证明这一点,可在 card.tex 的表格里再加入一行,令 card.tex 变为:
\environment card-env
\showframe
\starttext
\timestamp{2021 年 05 月 01 日}
我今天做什么?
\startxtable
\startxrow
\startxcell[width=.0655\textwidth] \stopxcell
\startxcell[width=0.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth]\strut\no\stopxcell
\stopxrow
\startxrow
\startxcell \stopxcell
\startxcell 成为圣人 \stopxcell
\startxcell\strut\no\stopxcell
\stopxrow
\stopxtable
\stoptext
排版结果如下:
用同样的方式,可向表格添加近乎无数行。
隐藏边框
现在,表格已经有些像一个更强大的列表了,但是有两个问题需要解决。首先是隐藏表格的边框,其次是在列表的第一列单元格里放置一个符号,用作列表项符号。
第一个问题,只需要将 xtable 的 frame
参数设为 off
便可解决。例如,
\startxtable[frame=off]
\startxrow
\startxcell[width=.0655\textwidth] \stopxcell
\startxcell[width=0.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth]\strut\no\stopxcell
\stopxrow
\startxrow
\startxcell \stopxcell
\startxcell 成为圣人 \stopxcell
\startxcell\strut\no\stopxcell
\stopxrow
\stopxtable
排版结果为
去掉边框后,暴露出一个新问题:表格与正文的间距太过于紧密了。在解决列表项符号问题之前,先将这个问题解决掉。
在 ConTeXt 里,有一个命令 \blank
,可以在段落、列表、插图、表格等对象之间制造竖向的空白间距。例如在两个段落之间插入与段落行高相同高度的竖向间距:
一个段落
\blank[line]
又一个段落
结果如下图所示:
要插入与 n 行文本的高度相同高度的竖向间距,可使用 \blank[n*line]
。例如,插入 2 行文本高度的竖向间距:
一个段落
\blank[2*line]
又一个段落
排版结果如下:
也可以插入负的竖向间距,例如:
一个段落
\blank[-line]
又一个段落
结果为
\blank
有非常多的参数,详见:
我要在表格前后插入半行文本(halfline)高度的竖向空间,只需:
\blank[halfline]
\startxtable[frame=off]
\startxrow
\startxcell[width=.0655\textwidth] \stopxcell
\startxcell[width=0.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth]\strut\no\stopxcell
\stopxrow
\startxrow
\startxcell \stopxcell
\startxcell 成为圣人 \stopxcell
\startxcell\strut\no\stopxcell
\stopxrow
\stopxtable
\blank[halfline]
经过装饰的表格,现在可与正文里的段落拉开一些距离了,如下图所示:
接下来,解决列表项符号问题。
数学符号
列表项符号可使用数学符号里的 \cdot
表示。在 TeX 里,数学公式是另一套排版语言。TeX 有一套在源文件里插入数学公式的命令,ConTeXt 也有一套,我用 ConTeXt 的。
如果在文字行内嵌入数学公式,可使用 \m
命令。例如,
列表项符号可以使用数学符号里的 \m{\cdot} 表示
排版结果为
结果……并没有出现 \cdot
的真身,而是出现了一个问号,而且 TeX 编译器会抱怨:
unknown math characters: ⋅ (U+022C5) (n=1)
这是因为,我在 card-env.tex 里定义的字体,未能包含数学字体,而我使用的 SourceHanSerifCN-Regular.otf 未包含数学符号。
ConTeXt 提供了许多数学字体。我想使用 xits,于是将 card-env.tex 里的字体设置部分修改为
\definefontfamily[myfont][serif][sourcehanserifcn]
\definefontfamily[myfont][math][xitsmath]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]
增加了数学字体之后,上面失败的例子,其正确的结果应该是
试验成功了,现在,可在表格里使用 \m{\cdot}
模拟列表项符号了,
\blank[halfline]
\startxtable[frame=off]
\startxrow
\startxcell[width=.0655\textwidth] \m{\cdot} \stopxcell
\startxcell[width=0.8845\textwidth] 成为天才 \stopxcell
\startxcell[width=.05\textwidth] \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 成为圣人 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\stopxtable
\blank[halfline]
排版结果如下:
表格时光机的用法
在上述历经艰辛创造的表格时光机的第三列里,用 \ok
表示事情完成,用 \no
表示事情未完成,用 \m{\cdots}
表示事情尚在进行或尚未开始。
下面,对一下最新的 card-env.tex,card.tex 以及 card.pdf 吧!
card-env.tex:
\definepapersize[card][width=85.6mm,height=53.98mm]
\setuppapersize[card]
\setuplayout
[backspace=.1\paperwidth,
width=.8\paperwidth,
topspace=.015\paperheight,
height=.97\paperheight,
leftmargin=.666\backspace,
rightmargin=.666\cutspace,
headerdistance=.025\makeupheight,
footerdistance=.025\makeupheight,
textheight=.95\makeupheight]
\setuppagenumbering[location=]
\setupfootertexts[margin][][\hfill\pagenumber\hfill]
\setuphead[title][align=middle]
\def\timestamp#1{%
\setuptexttexts[margin]
[]
[\hfill{\rotate[rotation=270]{#1}}\hfill]
}
\setupindenting[first,always,2em]
\setupinterlinespace[line=1.5em]
\def\cangjie#1{%
\lower.2ex\hbox{\externalfigure[#1][width=\bodyfontsize]}}
\def\ok{\cangjie{ok}}
\def\no{\cangjie{no}}
\definefontfamily[myfont][serif][sourcehanserifcn]
\definefontfamily[myfont][math][xitsmath]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]
card.tex:
\environment card-env
\starttext
\timestamp{2021 年 05 月 01 日}
\blank[halfline]
\startxtable[frame=off]
\startxrow
\startxcell[width=.0655\textwidth] \m{\cdot} \stopxcell
\startxcell[width=0.8845\textwidth] 晒被子 \stopxcell
\startxcell[width=.05\textwidth] \strut\ok \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 成为天才 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 成为圣人 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 成为狂人 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 继续默默无闻 \stopxcell
\startxcell \strut\ok \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 创造表格时光机 \stopxcell
\startxcell \m{\cdots}\stopxcell
\stopxrow
\stopxtable
\blank[halfline]
\stoptext
card.pdf 如下图所示:
结语
用表格在卡片里创造时光机,需要写很多代码,有些不值。但是,表格对版面的控制力又让我难以割舍。也许 Lua 能化解这个矛盾。
网友评论