\chapter[metapost]{\METAPOST}

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

\section{作图环境}

\METAPOST\ 是一种计算机作图语言，与 \TEX\ 一样，皆为宏编程语言。\CONTEXT\ 为 \METAPOST\ 代码提供了五种环境：

\startTEX
\startMPcode ... \stopMPcode
\startMPpage ... \stopMPpage
\startuseMPgraphic{name} ... \stopuseMPgraphic
\startuniqueMPgraphic{name} ... \stopuniqueMPgraphic
\startreusableMPgraphic{name} ... \stopreusableMPgraphic
\stopTEX

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

\index[useMPgraphic]{\type{useMPgraphic} 环境}
\index[uniqueMPgraphic]{\type{uniqueMPgraphic} 环境}
\index[reusableMPgraphic]{\type{reusableMPgraphic} 环境}
\startitemize[packed]
\item \type{useMPgraphic}：每被使用一次，\METAPOST\ 代码便会被重新编译一次。
\item \type{uniqueMPgraphic}：只要图形所处环境不变，\METAPOST\ 代码只会被编译一次。
\item \type{reusableMPgraphic}：无论如何使用，\METAPOST\ 代码只会被编译一次。
\stopitemize

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

在 \CONTEXT\ 中使用 \METAPOST\ 时，通常会使用 \CONTEXT\ 定义的一些 \METAPOST\ 宏，这些宏构成的集合，名曰 \MetaFun。

\section{画一个盒子}

\METAPOST\ 作图语句遵守基本的英文语法，理解起来颇为简单。下例，用粗度为 2 pt 的圆头笔用暗红色绘制一条经过四个点的封闭路径。

\index[MPcode]{\type{MPcode} 环境}
\startexample
\startMPcode
pickup pencircle scaled 2pt;
draw (0, 0) -- (3cm, 0) -- (3cm, 1cm)
     -- (0, 1cm) -- cycle withcolor darkred;
\stopMPcode
\stopexample
\simpleexample[option=MP]{\getexample}

\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 将路径保存在变量中，是为了更便于对路径进行一些运算，例如

\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

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

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

\index[xscaled]{\type{xsclaed}：\METAPOST\ 原语}
\index[yscaled]{\type{ysclaed}：\METAPOST\ 原语}
\startexample
\startMPcode
pickup pencircle scaled 2pt;
draw fullsquare xscaled 3cm yscaled 1cm
                withcolor darkred;
\stopMPcode
\stopexample
\simpleexample[option=MP]{\getexample}

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

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

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

\index[overlay]{overlay}
\startexample
\defineoverlay[晃晃][\useMPgraphic{随机晃动的矩形}]
\framed[frame=off,background=晃晃,width=3cm]{光辉岁月}
\stopexample
\simpleexample[option=TEX]{\getexample}

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

\startexample
\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=新的晃晃]
       {今天只有残留的躯壳，迎接光辉岁月，风雨中抱紧自由。}
\stopexample
\typeexample[option=MP]  
\midaligned{\getexample}

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

\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} 皆为封闭路径。下面示例，为封闭路径填充颜色：

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

\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} 宏可用于构造带有透明度的颜色值。例如

\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

\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\ 图形变换命令可对文字进行定位、缩放、旋转。例如

\index[textext]{\type{textext}：\METAFUN\ 宏}
\startexample
\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
\stopexample
\typeexample[option=MP]
\midaligned{\getexample}

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

\index[thetextext]{\type{thetextext}：\METAFUN\ 宏}
\startexample
\startMPcode
string s; s = "{\bf 江山如此多娇}";
draw (0, 0) withpen pensquare scaled 11pt withcolor darkred;
draw thetextext(s, (4cm, 0)) withcolor darkred;
\stopMPcode
\stopexample
\typeexample[option=MP]
\getexample

\section{方向路径}

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

\startexample
\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
\stopexample
\typeexample[option=MP]
\midaligned{\getexample}

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

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

\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

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

\section{画面}

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

\index[picture]{\type{picture}：\METAPOST\ 类型}
\index[image]{\type{image}：\METAPOST\ 宏}
\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

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

\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

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

\index[center]{\type{center}：\METAFUN\ 宏}
\index[bbwidth-height]{\type{bbwidth}，\type{bbheight}：\METAFUN\ 宏}
\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

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

\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

\section[macro]{宏}

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

\index[vardef]{\type{vardef}：\METAPOST\ 变量宏定义}
\startexample
\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
\stopexample
\simpleexample[option=MP]{\getexample}

\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[11/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[11/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[11/flowchart-3.pdf][frame=on]}

\section{代码简化}

\in[sec:flow-chart] 节的代码存在较多重复。可以用条件、循环，宏等形式予以简化。不过，我对它们给出的讲解并不会细致，为的正是走马观花，观其大略。

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

\index[xysized]{\type{xysized}：\METAFUN\ 宏}
\index[ifelse]{\type{if/else}：\METAPOST\ 条件语法}
\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 可使用循环语句予以简化：

\index[for]{\METAPOST\ \type{for} 循环语法}
\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[11/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\ 宏，用于获取点的坐标分量。

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

\index[ifelse]{\type{if/else}：\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 的认识再加深一些。

overlay，词义是「覆盖物」\index[overlay]{overlay}，你可以将其理解为图层。所谓 overlay，实际上不是它覆盖别的元素，而是让别的元素覆盖它，亦即你可以将一些文字置于 overlay 对象之上，故而 overlay 常作为一些排版元素的背景存在。在 \in[framebg] 节里，已经见过了基于 overlay 构造带圆圈文本的方法，在例子里，用 MetaPost 代码画了一个直径与 overlay 宽度相同的圆，并将其作为 \tex{framed} 的背景。事实上，\type{\framed} 的背景能够支持多个 overlay 的叠加，见下例。

\startexample
\startuseMPgraphic{一个矩形}
path p; p := fullsquare xyscaled (OverlayWidth, OverlayHeight);
% 定义颜色变量，其 r、g、b 三个成分可被随机扰动
color c; c := white randomized (.7, .7, .7);
draw p randomized 5mm
       withcolor transparent (1, .5 randomized .25, c)
       withpen pencircle scaled 2pt;
\stopuseMPgraphic
\defineoverlay[叠叠-1][\useMPgraphic{一个矩形}]
\defineoverlay[叠叠-2][\useMPgraphic{一个矩形}]
\defineoverlay[叠叠-3][\useMPgraphic{一个矩形}]

\defineframed
  [foo][frame=off, background={叠叠-1, 叠叠-2, 叠叠-3}]
\foo{迎接光辉岁月}
\stopexample
\simpleexample[option=MP]{\getexample}

需要注意的是，当 overlay 作为 \tex{framed} 之类的盒子的背景时，盒子的尺寸会传递给 overlay，而 \METAPOST\ 代码里，也可以通过 \METAFUN\ 的内置变量 \type{OverlayWidth} 和 \type{OverlayHeight} 获得 overlay 的宽度和高度，亦即 \CONTEXT\ 里的 MetaPost 代码与 \CONTEXT\ 排版环境存在数据传递机制，该机制使得 \METAPOST\ 图形适配排版元素的尺寸成为可能。

层\index[layer]{layer}本质上是一个可作为全页背景的 overlay，可使用绝对坐标或相对坐标对排版元素在页面上定位放置。下例定义了一个层 foo，并在三个不同位置分别放置一个随机矩形。

\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

\startexample
\definelayer[foo]
\setlayer[foo][x=0cm,y=0cm]{\useMPgraphic{square}}
\setlayer[foo][x=6cm,y=1cm]{\useMPgraphic{square}}
\setlayer[foo][x=\textwidth,y=2cm,hoffset=-4cm,vhoffset=-.5cm]{\useMPgraphic{square}}
\flushlayer[foo]
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\noindent 上述代码定义的层 foo，其坐标原点是层被投放的位置，亦即在本行文字的左上角。$x$ 坐标向右递增，$y$ 坐标向下递增。

如果将层的宽度和高度分别设为页面的宽度和高度，并将其设为页面背景，则坐标原点在页面的左上角，且可使用一些预定义位置投放内容。通过 \type{preset} 参数可调整层的坐标原点和坐标方向。\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{rightbottom} 将层的坐标原点定位于层的右下角，同时 $x$ 坐标方向变为向左递增，$y$ 坐标方向变为向上递增。\type{preset} 的用法见下例。该例在层的右下角画了一个绿色的圆，在层的中右位置画了两个小正方形，然后用 \tex{setupbackgrounds} 命令\cmdindex{setupbackgrounds}将其作为当前页面的背景。

\startexample
% 背景 foo
\startuniqueMPgraphic{circle}
draw fullcircle scaled 2cm withpen pencircle scaled 2pt withcolor darkgreen;
\stopuniqueMPgraphic
\definelayer[foo][width=\paperwidth,height=\paperheight]
\setlayer[foo][preset=rightbottom]{\uniqueMPgraphic{circle}}
% 背景 bar
\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={foo, bar}]
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\subject{结语}

领域专用语言（DSL）最好是某种宏语言，而非模仿那些通用编程语言的机械化语言。\TEX\ 是排版领域的专用语言，而 \MetaPost\ 是绘图领域的专用语言，宏是它们的气质所在。

宏语言编程并不难，反而比大多数正经的编程语言容易很多，只是宏编程很容易出错。有时你只是手误，写错了一个变量的名字或者符号，宏编译器无法准确捕捉到错误的位置。对此，我的建议是，如果你打算定义一个宏，请不要去尝试将这个宏的代码写完，再去测试，而是先写出这个宏的最简单的形式，哪怕它一开始是空的，也应该代入参数测试一下，然后试着让这个宏逐渐生长，在生长过程中不断测试，亦即像照顾一株植物那样定义宏。

关于 \MetaPost\ 更为深入和全面的介绍，可参考其手册\cite[mpman] 以及 Hans Hagon 所写的 \METAFUN\ 手册，后者已在你所安装的 \CONTEXT\ 系统中了，使用以下命令可以找到它。

\starttyping
$ mtxrun --search metafun-p.pdf
\stoptyping

另外，由 Hans Hagon 主持且尚在开发中的 LuaMetaFun 项目可谓是下一代 \METAPOST，关于该项目的一些进展和成果，见

\starttyping
$ mtxrun --search luametafun.pdf
\stoptyping

% 前面的示例导致水印消失，现在恢复。
\setupbackgrounds[page][background=水印]
