diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua | 340 |
1 files changed, 273 insertions, 67 deletions
diff --git a/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua b/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua index f1cfd493887..9069ab7147e 100644 --- a/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua +++ b/Master/texmf-dist/tex/luatex/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 |