美文网首页
MetaFun 04:静物

MetaFun 04:静物

作者: 明夷 | 来源:发表于2021-05-15 09:21 被阅读0次

    蜗牛生活的世界,有许多静物,其中据说有法国的餐厅。蜗牛需要爬行,这些静物同样需要。所有的静物,可以从同一个地方爬出来。在为静物爬行编造故事之前,要对它们有所介绍。

    文字

    第一个静物是文字,确切地说,是 TeX 世界里的文字。

    文字可使用 textext 宏构建,例如

    vardef text (expr s) =
      if picture s:
        s
      else:
        fullsquare scaled 1cm withcolor red
      fi
    enddef;
    
    draw text(textext("来自 \TEX\ 世界的文本"));
    

    在 MetaPost 语言里,vardef 宏用于定义有返回值的宏。上述代码主要目的是为了展现,textext 构造的对象,其类型是 picture

    结果为

    带框的文字

    为文字加一个外框,在 MetaPost 里要比在 ConTeXt 里容易得多,只需先绘制一个外框,再绘制文字即可,例如:

    draw 
      fullsquare xscaled 2cm yscaled 1cm 
      withpen pencircle scaled 4pt withcolor darkgrey;
    draw textext("来自 \TEX\ 世界的文字") withcolor darkred;
    

    结果为

    然而,这样的结果是出于巧合—— fullsquare 构造的封闭路径和 textext 构造的 picture 对象,它们的中心点默认情况下恰好皆为原点。

    貌合神离,不可接受。最好的解决方案是将它们组合为一个 picture 对象,例如

    picture framed_text;
    framed_text := image(
      draw 
        fullsquare xscaled 2cm yscaled 1cm 
        withpen pencircle scaled 4pt withcolor darkgrey;
      draw textext("来自 \TEX\ 世界的文字") withcolor darkred;
    );
    
    draw framed_text;
    

    将文字和外框合成为 picture 对象的好处是,可以对它们进行整体性的旋转和平移变换。例如,

    draw framed_text;
    draw framed_text shifted (-2cm, -1cm);
    draw framed_text rotated 30;
    draw framed_text rotated 75 shifted (2cm, 1cm);
    

    结果为

    包围盒

    为不同的文字制作外框,需要人为设定外框的尺寸,令其能够包含文本,这种事情甚为无趣。

    MetaFun 提供了两个宏,bbwidthbbheight,它们可分别用于计算 pathpicture 对象的包围盒宽度和高度,例如

    picture text;
    text := textext("来自 \TEX\ 世界的文字");
    path frame;
    frame := fullsquare xscaled (bbwidth text) yscaled (bbheight text);
    
    draw frame withpen pencircle scaled 2pt withcolor darkgray;
    draw text withcolor darkred;
    

    结果为

    文字的外框过于紧致,可使用 MetaFun 的 enlarged 宏令其有所扩大:

    path frame;
    frame := fullsquare xscaled (bbwidth text) yscaled (bbheight text);
    frame := frame enlarged (4pt, 4pt);
    

    上述代码里,enlargedframe 的四周向外延伸 4pt 的距离。结果如下

    于是,一个能与自身包含的文字区域如影随形的外框就有办法定义了。

    框文

    带框的文字,我原本是想表达为 framed text,但是忽然觉得,叫框文也不错:

    vardef 框文 expr a =
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文)
            enlarged (4pt, 4pt);
      image(draw 框 withpen pencircle scaled 2pt withcolor darkgray;
            draw 文 withcolor darkred;)
    enddef;
    
    draw 框文("来自 \TEX\ 世界的文字");
    

    局部变量

    框文 宏,很前卫,但是它的定义里的变量,都是全局变量,即使它们在宏内。下面这个实验能够给出证明:

    draw 框文("来自 \TEX\ 世界的文字");
    draw 文 rotated 30 shifted (1cm, 1cm);
    

    结果为

    迄今为止,我定义的所有变量皆为全局变量。过多使用全局变量,程序缺乏发展壮大的可能。

    MetaPost 支持局部变量,只是要多写一些代码:

    vardef 框文 expr a =
      begingroup
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文)
            enlarged (4pt, 4pt);
      image(draw 框 withpen pencircle scaled 2pt withcolor darkgray;
            draw 文 withcolor darkred;)
      endgroup
    enddef;
    

    重新定义的 框文,其中的变量在外部便无法使用了。

    对于 vardef 定义的宏,它内含了 begingroupendgroup,因此上述代码可写为

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文)
            enlarged (4pt, 4pt);
      image(draw 框 withpen pencircle scaled 2pt withcolor darkgray;
            draw 文 withcolor darkred;)
    enddef;
    

    背景色

    现在,对 框文 再增加一个小功能,为框增加背景色:

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文)
            enlarged (4pt, 4pt);
      image(draw 框 withpen pencircle scaled 2pt withcolor darkgray;
            fill 框 withcolor lightgray;
            draw 文 withcolor darkred;)
    enddef;
    

    效果如下图所示

    外观

    有什么理由决定,框文的框是暗灰色的,框的背景是浅灰色的,文字是暗红色的,以及有什么理由决定,框要向四周延伸 4pt 范围,框的粗细为 2pt?

    没有理由。这些在定义一个宏时,都是不可确定的因素,要在具体场景里调用宏的时候方能确定。

    在定义宏时,有两种办法对付这些不可确定的因素,一种是用宏的参数,一种是用全局变量。如果不确定的因素太多,宏就需要很多参数,其中有些参不确定因素在多数情况下也可以用默认的值。出于这一考虑,我觉得,用全局变量控制 框文 里的不确定因素,更为方便。但是,需要试验,看是否可行。

    首先,试着将 enlarged 的参数定义为全局变量:

    numeric 扩充;
    扩充 := 4pt;
    

    然后在 框文 的定义里使用 padding

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文)
            enlarged (扩充, 扩充);
      image(draw 框 withpen pencircle scaled 2pt withcolor darkgray;
            fill 框 withcolor lightgray;
            draw 文 withcolor darkred;)
    enddef;
    

    然后在调用 框文 时,可以修改 padding 的值,从而达到控制框文外观的目的,例如:

    扩充 := 1cm;
    draw 框文("来自 \TEX\ 世界的文字");
    

    结果为

    既然如此成功,那么就为 框文 定义所有的全局变量:

    numeric 扩充, 线粗, 玩笑;
    扩充 := 4pt; 线粗 := 4pt; 玩笑 := 0;
    color 文色, 框色, 背景;
    文色 := black; 框色 := darkgray; 背景 := lightgray;
    

    然后将 框文 的定义修改为:

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 := fullsquare xscaled (bbwidth 文) yscaled (bbheight 文) enlarged (扩充, 扩充);
      if 玩笑 > 0: 框 := 框 randomized 玩笑; fi;
      image(draw 框 withpen pencircle scaled 线粗 withcolor 框色;
            fill 框 withcolor 背景;
            draw 文 withcolor 文色;)
    enddef;
    

    那个叫玩笑的变量,轻易不要用,用了就不严肃了:

    文色 := darkred;
    扩充 := 5mm;
    玩笑 := 扩充;
    draw 框文("来自 \TEX\ 世界的文字");
    

    姓甚名谁

    利用 MetaPost 对变量名称的限定非常之少的特性,可以为全局变量建立命名空间,以防名字这种非常宝贵的资源被很快耗尽。

    我可以将 框文 宏依赖的所有全局变量定义为

    numeric 框文.框.扩充, 框文.框.线粗, 框文.框.玩笑;
    框文.框.扩充 := 4pt; 
    框文.框.线粗 := 4pt; 
    框文.框.玩笑 := 0;
    
    color 框文.文字.颜色, 框文.框.颜色, 框文.框.背景;
    框文.文字.颜色 := black; 
    框文.框.颜色 := darkgray; 
    框文.框.背景 := lightgray;
    

    形如 框文.框.扩充 之类的变量名,不妨读作「框文的框的扩充」。虽然上述代码有些繁琐,但是安全。虽然安全,但是却不可行。因为全局变量名称里的 框文框文 这个宏名存在冲突。我言不虚,名字的确是稀有资源。既然如此,就让全局变量的名字再冗长一些:

    numeric 框文配置.框.扩充, 框文配置.框.线粗, 框文配置.框.玩笑;
    框文配置.框.扩充 := 4pt; 
    框文配置.框.线粗 := 4pt; 
    框文配置.框.玩笑 := 0;
    
    color 框文配置.文字.颜色, 框文配置.框.颜色, 框文配置.框.背景;
    框文配置.文字.颜色 := black; 
    框文配置.框.颜色 := darkgray; 
    框文配置.框.背景 := lightgray;
    

    然后再对 框文 的定义适应性修改:

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 :=
        fullsquare
        xscaled (bbwidth 文) yscaled (bbheight 文)
        enlarged (框文配置.框.扩充 * (1, 1));
      if 框文配置.框.玩笑 > 0: 框 := 框 randomized 框文配置.框.玩笑; fi;
      image(draw 框 withpen pencircle scaled 框文配置.框.线粗 withcolor 框文配置.框.颜色;
            fill 框 withcolor 框文配置.框.背景;
            draw 文 withcolor 框文配置.文字.颜色;)
    enddef;
    

    框形

    有什么理由可以确定,框文的框一定是矩形或正方形呢?

    没有。

    所以,还应该再增加一个全局变量,

    path 框文配置.框形;
    框文配置.框形 := fullsquare;
    

    但是,现在到了重新理解 enlarged 宏的用法的时候了。enlarged 并非是对一个路径对象进行扩张或缩小,它左边的参数可以是任意路径,右边是扩张或缩小的程度,但是它的返回值是一个矩形。所以,

    即使将框形设为圆,

    框文配置.框形 := fullcircle;
    draw 框文("来自 \TEX\ 世界的文字");
    

    得到的结果依然是矩形的框文。所以,需要继续修改 框文 的定义:

    vardef 框文 expr a =
      save 框, 文;
      path 框; picture 文;
      文 = textext(a);
      框 :=
        fullsquare
        xscaled (bbwidth 文) yscaled (bbheight 文)
        enlarged (框文配置.框.扩充 * (1, 1));
      框 := 框文配置.框形 xscaled (bbwidth 框) yscaled (bbheight 框);   % <= 关键之处
      if 框文配置.框.玩笑 > 0: 框 := 框 randomized 框文配置.框.玩笑; fi;
      image(draw 框 withpen pencircle scaled 框文配置.框.线粗 withcolor 框文配置.框.颜色;
            fill 框 withcolor 框文配置.框.背景;
            draw 文 withcolor 框文配置.文字.颜色;)
    enddef;
    

    现在,

    框文配置.框形 := fullcircle;
    框文配置.框.扩充 := 5mm;
    框文配置.文字.颜色 := darkred;
    draw 框文("来自 \TEX\ 世界的文字");
    

    结果就有了一个椭圆形状的框文:

    fullsquarefullcircle 绘制的图形,皆为单位图形,即它们的包围盒长度和宽度皆为 1。在 MetaPost 语言里,不带任何长度单位的数字,例如 1,它们也是有长度的,它们使用的长度单位是 bp(big point)。打印机知道 1bp 的尺度是多大。

    结语

    蜗牛很失望,没有遇到一个叫作法国餐馆的静物,导致它无从展现自己的逃跑速度。

    相关文章

      网友评论

          本文标题:MetaFun 04:静物

          本文链接:https://www.haomeiwen.com/subject/ntsxjltx.html