Logo Public

ConTeXt

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