安装、配置、使用 mpost 画图

metapost 可以用来绘制专业的图表,我在 如何绘制 TCP/IP 详解卷 2 中的图 这篇博文中描述了我尝试用这个语言来绘制 TCP/IP 详解卷 2 中的一个图的过程。

这是我第一次与 metapost 短兵相接,在学习的过程中着实遇到了一些问题,在这里记录一下。

安装 mpost 命令

执行 apt-get install mpost 来安装 mpost 的解析器。如果你玩过 tex 并且安装过 texlive,那么可以跳过这一步,mpost 实际上也是 texlive 中的一个组件,在安装 texlive 的时候会自动安装。

本地已安装 texlive 的情况

我之前曾经将 texlive 安装到了家目录中,并打包备份到网盘中,这样以后如果需要重新安装 texlive,我只需要从网盘中下载下来然后解压并配置相应的环境变量就行了,这就避免每次重装系统后重装 texlive。

要知道 texlive 是相当庞大的,并且可能存在安装不正确的情况,这意味着最后生成 pdf 的时候可能会失败。我之前就安装过几次,发现很难保证每次安装后都能正常使用。

设定必要的的环境变量

经过检查我本地的 texlive 安装目录,我发现其中是有 mpost 命令的。但是直接执行会报错,man mpost 发现需要设定 TEXMFMAIN 这个环境变量。

manual 中的相关信息摘录如下:

FILES
       plain.mp
              The standard preload file.

       mfplain.mp
              The Metafont-compatible preload file.

       $TEXMFMAIN/metapost/base/*.mp
              The standard MetaPost macros included in the original distribution.

       $TEXMFMAIN/metapost/support/*
              Various tables for handling included tex and troff.

       $TEXMFMAIN/metapost/support/trfonts.map
              Table of corresponding font names for troff and PostScript.

       psfonts.map
              Table of corresponding font names for tex and PostScript.

       $TEXMFMAIN/doc/metapost/*
              The MetaPost manual and tutorial source, also including sample figures

mpost 命令在执行的时候需要加载 *.mp 文件,当编写的 mp 文件需要使用 plain.mp、mfplain.mp 之外的一些扩展 mp 控件时,需要设定 TEXMFMAIN 变量,指定 *.mp 的搜索目录。

在我的系统中,我设定 TEXMFMAIN 为如下路径:

[longyu@debian-10:20:24:35] grub $ echo $TEXMFMAIN
/home/longyu/my_program/texlive/2018/texmf-dist

这个路径指向了本地安装目录中的 texmf-dist 子目录,在 texmf-dist 目录中有 metapost 的子目录,其中就存放了不同的 mp 控件

从哪里开始学习 metapost?

网上搜索了下 metapost,找到了一些链接,找了一会后,我觉得应该在 texlive 安装目录中的 doc 子目录中寻找

经过寻找,我找到了如下文件。

texlive/2018/texmf-dist/doc/metapost/base/mpman.pdf 

这个 mpman.pdf 就是 metapost 语言的使用文档,doc/metapost 目录中还放着 metapost 不同的扩展 mp 控件的使用文档,这些文档算是第一手资料,阅读它们就能够快速的上手 metapost。

mpost 命令执行卡住?

我参考 mpman.pdf 学习时,使用 pdf 中的如下 demo 进行测试:

beginfig(1);
	draw (0,0)..{dir 80}(10,0){up};
endfig

将上述文件保存为 dir.mp 文件,然后执行编译,编译过程记录如下:

[longyu@debian-10:08:05:09] The_METAPOST_LANGUAGE $ mpost dir.mp 
This is MetaPost, version 2.00 (TeX Live 2018) (kpathsea version 6.3.0)
(/home/longyu/my_program/texlive/2018/texmf-dist/metapost/base/mpost.mp
(/home/longyu/my_program/texlive/2018/texmf-dist/metapost/base/plain.mp
Preloading the plain mem file, version 1.005) ) (./dir.mp [1])
*

可以看到程序最后一行输出个 * 后就卡住了,使用 strace 跟踪发现这时候阻塞在一个 read 系统调用上了。

继续研究了下,发现这是正常的行为。mpost 可以同时生成多个图表,当遇到 end 命令的时候它才会退出。

我上面使用的示例中没有在 endfig 下一行添加 end 命令,因此 mpost 还在等待处理来自 stdout 输入的图表,这时候只需要输入 end 然后敲回车就可以了。

如何预览生成的 ps 格式文件

执行 mpost 命令后默认生成 PostScript 格式的文件,这种格式的文件可以通过 evince、ghostview 来预览。也可以修改 mpost 命令默认生成的文件格式,指定生成 png 格式。

这可以通过设定 outputformat 来完成,手册中的相关内容摘录如下:

Output Format MetaPost can generate graphics in three output formats:
• Encapsulated PostScript (EPSF),
• Scalable Vector Graphics (SVG) following version 1.1 of the SVG specification [14] (since
MetaPost version 1.200),
• Portable Network Graphics (PNG), a losslessly compressing bitmap format (since MetaPost
version 1.800).
By default, MetaPost outputs PostScript files—hence the name MetaPost. The output format can
be changed by assigning values "svg" or "png" to the internal string variable outputformat:
outputformat := "svg";

阅读上面的内容,可以发现 metapost 支持 EPSF、SVG、PNG、PostScript 输出格式。如果我们要生成 png 格式的图表,可以在 beginfig 之前设定 outputformat := “png”; 来配置。

label 内容无法显示问题

阅读 mpman.pdf 并编写里面的一些例子,我发现并没有太困难。画点,画曲线,画直线,直到画 label 时,我遇到了问题。

我使用 mpman.pdf 中的如下 demo 学习 label 的功能,编译后能够成功生成 postscript 格式的文件。

beginfig(17);
a=.7in; b=.5in;
z0=(0,0);
z1=-z3=(a,0);
z2=-z4=(0,b);
draw z1..z2..z3..z4..cycle;
draw z1--z0--z2;
label.top("a", .5[z0,z1]);
label.lft("b", .5[z0,z2]);
dotlabel.bot("(0,0)", z0);
endfig;

当我用 evince 去预览的时候,首先看到终端打印如下报警信息:

undefined -21
undefined -21
undefined -21
undefined -21

同时 evince 中也没有显示出图形,正在载入一直在转圈圈,看上去是哪里有问题了。

网上搜索了下也没有搜到相关的内容,捣鼓了一会发现 metapost-examples 这个东东。

编译 metapost-examples 并查看

metapost-examples 中放了一些 metapost 的示例 demo,可以在这个目录中执行 mp2html.pl 这个 perl 脚本来编译并生成一个 html,这样我们就能够浏览网页来查看生成的图表与对应的代码。

这个 perl 脚本在执行的过程中一直报某命令找不到的错误,相关的命令如下:

pnmcrop
ppmtogif

网上搜索了下,debian 中可以执行如下命令进行安装。

sudo apt-get install netpbm

安装成功后再次执行上述 perl 脚本,这次生成了 examples.html 与一大堆的 gif 图片。执行 firefox examples.html 预览网页,可以看到如下内容:

在这里插入图片描述
在网页中代码与生成的表格示例都能看到,方便我们先找类似的表格,然后查看相关的代码。浏览这个网页,我发现它上面使用了 label 的图表能够正常显示,我就研究了下编译过程中生成的文件。

发现有生成一个 example.tex 文件,主要的内容如下:

\documentclass[a4paper]{article}                                                                                                                                             
\usepackage{graphicx}
\begin{document}
\begin{verbatim}
..............
\includegraphics{examples_3.mps}
\begin{verbatim}
beginfig(3)
  pair A[];
  A[0]:=(-1cm, -1cm);
  A[1]:=( 1cm, -1cm);
  A[2]:=( 1cm,  1cm);
  A[3]:=(-1cm,  1cm);
  draw A[0]--A[1]--A[2]--A[3]--cycle;
  draw A[0]--A[2];
  draw A[1]--A[3];
endfig;
\end{verbatim}
\hrulefill
\begin{verbatim}
\end{verbatim}
......................
\end{document}

这个 tex 文件中使用 \includegraphics 命令插入 mpost 编译生成的 xx.mps 文件,我执行 xelatex examples.tex 生成 pdf,发现生成的 pdf 中图表的 label 可以正常显示。

了解到这一点后,我按照上面 tex 文件的写法,也同样调用 \includegraphics 命令插入 xx.mps 文件,然后使用 xlatex 生成 pdf,生成的 pdf 中图表的 label 果然可以正常显示了。

设定 prologues 选项

尽管我使用上面的方式已经能够显示 label 中的内容,但是这样的方式有点复杂。最终通过阅读 《LATEX 入门》这本书中的相关内容,我发现可以通过设定 prologues 选项来输出直接嵌入字体的图形。

在 mp 文件的 beginfig 命令前设定 prologues := 3,然后重新编译生成 ps 文件,这次再用 evince 预览生成的 ps 文件 label 的内容终于能够正常显示了。

使用 metaobj

有了上面的基础,我继续阅读 《LATEX 入门》中 metapost 相关内容,发现可以使用 metaobj 宏包来生成一个树的图形。

input metaobj;

prologues	:= 3;
beginfig(1);
x = new_Tree(new_Box(btex root etex))
(new_Circle(btex child etex),
 new_Circle(btex child etex));
Obj(x).c=origin;
draw_Obj(x);
endfig;
end

注意我添加的 prologues 的配置,不添加这个配置无法直接预览生成的 ps 文件。

生成的图表内容如下所示:
在这里插入图片描述

mpgraph.pdf 中的一个示例

我在阅读 mpman.pdf 的同级目录中的 mpgraph.pdf 时,发现其中的一张图表与我想要生成的图表非常贴近,就拷贝其中的示例代码编译查看。代码内容摘录如下:

vardef cuta(suffix a,b) expr p =
drawarrow p cutbefore bpath.a cutafter bpath.b;
point .5*length p of p
enddef;
vardef self@# expr p =
cuta(@#,@#) @#.c{curl0}..@#.c+p..{curl0}@#.c
enddef;
beginfig(52);
verbatimtex \def\stk#1#2{$\displaystyle{\matrix{#1\cr#2\cr}}$} etex
circleit.aa(btex\strut Start etex); aa.dx=aa.dy;
circleit.bb(btex \stk B{(a|b)^*a} etex);
circleit.cc(btex \stk C{b^*} etex);
circleit.dd(btex \stk D{(a|b)^*ab} etex);
circleit.ee(btex\strut Stop etex); ee.dx=ee.dy;
numeric hsep;
bb.c-aa.c = dd.c-bb.c = ee.c-dd.c = (hsep,0);
cc.c-bb.c = (0,.8hsep);
xpart(ee.e - aa.w) = 3.8in;
drawboxed(aa,bb,cc,dd,ee);
label.ulft(btex$b$etex, cuta(aa,cc) aa.c{dir50}..cc.c);
label.top(btex$b$etex, self.cc(0,30pt));
label.rt(btex$a$etex, cuta(cc,bb) cc.c..bb.c);
label.top(btex$a$etex, cuta(aa,bb) aa.c..bb.c);
label.llft(btex$a$etex, self.bb(-20pt,-35pt));
label.top(btex$b$etex, cuta(bb,dd) bb.c..dd.c);
label.top(btex$b$etex, cuta(dd,ee) dd.c..ee.c);
label.lrt(btex$a$etex, cuta(dd,bb) dd.c..{dir140}bb.c);
label.bot(btex$a$etex, cuta(ee,bb) ee.c..tension1.3 ..{dir115}bb.c);
label.urt(btex$b$etex, cuta(ee,cc) ee.c{(cc.c-ee.c)rotated-15}..cc.c);
endfig;

编译报错,报错信息如下:

>> circleit.aa
! Isolated expression.
<to be read again> 
                   (
l.10 circleit.aa(
                 btex\strut Start etex); aa.dx=aa.dy;

搞了一会,发现这个问题是没有 input 扩展 mp 组件,故在 beginfig 之前添加如下命令:

input rboxes;
input sarith;
input graph;

prologues	:= 3;

重新编译,这次成功了。

生成的图表如下:

在这里插入图片描述

! ! Unable to read mpx file. 的问题

有了上面的基础后,我开始编写生成 TCP/IP 详解卷 2 中一个图的代码,编译时遇到报错 ! ! Unable to read mpx file。

这个报错比较隐晦,不能对应到具体出问题的点,搞了下发现这个报错的根本原因是我在使用诸如 label 与 circleit 这种控件时设置的参数有问题,修改参数后问题得到解决。

一个具体的示例

input rboxes;
input sarith;
input graph;

prologues	:= 3;
defaultscale := 1.2;
defaultfont:="ptmr8r";
  
beginfig(1);
  circleit.a("ethtool");
  circleit.b("ioctl");
  circleit.c("dev_ethtool");
  circleit.d("begin->method");
  circleit.e("switch ethcmd");
  circleit.f("complete method");
  
  a.dx=a.dy;
  b.dx=b.dy;
  c.dx=c.dy;
  d.dx=d.dy;
  e.dx=e.dy;
  f.dx=f.dy;
  a.c - b.c = (0, 1.5cm);
  b.c - c.c = a.c - b.c;
  c.c - d.c = b.c - c.c;
  d.c - e.c = c.c - d.c;
  e.c - f.c = d.c - e.c;

  drawboxed(a, b, c, d, e, f);
  drawarrow a.c -- b.c cutbefore bpath.a cutafter bpath.b;
  drawarrow b.c -- c.c cutbefore bpath.b cutafter bpath.c;
  drawarrow c.c -- d.c cutbefore bpath.c cutafter bpath.d;
  drawarrow d.c -- e.c cutbefore bpath.d cutafter bpath.e;
  drawarrow e.c -- f.c cutbefore bpath.e cutafter bpath.f;
  
endfig
end

生成的图表如下所示:
在这里插入图片描述

总结

本文描述了我在第一次使用 metapost 时遇到的一些问题,描述了从 metapost 一手学习资料的获取到最后初步上手的过程。

在这个过程中遇到了不少的问题,这些问题事后想想关键问题在于我没有好好阅读手册就开始写相对复杂的图表,过早的陷入到细节中。

实际上我应该先大致阅读完 metapost 的参考资料,有了这个基础后再去尝试写复杂的图表。我遇到的那些问题,在 metapost 的参考资料中都有描述,不过我并不是阅读了这个资料解决了问题的,中间走了一些弯路。

在第一步扎扎实实打好基础,这一步是不能跳过的!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值