\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