What is ConTeXt

时常见到有人问“什么是 ConTeXt”,“ConTeXt 和 LaTeX 是 什么关系”,还有“我学过 LaTeX 了,还要学 ConTeXt 么”。我的理解是,LaTeX 是用来写文章的,ConTeXt 是用来排版的。LaTeX 有一套非常稳重的默认风格, 写论文很方便。而且平常能见到的 LaTeX 文档大多都是使用默认风格或者只做 很少的修改写成的。ConTeXt 的默认风格很垃圾,使用时需要改很多东西才能有 比较好的效果。但是修改 ConTeXt 的选项非常方便,它有一套统一的界面用来 修改所有的设置。LaTeX 则需要使用很多宏包,各个宏包的界面都不一样。

关于 ConTeXt 的文档

ConTeXt 的官方 reference manual 已经很旧了(因为 ConTeXt 更新很快), 现在 Hans (也就是发明 ConTeXt 的人)等人正在重写,但是进度貌似比较慢。 如果你需要查询 ConTeXt 的某个选项,最好的方法是去它的官方 wiki,或者搜索它的邮件列表历史。当然下 载一份旧版的 reference manual 备用也是不错的。另外 ConTeXt 有很多非常 强悍而诡异的功能(比如 fake text,MathML…​)被记录在一些零星的小文档 或者幻灯片中,这些文档可以在 Hans 公司的网 站上找到。除了可以获取信息以外,它们都排版的非常漂亮,很有参考价值。

在阅读 ConTeXt 的文档时,经常可以看到两个词: MKIIMKIV。这是 ConTeXt 的两个版本。MKII 是使用 PDFTeX 或 XeTeX 作为编译器的版本,已经 过时了;MKIV 是现在一般使用的版本,使用 LuaTeX 作为编译器。

使用 ConTeXt

关于发行版的选择

和 LaTeX 一样,ConTeXt 有很多发行版。比如 TeXLive 里就带了 MKII 和 MKIV。但是由于 ConTeXt 更新很快,一般推荐使用 ConTeXt Minimals。安装过 程请见这里

ConTeXt 的设置界面

ConTeXt 有一个统一的设置界面:

\setupblabla[switch1, switch2, ...]...[var1=value1, var2=value2, ...]

如果是要定义一个新的东西,就是

\defineblabla[name][switch1, switch2, ...]...[var1=value1, var2=value2, ...]

ConTeXt 鼓励用户定义自己的样式。比如默认的列表是这样的

\startitemize
\item Pink Floyd
\item Muse
\stopitemize

但是真正使用时一般都会定义自己的列表类型

\defineitemgroup[mylist][levels=1]

实际上默认的 itemize 列表只是预定义的一种 itemgroup。这时我们就可以设 置 mylist 这种列表

\setupitemgroup[mylist][1][n, packed]

然后在文档中我们就可以这样使用 mylist:

\startmylist
\item Pink Floyd
\item Muse
\stopmylist

常用选项和环境

ConTeXt 的宏和环境浩如烟海,浩到官方文档都写不全…​ 这里只讲解一些曾经 让我困惑良久,现在还继续让我困惑的。

Text 环境

Text 环境就像 LaTeX 中的 document 环境,所有会在文档中显示出来的东西都 被包含在 \starttext\stoptext 中。而设置一般都放在 \starttext 之前。

页面设置

ConTeXt 使用 \setuppapersize 设置页面大小

\setuppapersize[A4][A4]

当两个选项的尺寸不一样时,可以实现把很多页印在一张大纸上,方便印刷。另 外 ConTeXt 还提供一个 S 系列尺寸,宽高比是 4:3,用来排幻灯片,一般的 幻灯片都用 =S6=。在每个尺寸之后还可以再加一个选项,详情请见 Wiki

在 ConTeXt 中,一个页面从上到下共有七个尺寸,分别是 topspace, header, headerdistance, height, footerdistance, footer, 和 bottomspace。其中 topspace 是纸张上沿到 header 上沿的距离, header 是 header 的高度,headerdistance 是 header 下沿到正文上沿的 距离, height 是 header 上沿到footer 下沿的距离。Footer 版本以此类推。 从左到右也有类似的七个尺寸,分别是 leftedge, leftmargin, leftmargindistance, width, rightmargindistance, rightmargin, rightedge,注意 width 是正文的宽度。另外再加一个 backspace,是纸张 内沿(就是靠近书脊的那个边缘)到正文内沿的距离。如果你看看 Wiki 中的 Layout 条目,就会发现其实还有很 多很多的尺寸,比如与 topspace 类似的还有 topdistancetop,具 体区别貌似与 oversize print 等印刷问题有关。

我一般对尺寸没有很严格的要求,所以设置竖直方向的尺寸时,我一般设置 height 为 fit,(表示根据其他尺寸自动调整,以填满整个页面的高度为准。) 然后调整其他六个尺寸,如果要求再松一些,我就只调 topspacebottomspace 两个尺寸,其他四个尺寸直接使用默认值。设置水平方向时,如果 是单面打印,就设置 widthmiddle,然后只调整 backspace,这样 ConTeXt 就会把正文放在中间,两边到纸张边缘的距离都是 backspace;如果是双面打印, 就设置 backspacewidth,然后根据情况设置 margin 的参数。所以我的单面 文档一般就是这样:

\setuppapersize[letter][letter]
\setuplayout[backspace=2.5cm, width=middle,
             height=fit, topspace=2cm, bottomspace=3cm]

注意 2.5cm 的 backspace 是比较小的,排有长公式的文档比较方便,一般的文 档可以设为 3.5cm 或 4cm。

如果是排双面文档,会稍微复杂些

\setuplayout[
    location=doublesided,        % This line seems to be optional.
    backspace=.111\paperwidth,
    width=.6666\paperwidth,
    height=.8176\paperheight,
    leftmargin=0.06\paperwidth,
    rightmargin=.15\paperwidth,
    topspace=.0555\paperheight,
    header=.0555\paperheight,
    footer=.0555\paperheight,
    headerdistance=0pt,
    footerdistance=0.04\paperheight,
]

这个设置使用了经典的 Van de Graaf canon。排双面文档时还要注意两点:

  • 与一般人的想象不同(好吧,应该说与我一开始的想象不同…​),双面文 档的正文是靠里的,内侧的距离比较小

  • 要想让双面排版生效(就是出那种奇数页与偶数页对称的效果),还要设 置一下 pagenumbering

\setuppagenumbering[alternative=doublesided]

在调整页面设置的时候,我们可以使用 ConTeXt 的一大特色—— visual debugging。这个功能博大精深,这里讲解在调整页面时比较有用的几个宏。在 使用 visual debugging 之前,先要包含它的模块 m-visual

\usemodule[m-visual]

第一个宏是 \showframe,把这个宏放在 \starttext 之前的任意位置,在编译 后页面的各个部分的边框就会清晰地显示在文档里。第二个是 \showlayout,这 个宏在 \showframe 的基础上在文档的最前面列出与页面设置有关的所有变量的 值,包括前面说过的 topspace, backspace 等等。最后是传说中的 fake text。 很多时候我们需要在开始写文档之前就定好页面设置。这时的文档就是白纸一张, 即使用 \showframe 看上去也比较别扭,我们需要填充一些文字来预览效果。当 然,我们可以到网上随机粘贴一段文字过来,但是 ConTeXt 的方法是随机生成 一些黑方块来假装单词。比如

\fakewords{100}{200}

的作用是产生一段黑方块假装单词,单词的个数在 100 到 200 之间。Fake text 这个功能被记录在 Faking Text and More 这个文档里。ConTeXt 除了能假装文字之外还能假装图表和公式😑;…​

列表

ConTeXt 里的列表样式非常多,所以还是用一个例子来说明。我在写作业时希望 整个文档是一个大列表,每个大题是一个 \item,其中可以有多个小题,每个大 题中的小题是一个子列表。另外大题的题号是阿拉伯数字,放在左 margin 里, 小题的题号是小写字母,就放在正文左边。

首先定义我们自己的 item group,名叫 problem,可以有两层:

\defineitemgroup[problem][levels=2]

设置大题的样式

\setupitemgroup[problem][1][n, inmargin]
\setupitemgroup[problem][1][stopper=., inbetween={\blank[3em]}]

那个 [1] 表示设置第一层的样式, n 表示使用阿拉伯数字, stopper 是显示在题号后面的东西,最后在大题之间加入 3em 的空白。注意在 ConTeXt 里switches 和 variables 的设置貌似不能写在一起,也就是说,上面两行*不能*合成这样的一行:

\setupitemgroup[problem][1][n, inmargin, stopper=., inbetween={\blank[3em]}]

接下来设置小题的样式

\setupitemgroup[problem][2][a, standard]
\setupitemgroup[problem][2][left=(, right=), stopper=]

第一行的 a 表示使用小写字母作为题号。第二行在题号的两边加小括号。

最后我们可以这样使用新定义的列表:

\startproblem
\item This is problem 1.
  \startproblem
  \item This is question 1(a).  Since $2$ is defined as the next
        number of $1$, it is trivial that $1 + 1 = 2$.

\item This is question 1(b).  Since $1 + 1 = 2$, it is trivial that
      $1 + 1 \neq 3$.
\stopproblem

\item This is problem 2, which is easy.
\stopproblem

注意我们第二次使用 \startproblem 的时候,列表自动进入第二层。ConTeXt 列表的详细用法请看官方文档和 Wiki

字体

ConTeXt 中的字体设置是生命中不能承受之复杂,同时也是生命中不能承受之强 大。在新版的官方文档中至少会有两章完整的描述字体机制。在 ConTeXt 中,常 用的(仅仅是常用的而已…​)字体有三个 styles: serif, sans serif 和 teletype (monospaced);每个 style 里有多个 alternatives,比如 roman, italic, bold, slanted 和 smallcap。注意除了 slanted 一般就是 roman 变一下形以 外,其他 alternatives 都是单独设计的。除此之外,有些字体(比如自带的 Computer Modern 和 Latin Modern)在每个 alternatives 里还包含不同的尺 寸,每种尺寸的形状略有差别。

字体的详细说明请见新版官方文档中的 typography 章和 font 章。

字体切换

ConTeXt 使用 Plain-TeX 形式的宏来切换字体。比如三个 styles 对应的宏是 \rm, \ss\tt;上面提到的五个 alternatives 的宏分别是 \tf, \it, \bf, \sl\sc。这两组宏可以组合起来,比如 \rmit 选择 italic 的 serif 字体,\ssbf 选择 bold 的 sans serif 字体。宏 \rm 比较特殊,一般使用 \rm 就表示既 serif 又 roman。

ConTeXt 还提供一组宏方便的切换字号,以 1.2 为比例,以一个 x 结尾的宏 是小号,以 xx 结尾的宏是超小号,以 a, b, c, d 结尾的宏分别是 大号,巨大号,超巨大号,和生命中不能承受之大号。比如,如果正文的字号是 12pt,那么`\rmx` 是 10pt serif roman,\tta 是 14.4pt monospaced。如 果只想改变字号不想改变 style 和 alternatives,可以使用 t 系列宏: \tx, \txx, \ta …​

Font features

一个字体文件中往往有很多附加的字符,比如 fi 的连写,比例数(比如 ½)等 等,这些东西称为 features。ConTeXt 可以通过定义 feature sets 选择性的使 用这些 features,比如可以定一个 feature set 叫 fancy

\definefontfeature
[fancy]
[language=dflt, script=latn, pnum=yes, mode=node,
 onum=yes, kern=yes, liga=yes, dlig=no, zero=no,
 protrusion=quality, expansion=quality]

其中几个比较重要的 features 是

  • mode: 在 MKIV 中一般就设为 node.

  • pnum: 一般来说一个字体中的数字的宽度都是一样的,打开 pnum 以后宽 度就会不一样,比如 1 可能就要比 0 窄一点。

  • onum: 小写数字,又称老式数字,就是数字都不一样高的那种。

  • kern: Kerning,打开就是了。

  • liga: 普通的连写。fi, fl 这些。

  • tlig: TeX 连写,不知道和 liga 有什么区别。

  • trep: TeX 的引号支持,就是用 \`` 和 ’'` 来排版双引号的那个。

  • smcp: Smallcap

另外 protrusionexpansion 是 ConTeXt 中的两个微排版算法,具体用法请 看官方文档。

ConTeXt 预定义了三个 feature sets,分别是

  • default: liga + kern + tlig + trep

  • smallcaps: default + smcp

  • oldstyle: default + onum

字体定义

在 ConTeXt 中可以使用 \definefontsynonym 来给一个字体定义一个别名。这 个宏有三个参数,第一个是要定义的别名,第二个是字体的名字,第三个选项列 表。比如

\definefontsynonym[Serif]   [Palatino][features=default]
\definefontsynonym[SmallCap][Palatino][features=smallcaps]

定义了两个别名: SerifSmallCap。这两个名字都指向一个叫做 Palatino 的字体,但是 SmallCap 使用了 smallcaps feature set。所以使 用 Serif 这个名字的时候出来的普通的 Palatino,使用 SmallCap 这个名 字的时候出来的就是 smallcap 版的 Palatino。注意这个宏的第二个参数也可以 是一个别名。

MKIV 的编译器 LuaTeX 可以使用操作系统中安装的 TrueType 和 OpenType 字 体文件,这时 \definefontsynonym 的第二个参数可以以 name:file: 开头来使用这些字体文件。 name: 后接的是字体的名字去掉空格, 字体的名字可以通过 mtxrun --script fonts 来查看,具体用法请看 Wiki 中的 Font in LuaTeX 条目。 file: 后接的是字体的文件名去掉扩展名。

ConTeXt 中还可以使用 \definefont 来定义某种字体的宏。比如

\definefont[FancyFont][Serif]

定义了一个宏 \FancyFont 用来使用一个名(或别名)叫 Serif 的字体。第二 个参数中也可以使用 TeX 标准的 atscaled 来引用某个尺寸的字体。比如

\definefont[FancyFont][Serif at 72pt]

详情请见新版官方文档的 fonts 章。

前面提到字体可以有 n 多种 styles 和 m 多种 alternatives。要定义全部这些 字体需要行字体定义。为了方便字体定义的重用,ConTeXt 提供一个typescript 机制来组织字体定义。一个 typescript 定义可以包含多个 \definefontsynonym\definefont。当一个定义好的 typescript 被引用的时 候,里面的字体定义才会生效。这样就可以在一个文件里定义很多个 typescripts,然后在写文档的时候包含这个文件,简单的引用几个 typescripts,就可以完成多个字体的定义。比如

\starttypescript[myFonts]
  \definefontsynonym[Serif][Palatino]
  \definefontsynonym[Sans][Optima]
\stoptypescript

然后使用这个 typescripts

\usetypescript[myFonts]

就可以完成 SerifSans 两个字体的定义。

实际使用时,我们不会使用 \definefont 来定义字体,而是使用 \definetypeface。这个宏与 typescript 的关系异常暧昧。简单地说,这个宏的 定义里包含数个 \usetypescript,并且它的第三和第四个参数与 \usetypescript 的第一和第二个参数分别匹配,而 \usetypescript 的参数又与 \starttypescript 的参数匹配。更复杂的字体定义工作还会使用到第五个参数。 比如,现在假定我们定义 Palatino 的 typescript 如下

\starttypescript[serif][myPalatino]
  \definefontsynonym[palarm][name:PalatinoLTStd-Roman]
  \definefontsynonym[palait][name:PalatinoLTStd-Italic]
  \definefontsynonym[palabf][name:PalatinoLTStd-Bold]
  \definefontsynonym[palabi][name:PalatinoLTStd-BoldItalic]
  \definefontsynonym[Serif]          [palarm][features=fancy]
  \definefontsynonym[SerifItalic]    [palait][features=fancy]
  \definefontsynonym[SerifBold]      [palabf][features=fancy]
  \definefontsynonym[SerifBoldItalic][palabi][features=fancy]
  \definefontsynonym[SerifCaps]      [Serif] [features=smallcaps]
\stoptypescript

然后把 \definetypeface 包含在另一个 typescript 里

\starttypescript[NiuBiFonts]
  \definetypeface[NiuBiFonts][rm][serif][myPalatino][default]
\stoptypescript

然后把正文字体设定为新定义的 NiuBiFonts

\usetypescript[NiuBiFonts]
\setupbodyfont[NiuBiFonts, 12pt]

注意在使用 NiuBiFonts 这个 typescript 的时候会自动执行那个 \definetypeface,而由于 \definetypeface 内部的 \usetypescript, 它又会使用 myPalatino 这个 typescript,于是就完成了一大坨字体的定义。 另外注意 myPalatino 里面对 Serif, SerifItalic 等一票字体的定义, 最终会自动变成 \rm, \rmit 等等。所有这些暧昧关系的结果就是,我们可 以把所有的字体和常用字体组合都直观地定义在一个文件里,在写文档的时候只 要直接使用一个字体组合,就可以为这个文档定义好所有的字体。比如可以定义 一套 serif + sans serif + mono 组合

\starttypescript[serif][myPalatino]
  \definefontsynonym[palarm][name:PalatinoLTStd-Roman]
  \definefontsynonym[palait][name:PalatinoLTStd-Italic]
  \definefontsynonym[palabf][name:PalatinoLTStd-Bold]
  \definefontsynonym[palabi][name:PalatinoLTStd-BoldItalic]
  \definefontsynonym[Serif]          [palarm][features=fancy]
  \definefontsynonym[SerifItalic]    [palait][features=fancy]
  \definefontsynonym[SerifBold]      [palabf][features=fancy]
  \definefontsynonym[SerifBoldItalic][palabi][features=fancy]
  \definefontsynonym[SerifCaps]      [Serif] [features=smallcaps]
\stoptypescript

\starttypescript[sans][myOptima]
  \definefontsynonym[prosans][name:OptimaLTStd]
  \definefontsynonym[prosansit][name:OptimaLTStd-Italic]
  \definefontsynonym[prosansbf][name:OptimaLTStd-Bold]
  \definefontsynonym[prosansbi][name:OptimaLTStd-BoldItalic]
  \definefontsynonym[Sans][prosans][features=fancy]
  \definefontsynonym[SansItalic][prosansit][features=fancy]
  \definefontsynonym[SansBold][prosansbf][features=fancy]
  \definefontsynonym[SansBoldItalic][prosansbi][features=fancy]
  \definefontsynonym[SansCaps][Sans][features=smallcaps]
\stoptypescript

\starttypescript[mono][myMonaco]
  \definefontsynonym[promono][name:Monaco]
  \definefontsynonym[Mono][promono][features=default]
\stoptypescript

\starttypescript[PalatinoOptima]
  \definetypeface[PalatinoOptima][rm][serif][myPalatino][default]
  \definetypeface[PalatinoOptima][ss][sans][myOptima][default]
  \definetypeface[PalatinoOptima][tt][mono][myMonaco][default][rscale=0.8]
\stoptypescript

在写文档时只需简单地

\usetypescript[PalatinoOptima]
\setupbodyfont[PalatinoOptima, 12pt]

这三种字体就全部定义好了。

一些奇淫技巧

  • 在同一行上放置左对齐和居中的文字

\hbox to \textwidth{\rlap{Left aligned} \hss Center aligned \hss}

或者

\startoverlay
  {\leftaligned{left}} {\midaligned{middle}} % {\rightaligned{right}}
\stopoverlay