\chapter{学一点 \METAPOST}

想必你已迫不及待想学习 \METAPOST\ 了。这大概是来自人类上古基因的冲动。人类先学会的是绘画,而后才是文字。只是不要妄图通过这区区一章内容掌握 \METAPOST,因为关于它的全部内容,足够写一本至少三百多页的书籍了。不过,本章内容足以给你打开一扇窗户,让 \METAPOST\ 的优雅气息拂过时常过于严肃的 \ConTeXt\ 世界。

\section{作图环境}

\METAPOST\ 是一种计算机作图语言,与 \TeX\ 一样,皆为宏编程语言。使用 \METAPOST\ 语言编写的代码可被 mpost 程序编译成 PS 格式的图形文件。自 \LuaTeX\ 开始,mpost 的核心功能集成到了 \LuaTeX\ 中,从此以后,在 \TeX\ 环境中使用 \METAPOST\ 语言作图便不需依赖外部程序了。

\ConTeXt\ 为 \METAPOST\ 代码提供了五种环境:

\starttyping
\startMPcode ... \stopMPcode
\startMPpage ... \stopMPpage
\startuseMPgraphic{name} ... \stopuseMPgraphic
\startuniqueMPgraphic{name} ... \stopuniqueMPgraphic
\startreusableMPgraphic{name} ... \stopreusableMPgraphic
\stoptyping

\noindent 第一种环境用于临时作图,生成的图形会被插入到代码所在位置。第二种环境是生成单独的图形文件,以作其他用途。后面三种环境,生成的图形可根据环境的名称作为文章插图随处使用,但它们又有三种不同用途:

\startitemize[nowhite]
\item useMPgraphic:每被使用一次,对应的 \METAPOST\ 代码便会被重新编译一次。
\item uniqueMPgraphic:只要图形所处环境不变,\METAPOST\ 代码只会被编译一次。
\item reusableMPgraphic:无论如何使用,其 \METAPOST\ 只会被编译一次。
\stopitemize

\noindent 大多数情况下,建议选用 uniqueMPgraphic,但若图形中存在一些需要每次使用时都要有所变化的内容,可选用 useMPgraphic。

另外需要注意,在 \ConTeXt\ 中使用 \METAPOST\ 时,通常会使用 \ConTeXt\ 定义的一些 \METAPOST\ 宏,这些宏构成的集合,名曰 \MetaFun。

\section{画一个盒子}

\METAPOST\ 作图语句遵守基本的英文语法,理解起来颇为简单。例如,用粗度为 2 pt 的圆头笔用暗红色绘制一条经过 $(0, 0)$,$(3\,{\rm cm}, 0)$,$(3\,{\rm cm}, 1\,{\rm cm})$,$(0, 1\,{\rm cm})$ 的封闭路径,

\startsample
\startMPcode
pickup pencircle scaled 2pt;
draw (0, 0) -- (3cm, 0) -- (3cm, 1cm)
    -- (0, 1cm) -- cycle withcolor darkred;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 上述代码中,\type{(0, 0) -- ... -- cycle} 构造的是一条封闭路径,可将其保存于路径变量:

\starttyping[option=MP]
path p;
p := (0, 0) -- (3cm, 0) -- (3cm, 1cm) -- (0, 1cm) -- cycle;
pickup pencircle scaled 2pt;
draw p withcolor darkred;
\stoptyping

\noindent 将路径保存在变量中,是为了更便于对路径进行一些运算,例如

\startsample
\startMPcode
path p;
p := (0, 0) -- (3cm, 0)
    -- (3cm, 1cm) -- (0, 1cm) -- cycle;
pickup pencircle scaled 2pt;
draw p withcolor darkred;
draw p shifted (2cm, .5cm) withcolor darkblue;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 路径 \type{p} 被向右平移了 2 cm,继而被向上平移动了 0.5 cm。

还有一种构造矩形路径的方法:先构造一个单位正方形,然后对其缩放。例如

\startsample
\startMPcode
pickup pencircle scaled 2pt;
draw fullsquare xscaled 3cm yscaled 1cm
               withcolor darkred;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\MetaFun\ 宏 \type{randomized} 可用于对路径随机扰动。例如,对一个宽为 3 cm,高为 1cm 的矩形路径以幅度 2mm 的程度予以扰动:

\startsample
\startuseMPgraphic{随机晃动的矩形}
pickup pencircle scaled 2pt;
draw (fullsquare xscaled 3cm yscaled 1cm)
                randomized 2mm withcolor darkred;
\stopuseMPgraphic
\useMPgraphic{随机晃动的矩形}
\stopsample
\simplesample[option=MP]{\getsample}

还记得 overlay 吗?只要将上述 useMPgraphic 环境构造的图形制作为 overlay,便可将其作为 \type{\framed} 的背景,从而可以得到一种外观颇为别致的盒子。

\startsample
\defineoverlay[晃晃][\useMPgraphic{随机晃动的矩形}]
\framed[frame=off,background=晃晃,width=3cm]{光辉岁月}
\stopsample
\simplesample[option=TEX]{\getsample}

在 \ConTeXt\ 为 \METAPOST\ 提供的作图环境里,可分别通过 \type{\overlaywidth} 和 \type{\overlayheight} 获得 overlay 的宽度和高度。在将 overlay 作为 \type{\framed} 的背景时,\type{\framed} 的宽度和高度便是 overlay 的宽度和高度。基于这一特性,便可实现 \METAPOST\ 绘制的图形能够自动适应 \type{\framed} 的宽度和高度的变化。例如

\startsample
\startuseMPgraphic{新的随机晃动的矩形}
path p;
p := fullsquare xscaled \overlaywidth yscaled \overlayheight;
pickup pencircle scaled 2pt;
draw p randomized 2mm withcolor darkred;
\stopuseMPgraphic

\defineoverlay[新的晃晃][\useMPgraphic{新的随机晃动的矩形}]
\framed
 [frame=off,background=新的晃晃]
 {今天只有残留的躯壳,迎接光辉岁月,风雨中抱紧自由。}
\stopsample
\typesample[option=MP]
\midaligned{\getsample}

对于需要重复使用的盒子,为了避免每次重复设置其样式,可以将它定义为专用盒子。例如

\starttyping[option=TEX]
\defineframed[funnybox][frame=off,background=新的晃晃]
\funnybox{今天只有残留的躯壳,迎接光辉岁月,风雨中抱紧自由。}
\stoptyping

\METAPOST\ 可以为一条封闭路径填充颜色。在此需要明确,何为封闭路径。例如

\starttyping[option=MP]
path p, q, r;
p := (0, 0) -- (1, 0) -- (1, 1) -- (0, 0) -- (0, 0);
q := (0, 0) -- (1, 0) -- (1, 1) -- (0, 0) -- cycle;
r := fullsquare;
\stoptyping

\noindent 其中路径 \type{p} 的终点的坐标恰好是其起点,但它并非封闭路径,而路径 \type{q} 和 \type{r} 皆为封闭路径。下面示例,为封闭路径填充颜色:

\startsample
\startMPcode
path p;
p := (fullsquare xscaled 3cm yscaled 1cm) randomized 2mm;
pickup pencircle scaled 2pt;
fill p withcolor darkgray;
draw p withcolor darkred;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 注意,对于封闭路径,应当先填充颜色,再绘制路径,否则所填充的颜色会覆盖一部分路径线条。

\section{颜色}

\METAPOST\ 以含有三个分量的向量表示颜色。向量的三个分量分别表示红色、绿色和蓝色,取值范围为 [0, 1],例如 \type{(0.4, 0.5, 0.6)}。可将颜色保存到 color 类型的变量中,以备绘图中重复使用。例如定义一个值为暗红色的颜色变量:

\starttyping[option=MP]
color foo;
foo := (0.3, 0, 0);
\stoptyping

由于 METAPOST 内部已经定义了用于表示红色的变量 \type{red},因此 \type{foo} 的定义也可写为

\starttyping[option=MP]
color foo;
foo := 0.3 * red;
\stoptyping

\noindent 小于 1 的倍数,可以忽略前缀 0,且可以直接作用于颜色:

\starttyping[option=MP]
foo := .3red;
\stoptyping

使用 \type{transparent} 宏可用于构造带有透明度的颜色值。例如

\startsample
\startMPcode
path p; p := fullsquare scaled 1cm;
color foo; foo := .3red;
pickup pencircle scaled 4pt;
draw p withcolor transparent (1, 0.3, foo);
draw p shifted (.5cm, .5cm) withcolor transparent (1, 0.25, blue);
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent \type{transparent} 的第一个参数表示选用的颜色透明方法,共有 12 种方法可选:

\startitemize[n,packed,columns,four]
\item normal
\item multiply
\item screen
\item overlay
\item softlight
\item hardlight
\item colordodge
\item colorburn
\item darken
\item lighten
\item difference
\item exclusion
\stopitemize
\noindent 第二个参数表示透明度,取值范围 [0, 1],其值越大,透明程度越低。第三个参数为颜色值。需要注意的是,\METAPOST\ 并不支持以 \type{color} 类型的变量保存带透明度的颜色值,而且 \METAPOST\ 里也没有与之对应的变量类型。

\section{文字}

使用 \MetaFun\ 宏 \type{textext} 可在 \METAPOST\ 图形中插入文字,且基于 \METAPOST\ 图形变换命令可对文字进行定位、缩放、旋转。例如

\startsample
\startMPcode
string s; % 字符串类型变量
s = "\color[darkred]{\bf 江山如此多娇}";
draw textext(s);
draw textext(s) shifted (4cm, 0);
draw textext(s) scaled 1.5 shifted (8cm, 0) ;
draw textext(s) scaled 1.5 rotated 45 shifted (12cm, 0);
\stopMPcode
\stopsample
\typesample[option=MP]
\midaligned{\getsample}

也可使用 \type{\thetextext} 宏直接对文字进行定位,从而可省去 \type{shifted} 变换。例如

\startsample
\startMPcode
string s; s = "{\bf 江山如此多娇}";
draw (0, 0) withpen pensquare scaled 11pt withcolor darkred;
draw thetextext(s, (4cm, 0)) withcolor darkred;
\stopMPcode
\stopsample
\typesample[option=MP]
\getsample

\section{方向路径}

\METAPOST\ 宏 \type{drawarrow} 可绘制带箭头的路径。例如

\startsample
\startMPcode
path p; p := (0, 0) -- (4cm, 0) -- (4cm, 2cm) -- (0, 2cm) -- (0, 1cm);
pickup pencircle scaled 2pt;
drawarrow p withcolor darkred;
drawarrow p shifted (6cm, 0) dashed (evenly scaled .5mm) withcolor darkred;
\stopMPcode
\stopsample
\typesample[option=MP]
\midaligned{\getsample}

\noindent 上述代码也给出了虚线路径的画法。

\METAPOST\ 的任何一条路径,从起点到终点可基于取值范围为 [0, 1] 的参数选择该路径上的某一点。基于该功能可实现路径标注。例如选择路径参数 0.5 对应的点,在该点右侧放置 \ConTeXt\ 旋转 90 度的文字:

\startsample
\startMPcode
path p; p := (0, 0) -- (4cm, 0)
            -- (4cm, 2cm) -- (0, 2cm) -- (0, 1cm);
pair pos; pos := point .5 along p;
pickup pencircle scaled 2pt;
drawarrow p withcolor darkred;
draw pos withpen pensquare scaled 4pt
        withcolor darkgreen;
draw thetextext.rt("\rotate[rotation=-90]{路过}",
                  pos shifted (1mm, 0));
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 上述代码中出现了 \type{thetextext} 的后缀形式。除了默认形式,\type{thetextext} 还有 4 种后缀形式,后缀名为 \inframed{\type{.lft}},\inframed{\type{.top}},\inframed{\type{.rt}} 和 \inframed{\type{.bot}},分别表示将文字放在指定位置的左侧、上方、右侧和下方。

\section{画面}

\METAPOST\ 有一种变量类型 \type{picture},可将其用于将一组绘图语句合并为一个图形,然后予以绘制。使用 \METAPOST\ 宏 \type{image} 可构造 \type{picture} 实例。例如

\startsample
\startMPcode
path a; a := fullsquare xscaled 4cm yscaled 1cm;
picture p; p := image(
 fill a withcolor darkgray;
 draw a withpen pencircle scaled 2pt withcolor darkblue;
);
draw p;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

使用 \METAPOST\ 宏 \type{center} 可以获得 \type{picture} 实例的中心坐标,结果可保存于一个 \type{pair} 类型的变量。例如

\startsample
\startMPcode
picture p; p := image(draw textext("密云不雨,自我西郊"););
pair c; c := center p;
draw c withpen pensquare scaled 4pt withcolor darkred;
draw p withcolor darkblue;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

使用 \MetaFun\ 宏 \type{bbwidth} 和 \type{bbheight} 可以获得 \type{picture} 实例的宽度和高度。使用这两个宏,可为任何图形和文字构造边框。例如

\startsample
\startMPcode
picture p; p := image(draw textext("归妹愆期,迟归有时"));
numeric w, h; w := bbwidth(p); h := bbheight(p);
path q; q := fullsquare xscaled w yscaled h;
fill q withcolor darkgray;
draw q withpen pencircle scaled 2pt withcolor darkred;
draw p;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 上述实例为文字构造的边框太紧了,利用现有所学,让它宽松一些并不困难:

\startsample
\startMPcode
picture p; p := image(draw textext("归妹愆期,迟归有时"));
numeric w, h; w := bbwidth(p); h := bbheight(p);
numeric offset; offset := 5mm;
path q;
q := fullsquare xscaled (w + offset)
               yscaled (h + offset)
               shifted center p;
fill q withcolor darkgray;
draw q withpen pencircle scaled 2pt withcolor darkred;
draw p;
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\section[macro]{宏}

定义一个宏,令其接受一个字符串类型的参数,返回一个矩形框,并令文字居于矩形框中心:

\startsample
\startMPcode
vardef framed (expr text, offset) =
 picture p; p := image(draw textext(text));
 numeric w, h; w := bbwidth(p); h := bbheight(p);
 path q;
 q := fullsquare xscaled (w + offset) yscaled (h + offset) shifted center p;
 image(fill q withcolor lightgray;
       draw q withpen pencircle scaled 2pt withcolor darkred;
       draw p;)
enddef;
draw framed("{\bf 亢龙有悔}", 5mm);
\stopMPcode
\stopsample
\simplesample[option=MP]{\getsample}

\METAPOST\ 的 \type{vardef} 用于定义一个有返回值的宏,宏定义的最后一条语句即返回值,该条语句不可以分号作为结尾。\METAPOST\ 还有其他几种宏定义形式,但是对于大多数作图任务而言,\type{vardef} 已足够应付。

\section[sec:flow-chart]{画一幅简单的流程图}

现在,请跟随我敲击键盘的手指,逐步画一幅描述数字求和过程的流程图,希望这次旅程能让你对 \METAPOST\ 的基本语法有一些全面的认识。

首先,构造一个结点,表示数据输入“”

\starttyping[option=MP]
string f; f := "\framed[frame=off,align=center]";
picture a;
a := image(
 % 符号 & 用于拼接两个字符串
 draw textext(f & "{$i\leftarrow 1$\\$s\leftarrow 0$}");
);
\stoptyping

然后,用 \in[macro] 节定义的 \type{framed} 宏构造两个运算过程结点:

\starttyping[option=MP]
numeric offset; offset := 5mm;
picture b; b := framed(f & "{$s\leftarrow s + i$}", offset);
picture c; c := framed(f & "{$i\leftarrow i + 1$}", offset);
\stoptyping

还需要定义一个宏,用它构造菱形的条件判断结点:

\starttyping[option=MP]
vardef diamond (expr text, offset) =
 picture p; p := image(draw textext(text));
 numeric w, h; w := bbwidth(p); h := bbheight(p);
 path q;
 q := fulldiamond xscaled (w + offset) yscaled (h + offset) shifted center p;
 image(fill q withcolor darkgray;
       draw q withpen pencircle scaled 2pt withcolor darkred;
       draw p withcolor white;)
enddef;
picture d; d := diamond(f & "{$i > 100$}", 3 * offset);
\stoptyping

最后,再构造一个结点表示程序输出:

\starttyping[option=MP]
picture e;
e := image(draw textext(f & "{$s$}"););
\stoptyping

保持结点 \type{a} 不动,对 \type{b},\type{c},\type{d} 和 \type{e} 进行定位:

\starttyping[option=MP]
b := b shifted (0, -2cm);
c := c shifted (4cm, -4.5cm);
d := d shifted (0, -4.5cm);
e := e shifted (0, -7cm);
\stoptyping

现在可以画出所有结点了,即

\starttyping[option=MP]
draw a; draw b; draw c; draw d; draw e;
\stoptyping
\midaligned{\externalfigure[12/flowchart-1.pdf][frame=on]}
\blank

现在开始构造连接各结点的路径。首先构造结点 \type{a} 的底部中点到 \type{b} 的顶部中点的路径:

\starttyping[option=MP]
path ab;
ab := .5[llcorner a, lrcorner a] -- .5[ulcorner b, urcorner b];
\stoptyping

\noindent 其中 \type{llcorner} 用于获取路径或画面实例的最小包围盒的左下角顶点坐标。同理,\type{ulcorner},\type{urcorner} 和 \type{lrcorner} 分别获取包围盒的左上角、右上角和右下角顶点坐标。\type{.5[..., ...]} 用于计算两个点连线的中点。

用类似的方法可以构造其他连接各结点的路径:

\starttyping[option=MP]
path bd;
bd := .5[llcorner b, lrcorner b] -- .5[ulcorner d, urcorner d];
path dc;
dc := .5[lrcorner d, urcorner d] -- .5[ulcorner c, llcorner c];
path cb;
cb := .5[ulcorner c, urcorner c] -- (4cm, -2cm) -- .5[urcorner b, lrcorner b];
path de;
de := .5[llcorner d, lrcorner d] -- .5[ulcorner e, urcorner e];
\stoptyping

画出所有路径:

\starttyping[option=MP]
drawarrow ab withcolor darkred; drawarrow bd withcolor darkred;
drawarrow cb withcolor darkred; drawarrow dc withcolor darkred;
drawarrow de withcolor darkred;
\stoptyping
\midaligned{\externalfigure[12/flowchart-2.pdf][frame=on]}
\blank

最后一步,路径标注:

\starttyping[option=MP]
pair no;  no  := point .4 along dc;
pair yes; yes := point .5 along de;
draw thetextext.top("否", no);
draw thetextext.rt("是", yes);
\stoptyping
\midaligned{\externalfigure[12/flowchart-3.pdf][frame=on]}

\section{代码简化}

\in[sec:flow-chart] 节中用于绘制程序流程图的代码存在许多重复。我们可以尝试使用条件、循环,宏等形式对代码进行简化,但我对它们给出的讲解并不会细致,为的正是走马观花,观其大略。

首先,观察宏 \type{framed} 和 \type{diamond} 的定义,发现二者仅有的不同是前者用 \type{fullsquare} 绘制盒子,后者用 \type{fulldiamond}。因此,可以重新定义一个更为灵活的宏,用于制作结点:

\starttyping[option=MP]
vardef make_node(expr text, shape, offset) =
 picture p; p := image(draw textext(text));
 numeric w, h; w := bbwidth(p); h := bbheight(p);
 if path shape:
   path q;
   q := shape xysized (w + offset, h + offset) shifted center p;
   image(fill q withcolor lightgray;
         draw q withpen pencircle scaled 2pt withcolor darkred;
         draw p;)
 else:
   image(draw p;)
 fi
enddef;
\stoptyping

\noindent 由于上述代码使用了 \METAPOST\ 的条件判断语法,以 \type{path shape} 判断 \type{shape} 是否为路径变量,从而使得 \type{make_node} 能构用于构造有无边框和有边框的结点:

\starttyping[option=MP]
numeric offset; offset := 5mm;
string f; f := "\framed[frame=off,align=center]";
picture a, b, c, d, e;
a := make_node(f & "{$i\leftarrow 1$\\$s\leftarrow 0$}", none, 0);
b := make_node(f & "{$s\leftarrow s + i$}", fullsquare, offset);
c := make_node(f & "{$i\leftarrow i + 1$}", fullsquare, offset);
d := make_node(f & "{$i > 100$}", fulldiamond, offset);
e := make_node(f & "{$s$}", none, 0);
\stoptyping

在 \type{vardef} 宏中使用条件语句时需要注意,通常情况下不要条件结束语句 \type{fi} 后面添加分号,否则 \type{vardef} 宏的返回值会带上这个分号。在其他情境下,通常需要在  \type{fi} 加分号。还要注意,我在 \type{make_node} 宏中使用 \type{xysized} 取代了之前的 \type{xscale} 和 \type{yscale},可直接指定路径或画面的尺寸。之所以如此,是因为我们无法确定 \type{make_node} 宏的第 2 个参数对应的路径是否为标准图形。

在绘制结点和路径时,存在重复使用 \type{draw} 语句的情况,例如

\starttyping[option=MP]
drawarrow ab withcolor darkred;
drawarrow bd withcolor darkred;
drawarrow cb withcolor darkred;
drawarrow dc withcolor darkred;
drawarrow de withcolor darkred;
\stoptyping

\noindent 可使用循环语句予以简化:

\starttyping[option=MP]
for i = ab, bd, cb, dc, de:
 draw i withcolor darkred;
endfor;
\stoptyping

为了便于获得路径或画面的包围盒的四边中点,定义以下宏:

\starttyping[option=MP]
vardef left(expr p) = .5[llcorner p, ulcorner p] enddef;
vardef top(expr p) = .5[ulcorner p, urcorner p] enddef;
vardef right(expr p) = .5[lrcorner p, urcorner p] enddef;
vardef bottom(expr p) = .5[llcorner p, lrcorner p] enddef;
\stoptyping

\noindent 以绘制结点 \type{b} 的四边中点为测试用例

\starttyping[option=MP]
for i = left(b), top(b), right(b), bottom(b):
 draw i withpen pensquare scaled 4pt withcolor darkblue;
endfor;
\stoptyping
\midaligned{\externalfigure[12/flowchart-4.pdf]}

基于上述宏,可以更为简洁地构造连接各结点的路径:

\starttyping[option=MP]
path ab; ab := bottom(a) -- top(b);
path bd; bd := bottom(b) -- top(d);
path dc; dc := right(d) -- left(c);
path cb; cb := top(c) -- (xpart center c, ypart center b) -- right(b);
path de; de := bottom(d) -- top(e);
\stoptyping

\noindent \type{center} 是 \METAPOST\ 宏,可用于获取路径或画面的包围盒中心点坐标。\type{xpart} 和 \type{ypart} 也皆为 \METAPOST\ 宏,用于获取点的坐标分量。

路径标注也可以通过定义一个宏予以简化:

\starttyping[option=MP]
vardef tag(expr p, text, pos, loc) =
 if loc = "left": thetextext.lft(text, point pos along p)
 elseif loc = "right": thetextext.rt(text, point pos along p)
 elseif loc = "top": thetextext.top(text, point pos along p)
 elseif loc = "bottom": thetextext.bot(text, point pos along p)
 else thetextext(text, point pos along p)
 fi
enddef;
\stoptyping

\noindent 其用法为

\starttyping[option=MP]
draw tag(dc, "否", .5, "top"); draw tag(de, "是", .5, "right");
\stoptyping


\section{层层叠叠}

\ConTeXt\ 有一个以 overlay 为基础的层(Layer)机制。利用层机制,我们可将 \METAPOST\ 图形绘制在页面上的任何一个位置。在学习层之前,我们需要对 overlay 的认识再加深一些。

\subsection{overlay}

实际上 overlay 环境是一个图形「栈」。通过它,可实现图形(包括文字)的叠加。例如,对于以下 \METAPOST\ 图形:

\startsample
\startuseMPgraphic{一个矩形}
path p; p := fullsquare xscaled 4cm yscaled 1cm;
draw p randomized 3mm
      withcolor transparent (1, .5 randomized .25, red)
      withpen pencircle scaled 2pt;
\stopuseMPgraphic
\startoverlay
 {\useMPgraphic{一个矩形}}
 {\useMPgraphic{一个矩形}}
 {\useMPgraphic{一个矩形}}
\stopoverlay
\stopsample
\simplesample[option=MP]{\getsample}

\noindent 上述代码中,\type{transparent (1, .5 randomized .3, red)} 用于构造在一定程度上不确定透明度的红色,透明度在 [0.2, 0.8] 之间,亦即 \type{randomized} 不仅可作用于路径,也可作用于数字或颜色值。由于上述 \METAPOST\ 图形环境包含着随机内容,将其作为插图,在一个 overlay 中多次使用,每次使用都会产生不同的结果,在 overlay 中它们会被叠加到一起。

之前在为 \type{\framed} 定义 overlay 时,overlay 中只有单个图形,亦即我们并未充分利用 overlay 的特性。以下代码定义的 \type{\framed} 的背景便包含了三个叠加的 overlay:

\startsample
\defineoverlay
 [叠叠]
 [\startoverlay
    {\useMPgraphic{一个矩形}}
    {\useMPgraphic{一个矩形}}
    {\useMPgraphic{一个矩形}}
  \stopoverlay]
\defineframed
 [foo]
 [frame=off,background={叠叠},width=4cm]
\foo{迎接光辉岁月}
\stopsample
\simplesample[option=TEX]{\getsample}

\noindent \type{\framed} 的背景支持多个 overlay 叠加,以下代码与上例等效:

\starttyping[option=TEX]
\defineoverlay[叠叠-1]{\useMPgraphic{一个矩形}}
\defineoverlay[叠叠-2]{\useMPgraphic{一个矩形}}
\defineoverlay[叠叠-3]{\useMPgraphic{一个矩形}}
\defineframed[foo][frame=off,background={叠叠-1,重叠-2,重叠-3}]
\foo{迎接光辉岁月}
\stoptyping

\noindent 这意味着,\type{\framed} 的背景,本质上也是一个 overlay。

基于 overlay,也可为文档设置水印。例如,若在 \type{\starttext} 之前添加以下代码:

\starttyping[option=TEX]
\startuseMPgraphic{square}
path p; p := fullsquare xscaled 4cm yscaled 1cm;
draw p randomized 3mm
      withcolor transparent (1, .5 randomized .3, red)
      withpen pencircle scaled 2pt;
\stopuseMPgraphic
\defineoverlay[watermark][\useMPgraphic{square}]
\setupbackgrounds[page][background=watermark]
\stoptyping
\midaligned{\externalfigure[12/watermark.png][width=.6\textwidth]}

\subsection[layer]{层}

\ConTeXt\ 的层,本质上是一个可作为全页背景的 overlay,可使用绝对坐标或相对坐标对排版元素在页面上定位放置。例如,定义一个层 foo,在三个不同位置分别放置一幅 \METAPOST\ 图形,并将其作用于当前页面:

\starttyping[option=TEX]
\definelayer[foo]
\setlayer[foo][x=0cm,y=0cm]{\useMPgraphic{一个矩形}}
\setlayer[foo][x=6cm,y=1cm]{\useMPgraphic{一个矩形}}
\stoptyping
\starttyping[option=TEX]
\setlayer
 [foo]
 [x=\textwidth,y=2cm,hoffset=-4cm,vhoffset=-.5cm]{\useMPgraphic{一个矩形}}
\flushlayer[foo]
\stoptyping
\definelayer[test]
\setlayer[test][x=0cm,y=0cm]{\useMPgraphic{一个矩形}}
\setlayer[test][x=6cm,y=1cm]{\useMPgraphic{一个矩形}}
\setlayer
 [test]
 [x=\textwidth,y=2cm,hoffset=-4cm,voffset=-3cm]{\useMPgraphic{一个矩形}}
\flushlayer[test]
\noindent 上述代码定义的层 foo,其坐标原点是层被投放的位置,亦即在本行文字的左上角。$x$ 坐标向右递增,$y$ 坐标向下递增。
\definelayer[foo]
\setlayer[foo][x=0cm,y=0cm]{\useMPgraphic{circle}}
\setlayer[foo][preset=rightbottom]{\useMPgraphic{circle}}
\setupbackgrounds[page][background=foo]

如果将层的宽度和高度分别设为页面的宽度和高度,并将其设为页面背景,则坐标原点在页面的左上角,且可使用一些预定义位置投放内容。例如以下代码可在当前页面的左上角和右下角位置各放一个圆圈:

\starttyping[option=TEX]
\startuniqueMPgraphic{circle}
draw fullcircle scaled 2cm withpen pencircle scaled 2pt withcolor darkgreen;
\stopuniqueMPgraphic
\definelayer[foo][width=\paperwidth,height=\paperheight]
\setlayer[foo][x=0cm,y=0cm]{\uniqueMPgraphic{circle}}
\setlayer[foo][preset=rightbottom]{\uniqueMPgraphic{circle}}
\setupbackgrounds[page][background=foo]
\stoptyping
\startuniqueMPgraphic{circle}
draw fullcircle scaled 2cm withpen pencircle scaled 2pt withcolor darkgreen;
\stopuniqueMPgraphic
\definelayer[foo][width=\paperwidth,height=\paperheight]
\setlayer[foo][x=0cm,y=0cm]{\uniqueMPgraphic{circle}}
\setlayer[foo][preset=rightbottom]{\uniqueMPgraphic{circle}}
\setupbackgrounds[page][background=foo]

\noindent 需要注意的是,层被使用一次后,便会被清空,因而将其作为页面背景,仅对当前页有效。

通过 \type{preset} 参数可调整层的坐标原点和坐标方向。在上述代码中,\type{rightbottom} 可将层的坐标原点定位于层的右下角,同时 $x$ 坐标方向变为向左递增,$y$ 坐标方向变为向上递增。

\ConTeXt\ 预定义的 \type{preset} 参数值有

\startitemize[packed,columns,five]
\item lefttop\item righttop\item leftbottom\item rightbottom\item middle\item middletop
\item middlebottom\item middleleft\item middleright\item lefttopleft\item lefttopright
\stopitemize

\noindent 使用这些参数值时,要注意坐标方向。例如,若 \type{preset=middleright},其 $x$ 坐标方向变为向左递增,而 $y$ 坐标方向依然保持向上递增,以下代码可予以验证:

\starttyping[option=TEX]
\definelayer[bar][width=\paperwidth,height=\paperheight]
\setlayer[bar][preset=middleright]{
 \startMPcode
 draw (0, 0) withpen pensquare scaled 12pt withcolor darkred;
 \stopMPcode
}
\stoptyping
\starttyping[option=TEX]
\setlayer[bar][preset=middleright,x=2cm,y=2cm]{
 \startMPcode
 draw (0, 0) withpen pensquare scaled 12pt
             withcolor transparent (1, .3, darkred);
 \stopMPcode
}
\setupbackgrounds[page][background=bar]
\stoptyping

\definelayer[bar][width=\paperwidth,height=\paperheight]
\setlayer[bar][preset=middleright]{
 \startMPcode
 draw (0, 0) withpen pensquare scaled 12pt withcolor darkred;
 \stopMPcode
}
\setlayer[bar][preset=middleright,x=2cm,y=2cm]{
 \startMPcode
 draw (0, 0) withpen pensquare scaled 12pt
             withcolor transparent (1, .3, darkred);
 \stopMPcode
}
\setupbackgrounds[page][background=bar]

\section{小结}

也许你意犹未尽,甚至觉得我语焉不详。切莫怪我,我原本仅仅是介绍如何使用 \METAPOST\ 绘制一个边框受随机扰动的盒子。不过,在大致掌握了本章所述的内容的基础上,关于 \METAPOST\ 更多的知识,特别本章所有你觉得语焉不详之处,皆可阅读它的手册\cite[mpman]以增进认识。此外,\ConTeXt\ 开发者 Hans Hagen 所写的 \MetaFun\ 手册除了讲述 \MetaFun\ 之外也大量介绍了 \METAPOST\ 的基本知识和技巧,该手册在你的 \ConTeXt\ 系统中,使用以下命令查找:

\starttyping
$ mtxrun --script base --search metafun-s.pdf
\stoptyping