summaryrefslogtreecommitdiff
path: root/info/context-notes-zh-cn/src/12.tex
blob: 4ea4987933c833a50aac1dc0111bb31817ff2259 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
\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