\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=水印]