The luamplib package
Hans Hagen, Taco Hoekwater, Elie Roux, Philipp Gesang and Kim Dohyun
Current Maintainer: Kim Dohyun
Support: https://github.com/lualatex/luamplib
2024/08/03 v2.34.5
Abstract
Package to have metapost code typeset directly in a document with LuaTEX.

1

Documentation

This package aims at providing a simple way to typeset directly metapost code in adocument with LuaTEX. LuaTEX is built with the Lua mplib library, that runs metapost
code. This package is basically a wrapper for the Lua mplib functions and some TEX
functions to have the output of the mplib functions in the pdf.
Using this package is easy: in Plain, type your metapost code between the macros\mplibcode and \endmplibcode, and in LATEX in the mplibcode environment.
The resulting metapost figures are put in a TEX hbox with dimensions adjusted tothe metapost code.
The code of luamplib is basically from the luatex-mplib.lua and luatex-mplib.tex
files from ConTEXt. They have been adapted to LATEX and Plain by Elie Roux and Philipp
Gesang and new functionalities have been added by Kim Dohyun. The most notablechanges are:• possibility to use btex ... etex to typeset TEX code. textext() is a more versatile
macro equivalent to TEX() from TEX.mp. TEX() is also allowed and is a synonymof textext(). The argument of mplib’s primitive maketext will also be processed by
the same routine.
• possibility to use verbatimtex ... etex, though it’s behavior cannot be the same as
the stand-alone mpost. Of course you cannot include \documentclass, \usepackage
etc. When these TEX commands are found in verbatimtex ... etex, the entire code
will be ignored. The treatment of verbatimtex command has changed a lot sincev2.20: see below § 1.1.
• in the past, the package required PDF mode in order to have some output. Startingwith version 2.7 it works in DVI mode as well, though DVIPDFMx is the only DVI
tool currently supported.
It seems to be convenient to divide the explanations of some more changes and cautions into three parts: TEX, MetaPost, and Lua interfaces.

1

1.1

TEX

When this macro is declared, every metapost figure box will be typeset in horizontal mode, so \centering, \raggedleft etc will have effects. \mplibnoforcehmode,
being default, reverts this setting. (Actually these commands redefine \prependtomplibbox;
you can redefine this command with anything suitable before a box.)\mplibforcehmode\everymplib{...}, \everyendmplib{...}\everymplib and \everyendmplib redefine the lua
table containing metapost code which will be automatically inserted at the beginningand ending of each metapost code chunk.
\everymplib{ beginfig(0); }\everyendmplib{ endfig; }\begin{mplibcode}% beginfig/endfig not neededdraw fullcircle scaled 1cm;\end{mplibcode}

There are (basically) two formats for metapost: plain
and metafun. By default, the plain format is used, but you can set the format to be usedby future figures at any time using \mplibsetformat{<format name>}.
n.b. As metafun is such a complicated format, we cannot support all the functionalities producing special effects provided by metafun. At least, however, transparency(actually opacity), transparency group, and shading (gradient colors) are fully supported,and outlinetext is supported by our own alternative mpliboutlinetext (see below § 1.2).
☞
Among these, transparency is so simple that you can apply it to an object, evenwith the plain format, just by appending withprescript "tr_transparency=<number>" tothe sentence. (0 ≤ <number> ≤ 1)
As for transparency group, the current metafun document § 8.8 is not correct. The
true syntax is:\mplibsetformat{plain|metafun}

draw <picture>|<path> asgroup <string>where <string> should be "" (empty), "isolated", "knockout", or "isolated,knockout". Beware that currently many of the PDF rendering applications, except Adobe Acrobat
Reader, cannot properly render the isolated or knockout effect. Transparency groupis available with plain format as well, with extended functionality. See below § 1.2.
One thing worth mentioning about shading is: when a color expression is given in
string type, it is regarded by luamplib as a color expression of TEX side. For instance,
when withshadecolors("orange", 2/3red) is given, the first color "orange" will be interpreted as a color, xcolor or l3color’s expression.
\mplibnumbersystem{scaled|double|decimal} Users can choose numbersystem option. The
default value is scaled, which can be changed by declaring \mplibnumbersystem{double}or \mplibnumbersystem{decimal}.

Default: disable. When \mplibshowlog{enable}1 is declared, log messages returned by the metapost process will be printed to the .log file.
This is the TEX side interface for luamplib.showlog.
\mplibshowlog{enable|disable}

1 As for user’s setting, enable, true and yes are identical; disable, false and no are identical.

2

By default, \mpliblegacybehavior{enable} is already declared for backward compatibility, in which case TEX code in verbatimtex ...
etex that comes just before beginfig() will be inserted before the following metapost
figure box. In this way, each figure box can be freely moved horizontally or vertically.
Also, a box number can be assigned to a figure box, allowing it to be reused later.
\mpliblegacybehavior{enable|disable}\mplibcodeverbatimtex \moveright 3cm etex; beginfig(0); ... endfig;
verbatimtex \leavevmode etex; beginfig(1); ... endfig;
verbatimtex \leavevmode\lower 1ex etex; beginfig(2); ... endfig;
verbatimtex \endgraf\moveright 1cm etex; beginfig(3); ... endfig;\endmplibcoden.b. \endgraf should be used instead of \par inside verbatimtex ... etex.
On the other hand, TEX code in verbatimtex ... etex between beginfig() and endfig
will be inserted after flushing out the metapost figure. As shown in the example below,
VerbatimTeX() is a synonym of verbatimtex ... etex.
\mplibcode
D := sqrt(2)**7;
beginfig(0);draw fullcircle scaled D;
VerbatimTeX("\gdef\Dia{" & decimal D & "}");
endfig;\endmplibcode
diameter: \Dia bp.

By contrast, when \mpliblegacybehavior{disable} is declared, any verbatimtex ...
etex will be executed, along with btex ... etex, sequentially one by one. So, some TEX
code in verbatimtex ... etex will have effects on following btex ... etex codes.
\begin{mplibcode}
beginfig(0);draw btex ABC etex;
verbatimtex \bfseries etex;
draw btex DEF etex shifted (1cm,0); % bold facedraw btex GHI etex shifted (2cm,0); % bold face
endfig;\end{mplibcode}\mplibtextextlabel{enable|disable} Default: disable. \mplibtextextlabel{enable} enables the labels typeset via textext instead of infont operator. So, label("my text",origin)
thereafter is exactly the same as label(textext("my text"),origin).
n.b. In the background, luamplib redefines infont operator so that the right side ar-gument (the font part) is totally ignored. Therefore the left side arguemnt will be typeset
with the current TEX font. Also take care of char operator in the left side argument, as
this might bring unpermitted characters into TEX.
Default: disable. \mplibcodeinherit{enable} enables
the inheritance of variables, constants, and macros defined by previous metapost code
chunks. On the contrary, \mplibcodeinherit{disable} will make each code chunk being
treated as an independent instance, never affected by previous code chunks.
\mplibcodeinherit{enable|disable}

3

Separate metapost instances luamplib v2.22 has added the support for several
named metapost instances in LATEX mplibcode environment. Plain TEX users also canuse this functionality. The syntax for LATEX is:\begin{mplibcode}[instanceName]% some mp code\end{mplibcode}

The behavior is as follows.
• All the variables and functions are shared only among all the environments belonging to the same instance.
• \mplibcodeinherit only affects environments with no instance name set (since if aname is set, the code is intended to be reused at some point).
• btex ... etex boxes are also shared and do not require \mplibglobaltextext.
• When an instance names is set, respective \currentmpinstancename is set as well.
In parellel with this functionality, we support optional argument of instance name for\everymplib and \everyendmplib, affecting only those mplibcode environments of the same
name. Unnamed \everymplib affects not only those instances with no name, but also
those with name but with no corresponding \everymplib. The syntax is:\everymplib[instanceName]{...}\everyendmplib[instanceName]{...}

Default: disable. Formerly, to inherit btex ... etex
boxes as well as other metapost macros, variables and constants, it was necessary todeclare \mplibglobaltextext{enable} in advance. But from v2.27, this is implicitly enabled when \mplibcodeinherit is enabled. This optional command still remains mostly
for backward compatibility.
\mplibglobaltextext{enable|disable}\mplibcodeinherit{enable}%\mplibglobaltextext{enable}\everymplib{ beginfig(0);} \everyendmplib{ endfig;}\mplibcode
label(btex $\sqrt{2}$ etex, origin);draw fullcircle scaled 20;
picture pic; pic := currentpicture;\endmplibcode\mplibcode
currentpicture := pic scaled 2;\endmplibcode

Default: disable. Users can issue \mplibverbatim{enable},after which the contents of mplibcode environment will be read verbatim. As a result,
except for \mpdim and \mpcolor (see below), all other TEX commands outside of the btex
or verbatimtex ... etex are not expanded and will be fed literally to the mplib library.
\mplibverbatim{enable|disable}

4

Besides other TEX commands, \mpdim is specially allowed in the mplibcode environment. This feature is inpired by gmp package authored by Enrico Gregorio.
Please refer to the manual of gmp package for details.
\mpdim{...}\begin{mplibcode}
beginfig(1)draw origin--(.6\mpdim{\linewidth},0) withpen pencircle scaled 4dashed evenly scaled 4 withcolor \mpcolor{orange};
endfig;\end{mplibcode}\mpcolor[...]{...} With \mpcolor command, color names or expressions of color, xcolorand l3color module/packages can be used in the mplibcode environment (after withcolor
operator). See the example above. The optional [...] denotes the option of xcolor’s\color command. For spot colors, l3color (in PDF/DVI mode), colorspace, spotcolor (inPDF mode) and xespotcolor (in DVI mode) packages are supported as well.
\mpfig … \endmpfig Besides the mplibcode environment (for LATEX) and \mplibcode ...
\endmplibcode (for Plain), we also provide unexpandable TEX macros \mpfig ... \endmpfig
and its starred version \mpfig* ... \endmpfig to save typing toil. The former is roughly

the same as follows:\begin{mplibcode}[@mpfig]
beginfig(0)token list declared by \everymplib[@mpfig]...
token list declared by \everyendmplib[@mpfig]
endfig;\end{mplibcode}and the starred version is roughly the same as follows:\begin{mplibcode}[@mpfig]...
\end{mplibcode}

In these macros \mpliblegacybehavior{disable} is forcibly declared. Again, as both sharethe same instance name, metapost codes are inherited among them. A simple example:\everymplib[@mpfig]{ drawoptions(withcolor .5[red,white]); }\mpfig* input boxes \endmpfig\mpfigcircleit.a(btex Box 1 etex); drawboxed(a);\endmpfig

The instance name (default: @mpfig) can be changed by redefining \mpfiginstancename,after which a new mplib instance will start and code inheritance too will begin anew.
\let\mpfiginstancename\empty will prevent code inheritance if \mplibcodeinherit{true} is
not declared.
About cache files To support btex ... etex in external .mp files, luamplib inspects
the content of each and every .mp file and makes caches if nececcsary, before returning
their paths to LuaTEX’s mplib library. This could waste the compilation time, as most .mp
files do not contain btex ... etex commands. So luamplib provides macros as follows,
so that users can give instructions about files that do not require this functionality.
5• \mplibmakenocache{<filename>[,<filename>,...]}• \mplibcancelnocache{<filename>[,<filename>,...]}
where <filename> is a filename excluding .mp extension. Note that .mp files under$TEXMFMAIN/metapost/base and $TEXMFMAIN/metapost/context/base are already registeredby default.
By default, cache files will be stored in $TEXMFVAR/luamplib_cache or, if it’s not available (mostly not writable), in the directory where output files are saved: to be specific,$TEXMF_OUTPUT_DIRECTORY/luamplib_cache, ./luamplib_cache, $TEXMFOUTPUT/luamplib_cache,and ., in this order. $TEXMF_OUTPUT_DIRECTORY is normally the value of --output-directorycommand-line option.
Users can change this behavior by the command \mplibcachedir{<directory path>},where tilde (~) is interpreted as the user’s home directory (on a windows machine aswell). As backslashes (\) should be escaped by users, it would be easier to use slashes (/)
instead.
About figure box metric Notice that, after each figure is processed, the macro\MPwidth stores the width value of the latest figure; \MPheight, the height value. Incidentally, also note that \MPllx, \MPlly, \MPurx, and \MPury store the bounding box informationof the latest figure without the unit bp.
luamplib.cfg At the end of package loading, luamplib searches luamplib.cfg and, if
found, reads the file in automatically. Frequently used settings such as \everymplib,\mplibforcehmode or \mplibcodeinherit are suitable for going into this file.

1.2 MetaPost
mplibdimen(...), mplibcolor(...) These are metapost interfaces for the TEX commands \mpdim and \mpcolor (see above). For example, mplibdimen("\linewidth") is basically the same as \mpdim{\linewidth}, and mplibcolor("red!50") is basically the same as\mpcolor{red!50}. The difference is that these metapost operators can also be used inexternal .mp files, which cannot have TEX commands outside of the btex or verbatimtex... etex.
mplibtexcolor ..., mplibrgbtexcolor ...

mplibtexcolor, which accepts a string argument, is a metapost operator that converts a TEX color expression to a metapost color
expression, that can be used anywhere color expression is expected as well as after thewithcolor operator. For instance:color col;col := mplibtexcolor "olive!50";

But the result may vary in its color model (gray/rgb/cmyk) according to the given TEX
color. (Spot colors are forced to cmyk model, so this operator is not recommended
for spot colors.) Therefore the example shown above would raise a metapost error:
cmykcolor col; should have been declared. By contrast, mplibrgbtexcolor <string> always
returns rgb model expressions.

6mplibgraphictext is a metapost operator, the effect of which issimilar to that of ConTEXt’s graphictext or our own mpliboutlinetext (see below). However the syntax is somewhat different.
mplibgraphictext ...

mplibgraphictext "Funny"
fakebold 2.3% fontspec option
drawcolor .7blue fillcolor "red!50" % color expressions

fakebold, drawcolor and fillcolor are optional; default values are 2, "black" and "white"

respectively. When the color expressions are given in string type, they are regarded ascolor, xcolor or l3color’s expressions. All from mplibgraphictext to the end of sentence
will compose an anonymous picture, which can be drawn or assigned to a variable.
Incidentally, withdrawcolor and withfillcolor are synonyms of drawcolor and fillcolor,
hopefully to be compatible with graphictext.
n.b. In some cases, mplibgraphictext will produce better results than ConTEXt or eventhan our own mpliboutlinetext, especially when processing complicated TEX code suchas the vertical writing in Chinese or Japanese. However, because the implementation isquite different from others, there are some limitations such that you can’t apply shading(gradient colors) to the text. Again, in DVI mode, unicode-math package is needed formath formula, as we cannot embolden type1 fonts in DVI mode.
From v2.30, we provide a new metapost operator mplibglyph,which returns a metapost picture containing outline paths of a glyph in opentype, truetype or type1 fonts. When a type1 font is specified, metapost primitive glyph will becalled.
mplibglyph ... of ...

mplibglyph 50 of \fontid\font% slot 50 of current fontmplibglyph "Q" of "TU/TeXGyrePagella(0)/m/n/10"% font csname
mplibglyph "Q" of "texgyrepagella-regular.otf"% raw filename
mplibglyph "Q" of "Times.ttc(2)"% subfont number
mplibglyph "Q" of "SourceHanSansK-VF.otf[Regular]" % instance nameBoth arguments before and after of “of” can be either a number or a string. Number
arguments are regarded as a glyph slot (GID) and a font id number, repectively. Stringargument at the left side is regarded as a glyph name in the font or a unicode character.
String argument at the right side is regarded as a TEX font csname (without backslash)or the raw filename of a font. When it is a font filename, a number within parentheses
after the filename denotes a subfont number (starting from zero) of a TTC font; a string
within brackets denotes an instance name of a variable font.
mplibdrawglyph ... The picture returned by mplibglyph will be quite similar to the resultof glyph primitive in its structure. So, metapost’s draw command will fill the inner pathof the picture with the background color. In contrast, mplibdrawglyph <picture> command

fills the paths according to the nonzero winding number rule. As a result, for instance,
the area surrounded by inner path of “O” will remain transparent.
☞
To apply the nonzero winding number rule to a picture containing paths, luamplib
appends withpostscript "collect" to the paths except the last one in the picture. If you
want the even-odd rule instead, you can, with plain format as well, additionally declare
withpostscript "evenodd" to the last path in the picture.

7

From v2.31, a new metapost operator mpliboutlinetext is available, which mimicks metafun’s outlinetext. So the syntax is the same: see the metafun
manual § 8.7 (texdoc metafun). A simple example:
mpliboutlinetext (...)

draw mpliboutlinetext.b ("$\sqrt{2+\alpha}$")(withcolor \mpcolor{red!50})(withpen pencircle scaled .2 withcolor red)scaled 2 ;After the process, mpliboutlinepic[] and mpliboutlinenum will be preserved as global variables; mpliboutlinepic[1] ... mpliboutlinepic[mpliboutlinenum] will be an array of images each of which containing a glyph or a rule.
n.b. As Unicode grapheme cluster is not considered in the array, a unit that must bea single cluster might be separated apart.
\mppattern{...} ... \endmppattern, ... withpattern ... TEX macros \mppattern{<name>}... \endmppattern define a tiling pattern associated with the <name>. metapost operator
withpattern, the syntax being <path> | <textual picture> withpattern <string>, will return
a metapost picture which fills the given path or text with a tiling pattern of the <name>by replicating it horizontally and vertically. The textual picture here means any text
typeset by TEX, mostly the result of the btex command (though technically this is not atrue textual picture) or the infont operator.
An example:\mppattern{mypatt}[
xstep = 10,
ystep = 12,
matrix = {0, 1, -1, 0},]\mpfigdraw (origin--(1,1))
scaled 10
withcolor 1/3[blue,white];
draw (up--right)
scaled 10
withcolor 1/3[red,white];\endmpfig\endmppattern% or \begin{mppattern}{mypatt}% options: see below% or "0 1 -1 0"% or any other TeX code,% or \end{mppattern}\mpfigdraw fullcircle scaled 90
withpostscript "collect";draw fullcircle scaled 200
withpattern "mypatt"
withpen pencircle scaled 1
withcolor \mpcolor{red!50!blue!50}
withpostscript "evenodd";\endmpfig

8

Key
xstep
ystep
xshiftyshiftbbox
matrixresources
colored or coloured

Table 1: options for \mppattern
Value Type
Explanation
numberhorizontal spacing between pattern cells
numbervertical spacing between pattern cells
numberhorizontal shifting of pattern cells
numbervertical shifting of pattern cells
table or string llx, lly, urx, ury values*
table or string xx, yx, xy, yy values* or MP transform code
string
PDF resources if neededbooleanfalse for uncolored pattern. default: true
* in string type, numbers are separated by spaces

The available options are listed in Table 1.
For the sake of convenience, the width and height values of tiling patterns will be
written down into the log file. (depth is always zero.) Users can refer to them for option
setting.
As for matrix option, metapost code such as ‘rotated 30 slanted .2’ is allowed aswell as string or table of four numbers. You can also set xshift and yshift values byusing ‘shifted’ operator. But when xshift or yshift option is explicitly given, they haveprecedence over the effect of ‘shifted’ operator.
When you use special effects such as transparency in a pattern, resources option isneeded: for instance, resources="/ExtGState 1 0 R". However, as luamplib automatically
includes the resources of the current page, this option is not needed in most cases.
Option colored=false (coloured is a synonym of colored) will generate an uncoloredpattern which shall have no color at all. Uncolored pattern will be painted later by thecolor of a metapost object. An example:\begin{mppattern}{pattnocolor}[colored = false,
matrix = "slanted .3 rotated 30",]
\tiny\TeX\end{mppattern}\begin{mplibcode}
beginfig(1)picture tex;
tex = mpliboutlinetext.p ("\bfseries \TeX");
for i=1 upto mpliboutlinenum:
j:=0;
for item within mpliboutlinepic[i]:j:=j+1;draw pathpart item scaled 10
if j < length mpliboutlinepic[i]:withpostscript "collect"else:
withpattern "pattnocolor"
withpen pencircle scaled 1/2
withcolor (i/4)[red,blue]% paints the patternfi;

9endforendforendfig;\end{mplibcode}

A much simpler and efficient way to obtain a similar result (without colorful charactersin this example) is to give a textual picture as the operand of withpattern:\begin{mplibcode}
beginfig(2)picture pic;
pic = mplibgraphictext "\bfseries\TeX"
fakebold 1/2
fillcolor 1/3[red,blue]
drawcolor 2/3[red,blue]
scaled 10 ;draw pic withpattern "pattnocolor" ;endfig;\end{mplibcode}% paints the patternThis is a metapost operator which makes the color of an object
gradiently transparent. The syntax is <path>|<picture> withfademethod <string>, the latterbeing either "linear" or "circular". Though it is similar to the withshademethod from
metafun, the differences are: (1) the operand of withfademethod can be a picture as well
as a path; (2) you cannot make gradient colors, but can only make gradient opacity.
Related macros to control optional values are:... withfademethod ...

withfadeopacity (number, number ) sets the starting opacity and the ending opacity, default value being (1,0). ‘1’ denotes full color; ‘0’ full transparency.
withfadevector (pair, pair ) sets the starting and ending points. Default value in thelinear mode is (llcorner p, lrcorner p), where p is the operand, meaning thatfading starts from the left edge and ends at the right edge. Default value in thecircular mode is (center p, center p), which means centers of both starting andending circles are the center of the bounding box.
withfadecenter is a synonym of withfadevector.
withfaderadius (number, number ) sets the radii of starting and ending circles. This is
no-op in the linear mode. Default value is (0, abs(center p - urcorner p)), mean-

ing that fading starts from the center and ends at the four corners of the boundingbox.
withfadebbox (pair, pair ) sets the bounding box of the fading area, default value being(llcorner p, urcorner p). Though this option is not needed in most cases, there

could be cases when users want to explicitly control the bounding box. Particularly, see the description below on the analogous macro withgroupbbox.
An example:\mpfigpicture mill;
mill = btex \includegraphics[width=100bp]{mill} etex;
draw mill

10withfademethod "circular"
withfadecenter (center mill, center mill)
withfaderadius (20, 50)
withfadeopacity (1, 0);\endmpfig

As said before, transparency group is available with plain as well as
metafun format. The syntax is exactly the same: <picture> | <path> asgroup "" | "isolated"| "knockout" | "isolated,knockout", which will return a metapost picture. It is called
Transparency Group because the objects contained in the group are composited to produce a single object, so that outer transparency effect, if any, will be applied to the groupas a whole, not to the individual objects cumulatively.
The additional feature provided by luamplib is that you can reuse the group as manytimes as you want in the TEX code or in other metapost code chunks, with infinitesimalincrease in the size of PDF file. For this functionality we provide TEX and metapost
macros as follows:... asgroup ...

withgroupname <string> associates a transparency group with the given name. When thisis not appended to the sentense with asgroup operator, the default group name‘lastmplibgroup’ will be used.
\usemplibgroup{...} is a TEX command to reuse a transparency group of the name once

used. Note that the position of the group will be origin-based: in other words,
lower-left corner of the group will be shifted to the origin.

usemplibgroup <string> is a metapost command which will add a transparency group of
the name to the currentpicture. Contrary to the TEX command just mentioned,

the position of the group is the same as the original transparency group.

withgroupbbox (pair ,pair ) sets the bounding box of the transparency group, default
value being (llcorner p, urcorner p). This option might be needed especiallywhen you draw with a thick pen a path that touches the boundary; you would
probably want to append to the sentense ‘withgroupbbox (bot lft llcorner p, top rt
urcorner p)’, supposing that the pen was selected by the pickup command.
An example showing the difference between the TEX and metapost commands:\mpfigdraw image(fill fullcircle scaled 100 shifted 25right withcolor .5[blue,white];
fill fullcircle scaled 100 withcolor .5[red,white] ;) asgroup ""withgroupname "mygroup";
draw (left--right) scaled 10;
draw (up--down) scaled 10;\endmpfig\noindent\clap{\vrule width 20pt height .25pt depth .25pt}%\clap{\vrule width .5pt height 10pt depth 10pt}%\usemplibgroup{mygroup}

11

Key
asgroupbbox
matrixresources

Table 2: options for \mplibgroup
Value Type
Explanation
string"", "isolated", "knockout", or "isolated,knockout"table or string llx, lly, urx, ury values*
table or string xx, yx, xy, yy values* or MP transform code
string
PDF resources if needed* in string type, numbers are separated by spaces\mpfigusemplibgroup "mygroup" rotated 15;
draw (left--right) scaled 10;
draw (up--down) scaled 10;\endmpfig

Also note that normally the reused transparency groups are not affected by outercolor commands. However, if you have made the original transparency group using
withoutcolor command, colors will have effects on the uncolored objects in the group.
These TEX macros are described here in this subsection, as they are deeply related to the asgroup operator. Users can define a transparency group or a normal form XObject with these macros from TEX side. The syntax
is similar to the \mppattern command (see above). An example:\mplibgroup{...} ... \endmplibgroup\mplibgroup{mygrx}% or \begin{mplibgroup}{mygrx}[% options: see below
asgroup="",]\mpfig% or any other TeX code
pickup pencircle scaled 10;
draw (left--right) scaled 30 rotated 45 ;draw (left--right) scaled 30 rotated -45 ;\endmpfig\endmplibgroup% or \end{mplibgroup}\usemplibgroup{mygrx}\mpfigusemplibgroup "mygrx"
scaled 1.5
withprescript "tr_transparency=0.5" ;\endmpfig

Availabe options, much fewer than those for \mppattern, are listed in Table 2. Again,
the width/height/depth values of the mplibgroup will be written down into the log file.
When asgroup option, including empty string, is not given, a normal form XObject
will be generated rather than a transparency group. Thus the individual objects, not the
XObject as a whole, will be affected by outer transparency command.
As shown in the example, you can reuse the mplibgroup once defined using the
TEX command \usemplibgroup or the metapost command usemplibgroup. The behaviorof these commands is the same as that described above.

12

1.3

Lua

Using the primitive runscript <string>, you can run a Lua code chunkfrom metapost side and get some metapost code returned by Lua if you want. As thefunctionality is provided by the mplib library itself, luamplib does not have much to say
about it.
One thing is worth mentioning, however: if you return a Lua table to the metapost
process, it is automatically converted to a relevant metapost value type such as pair,color, cmykcolor or transform. So users can save some extra toil of converting a table
to a string, though it’s not a big deal. For instance, runscript "return {1,0,0}" will give
you the metapost color expression (1,0,0) automatically.
runscript ...

Lua table luamplib.instances Users can access the Lua table containing mplib instances, luamplib.instances, through which metapost variables are also easily accessible
from Lua side, as documented in LuaTEX manual § 11.2.8.4 (texdoc luatex). The following will print false, 3.0, MetaPost and the knots and the cyclicity of the path unitsquare,consecutively.
\begin{mplibcode}[instance1]
boolean b; b = 1 > 2;
numeric n; n = 3;
string s; s = "MetaPost";
path p; p = unitsquare;\end{mplibcode}\directlua{
local instance1 = luamplib.instances.instance1
print( instance1:get_boolean "b" )print( instance1:get_number "n" )print( instance1:get_string "s" )local t = instance1:get_path "p"for k,v in pairs(t) do
print(k, type(v)=='table' and table.concat(v,' ') or v)
end}

Lua function luamplib.process_mplibcode Users can execute a metapost code chunkfrom Lua side by using this function:
luamplib.process_mplibcode (<string> metapost code, <string> instance name)

The second argument cannot be absent, but can be an empty string ("") which meansthat it has no instance name.
Some other elements in the luamplib namespace, listed in Table 3, can have effects
on the process of process_mplibcode.

2

Implementation

2.1

Lua module

1
2 luatexbase.provides_module {13

Table 3: elements in luamplib table (partial)
Type
Related TEX macro
codeinherit
boolean\mplibcodeinherit
everyendmplib
table\everyendmplib
everymplib
table\everymplib
getcachedir
function (<string>) \mplibcachedir
globaltextext
boolean\mplibglobaltextext
legacyverbatimtex boolean\mpliblegacybehavior
noneedtoreplace
table\mplibmakenocache
numbersystem
string\mplibnumbersystem
setformat
function (<string>) \mplibsetformat
showlogboolean\mplibshowlog
textextlabel
boolean\mplibtextextlabel
verbatiminput
boolean\mplibverbatim
Key

name
versiondate
description

3
4
5
6

= "luamplib",= "2.34.5",= "2024/08/03",= "Lua package to typeset Metapost with LuaTeX's MPLib.",

7}
8

Use the luamplib namespace, since mplib is for the metapost library itself. ConTEXt
uses metapost.
9 luamplib
10 local luamplib= luamplib or { }= luamplib

11
12 local format, abs = string.format, math.abs
13

Use our own function for warn/info/err.
14 local function termorlog (target, text, kind)
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

if text thenlocal mod, write, append = "luamplib", texio.write_nl, texio.write
kind = kindor target == "term" and "Warning (more info in the log)"
or target == "log" and "Info"or target == "term and log" and "Warning"or "Error"
target = kind == "Error" and "term and log" or target
local t = text:explode"\n+"write(target, format("Module %s %s:", mod, kind))
if #t == 1 thenappend(target, format(" %s", t[1]))else
for _,line in ipairs(t) dowrite(target, line)
end
write(target, format("(%s)", mod))end
append(target, format(" on input line %s", tex.inputlineno))
write(target, "")14

if kind == "Error" then error() end
end
37 end38 local function warn (...) -- beware '%' symbol
39 termorlog("term and log", select("#",...) > 1 and format(...) or ...)
40 end41 local function info (...)
42 termorlog("log", select("#",...) > 1 and format(...) or ...)
43 end44 local function err (...)
45 termorlog("error", select("#",...) > 1 and format(...) or ...)
46 end35
36

47
48 luamplib.showlog= luamplib.showlog or false

49

This module is a stripped down version of libraries that are used by ConTEXt. Provide
a few “shortcuts” expected by the code.
50 local tableconcat = table.concat
51 local tableinsert = table.insert
52 local tableunpack = table.unpack
53 local texsprint= tex.sprint= tex.gettoks
55 local texgetbox= tex.getbox
56 local texruntoks = tex.runtoks
57 if not texruntoks then58 err("Your LuaTeX version is too old. Please upgrade it to the latest")
59 end60 local is_defined = token.is_defined
61 local get_macro= token.get_macro
62 local mplib = require ('mplib')
63 local kpse = require ('kpse')
64 local lfs= require ('lfs')
65 local lfsattributes = lfs.attributes
66 local lfsisdir= lfs.isdir
67 local lfsmkdir= lfs.mkdir
68 local lfstouch= lfs.touch
69 local ioopen= io.open
54 local texgettoks

70

Some helper functions, prepared for the case when l-file etc is not loaded.
71 local file = file or { }72 local replacesuffix = file.replacesuffix or function(filename, suffix)
73

return (filename:gsub("%.[%a%d]+$","")) .. "." .. suffix

74 end75 local is_writable = file.is_writable or function(name)

if lfsisdir(name) thenname = name .. "/_luam_plib_temp_file_"
78
local fh = ioopen(name,"w")
79
if fh then80
fh:close(); os.remove(name)81
return true
82
end
83 end84 end76
77

15

85 local mk_full_path = lfs.mkdirp or lfs.mkdirs or function(path)

local full = ""for sub in path:gmatch("(/*[^\\/]+)") do88
full = full .. sub89
lfsmkdir(full)
90 end91 end86
87

92

btex ... etex in input .mp files will be replaced in finder. Because of the limitation of
mplib regarding make_text, we might have to make cache files modified from input files.
93 local luamplibtime = lfsattributes(kpse.find_file"luamplib.lua", "modification")
94 local currenttime = os.time()
95 local outputdir, cachedir
96 if lfstouch thenfor i,v in ipairs{'TEXMFVAR','TEXMF_OUTPUT_DIRECTORY','.','TEXMFOUTPUT'} dolocal var = i == 3 and v or kpse.var_value(v)99
if var and var ~= "" then100
for _,vv in next, var:explode(os.type == "unix" and ":" or ";") do
101
local dir = format("%s/%s",vv,"luamplib_cache")102
if not lfsisdir(dir) then103
mk_full_path(dir)
104
end
105
if is_writable(dir) then106
outputdir = dir
107
break
108
end
109
end
110
if outputdir then break end
111
end
112 end
113 end
114 outputdir = outputdir or '.'115 function luamplib.getcachedir(dir)116 dir = dir:gsub("##","#")
117 dir = dir:gsub("^~",
118
os.type == "windows" and os.getenv("UserProfile") or os.getenv("HOME"))119 if lfstouch and dir then120
if lfsisdir(dir) then121
if is_writable(dir) then122
cachedir = dir
123
else
124
warn("Directory '%s' is not writable!", dir)
125
end
126
else
127
warn("Directory '%s' does not exist!", dir)
128
end
129 end
130 end97
98

Some basic metapost files not necessary to make cache files.
131 local noneedtoreplace = {
132
133
134

["boxes.mp"] = true, -- ["format.mp"] = true,["graph.mp"] = true, ["marith.mp"] = true, ["mfplain.mp"] = true,["mpost.mp"] = true, ["plain.mp"] = true, ["rboxes.mp"] = true,16

["sarith.mp"] = true, ["string.mp"] = true, -- ["TEX.mp"] = true,["metafun.mp"] = true, ["metafun.mpiv"] = true, ["mp-abck.mpiv"] = true,["mp-apos.mpiv"] = true, ["mp-asnc.mpiv"] = true, ["mp-bare.mpiv"] = true,["mp-base.mpiv"] = true, ["mp-blob.mpiv"] = true, ["mp-butt.mpiv"] = true,["mp-char.mpiv"] = true, ["mp-chem.mpiv"] = true, ["mp-core.mpiv"] = true,["mp-crop.mpiv"] = true, ["mp-figs.mpiv"] = true, ["mp-form.mpiv"] = true,["mp-func.mpiv"] = true, ["mp-grap.mpiv"] = true, ["mp-grid.mpiv"] = true,["mp-grph.mpiv"] = true, ["mp-idea.mpiv"] = true, ["mp-luas.mpiv"] = true,["mp-mlib.mpiv"] = true, ["mp-node.mpiv"] = true, ["mp-page.mpiv"] = true,["mp-shap.mpiv"] = true, ["mp-step.mpiv"] = true, ["mp-text.mpiv"] = true,["mp-tool.mpiv"] = true, ["mp-cont.mpiv"] = true,

135
136
137
138
139
140
141
142
143
144
145
146 }

147 luamplib.noneedtoreplace = noneedtoreplaceformat.mp is much complicated, so specially treated.
148 local function replaceformatmp(file,newfile,ofmodify)

local fh = ioopen(file,"r")if not fh then return file end
151 local data = fh:read("*all"); fh:close()
152 fh = ioopen(newfile,"w")
153 if not fh then return file end
154 fh:write(
155"let normalinfont = infont;\n",156"primarydef str infont name = rawtextext(str) enddef;\n",
157
data,
158
"vardef Fmant_(expr x) = rawtextext(decimal abs x) enddef;\n",159"vardef Fexp_(expr x) = rawtextext(\"$^{\"&decimal x&\"}$\") enddef;\n",160"let infont = normalinfont;\n"161 ); fh:close()
162 lfstouch(newfile,currenttime,ofmodify)
163 return newfile
164 end149
150

Replace btex ... etex and verbatimtex ... etex in input files, if needed.
165 local name_b = "%f[%a_]"
166 local name_e = "%f[^%a_]"
167 local btex_etex = name_b.."btex"..name_e.."%s*(.-)%s*"..name_b.."etex"..name_e
168 local verbatimtex_etex = name_b.."verbatimtex"..name_e.."%s*(.-)%s*"..name_b.."etex"..name_e
169 local function replaceinputmpfile (name,file)
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

local ofmodify = lfsattributes(file,"modification")
if not ofmodify then return file end
local newfile = name:gsub("%W","_")
newfile = format("%s/luamplib_input_%s", cachedir or outputdir, newfile)
if newfile and luamplibtime thenlocal nf = lfsattributes(newfile)if nf and nf.mode == "file" andofmodify == nf.modification and luamplibtime < nf.access thenreturn nf.size == 0 and file or newfile
end
end
if name == "format.mp" then return replaceformatmp(file,newfile,ofmodify) end
local fh = ioopen(file,"r")if not fh then return file end
local data = fh:read("*all"); fh:close()“etex” must be preceded by a space and followed by a space or semicolon as specified in
17

LuaTEX manual, which is not the case of standalone metapost though.

local count,cnt = 0,0data, cnt = data:gsub(btex_etex, "btex %1 etex ") -- space
187 count = count + cnt
188 data, cnt = data:gsub(verbatimtex_etex, "verbatimtex %1 etex;") -- semicolon
189 count = count + cnt
190 if count == 0 then191
noneedtoreplace[name] = true192
fh = ioopen(newfile,"w");
193
if fh then194
fh:close()
195
lfstouch(newfile,currenttime,ofmodify)
196
end
197
return file
198 end
199 fh = ioopen(newfile,"w")
200 if not fh then return file end
201 fh:write(data); fh:close()
202 lfstouch(newfile,currenttime,ofmodify)
203 return newfile
204 end
185
186

205

As the finder function for mplib, use the kpse library and make it behave like as if
metapost was used. And replace .mp files with cache files if needed. See also #74, #97.
206 local mpkpse
207 dolocal exe = 0
while arg[exe-1] do
210
exe = exe-1
211 end
212 mpkpse = kpse.new(arg[exe], "mpost")
213 end214 local special_ftype = {215 pfb = "type1 fonts",
216 enc = "enc files",
217 }218 function luamplib.finder (name, mode, ftype)219 if mode == "w" then220
if name and name ~= "mpout.log" then221
kpse.record_output_file(name) -- recorder
222
end
223
return name
224 else
225
ftype = special_ftype[ftype] or ftype
226
local file = mpkpse:find_file(name,ftype)227
if file then228
if lfstouch and ftype == "mp" and not noneedtoreplace[name] then229
file = replaceinputmpfile(name,file)
230
end
231
else
232
file = mpkpse:find_file(name, name:match("%a+$"))
233
end
234
if file then235
kpse.record_input_file(file) -- recorder
208
209

18

end
return file238 end
239 end
236
237

240

Create and load mplib instances. We do not support ancient version of mplib any
more. (Don’t know which version of mplib started to support make_text and run_script;
let the users find it.)241 local preamble = [[boolean mplib ; mplib := true ;let dump = endinput ;244 let normalfontsize = fontsize;
245 input %s ;246 ]]
242
243

plain or metafun, though we cannot support metafun format fully.
247 local currentformat = "plain"
248 function luamplib.setformat (name)249

currentformat = name

250 endv2.9 has introduced the concept of “code inherit”
251 luamplib.codeinherit = false
252 local mplibinstances = {}253 luamplib.instances = mplibinstances
254 local has_instancename = false
255 local function reporterror (result, prevlog)
256
257
258
259

if not result thenerr("no result object returned")
else
local t, e, l = result.term, result.error, result.log

log has more information than term, so log first (2021/08/02)
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

local log = l or t or "no-term"
log = log:gsub("%(Please type a command or say `end'%)",""):gsub("\n+","\n")if result.status > 0 thenlocal first = log:match"(.-\n! .-)\n! "if first thentermorlog("term", first)
termorlog("log", log, "Warning")
else
warn(log)
end
if result.status > 1 thenerr(e or "see above messages")end
elseif prevlog thenlog = prevlog..log

v2.6.1: now luamplib does not disregard show command, even when luamplib.showlog isfalse. Incidentally, it does not raise error nor prints an info, even if output has no figure.
275
276
277
278
279

local show = log:match"\n>>? .+"if show thentermorlog("term", show, "Info (more info in the log)")info(log)
elseif luamplib.showlog and log:find"%g" then19

info(log)
end
282
end
283
return log
284 end
285 end280281

lualibs-os.lua installs a randomseed. When this file is not loaded, we should explicitly seed a unique integer to get random randomseed for each run.
286 if not math.initialseed then math.randomseed(currenttime) end
287 local function luamplibload (name)
288
289
290

local mpx = mplib.new {ini_version = true,
find_file = luamplib.finder,

Make use of make_text and run_script, which will co-operate with LuaTEX’s tex.runtoks
or other Lua functions. And we provide numbersystem option since v2.4. See https://github.com/lualatex/luamplib/issues/21.
make_text = luamplib.maketext,
run_script = luamplib.runscript,
math_mode = luamplib.numbersystem,
job_name= tex.jobname,random_seed = math.random(4095),
extensions = 1,

291
292
293
294
295
296
297

}

Append our own metapost preamble to the preamble above.
local preamble = tableconcat{
format(preamble, replacesuffix(name,"mp")),300
luamplib.preambles.mplibcode,301
luamplib.legacyverbatimtex and luamplib.preambles.legacyverbatimtex or "",302
luamplib.textextlabel and luamplib.preambles.textextlabel or "",303 }304 local result, log
305 if not mpx then306
result = { status = 99, error = "out of memory"}
307 else308
result = mpx:execute(preamble)
309 end
310 log = reporterror(result)311 return mpx, result, log
312 end298
299

Here, excute each mplibcode data, ie \begin{mplibcode} ... \end{mplibcode}.
313 local function process (data, instancename)
314
315
316
317
318
319
320
321
322
323
324

local currfmt
if instancename and instancename ~= "" thencurrfmt = instancename
has_instancename = trueelse
currfmt = tableconcat{
currentformat,
luamplib.numbersystem or "scaled",
tostring(luamplib.textextlabel),
tostring(luamplib.legacyverbatimtex),}20has_instancename = false
end
327 local mpx = mplibinstances[currfmt]
328 local standalone = not (has_instancename or luamplib.codeinherit)329 if mpx and standalone then330
mpx:finish()331 end
332 local log = ""333 if standalone or not mpx then334
mpx, _, log = luamplibload(currentformat)
335
mplibinstances[currfmt] = mpx
336 end
337 local converted, result = false, {}338 if mpx and data then339
result = mpx:execute(data)340
local log = reporterror(result, log)
341
if log then342
if result.fig then343
converted = luamplib.convert(result)
344
end
345
end
346 else
347
err"Mem file unloadable. Maybe generated with a different version of mplib?"
348 end349 return converted, result
350 end
325
326

351

dvipdfmx is supported, though nobody seems to use it.
352 local pdfmode = tex.outputmode > 0353

make_text and some run_script uses LuaTEX’s tex.runtoks.

354 local catlatex = luatexbase.registernumber("catcodetable@latex")
355 local catat11= luatexbase.registernumber("catcodetable@atletter")

tex.scantoks sometimes fail to read catcode properly, especially \#, \&, or \%. Aftersome experiment, we dropped using it. Instead, a function containing tex.sprint seems
to work nicely.
356 local function run_tex_code (str, cat)357

texruntoks(function() texsprint(cat or catlatex, str) end)

358 end

Prepare textext box number containers, locals and globals. localid can be any number. They are local anyway. The number will be reset at the start of a new code chunk.
Global boxes will use \newbox command in tex.runtoks process. This is the same whencodeinherit is true. Boxes in instances with name will also be global, so that their tex
boxes can be shared among instances of the same name.
359 local texboxes = { globalid = 0, localid = 4096 }For conversion of sp to bp.
360 local factor = 65536*(7227/7200)
361 local textext_fmt = 'image(addto currentpicture doublepath unitsquare \zxscaled %f yscaled %f shifted (0,-%f) \z
withprescript "mplibtexboxid=%i:%f:%f")'
364 local function process_tex_text (str)
365 if str then
362
363

21

local global = (has_instancename or luamplib.globaltextext or luamplib.codeinherit)and "\\global" or ""368
local tex_box_id369
if global == "" then370
tex_box_id = texboxes.localid + 1
371
texboxes.localid = tex_box_id
372
else
373
local boxid = texboxes.globalid + 1
374
texboxes.globalid = boxid375
run_tex_code(format([[\expandafter\newbox\csname luamplib.box.%s\endcsname]], boxid))376
tex_box_id = tex.getcount'allocationnumber'
377
end
378
run_tex_code(format("%s\\setbox%i\\hbox{%s}", global, tex_box_id, str))
379
local box = texgetbox(tex_box_id)
380
local wd = box.width / factor
381
local ht = box.height / factor
382
local dp = box.depth / factor
383
return textext_fmt:format(wd, ht+dp, dp, tex_box_id, wd, ht+dp)
384 end385 return ""386 end
366
367

387

Make color or xcolor’s color expressions usable, with \mpcolor or mplibcolor. These
commands should be used with graphical objects. Attempt to support l3color as well.
388 local mplibcolorfmt = {
389
390
391
392
393
394
395
396
397
398
399

xcolor = tableconcat{[[\begingroup\let\XC@mcolor\relax]],[[\def\set@color{\global\mplibtmptoks\expandafter{\current@color}}]],[[\color%s\endgroup]],},l3color = tableconcat{[[\begingroup\def\__color_select:N#1{\expandafter\__color_select:nn#1}]],[[\def\__color_backend_select:nn#1#2{\global\mplibtmptoks{#1 #2}}]],[[\def\__kernel_backend_literal:e#1{\global\mplibtmptoks\expandafter{\expanded{#1}}}]],[[\color_select:n%s\endgroup]],
},

400 }
401 local colfmt = is_defined'color_select:n' and "l3color" or "xcolor"
402 if colfmt == "l3color" thenrun_tex_code{"\\newcatcodetable\\luamplibcctabexplat",405"\\begingroup",406"\\catcode`@=11 ",407"\\catcode`_=11 ",408"\\catcode`:=11 ",409"\\savecatcodetable\\luamplibcctabexplat",410"\\endgroup",411 }412 end
413 local ccexplat = luatexbase.registernumber"luamplibcctabexplat"
414 local function process_color (str)
415 if str then416
if not str:find("%b{}") then417
str = format("{%s}",str)
403
404

22

end
local myfmt = mplibcolorfmt[colfmt]
420
if colfmt == "l3color" and is_defined"color" then421
if str:find("%b[]") then422
myfmt = mplibcolorfmt.xcolor
423
else
424
for _,v in ipairs(str:match"{(.+)}":explode"!") do425
if not v:find("^%s*%d+%s*$") then426
local pp = get_macro(format("l__color_named_%s_prop",v))427
if not pp or pp == "" then428
myfmt = mplibcolorfmt.xcolor
429
break
430
end
431
end
432
end
433
end
434
end
435
run_tex_code(myfmt:format(str), ccexplat or catat11)436
local t = texgettoks"mplibtmptoks"437
if not pdfmode and not t:find"^pdf" then438
t = t:gsub("%a+ (.+)","pdf:bc [%1]")
439
end
440
return format('1 withprescript "mpliboverridecolor=%s"', t)
441 end442 return ""443 end
418
419

444

for \mpdim or mplibdimen
445 local function process_dimen (str)

if str thenstr = str:gsub("{(.+)}","%1")448
run_tex_code(format([[\mplibtmptoks\expandafter{\the\dimexpr %s\relax}]], str))449
return format("begingroup %s endgroup", texgettoks"mplibtmptoks")
450 end451 return ""452 end
446
447

453

Newly introduced method of processing verbatimtex ... etex. This function is used
when \mpliblegacybehavior{false} is declared.
454 local function process_verbatimtex_text (str)

if str thenrun_tex_code(str)457 end458 return ""459 end
455
456

460

For legacy verbatimtex process. verbatimtex ... etex before beginfig() is not ignored, but the TEX code is inserted just before the mplib box. And TEX code inside
beginfig() ... endfig is inserted after the mplib box.
461 local tex_code_pre_mplib = {}462 luamplib.figid = 1
463 luamplib.in_the_fig = false
464 local function process_verbatimtex_prefig (str)23

if str thentex_code_pre_mplib[luamplib.figid] = str
467 end468 return ""469 end
470 local function process_verbatimtex_infig (str)471 if str then472
return format('special "postmplibverbtex=%s";', str)
473 end474 return ""475 end
465
466

476
477 local runscript_funcs = {luamplibtext= process_tex_text,
luamplibcolor = process_color,
luamplibdimen = process_dimen,
luamplibprefig = process_verbatimtex_prefig,
luamplibinfig = process_verbatimtex_infig,
luamplibverbtex = process_verbatimtex_text,

478
479
480
481
482
483
484 }
485

For metafun format. see issue #79.
486 mp = mp or {}487 local mp = mp
488 mp.mf_path_reset = mp.mf_path_reset or function() end
489 mp.mf_finish_saving_data = mp.mf_finish_saving_data or function() end
490 mp.report = mp.report or info

metafun 2021-03-09 changes crashes luamplib.
491 catcodes = catcodes or {}492 local catcodes = catcodes
493 catcodes.numbers = catcodes.numbers or {}494 catcodes.numbers.ctxcatcodes = catcodes.numbers.ctxcatcodes or catlatex
495 catcodes.numbers.texcatcodes = catcodes.numbers.texcatcodes or catlatex
496 catcodes.numbers.luacatcodes = catcodes.numbers.luacatcodes or catlatex
497 catcodes.numbers.notcatcodes = catcodes.numbers.notcatcodes or catlatex
498 catcodes.numbers.vrbcatcodes = catcodes.numbers.vrbcatcodes or catlatex
499 catcodes.numbers.prtcatcodes = catcodes.numbers.prtcatcodes or catlatex
500 catcodes.numbers.txtcatcodes = catcodes.numbers.txtcatcodes or catlatex
501

A function from ConTEXt general.

502 local function mpprint(buffer,...)
503
504
505
506
507
508
509
510
511
512
513
514

for i=1,select("#",...) do
local value = select(i,...)
if value ~= nil thenlocal t = type(value)
if t == "number" thenbuffer[#buffer+1] = format("%.16f",value)elseif t == "string" thenbuffer[#buffer+1] = value
elseif t == "table" thenbuffer[#buffer+1] = "(" .. tableconcat(value,",") .. ")"else -- boolean or whatever
buffer[#buffer+1] = tostring(value)

24

end
end
517 end
518 end519 function luamplib.runscript (code)
520 local id, str = code:match("(.-){(.*)}")
521 if id and str then522
local f = runscript_funcs[id]523
if f then524
local t = f(str)525
if t then return t end
526
end
527 end
528 local f = loadstring(code)
529 if type(f) == "function" then530
local buffer = {}531
function mp.print(...)
532
mpprint(buffer,...)
533
end
534
local res = {f()}535buffer = tableconcat(buffer)536
if buffer and buffer ~= "" then537
return buffer
538
end
539
buffer = {}540
mpprint(buffer, tableunpack(res))541
return tableconcat(buffer)
542 end543 return ""544 end
515
516

545

make_text must be one liner, so comment sign is not allowed.
546 local function protecttexcontents (str)return str:gsub("\\%%", "\0PerCent\0"):gsub("%%.-\n", "")549:gsub("%%.-$", "")550:gsub("%zPerCent%z", "\\%%")
551:gsub("%s+", " ")552 end553 luamplib.legacyverbatimtex = true
554 function luamplib.maketext (str, what)
555 if str and str ~= "" then556
str = protecttexcontents(str)557
if what == 1 then558
if not str:find("\\documentclass"..name_e) and559
not str:find("\\begin%s*{document}") and560
not str:find("\\documentstyle"..name_e) and561
not str:find("\\usepackage"..name_e) then562
if luamplib.legacyverbatimtex then563
if luamplib.in_the_fig then564
return process_verbatimtex_infig(str)
565
else
566
return process_verbatimtex_prefig(str)
567
end
547
548

25

else
return process_verbatimtex_text(str)
570
end
571
end
572
else
573
return process_tex_text(str)574
end
575 end
576 return ""577 end
568
569

578

luamplib’s metapost color operators
579 local function colorsplit (res)local t, tt = { }, res:gsub("[%[%]]","",2):explode()
local be = tt[1]:find"^%d" and 1 or 2
582 for i=be, #tt do583
if not tonumber(tt[i]) then break end
584
t[#t+1] = tt[i]585 end
586 return t
587 end
580
581

588
589 luamplib.gettexcolor = function (str, rgb)

local res = process_color(str):match'"mpliboverridecolor=(.+)"'
if res:find" cs " or res:find"@pdf.obj" then592
if not rgb then593
warn("%s is a spot color. Forced to CMYK", str)
594
end
595
run_tex_code({596"\\color_export:nnN{",
597
str,
598
"}{",
599
rgb and "space-sep-rgb" or "space-sep-cmyk",
600"}\\mplib_@tempa",
601},ccexplat)602
return get_macro"mplib_@tempa":explode()
603 end604 local t = colorsplit(res)605 if #t == 3 or not rgb then return t end
606 if #t == 4 then607
return { 1 - math.min(1,t[1]+t[4]), 1 - math.min(1,t[2]+t[4]), 1 - math.min(1,t[3]+t[4]) }608 end609 return { t[1], t[1], t[1] }610 end
590
591

611
612 luamplib.shadecolor = function (str)613
614

local res = process_color(str):match'"mpliboverridecolor=(.+)"'
if res:find" cs " or res:find"@pdf.obj" then -- spot color shade: l3 only

An example of spot color shading:\documentclass{article}\usepackage{luamplib}\mplibsetformat{metafun}\ExplSyntaxOn

26

\color_model_new:nnn { pantone3005 }{ Separation }{ name = PANTONE~3005~U ,alternative-model = cmyk ,alternative-values = {1, 0.56, 0, 0}}\color_set:nnn{spotA}{pantone3005}{1}\color_set:nnn{spotB}{pantone3005}{0.6}\color_model_new:nnn { pantone1215 }{ Separation }{ name = PANTONE~1215~U ,alternative-model = cmyk ,alternative-values = {0, 0.15, 0.51, 0}}\color_set:nnn{spotC}{pantone1215}{1}\color_model_new:nnn { pantone2040 }{ Separation }{ name = PANTONE~2040~U ,alternative-model = cmyk ,alternative-values = {0, 0.28, 0.21, 0.04}}\color_set:nnn{spotD}{pantone2040}{1}\ExplSyntaxOff\begin{document}\begin{mplibcode}
beginfig(1)fill unitsquare xyscaled (\mpdim\textwidth,1cm)
withshademethod "linear"withshadevector (0,1)
withshadestep (withshadefraction .5
withshadecolors ("spotB","spotC"))
withshadestep (withshadefraction 1
withshadecolors ("spotC","spotD"));
endfig;\end{mplibcode}\end{document}

another one: user-defined DeviceN colorspace\DocumentMetadata{ }\documentclass{article}\usepackage{luamplib}\mplibsetformat{metafun}\ExplSyntaxOn\color_model_new:nnn { pantone1215 }{ Separation }{ name = PANTONE~1215~U ,alternative-model = cmyk ,alternative-values = {0, 0.15, 0.51, 0}}

27

\color_model_new:nnn { pantone+black }{ DeviceN }{names = {pantone1215,black}}\color_set:nnn{purepantone}{pantone+black}{1,0}\color_set:nnn{pureblack} {pantone+black}{0,1}\ExplSyntaxOff\begin{document}\mpfigfill unitsquare xscaled \mpdim{\textwidth} yscaled 30
withshademethod "linear"withshadecolors ("purepantone","pureblack");\endmpfig\end{document}
run_tex_code({[[\color_export:nnN{]], str, [[}{backend}\mplib_@tempa]],
617},ccexplat)
618
local name, value = get_macro'mplib_@tempa':match'{(.-)}{(.-)}'
619
local t, obj = res:explode()
620
if pdfmode then621
obj = format("%s 0 R", ltx.pdf.object_id( t[1]:sub(2,-1) ))
622
else
623
obj = t[2]
624
end
625
return format('(1) withprescript"mplib_spotcolor=%s:%s:%s"', value,obj,name)626 end
627 return colorsplit(res)628 end
615
616

629

Remove trailing zeros for smaller PDF
630 local decimals = "%.%d+"631 local function rmzeros(str) return str:gsub("%.?0+$","") end
632

luamplib’s mplibgraphictext operator
633 local emboldenfonts = { }634 local function getemboldenwidth (curr, fakebold)
635
636
637
638
639
640
641
642
643
644
645
646
647
648

local width = emboldenfonts.width
if not width thenlocal flocal function getglyph(n)while n do
if n.head thengetglyph(n.head)elseif n.font and n.font > 0 thenf = n.font; breakend
n = node.getnext(n)endendgetglyph(curr)

28

width = font.getcopy(f or font.current()).size * fakebold / factor * 10emboldenfonts.width = width
651 end652 return width
653 end654 local function getrulewhatsit (line, wd, ht, dp)
655 line, wd, ht, dp = line/1000, wd/factor, ht/factor, dp/factor
656 local pl
657 local fmt = "%f w %f %f %f %f re %s"
658 if pdfmode then659
pl = node.new("whatsit","pdf_literal")660
pl.mode = 0661 else662
fmt = "pdf:content "..fmt
663
pl = node.new("whatsit","special")
664 end665 pl.data = fmt:format(line, 0, -dp, wd, ht+dp, "B") :gsub(decimals,rmzeros)
666 local ss = node.new"glue"
667 node.setglue(ss, 0, 65536, 65536, 2, 2)
668 pl.next = ss
669 return pl
670 end671 local function getrulemetric (box, curr, bp)
672 local running = -1073741824
673 local wd,ht,dp = curr.width, curr.height, curr.depth
674 wd = wd == running and box.width or wd
675 ht = ht == running and box.height or ht
676 dp = dp == running and box.depth or dp
677 if bp then678
return wd/factor, ht/factor, dp/factor
679 end
680 return wd, ht, dp
681 end
682 local function embolden (box, curr, fakebold)
683 local head = curr
684 while curr do
685
if curr.head then686
curr.head = embolden(curr, curr.head, fakebold)
687
elseif curr.replace then688
curr.replace = embolden(box, curr.replace, fakebold)
689
elseif curr.leader then690
if curr.leader.head then691
curr.leader.head = embolden(curr.leader, curr.leader.head, fakebold)
692
elseif curr.leader.id == node.id"rule" then693
local glue = node.effective_glue(curr, box)
694
local line = getemboldenwidth(curr, fakebold)695
local wd,ht,dp = getrulemetric(box, curr.leader)696
if box.id == node.id"hlist" then697
wd = glue
698
else
699
ht, dp = 0, glue
700
end
701
local pl = getrulewhatsit(line, wd, ht, dp)702
local pack = box.id == node.id"hlist" and node.hpack or node.vpack
649
650

29

local list = pack(pl, glue, "exactly")
head = node.insert_after(head, curr, list)
705
head, curr = node.remove(head, curr)
706
end
707
elseif curr.id == node.id"rule" and curr.subtype == 0 then708
local line = getemboldenwidth(curr, fakebold)709
local wd,ht,dp = getrulemetric(box, curr)
710
if box.id == node.id"vlist" then711
ht, dp = 0, ht+dp
712
end
713
local pl = getrulewhatsit(line, wd, ht, dp)714
local list
715
if box.id == node.id"hlist" then716
list = node.hpack(pl, wd, "exactly")
717
else
718
list = node.vpack(pl, ht+dp, "exactly")
719
end
720
head = node.insert_after(head, curr, list)
721
head, curr = node.remove(head, curr)722
elseif curr.id == node.id"glyph" and curr.font > 0 then723
local f = curr.font724
local key = format("%s:%s",f,fakebold)725
local i = emboldenfonts[key]726
if not i then727
local ft = font.getfont(f) or font.getcopy(f)728
if pdfmode then729
width = ft.size * fakebold / factor * 10730
emboldenfonts.width = width731
ft.mode, ft.width = 2, width732
i = font.define(ft)
733
else
734
if ft.format ~= "opentype" and ft.format ~= "truetype" then735
goto skip_type1
736
end
737
local name = ft.name:gsub('"',''):gsub(';$','')
738
name = format('%s;embolden=%s;',name,fakebold)739_, i = fonts.constructors.readanddefine(name,ft.size)
740
end
741
emboldenfonts[key] = i
742
end
743
curr.font = i
744
end
745
::skip_type1::
746
curr = node.getnext(curr)
747 end748 return head
749 end
750 local function graphictextcolor (col, filldraw)
751 if col:find"^[%d%.:]+$" then752
col = col:explode":"753
for i=1,#col do754
col[i] = format("%.3f", col[i])
755
end
756
if pdfmode then
703
704

30

local op = #col == 4 and "k" or #col == 3 and "rg" or "g"col[#col+1] = filldraw == "fill" and op or op:upper()759
return tableconcat(col," ")
760
end
761
return format("[%s]", tableconcat(col," "))762 end763 col = process_color(col):match'"mpliboverridecolor=(.+)"'
764 if pdfmode then765
local t, tt = col:explode(), { }766local b = filldraw == "fill" and 1 or #t/2+1
767
local e = b == 1 and #t/2 or #t
768
for i=b,e do769
tt[#tt+1] = t[i]
770
end
771
return tableconcat(tt," ")772 end773 return col:gsub("^.- ","")
774 end775 luamplib.graphictext = function (text, fakebold, fc, dc)
776 local fmt = process_tex_text(text):sub(1,-2)
777 local id = tonumber(fmt:match"mplibtexboxid=(%d+):")
778 emboldenfonts.width = nil
779 local box = texgetbox(id)780 box.head = embolden(box, box.head, fakebold)
781 local fill = graphictextcolor(fc,"fill")
782 local draw = graphictextcolor(dc,"draw")
783 local bc = pdfmode and "" or "pdf:bc "784 return format('%s withprescript "mpliboverridecolor=%s%s %s")', fmt, bc, fill, draw)
785 end
757
758

786

luamplib’s mplibglyph operator
787 local function mperr (str)788

return format("hide(errmessage %q)", str)

789 end
790 local function getangle (a,b,c)local r = math.deg(math.atan(c.y-b.y, c.x-b.x) - math.atan(b.y-a.y, b.x-a.x))
if r > 180 then793
r = r - 360
794 elseif r < -180 then795
r = r + 360
796 end
797 return r
798 end
799 local function turning (t)800 local r, n = 0, #t
801 for i=1,2 do
802tableinsert(t, t[i])803 end
804 for i=1,n do
805
r = r + getangle(t[i], t[i+1], t[i+2])
806 end
807 return r/360
808 end
809 local function glyphimage(t, fmt)
791
792

31

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863

local q,p,r = {{},{}}
for i,v in ipairs(t) do
local cmd = v[#v]
if cmd == "m" thenp = {format('(%s,%s)',v[1],v[2])}r = {{x=v[1],y=v[2]}}else
local nt = t[i+1]local last = not nt or nt[#nt] == "m"if cmd == "l" thenlocal pt = t[i-1]local seco = pt[#pt] == "m"if (last or seco) and r[1].x == v[1] and r[1].y == v[2] thenelsetableinsert(p, format('--(%s,%s)',v[1],v[2]))tableinsert(r, {x=v[1],y=v[2]})end
if last thentableinsert(p, '--cycle')end
elseif cmd == "c" thentableinsert(p, format('..controls(%s,%s)and(%s,%s)',v[1],v[2],v[3],v[4]))if last and r[1].x == v[5] and r[1].y == v[6] thentableinsert(p, '..cycle')else
tableinsert(p, format('..(%s,%s)',v[5],v[6]))if last thentableinsert(p, '--cycle')endtableinsert(r, {x=v[5],y=v[6]})end
else
return mperr"unknown operator"end
if last thentableinsert(q[ turning(r) > 0 and 1 or 2 ], tableconcat(p))
end
end
end
r = { }
if fmt == "opentype" thenfor _,v in ipairs(q[1]) dotableinsert(r, format('addto currentpicture contour %s;',v))
end
for _,v in ipairs(q[2]) dotableinsert(r, format('addto currentpicture contour %s withcolor background;',v))
end
else
for _,v in ipairs(q[2]) dotableinsert(r, format('addto currentpicture contour %s;',v))
end
for _,v in ipairs(q[1]) dotableinsert(r, format('addto currentpicture contour %s withcolor background;',v))
end

32

end
return format('image(%s)', tableconcat(r))
866 end
867 if not table.tofile then require"lualibs-lpeg"; require"lualibs-table"; end
868 function luamplib.glyph (f, c)
869 local filename, subfont, instance, kind, shapedata
870 local fid = tonumber(f) or font.id(f)871 if fid > 0 then872
local fontdata = font.getfont(fid) or font.getcopy(fid)873
filename, subfont, kind = fontdata.filename, fontdata.subfont, fontdata.format
874
instance = fontdata.specification and fontdata.specification.instance875
filename = filename and filename:gsub("^harfloaded:","")
876 else
877
local name878
f = f:match"^%s*(.+)%s*$"879
name, subfont, instance = f:match"(.+)%((%d+)%)%[(.-)%]$"
880
if not name then881
name, instance = f:match"(.+)%[(.-)%]$" -- SourceHanSansK-VF.otf[Heavy]
882
end
883
if not name then884
name, subfont = f:match"(.+)%((%d+)%)$" -- Times.ttc(2)
885
end
886
name = name or f
887
subfont = (subfont or 0)+1
888
instance = instance and instance:lower()889
for _,ftype in ipairs{"opentype", "truetype"} do890
filename = kpse.find_file(name, ftype.." fonts")
891
if filename then892
kind = ftype; break
893
end
894
end
895 end
896 if kind ~= "opentype" and kind ~= "truetype" then897
f = fid and fid > 0 and tex.fontname(fid) or f898
if kpse.find_file(f, "tfm") then899
return format("glyph %s of %q", tonumber(c) or format("%q",c), f)
900
else
901
return mperr"font not found"902
end
903 end
904 local time = lfsattributes(filename,"modification")
905 local k = format("shapes_%s(%s)[%s]", filename, subfont or "", instance or "")906 local h = format(string.rep('%02x', 256/8), string.byte(sha2.digest256(k), 1, -1))
907 local newname = format("%s/%s.lua", cachedir or outputdir, h)
908 local newtime = lfsattributes(newname,"modification") or 0909 if time == newtime then910
shapedata = require(newname)911 end
912 if not shapedata then913
shapedata = fonts and fonts.handlers.otf.readers.loadshapes(filename,subfont,instance)914
if not shapedata then return mperr"loadshapes() failed. luaotfload not loaded?" end915
table.tofile(newname, shapedata, "return")
916
lfstouch(newname, time, time)
917 end
864
865

33

local gid = tonumber(c)if not gid then920
local uni = utf8.codepoint(c)921
for i,v in pairs(shapedata.glyphs) do
922
if c == v.name or uni == v.unicode then923
gid = i; break
924
end
925
end
926 end927 if not gid then return mperr"cannot get GID (glyph id)" end928 local fac = 1000 / (shapedata.units or 1000)
929 local t = shapedata.glyphs[gid].segments
930 if not t then return "image()" end
931 for i,v in ipairs(t) do
932
if type(v) == "table" then933
for ii,vv in ipairs(v) do
934
if type(vv) == "number" then935
t[i][ii] = format("%.0f", vv * fac)
936
end
937
end
938
end
939 end
940 kind = shapedata.format or kind
941 return glyphimage(t, kind)942 end
918
919

943

mpliboutlinetext : based on mkiv’s font-mps.lua
944 local rulefmt = "mpliboutlinepic[%i]:=image(addto currentpicture contour \z945

unitsquare shifted - center unitsquare;) xscaled %f yscaled %f shifted (%f,%f);"

946 local outline_horz, outline_vert
947 function outline_vert (res, box, curr, xshift, yshift)
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970

local b2u = box.dir == "LTL"
local dy = (b2u and -box.depth or box.height)/factor
local ody = dy
while curr do
if curr.id == node.id"rule" thenlocal wd, ht, dp = getrulemetric(box, curr, true)
local hd = ht + dp
if hd ~= 0 thendy = dy + (b2u and dp or -ht)
if wd ~= 0 and curr.subtype == 0 thenres[#res+1] = rulefmt:format(#res+1, wd, hd, xshift+wd/2, yshift+dy+(ht-dp)/2)
end
dy = dy + (b2u and ht or -dp)
end
elseif curr.id == node.id"glue" thenlocal vwidth = node.effective_glue(curr,box)/factor
if curr.leader thenlocal curr, kind = curr.leader, curr.subtype
if curr.id == node.id"rule" thenlocal wd = getrulemetric(box, curr, true)
if wd ~= 0 thenlocal hd = vwidth
local dy = dy + (b2u and 0 or -hd)34

if hd ~= 0 and curr.subtype == 0 thenres[#res+1] = rulefmt:format(#res+1, wd, hd, xshift+wd/2, yshift+dy+hd/2)
973
end
974
end
975
elseif curr.head then976
local hd = (curr.height + curr.depth)/factor
977
if hd <= vwidth then
978
local dy, n, iy = dy, 0, 0979
if kind == 100 or kind == 103 then -- todo: gleaders980
local ady = abs(ody - dy)981
local ndy = math.ceil(ady / hd) * hd982
local diff = ndy - ady983
n = (vwidth-diff) // hd984
dy = dy + (b2u and diff or -diff)
985
else
986
n = vwidth // hd987
if kind == 101 then988
local side = vwidth % hd / 2989
dy = dy + (b2u and side or -side)990
elseif kind == 102 then991
iy = vwidth % hd / (n+1)
992dy = dy + (b2u and iy or -iy)
993
end
994
end
995
dy = dy + (b2u and curr.depth or -curr.height)/factor
996
hd = b2u and hd or -hd
997
iy = b2u and iy or -iy
998
local func = curr.id == node.id"hlist" and outline_horz or outline_vert
999
for i=1,n do
1000
res = func(res, curr, curr.head, xshift+curr.shift/factor, yshift+dy)
1001
dy = dy + hd + iy
1002
end
1003
end
1004
end
1005
end
1006
dy = dy + (b2u and vwidth or -vwidth)
1007
elseif curr.id == node.id"kern" then1008
dy = dy + curr.kern/factor * (b2u and 1 or -1)
1009
elseif curr.id == node.id"vlist" then1010
dy = dy + (b2u and curr.depth or -curr.height)/factor
1011
res = outline_vert(res, curr, curr.head, xshift+curr.shift/factor, yshift+dy)
1012
dy = dy + (b2u and curr.height or -curr.depth)/factor
1013
elseif curr.id == node.id"hlist" then1014
dy = dy + (b2u and curr.depth or -curr.height)/factor
1015
res = outline_horz(res, curr, curr.head, xshift+curr.shift/factor, yshift+dy)
1016
dy = dy + (b2u and curr.height or -curr.depth)/factor
1017
end
1018
curr = node.getnext(curr)
1019 end
1020 return res
1021 end
1022 function outline_horz (res, box, curr, xshift, yshift, discwd)
1023 local r2l = box.dir == "TRT"1024 local dx = r2l and (discwd or box.width/factor) or 0
971
972

35

1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078

local dirs = { { dir = r2l, dx = dx } }while curr do
if curr.id == node.id"dir" thenlocal sign, dir = curr.dir:match"(.)(...)"
local level, newdir = curr.level, r2l
if sign == "+" thennewdir = dir == "TRT"
if r2l ~= newdir thenlocal n = node.getnext(curr)
while n do
if n.id == node.id"dir" and n.level+1 == level then break end
n = node.getnext(n)end
n = n or node.tail(curr)
dx = dx + node.rangedimensions(box, curr, n)/factor * (newdir and 1 or -1)
end
dirs[level] = { dir = r2l, dx = dx }else
local level = level + 1
newdir = dirs[level].dir
if r2l ~= newdir thendx = dirs[level].dx
end
end
r2l = newdir
elseif curr.char and curr.font and curr.font > 0 thenlocal ft = font.getfont(curr.font) or font.getcopy(curr.font)local gid = ft.characters[curr.char].index or curr.char
local scale = ft.size / factor / 1000
local slant = (ft.slant or 0)/1000
local extend = (ft.extend or 1000)/1000
local squeeze = (ft.squeeze or 1000)/1000
local expand = 1 + (curr.expansion_factor or 0)/1000000
local xscale = scale * extend * expand
local yscale = scale * squeeze
dx = dx - (r2l and curr.width/factor*expand or 0)
local xpos = dx + xshift + (curr.xoffset or 0)/factor
local ypos = yshift + (curr.yoffset or 0)/factor
local vertical = ft.shared and ft.shared.features.vertical and "rotated 90" or ""if vertical ~= "" then -- luatexko
for _,v in ipairs(ft.characters[curr.char].commands or { }) do
if v[1] == "down" thenypos = ypos - v[2] / factor
elseif v[1] == "right" thenxpos = xpos + v[2] / factor
else
break
end
end
end
local image
if ft.format == "opentype" or ft.format == "truetype" thenimage = luamplib.glyph(curr.font, gid)
else

36

1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132

local name, scale = ft.name, 1
local vf = font.read_vf(name, ft.size)
if vf and vf.characters[gid] thenlocal cmds = vf.characters[gid].commands or {}for _,v in ipairs(cmds) do
if v[1] == "char" thengid = v[2]elseif v[1] == "font" and vf.fonts[v[2]] thenname = vf.fonts[v[2]].name
scale = vf.fonts[v[2]].size / ft.size
end
end
end
image = format("glyph %s of %q scaled %f", gid, name, scale)end
res[#res+1] = format("mpliboutlinepic[%i]:=%s xscaled %f yscaled %f slanted %f %s shifted (%f,%f);",#res+1, image, xscale, yscale, slant, vertical, xpos, ypos)
dx = dx + (r2l and 0 or curr.width/factor*expand)elseif curr.replace thenlocal width = node.dimensions(curr.replace)/factor
dx = dx - (r2l and width or 0)
res = outline_horz(res, box, curr.replace, xshift+dx, yshift, width)dx = dx + (r2l and 0 or width)
elseif curr.id == node.id"rule" thenlocal wd, ht, dp = getrulemetric(box, curr, true)
if wd ~= 0 thenlocal hd = ht + dp
dx = dx - (r2l and wd or 0)
if hd ~= 0 and curr.subtype == 0 thenres[#res+1] = rulefmt:format(#res+1, wd, hd, xshift+dx+wd/2, yshift+(ht-dp)/2)
end
dx = dx + (r2l and 0 or wd)
end
elseif curr.id == node.id"glue" thenlocal width = node.effective_glue(curr, box)/factor
dx = dx - (r2l and width or 0)
if curr.leader thenlocal curr, kind = curr.leader, curr.subtype
if curr.id == node.id"rule" thenlocal wd, ht, dp = getrulemetric(box, curr, true)
local hd = ht + dp
if hd ~= 0 thenwd = width
if wd ~= 0 and curr.subtype == 0 thenres[#res+1] = rulefmt:format(#res+1, wd, hd, xshift+dx+wd/2, yshift+(ht-dp)/2)
end
end
elseif curr.head thenlocal wd = curr.width/factor
if wd <= width thenlocal dx = r2l and dx+width or dx
local n, ix = 0, 0
if kind == 100 or kind == 103 then -- todo: gleaderslocal adx = abs(dx-dirs[1].dx)

37

local ndx = math.ceil(adx / wd) * wd
local diff = ndx - adx1135
n = (width-diff) // wd1136
dx = dx + (r2l and -diff-wd or diff)
1137
else
1138
n = width // wd1139
if kind == 101 then1140
local side = width % wd /21141
dx = dx + (r2l and -side-wd or side)
1142
elseif kind == 102 then1143
ix = width % wd / (n+1)
1144dx = dx + (r2l and -ix-wd or ix)
1145
end
1146
end
1147
wd = r2l and -wd or wd
1148
ix = r2l and -ix or ix
1149
local func = curr.id == node.id"hlist" and outline_horz or outline_vert
1150
for i=1,n do1151
res = func(res, curr, curr.head, xshift+dx, yshift-curr.shift/factor)
1152
dx = dx + wd + ix
1153
end
1154
end
1155
end
1156
end
1157
dx = dx + (r2l and 0 or width)1158
elseif curr.id == node.id"kern" then1159
dx = dx + curr.kern/factor * (r2l and -1 or 1)
1160
elseif curr.id == node.id"math" then1161
dx = dx + curr.surround/factor * (r2l and -1 or 1)1162
elseif curr.id == node.id"vlist" then1163
dx = dx - (r2l and curr.width/factor or 0)1164
res = outline_vert(res, curr, curr.head, xshift+dx, yshift-curr.shift/factor)
1165
dx = dx + (r2l and 0 or curr.width/factor)
1166
elseif curr.id == node.id"hlist" then1167
dx = dx - (r2l and curr.width/factor or 0)1168
res = outline_horz(res, curr, curr.head, xshift+dx, yshift-curr.shift/factor)
1169
dx = dx + (r2l and 0 or curr.width/factor)
1170
end
1171
curr = node.getnext(curr)
1172 end
1173 return res
1174 end
1175 function luamplib.outlinetext (text)
1176 local fmt = process_tex_text(text)
1177 local id = tonumber(fmt:match"mplibtexboxid=(%d+):")
1178 local box = texgetbox(id)1179 local res = outline_horz({ }, box, box.head, 0, 0)1180 if #res == 0 then res = { "mpliboutlinepic[1]:=image();" } end
1181 return tableconcat(res) .. format("mpliboutlinenum:=%i;", #res)
1182 end
1133
1134

1183

Our metapost preambles
1184 luamplib.preambles = {1185

mplibcode = [[38

1186 texscriptmode := 2;1187 def rawtextext (expr t) = runscript("luamplibtext{"&t&"}") enddef;
1188 def mplibcolor (expr t) = runscript("luamplibcolor{"&t&"}") enddef;
1189 def mplibdimen (expr t) = runscript("luamplibdimen{"&t&"}") enddef;
1190 def VerbatimTeX (expr t) = runscript("luamplibverbtex{"&t&"}") enddef;
1191 if known context_mlib:

defaultfont := "cmtt10";
let infont = normalinfont;
1194 let fontsize = normalfontsize;
1195 vardef thelabel@#(expr p,z) =1196
if string p :1197
thelabel@#(p infont defaultfont scaled defaultscale,z)1198
else :
1199
p shifted (z + labeloffset*mfun_laboff@# 1200
(mfun_labxf@#*lrcorner p + mfun_labyf@#*ulcorner p +1201(1-mfun_labxf@#-mfun_labyf@#)*llcorner p))1202
fi1203 enddef;
1204 else:
1205 vardef textext@# (text t) = rawtextext (t) enddef;
1206 def message expr t =1207
if string t: runscript("mp.report[=["&t&"]=]") else: errmessage "Not a string" fi
1208 enddef;
1209 fi
1210 def resolvedcolor(expr s) =1211 runscript("return luamplib.shadecolor('"& s &"')")
1212 enddef;
1213 def colordecimals primary c =1214 if cmykcolor c:
1215
decimal cyanpart c & ":" & decimal magentapart c & ":" &1216
decimal yellowpart c & ":" & decimal blackpart c
1217 elseif rgbcolor c:
1218
decimal redpart c & ":" & decimal greenpart c & ":" & decimal bluepart c
1219 elseif string c:1220
if known graphictextpic: c else: colordecimals resolvedcolor(c) fi
1221 else:
1222
decimal c1223 fi
1224 enddef;
1225 def externalfigure primary filename =1226 draw rawtextext("\includegraphics{"& filename &"}")
1227 enddef;
1228 def TEX = textext enddef;
1229 def mplibtexcolor primary c =1230 runscript("return luamplib.gettexcolor('"& c &"')")
1231 enddef;
1232 def mplibrgbtexcolor primary c =1233 runscript("return luamplib.gettexcolor('"& c &"','rgb')")1234 enddef;
1235 def mplibgraphictext primary t =1236 begingroup;1237 mplibgraphictext_ (t)1238 enddef;
1239 def mplibgraphictext_ (expr t) text rest =
1192
1193

39

save fakebold, scale, fillcolor, drawcolor, withfillcolor, withdrawcolor,fb, fc, dc, graphictextpic;
1242 picture graphictextpic; graphictextpic := nullpicture;1243 numeric fb; string fc, dc; fb:=2; fc:="white"; dc:="black";
1244 let scale = scaled;
1245 def fakebold primary c = hide(fb:=c;) enddef;
1246 def fillcolor primary c = hide(fc:=colordecimals c;) enddef;
1247 def drawcolor primary c = hide(dc:=colordecimals c;) enddef;
1248 let withfillcolor = fillcolor; let withdrawcolor = drawcolor;
1249 addto graphictextpic doublepath origin rest; graphictextpic:=nullpicture;
1250 def fakebold primary c = enddef;
1251 let fillcolor = fakebold; let drawcolor = fakebold;
1252 let withfillcolor = fillcolor; let withdrawcolor = drawcolor;
1253 image(draw runscript("return luamplib.graphictext([===["&t&"]===],"
1254& decimal fb &",'"& fc &"','"& dc &"')") rest;)
1255 endgroup;
1256 enddef;
1257 def mplibglyph expr c of f =1258 runscript (1259"return luamplib.glyph('"1260& if numeric f: decimal fi f
1261
& "','"
1262& if numeric c: decimal fi c
1263& "')"
1264 )
1265 enddef;
1266 def mplibdrawglyph expr g =1267 draw image(1268
save i; numeric i; i:=0;1269
for item within g:
1270
i := i+1;
1271
fill pathpart item
1272
if i < length g: withpostscript "collect" fi;1273
endfor1274 )
1275 enddef;
1276 def mplib_do_outline_text_set_b (text f) (text d) text r =1277 def mplib_do_outline_options_f = f enddef;
1278 def mplib_do_outline_options_d = d enddef;
1279 def mplib_do_outline_options_r = r enddef;
1280 enddef;
1281 def mplib_do_outline_text_set_f (text f) text r =1282 def mplib_do_outline_options_f = f enddef;
1283 def mplib_do_outline_options_r = r enddef;
1284 enddef;
1285 def mplib_do_outline_text_set_u (text f) text r =1286 def mplib_do_outline_options_f = f enddef;
1287 enddef;
1288 def mplib_do_outline_text_set_d (text d) text r =1289 def mplib_do_outline_options_d = d enddef;
1290 def mplib_do_outline_options_r = r enddef;
1291 enddef;
1292 def mplib_do_outline_text_set_r (text d) (text f) text r =1293 def mplib_do_outline_options_d = d enddef;
1240
1241

40

def mplib_do_outline_options_f = f enddef;
def mplib_do_outline_options_r = r enddef;
1296 enddef;
1297 def mplib_do_outline_text_set_n text r =1298 def mplib_do_outline_options_r = r enddef;
1299 enddef;
1300 def mplib_do_outline_text_set_p = enddef;
1301 def mplib_fill_outline_text =1302 for n=1 upto mpliboutlinenum:
1303
i:=0;
1304
for item within mpliboutlinepic[n]:1305i:=i+1;1306
fill pathpart item mplib_do_outline_options_f withpen pencircle scaled 01307
if (n<mpliboutlinenum) or (i<length mpliboutlinepic[n]): withpostscript "collect"; fi1308
endfor1309 endfor
1310 enddef;
1311 def mplib_draw_outline_text =1312 for n=1 upto mpliboutlinenum:1313
for item within mpliboutlinepic[n]:1314draw pathpart item mplib_do_outline_options_d;
1315
endfor1316 endfor
1317 enddef;
1318 def mplib_filldraw_outline_text =1319 for n=1 upto mpliboutlinenum:
1320
i:=0;
1321
for item within mpliboutlinepic[n]:1322
i:=i+1;1323
if (n<mpliboutlinenum) or (i<length mpliboutlinepic[n]):1324fill pathpart item mplib_do_outline_options_f withpostscript "collect";
1325
else:
1326
draw pathpart item mplib_do_outline_options_f withpostscript "both";
1327
fi
1328
endfor1329 endfor
1330 enddef;
1331 vardef mpliboutlinetext@# (expr t) text rest =1332 save kind; string kind; kind := str @#;1333 save i; numeric i;1334 picture mpliboutlinepic[]; numeric mpliboutlinenum;
1335 def mplib_do_outline_options_d = enddef;
1336 def mplib_do_outline_options_f = enddef;
1337 def mplib_do_outline_options_r = enddef;
1338 runscript("return luamplib.outlinetext[===["&t&"]===]");
1339 image ( addto currentpicture also image (1340
if kind = "f":1341
mplib_do_outline_text_set_f rest;1342
mplib_fill_outline_text;
1343
elseif kind = "d":1344
mplib_do_outline_text_set_d rest;1345
mplib_draw_outline_text;
1346
elseif kind = "b":1347
mplib_do_outline_text_set_b rest;
1294
1295

41

mplib_fill_outline_text;
mplib_draw_outline_text;
1350
elseif kind = "u":1351
mplib_do_outline_text_set_u rest;1352
mplib_filldraw_outline_text;
1353
elseif kind = "r":1354
mplib_do_outline_text_set_r rest;1355
mplib_draw_outline_text;
1356
mplib_fill_outline_text;
1357
elseif kind = "p":1358
mplib_do_outline_text_set_p;1359
mplib_draw_outline_text;
1360
else:
1361
mplib_do_outline_text_set_n rest;1362
mplib_fill_outline_text;1363
fi;1364 ) mplib_do_outline_options_r; )1365 enddef ;1366 primarydef t withpattern p =1367 image(1368
if cycle t:
1369
fill
1370
else:
1371
draw
1372
fi
1373
t withprescript "mplibpattern=" & if numeric p: decimal fi p; )1374 enddef;
1375 vardef mplibtransformmatrix (text e) =1376 save t; transform t;
1377 t = identity e;1378 runscript("luamplib.transformmatrix = {"1379 & decimal xxpart t & ","1380 & decimal yxpart t & ","1381 & decimal xypart t & ","1382 & decimal yypart t & ","1383 & decimal xpart t & ","1384 & decimal ypart t & ","1385 & "}");
1386 enddef;
1387 primarydef p withfademethod s =1388 if picture p:
1389
image(
1390
draw p;1391
draw center p withprescript "mplibfadestate=stop";
1392)
1393 else:1394
p withprescript "mplibfadestate=stop"
1395 fi1396
withprescript "mplibfadetype=" & s1397
withprescript "mplibfadebbox=" &1398decimal (xpart llcorner p -1/4) & ":" &1399
decimal (ypart llcorner p -1/4) & ":" &1400
decimal (xpart urcorner p +1/4) & ":" &1401
decimal (ypart urcorner p +1/4)
1348
1349

42

1402 enddef;
1403 def withfadeopacity (expr a,b) =withprescript "mplibfadeopacity=" &decimal a & ":" &1406
decimal b1407 enddef;
1408 def withfadevector (expr a,b) =1409 withprescript "mplibfadevector=" &1410
decimal xpart a & ":" &1411
decimal ypart a & ":" &1412
decimal xpart b & ":" &1413
decimal ypart b
1414 enddef;
1415 let withfadecenter = withfadevector;
1416 def withfaderadius (expr a,b) =1417 withprescript "mplibfaderadius=" &1418decimal a & ":" &1419
decimal b
1420 enddef;
1421 def withfadebbox (expr a,b) =1422 withprescript "mplibfadebbox=" &1423
decimal xpart a & ":" &1424
decimal ypart a & ":" &1425
decimal xpart b & ":" &1426
decimal ypart b
1427 enddef;
1428 primarydef p asgroup s =1429 image(1430
draw center p
1431
withprescript "mplibgroupbbox=" &1432decimal (xpart llcorner p -1/4) & ":" &1433
decimal (ypart llcorner p -1/4) & ":" &1434
decimal (xpart urcorner p +1/4) & ":" &1435
decimal (ypart urcorner p +1/4)
1436
withprescript "gr_state=start"
1437
withprescript "gr_type=" & s;
1438
draw p;1439
draw center p withprescript "gr_state=stop";
1440 )
1441 enddef;
1442 def withgroupbbox (expr a,b) =1443 withprescript "mplibgroupbbox=" &1444decimal xpart a & ":" &1445
decimal ypart a & ":" &1446
decimal xpart b & ":" &1447
decimal ypart b
1448 enddef;
1449 def withgroupname expr s =1450 withprescript "mplibgroupname=" & s1451 enddef;
1452 def usemplibgroup primary s =1453 draw maketext("\usemplibgroup{" & s & "}")
1454
shifted runscript("return luamplib.trgroupshifts['" & s & "']")
1455 enddef;
1404
1405

43

1456 ]],
1457

legacyverbatimtex = [[1458 def specialVerbatimTeX (text t) = runscript("luamplibprefig{"&t&"}") enddef;
1459 def normalVerbatimTeX(text t) = runscript("luamplibinfig{"&t&"}") enddef;

1460 let VerbatimTeX = specialVerbatimTeX;
1461 extra_beginfig := extra_beginfig & " let VerbatimTeX = normalVerbatimTeX;"&1462"runscript(" &ditto& "luamplib.in_the_fig=true" &ditto& ");";

1463 extra_endfig := extra_endfig & " let VerbatimTeX = specialVerbatimTeX;"&"runscript(" &ditto&"if luamplib.in_the_fig then luamplib.figid=luamplib.figid+1 end "&1466 "luamplib.in_the_fig=false" &ditto& ");";
1467 ]],
1468 textextlabel = [[1469 primarydef s infont f = rawtextext(s) enddef;
1470 def fontsize expr f =1471 begingroup1472 save size; numeric size;
1473 size := mplibdimen("1em");
1474 if size = 0: 10pt else: size fi
1475 endgroup1476 enddef;
1477 ]],
1478 }
1464
1465

1479

When \mplibverbatim is enabled, do not expand mplibcode data.
1480 luamplib.verbatiminput = false

Do not expand btex ... etex, verbatimtex ... etex, and string expressions.
1481 local function protect_expansion (str)

if str thenstr = str:gsub("\\","!!!Control!!!")
1484:gsub("%%","!!!Comment!!!")
1485:gsub("#", "!!!HashSign!!!")
1486:gsub("{", "!!!LBrace!!!")
1487:gsub("}", "!!!RBrace!!!")
1488
return format("\\unexpanded{%s}",str)1489 end
1490 end
1491 local function unprotect_expansion (str)1492 if str then
1493
return str:gsub("!!!Control!!!", "\\")
1494:gsub("!!!Comment!!!", "%%")
1495:gsub("!!!HashSign!!!","#")
1496:gsub("!!!LBrace!!!", "{")
1497
:gsub("!!!RBrace!!!", "}")
1498 end
1499 end
1500 luamplib.everymplib= setmetatable({ [""] = "" },{ __index = function(t) return t[""] end })1501 luamplib.everyendmplib = setmetatable({ [""] = "" },{ __index = function(t) return t[""] end })1502 function luamplib.process_mplibcode (data, instancename)1503 texboxes.localid = 4096
1482
1483

This is needed for legacy behavior1504

if luamplib.legacyverbatimtex then

44

1505
1506
1507
1508
1509
1510

luamplib.figid, tex_code_pre_mplib = 1, {}end
local everymplib= luamplib.everymplib[instancename]local everyendmplib = luamplib.everyendmplib[instancename]data = format("\n%s\n%s\n%s\n",everymplib, data, everyendmplib):gsub("\r","\n")These five lines are needed for mplibverbatim mode.
1511
1512
1513
1514
1515
1516

if luamplib.verbatiminput thendata = data:gsub("\\mpcolor%s+(.-%b{})","mplibcolor(\"%1\")"):gsub("\\mpdim%s+(%b{})", "mplibdimen(\"%1\")"):gsub("\\mpdim%s+(\\%a+)","mplibdimen(\"%1\")"):gsub(btex_etex, "btex %1 etex "):gsub(verbatimtex_etex, "verbatimtex %1 etex;")

If not mplibverbatim, expand mplibcode data, so that users can use TEX codes in it. It has
turned out that no comment sign is allowed.
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529

else
data = data:gsub(btex_etex, function(str)return format("btex %s etex ", protect_expansion(str)) -- spaceend):gsub(verbatimtex_etex, function(str)return format("verbatimtex %s etex;", protect_expansion(str)) -- semicolonend):gsub("\".-\"", protect_expansion):gsub("\\%%", "\0PerCent\0"):gsub("%%.-\n","\n"):gsub("%zPerCent%z", "\\%%")
run_tex_code(format("\\mplibtmptoks\\expandafter{\\expanded{%s}}",data))
data = texgettoks"mplibtmptoks"

Next line to address issue #55:gsub("##", "#")
:gsub("\".-\"", unprotect_expansion)
1532:gsub(btex_etex, function(str)1533
return format("btex %s etex", unprotect_expansion(str))
1534
end)
1535
:gsub(verbatimtex_etex, function(str)1536
return format("verbatimtex %s etex", unprotect_expansion(str))
1537
end)
1538 end
1539 process(data, instancename)1540 end
1530
1531

1541

For parsing prescript materials.
1542 local function script2table(s)local t = {}for _,i in ipairs(s:explode("\13+")) do
1545
local k,v = i:match("(.-)=(.*)") -- v may contain = or empty.
1546
if k and v and k ~= "" and not t[k] then1547
t[k] = v
1548
end1549 end
1550 return t
1551 end
1543
1544

45

1552

pdfliterals will be stored in figcontents table, and written to pdf in one go at the endof the flushing figure. Subtable post is for the legacy behavior.
1553 local figcontents = { post = { } }1554 local function put2output(a,...)
1555

figcontents[#figcontents+1] = type(a) == "string" and format(a,...) or a1556 end
1557 local function pdf_startfigure(n,llx,lly,urx,ury)1558

put2output("\\mplibstarttoPDF{%f}{%f}{%f}{%f}",llx,lly,urx,ury)

1559 end
1560 local function pdf_stopfigure()1561

put2output("\\mplibstoptoPDF")

1562 end

tex.sprint with catcode regime -2, as sometimes # gets doubled in the argument of

pdfliteral.
1563 local function pdf_literalcode (...)
1564

put2output{ -2, format(...) :gsub(decimals,rmzeros) }1565 end
1566 local start_pdf_code = pdfmodeand function() pdf_literalcode"q" endor function() put2output"\\special{pdf:bcontent}" end
1569 local stop_pdf_code = pdfmode1570 and function() pdf_literalcode"Q" end
1571 or function() put2output"\\special{pdf:econtent}" end
1567
1568

1572

Now we process hboxes created from btex ... etex or textext(...) or TEX(...), allbeing the same internally.
1573 local function put_tex_boxes (object,prescript)

local box = prescript.mplibtexboxid:explode":"local n,tw,th = box[1],tonumber(box[2]),tonumber(box[3])1576 if n and tw and th then1577
local op = object.path1578
local first, second, fourth = op[1], op[2], op[4]1579
local tx, ty = first.x_coord, first.y_coord
1580
local sx, rx, ry, sy = 1, 0, 0, 1
1581
if tw ~= 0 then1582sx = (second.x_coord - tx)/tw
1583
rx = (second.y_coord - ty)/tw
1584
if sx == 0 then sx = 0.00001 end
1585
end
1586
if th ~= 0 then1587
sy = (fourth.y_coord - ty)/th1588
ry = (fourth.x_coord - tx)/th1589
if sy == 0 then sy = 0.00001 end
1590
end
1591
start_pdf_code()
1592
pdf_literalcode("%f %f %f %f %f %f cm",sx,rx,ry,sy,tx,ty)
1593
put2output("\\mplibputtextbox{%i}",n)1594
stop_pdf_code()
1595 end
1596 end
1574
1575

1597

46

Colors
1598 local prev_override_color
1599 local function do_preobj_CR(object,prescript)

if object.postscript == "collect" then return end
local override = prescript and prescript.mpliboverridecolor
1602 if override then1603
if pdfmode then1604
pdf_literalcode(override)1605
override = nil
1606
else
1607
put2output("\\special{%s}",override)1608
prev_override_color = override1609
end1610 else
1611
local cs = object.color1612
if cs and #cs > 0 then1613
pdf_literalcode(luamplib.colorconverter(cs))
1614
prev_override_color = nil
1615
elseif not pdfmode then1616
override = prev_override_color
1617
if override then1618
put2output("\\special{%s}",override)
1619
end
1620
end
1621 end
1622 return override
1623 end
1600
1601

1624

For transparency and shading
1625 local pdfmanagement = is_defined'pdfmanagement_add:nnn'
1626 local pdfobjs, pdfetcs = {}, {}1627 pdfetcs.pgfextgs = "pgf@sys@addpdfresource@extgs@plain"
1628 pdfetcs.pgfpattern = "pgf@sys@addpdfresource@patterns@plain"
1629 pdfetcs.pgfcolorspace = "pgf@sys@addpdfresource@colorspaces@plain"
1630 local function update_pdfobjs (os, stream)
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648

local key = os
if stream then key = key..stream end
local on = pdfobjs[key]if on thenreturn on,falseend
if pdfmode thenif stream thenon = pdf.immediateobj("stream",stream,os)
else
on = pdf.immediateobj(os)endelse
on = pdfetcs.cnt or 1
if stream thentexsprint(format("\\special{pdf:stream @mplibpdfobj%s (%s) <<%s>>}",on,stream,os))
else
texsprint(format("\\special{pdf:obj @mplibpdfobj%s %s}",on,os))

47

end
pdfetcs.cnt = on + 1
1651 end
1652 pdfobjs[key] = on
1653 return on,true
1654 end
1655 pdfetcs.resfmt = pdfmode and "%s 0 R" or "@mplibpdfobj%s"1656 if pdfmode then
1657 pdfetcs.getpageres = pdf.getpageresources or function() return pdf.pageresources end
1658 local getpageres = pdfetcs.getpageres
1659 local setpageres = pdf.setpageresources or function(s) pdf.pageresources = s end
1660 local initialize_resources = function (name)1661
local tabname = format("%s_res",name)1662
pdfetcs[tabname] = { }1663
if luatexbase.callbacktypes.finish_pdffile then -- ltluatex
1664
local obj = pdf.reserveobj()
1665
setpageres(format("%s/%s %i 0 R", getpageres() or "", name, obj))
1666
luatexbase.add_to_callback("finish_pdffile", function()
1667
pdf.immediateobj(obj, format("<<%s>>", tableconcat(pdfetcs[tabname])))
1668
end,
1669
format("luamplib.%s.finish_pdffile",name))
1670
end
1671 end
1672 pdfetcs.fallback_update_resources = function (name, res)1673
local tabname = format("%s_res",name)1674
if not pdfetcs[tabname] then
1675
initialize_resources(name)
1676
end
1677
if luatexbase.callbacktypes.finish_pdffile then1678
local t = pdfetcs[tabname]1679
t[#t+1] = res
1680
else
1681
local tpr, n = getpageres() or "", 01682
tpr, n = tpr:gsub(format("/%s<<",name), "%1"..res)1683
if n == 0 then1684
tpr = format("%s/%s<<%s>>", tpr, name, res)
1685
end
1686
setpageres(tpr)1687
end1688 end
1689 else
1690 texsprint {1691"\\luamplibatfirstshipout{",
1692"\\special{pdf:obj @MPlibTr<<>>}",
1693"\\special{pdf:obj @MPlibSh<<>>}",
1694"\\special{pdf:obj @MPlibCS<<>>}",
1695"\\special{pdf:obj @MPlibPt<<>>}}",
1696 }
1697 pdfetcs.resadded = { }1698 pdfetcs.fallback_update_resources = function (name,res,obj)
1699
texsprint{"\\special{pdf:put ", obj, " <<", res, ">>}"}
1700
if not pdfetcs.resadded[name] then1701
texsprint{"\\luamplibateveryshipout{\\special{pdf:put @resources <</", name, " ", obj, ">>}}"}
1702
pdfetcs.resadded[name] = obj
1649
1650

48

end
end
1705 end
1703
1704
1706

Transparency
1707 local transparancy_modes = { [0] = "Normal",
1708
1709
1710
1711
1712

"Normal","SoftLight","Darken","Hue","Compatible","Multiply","HardLight","Lighten","Saturation","Screen","ColorDodge","Difference","Color","Overlay","ColorBurn","Exclusion","Luminosity",

1713 }
1714 local function add_extgs_resources (on, new)

local key = format("MPlibTr%s", on)
if new then1717
local val = format(pdfetcs.resfmt, on)1718
if pdfmanagement then1719
texsprint {1720"\\csname pdfmanagement_add:nnn\\endcsname{Page/Resources/ExtGState}{", key, "}{", val, "}"
1721
}
1722
else
1723
local tr = format("/%s %s", key, val)1724
if is_defined(pdfetcs.pgfextgs) then1725
texsprint { "\\csname ", pdfetcs.pgfextgs, "\\endcsname{", tr, "}" }1726
elseif is_defined"TRP@list" then1727
texsprint(catat11,{1728[[\if@filesw\immediate\write\@auxout{]],1729[[\string\g@addto@macro\string\TRP@list{]],
1730
tr,
1731
[[}}\fi]],
1732
})
1733
if not get_macro"TRP@list":find(tr) then1734
texsprint(catat11,[[\global\TRP@reruntrue]])
1735
end
1736
else
1737
pdfetcs.fallback_update_resources("ExtGState",tr,"@MPlibTr")
1738
end
1739
end
1740 end
1741 return key
1742 end
1743 local function do_preobj_TR(object,prescript)
1744 if object.postscript == "collect" then return end
1745 local opaq = prescript and prescript.tr_transparency
1746 if opaq then1747
local key, on, os, new1748
local mode = prescript.tr_alternative or 1
1749
mode = transparancy_modes[tonumber(mode)] or mode
1750
opaq = format("%.3f", opaq) :gsub(decimals,rmzeros)1751
for i,v in ipairs{ {mode,opaq},{"Normal",1} } do1752
os = format("<</BM/%s/ca %s/CA %s/AIS false>>",v[1],v[2],v[2])1753
on, new = update_pdfobjs(os)1754
key = add_extgs_resources(on,new)1755
if i == 1 then
1715
1716

49

pdf_literalcode("/%s gs",key)
else
1758
return format("/%s gs",key)
1759
end
1760
end
1761 end
1762 end
1756
1757

1763

Shading with metafun format.
1764 local function sh_pdfpageresources(shtype,domain,colorspace,ca,cb,coordinates,steps,fractions)
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808

for _,v in ipairs{ca,cb} dofor i,vv in ipairs(v) dofor ii,vvv in ipairs(vv) do
v[i][ii] = tonumber(vvv) and format("%.3f",vvv) or vvv
end
end
end
local fun2fmt,os = "<</FunctionType 2/Domain[%s]/C0[%s]/C1[%s]/N 1>>"
if steps > 1 thenlocal list,bounds,encode = { },{ },{ }for i=1,steps do
if i < steps thenbounds[i] = format("%.3f", fractions[i] or 1)
end
encode[2*i-1] = 0
encode[2*i] = 1
os = fun2fmt:format(domain,tableconcat(ca[i],' '),tableconcat(cb[i],' ')):gsub(decimals,rmzeros)
list[i] = format(pdfetcs.resfmt, update_pdfobjs(os))
end
os = tableconcat {"<</FunctionType 3",
format("/Bounds[%s]",tableconcat(bounds,' ')),
format("/Encode[%s]",tableconcat(encode,' ')),
format("/Functions[%s]", tableconcat(list, ' ')),
format("/Domain[%s]>>", domain),} :gsub(decimals,rmzeros)else
os = fun2fmt:format(domain,tableconcat(ca[1],' '),tableconcat(cb[1],' ')):gsub(decimals,rmzeros)end
local objref = format(pdfetcs.resfmt, update_pdfobjs(os))
os = tableconcat {format("<</ShadingType %i", shtype),
format("/ColorSpace %s",colorspace),
format("/Function %s",objref),format("/Coords[%s]",
coordinates),"/Extend[true true]/AntiAlias true>>",} :gsub(decimals,rmzeros)
local on, new = update_pdfobjs(os)if new thenlocal key, val = format("MPlibSh%s", on), format(pdfetcs.resfmt, on)if pdfmanagement thentexsprint {50

"\\csname pdfmanagement_add:nnn\\endcsname{Page/Resources/Shading}{", key, "}{", val, "}"
}
1811
else
1812
local res = format("/%s %s", key, val)1813
pdfetcs.fallback_update_resources("Shading",res,"@MPlibSh")
1814
end1815 end
1816 return on
1817 end
1818 local function color_normalize(ca,cb)
1819 if #cb == 1 then1820
if #ca == 4 then1821
cb[1], cb[2], cb[3], cb[4] = 0, 0, 0, 1-cb[1]1822else -- #ca = 3
1823
cb[1], cb[2], cb[3] = cb[1], cb[1], cb[1]1824
end1825 elseif #cb == 3 then -- #ca == 41826
cb[1], cb[2], cb[3], cb[4] = 1-cb[1], 1-cb[2], 1-cb[3], 01827 end
1828 end
1829 pdfetcs.clrspcs = setmetatable({ }, { __index = function(t,names)
1830 run_tex_code({1831[[\color_model_new:nnn]],
1832
format("{mplibcolorspace_%s}", names:gsub(",","_")),
1833
format("{DeviceN}{names={%s}}", names),
1834[[\edef\mplib_@tempa{\pdf_object_ref_last:}]],
1835 }, ccexplat)
1836 local colorspace = get_macro'mplib_@tempa'
1837 t[names] = colorspace
1838 return colorspace
1839 end })1840 local function do_preobj_SH(object,prescript)
1841 local shade_no1842 local sh_type = prescript and prescript.sh_type
1843 if not sh_type then1844
return1845 else
1846
local domain = prescript.sh_domain or "0 1"1847
local centera = (prescript.sh_center_a or "0 0"):explode()1848
local centerb = (prescript.sh_center_b or "0 0"):explode()1849
local transform = prescript.sh_transform == "yes"1850
local sx,sy,sr,dx,dy = 1,1,1,0,0
1851
if transform then1852
local first = (prescript.sh_first or "0 0"):explode()1853
local setx = (prescript.sh_set_x or "0 0"):explode()1854
local sety = (prescript.sh_set_y or "0 0"):explode()
1855
local x,y = tonumber(setx[1]) or 0, tonumber(sety[1]) or 0
1856
if x ~= 0 and y ~= 0 then1857
local path = object.path
1858
local path1x = path[1].x_coord
1859
local path1y = path[1].y_coord
1860
local path2x = path[x].x_coord
1861
local path2y = path[y].y_coord
1862
local dxa = path2x - path1x
1809
1810

51

1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916

local dya = path2y - path1y
local dxb = setx[2] - first[1]local dyb = sety[2] - first[2]if dxa ~= 0 and dya ~= 0 and dxb ~= 0 and dyb ~= 0 thensx = dxa / dxb ; if sx < 0 then sx = - sx end
sy = dya / dyb ; if sy < 0 then sy = - sy end
sr = math.sqrt(sx^2 + sy^2)dx = path1x - sx*first[1]dy = path1y - sy*first[2]
end
end
end
local ca, cb, colorspace, steps, fractions
ca = { (prescript.sh_color_a_1 or prescript.sh_color_a or "0"):explode":" }cb = { (prescript.sh_color_b_1 or prescript.sh_color_b or "1"):explode":" }steps = tonumber(prescript.sh_step) or 1
if steps > 1 thenfractions = { prescript.sh_fraction_1 or 0 }for i=2,steps do
fractions[i] = prescript[format("sh_fraction_%i",i)] or (i/steps)
ca[i] = (prescript[format("sh_color_a_%i",i)] or "0"):explode":"cb[i] = (prescript[format("sh_color_b_%i",i)] or "1"):explode":"end
end
if prescript.mplib_spotcolor thenca, cb = { }, { }local names, pos, objref = { }, -1, ""local script = object.prescript:explode"\13+"for i=#script,1,-1 do
if script[i]:find"mplib_spotcolor" thenlocal t, name, value = script[i]:explode"="[2]:explode":"value, objref, name = t[1], t[2], t[3]if not names[name] thenpos = pos+1
names[name] = pos
names[#names+1] = nameendt = { }
for j=1,names[name] do t[#t+1] = 0 end
t[#t+1] = value
tableinsert(#ca == #cb and ca or cb, t)
end
end
for _,t in ipairs{ca,cb} dofor _,tt in ipairs(t) dofor i=1,#names-#tt do tt[#tt+1] = 0 end
end
end
if #names == 1 thencolorspace = objref
else
colorspace = pdfetcs.clrspcs[ tableconcat(names,",") ]
end
else

52

local model = 0
for _,t in ipairs{ca,cb} do
1919
for _,tt in ipairs(t) do
1920
model = model > #tt and model or #tt
1921
end
1922
end
1923
for _,t in ipairs{ca,cb} do
1924
for _,tt in ipairs(t) do
1925
if #tt < model then1926
color_normalize(model == 4 and {1,1,1,1} or {1,1,1},tt)
1927
end
1928
end
1929
end
1930
colorspace = model == 4 and "/DeviceCMYK"
1931
or model == 3 and "/DeviceRGB"
1932
or model == 1 and "/DeviceGray"1933
or err"unknown color model"
1934
end
1935
if sh_type == "linear" then1936
local coordinates = format("%f %f %f %f",
1937
dx + sx*centera[1], dy + sy*centera[2],1938
dx + sx*centerb[1], dy + sy*centerb[2])1939
shade_no = sh_pdfpageresources(2,domain,colorspace,ca,cb,coordinates,steps,fractions)1940
elseif sh_type == "circular" then1941
local factor = prescript.sh_factor or 1
1942
local radiusa = factor * prescript.sh_radius_a
1943
local radiusb = factor * prescript.sh_radius_b
1944
local coordinates = format("%f %f %f %f %f %f",1945
dx + sx*centera[1], dy + sy*centera[2], sr*radiusa,
1946
dx + sx*centerb[1], dy + sy*centerb[2], sr*radiusb)
1947
shade_no = sh_pdfpageresources(3,domain,colorspace,ca,cb,coordinates,steps,fractions)
1948
else
1949
err"unknown shading type"1950
end1951 end
1952 return shade_no1953 end
1917
1918

1954

Patterns
1955 pdfetcs.patterns = { }1956 local function gather_resources (optres)
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969

local t, do_pattern = { }, not optres
local names = {"ExtGState","ColorSpace","Shading"}
if do_pattern thennames[#names+1] = "Pattern"end
if pdfmode thenif pdfmanagement thenfor _,v in ipairs(names) do
local pp = get_macro(format("g__pdfdict_/g__pdf_Core/Page/Resources/%s_prop",v))if pp and pp:find"__prop_pair" thent[#t+1] = format("/%s %s 0 R", v, ltx.pdf.object_id("__pdf/Page/Resources/"..v))
end
end

53

else
local res = pdfetcs.getpageres() or ""1972
run_tex_code[[\mplibtmptoks\expandafter{\the\pdfvariable pageresources}]]1973
res = res .. texgettoks'mplibtmptoks'
1974
if do_pattern then return res end
1975
res = res:explode"/+"1976
for _,v in ipairs(res) do
1977
v = v:match"^%s*(.-)%s*$"1978
if not v:find"Pattern" and not optres:find(v) then1979
t[#t+1] = "/" .. v
1980
end
1981
end
1982
end
1983 else
1984
if pdfmanagement then1985
for _,v in ipairs(names) do
1986
local pp = get_macro(format("g__pdfdict_/g__pdf_Core/Page/Resources/%s_prop",v))1987
if pp and pp:find"__prop_pair" then1988
run_tex_code {1989"\\mplibtmptoks\\expanded{{",1990
format("/%s \\csname pdf_object_ref:n\\endcsname{__pdf/Page/Resources/%s}",v,v),
1991
"}}",
1992
}
1993
t[#t+1] = texgettoks'mplibtmptoks'
1994
end
1995
end
1996
elseif is_defined(pdfetcs.pgfextgs) then1997
run_tex_code ({1998"\\mplibtmptoks\\expanded{{",1999"\\ifpgf@sys@pdf@extgs@exists /ExtGState @pgfextgs\\fi",2000"\\ifpgf@sys@pdf@colorspaces@exists /ColorSpace @pgfcolorspaces\\fi",2001
do_pattern and "\\ifpgf@sys@pdf@patterns@exists /Pattern @pgfpatterns \\fi" or "",
2002
"}}",
2003
}, catat11)
2004
t[#t+1] = texgettoks'mplibtmptoks'
2005
else
2006
for _,v in ipairs(names) do
2007
local vv = pdfetcs.resadded[v]2008
if vv then2009
t[#t+1] = format("/%s %s", v, vv)
2010
end
2011
end
2012
end
2013 end
2014 return tableconcat(t)2015 end
2016 function luamplib.registerpattern ( boxid, name, opts )2017 local box = texgetbox(boxid)2018 local wd = format("%.3f",box.width/factor)
2019 local hd = format("%.3f",(box.height+box.depth)/factor)
2020 info("w/h/d of pattern '%s': %s 0", name, format("%s %s",wd, hd):gsub(decimals,rmzeros))2021 if opts.xstep == 0 then opts.xstep = nil end
2022 if opts.ystep == 0 then opts.ystep = nil end
2023 if opts.colored == nil then
1970
1971

54

opts.colored = opts.coloured
if opts.colored == nil then2026
opts.colored = true2027
end2028 end
2029 if type(opts.matrix) == "table" then opts.matrix = tableconcat(opts.matrix," ") end
2030 if type(opts.bbox) == "table" then opts.bbox = tableconcat(opts.bbox," ") end
2031 if opts.matrix and opts.matrix:find"%a" then2032
local data = format("mplibtransformmatrix(%s);",opts.matrix)
2033
process(data,"@mplibtransformmatrix")2034
local t = luamplib.transformmatrix
2035
opts.matrix = format("%f %f %f %f", t[1], t[2], t[3], t[4])2036
opts.xshift = opts.xshift or format("%f",t[5])2037
opts.yshift = opts.yshift or format("%f",t[6])2038 end
2039 local attr = {2040"/Type/Pattern",
2041"/PatternType 1",
2042
format("/PaintType %i", opts.colored and 1 or 2),
2043"/TilingType 2",
2044
format("/XStep %s", opts.xstep or wd),
2045
format("/YStep %s", opts.ystep or hd),
2046
format("/Matrix[%s %s %s]", opts.matrix or "1 0 0 1", opts.xshift or 0, opts.yshift or 0),2047 }
2048 local optres = opts.resources or ""2049 optres = optres .. gather_resources(optres)
2050 local patterns = pdfetcs.patterns
2051 if pdfmode then2052
if opts.bbox then2053
attr[#attr+1] = format("/BBox[%s]", opts.bbox)
2054
end
2055
attr = tableconcat(attr) :gsub(decimals,rmzeros)
2056
local index = tex.saveboxresource(boxid, attr, optres, true, opts.bbox and 4 or 1)
2057
patterns[name] = { id = index, colored = opts.colored }2058 else
2059
local cnt = #patterns + 1
2060
local objname = "@mplibpattern" .. cnt
2061
local metric = format("bbox %s", opts.bbox or format("0 0 %s %s",wd,hd))2062
texsprint {2063"\\expandafter\\newbox\\csname luamplib.patternbox.", cnt, "\\endcsname",
2064"\\global\\setbox\\csname luamplib.patternbox.", cnt, "\\endcsname",
2065"\\hbox{\\unhbox ", boxid, "}\\luamplibatnextshipout{",
2066"\\special{pdf:bcontent}",
2067"\\special{pdf:bxobj ", objname, " ", metric, "}",
2068"\\raise\\dp\\csname luamplib.patternbox.", cnt, "\\endcsname",2069"\\box\\csname luamplib.patternbox.", cnt, "\\endcsname",
2070"\\special{pdf:put @resources <<", optres, ">>}",
2071"\\special{pdf:exobj <<", tableconcat(attr), ">>}",
2072"\\special{pdf:econtent}}",
2073
}
2074
patterns[cnt] = objname
2075
patterns[name] = { id = cnt, colored = opts.colored }2076 end
2077 end
2024
2025

55

2078 local function pattern_colorspace (cs)local on, new = update_pdfobjs(format("[/Pattern %s]", cs))
if new then2081
local key, val = format("MPlibCS%i",on), format(pdfetcs.resfmt,on)
2082
if pdfmanagement then2083
texsprint {2084"\\csname pdfmanagement_add:nnn\\endcsname{Page/Resources/ColorSpace}{", key, "}{", val, "}"
2085
}
2086
else
2087
local res = format("/%s %s", key, val)
2088
if is_defined(pdfetcs.pgfcolorspace) then2089
texsprint { "\\csname ", pdfetcs.pgfcolorspace, "\\endcsname{", res, "}" }
2090
else
2091
pdfetcs.fallback_update_resources("ColorSpace",res,"@MPlibCS")
2092
end
2093
end
2094 end
2095 return on
2096 end
2097 local function do_preobj_PAT(object, prescript)
2098 local name = prescript and prescript.mplibpattern
2099 if not name then return end
2100 local patterns = pdfetcs.patterns
2101 local patt = patterns[name]2102 local index = patt and patt.id or err("cannot get pattern object '%s'", name)2103 local key = format("MPlibPt%s",index)
2104 if patt.colored then2105
pdf_literalcode("/Pattern cs /%s scn", key)
2106 else
2107
local color = prescript.mpliboverridecolor2108
if not color then2109
local t = object.color
2110
color = t and #t>0 and luamplib.colorconverter(t)
2111
end
2112
if not color then return end
2113
local cs
2114
if color:find" cs " or color:find"@pdf.obj" then2115
local t = color:explode()
2116
if pdfmode then2117
cs = format("%s 0 R", ltx.pdf.object_id( t[1]:sub(2,-1) ))2118
color = t[3]
2119
else
2120
cs = t[2]2121color = t[3]:match"%[(.+)%]"
2122
end
2123
else
2124
local t = colorsplit(color)2125
cs = #t == 4 and "/DeviceCMYK" or #t == 3 and "/DeviceRGB" or "/DeviceGray"
2126
color = tableconcat(t," ")
2127
end
2128
pdf_literalcode("/MPlibCS%i cs %s /%s scn", pattern_colorspace(cs), color, key)2129 end
2130 if not patt.done then2131
local val = pdfmode and format("%s 0 R",index) or patterns[index]
2079
2080

56

if pdfmanagement thentexsprint {2134"\\csname pdfmanagement_add:nnn\\endcsname{Page/Resources/Pattern}{", key, "}{", val, "}"
2135
}
2136
else
2137
local res = format("/%s %s", key, val)2138
if is_defined(pdfetcs.pgfpattern) then2139
texsprint { "\\csname ", pdfetcs.pgfpattern, "\\endcsname{", res, "}" }
2140
else
2141
pdfetcs.fallback_update_resources("Pattern",res,"@MPlibPt")
2142
end
2143
end
2144 end
2145 patt.done = true
2146 end
2132
2133

2147

Fading2148 pdfetcs.fading = { }2149 local function do_preobj_FADE (object, prescript)
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184

local fd_type = prescript and prescript.mplibfadetypelocal fd_stop = prescript and prescript.mplibfadestate
if not fd_type thenreturn fd_stop -- returns "stop" (if picture) or nil
end
local bbox = prescript.mplibfadebbox:explode":"local dx, dy = -bbox[1], -bbox[2]local vec = prescript.mplibfadevector; vec = vec and vec:explode":"if not vec thenif fd_type == "linear" thenvec = {bbox[1], bbox[2], bbox[3], bbox[2]} -- left to rightelse
local centerx, centery = (bbox[1]+bbox[3])/2, (bbox[2]+bbox[4])/2
vec = {centerx, centery, centerx, centery} -- center for both circles
end
end
local coords = { vec[1]+dx, vec[2]+dy, vec[3]+dx, vec[4]+dy }if fd_type == "linear" thencoords = format("%f %f %f %f", tableunpack(coords))
elseif fd_type == "circular" thenlocal width, height = bbox[3]-bbox[1], bbox[4]-bbox[2]local radius = (prescript.mplibfaderadius or "0:"..math.sqrt(width^2+height^2)/2):explode":"tableinsert(coords, 3, radius[1])tableinsert(coords, radius[2])coords = format("%f %f %f %f %f %f", tableunpack(coords))
else
err("unknown fading method '%s'", fd_type)end
fd_type = fd_type == "linear" and 2 or 3
local opaq = (prescript.mplibfadeopacity or "1:0"):explode":"local on, os, new
on = sh_pdfpageresources(fd_type, "0 1", "/DeviceGray", {{opaq[1]}}, {{opaq[2]}}, coords, 1)
os = format("<</PatternType 2/Shading %s>>", format(pdfetcs.resfmt, on))
on = update_pdfobjs(os)bbox = format("0 0 %f %f", bbox[3]+dx, bbox[4]+dy)

57

local streamtext = format("q /Pattern cs/MPlibFd%s scn %s re f Q", on, bbox):gsub(decimals,rmzeros)
2187 os = format("<</Pattern<</MPlibFd%s %s>>>>", on, format(pdfetcs.resfmt, on))2188 on = update_pdfobjs(os)2189 local resources = format(pdfetcs.resfmt, on)2190 on = update_pdfobjs"<</S/Transparency/CS/DeviceGray>>"2191 local attr = tableconcat{
2192"/Subtype/Form",
2193"/BBox[", bbox, "]",
2194"/Matrix[1 0 0 1 ", format("%f %f", -dx,-dy), "]",
2195"/Resources ", resources,
2196"/Group ", format(pdfetcs.resfmt, on),2197 } :gsub(decimals,rmzeros)
2198 on = update_pdfobjs(attr, streamtext)
2199 os = "<</SMask<</S/Luminosity/G " .. format(pdfetcs.resfmt, on) .. ">>>>"
2200 on, new = update_pdfobjs(os)2201 local key = add_extgs_resources(on,new)2202 start_pdf_code()
2203 pdf_literalcode("/%s gs", key)
2204 if fd_stop then return "standalone" end
2205 return "start"
2206 end
2185
2186

2207

Transparency Group
2208 pdfetcs.tr_group = { shifts = { } }2209 luamplib.trgroupshifts = pdfetcs.tr_group.shifts
2210 local function do_preobj_GRP (object, prescript)
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237

local grstate = prescript and prescript.gr_state
if not grstate then return end
local trgroup = pdfetcs.tr_groupif grstate == "start" thentrgroup.name = prescript.mplibgroupname or "lastmplibgroup"
trgroup.isolated, trgroup.knockout = false, false
for _,v in ipairs(prescript.gr_type:explode",+") dotrgroup[v] = trueend
trgroup.bbox = prescript.mplibgroupbbox:explode":"put2output[[\begingroup\setbox\mplibscratchbox\hbox\bgroup]]
elseif grstate == "stop" thenlocal llx,lly,urx,ury = tableunpack(trgroup.bbox)
put2output(tableconcat{"\\egroup",
format("\\wd\\mplibscratchbox %fbp", urx-llx),
format("\\ht\\mplibscratchbox %fbp", ury-lly),"\\dp\\mplibscratchbox 0pt",})
local grattr = format("/Group<</S/Transparency/I %s/K %s>>",trgroup.isolated,trgroup.knockout)
local res = gather_resources()
local bbox = format("%f %f %f %f", llx,lly,urx,ury) :gsub(decimals,rmzeros)
if pdfmode thenput2output(tableconcat{"\\saveboxresource type 2 attr{/Type/XObject/Subtype/Form/FormType 1","/BBox[", bbox, "]", grattr, "} resources{", res, "}\\mplibscratchbox",[[\setbox\mplibscratchbox\hbox{\useboxresource\lastsavedboxresourceindex}]],58

[[\wd\mplibscratchbox 0pt\ht\mplibscratchbox 0pt\dp\mplibscratchbox 0pt]],[[\box\mplibscratchbox\endgroup]],
2240"\\expandafter\\xdef\\csname luamplib.group.", trgroup.name, "\\endcsname{",2241"\\noexpand\\mplibstarttoPDF{",llx,"}{",lly,"}{",urx,"}{",ury,"}",
2242"\\useboxresource \\the\\lastsavedboxresourceindex\\noexpand\\mplibstoptoPDF}",
2243
})
2244
else
2245
trgroup.cnt = (trgroup.cnt or 0) + 1
2246
local objname = format("@mplibtrgr%s", trgroup.cnt)
2247
put2output(tableconcat{
2248"\\special{pdf:bxobj ", objname, " bbox ", bbox, "}",
2249"\\unhbox\\mplibscratchbox",
2250"\\special{pdf:put @resources <<", res, ">>}",
2251"\\special{pdf:exobj <<", grattr, ">>}",
2252"\\special{pdf:uxobj ", objname, "}\\endgroup",
2253
})
2254
token.set_macro("luamplib.group."..trgroup.name, tableconcat{
2255"\\mplibstarttoPDF{",llx,"}{",lly,"}{",urx,"}{",ury,"}",
2256"\\special{pdf:uxobj ", objname, "}\\mplibstoptoPDF",
2257}, "global")
2258
end
2259
trgroup.shifts[trgroup.name] = { llx, lly }2260 end
2261 return grstate2262 end
2263 function luamplib.registergroup (boxid, name, opts)
2264 local box = texgetbox(boxid)
2265 local wd, ht, dp = node.getwhd(box)2266 local res = (opts.resources or "") .. gather_resources()
2267 local attr = { "/Type/XObject/Subtype/Form/FormType 1" }2268 if type(opts.matrix) == "table" then opts.matrix = tableconcat(opts.matrix," ") end
2269 if type(opts.bbox) == "table" then opts.bbox = tableconcat(opts.bbox," ") end
2270 if opts.matrix and opts.matrix:find"%a" then2271
local data = format("mplibtransformmatrix(%s);",opts.matrix)
2272
process(data,"@mplibtransformmatrix")
2273
opts.matrix = format("%f %f %f %f %f %f",tableunpack(luamplib.transformmatrix))
2274 end
2275 local grtype = 32276 if opts.bbox then2277
attr[#attr+1] = format("/BBox[%s]", opts.bbox)
2278
grtype = 22279 end
2280 if opts.matrix then2281
attr[#attr+1] = format("/Matrix[%s]", opts.matrix)
2282
grtype = opts.bbox and 4 or 1
2283 end
2284 if opts.asgroup then2285
local t = { isolated = false, knockout = false }2286
for _,v in ipairs(opts.asgroup:explode",+") do t[v] = true end
2287
attr[#attr+1] = format("/Group<</S/Transparency/I %s/K %s>>", t.isolated, t.knockout)
2288 end
2289 local trgroup = pdfetcs.tr_group2290 trgroup.shifts[name] = { get_macro'MPllx', get_macro'MPlly' }2291 local whd
2238
2239

59

if pdfmode thenattr = tableconcat(attr) :gsub(decimals,rmzeros)2294
local index = tex.saveboxresource(boxid, attr, res, true, grtype)2295
token.set_macro("luamplib.group."..name, "\\useboxresource "..index, "global")2296
whd = format("%.3f %.3f 0", wd/factor, (ht+dp)/factor) :gsub(decimals,rmzeros)
2297 else
2298
trgroup.cnt = (trgroup.cnt or 0) + 1
2299
local objname = format("@mplibtrgr%s", trgroup.cnt)
2300
texsprint {2301"\\expandafter\\newbox\\csname luamplib.groupbox.", trgroup.cnt, "\\endcsname",
2302"\\global\\setbox\\csname luamplib.groupbox.", trgroup.cnt, "\\endcsname",2303"\\hbox{\\unhbox ", boxid, "}\\luamplibatnextshipout{",
2304"\\special{pdf:bcontent}",
2305"\\special{pdf:bxobj ", objname, " width ", wd, "sp height ", ht, "sp depth ", dp, "sp}",
2306"\\unhbox\\csname luamplib.groupbox.", trgroup.cnt, "\\endcsname",
2307"\\special{pdf:put @resources <<", res, ">>}",
2308"\\special{pdf:exobj <<", tableconcat(attr), ">>}",
2309"\\special{pdf:econtent}}",
2310
}
2311
token.set_macro("luamplib.group."..name, tableconcat{
2312"\\begingroup\\setbox\\mplibscratchbox\\hbox{\\special{pdf:uxobj ", objname, "}}",
2313"\\wd\\mplibscratchbox ", wd, "sp",2314"\\ht\\mplibscratchbox ", ht, "sp",2315"\\dp\\mplibscratchbox ", dp, "sp",2316"\\box\\mplibscratchbox\\endgroup",2317}, "global")2318
whd = format("%.3f %.3f %.3f", wd/factor, ht/factor, dp/factor) :gsub(decimals,rmzeros)2319 end
2320 info("w/h/d of group '%s': %s", name, whd)
2321 end
2292
2293

2322
2323 local function stop_special_effects(fade,opaq,over)

if fade then -- fading
stop_pdf_code()
2326 end
2327 if opaq then -- opacity2328
pdf_literalcode(opaq)
2329 end
2330 if over then -- color2331
put2output"\\special{pdf:ec}"2332 end
2333 end
2324
2325

2334

Codes below for inserting PDF lieterals are mostly from ConTeXt general, with smallchanges when needed.
2335 local function getobjects(result,figure,f)2336

return figure:objects()

2337 end
2338
2339 function luamplib.convert (result, flusher)

luamplib.flush(result, flusher)return true -- done
2342 end
2340
2341

60

2343
2344 local function pdf_textfigure(font,size,text,width,height,depth)

text = text:gsub(".",function(c)return format("\\hbox{\\char%i}",string.byte(c)) -- kerning happens in metapost : false
2347 end)
2348 put2output("\\mplibtextext{%s}{%f}{%s}{%s}{%s}",font,size,text,0,0)2349 end
2345
2346

2350
2351 local bend_tolerance = 131/65536
2352
2353 local rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 12354
2355 local function pen_characteristics(object)

local t = mplib.pen_info(object)
rx, ry, sx, sy, tx, ty = t.rx, t.ry, t.sx, t.sy, t.tx, t.ty
2358 divider = sx*sy - rx*ry2359 return not (sx==1 and rx==0 and ry==0 and sy==1 and tx==0 and ty==0), t.width2360 end
2356
2357

2361
2362 local function concat(px, py) -- no tx, ty here
2363

return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider

2364 end
2365
2366 local function curved(ith,pth)

local d = pth.left_x - ith.right_x
if abs(ith.right_x - ith.x_coord - d) <= bend_tolerance and abs(pth.x_coord - pth.left_x - d) <= bend_tolerance th2369
d = pth.left_y - ith.right_y
2370
if abs(ith.right_y - ith.y_coord - d) <= bend_tolerance and abs(pth.y_coord - pth.left_y - d) <= bend_tolerance
2371
return false
2372
end
2373 end
2374 return true
2375 end
2367
2368

2376
2377 local function flushnormalpath(path,open)
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396

local pth, ith
for i=1,#path do
pth = path[i]if not ith then
pdf_literalcode("%f %f m",pth.x_coord,pth.y_coord)
elseif curved(ith,pth) then
pdf_literalcode("%f %f %f %f %f %f c",ith.right_x,ith.right_y,pth.left_x,pth.left_y,pth.x_coord,pth.y_coord)else
pdf_literalcode("%f %f l",pth.x_coord,pth.y_coord)
end
ith = pthend
if not open thenlocal one = path[1]if curved(pth,one) then
pdf_literalcode("%f %f %f %f %f %f c",pth.right_x,pth.right_y,one.left_x,one.left_y,one.x_coord,one.y_coord )else
pdf_literalcode("%f %f l",one.x_coord,one.y_coord)end61elseif #path == 1 then -- special case .. draw point
local one = path[1]2399
pdf_literalcode("%f %f l",one.x_coord,one.y_coord)
2400 end
2401 end
2397
2398

2402
2403 local function flushconcatpath(path,open)

pdf_literalcode("%f %f %f %f %f %f cm", sx, rx, ry, sy, tx ,ty)
local pth, ith
2406 for i=1,#path do2407
pth = path[i]2408
if not ith then2409
pdf_literalcode("%f %f m",concat(pth.x_coord,pth.y_coord))
2410
elseif curved(ith,pth) then2411
local a, b = concat(ith.right_x,ith.right_y)2412
local c, d = concat(pth.left_x,pth.left_y)2413
pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concat(pth.x_coord, pth.y_coord))
2414
else
2415
pdf_literalcode("%f %f l",concat(pth.x_coord, pth.y_coord))
2416
end
2417
ith = pth2418 end
2419 if not open then2420
local one = path[1]2421
if curved(pth,one) then2422
local a, b = concat(pth.right_x,pth.right_y)2423
local c, d = concat(one.left_x,one.left_y)2424
pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concat(one.x_coord, one.y_coord))
2425
else
2426
pdf_literalcode("%f %f l",concat(one.x_coord,one.y_coord))
2427
end
2428 elseif #path == 1 then -- special case .. draw point2429
local one = path[1]2430
pdf_literalcode("%f %f l",concat(one.x_coord,one.y_coord))
2431 end
2432 end
2404
2405

2433

Finally, flush figures by inserting PDF literals.
2434 function luamplib.flush (result,flusher)
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446

if result thenlocal figures = result.fig
if figures thenfor f=1, #figures doinfo("flushing figure %s",f)local figure = figures[f]local objects = getobjects(result,figure,f)
local fignum = tonumber(figure:filename():match("([%d]+)$") or figure:charcode() or 0)
local miterlimit, linecap, linejoin, dashed = -1, -1, -1, false
local bbox = figure:boundingbox()
local llx, lly, urx, ury = bbox[1], bbox[2], bbox[3], bbox[4] -- faster than unpack
if urx < llx thenluamplib silently ignores this invalid figure for those that do not contain beginfig ... endfig.

62

(issue #70) Original code of ConTeXt general was:-- invalid
pdf_startfigure(fignum,0,0,0,0)
pdf_stopfigure()
2447

else

For legacy behavior, insert ‘pre-fig’ TEX code here.
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458

if tex_code_pre_mplib[f] thenput2output(tex_code_pre_mplib[f])end
pdf_startfigure(fignum,llx,lly,urx,ury)
start_pdf_code()
if objects thenlocal savedpath = nil
local savedhtap = nil
for o=1,#objects do
local object= objects[o]local objecttype= object.type

The following 9 lines are part of btex...etex patch. Again, colors are processed at thisstage.
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477

local prescript= object.prescript
prescript = prescript and script2table(prescript) -- prescript is now a tablelocal cr_over = do_preobj_CR(object,prescript) -- color
local tr_opaq = do_preobj_TR(object,prescript) -- opacity
local fading_ = do_preobj_FADE(object,prescript) -- fading
local trgroup = do_preobj_GRP(object,prescript) -- transparency grouplocal pattern_ = do_preobj_PAT(object,prescript) -- pattern
if prescript and prescript.mplibtexboxid then
put_tex_boxes(object,prescript)
elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then --skip
elseif objecttype == "start_clip" thenlocal evenodd = not object.istext and object.postscript == "evenodd"
start_pdf_code()
flushnormalpath(object.path,false)
pdf_literalcode(evenodd and "W* n" or "W n")elseif objecttype == "stop_clip" thenstop_pdf_code()
miterlimit, linecap, linejoin, dashed = -1, -1, -1, false
elseif objecttype == "special" thenCollect TEX codes that will be executed after flushing. Legacy behavior.
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489

if prescript and prescript.postmplibverbtex thenfigcontents.post[#figcontents.post+1] = prescript.postmplibverbtex
end
elseif objecttype == "text" thenlocal ot = object.transform -- 3,4,5,6,1,2
start_pdf_code()
pdf_literalcode("%f %f %f %f %f %f cm",ot[3],ot[4],ot[5],ot[6],ot[1],ot[2])pdf_textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth)
stop_pdf_code()
elseif not trgroup and fading_ ~= "stop" thenlocal evenodd, collect, both = false, false, false
local postscript = object.postscript

63

2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510

if not object.istext thenif postscript == "evenodd" thenevenodd = true
elseif postscript == "collect" thencollect = true
elseif postscript == "both" thenboth = true
elseif postscript == "eoboth" thenevenodd = trueboth
= trueend
end
if collect thenif not savedpath thensavedpath = { object.path or false }savedhtap = { object.htap or false }else
savedpath[#savedpath+1] = object.path or false
savedhtap[#savedhtap+1] = object.htap or false
end
else

Removed from ConTeXt general: color stuff.
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542

local ml = object.miterlimit
if ml and ml ~= miterlimit thenmiterlimit = ml
pdf_literalcode("%f M",ml)end
local lj = object.linejoin
if lj and lj ~= linejoin thenlinejoin = lj
pdf_literalcode("%i j",lj)
end
local lc = object.linecap
if lc and lc ~= linecap thenlinecap = lc
pdf_literalcode("%i J",lc)end
local dl = object.dash
if dl thenlocal d = format("[%s] %f d",tableconcat(dl.dashes or {}," "),dl.offset)
if d ~= dashed thendashed = d
pdf_literalcode(dashed)endelseif dashed then
pdf_literalcode("[] 0 d")dashed = false
end
local path = object.path
local transformed, penwidth = false, 1
local open = path and path[1].left_type and path[#path].right_type
local pen = object.pen
if pen thenif pen.type == 'elliptical' then

64

2543
2544
2545
2546
2547
2548
2549
2550
2551

transformed, penwidth = pen_characteristics(object) -- boolean, value
pdf_literalcode("%f w",penwidth)
if objecttype == 'fill' thenobjecttype = 'both'endelse -- calculated by mplib itself
objecttype = 'fill'end
end

Added : shading
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592

local shade_no = do_preobj_SH(object,prescript) -- shading
if shade_no then
pdf_literalcode"q /Pattern cs"
objecttype = falseend
if transformed thenstart_pdf_code()end
if path thenif savedpath thenfor i=1,#savedpath dolocal path = savedpath[i]if transformed thenflushconcatpath(path,open)
else
flushnormalpath(path,open)
end
end
savedpath = nil
end
if transformed thenflushconcatpath(path,open)
else
flushnormalpath(path,open)end
if objecttype == "fill" then
pdf_literalcode(evenodd and "h f*" or "h f")elseif objecttype == "outline" thenif both then
pdf_literalcode(evenodd and "h B*" or "h B")
else
pdf_literalcode(open and "S" or "h S")
end
elseif objecttype == "both" then
pdf_literalcode(evenodd and "h B*" or "h B")
end
end
if transformed thenstop_pdf_code()end
local path = object.htap

How can we generate an htap object? Please let us know if you have succeeded.
2593

if path then

65

2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624

if transformed thenstart_pdf_code()end
if savedhtap thenfor i=1,#savedhtap do
local path = savedhtap[i]if transformed thenflushconcatpath(path,open)
else
flushnormalpath(path,open)
end
end
savedhtap = nil
evenodd = trueend
if transformed thenflushconcatpath(path,open)
else
flushnormalpath(path,open)end
if objecttype == "fill" then
pdf_literalcode(evenodd and "h f*" or "h f")elseif objecttype == "outline" then
pdf_literalcode(open and "S" or "h S")
elseif objecttype == "both" then
pdf_literalcode(evenodd and "h B*" or "h B")
end
if transformed thenstop_pdf_code()end
end

Added to ConTeXt general: post-object colors and shading stuff. We should beware theq ... Q scope.
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644

if shade_no then -- shading
pdf_literalcode("W%s n /MPlibSh%s sh Q",evenodd and "*" or "",shade_no)
end
end
end
if fading_ == "start" thenpdfetcs.fading.specialeffects = {fading_, tr_opaq, cr_over}elseif trgroup == "start" thenpdfetcs.tr_group.specialeffects = {fading_, tr_opaq, cr_over}elseif fading_ == "stop" thenlocal se = pdfetcs.fading.specialeffects
if se then stop_special_effects(se[1], se[2], se[3]) end
elseif trgroup == "stop" thenlocal se = pdfetcs.tr_group.specialeffects
if se then stop_special_effects(se[1], se[2], se[3]) end
else
stop_special_effects(fading_, tr_opaq, cr_over)end
if fading_ or trgroup then -- extgs resetted
miterlimit, linecap, linejoin, dashed = -1, -1, -1, false

66

end
end
end
stop_pdf_code()
pdf_stopfigure()

2645
2646
2647
2648
2649

output collected materials to PDF, plus legacy verbatimtex code.
for _,v in ipairs(figcontents) do
if type(v) == "table" then2652
texsprint"\\mplibtoPDF{"; texsprint(v[1], v[2]); texsprint"}"
2653
else
2654
texsprint(v)
2655
end
2656
end
2657
if #figcontents.post > 0 then texsprint(figcontents.post) end
2658
figcontents = { post = { } }
2659
end
2660
end
2661
end
2662 end
2663 end
2650
2651

2664
2665 function luamplib.colorconverter (cr)local n = #cr
if n == 4 then2668
local c, m, y, k = cr[1], cr[2], cr[3], cr[4]2669
return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
2670 elseif n == 3 then2671
local r, g, b = cr[1], cr[2], cr[3]2672
return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
2673 else
2674
local s = cr[1]2675
return format("%.3f g %.3f G",s,s), "0 g 0 G"
2676 end
2677 end
2666
2667

2.2

TEX package

First we need to load some packages.
2678 \ifcsname ProvidesPackage\endcsname

We need LATEX 2024-06-01 as we use ltx.pdf.object_id when pdfmanagement is loaded.
But as fp package does not accept an option, we do not append the date option.
\NeedsTeXFormat{LaTeX2e}\ProvidesPackage{luamplib}
2681[2024/08/03 v2.34.5 mplib package for LuaTeX]
2682 \fi2683 \ifdefined\newluafunction\else2684 \input ltluatex2685 \fi2679
2680

In DVI mode, a new XObject (mppattern, mplibgroup) must be encapsulated in an\hbox. But this should not affect typesetting. So we use Hook mechanism provided by
LATEX kernel. In Plain, atbegshi.sty is loaded.

67

2686 \ifnum\outputmode=0\ifdefined\AddToHookNext\def\luamplibatnextshipout{\AddToHookNext{shipout/background}}2689\def\luamplibatfirstshipout{\AddToHook{shipout/firstpage}}2690\def\luamplibateveryshipout{\AddToHook{shipout/background}}2691 \else2692\input atbegshi.sty2693\def\luamplibatnextshipout#1{\AtBeginShipoutNext{\AtBeginShipoutAddToBox{#1}}}
2694\let\luamplibatfirstshipout\AtBeginShipoutFirst
2695\def\luamplibateveryshipout#1{\AtBeginShipout{\AtBeginShipoutAddToBox{#1}}}
2696 \fi2697 \fi2687
2688

Loading of lua code.
2698 \directlua{require("luamplib")}

legacy commands. Seems we don’t need it, but no harm.
2699 \ifx\pdfoutput\undefined\let\pdfoutput\outputmode

2700

2701 \fi2702 \ifx\pdfliteral\undefined\protected\def\pdfliteral{\pdfextension literal}

2703

2704 \fiSet the format for metapost.
2705 \def\mplibsetformat#1{\directlua{luamplib.setformat("#1")}}

luamplib works in both PDF and DVI mode, but only DVIPDFMx is supported currently among a number of DVI tools. So we output a info.
2706 \ifnum\pdfoutput>0\let\mplibtoPDF\pdfliteral

2707

2708 \else\def\mplibtoPDF#1{\special{pdf:literal direct #1}}
\ifcsname PackageInfo\endcsname
2711\PackageInfo{luamplib}{only dvipdfmx is supported currently}
2712 \else2713\immediate\write-1{luamplib Info: only dvipdfmx is supported currently}
2714 \fi2715 \fi2709
2710

To make mplibcode typeset always in horizontal mode.
2716 \def\mplibforcehmode{\let\prependtomplibbox\leavevmode}
2717 \def\mplibnoforcehmode{\let\prependtomplibbox\relax}2718 \mplibnoforcehmode

Catcode. We want to allow comment sign in mplibcode.
2719 \def\mplibsetupcatcodes{%%catcode`\{=12 %catcode`\}=12\catcode`\#=12 \catcode`\^=12 \catcode`\~=12 \catcode`\_=12\catcode`\&=12 \catcode`\$=12 \catcode`\%=12 \catcode`\^^M=12

2720
2721
2722
2723 }

Make btex...etex box zero-metric.
2724 \def\mplibputtextbox#1{\vbox to 0pt{\vss\hbox to 0pt{\raise\dp#1\copy#1\hss}}}use Transparency Group2725 \protected\def\usemplibgroup#1{\csname luamplib.group.#1\endcsname}682726 \protected\def\mplibgroup#1{%\begingroup\def\MPllx{0}\def\MPlly{0}%\def\mplibgroupname{#1}%\mplibgroupgetnexttok

2727
2728
2729
2730
2731 }

2732 \def\mplibgroupgetnexttok{\futurelet\nexttok\mplibgroupbranch}2733 \def\mplibgroupskipspace{\afterassignment\mplibgroupgetnexttok\let\nexttok= }2734 \def\mplibgroupbranch{%\ifx [\nexttok\expandafter\mplibgroupopts\else\ifx\mplibsptoken\nexttok\expandafter\expandafter\expandafter\mplibgroupskipspace\else\let\mplibgroupoptions\empty\expandafter\expandafter\expandafter\mplibgroupmain
\fi
\fi

2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745 }

2746 \def\mplibgroupopts[#1]{\def\mplibgroupoptions{#1}\mplibgroupmain}2747 \def\mplibgroupmain{\setbox\mplibscratchbox\hbox\bgroup\ignorespaces}
2748 \protected\def\endmplibgroup{\egroup\directlua{ luamplib.registergroup(\the\mplibscratchbox, '\mplibgroupname', {\mplibgroupoptions})}%\endgroup

2749
2750
2751
2752
2753 }

Patterns
2754 {\def\:{\global\let\mplibsptoken= } \: }2755 \protected\def\mppattern#1{%
2756
2757
2758

\begingroup\def\mplibpatternname{#1}%\mplibpatterngetnexttok

2759 }
2760 \def\mplibpatterngetnexttok{\futurelet\nexttok\mplibpatternbranch}
2761 \def\mplibpatternskipspace{\afterassignment\mplibpatterngetnexttok\let\nexttok= }2762 \def\mplibpatternbranch{%
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772

\ifx [\nexttok\expandafter\mplibpatternopts\else\ifx\mplibsptoken\nexttok\expandafter\expandafter\expandafter\mplibpatternskipspace\else\let\mplibpatternoptions\empty\expandafter\expandafter\expandafter\mplibpatternmain\fi\fi

2773 }
2774 \def\mplibpatternopts[#1]{%
2775
2776

\def\mplibpatternoptions{#1}%\mplibpatternmain

2777 }
2778 \def\mplibpatternmain{%69

\setbox\mplibscratchbox\hbox\bgroup\ignorespaces

2779
2780 }

2781 \protected\def\endmppattern{%\egroup\directlua{ luamplib.registerpattern(\the\mplibscratchbox, '\mplibpatternname', {\mplibpatternoptions})}%\endgroup

2782
2783
2784
2785
2786
2787 }

simple way to use mplib: \mpfig draw fullcircle scaled 10; \endmpfig
2788 \def\mpfiginstancename{@mpfig}
2789 \protected\def\mpfig{%
2790
2791

\begingroup\futurelet\nexttok\mplibmpfigbranch2792 }
2793 \def\mplibmpfigbranch{%
2794
2795
2796
2797
2798

\ifx *\nexttok\expandafter\mplibprempfig\else\expandafter\mplibmainmpfig\fi2799 }
2800 \def\mplibmainmpfig{%
2801
2802
2803

\begingroup\mplibsetupcatcodes\mplibdomainmpfig

2804 }
2805 \long\def\mplibdomainmpfig#1\endmpfig{%
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821

\endgroup\directlua{
local legacy = luamplib.legacyverbatimtex
local everympfig = luamplib.everymplib["\mpfiginstancename"] or ""local everyendmpfig = luamplib.everyendmplib["\mpfiginstancename"] or ""luamplib.legacyverbatimtex = false
luamplib.everymplib["\mpfiginstancename"] = ""luamplib.everyendmplib["\mpfiginstancename"] = ""luamplib.process_mplibcode("beginfig(0) "..everympfig.." "..[===[\unexpanded{#1}]===].." "..everyendmpfig.." endfig;","\mpfiginstancename")
luamplib.legacyverbatimtex = legacy
luamplib.everymplib["\mpfiginstancename"] = everympfig
luamplib.everyendmplib["\mpfiginstancename"] = everyendmpfig}%\endgroup2822 }
2823 \def\mplibprempfig#1{%
2824
2825
2826

\begingroup\mplibsetupcatcodes\mplibdoprempfig

2827 }
2828 \long\def\mplibdoprempfig#1\endmpfig{%
2829
2830
2831

\endgroup\directlua{
local legacy = luamplib.legacyverbatimtex

70

local everympfig = luamplib.everymplib["\mpfiginstancename"]local everyendmpfig = luamplib.everyendmplib["\mpfiginstancename"]
luamplib.legacyverbatimtex = false
luamplib.everymplib["\mpfiginstancename"] = ""luamplib.everyendmplib["\mpfiginstancename"] = ""luamplib.process_mplibcode([===[\unexpanded{#1}]===],"\mpfiginstancename")
luamplib.legacyverbatimtex = legacy
luamplib.everymplib["\mpfiginstancename"] = everympfig
luamplib.everyendmplib["\mpfiginstancename"] = everyendmpfig

2832
2833
2834
2835
2836
2837
2838
2839
2840

}%
\endgroup

2841
2842
2843 }

2844 \protected\def\endmpfig{endmpfig}

The Plain-specific stuff.
2845 \unless\ifcsname ver@luamplib.sty\endcsname\def\mplibcodegetinstancename[#1]{\gdef\currentmpinstancename{#1}\mplibcodeindeed}\protected\def\mplibcode{%2848\begingroup2849\futurelet\nexttok\mplibcodebranch2850 }
2851 \def\mplibcodebranch{%2852\ifx [\nexttok
2853\expandafter\mplibcodegetinstancename
2854
\else
2855
\global\let\currentmpinstancename\empty
2856\expandafter\mplibcodeindeed
2857\fi
2858 }
2859 \def\mplibcodeindeed{%2860\begingroup2861\mplibsetupcatcodes
2862\mplibdocode
2863 }
2864 \long\def\mplibdocode#1\endmplibcode{%2865\endgroup
2866\directlua{luamplib.process_mplibcode([===[\unexpanded{#1}]===],"\currentmpinstancename")}%2867\endgroup2868 }
2869 \protected\def\endmplibcode{endmplibcode}
2870 \else
2846
2847

The LATEX-specific part: a new environment.
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882

\newenvironment{mplibcode}[1][]{%\global\def\currentmpinstancename{#1}%\mplibtmptoks{}\ltxdomplibcode}{}\def\ltxdomplibcode{%\begingroup\mplibsetupcatcodes\ltxdomplibcodeindeed}\def\mplib@mplibcode{mplibcode}\long\def\ltxdomplibcodeindeed#1\end#2{%\endgroup71\mplibtmptoks\expandafter{\the\mplibtmptoks#1}%\def\mplibtemp@a{#2}%\ifx\mplib@mplibcode\mplibtemp@a\directlua{luamplib.process_mplibcode([===[\the\mplibtmptoks]===],"\currentmpinstancename")}%\end{mplibcode}%\else\mplibtmptoks\expandafter{\the\mplibtmptoks\end{#2}}%\expandafter\ltxdomplibcode
\fi

2883
2884
2885
2886
2887
2888
2889
2890
2891
2892

}

2893 \fiUser settings.
2894 \def\mplibshowlog#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.showlog = trueelse
luamplib.showlog = false
end

2895
2896
2897
2898
2899
2900
2901 }}

2902 \def\mpliblegacybehavior#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.legacyverbatimtex = trueelse
luamplib.legacyverbatimtex = false
end

2903
2904
2905
2906
2907
2908
2909 }}

2910 \def\mplibverbatim#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.verbatiminput = trueelse
luamplib.verbatiminput = false
end

2911
2912
2913
2914
2915
2916
2917 }}

2918 \newtoks\mplibtmptoks\everymplib & \everyendmplib: macros resetting luamplib.every(end)mplib tables
2919 \ifcsname ver@luamplib.sty\endcsname
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933

\protected\def\everymplib{%\begingroup\mplibsetupcatcodes\mplibdoeverymplib}\protected\def\everyendmplib{%\begingroup\mplibsetupcatcodes\mplibdoeveryendmplib}\newcommand\mplibdoeverymplib[2][]{%\endgroup\directlua{
luamplib.everymplib["#1"] = [===[\unexpanded{#2}]===]

72

}%
}
2936 \newcommand\mplibdoeveryendmplib[2][]{%
2937\endgroup
2938\directlua{
2939
luamplib.everyendmplib["#1"] = [===[\unexpanded{#2}]===]
2940}%
2941 }
2942 \else2943 \def\mplibgetinstancename[#1]{\def\currentmpinstancename{#1}}2944 \protected\def\everymplib#1#{%
2945\ifx\empty#1\empty \mplibgetinstancename[]\else \mplibgetinstancename#1\fi2946\begingroup2947\mplibsetupcatcodes
2948\mplibdoeverymplib
2949 }
2950 \long\def\mplibdoeverymplib#1{%
2951\endgroup2952\directlua{
2953
luamplib.everymplib["\currentmpinstancename"] = [===[\unexpanded{#1}]===]
2954}%
2955 }
2956 \protected\def\everyendmplib#1#{%
2957\ifx\empty#1\empty \mplibgetinstancename[]\else \mplibgetinstancename#1\fi2958\begingroup2959\mplibsetupcatcodes
2960\mplibdoeveryendmplib
2961 }
2962 \long\def\mplibdoeveryendmplib#1{%
2963\endgroup
2964\directlua{
2965
luamplib.everyendmplib["\currentmpinstancename"] = [===[\unexpanded{#1}]===]
2966}%
2967 }
2968 \fi2934
2935

Allow TEX dimen/color macros. Now runscript does the job, so the following lines
are not needed for most cases.
2969 \def\mpdim#1{ runscript("luamplibdimen{#1}") }2970 \def\mpcolor#1#{\domplibcolor{#1}}2971 \def\domplibcolor#1#2{ runscript("luamplibcolor{#1{#2}}") }mplib’s number system. Now binary has gone away.
2972 \def\mplibnumbersystem#1{\directlua{

local t = "#1"
if t == "binary" then t = "decimal" end2975 luamplib.numbersystem = t
2976 }}
2973
2974

Settings for .mp cache files.
2977 \def\mplibmakenocache#1{\mplibdomakenocache #1,*,}2978 \def\mplibdomakenocache#1,{%
2979
2980
2981

\ifx\empty#1\empty\expandafter\mplibdomakenocache
\else

73

\ifx*#1\else\directlua{luamplib.noneedtoreplace["#1.mp"]=true}%\expandafter\expandafter\expandafter\mplibdomakenocache
\fi
\fi

2982
2983
2984
2985
2986
2987 }

2988 \def\mplibcancelnocache#1{\mplibdocancelnocache #1,*,}2989 \def\mplibdocancelnocache#1,{%\ifx\empty#1\empty\expandafter\mplibdocancelnocache\else\ifx*#1\else\directlua{luamplib.noneedtoreplace["#1.mp"]=false}%\expandafter\expandafter\expandafter\mplibdocancelnocache
\fi
\fi

2990
2991
2992
2993
2994
2995
2996
2997
2998 }

2999 \def\mplibcachedir#1{\directlua{luamplib.getcachedir("\unexpanded{#1}")}}

More user settings.
3000 \def\mplibtextextlabel#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.textextlabel = trueelse
luamplib.textextlabel = false
end

3001
3002
3003
3004
3005
3006
3007 }}

3008 \def\mplibcodeinherit#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.codeinherit = trueelse
luamplib.codeinherit = false
end

3009
3010
3011
3012
3013
3014
3015 }}

3016 \def\mplibglobaltextext#1{\directlua{

local s = string.lower("#1")
if s == "enable" or s == "true" or s == "yes" thenluamplib.globaltextext = trueelse
luamplib.globaltextext = false
end

3017
3018
3019
3020
3021
3022
3023 }}

The followings are from ConTeXt general, mostly.
We use a dedicated scratchbox.
3024 \ifx\mplibscratchbox\undefined \newbox\mplibscratchbox \fi

We encapsulate the literals.
3025 \def\mplibstarttoPDF#1#2#3#4{%
3026
3027
3028
3029

\prependtomplibbox\hbox dir TLT\bgroup\xdef\MPllx{#1}\xdef\MPlly{#2}%\xdef\MPurx{#3}\xdef\MPury{#4}%74\xdef\MPwidth{\the\dimexpr#3bp-#1bp\relax}%\xdef\MPheight{\the\dimexpr#4bp-#2bp\relax}%\parskip0pt%\leftskip0pt%\parindent0pt%\everypar{}%\setbox\mplibscratchbox\vbox\bgroup\noindent

3030
3031
3032
3033
3034
3035
3036
3037
3038 }

3039 \def\mplibstoptoPDF{%\par\egroup %\setbox\mplibscratchbox\hbox %{\hskip-\MPllx bp%\raise-\MPlly bp%\box\mplibscratchbox}%\setbox\mplibscratchbox\vbox to \MPheight{\vfill\hsize\MPwidth\wd\mplibscratchbox0pt%\ht\mplibscratchbox0pt%\dp\mplibscratchbox0pt%\box\mplibscratchbox}%\wd\mplibscratchbox\MPwidth\ht\mplibscratchbox\MPheight\box\mplibscratchbox\egroup

3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057 }

Text items have a special handler.
3058 \def\mplibtextext#1#2#3#4#5{%\begingroup\setbox\mplibscratchbox\hbox{\font\temp=#1 at #2bp%
\temp
#3}%
\setbox\mplibscratchbox\hbox{\hskip#4 bp%\raise#5 bp%\box\mplibscratchbox}%\wd\mplibscratchbox0pt%\ht\mplibscratchbox0pt%\dp\mplibscratchbox0pt%\box\mplibscratchbox\endgroup

3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073 }

Input luamplib.cfg when it exists.
3074 \openin0=luamplib.cfg
3075 \ifeof0 \else\closein0\input luamplib.cfg
3078 \fi3076
3077

That’s all folks!

75

3

The GNU GPL License v2

The GPL requires the complete license text to be distributed along with the code. I
recommend the canonical source, instead: http://www.gnu.org/licenses/old-licenses/
gpl-2.0.html. But if you insist on an included copy, here it is. You might want to zoomin.
GNU GENERAL PUBLIC LICENSE

on the terms of this License, whose permissions for other licensees extend to
the entire whole, and thus to each and every part regardless of who wrote it.

Version 2, June 1991

Thus, it is not the intent of this section to claim rights or contest your rights
to work written entirely by you; rather, the intent is to exercise the right tocontrol the distribution of derivative or collective works based on the Program.

Copyright © 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.

Preamble
The licenses for most software are designed to take away your freedom to share andchange it. By contrast, the GNU General Public License is intended to guarantee
your freedom to share and change free software—to make sure the software is free
for all its users. This General Public License applies to most of the Free Software
Foundation’s software and to any other program whose authors commit to usingit. (Some other Free Software Foundation software is covered by the GNU Library
General Public License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you
receive source code or can get it if you want it, that you can change the software oruse pieces of it in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny
you these rights or to ask you to surrender the rights. These restrictions translate
to certain responsibilities for you if you distribute copies of the software, or if you
modify it.
For example, if you distribute copies of such a program, whether gratis or for a fee,you must give the recipients all the rights that you have. You must make sure that
they, too, receive or can get the source code. And you must show them these terms
so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you
this license which gives you legal permission to copy, distribute and/or modify thesoftware.
Also, for each author’s protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software ismodified by someone else and passed on, we want its recipients to know that what
they have is not the original, so that any problems introduced by others will notreflect on the original authors’ reputations.
Finally, any free program is threatened constantly by software patents. We wish
to avoid the danger that redistributors of a free program will individually obtainpatent licenses, in effect making the program proprietary. To prevent this, we havemade it clear that any patent must be licensed for everyone’s free use or not licensed
at all.
The precise terms and conditions for copying, distribution and modification follow.

Terms and Conditions For Copying, Distribution
and Modification
1. This License applies to any program or other work which contains a noticeplaced by the copyright holder saying it may be distributed under the termsof this General Public License. The “Program”, below, refers to any such program or work, and a “work based on the Program” means either the Programor any derivative work under copyright law: that is to say, a work containing
the Program or a portion of it, either verbatim or with modifications and/ortranslated into another language. (Hereinafter, translation is included without limitation in the term “modification”.) Each licensee is addressed as “you”.
Activities other than copying, distribution and modification are not coveredby this License; they are outside its scope. The act of running the Program is
not restricted, and the output from the Program is covered only if its contents
constitute a work based on the Program (independent of having been madeby running the Program). Whether that is true depends on what the Program
does.
2. You may copy and distribute verbatim copies of the Program’s source code as
you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer
of warranty; keep intact all the notices that refer to this License and to theabsence of any warranty; and give any other recipients of the Program a copyof this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may
at your option offer warranty protection in exchange for a fee.
3. You may modify your copy or copies of the Program or any portion of it, thus
forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also
meet all of these conditions:(a) You must cause the modified files to carry prominent notices statingthat you changed the files and the date of any change.
(b) You must cause any work that you distribute or publish, that in whole
or in part contains or is derived from the Program or any part thereof,
to be licensed as a whole at no charge to all third parties under the termsof this License.
(c) If the modified program normally reads commands interactively whenrun, you must cause it, when started running for such interactive use in
the most ordinary way, to print or display an announcement including
an appropriate copyright notice and a notice that there is no warranty(or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how toview a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work
based on the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole
which is a work based on the Program, the distribution of the whole must be

In addition, mere aggregation of another work not based on the Program with
the Program (or with a work based on the Program) on a volume of a storage
or distribution medium does not bring the other work under the scope of thisLicense.
4. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2
above provided that you also do one of the following:(a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections 1
and 2 above on a medium customarily used for software interchange;or,(b) Accompany it with a written offer, valid for at least three years, to give
any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of thecorresponding source code, to be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange;or,(c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only fornoncommercial distribution and only if you received the program in
object code or executable form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of
the executable. However, as a special exception, the source code distributed
need not include anything that is normally distributed (in either source or
binary form) with the major components (compiler, kernel, and so on) of theoperating system on which the executable runs, unless that component itself
accompanies the executable.
If distribution of executable or object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the sourcecode from the same place counts as distribution of the source code, eventhough third parties are not compelled to copy the source along with theobject code.
5. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses terminatedso long as such parties remain in full compliance.
6. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Programor its derivative works. These actions are prohibited by law if you do not
accept this License. Therefore, by modifying or distributing the Program (orany work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or
modifying the Program or works based on it.
7. Each time you redistribute the Program (or any work based on the Program),
the recipient automatically receives a license from the original licensor tocopy, distribute or modify the Program subject to these terms and conditions.
You may not impose any further restrictions on the recipients’ exercise of therights granted herein. You are not responsible for enforcing compliance bythird parties to this License.
8. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposedon you (whether by court order, agreement or otherwise) that contradict theconditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously yourobligations under this License and any other pertinent obligations, then asa consequence you may not distribute the Program at all. For example, if apatent license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then the only
way you could satisfy both it and this License would be to refrain entirely
from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and thesection as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims; thissection has the sole purpose of protecting the integrity of the free software
distribution system, which is implemented by public license practices. Many
people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute
software through any other system and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be aconsequence of the rest of this License.
9. If the distribution and/or use of the Program is restricted in certain countries
either by patents or by copyrighted interfaces, the original copyright holder
who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution ispermitted only in or among countries not thus excluded. In such case, thisLicense incorporates the limitation as if written in the body of this License.

76

10. The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and “any later version”, you have the option of following the terms and conditions either ofthat version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you
may choose any version ever published by the Free Software Foundation.
11. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask forpermission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions
for this. Our decision will be guided by the two goals of preserving the freestatus of all derivatives of our free software and of promoting the sharing andreuse of software generally.

No Warranty
12. Because the program is licensed free of charge, there is no warranty
for the program, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or
other parties provide the program “as is” without warranty of any
kind, either expressed or implied, including, but not limited to, theimplied warranties of merchantability and fitness for a particularpurpose. The entire risk as to the qality and performance of the
program is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction.
13. In no event unless reqired by applicable law or agreed to in writing will any copyright holder, or any other party who may modify
and/or redistribute the program as permitted above, be liable toyou for damages, including any general, special, incidental or conseqential damages arising out of the use or inability to use the program (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a
failure of the program to operate with any other programs), evenif such holder or other party has been advised of the possibility ofsuch damages.

End of Terms and Conditions
Appendix: How to Apply These Terms to Your New
Programs
If you develop a new program, and you want it to be of the greatest possible use to
the public, the best way to achieve this is to make it free software which everyone
can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them tothe start of each source file to most effectively convey the exclusion of warranty;
and each file should have at least the “copyright” line and a pointer to where thefull notice is found.
one line to give the program’s name and a brief idea of what it does.
Copyright (C) yyyy name of author
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by theFree Software Foundation; either version 2 of the License, or (at youroption) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it starts
in an interactive mode:
Gnomovision version 69, Copyright (C) yyyy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type ‘show w’.
This is free software, and you are welcome to redistribute it under certain conditions; type ‘show c’ for details.
The hypothetical commands show w and show c should show the appropriate partsof the General Public License. Of course, the commands you use may be calledsomething other than show w and show c; they could even be mouse-clicks or menu
items—whatever suits your program.
You should also get your employer (if you work as a programmer) or your school, if
any, to sign a “copyright disclaimer” for the program, if necessary. Here is a sample;
alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program‘Gnomovision’ (which makes passes at compilers) written by James
Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it
more useful to permit linking proprietary applications with the library. If this iswhat you want to do, use the GNU Library General Public License instead of thisLicense.