summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/minim-mp/minim-mp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/generic/minim-mp/minim-mp.lua')
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.lua340
1 files changed, 273 insertions, 67 deletions
diff --git a/macros/luatex/generic/minim-mp/minim-mp.lua b/macros/luatex/generic/minim-mp/minim-mp.lua
index f1cfd49388..9069ab7147 100644
--- a/macros/luatex/generic/minim-mp/minim-mp.lua
+++ b/macros/luatex/generic/minim-mp/minim-mp.lua
@@ -4,7 +4,9 @@ local cb = require('minim-callbacks')
local pdfres = require('minim-pdfresources')
alloc.remember ('minim-mp')
-local M = {}
+local M = { } -- module contents
+local E = { } -- runscript environment
+local A = { } -- postprocessing functions
-- 1 AUXILIARY FUNCTIONS
@@ -29,7 +31,6 @@ end
-- We can call append:somefunction(...) to append a node or append:somevariable
-- to query the graphics state. These go via its metatable:
-local A = { } -- appending functions
local append_meta =
{
-- Either return an appending function or an entry in the graphics state.
@@ -48,6 +49,7 @@ local function init_append()
node_count = 0, -- node count
state = { }, -- graphics state variables
user = { }, -- user data for extensions
+ object_info = { }, -- data for current object
}, append_meta)
end
@@ -78,7 +80,8 @@ end
-- The following callback is executed just before the final step of surrounding
-- the image in properly-dimensioned boxes. It receives the processed image
--- object as argument. That object has the following fields:
+-- object as argument. That object may have the following fields (which can be
+-- changed):
-- head the head of the node list
-- name the image name
-- user the image user table
@@ -97,6 +100,7 @@ function M.enable_debugging()
pdf.setcompresslevel(0)
pdf.setobjcompresslevel(0)
end
+E.enable_debugging = M.enable_debugging
local function print_prop(append, obj, prop)
if obj[prop] then
@@ -179,7 +183,8 @@ end
local function format_numbers(...)
return (string.format(...)
:gsub('[.]0+ ', ' ')
- :gsub('([.][1-9]+)0+ ', '%1 '))
+ :gsub('([.][1-9]+)0+ ', '%1 ')
+ :gsub(' +$', ''))
end
-- Only to be used for coordinates: ‘cm’ parameters should not be rounded.
@@ -188,6 +193,12 @@ local function pdfnum(operator, ...)
return format_numbers(fmt, ...)
end
+local function make_pdf_dashpattern(dl)
+ return string.format('[%s] %s',
+ table.concat(dl.dashes or {},' '),
+ pdfnum('d', dl.offset))
+end
+
local function point_fmt(operator, ...)
local dd = pdf.getdecimaldigits()
local fmt = string.format('%%.%sf %%.%sf %s', dd, dd, operator)
@@ -212,6 +223,43 @@ function A.literal(append, fmt, ...)
append:node(lit)
end
+-- 2 parsing helpers
+
+local function parse_lua_table(name, str)
+ local f, msg = load('return '..str, name, 't')
+ if msg then return alloc.err(msg) end
+ t = f()
+ if not t or type(t) ~= 'table' then
+ return alloc.err('%s attributes must be given as lua table', name)
+ end
+ -- format all numbers with pdfnum
+ for k, v in pairs(t) do
+ if type(v) == 'number' then
+ t[k] = pdfnum('', v)
+ end
+ end
+ return t
+end
+
+-- 2 boxing helpers
+
+local function make_surrounding_box(nd_id, head)
+ local nd = node.new(nd_id)
+ nd.dir, nd.head = 'TLT', head
+ return nd
+end
+
+local function wrap_picture(head, x0, y0)
+ -- Note: this does not set the image dimensions
+ local horizontal = make_surrounding_box('hlist', head)
+ local vertical = make_surrounding_box('vlist', horizontal)
+ local outer = make_surrounding_box('hlist', vertical)
+ vertical.shift = -y0
+ horizontal.shift = x0
+ return outer
+end
+M.wrap_picture = wrap_picture
+
-- 2 colour conversion
local function rgb_to_gray (r,g,b)
@@ -219,6 +267,7 @@ local function rgb_to_gray (r,g,b)
+ tex.count['GtoG'] * g / 10000
+ tex.count['BtoG'] * b / 10000 )
end
+E.rgb_to_gray = rgb_to_gray
local function cmyk_to_rgb (c,m,y,k)
return (1-k)*(1-c), (1-k)*(1-m), (1-k)*(1-y)
@@ -283,7 +332,7 @@ local colour_fill_operators = { 'g', nil, 'rg', 'k' }
local colour_pattern_spaces = { 'PsG', nil, 'PsRG', 'PsK' }
local function get_colour_params(cr)
- return format_numbers(colour_template[#cr], table.unpack(cr))
+ return format_numbers(colour_template[#cr], table.unpack(cr)) .. ' '
end
local function get_stroke_colour(cr)
@@ -362,7 +411,7 @@ function A.linestate (append, object)
end
local dl = object.dash
if dl then
- local d = string.format('[%s] %s', table.concat(dl.dashes or {},' '), pdfnum('d', dl.offset))
+ local d = make_pdf_dashpattern(dl)
if d ~= append.dashed then
append.dashed = d
table.insert(res, d)
@@ -653,6 +702,105 @@ specials.BASELINE = function(append, _, object)
append.baseline = object.path[1].y_coord
end
+-- 2 box resources (xforms)
+
+-- The first problem that we run into is that we want to refer to xforms (from
+-- the metapost side) whose numerical id* we do know know yet (because the
+-- xform will only be made at the end of the run, during the lua-side
+-- processing of mplib output).
+--
+-- Our solution is using negative ids for metapost-defined xforms, which will
+-- be converted to actual boxresource ids by means of a conversion table.
+--
+-- The second problem is the need to position metapost-defined xforms by their
+-- original (metapost) origin. This necessitates storing extra information
+-- lua-side.
+--
+-- * Not to be confused with the object number which we, due to pdftex’s
+-- special handling of xforms, will never come to know.
+
+local xform_nr_shim = { }
+
+local function resolve_xform_id(nr)
+ nr = tonumber(nr)
+ if nr < 0 then
+ return xform_nr_shim[-nr]
+ else
+ return nr
+ end
+end
+
+local function reserve_xform_id()
+ xform_nr_shim[1+#xform_nr_shim] = false
+ return -#xform_nr_shim
+end
+
+M.resolve_xform_id = resolve_xform_id
+E.resolve_xform_id = resolve_xform_id
+E.reserve_xform_id = reserve_xform_id
+
+-- When using xforms in metapost, the positive numbers refer to tex-defined and
+-- the negative to metapost-defined box resources.
+
+specials.BOXRESOURCE = function(append, id, object)
+ local realid = resolve_xform_id(id)
+ local rule = tex.useboxresource(realid)
+ append:box(object, node.hpack(rule))
+end
+
+local xform_sizes = { }
+local xform_center = { }
+
+local function set_boxresource_dimensions (id, transf, center)
+ xform_sizes[id] = transf
+ xform_center[id] = center
+end
+
+local function get_boxresource_dimensions (id)
+ if xform_sizes[id] then
+ return xform_sizes[id]
+ else
+ return { 'box_size', tex.getboxresourcedimensions(id) }
+ end
+end
+
+local function get_boxresource_center (id)
+ if xform_center[id] then
+ return xform_center[id]
+ else
+ return "(0,0)"
+ end
+end
+
+E.set_boxresource_dimensions = set_boxresource_dimensions
+E.get_boxresource_dimensions = get_boxresource_dimensions
+E.get_boxresource_center = get_boxresource_center
+
+local function save_as_boxresource(pic)
+ -- attributes
+ local attrs = { '/Subtype/Form', bbox_fmt(0, -pic.dp, pic.wd, pic.ht) }
+ for k, v in pairs(pic.user.xform_attrs or { }) do
+ table.insert(attrs, string.format('%s %s', alloc.pdf_name(k), v))
+ end
+ -- box to-be-saved
+ local box = wrap_picture(node.copy_list(pic.head), pic.x0, pic.y0)
+ box.width, box.height, box.depth = pic.wd, pic.ht, pic.dp
+ -- save the box
+ local xform = tex.saveboxresource(box,
+ table.concat(attrs, ' '), nil, false, 4) -- 4: no /Type, /BBox or /Matrix
+ xform_nr_shim[-pic.user.xform_id] = xform
+end
+
+specials.XFORMINDEX = function(append, id, object)
+ append.user.xform_id = tonumber(id)
+ append.user.save_fn = save_as_boxresource
+end
+
+specials.XFORMATTRS = function(append, attrs, object)
+ local t = parse_lua_table('XForm attributes', attrs)
+ append.user.xform_attrs = t or { }
+end
+
-- 2 patterns
-- Saved patterns have the following fields:
@@ -678,27 +826,20 @@ prescripts.fillpattern = function(append, str, object)
end
end
-specials.definepattern = function(append, str, object)
- local nr, tiling, paint, xs, ys, xx, xy, yx, yy = table.unpack(str:explode())
- append.user.pattern_info = { nr = nr, xstep = xs, ystep = ys,
- tilingtype = tiling, painttype = paint,
- 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)
+ local xform = tex.saveboxresource((node.hpack(node.copy_list(head))),
+ '/Subtype/Form '..bb, nil, true, 4) -- 4: no /Type, /BBox or /Matrix
return string.format(' /Resources << /XObject << /PTempl %d 0 R >> >>', xform), '/PTempl Do'
end
-local function definepattern(head, user, bbox)
- local bb = bbox_fmt(table.unpack(bbox))
+local function definepattern(pic)
+ local bb = bbox_fmt(table.unpack(pic.bbox))
local pat, literals, resources = { write = write_pattern_object }, { }
-- pattern content
- for n in node.traverse(head) do
+ for n in node.traverse(pic.head) do
-- try if we can construct the content stream ourselves; otherwise,
-- stuff the pattern template into an xform.
if n.id == WHATSIT_NODE then
@@ -710,18 +851,18 @@ local function definepattern(head, user, bbox)
elseif n.subtype == node.subtype 'restore' then
table.insert(literals, 'Q')
else
- resources, pat.stream = make_pattern_xform(head, bb)
+ resources, pat.stream = make_pattern_xform(pic.head, bb)
goto continue
end
else
- resources, pat.stream = make_pattern_xform(head, bb)
+ resources, pat.stream = make_pattern_xform(pic.head, bb)
goto continue
end
pat.stream = table.concat(literals, '\n')
end
::continue::
-- construct the pattern object
- local i = user.pattern_info
+ local i = pic.user.pattern_info
local m = i.matrix
pat.painttype = tonumber(i.painttype)
pat.attr = table.concat({
@@ -733,12 +874,88 @@ local function definepattern(head, user, bbox)
pdfres.add_resource('Pattern', 'MnmP'..i.nr, pat)
end
-cb.register('finish_mpfigure', function(img)
- if img.user.pattern_info then
- definepattern(img.head, img.user, img.bbox)
- img.discard = true
+specials.definepattern = function(append, str, object)
+ local nr, tiling, paint, xs, ys, xx, xy, yx, yy = table.unpack(str:explode())
+ append.user.pattern_info = { nr = nr, xstep = xs, ystep = ys,
+ tilingtype = tiling, painttype = paint,
+ matrix = { xx = xx, xy = xy, yx = yx, yy = yy, x = 0, y = 0 } }
+ append.user.save_fn = definepattern
+end
+
+-- 2 extgstates
+
+-- Specials for the graphics state:
+--
+-- gstate:save q
+-- gstate:restore Q
+--
+-- extgstate:label /label gs
+-- extgstate:{...} /newlabel gs
+--
+-- If a lua table is passed to the extgstate, minim will try and find an
+-- earlier-defined matching ExtGState object. If it cannot be found, a new one
+-- will be created and used. The values of the table will be written to the pdf
+-- as-they-are (this means booleans and numbers are alright), but the keys will
+-- be converted to pdf name objects.
+
+prescripts.gstate = function(append, str, object)
+ if str == 'save' or str == 'store' then
+ append:save()
+ elseif str == 'restore' then
+ append:restore()
+ else
+ alloc.err('Unknown gstate operator: »%s«', str)
end
-end)
+end
+postscripts.gstate = prescripts.gstate
+
+local gs_next, gs_index = 1, { }
+
+function M.get_gstate_name(t)
+ -- format the pdf dict
+ local d = { }
+ for k,v in pairs(t) do
+ if type(v) == 'table' and t.dashes then
+ v = '[ '..make_pdf_dashpattern(v)..' ]'
+ end
+ table.insert(d, string.format('%s %s', alloc.pdf_name(k), v))
+ end
+ table.sort(d)
+ d[0] = '<<'; table.insert(d, '>>')
+ t.value = table.concat(d, ' ', 0)
+ -- check the index
+ if not gs_index[t.value] then
+ local name = string.format('MnmGs%d', gs_next)
+ gs_index[t.value] = name
+ gs_next = gs_next + 1
+ pdfres.add_resource('ExtGState', name, t)
+ end
+ -- return the resource name
+ return gs_index[t.value]
+end
+
+prescripts.extgstate = function(append, str, object)
+ -- determine resource name
+ local name, t = str
+ if string.find(str, '^[A-Za-z0-9_]+$') then
+ t = pdfres.get_resource('ExtGState', name)
+ if not t then return alloc.err('Unknown named ExtGState: %s', name) end
+ else
+ t = parse_lua_table('ExtGState', str)
+ if not t then return end
+ name = M.get_gstate_name(t)
+ end
+-- -- update state parameters (disabled for now; may be enabled if it has practical use)
+-- if t.LW then append.linewidth = t.LW end
+-- if t.ML then append.miterlimit = t.LW end
+-- if t.LJ then append.linejoin = t.LW end
+-- if t.LC then append.linecap = t.LW end
+-- if t.D and type(t.D) == 'table' then append.dashed = make_pdf_dashpattern(t.D) end
+ -- apply the state change
+ append:literal('%s gs', alloc.pdf_name(name))
+ append:node(pdfres.use_resource_node('ExtGState', name))
+end
+postscripts.extgstate = prescripts.extgstate
-- 2 text
@@ -758,26 +975,13 @@ local function get_transform(rect)
return sx, rx, ry, sy, tx, ty
end
-local function make_surrounding_box(nd_id, head)
- local nd = node.new(nd_id)
- nd.dir, nd.head = 'TLT', head
- return nd
-end
-
-local function wrap_picture(head, tx, ty)
- local horizontal = make_surrounding_box('hlist', head)
- local vertical = make_surrounding_box('vlist', horizontal)
- local outer = make_surrounding_box('hlist', vertical)
- vertical.shift = tex.sp('-'..ty..'bp')
- horizontal.shift = tex.sp(''..tx..'bp')
- return outer
-end
-
local function apply_transform(rect, box)
local sx, rx, ry, sy, tx, ty = get_transform(rect)
local transform = node.new('whatsit', 'pdf_setmatrix')
transform.next, box.prev = box, transform
transform.data = string.format('%f %f %f %f', sx, rx, ry, sy)
+ tx = tex.sp(''..tx..'bp')
+ ty = tex.sp(''..ty..'bp')
return wrap_picture(transform, tx, ty)
end
@@ -803,11 +1007,6 @@ specials.CHAR = function(append, data, object)
append:box(object, node.hpack(n))
end
-specials.BOXRESOURCE = function(append, resource, object)
- local rule = tex.useboxresource(tonumber(resource))
- append:box(object, rule)
-end
-
--
-- 1 METAPOST INSTANCES
@@ -935,8 +1134,12 @@ local function process_results(nr, res)
pic.y0 = tex.sp(string.format('%s bp', -bas ))
end
cb.call_callback('finish_mpfigure', pic)
+ if pic.user.save_fn then
+ pic.discard = true
+ pic.user.save_fn(pic)
+ end
if not pic.discard then
- pic.head = wrap_picture(append.head, -llx, -bas)
+ pic.head = wrap_picture(pic.head, pic.x0, pic.y0)
end
if debugging then
alloc.msg('┌ image %s, with %s objects, %s nodes',
@@ -967,11 +1170,11 @@ end
-- a string that metapost can understand.
--
-- Tables of the form { 'box_size', width, height, depth, margin } are
--- converted to a transformation. (The margin will be ignored for now, this may
+-- converted to a transform. (The margin will be ignored for now, this may
-- change in the future.)
local function mkluastring(s)
- return "'"..(s:gsub('\\', '\\\\'):gsub("'", "\\'")).."'"
+ return "'"..(s:gsub('\\', '\\\\'):gsub("'", "\\'"):gsub('\n', '\\n')).."'"
end
local function runscript(code)
@@ -991,15 +1194,22 @@ local function runscript(code)
return string.format('%.16f', result)
elseif t == 'string' then
return result
- elseif t == 'table' and t[1] == 'box_size' then
- -- TODO: take the margin into account if present (t[5])?
- return make_transform(t[2], t[3], t[4])
- elseif t == 'table' and #t < 5 then
- local fmt = #t == 1 and '%.16f'
- or #t == 2 and '(%.16f, %.16f)'
- or #t == 3 and '(%.16f, %.16f, %.16f)'
- or #t == 4 and '(%.16f, %.16f, %.16f, %.16f)'
- return fmt:format(table.unpack(t))
+ elseif t == 'table' and result[1] == 'box_size' then
+ return make_transform(result[2], result[3], result[4])
+ elseif t == 'table' and #result < 5 then
+ local fmt = #result == 1 and '%.16f'
+ or #result == 2 and '(%.16f, %.16f)'
+ or #result == 3 and '(%.16f, %.16f, %.16f)'
+ or #result == 4 and '(%.16f, %.16f, %.16f, %.16f)'
+ return fmt:format(table.unpack(result))
+ elseif t == 'table' and #result == 6 then
+ return table.concat({
+ 'begingroup save t; transform t;',
+ 'xxpart t = %.16f', 'xypart t = %.16f',
+ 'yxpart t = %.16f', 'yypart t = %.16f',
+ 'xpart t = %.16f', 'ypart t = %.16f',
+ 't endgroup' },
+ ';'):format(table.unpack(result))
else
return tostring(result)
end
@@ -1016,8 +1226,8 @@ end
-- environment. The runscript environment will then be extended with all
-- elements of the M.mp_functions table.
-local E = { }
M.mp_functions = E
+E.texmessage = alloc.msg
local function prepare_env(e) -- in M.open()
local env = e or copy_table(_G, { })
@@ -1038,9 +1248,6 @@ function E.quote_for_lua(s)
return E.quote(mkluastring(s))
end
-E.rgb_to_gray = rgb_to_gray
-E.enable_debugging = M.enable_debugging
-
-- 2 typesetting with tex
-- The result of the maketext function is fed back into metapost; on that side,
@@ -1065,9 +1272,8 @@ local function maketext(text, mode)
local assignment = string.format('\\global\\setbox%s=\\hbox{\\the\\everymaketext %s}', nr, text)
tex.runtoks(function() tex.print(current_instance.catcodes, assignment:explode('\n')) end)
local box = tex.box[nr]
- return 'image ( fill unitsquare withprescript "TEXBOX:' ..nr..'";'..
- 'setbounds currentpicture to unitsquare transformed '..
- make_transform(box.width, box.height, box.depth) .. ';)'
+ return string.format('_set_maketext_result_(%d, %s)', nr,
+ make_transform(box.width, box.height, box.depth))
elseif mode == 1 then -- verbatimtex
tex.runtoks(function() tex.print(current_instance.catcodes, text:explode('\n')) end)
end
@@ -1311,7 +1517,7 @@ end
function M.get_image(nr, name)
local image = retrieve(nr, name)
if image then
- local box = node.hpack(image.head)
+ local box = image.head
box.width = image.wd
box.height = image.ht
box.depth = image.dp
@@ -1329,7 +1535,7 @@ function M.shipout(nr)
tex.pageheight = (image.ht + image.dp)
tex.pagewidth = image.wd
tex.voffset = image.ht
- tex.box[255] = node.vpack(node.hpack(image.head))
+ tex.box[255] = image.head
tex.shipout(255)
tex.count[0] = tex.count[0] + 1
end