summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/minim-mp
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-03-04 03:01:04 +0000
committerNorbert Preining <norbert@preining.info>2022-03-04 03:01:04 +0000
commit575ab7d0e7534edd510bbc89a2ef4b04582af591 (patch)
tree9fc4cf55b7ca8b168bdfd747be21de4c9ef2b30e /macros/luatex/generic/minim-mp
parent6b9ed20cb01cb52c7aaf2a48202f5d8aa588ff79 (diff)
CTAN sync 202203040301
Diffstat (limited to 'macros/luatex/generic/minim-mp')
-rw-r--r--macros/luatex/generic/minim-mp/README32
-rw-r--r--macros/luatex/generic/minim-mp/minim-lamp.ini20
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.doc112
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.ini1
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.lua390
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.pdfbin82287 -> 94557 bytes
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.tex3
-rw-r--r--macros/luatex/generic/minim-mp/minim.mp81
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
index b511de3297..1011878d1e 100644
--- a/macros/luatex/generic/minim-mp/minim-mp.pdf
+++ b/macros/luatex/generic/minim-mp/minim-mp.pdf
Binary files differ
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;
+