% \iffalse meta-comment -- by the way, this file contains UTF-8 % % Copyright (C) 2008-2010 by Hans Hagen, Taco Hoekwater, Elie Roux, and % Manuel Pégourié-Gonnard. % Currently maintained by Manuel Pégourié-Gonnard % Support: % % This work is under the CC0 license. % % This work consists of the main source file luamplib.dtx % and the derived files % luamplib.sty, luamplib.lua and luamplib.pdf. % % Unpacking: % tex luamplib.dtx % % Documentation: % pdflatex luamplib.dtx % %<*ignore> \begingroup \def\x{LaTeX2e}% \expandafter\endgroup \ifcase 0\ifx\install y1\fi\expandafter \ifx\csname processbatchFile\endcsname\relax\else1\fi \ifx\fmtname\x\else 1\fi\relax \else\csname fi\endcsname % %<*install> \input docstrip.tex \Msg{************************************************************************} \Msg{* Installation} \Msg{* Package: luamplib - metapost package for LuaTeX.} \Msg{************************************************************************} \keepsilent \askforoverwritefalse \let\MetaPrefix\relax \preamble See source file '\inFileName' for licencing and contact information. \endpreamble \let\MetaPrefix\DoubleperCent \generate{% \usedir{tex/luatex/mplib}% \file{luamplib.sty}{\from{luamplib.dtx}{package}}% } \def\MetaPrefix{-- } \def\luapostamble{% \MetaPrefix^^J% \MetaPrefix\space End of File `\outFileName'.% } \def\currentpostamble{\luapostamble}% \generate{% \usedir{tex/luatex/mplib}% \file{luamplib.lua}{\from{luamplib.dtx}{lua}}% } \obeyspaces \Msg{************************************************************************} \Msg{*} \Msg{* To finish the installation you have to move the following} \Msg{* files into a directory searched by TeX:} \Msg{*} \Msg{* luamplib.sty luamplib.lua} \Msg{*} \Msg{* Happy TeXing!} \Msg{*} \Msg{************************************************************************} \endbatchfile % %<*ignore> \fi % %<*driver> \documentclass{ltxdoc} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{textcomp} \usepackage{lmodern} \usepackage{fixltx2e} \usepackage[a4paper]{geometry} \usepackage[english]{babel} \usepackage[colorlinks]{hyperref} \usepackage{bookmark} \newcommand*\email [1] {<\href{mailto:#1}{#1}>} \newcommand \file {\nolinkurl} \newcommand \pk {\textsf} \begin{document} \DocInput{luamplib.dtx}% \end{document} % % \fi % % \CheckSum{0} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} % % \title{The \textsf{luamplib} package} % \author{Hans Hagen, Taco Hoekwater and Elie Roux \\ % Maintainer: Manuel Pégourié-Gonnard --- % Support: \email{lualatex-dev@tug.org}} % \date{2011/12/09 v1.09} % % \maketitle % % \begin{abstract} % Package to have metapost code typeset directly in a document with Lua\TeX . % \end{abstract} % % \section{Documentation} % % This packages aims at providing a simple way to typeset directly metapost % code in a document with Lua\TeX . Lua\TeX\ is built with the lua % \texttt{mplib} library, that runs metapost code. This package is basically a % wrapper (in Lua) for the Lua \texttt{mplib} functions and some \TeX\ % functions to have the output of the \texttt{mplib} functions in the pdf. % % The package needs to be in PDF mode in order to output something, as PDF % specials are not supported by the DVI format and tools. % % The metapost figures are put in a \TeX\ \texttt{hbox} with dimensions % adjusted to the metapost code. % % The code is from the \texttt{supp-mpl.lua} and \texttt{supp-mpl.tex} files % from Con\TeX t, they have been adapted to \LaTeX\ and Plain by Elie Roux. % The changes are: % % \begin{itemize} % \item a \LaTeX\ environment % \item all \TeX\ macros start by \texttt{mplib} % \item use of luatexbase for errors, warnings and declaration % \end{itemize} % % Using this package is easy: in Plain, type your metapost code between the % macros \texttt{mplibcode} and \texttt{endmplibcode}, and in \LaTeX\ in the % \texttt{mplibcode} environment. % % There are (basically) two formats for metapost: \emph{plain} and % \emph{mpfun}. By default, the \emph{plain} format is used, but you can set % the format to be used by future figures at any time using % \cs{mplibsetformat}\marg{format name}. % % \section{Implementation} % % \subsection{Lua module} % % \begin{macrocode} %<*lua> % \end{macrocode} % % Use the |luamplib| namespace, since |mplib| is for the metapost library % itself. % % \begin{macrocode} module('luamplib', package.seeall) % \end{macrocode} % % Identification, and additional reporting function, for compatibility with % the original |report()| function and associated data structure. % % \begin{macrocode} local err, warn, info, log = luatexbase.provides_module({ name = "luamplib", version = 1.09, date = "2011/11/09", description = "Lua package to typeset Metapost with LuaTeX's MPLib.", }) local function term(...) texio.write_nl('term', 'luamplib: ' .. string.format(...)) end % \end{macrocode} % % This module is a stripped down version of libraries that are used by % Con\TeX t. Provide a few ``shortcuts'' expected by the imported code. % % \begin{macrocode} local format, concat, abs = string.format, table.concat, math.abs % \end{macrocode} % % This is a small trick for \LaTeX . In \LaTeX\ we read the metapost code % line by line, but it needs to be passed entirely to |process()|, so we % simply add the lines in |data| and at the end we call |process(data)|. % % \begin{macrocode} local data = "" local function resetdata() data = "" end function addline(line) data = data .. '\n' .. line end function processlines() process(data) resetdata() end % \end{macrocode} % % As the finder function for |mplib|, use the |kpse| library and % make it behave like as if MetaPost was used (or almost, since the engine % name is not set this way---not sure if this is a problem). % % \begin{macrocode} local mpkpse = kpse.new("luatex", "mpost") local function finder(name, mode, ftype) if mode == "w" then return name else return mpkpse:find_file(name,ftype) end end % \end{macrocode} % % Default format name, and a public function to change it. % % \begin{macrocode} local currentformat = "plain" function setformat (name) currentformat = name end % \end{macrocode} % % Create a new mplib object and input the correct .mp file as given by % |currentformat|. With older versions of MetaPost, using a |mem| file may % be more efficient, but newer versions don't support it any more so for % the sake of simplicity, don't even try to use a |mem| file. % |ini_version| is ignored by new versions but is useful to prevent old % ones from trying to load a |mem| file. % % \begin{macrocode} function load() local mpx = mplib.new { find_file = finder, ini_version = true, } mpx:execute(format("input %s ;", currentformat)) return mpx end % \end{macrocode} % % The rest of this module is not documented. More information can be found % in the Lua\TeX\ manual, articles in user group journals and the files % that ship with Con\TeX t. % % \begin{macrocode} function report(result) if not result then err("no result object") elseif result.status > 0 then local t, e, l, f = result.term, result.error, result.log if l then log(l) end if t then term(t) end if e then if result.status == 1 then warn(e) else err(e) end end if not t and not e and not l then if result.status == 1 then warn("unknown error, no error, terminal or log messages, maybe missing beginfig/endfig") else err("unknown error, no error, terminal or log messages, maybe missing beginfig/endfig") end end else return true end return false end function process(data) local converted, result = false, {} local mpx = load() if mpx and data then local result = mpx:execute(data) if report(result) then if result.fig then converted = convert(result) else warn("no figure output") end end else err("Mem file unloadable. Maybe generated with a different version of mplib?") end return converted, result end local function getobjects(result,figure,f) return figure:objects() end function convert(result, flusher) flush(result, flusher) return true -- done end local function pdf_startfigure(n,llx,lly,urx,ury) tex.sprint(format("\\mplibstarttoPDF{%s}{%s}{%s}{%s}",llx,lly,urx,ury)) end local function pdf_stopfigure() tex.sprint("\\mplibstoptoPDF") end function pdf_literalcode(fmt,...) -- table tex.sprint(format("\\mplibtoPDF{%s}",format(fmt,...))) end function pdf_textfigure(font,size,text,width,height,depth) text = text:gsub(".","\\hbox{%1}") -- kerning happens in metapost tex.sprint(format("\\mplibtextext{%s}{%s}{%s}{%s}{%s}",font,size,text,0,-( 7200/ 7227)/65536*depth)) end local bend_tolerance = 131/65536 local rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 1 local function pen_characteristics(object) if mplib.pen_info then local t = mplib.pen_info(object) rx, ry, sx, sy, tx, ty = t.rx, t.ry, t.sx, t.sy, t.tx, t.ty divider = sx*sy - rx*ry return not (sx==1 and rx==0 and ry==0 and sy==1 and tx==0 and ty==0), t.width else rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 1 return false, 1 end end local function concat(px, py) -- no tx, ty here return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider end 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 then d = pth.left_y - ith.right_y if abs(ith.right_y - ith.y_coord - d) <= bend_tolerance and abs(pth.y_coord - pth.left_y - d) <= bend_tolerance then return false end end return true end local function flushnormalpath(path,open) 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 = pth end if not open then local 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) end elseif #path == 1 then -- special case .. draw point local one = path[1] pdf_literalcode("%f %f l",one.x_coord,one.y_coord) end return t end local function flushconcatpath(path,open) pdf_literalcode("%f %f %f %f %f %f cm", sx, rx, ry, sy, tx ,ty) local pth, ith for i=1,#path do pth = path[i] if not ith then pdf_literalcode("%f %f m",concat(pth.x_coord,pth.y_coord)) elseif curved(ith,pth) then local a, b = concat(ith.right_x,ith.right_y) local c, d = concat(pth.left_x,pth.left_y) pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concat(pth.x_coord, pth.y_coord)) else pdf_literalcode("%f %f l",concat(pth.x_coord, pth.y_coord)) end ith = pth end if not open then local one = path[1] if curved(pth,one) then local a, b = concat(pth.right_x,pth.right_y) local c, d = concat(one.left_x,one.left_y) pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concat(one.x_coord, one.y_coord)) else pdf_literalcode("%f %f l",concat(one.x_coord,one.y_coord)) end elseif #path == 1 then -- special case .. draw point local one = path[1] pdf_literalcode("%f %f l",concat(one.x_coord,one.y_coord)) end return t end % \end{macrocode} % % Support for specials in DVI has been removed. % % \begin{macrocode} function flush(result,flusher) if result then local figures = result.fig if figures then for f=1, #figures do log("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 then -- invalid pdf_startfigure(fignum,0,0,0,0) pdf_stopfigure() else pdf_startfigure(fignum,llx,lly,urx,ury) pdf_literalcode("q") if objects then for o=1,#objects do local object = objects[o] local objecttype = object.type if objecttype == "start_bounds" or objecttype == "stop_bounds" then -- skip elseif objecttype == "start_clip" then pdf_literalcode("q") flushnormalpath(object.path,t,false) pdf_literalcode("W n") elseif objecttype == "stop_clip" then pdf_literalcode("Q") miterlimit, linecap, linejoin, dashed = -1, -1, -1, false elseif objecttype == "special" then -- not supported elseif objecttype == "text" then local ot = object.transform -- 3,4,5,6,1,2 pdf_literalcode("q %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) pdf_literalcode("Q") else local cs = object.color if cs and #cs > 0 then pdf_literalcode(colorconverter(cs)) end local ml = object.miterlimit if ml and ml ~= miterlimit then miterlimit = ml pdf_literalcode("%f M",ml) end local lj = object.linejoin if lj and lj ~= linejoin then linejoin = lj pdf_literalcode("%i j",lj) end local lc = object.linecap if lc and lc ~= linecap then linecap = lc pdf_literalcode("%i J",lc) end local dl = object.dash if dl then local d = format("[%s] %i d",concat(dl.dashes or {}," "),dl.offset) if d ~= dashed then dashed = d pdf_literalcode(dashed) end elseif 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 then if pen.type == 'elliptical' then transformed, penwidth = pen_characteristics(object) -- boolean, value pdf_literalcode("%f w",penwidth) if objecttype == 'fill' then objecttype = 'both' end else -- calculated by mplib itself objecttype = 'fill' end end if transformed then pdf_literalcode("q") end if path then if transformed then flushconcatpath(path,open) else flushnormalpath(path,open) end if objecttype == "fill" then pdf_literalcode("h f") elseif objecttype == "outline" then pdf_literalcode((open and "S") or "h S") elseif objecttype == "both" then pdf_literalcode("h B") end end if transformed then pdf_literalcode("Q") end local path = object.htap if path then if transformed then pdf_literalcode("q") end if transformed then flushconcatpath(path,open) else flushnormalpath(path,open) end if objecttype == "fill" then pdf_literalcode("h f") elseif objecttype == "outline" then pdf_literalcode((open and "S") or "h S") elseif objecttype == "both" then pdf_literalcode("h B") end if transformed then pdf_literalcode("Q") end end if cr then pdf_literalcode(cr) end end end end pdf_literalcode("Q") pdf_stopfigure() end end end end end function colorconverter(cr) local n = #cr if n == 4 then local c, m, y, k = cr[1], cr[2], cr[3], cr[4] return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G" elseif n == 3 then local r, g, b = cr[1], cr[2], cr[3] return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G" else local s = cr[1] return format("%.3f g %.3f G",s,s), "0 g 0 G" end end % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\texorpdfstring{\TeX}{TeX} package} % % \begin{macrocode} %<*package> % \end{macrocode} % % First we need to load fancyvrb, to define the environment mplibcode. % % \begin{macrocode} \bgroup\expandafter\expandafter\expandafter\egroup \expandafter\ifx\csname ProvidesPackage\endcsname\relax \input luatexbase-modutils.sty \else \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{luamplib} [2011/12/09 v1.09 mplib package for LuaTeX] \RequirePackage{luatexbase-modutils} \RequirePackage{fancyvrb} \fi % \end{macrocode} % % Loading of lua code. % % \begin{macrocode} \RequireLuaModule{luamplib} % \end{macrocode} % % Set the format for metapost. % % \begin{macrocode} \def\mplibsetformat#1{% \directlua{luamplib.setformat("\luatexluaescapestring{#1}")}} % \end{macrocode} % % MPLib only works in PDF mode, we don't do anything if we are in DVI mode, % and we output a warning. % % \begin{macrocode} \ifnum\pdfoutput>0 \let\mplibtoPDF\pdfliteral \else %\def\MPLIBtoPDF#1{\special{pdf:literal direct #1}} % not ok yet \def\mplibtoPDF#1{} \expandafter\ifx\csname PackageWarning\endcsname\relax \write16{} \write16{Warning: MPLib only works in PDF mode, no figure will be output.} \write16{} \else \PackageWarning{mplib}{MPLib only works in PDF mode, no figure will be output.} \fi \fi % \end{macrocode} % % The Plain-specific stuff. % % \begin{macrocode} \bgroup\expandafter\expandafter\expandafter\egroup \expandafter\ifx\csname ProvidesPackage\endcsname\relax \def\mplibsetupcatcodes{% \catcode`\{=12 \catcode`\}=12 \catcode`\#=12 \catcode`\^=12 \catcode`\~=12 \catcode`\_=12 \catcode`\%=12 \catcode`\&=12 \catcode`\$=12 } \def\mplibcode{% \bgroup % \mplibsetupcatcodes % \mplibdocode % } \long\def\mplibdocode#1\endmplibcode{% \egroup % \mplibprocess{#1}% } \long\def\mplibprocess#1{% \directlua{luamplib.process("\luatexluaescapestring{#1}")}% } \else % \end{macrocode} % % The \LaTeX-specific parts. First a Hack for the catcodes in \LaTeX . % % \begin{macrocode} \begingroup \catcode`\,=13 \catcode`\-=13 \catcode`\<=13 \catcode`\>=13 \catcode`\^^I=13 \catcode`\`=13 % must be last... \gdef\FV@hack{% \def,{\string,}% \def-{\string-}% \def<{\string<}% \def>{\string>}% \def`{\string`}% \def^^I{\string^^I}% } \endgroup % \end{macrocode} % % In \LaTeX\ (it's not the case in plain\TeX ), we get the metapost code % line by line, here is the function handling a line. % % \begin{macrocode} \newcommand\mplibaddlines[1]{% \begingroup % \FV@hack % \def\FV@ProcessLine##1{% \directlua{luamplib.addline("\luatexluaescapestring{##1}")}% }% \csname FV@SV@#1\endcsname % \endgroup % } % \end{macrocode} % % The \LaTeX\ environment is a modified \texttt{verbatim} environment. % % \begin{macrocode} \newenvironment{mplibcode}{% \VerbatimEnvironment % \begin{SaveVerbatim}{memoire}% }{% \end{SaveVerbatim}% \mplibaddlines{memoire}% \directlua{luamplib.processlines()}% } \fi % \end{macrocode} % % We use a dedicated scratchbox. % % \begin{macrocode} \ifx\mplibscratchbox\undefined \newbox\mplibscratchbox \fi % \end{macrocode} % % We encapsulate the litterals. % % \begin{macrocode} \def\mplibstarttoPDF#1#2#3#4{% \hbox\bgroup \xdef\MPllx{#1}\xdef\MPlly{#2}% \xdef\MPurx{#3}\xdef\MPury{#4}% \xdef\MPwidth{\the\dimexpr#3bp-#1bp\relax}% \xdef\MPheight{\the\dimexpr#4bp-#2bp\relax}% \parskip0pt% \leftskip0pt% \parindent0pt% \everypar{}% \setbox\mplibscratchbox\vbox\bgroup \noindent } % \end{macrocode} % % \begin{macrocode} \def\mplibstoptoPDF{% \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 } % \end{macrocode} % % Text items have a special handler. % % \begin{macrocode} \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 } % \end{macrocode} % % That's all folks! % % \begin{macrocode} % % \end{macrocode} % % % \Finale \endinput