MetaPost (luamplib, gmp) で☃


これは「TeX & LaTeX Advent Calendar 2018」の 3 日目の記事です.2 日目は puripuri2100 さん,4 日目は golden_lucky さんです.

はじめに

LuaTeX-ja に関わっている関係で,私は LaTeX で文書を作る時はほぼすべて LuaLaTeX を使用しています.

その際に図をどうやって作るか,ということはいつも問題になります.(u)pLaTeX + dvipdfmx を主に使っていたときには 初等数学プリント作成マクロ emath を主に使用していました(今でも周囲にはそういう人がちらほらいます).また,今から始めるというのであれば PGF/TikZ(TeX Wiki 内の記事)を使う,というのがメジャーな選択肢となるのでしょう.

さて,LuaTeX には 内部に MetaPost ライブラリが含まれており,さらにそれを TeX から扱うためのパッケージとして luamplib パッケージというものが制作されています1.これを利用しない手はないではないか……ということで,私は専ら MetaPost を図の作成に用いるようになりました.

luamplib パッケージの使い方

基本的な使い方

基本的には \usepackage で読み込み,mplibcode 環境内に MetaPost コードを記述するだけです.点につけるラベルなど,TeX コードを記述したい場合は btex ... etex で囲んだり,textext() を使ったりします.

test.tex
\documentclass{article}
\usepackage{luamplib}
\begin{document}
Hello, MetaPost! \vrule
\begin{mplibcode}
  beginfig(1);
  pickup pencircle scaled 0.25mm;
  draw (0,0)..(0,5mm)..(1cm,5mm)..(5mm,0mm)..(-5mm,0)..cycle;
  dotlabel.lrt(btex $P_1$ etex, (0,0));
  endfig;
\end{mplibcode}
\vrule HOGEHOGE
\end{document}

ラベルに日本語を使いたい場合,LuaTeX-ja パッケージをいつもどおり読み込むだけで問題ありません(LuaTeX-ja 側で何か特別なことをしているのでもありません).

LuaJITTeX 使用時の注意

Lua 5.2/5.3 の代わりに LuaJIT を利用した LuaJITTeX で luamplib パッケージを読み込もうとすると,以下のようなエラーが発生します.これは LuaJITTeX において lfs が定義済みなのになぜか読み込み済みライブラリとしてマークされていないことによるようです.

module 'lfs' not found:
        no field package.preload['lfs']
        [kpse lua searcher] file not found: 'lfs'
        [kpse C searcher] file not found: 'lfs'
stack traceback:
        [C]: in function 'require'
        ...texlive/2018/texmf-dist/tex/luatex/luamplib/luamplib.lua:48: in main chunk
        [C]: in function 'require'
        [\directlua]:1: in main chunk.
l.22 \directlua{require("luamplib")}

?

どうやら luamplib.lua 中の 48 行目を以下のように書き換えれば良いようです:

local lfs   = lfs or require ('lfs') --  元は local lfs   = require ('lfs')

\everymplib, \everyendmplib

mplibcode 環境内に毎回 beginfig ... endfig や,図中で使いたい各種マクロを繰り返し書くのは面倒です.luamplib パッケージでは \everymplib, \everyendmplib を使って,mplibcode 環境の開始・終了に自動で挿入される MetaPost コードを指定できます.例えば,毎回自動で beginfig, endfig を追加させることにし,さらに領域を斜線で塗るマクロ hatch を入れると次のようになります.

test2.tex
\documentclass{article}
\usepackage{luamplib}
\everymplib{beginfig(1);
  def hatch(expr p)=
  begingroup
    save pct, x, y; picture pct[];
    numeric xl, xu, yl, yu;
    pct1 := currentpicture; currentpicture := nullpicture;
    draw p;
    (xl, yl) = llcorner currentpicture; (xu, yu) = urcorner currentpicture;
    currentpicture := nullpicture;
    pickup pencircle scaled 0.125mm;
    for i = xl-xu step 1.5mm until yu-yl:
      draw (xl, i+yl)--(xu, i+yl+(xu-xl));
    endfor;
    clip currentpicture to p;
    pct2 := currentpicture; currentpicture := pct1; draw pct2;
  endgroup;
enddef;
}
\everyendmplib{endfig;}
\begin{document}
\begin{mplibcode}
  path p; p:=(0,0)--(0,5mm)..(1cm,2mm){dir -50}..(5mm,-3cm)--(2cm,0mm)--cycle;
  hatch(p); pickup pencircle scaled 0.25mm; draw p;
\end{mplibcode}
\end{document}

寸法・色の指定

mplibcode環境で描かれる図は,周囲の文字色の影響は受けません(無指定時はいつも黒).mplibcode 環境内で寸法や色を指定するには,それぞれ \mpdim, \mpcolor 命令を使用します.color パッケージや xcolor パッケージを読み込んでいる場合には,\mpcolor の引数には \color で指定可能な色指定をそのまま記述できます.xcolor パッケージを読み込んでいると「現在の色」を . で表現できるので,\mpcolor{.} を活用することで周囲の文字色と同じ色にすることができます.

test3.tex
\documentclass{article}
\usepackage{luamplib}
\usepackage{xcolor}% 自動では読まない
\begin{document}
M\textcolor{red}{xyz%
\begin{mplibcode}
  beginfig(1);
  u:=0.5\mpdim{4ex}+2mm; v:=\mpdim{4em};
  fill (0,0)--(2v,0)--(2v,u)--cycle ; %無指定時は黒
  fill (0,0)--(2v,0)--(2v,.5u)--cycle withcolor \mpcolor{.}; % 周囲の文字色
  fill (0,0)--(1v,0)--(0,1u)--cycle withcolor \mpcolor{red!90!black};
  fill (0,0)--(.8v,0)--(0,.8u)--cycle withcolor \mpcolor{blue!80!black};
  fill (0,0)--(.6v,0)--(0,.6u)--cycle withcolor \mpcolor{green!70!black};
  endfig;
\end{mplibcode}
aa}
\end{document}

なお,\mpcolor で指定した色に対しては withcolor blue+0.5\mpcolor{red!90!black} のように演算を行うことはできません.

本題:scmpsnowman パッケージ

さて,luamplib パッケージの使い方を紹介するだけでは記事としておもしろくありません.日本の(一部の)TeX ユーザには☃(ゆきだるま)が流行しているので,それを使ってなにかやることにしました.

LaTeX 上でゆきだるまを描く手法として,aminophen さんの scsnowman パッケージ(GitHub リポジトリ開発者による紹介記事1紹介記事2)が知られています.このパッケージは TikZ による実装だったので,MetaPost (luamplib) に移植してみました.

ネタで作ったものなので,大元の scsnowman パッケージレポジトリの個人フォーク→その中の一ブランチ→さらに下位フォルダ,というひっそりとした箇所においています.

動作環境

想定する環境は TeX Live 2018 上の LuaLaTeX ですが,(u)pLaTeX/pdfLaTeX/XeLaTeX でも動くかもしれません(後述).以下のパッケージが必要です.

  • luamplib パッケージ(LuaLaTeX 下のみ),gmp パッケージ(その他)
  • xcolor パッケージ
  • keyval パッケージ(本家と同様,オプション引数の解釈のため.LuaTeX-ja を使っているならすでに入っている)
  • etoolbox パッケージ(LuaTeX-ja を使っているならすでに入っている)

基本的な使い方

まず,scmpsnowman-normal.def, scmpsnowman.sty, scmpsym-base.sty を TeX が参照できるディレクトリに配置しておきます.

LaTeX ソース内からは,通常通り \usepackage で読み込み,本家 scsnowman パッケージと同様にゆきだるまを置きたい場所で \scsnowman 命令を実行するだけです.

sample1.tex
\documentclass{ltjsarticle} % LuaLaTeX 文書
\usepackage{scmpsnowman}
\begin{document}
これはゆきだるま\scsnowman です.
\end{document}

下のサンプルのように,本家と同じ書式で各種オプションを指定できるようにしています.\scsnowman 命令以外の動作確認はほとんどしていません.

sample2.tex
\documentclass{ltjsarticle}
\usepackage[svgnames]{xcolor}
\usepackage{scmpsnowman}
\usepackage{mflogo} % MetaPost ロゴ
\scsnowmandefault{hat=Green,arms=Brown,snow=SkyBlue} % デフォルト指定
\begin{document}
\MP\scsnowman[muffler=Red,,adjustbaseline]を描けます.
\end{document}

(2018-12-04 追記)上記 sample1.tex, sample2.tex では LuaTeX-ja のクラスファイルを使いましたが,その必要はありません.例えば以下の例のように.

sample0.tex
\documentclass{minimal}
\usepackage{scmpsnowman}\listfiles
\begin{document}\scsnowman\end{document}
%  *File List*
%  minimal.cls    2001/05/25 Standard LaTeX minimal class
% scmpsnowman.sty    2018-12-02 Snowman variants using MetaPost
% etoolbox.sty    2018/08/19 v2.5f e-TeX tools for LaTeX (JAW)
%   xcolor.sty    2016/05/11 v2.12 LaTeX color extensions (UK)
%    color.cfg    2016/01/02 v1.6 sample color configuration
%   luatex.def    2018/01/08 v1.0l Graphics/color driver for luatex
% scmpsym-base.sty    2018-12-02 Base for emoji variants using MetaPost
% luamplib.sty    2018/09/27 v2.12.5 mplib package for LuaTeX
%   keyval.sty    2014/10/28 v1.15 key=value parser (DPC)
% scmpsnowman-normal.def
% supp-pdf.mkii
%  ***********

なお supp-pdf.mkii は luatex.def から読み込まれるようです.(追記ここまで)

非 LuaLaTeX 環境((u)pLaTeX/pdfLaTeX/XeLaTeX)

LuaLaTeX でない環境では,もちろん luamplib パッケージを使うことはできません.その代わりに,同様の「LaTeX ソース中に MetaPost コードを記述する」機能を提供するパッケージの一つに gmp パッケージがあります.scmpsnowman パッケージは,LuaLaTeX でない環境で読み込まれた場合には自動的に gmp パッケージを読み込みます.

gmp パッケージは,mpost 環境の中に書かれた内容を外部ファイルに書き出し,shell-escape が有効なら(パッケージ側にも shellescape オプションを渡す必要があります),その機能を使って MetaPost を実行する,という方式を取っています.shell-escape が無効の場合は sh sample3+mp.sh などとしてあとから自分で MetaPost 実行(など)を行うシェルスクリプトを自分で実行する必要があります.

ということで,LuaLaTeX でない環境下で scmpsnowman パッケージを使う場合には以下のようなソースを記述することになります.しかし shell-escape を有効にするのはセキュリティ的によろしくないので,非 LuaLaTeX 環境下では MetaPost をわざわざ使う意味は薄いように思います.☃☃☃おとなしく本家 scsnowman パッケージを使いましょう☃☃☃

sample3.tex
%#! ptex2pdf -l -ot '-shell-escape' sample3.tex
\documentclass[dvipdfmx]{minimal}
\usepackage[shellescape]{gmp}
\usepackage{scmpsnowman}
\begin{document}
This is a snowman:
\scsnowman[adjustbaseline,scale=2,hat,muffler=red,arms=blue]
\end{document}

gmp パッケージ下での色の指定

gmp パッケージでは \mpdim 命令で TeX の寸法を指定することができます2が,\mpcolor に対応する命令はありません.そこで,scmpsnowman パッケージ中では,luamplib の実装と似た(?)感じで適当に実装しています.

luamplib の \mpcolor 命令を使うと,withcolor \mpcolor{blue} は最終的には次の形になるようです(しっかり確かめたわけではありませんが):

withcolor 1 withprescript "MPlibOverrideColor=0 0 1 rg 0 0 1 RG"

LuaTeX 内蔵の MetaPost ライブラリでなく,本来の MetaPost ソースで同じような指定を考えてみます.

a.mp
beginfig(1);
draw (0,0)--(1,0) withcolor 1 withprescript "0 0 1 setrgbcolor";
draw (0,0)--(2,0) withcolor 1 withprescript "0 0 1 setrgbcolor";
endfig;
end.

上の a.mp を MetaPost に処理させると,次のような PostScript ファイル a.1 が出来上がります.withprescript による色指定の後に,withcolor 1 による 1 setgray が来てしまい,結果として最初のパスにおける withprescript による色指定は無効になってしまいます.

a.1
%!PS
% (省略)
%%Page: 1 1
0 0 1 setrgbcolor
 1 setgray 0 0.5 dtransform truncate idtransform setlinewidth pop [] 0 setdash
 1 setlinecap 1 setlinejoin 10 setmiterlimit
newpath 0 0 moveto
1 0 lineto stroke
0 0 1 setrgbcolor
newpath 0 0 moveto
2 0 lineto stroke
showpage
%%EOF

そのため,gmp パッケージを使う際には,パスの色指定のところで withoutcolor \mpcolor{blue} のように記述(\mpcolorwithprescript ... に最終的に展開)するようにしています3


  1. もとは ConTeXt 中のコードを plain TeX や LaTeX でも扱えるように変更を加えたものだそうです. 

  2. 実は,gmp パッケージが \mpdim 命令を先に実装し,luamplib パッケージがそれに触発されて \mpdim 命令を追加したという順番です. 

  3. xcolor パッケージの色指定から PostScript 形式の指定への変換では,dvips.def, dvipdfmx.def, xetex.def にある \c@lor@to@ps 命令を使って行っています.しかし pdftex.def にはこの命令がないので,PDF における色指定を PostScript 形式のそれに変換する処理を scmpsym-base.sty 中に適当に作りました.