《Lua in ConTeXt》07:时光机
今天,我要让卡片变成时光机。时光机的起点是从今天。如此重要的行动,先对一下 card.pdf,card.tex 以及 card-env.tex 吧!
card.pdf:
card.tex:
\environment card-env
\starttext
\title{2021 年 5 月 1 日}
今天要做什么事情呢?是成为天才,成为圣人,还是继续默默无闻?
\stoptext
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]
\definefontfamily[myfont][serif][sourcehanserifcn]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]
不用标题,用时间戳
card.pdf 展现的就是普通的文章的样子。对于一篇文章而言,标题很重要,对于卡片上的文章而言,依然如此,但是倘若用标题去显示众所周知的一个日期,太浪费版面了。毕竟,无论是要成为天才还是圣人,都需要很多空间。
用时间戳取代标题,如何?正文区域右侧的留白区域,不用也是浪费。没什么不可以的,在 card-env.tex 里添加以下内容:
\def\timestamp#1{%
\setuptexttexts[margin]
[]
[\hfill{\rotate[rotation=270]{#1}}\hfill]
}
上述内容,与我们已是故交了。
下面试试这个重新定义的 \timestamp
能不能像标题那样工作,修改 card.tex,令其内容变为
\environment card-env
\starttext
\timestamp{2021 年 5 月 1 日}
今天要做什么事情呢?
成为天才?
成为圣人?
还是继续默默无闻?
\page
\timestamp{2021 年 5 月 2 日}
今天要做什么事情呢?
成为天才?
成为圣人?
还是继续默默无闻?
\stoptext
上述 ConTeXt 源文件里,
今天要做什么事情呢?
成为天才?
成为圣人?
还是继续默默无闻?
\page
TeX 编译器会将其理解为 4 段文字。一段文本之后若存在一个空行,TeX 便认为这段文本是一个段落,那个空行相当于段落的终结标志,即 \par
。因此,上述文本等价于
今天要做什么事情呢?\par
成为天才?\par
成为圣人?\par
还是继续默默无闻?\par
\page
\page
是强制 TeX 对文章进行分页的命令。之所以强制分页,是因为我输入的文字太少了,而 TeX 觉得没必要分页,因此我需要强迫它在 \page
出现的位置进行分页,于是便得到了含有两页内容的 card.pdf,而且时间戳页的确能像我预想的那样工作,即正文内容总是对应着与它相关的一个时间:
buffer
对于制作示例而言,倘若需要重复某些文字,可以使用 ConTeXt 的 Buffer(缓存)命令。例如
\environment card-env
\startbuffer[待做之事]
今天要做什么事情呢?
成为天才?
成为圣人?
还是继续默默无闻?
\stopbuffer
\starttext
\timestamp{2021 年 5 月 1 日}
\getbuffer[待做之事]
\page
\timestamp{2021 年 5 月 2 日}
\getbuffer[待做之事]
\stoptext
排版结果与上一节最后给出的结果相同。
buffer 可消除重复。
多行文本构成的段落
上述示例的段落仅是单行文本。倘若将段落的内容在一行内存放不下,TeX 编译器会自动进行断行处理,从而可得到多行文本构成的段落。
重写 card.tex,令其内容如下图所示:
我之所以给出我正在编辑 TeX 源文件的文本编辑器的截图,是因为网页内容不支持代码的自动断行。
重新生成的 card.pdf 页面如下:
按照中文的排版习惯,段落的首行应当缩进两个汉字的长度,ConTeXt 命令 \setupindenting
可实现这一要求,例如:
\setupindenting[first,always,2em]
可将 em
理解为一个汉字的宽度,因为它表示的是英文字母 M
的宽度,这个字母与同字号的汉字宽度近似。
此外,我还觉得 ConTeXt 默认的段落行间距可能较为适合西文排版,对于中文排版,行间距过小。可使用 \setupinterlinespace
进行调整,例如
\setupinterlinespace[line=1.5em]
将上述设定添加到 card-env.tex。重新让 TeX 根据 card.tex 的内容进行排版,所的结果如下图所示:
这只是个插曲,跟时光机无关。现在需要再次回到时光机的设计中去。
列表
我要创造的时光机,可以用段落表达每一天要做的事情。每件事情可表达为一个段落,然而 ConTeXt 觉得这样的设计,对它有些侮辱。
ConTeXt 说,为什么你不试试用列表(itemize)呢?
\environment card-env
\showframe
\starttext
\timestamp{2021 年 5 月 1 日}
今天要做什么事呢?
\startitemize
\item 成为天才?
\item 成为圣人?
\item 还是继续默默无闻?
\stopitemize
\stoptext
排版结果如下:
列表看上去的确要好一些,因为每一项事情前面有一个符号,令它与普通的段落有所区别。不过,由于使用了 \showframe
,我很快就提出了一个问题:为什么列表里每一项之前的符号不能放在正文区域左边的留白区域里呢?
ConTeXt 说,没什么不能的!
\startitemize[inmargin]
\item 成为天才?
\item 成为圣人?
\item 还是继续默默无闻?
\stopitemize
列表项的符号果然可以放到左侧留白区域:
我想继续刁难 ConTeXt,又提出了一个问题,如果还是像刚才一样,列表项的符号还是在正文区域,但是该如何让列表项的内容与上面段落的首行对齐呢?
ConTeXt 这次有些迟疑,它先试了
\startitemize[2*broad]
\item 成为天才?
\item 成为圣人?
\item 还是继续默默无闻?
\stopitemize
发现列表项内容的缩进比段落首还要深,接着又试了
\startitemize[broad]
\item 成为天才?
\item 成为圣人?
\item 还是继续默默无闻?
\stopitemize
然后对我说,妥了!
我说,我不想用这么小的点作为列表项符号,我想用方块。
ConTeXt 说,用 8!
\startitemize[broad,8]
\item 成为天才?
\item 成为圣人?
\item 还是继续默默无闻?
\stopitemize
看!
我不死心,我想……
ConTeXt 打断了我,你什么都不要想……Talk is cheap,show you the document! 去看 http://pmrb.free.fr/contextref.pdf 的 13.6 节,或
但我依然坚持说了下去,我想要下面的列表效果!
ConTeXt:emmm……需要先懂一些绘画方面的技艺……
鬼画符
我用了我略微有些熟悉的矢量图绘制工具 inkscape,用左手画了两幅图,皆保存为 PDF 文件。一幅图是 ok.pdf,
另一幅图是 no.pdf,
插图
使用 \externalfigure
可将指定的 PDF 文件用作文章的插图。这就是 ConTeXt 在文章里插入图形的经典方法。
将 ok.pdf 和 no.pdf 放在 card.tex 所在目录,然后重写 card.tex:
\environment card-env
\starttext
ok.pdf: \externalfigure[ok.pdf][width=1cm]
no.pdf: \externalfigure[no.pdf][width=1cm]
\stoptext
排版结果如下:
\externalfigure
的第一个参数是图片文件名,倘若图片是 PDF 文件,可不需要扩展名 .pdf
,例如
\externalfigure[no][width=1cm]
\externalfigure
的第 2 个参数是图片的尺寸。在上例中,我只设置了图片的宽度 width
值。ConTeXt 会根据图片原有尺寸的宽高比例,自动计算出图片的高度 height
值。反之,倘若设定的是图片的高度值,ConTeXt 可自动算出图片的宽度值。这两个参数可同时使用,适用于需要让图片宽高比例失真的情况,例如
\externalfigure[no][width=1cm,height=0.25cm]
可得到比例失真结果:
倘若是文章里常规的插图,图片通常需要有序号和标题,为此 ConTeXt 提供了 \placefigure
,用法如下:
对应的排版结果为
上述示例里,\placefigure
的前两个参数可有可无。第一个参数主要用于设定图片的插入位置,其值若为 here
,表示尽量在 \placefigure
出现的位置插入图片。之所以要指定图片插入位置,是因为在 ConTeXt 世界里,插图和表格皆为浮动对象,TeX 的排版算法会自动为它们选择安身立命之处。
\placefigure
的第二个参数是引用。在正文里可使用 \in
获得插图的序号,这在上例里已有体现,无须赘述。至于图片的序号,是 ConTeXt 自动生成,无需人为干涉。
\placefigure
第三个参数是图片的标题。图片标题默认是英文形式,例如,Figure 1 ...
,倘若将其修改为 图 1 ...
,可在 card-env.tex 里添加
\setuplabeltext[en][figure=图\;]
其中,\;
在 TeX 里表示一个略大的空格,亦即在 图
后面放置一个空格,从而让 ConTeXt 为我排出 图 1
这样的结果,而非 图1
。上述设定对应的排版结果如下图所示:
可是我不需要在文章里像上面那样的正经的插图,我只是想用这两张图片作为列表项的符号。
ConTeXt 说,用宏就能做到。
\ok 和 \no
首先,定义 \cangjie
,
\def\cangjie#1{\externalfigure[#1][width=\bodyfontsize]}
这个宏之所以叫 cangjie
,是因为我很仰慕仓颉。将图片的宽度设为 \bodyfontsize
,是希望图片的宽度与文章默认的字号相同。\bodyfontsize
取到的尺寸信息,正是 card-env.tex 里 \setupbodyfont
设定的字体尺寸,默认是 12pt,但是我在 card-env.tex 里的设置是 7pt,所以在本例里,\bodyfontsize
就是 7pt。
然后便可以定义 \ok
和 \no
了,如下:
\def\ok{\cangjie{ok}}
\def\no{\cangjie{no}}
有了上述的宏定义,在 ConTeXt 源文件里,倘若我写了以下内容:
有事相求,\ok\ 还是 \no\ ?
能够得到以下排版结果:
注意,上述示例里,\ok
和 \no
之后之所以跟随 \
和一个空格,是因为 TeX 编译器会自动吞掉宏之后的空格,因此我使用 \
和一个空格可强行在宏之后插入空格,使得 \ok
和 \no
引入的微型插图能够与其相邻的文字有一个空格作为间隙,以增加些许美感。
我觉得效果很好,但是觉得不尽完美,与相邻的文字相比,我创造的图片文字所处的位置有些高了。使用 TeX 的 \lower
命令可将一个盒子下移。例如,
T\kern-.1667em\lower.5ex\hbox{E}\kern-.125em X
这是 Knuth 专门为排版 “TeX” 这个单词而设计的,对应的排版结果为
即使不知道 \hbox
的用法,但是不求甚解地师从 Knuth 便可解决 \ok
和 \no
的位置偏高的问题了,只需对 \cangjie
的定义略作修改:
\def\cangjie#1{%
\lower.2ex\hbox{\externalfigure[#1][width=\bodyfontsize]}}
这一修改产生的效果如下图所示:
自定义的列表项
我兴冲冲地对 ConTeXt 说,造字工作完成了。
ConTeXt 点点头说,你已上道了,接下来只需要用 \sym
……
\startitemize
\sym{\no} 成为天才
\sym{\no} 成为圣人
\sym{\no} 成为狂人
\sym{\ok} 继续默默无闻
\stopitemize
于是,奇迹就出现了……
时光机
最后再对一遍 card.pdf,card.tex 以及 card-env.tex,然后我再讲解如何使用这个时光机。
card.pdf:
card.tex:
\environment card-env
\starttext
\timestamp{2021 年 05 月 01 日}
\startitemize[broad]
\sym{\ok} 晒被子
\sym{\no} 成为圣人
\sym{\no} 成为狂人
\sym{\ok} 继续默默无闻
\item 制造时光机
\stopitemize
\stoptext
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]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]
在时光机里,每一天 \ok
的是已经做完了的事。\no
是未能完成的事。非 \ok
也非 \no
的事,是尚在进行或尚未进行的事。
结语
倘若想回到过去,就看看过去的卡片里有没有未竟之事,将其完成。
倘若想穿越到未来,就为未来规划一些想做的事情。
倘若珍惜现在,就让今天的事情都 \ok
吧!
本来有机会使用 Lua 的,但是篇幅已经很长了。下次吧。