diff options
author | Norbert Preining <norbert@preining.info> | 2022-03-04 03:01:04 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-03-04 03:01:04 +0000 |
commit | 575ab7d0e7534edd510bbc89a2ef4b04582af591 (patch) | |
tree | 9fc4cf55b7ca8b168bdfd747be21de4c9ef2b30e /macros/luatex/generic/minim-mp | |
parent | 6b9ed20cb01cb52c7aaf2a48202f5d8aa588ff79 (diff) |
CTAN sync 202203040301
Diffstat (limited to 'macros/luatex/generic/minim-mp')
-rw-r--r-- | macros/luatex/generic/minim-mp/README | 32 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-lamp.ini | 20 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-mp.doc | 112 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-mp.ini | 1 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-mp.lua | 390 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-mp.pdf | bin | 82287 -> 94557 bytes | |||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim-mp.tex | 3 | ||||
-rw-r--r-- | macros/luatex/generic/minim-mp/minim.mp | 81 |
8 files changed, 413 insertions, 226 deletions
diff --git a/macros/luatex/generic/minim-mp/README b/macros/luatex/generic/minim-mp/README index dee13d1bfe..2e04ee1050 100644 --- a/macros/luatex/generic/minim-mp/README +++ b/macros/luatex/generic/minim-mp/README @@ -1,4 +1,4 @@ -Version: 2021/1.0 +Version: 2022/1.1 SUMMARY @@ -17,9 +17,37 @@ In order to obtain the typeset manual of this package, simply say: luatex minim-mp.doc +HISTORY + +2022/1.1 (3/3/2022) + + New features: + + * Made instance initialisation more flexible; this replaces M.init_files + with M.init_code (see manual). + * Add support for the glyph of operator. + * Add support for the even-odd rule of filling and noncontinuous paths. + * Add experimental latex support (the minim-lamp format). + * Make minim-mp compatible with tikz/pgf. + * Expand the plain macro set slightly. + + Minor changes: + + * Improved display of metapost logs. + * Redefine units to fit other numerical engines better. + * Fixed a bug in pattern generation that prevented simplifying patterns + into raw pdf code. + * No longer write out superfluous line widths. + + +2021/1.0 (1/6/2021) + + This was the original release. + + COPYING -(c) 2021 Esger Renkema +(c) 2022 Esger Renkema, Michal Vlasák These files may be distributed under the terms of the European Union Public Licence (EUPL) version 1.2 or later. A copy can be obtained at: diff --git a/macros/luatex/generic/minim-mp/minim-lamp.ini b/macros/luatex/generic/minim-mp/minim-lamp.ini new file mode 100644 index 0000000000..7900b3936a --- /dev/null +++ b/macros/luatex/generic/minim-mp/minim-lamp.ini @@ -0,0 +1,20 @@ + +\let\DUMP=\dump +\let\dump=\endinput + +\input lualatex.ini + +\toksapp\everyjob{% + \documentclass{article} + \input minim-mp + \message{This is the minim-latex metapost processor.}\relax + \directlua {require('minim-mp').on_line = true}% + \newmetapostinstance[jobname="\jobname", mathmode="scaled"]\MP + \runmetapost\MP{input "\jobname";}% + \directlua {require('minim-mp').shipout(\the\MP)}% + \closemetapostinstance\MP + \csname @fileswfalse\endcsname\end{document}} + +\let\dump=\DUMP +\dump + diff --git a/macros/luatex/generic/minim-mp/minim-mp.doc b/macros/luatex/generic/minim-mp/minim-mp.doc index 788d26b6a0..b7fb47f416 100644 --- a/macros/luatex/generic/minim-mp/minim-mp.doc +++ b/macros/luatex/generic/minim-mp/minim-mp.doc @@ -8,8 +8,8 @@ \else \startmetadata author {Esger Renkema} title {minim-mp} - date {2021-06-01} - version {2021/1.0} + date {2022-03-03} + version {2022/1.1} keywords {LuaTeX; Plain TeX; MetaPost; mplib} stopmetadata \maketitle \fi @@ -35,7 +35,11 @@ error occurs, the logging will also be shown on the terminal. This package can also be used as a stand-alone metapost compiler. Saying \stopformulatagging$$\hbox{⟦luatex --fmt=minim-mp your_file.mp⟧}$$\startformulatagging will create a pdf file of all images in ⟦your_file.mp⟧, in order, with page -sizes adjusted to image dimensions. +sizes adjusted to image dimensions. You might need generate the format first, +this is done with +\stopformulatagging$$\hbox{⟦luatex --ini minim-mp.ini⟧}$$\startformulatagging +Use minim-lamp instead of minim-mp for an experimental latex-based version +of the minim-mp format. \section Metapost instances %1 @@ -95,10 +99,15 @@ Arbitrary tex statements may be included in ⟦*verbatimtex ... etex⟧, which m occur anywhere. These ⟦btex⟧ and ⟦verbatimtex⟧ statements are executed in the order they are given. +Previously-defined box resources can be included with ⟦*boxresource(nr)⟧. The +result will be an image object with the proper dimensions. This image can be +transformed in any way you like, but you cannot inspect the contents of the +resource within metapost. + You can also use metapost’s ⟦*infont⟧ operator, which restricts the text to-be-typeset to a single font, but returns an ⟦picture⟧ containing a ⟦picture⟧ for each character. The right-hand argument of ⟦infont⟧ should either be -a (numerical) font id or the (cs)name of a font. +a (numerical) font~id or the (cs)name of a font (without backslash). One possible use of the ⟦infont⟧ operator is setting text along curves: @@ -129,6 +138,52 @@ beginfig(1) endfor endfig;}\starttagging \stopelement{Figure} +For the greatest amount of control, however, you will need the ⟦*glyph g of f⟧ +operator, which returns the contours that make up a glyph. It is a bit more +versatile than its traditional metapost counterpart: ⟦g⟧ may also be the name +of the glyph instead of its index, while ⟦f⟧ can be a font~id or font name (as +with ⟦infont⟧). + +A variant of ⟦glyph of⟧ is the ⟦*contours t of f⟧ macro: it first typesets the +string ⟦t⟧ in the same way as ⟦infont⟧ does (so that kerning and font shaping +are applied), but returns a (comma-separated) list of contours, such as may be +used in a ⟦for⟧ loop. Due to rounding errors, the glyphs will not match exactly. + +Be aware that the contours returned by these operators may be disjoint: +a letter ⟦o⟧, for example, will consists of two. This means you cannot recreate +the characters by just filling each contour: this would turn the ⟦o⟧ into +a filled-in circle. Instead, you must use ⟦multifill⟧ on the entire output of +⟦glyph of⟧ or ⟦contours of⟧ (see the next section). + + +\section Partial paths and the even-odd rule %1 + +You can fill or draw two or more disjoint paths in one go by using ⟦*nofill⟧ as +drawing operator for all paths but the last. This may make it easier to cut +something out of a shape, since you do not have to worry about stitching the +paths together. + +While metapost fills paths according to the winding number, the pdf format also +supports filling according to the even-odd rule. This has been made possible +with the ⟦*eofill⟧ and ⟦*eofilldraw⟧ drawing statements, which can of course +also be used as the final statement after a series of nofills. + +The macros ⟦*multi(draw|fill|filldraw|eofill|eofilldraw)⟧ take a list of paths +between parentheses and can be followed by the usual drawing options. +For example, +⟦multidraw (contours "example" of "tenbf") withpen currentpen scaled 1/10;⟧ +will give the word% +\markelement{Span}{\setactualtext{example } +\directmetapost{beginfig(1) multidraw (contours "example" of "tenbf") +withpen currentpen scaled 1/10; baseline 0; endfig;}} +in a thin outline. + +Finally, two handy clipping macros have been added: +⟦*clipout⟧ and ⟦*clipto⟧, which both receive a list of paths as a ‘text’ +parameter and either clip their ensemble out of the picture, or the picture to +the ensemble. + + \section Running lua from within metapost %1 You can call out to lua with ⟦*runscript "lua code"⟧. For this purpose, each @@ -194,7 +249,7 @@ beginfig(1) fill fullcircle scaled 3cm withpattern(a) withcolor 3/4red; draw fullcircle scaled 3cm withpen pencircle scaled 1; endfig;}\stopelement{Figure}\vss}\hskip10pt\strut - +\par\nobreak ⟦% define the pattern picture letter; letter = maketext("a"); beginpattern(a) @@ -209,25 +264,16 @@ endfig;⟧ A small pattern library is available in the ⟦*minim-hatching.mp⟧ file; see the accompanying documentation sheet for an overview of patterns. -Tiling patterns cannot be used together with tikz/pgf; see below under -‘Resource management’. \section Other metapost extensions %1 -There is currently no support for the ⟦*glyph of⟧ operator. - You can set the baseline of an image with ⟦*baseline(p)⟧. There, ⟦p⟧ must either be a point through which the baseline should pass, or a number (where an x~coordinate of zero will be added). Transformations will be taken into account, hence the specification of two coordinates. The last given baseline will be used. -Previously-defined box resources can be included with ⟦*boxresource(nr)⟧. The -result will be an image object with the proper dimensions. This image can be -transformed in any way you like, but you cannot inspect the contents of the -resource within metapost. - You can write to tex’s log directly with ⟦*texmessage "hello"⟧. You can write direct pdf statements with ⟦*special "pdf: statements"⟧ and you @@ -235,6 +281,15 @@ can add comments to the pdf file with ⟦*special "pdfcomment: comments"⟧. Say ⟦*special "latelua: lua code"⟧ to insert a ⟦late_lua⟧ whatsit. All three specials can also be given as pre- or postscripts to another object. In that case, they will be added before or after the object they are attached to. +Do note that all ⟦special⟧ statements will appear at the beginning of the +image; use pre- and postscripts to drawing statements if the order matters. + +Minim-mp also provides a few elementary macros and constants that are +conspicuously absent from plain.mp; I hope their addition is uncontroversial. +The constants are ⟦*pi⟧ (355/113), ⟦*fullsquare⟧, ⟦*unitcircle⟧ and the +cmyk-colours ⟦*cyan⟧, ⟦*magenta⟧, ⟦*yellow⟧ and ⟦*key⟧. The macros are +⟦*clockwise⟧, ⟦*xshifted⟧, ⟦*yshifted⟧, ⟦*hflip⟧ and ⟦*vflip⟧, where the flips +are defined in such a way that ⟦p & hflip p⟧ gives the expected result. \section Lua interface %1 @@ -262,29 +317,6 @@ Each metapost instance is a table containing the following entries: ⟦env⟧&The lua environment for ⟦runscript⟧.\cr} -\section PDF resource management %1 - -This package can add ⟦/Pattern⟧ and ⟦/ColorSpace⟧ entries to all page and xform -resource dictionaries. -Both refer to a single, global dictionary shared by all pages. -Support for other keys may be added in the future. - -At the moment, this implementation only serves tiling pattern support; -the mechanism will be enabled automatically at the first use of a tiling -pattern (merely defining a pattern will not enable it) and is of little use for -anything else. -The relevant tables, should you want to expand on it yourself, are -⟦M.colourspaces⟧ and ⟦M.patterns⟧; see the source file for additional -instructions. - -Since pdf resource management must be done exactly once, this package may clash -with other graphics packages doing the same. -In particular, minim’s resource management will cause double (and thus invalid) -entries in pages’ attribute dictionaries when used together with \red{tikz} or -\red{pgf}. -They can be used together, however, if you do not use minim’s tiling patterns. - - \section Debugging %1 You can enable (global) debugging by saying ⟦*debug_pdf⟧ to metapost or @@ -321,9 +353,9 @@ processed; this information is still available inside the ⟦*finish_mpfigure⟧ callback that is executed just before the processed image is surrounded by properly-dimensioned boxes. -The ⟦*M.init_files⟧ table contains the list of metapost files that new instances -are initialised with. -The actual format will be loaded after the files in this table. +The ⟦*M.init_code⟧ table contains the code used for initialing new instances. +In it, the string ⟦INIT⟧ will be replaced with the value of the ⟦format⟧ option +(normally ⟦plain.mp⟧). \endmanual % diff --git a/macros/luatex/generic/minim-mp/minim-mp.ini b/macros/luatex/generic/minim-mp/minim-mp.ini index a257f6a18a..e3312a0cb9 100644 --- a/macros/luatex/generic/minim-mp/minim-mp.ini +++ b/macros/luatex/generic/minim-mp/minim-mp.ini @@ -7,6 +7,7 @@ \toksapp\everyjob{% \message{This is the minim metapost processor.}\relax + \directlua {require('minim-mp').on_line = true}% \newmetapostinstance[jobname="\jobname", mathmode="scaled"]\MP \runmetapost\MP{input "\jobname";}% \directlua {require('minim-mp').shipout(\the\MP)}% diff --git a/macros/luatex/generic/minim-mp/minim-mp.lua b/macros/luatex/generic/minim-mp/minim-mp.lua index 0f1851eba0..366313e60b 100644 --- a/macros/luatex/generic/minim-mp/minim-mp.lua +++ b/macros/luatex/generic/minim-mp/minim-mp.lua @@ -1,88 +1,13 @@ local alloc = require ('minim-alloc') local cb = require('minim-callbacks') +local pdfres = require('minim-pdfresources') alloc.remember ('minim-mp') local M = {} --1 AUXILIARY FUNCTIONS --- 2 pdf resource management - --- We add /ColorSpace and /Pattern entries to all page and xform resources. --- Each key refers to a central (global) object mapping names to objects. This --- central dictionary will be written to the pdf inside the finish_pdffile --- callback. --- --- In the future, /Shading and /ExtGState may also be added. --- --- Central resource dictionaries and all used resources will be written to the --- pdf inside the finish_pdf callback. --- --- Since a resource dictionary can only contain one entry of each kind, this --- package cannot coëxist with other packages doing their own resource --- management. I am aware of only one other package doing that, however, which --- is pgf. Luckily, pgf users are unlikely to be interested in metapost. Due to --- this incompatibility, however, resource management must be enabled --- explicitly. - --- keys are pdf names (starting with a slash) -local patterns = { }; M.patterns = patterns -local colourspaces = { }; M.colourspaces = colourspaces - -local global_resources, pattern_dict_objnum, colourspace_dict_objnum -function M.enable_resource_management() - if global_resources then return end - -- central dictionary objects - pattern_dict_objnum = pdf.reserveobj() - colourspace_dict_objnum = pdf.reserveobj() - global_resources = string.format('/Pattern %d 0 R /ColorSpace %d 0 R', - pattern_dict_objnum, colourspace_dict_objnum) - -- add to page and xform resources - pdf.setpageresources((pdf.getpageresources() or '')..global_resources) - pdf.setxformresources((pdf.getxformresources() or '')..global_resources) -end - --- Saved patterns should have the following fields: --- objnum the reserved object number for the pattern (optional) --- used true if the pattern is in use (should be set automatically) --- attr the pattern attributes --- stream the pattern drawing statements --- painttype the paint type of the pattern (1 or 2) --- Saved colour spaces should have the following fields: --- objnum the reserved object number for the colour space (optional) --- used true if the pattern is in use (should be set automatically) --- content the colour space contents (a pdf array) - -function M.write_resources() - if not global_resources then return end - -- patterns - local used_patterns = { '<<' } - for name, pat in pairs(patterns) do - if pat.used then - local objnum = pat.objnum or pdf.reserveobj() - pdf.immediateobj(objnum, 'stream', pat.stream, pat.attr) - table.insert(used_patterns, string.format('%s %d 0 R', name, objnum)) - end - end - table.insert(used_patterns, '>>') - pdf.immediateobj(pattern_dict_objnum, table.concat(used_patterns, ' ')) - -- colour spaces - local used_spaces = { '<<' } - for name, space in pairs(colourspaces) do - if space.used then - local objnum = space.objnum or pdf.reserveobj() - pdf.immediateobj(objnum, space.content) - table.insert(used_spaces, string.format('%s %d 0 R', name, objnum)) - end - end - table.insert(used_spaces, '>>') - pdf.immediateobj(colourspace_dict_objnum, table.concat(used_spaces, ' ')) -end - --- Write out resource objects at the end of the run -cb.register('finish_pdffile', M.write_resources) - -- 2 state metatable -- q Q store/restore graphics state @@ -125,6 +50,7 @@ function A.save(append) local st = append.state[#append.state] or { } append.state[#append.state+1] = { + linewidth = st.linewidth, miterlimit = st.miterlimit, linejoin = st.linejoin, linecap = st.linecap, @@ -159,6 +85,7 @@ local debugging = false function M.enable_debugging() debugging = true pdf.setcompresslevel(0) + pdf.setobjcompresslevel(0) end local function print_prop(append, obj, prop) @@ -270,7 +197,7 @@ local function curve_fmt(...) end function A.literal(append, fmt, ...) - local lit = node.new(8,16) + local lit = node.new(8,16) -- pdf_literal lit.data = fmt:format(...) append:node(lit) end @@ -336,14 +263,14 @@ end -- variables ‘stroke’ and ‘fill’ that record the last-used colour settings. -- preload device colour pattern colour spaces -colourspaces['/PsG'] = { content = '[ /Pattern /DeviceGray ]' } -colourspaces['/PsRG'] = { content = '[ /Pattern /DeviceRGB ]' } -colourspaces['/PsK'] = { content = '[ /Pattern /DeviceCMYK ]' } +pdfres.add_resource('ColorSpace', 'PsG', { value = '[ /Pattern /DeviceGray ]' }) +pdfres.add_resource('ColorSpace', 'PsRG', { value = '[ /Pattern /DeviceRGB ]' }) +pdfres.add_resource('ColorSpace', 'PsK', { value = '[ /Pattern /DeviceCMYK ]' }) local colour_template = { '%.3f ', '%.3f %.3f ', '%.3f %.3f %.3f ', '%.3f %.3f %.3f %.3f ' } local colour_stroke_operators = { 'G', nil, 'RG', 'K' } local colour_fill_operators = { 'g', nil, 'rg', 'k' } -local colour_pattern_spaces = { '/PsG', nil, '/PsRG', '/PsK' } +local colour_pattern_spaces = { 'PsG', nil, 'PsRG', 'PsK' } local function get_colour_params(cr) return format_numbers(colour_template[#cr], table.unpack(cr)) @@ -353,16 +280,16 @@ local function get_stroke_colour(cr) return get_colour_params(cr)..colour_stroke_operators[#cr] end -local function get_fill_colour(cr, pattern) +local function get_fill_colour(append, cr) local params = get_colour_params(cr) - if pattern then - local ptype, pname = table.unpack(pattern) + if append.pattern then + local ptype, pname = table.unpack(append.pattern) if ptype == 2 then -- coloured pattern local space = colour_pattern_spaces[#cr] - colourspaces[space].used = true - return string.format('%s cs %s%s scn', space, params, pname) + append:node(pdfres.use_resource_node('ColorSpace', space)) + return string.format('/%s cs %s%s scn', space, params, alloc.pdf_name(pname)) elseif ptype == 1 then -- uncoloured pattern - return string.format('/Pattern cs %s scn', pname) + return string.format('/Pattern cs %s scn', alloc.pdf_name(pname)) else -- should be unreachable alloc.err('Unknown pattern paint type %s', ptype) end @@ -386,7 +313,7 @@ function A.colour(append, cr, otype) end -- fill colour (possibly a pattern) if otype ~= 'outline' then - local fill = get_fill_colour(cr, append.pattern) + local fill = get_fill_colour(append, cr) append.pattern = nil -- patterns only apply to one object if fill ~= append.fill then append.fill = fill @@ -432,7 +359,7 @@ function A.linestate (append, object) end elseif append.dashed then append.dashed = false - table.insert(res, string.format('[] 0 d')) + table.insert(res, '[] 0 d') end append:literal(table.concat(res, ' ')) end @@ -505,16 +432,27 @@ end -- h close the path -- s close and draw the path (equivalent to h S) -- f fill the path (implies h) +-- f* fill the path, use even/odd rule -- b close, fill and draw the path (equivalent to h B) +-- b* close, fill and draw, use even/odd rule -- n do nothing (used for clipping paths) -local function get_path_operator (otype, open) +local function get_path_operator(otype, open) if otype == 'fill' then return 'f' elseif otype == 'outline' then return (open and 'S') or 's' elseif otype == 'both' then return 'b' + elseif otype == 'nofill' then + return '' + elseif otype == 'eofill' then + return 'f*' + elseif otype == 'eofilldraw' then + return 'b*' + else + alloc.err('Unknown path type ‘%s’', otype) + return 'f' end end @@ -557,7 +495,8 @@ local function split_specials(specials) end end -local function parse_object (append, object) +local function parse_object(append, object) + append.object_info = { } append:printobj(object) local processor = nil for sp, instr in split_specials(object.prescript) do @@ -616,29 +555,28 @@ end --2 fill and outline -function A.set_pen(append, object, otype, open) +process.fill_or_outline = function(append, object, otype) + append:linestate(object) + local t, appendpath if object.pen and object.pen.type == 'elliptical' then -- metapost includes nonelliptical pens in the outline - local t = mplib.pen_info(object) - append:literal(pdfnum('w', t.width)) - if otype == 'fill' then otype = 'both' end + t = mplib.pen_info(object) + if t.width ~= append.linewidth then + append:literal(pdfnum('w', t.width)) + append.linewidth = t.width + end + if otype == 'fill' then otype = 'both' end -- for in append:colour local transformed = not ( t.sx == 1 and t.rx == 0 and t.ry == 0 and t.sy == 1 and t.tx == 0 and t.ty == 0 ) - return transformed and t, get_path_operator(otype, open), otype - else - return false, get_path_operator(otype, open), otype + t = transformed and t end -end - -process.fill_or_outline = function(append, object, otype) + append:colour(object.color, otype) -- otype is 'fill' 'outline' or 'both' + otype = append.object_info.otype or otype -- 'eofill' etc. local open = object.path and object.path[1].left_type and object.path[#object.path].right_type - local t, operator, otype = append:set_pen(object, otype, open) - append:colour(object.color, otype) - append:linestate(object) - local appendpath + local operator = get_path_operator(otype, open) if t then local d = t.sx * t.sy - t.rx - t.ry local concat = function(px, py) @@ -671,6 +609,9 @@ end --2 specials +-- this will be pointed to the right table at the start of each run +local current_instance = false + -- pure specials are already taken care of in parse_object (they only have -- a ‘prescript’ field). process.special = function(append, object) end @@ -692,6 +633,10 @@ prescripts.latelua = function(append, str, object) end postscripts.latelua = prescripts.latelua +prescripts.OTYPE = function(append, str, object) + append.object_info.otype = append.object_info.otype or str +end + specials.BASELINE = function(append, str, object) -- object is a ‘fill’ statement with only a single point in its path (and -- will thus not have to be transformed). @@ -700,15 +645,26 @@ end --2 patterns +-- Saved patterns have the following fields: +-- attr the pattern attributes +-- stream the pattern drawing statements +-- painttype the paint type of the pattern (1 or 2) + +local function write_pattern_object(pat) + local objnum = pat.objnum or pdf.reserveobj() + pdf.obj(objnum, 'stream', pat.stream, pat.attr) + pdf.refobj(objnum) + return string.format('%d 0 R', objnum) +end + prescripts.fillpattern = function(append, str, object) - M.enable_resource_management() - local name = '/MnmP'..tonumber(str) - local pat = patterns[name] + local name = 'MnmP'..tonumber(str) + local pat = pdfres.get_resource('Pattern', name) if not pat then alloc.err('Unknown pattern %s', name) else append.pattern = { pat.painttype, name } - pat.used = true + append:node(pdfres.use_resource_node('Pattern', name)) end end @@ -719,9 +675,18 @@ specials.definepattern = function(append, str, object) matrix = { xx = xx, xy = xy, yx = yx, yy = yy, x = 0, y = 0 } } end +local function make_pattern_xform(head, bb) + -- regrettably, pdf.refobj does not work with xforms, so we must + -- write it to the pdf immediately, whether the pattern will be + -- used or not. + local xform = tex.saveboxresource(node.hpack(node.copy_list(head)), + '/Subtype/Form '..bb, nil, true, 4) + return string.format(' /Resources << /XObject << /PTempl %d 0 R >> >>', xform), '/PTempl Do' +end + local function definepattern(head, user, bb) local bb = bbox_fmt(table.unpack(bb)) - local pat, literals, resources = { }, { } + local pat, literals, resources = { write = write_pattern_object }, { } -- pattern content for n in node.traverse(head) do -- try if we can construct the content stream ourselves; otherwise, @@ -735,31 +700,27 @@ local function definepattern(head, user, bb) elseif n.subtype == 31 then -- restore table.insert(literals, 'Q') else - goto fail + resources, pat.stream = make_pattern_xform(head, bb) + goto continue end + else + resources, pat.stream = make_pattern_xform(head, bb) + goto continue end pat.stream = table.concat(literals, '\n') end - ::fail:: do - -- regrettably, pdf.refobj does not work with xforms, so we must - -- write it to the pdf immediately, whether the pattern will be - -- used or not. - local xform = tex.saveboxresource(node.hpack(node.copy_list(head)), - '/Subtype/Form '..bb, resources, true, 4) - resources = string.format(' /Resources << /XObject << /PTempl %d 0 R >> >>', xform) - pat.stream = '/PTempl Do' - end + ::continue:: -- construct the pattern object local i = user.pattern_info local m = i.matrix pat.painttype = tonumber(i.painttype) pat.attr = table.concat({ - string.format(' /PatternType 1 /PaintType %d /TilingType %s /XStep %f /YStep %f\n', + string.format('/PatternType 1 /PaintType %d /TilingType %s /XStep %f /YStep %f\n', i.painttype, i.tilingtype, i.xstep, i.ystep), string.format('%s\n/Matrix [ %s %s %s %s %s %s ]', bb, m.xx, m.xy, m.yx, m.yy, m.x, m.y), resources }, '') -- remember the pattern - patterns['/MnmP'..i.nr] = pat + pdfres.add_resource('Pattern', 'MnmP'..i.nr, pat) end cb.register('finish_mpfigure', function(img) @@ -787,17 +748,16 @@ local function get_transform(rect) return sx, rx, ry, sy, tx, ty end -local function make_surrounding(nd_id, head) +local function make_surrounding_box(nd_id, head) local nd = node.new(nd_id) - nd.dir = 'TLT' - nd.head = head + nd.dir, nd.head = 'TLT', head return nd end -local function apply_translation(box, tx, ty) - local horizontal = make_surrounding(0, box) - local vertical = make_surrounding(1, horizontal) - local outer = make_surrounding(0, vertical) +local function wrap_picture(head, tx, ty) + local horizontal = make_surrounding_box(0, head) + local vertical = make_surrounding_box(1, horizontal) + local outer = make_surrounding_box(0, vertical) vertical.shift = tex.sp('-'..ty..'bp') horizontal.shift = tex.sp(''..tx..'bp') return outer @@ -805,10 +765,10 @@ end local function apply_transform(rect, box) local sx, rx, ry, sy, tx, ty = get_transform(rect) - local transform = node.new(8,29) + local transform = node.new(8,29) -- pdf_setmatrix transform.next, box.prev = box, transform transform.data = string.format('%f %f %f %f', sx, rx, ry, sy) - return apply_translation(transform, tx, ty) + return wrap_picture(transform, tx, ty) end function A.box(append, object, box) @@ -827,7 +787,7 @@ end specials.CHAR = function(append, data, object) local char, font, xo, yo = table.unpack(data:explode(' ')) - local n = node.new(29) + local n = node.new(29) -- glyph n.char, n.font, n.xoffset, n.yoffset = tonumber(char), tonumber(font), tonumber(xo), tonumber(yo) append:box(object, node.hpack(n)) @@ -847,7 +807,7 @@ M.instances = instances --2 small instance helper functions -local default_catcodes = alloc.new_catcodetable('minim:mp:catcodes') +local default_catcodes = alloc.registernumber('minim:mp:catcodes') -- parameters: wd, ht+dp, dp local function make_transform(w, h, d) @@ -855,21 +815,27 @@ local function make_transform(w, h, d) w/65536, (h+d)/65536, d/65536) end -local function print_log (nr, res) +local status_names = { [0] = 'good', 'warning', 'error', 'fatal' } +local function print_status(st) + return string.format('status %d (%s)', st, status_names[st]) +end + +M.on_line = false +local function print_log (nr, res, why) local i = instances[nr] - -- only write to term if exit status increases + -- only write to term if on_line or if exit status increases local log, alog - if res.status > i.status then + if M.on_line or res.status > i.status then local nrlines, maxlines = 0, 16 alog = alloc.amsg log = function(...) - if nrlines == maxlines then + if M.on_line or nrlines < maxlines then + nrlines = nrlines + 1 + alloc.msg(...) + else alloc.log(...) alloc.term('╧ [... see log file for full report ...]') log, alog = alloc.log, alloc.alog - else - nrlines = nrlines + 1 - alloc.msg(...) end end else @@ -881,15 +847,11 @@ local function print_log (nr, res) report[#report] = nil end -- write out the log - if #report > 0 then - log('┌ exit status %d', res.status) - for _,line in ipairs(report) do - log('│ '..line) - end - log('└') - else - log('[ no logs for this run; exit status %d ]', res.status) + log('┌ %smetapost instance %s (%d)', why or '', i.jobname, i.nr) + for _,line in ipairs(report) do + log('│ '..line) end + log('└ %s', print_status(res.status)) -- generate error or warning if needed if res.status > i.status then if res.status == 3 then @@ -897,7 +859,7 @@ local function print_log (nr, res) elseif res.status == 2 then alloc.err('Error in metapost code. Further errors will be ignored') elseif res.status == 1 then - alloc.msg('Metapost: code caused warning') + alloc.msg('Metapost code caused warning') end end -- save the exit status for later comparison @@ -915,7 +877,6 @@ end --2 processing results local function process_results(nr, res) - print_log(nr, res) local pictures = {} if res.fig then alloc.alog (' (%d figures)', #res.fig) @@ -951,7 +912,7 @@ local function process_results(nr, res) end cb.call_callback('finish_mpfigure', pic) if not pic.discard then - pic.head = apply_translation(append.head, -llx, -bas) + pic.head = wrap_picture(append.head, -llx, -bas) end if debugging then alloc.msg('┌ image %s, with %s objects, %s nodes', @@ -987,8 +948,6 @@ local function default_env() return env end -local current_instance = false - local function runscript(code) local f, msg = load(code, current_instance.jobname, 't', current_instance.env) if f then @@ -1053,22 +1012,22 @@ local function maketext(text, mode) end end -local infont_box = alloc.new_box('infont box') -function M.infont(text, fnt) - local fontid = tonumber(fnt) or font.id(fnt) +local function getfontid(fnt) + return tonumber(fnt) or font.id(fnt) +end + +local typeset_box = alloc.new_box('infont box') + +local function process_typeset(text, fnt, sep, fn) tex.runtoks(function() tex.sprint(default_catcodes, string.format( - '\\setbox%d=\\hbox{\\setfontid%d\\relax', infont_box, fontid)) + '\\setbox%d=\\hbox{\\setfontid%d\\relax', typeset_box, getfontid(fnt))) tex.sprint(-2, text); tex.sprint(default_catcodes, '}') end) local res, x = { }, 0 - for n in node.traverse(tex.box[infont_box].list) do + for n in node.traverse(tex.box[typeset_box].list) do if n.id == 29 then -- glyph - table.insert(res, string.format( - 'draw image ( fill unitsquare shifted (%fpt,0) withprescript "CHAR:%d %d %d %d";' - ..'setbounds currentpicture to unitsquare transformed %s shifted (%fpt,0););', - x/65536, n.char, n.font, n.xoffset, n.yoffset, - make_transform(n.width, n.height, n.depth), x/65536)) + fn(res, n, x) x = x + n.width elseif n.id == 12 then -- glue x = x + n.width @@ -1076,7 +1035,88 @@ function M.infont(text, fnt) x = x + n.kern end end - return table.concat(res, '') + return table.concat(res, sep) +end + +function M.infont(text, fnt) + return process_typeset(text, fnt, '', function(res, n, x) + table.insert(res, string.format( + 'draw image ( fill unitsquare shifted (%fpt,0) withprescript "CHAR:%d %d %d %d";' + ..'setbounds currentpicture to unitsquare transformed %s shifted (%fpt,0););', + x/65536, n.char, n.font, n.xoffset, n.yoffset, + make_transform(n.width, n.height, n.depth), x/65536)) + end) +end + + +--2 glyph contours + +function M.make_glyph(glyphname, fnt) + -- gather information + if not luaotfload then + alloc.err('Luaotfload required for glyph of operator') + return { }, 10 + end + local fontid = getfontid(fnt) + local thefont = font.getfont(fontid) + local fontname = thefont.psname + local gid = luaotfload.aux.gid_of_name(fontid, glyphname) + if not gid then + alloc.err('Font %s has no glyph named %s', thefont.psname, glyphname) + return { }, 10 + end + local segments = fonts.hashes.shapes[fontid].glyphs[gid].segments + if #segments == 0 then return { }, 10 end + local q = 1000 / (thefont.units_per_em or 1000) + -- retrieve the contours + local path, paths = { '(' }, { } + for _, s in ipairs(segments) do + local op = s[#s] + if op == 'm' then + if #path > 1 then + table.insert(path, '--cycle)') + table.insert(paths, table.concat(path, '')) + path = { '(' } + end + table.insert(path, string.format('(%f, %f)', q*s[1], q*s[2])) + elseif op == 'l' then + table.insert(path, string.format('--(%f, %f)', q*s[1], q*s[2])) + elseif op == 'c' then + table.insert(path, string.format('..controls (%f, %f) and (%f, %f) .. (%f, %f)', + q*s[1], q*s[2], q*s[3], q*s[4], q*s[5], q*s[6])) + end + end + table.insert(path, '--cycle)') + table.insert(paths, table.concat(path, '')) + return paths, thefont.size +end + +function M.get_named_glyph(name, fnt) + local res, contours, size = {}, M.make_glyph(name, fnt) + for _, c in ipairs(contours) do + table.insert(res, string.format( + '%s scaled %f', c, size/65536000)) + end + return table.concat(res, ', ') +end + +local function get_glyphname(c_id) + return luaotfload.aux.name_of_slot(c_id) +end + +function M.get_glyph(c_id, fnt) + return M.get_named_glyph(get_glyphname(c_id), fnt) +end + +function M.get_contours(text, fnt) + return process_typeset(text, fnt, ', ', function(res, n, x) + local contours, size = M.make_glyph(get_glyphname(n.char), n.font) + for _, c in ipairs(contours) do + table.insert(res, string.format( + '%s scaled %f shifted (%fpt, %fpt)', c, + size/65536/1024, (x + n.xoffset)/65536, n.yoffset/65536)) + end + end) end --2 opening, running and and closing instances @@ -1122,27 +1162,21 @@ function M.run (nr, code) return end current_instance = self - alloc.log ('metapost: executing chunk in %s (%d)', self.jobname, nr) - local res = process_results(nr, self.instance:execute(code)) - save_image_list(self, res) + local res = self.instance:execute(code) + print_log(nr, res) + local picts = process_results(nr, res) + save_image_list(self, picts) end -M.init_files = { 'minim.mp' } +M.init_code = { 'let dump=endinput;', 'input INIT;', 'input minim.mp;' } function M.open (t) local nr = #instances + 1 t.jobname = t.jobname or ':metapost:' - alloc.log ('metapost: creating instance %s (%d)', t.jobname, nr) -- creating instance options - local init = "" - for _, f in ipairs(M.init_files) do - init = string.format('%s input %s;', init, f) - end - init = string.format('%s input %s;', init, t.format or 'plain.mp') + local init = string.gsub(table.concat(M.init_code, ''), 'INIT', t.format or 'plain.mp') local opts = apply_default_instance_opts(t) local instance = mplib.new(opts) - instance:execute(init) - local continue -- adding the instance instances[nr] = { nr = nr @@ -1154,15 +1188,15 @@ function M.open (t) , boxes = { } -- allocated by maketext , env = t.env or default_env() } + print_log(nr, instance:execute(init), 'opening ') return nr end function M.close (nr) local i = instances[nr] - alloc.log ('metapost: closing instance %s (%d) ', i.jobname, nr) if i.instance then local res = i.instance:finish() - print_log(nr, res) + print_log(nr, res, 'closing ') end for _, nr in ipairs(i.boxes) do -- remove allocated boxes diff --git a/macros/luatex/generic/minim-mp/minim-mp.pdf b/macros/luatex/generic/minim-mp/minim-mp.pdf Binary files differindex b511de3297..1011878d1e 100644 --- a/macros/luatex/generic/minim-mp/minim-mp.pdf +++ b/macros/luatex/generic/minim-mp/minim-mp.pdf diff --git a/macros/luatex/generic/minim-mp/minim-mp.tex b/macros/luatex/generic/minim-mp/minim-mp.tex index 8e39c7f227..ad84a4f19d 100644 --- a/macros/luatex/generic/minim-mp/minim-mp.tex +++ b/macros/luatex/generic/minim-mp/minim-mp.tex @@ -6,6 +6,7 @@ \catcode`\: = 11 \input minim-alloc +\input minim-pdfresources % a default catcode table \newcatcodetable \minim:mp:catcodes @@ -30,7 +31,7 @@ % \directmetapost [ options ] { code } \protected\def\directmetapost{\withoptions[]\directmetapost:} -\def\directmetapost:[#1]#2{% +\long\def\directmetapost:[#1]#2{% \begingroup \newmetapostinstance[#1]\:mpinst: \runmetapost\:mpinst:{#2}% diff --git a/macros/luatex/generic/minim-mp/minim.mp b/macros/luatex/generic/minim-mp/minim.mp index c78dde2a92..f3f03eb3d0 100644 --- a/macros/luatex/generic/minim-mp/minim.mp +++ b/macros/luatex/generic/minim-mp/minim.mp @@ -1,13 +1,19 @@ delimiters (); +% redefine some constants to work with the new numerical engines +numeric mm, cm, pt, pc, dd, cc; +pc = 12 pt; cc = 12 dd; cm = 10 mm; +803 pt = 800; 127 mm = 360; 1157 dd = 1238 pt; +eps := 1/2048; infinity := 64*64-epsilon; + message "Loading the minim extension macros"; % interaction with tex -def baseline expr o = - fill if numeric o: (0,o) else o fi +vardef baseline expr o = + fill if numeric o : (0,o) else: o fi -- cycle withprescript "BASELINE:"; enddef; -def boxresource expr nr = image( +vardef boxresource expr nr = image( fill unitsquare withprescript "BOXRESOURCE:" & decimal nr ; setbounds currentpicture to unitsquare transformed runscript ("return { 'box_size', tex.getboxresourcedimensions(" & decimal nr & ") }"); @@ -17,6 +23,46 @@ primarydef t infont f = image( runscript( "return require('minim-mp').infont("&ditto&t&ditto&", " &if numeric f: decimal(f) else: ditto&f&ditto fi&")" ) ) enddef; +% even-odd rule +def nofill expr c = fill c withprescript "OTYPE:nofill" enddef; +def eofill expr c = fill c withprescript "OTYPE:eofill" enddef; +def eofilldraw expr c = filldraw c withprescript "OTYPE:eofilldraw" enddef; + +def multidraw (text paths) text opts = draw image( + for p = paths: ; nofill p opts endfor + withprescript "OTYPE:outline";) enddef; +def multifill (text paths) text opts = draw image( + for p = paths: ; nofill p opts endfor + withprescript "OTYPE:fill";) enddef; +def multifilldraw (text paths) text opts = draw image( + for p = paths: ; nofill p opts endfor + withprescript "OTYPE:filldraw";) enddef; +def multieofill (text paths) text opts = draw image( + for p = paths: ; nofill p opts endfor + withprescript "OTYPE:eofill";) enddef; +def multieofilldraw (text paths) text opts = draw image( + for p = paths: ; nofill p opts endfor + withprescript "OTYPE:eofilldraw";) enddef; + +vardef clipto text t = + clip currentpicture to bbox currentpicture + for c = t: -- c -- cycle endfor -- cycle enddef; +vardef clipout text t = + clip currentpicture to + for c = t: c -- cycle -- endfor cycle enddef; + +vardef glyph expr c of f = image(for p = + runscript("return require('minim-mp').get_" + &if string c: "named_glyph("&ditto&c&ditto + else: "glyph("& decimal c fi &", " + &if numeric f: decimal(f) else: ditto&f&ditto fi + & ")" ) : ; nofill p endfor + withprescript "OTYPE:fill"; ) enddef; + +def contours expr t of f = + runscript("return require('minim-mp').get_contours("&ditto&t&ditto&", " + &if numeric f: decimal(f) else: ditto&f&ditto fi& ")" ) enddef; + % interaction with lua vardef hexadecimal expr n = % TODO: support other number systems @@ -68,6 +114,31 @@ def endpattern (expr xstep, ystep) = newinternal tilingtype; tilingtype:=1; _patterns_._last_ := 0; -% so that we can load plain.mp after this -let dump = endinput ; +% shorthands +primarydef p xshifted x = p shifted (x,0) enddef; +primarydef p yshifted y = p shifted (0,y) enddef; + +% reverse paths to allow „p & vflip p” +def hflip primary p = if path p: reverse fi p xscaled -1 enddef; +def vflip primary p = if path p: reverse fi p yscaled -1 enddef; + +% missing definitions +path fullsquare, unitcircle ; +fullsquare := unitsquare shifted - center unitsquare ; +unitcircle := fullcircle shifted urcorner fullcircle ; + +% complement counterclockwise +vardef clockwise primary c = + if turningnumber c > 0: reverse fi c enddef; + +% cmyk colours +cmykcolor cyan, magenta, yellow, key; +cyan = (1,0,0,0); +magenta = (0,1,0,0); +yellow = (0,0,1,0); +key = (0,0,0,1); + +% constants +pi := 355/113; + |