From 218e823b3c691868564686239bf240745742eda6 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Mon, 8 Jan 2024 21:47:22 +0000 Subject: minim (8jan24) git-svn-id: svn://tug.org/texlive/trunk@69352 c570f23f-e606-0410-a88d-b1316a301751 --- .../tex/luatex/minim-math/minim-math.tex | 2 + Master/texmf-dist/tex/luatex/minim-mp/minim-mp.lua | 340 +++++++++++++++++---- Master/texmf-dist/tex/luatex/minim-mp/minim-mp.sty | 19 +- .../texmf-dist/tex/luatex/minim-pdf/minim-pdf.lua | 135 ++++++-- .../texmf-dist/tex/luatex/minim-pdf/minim-pdf.tex | 53 +++- .../texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua | 6 + Master/texmf-dist/tex/luatex/minim/minim-doc.sty | 36 ++- .../tex/luatex/minim/minim-pdfresources.lua | 6 +- .../tex/luatex/minim/minim-pdfresources.tex | 1 + 9 files changed, 470 insertions(+), 128 deletions(-) (limited to 'Master/texmf-dist/tex/luatex') diff --git a/Master/texmf-dist/tex/luatex/minim-math/minim-math.tex b/Master/texmf-dist/tex/luatex/minim-math/minim-math.tex index 1a8f1157c11..7029952418a 100644 --- a/Master/texmf-dist/tex/luatex/minim-math/minim-math.tex +++ b/Master/texmf-dist/tex/luatex/minim-math/minim-math.tex @@ -468,6 +468,8 @@ \def\braket#1#2{\left⟨#1\middle\vert#2\right⟩} \def\norm#1{\left\vert#1\right\vert} \let \abs=\norm \def\Norm#1{\left\Vert#1\right\Vert} +\def\ceil#1{\left⌈#1\right⌉} +\def\floor#1{\left⌊#1\right⌋} % a slightly smaller unary minus \def\unaryminus{\mathord{\mathpalette\unaryminus:make{}}} 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 diff --git a/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.sty b/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.sty index ae0312b5609..250e86668e0 100644 --- a/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.sty +++ b/Master/texmf-dist/tex/luatex/minim-mp/minim-mp.sty @@ -3,10 +3,6 @@ \input minim-mp -\makeatletter - -\RequirePackage{environ} - % work around latex’s \protect mechanism \let\mnm@protect=\protect \everymaketext{\let\protect=\mnm@protect} @@ -21,12 +17,12 @@ \let\mpcolor = \minimpcolor \csname:metapost:\endcsname[#1]} {\csname end:metapost:\endcsname \endgroup} -\NewEnviron{:metapost:}[1][]{% +\NewDocumentEnvironment{:metapost:}{O{}+b}{% \mnm@setnormalfont \let\protect=\noexpand \directmetapost[#1]{% - defaultfont:="\mnm@normalfont"; \BODY; }% - \let\protect=\mnm@protect} + defaultfont:="\mnm@normalfont"; #2; }% + \let\protect=\mnm@protect}{} % separate metapost instances \newcommand\newmetapostenvironment[2][]{% @@ -37,13 +33,13 @@ \let\mpcolor = \minimpcolor \csname:#2:\endcsname} {\csname end:#2:\endcsname \endgroup}% - \NewEnviron{:#2:}{% + \NewDocumentEnvironment{:#2:}{+b}{% \mnm@setnormalfont \let\protect=\noexpand \runmetapostimage \csname #2@instance\endcsname - {defaultfont:="\mnm@normalfont";\BODY;}% - \let\protect=\mnm@protect}} + {defaultfont:="\mnm@normalfont";##1;}% + \let\protect=\mnm@protect}{}} % \mpcolor support \def\minimpcolor#1#{\dominimpcolor{#1}} @@ -54,6 +50,3 @@ \newtoks\mpcolorspectoks \newtoks\mpcolorruntoks \newtoks\mpcolorvaltoks \mpcolorruntoks{\expandafter\extractcolorspec\the\mpcolorspectoks\mptmpcolor \expandafter\mpcolorvaltoks\expandafter\@gobble\mptmpcolor} - -\makeatother - diff --git a/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.lua b/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.lua index 74d56eeaac2..875fd896592 100644 --- a/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.lua +++ b/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.lua @@ -7,9 +7,11 @@ alloc.remember('minim-pdf') -- 1 helper functions +local HLIST_NODE = node.id 'hlist' local GLUE_NODE = node.id 'glue' local GLYPH_NODE = node.id 'glyph' local RULE_NODE = node.id 'rule' +local DISC_NODE = node.id 'disc' local WHATSIT_NODE = node.id 'whatsit' local USER_DEFINED = node.subtype 'user_defined' local START_LINK = node.subtype 'pdf_start_link' @@ -51,9 +53,8 @@ local function singleton(t) return one end --- in-depth node list traversal --- returns current and parent node --- only dives into hbox, vbox and ins nodes +-- in-depth node list traversal; returns current and parent node +-- only dives into hbox, vbox and ins nodes, and in disc no-break text -- returns node and enclosing box local function full_traverse(head) return function(stack, last) @@ -178,7 +179,7 @@ local structure_types = alloc.saved_table('structure types', { -- inline structure elements ILSE Span = { type = 'inline' }, Quote = { type = 'inline' }, - Note = { type = 'inline' }, + Note = { type = 'extern' }, Reference = { type = 'inline' }, BibEntry = { type = 'inline' }, Code = { type = 'inline', attributes = { ['CSS-2.00'] = { ['white-space'] = '(pre)' } } }, @@ -198,9 +199,10 @@ local structure_type_compatibility = { none = { none=1, section=1, group=1, block=1, inline=1, contain=1 }, section = { none=1, section=1, group=1, block=1 }, group = { none=1, group=1, block=1 }, - block = { none=1, inline=1 }, - inline = { none=1, inline=1 }, - contain = { none=1, inline=1, block=1, group=1 }, + extern = { none=1, group=1, block=1 }, + block = { none=1, inline=1, extern=1 }, + inline = { none=1, inline=1, extern=1 }, + contain = { none=1, inline=1, block=1, group=1, extern=1 }, } local function check_structure_compatibility(parent, child, childtype) @@ -354,10 +356,19 @@ local function write_structure() local id_tree, id_tree_obj = { }, pdf.reserveobj() structure[1].parent = { objnum = root_obj } for _, se in ipairs(structure) do - if not se.hidden then se.objnum = pdf.reserveobj() end + if not se.hidden then + se.objnum = pdf.reserveobj() + if se.id then + id_tree[se.id] = se.objnum + end + end end -- update the document catalog - add_to_catalog('/MarkInfo << /Marked true >>') + if tex.count['pdfuaconformancelevel'] > 0 then + add_to_catalog('/MarkInfo << /Marked true /Suspects false >>') + else + add_to_catalog('/MarkInfo << /Marked true >>') + end add_to_catalog('/StructTreeRoot %s 0 R', root_obj) -- write the structure tree root pdf.immediateobj(root_obj, string.format( @@ -368,10 +379,11 @@ local function write_structure() if not se.hidden then local res = { '<<' } insert_formatted(res, '/Type/StructElem /S%s /P %d 0 R', pdf_name(se.struct), se.parent.objnum) - if se.id then id_tree[se.id] = se.objnum; insert_formatted(res, '/ID %s', pdf_string(se.id)) end - if se.lang and se.lang ~= se.parent.lang then insert_formatted(res, '/Lang (%s)', se.lang) end + if se.id then insert_formatted(res, '/ID %s', pdf_string(se.id)) end + if se.lang and se.lang ~= se.parent.lang then insert_formatted(res, '/Lang %s', pdf_string(se.lang)) end if se.alt then insert_formatted(res, '/Alt %s', pdf_string(se.alt)) end if se.actual then insert_formatted(res, '/ActualText %s', pdf_string(se.actual)) end + if se.title then insert_formatted(res, '/T %s', pdf_string(se.title)) end if #se.children > 0 then insert_formatted(res, '\n/K %s', format_K_array(se)) end if se.mainpage then insert_formatted(res, '/Pg %d 0 R', se.mainpage) end if se.class then @@ -382,6 +394,19 @@ local function write_structure() end if #se.class > 1 then table.insert(res, ']') end end + if se.ref then + table.insert(res, '/Ref') + table.insert(res, '[') + for _, c in ipairs(se.ref) do + local onum = id_tree[c] + if onum then + insert_formatted(res, '%d 0 R', onum) + else + alloc.err('Invalid structure element ID: %s', c) + end + end + table.insert(res, ']') + end if not is_empty(se.attributes) then table.insert(res, '/A') make_attributes(res, se.attributes) @@ -481,7 +506,7 @@ cb.register('uselanguage', function(name) end) local function write_language() - add_to_catalog('/Lang (%s)', structure[1].lang or 'und') + add_to_catalog('/Lang %s', pdf_string(structure[1].lang or 'und')) end -- 1 marking structure elements @@ -559,9 +584,11 @@ end alloc.luadef('tagging:startelement', function() local s = options_scanner() :string('id') + :string('ref', true) :string('type') -- 'section', 'group', 'block' etc. :argument('alt') :argument('actual') + :argument('title') :string('lang') :subtable('attr') :string('class', true) @@ -736,7 +763,7 @@ function M.mark_content_items(box) elseif marker and marker.what == 'mci_stop' then end_node = end_node and n; insert_tags(b) -- finally, see if we need to intervene between content nodes - elseif n.id == RULE_NODE or n.id == GLYPH_NODE + elseif n.id == RULE_NODE or n.id == GLYPH_NODE or n.id == DISC_NODE or marker and marker.what == 'content' then local _se, _order = n[current_struct], n[current_order] if n.id == RULE_NODE and (n.width == 0 or n.height == 0 and n.depth == 0) then @@ -768,7 +795,7 @@ cb.register('pre_shipout', function(nr) end end) --- 1 destinations +-- 1 links and destinations local dest_count = alloc.new_count('link dest count') local function new_dest_name() @@ -791,7 +818,41 @@ end alloc.luadef('newdestinationname', function() tex.sprint(new_dest_name()) end) alloc.luadef('lastdestinationname', function() tex.sprint(last_dest_name()) end) --- 1 bookmarks +-- provide the arguments to \pdfextension startlink +local link_types = { } +alloc.luadef('hyper:makelink', function() + local s = options_scanner() + :argument('alt'):argument('contents') -- same thing + :argument('attr') + :scan() + local type = token.scan_word() + local userf = link_types[type] + if not userf then + alloc.err('Unknown hyperlink type: %s', type) + userf = link_types.first + end + local user = userf() + attr = { 'attr {\\csname minim:linkattr\\endcsname' } + if s.attr then table.insert(attr, s.attr) end + if s.alt or s.contents then insert_formatted(attr, '/Contents %s', pdf_string(s.alt or s.contents)) end + insert_formatted(attr, '} user {%s}', user) + tex.sprint(table.concat(attr, ' ')) +end) + +function link_types.dest() + return string.format('/Subtype/Link /A <>', pdf_string(token.scan_argument())) +end +function link_types.url() + return string.format('/Subtype/Link /A <>', pdf_string(token.scan_argument())) +end + +function link_types.user() return token.scan_argument() end +function link_types.next() return '/Subtype/Link /A <>' end +function link_types.prev() return '/Subtype/Link /A <>' end +function link_types.first() return '/Subtype/Link /A <>' end +function link_types.last() return '/Subtype/Link /A <>' end + +-- 1 bookmarks (outlines) -- start with a dummy top-level bookmark local bookmarks = { { count = 0 } } @@ -811,8 +872,8 @@ local function write_bookmarks() -- write bookmark objects for i=2, #bookmarks do local bm, res = bookmarks[i], { } - insert_formatted(res, '<< /Title %s\n/Parent %s 0 R /Dest (%s)', pdf_string(bm.title), - bm.parent and bm.parent.outline_obj or bookmarks[1].outline_obj, bm.dest) + insert_formatted(res, '<< /Title %s\n/Parent %s 0 R /Dest %s', pdf_string(bm.title), + bm.parent and bm.parent.outline_obj or bookmarks[1].outline_obj, pdf_string(bm.dest)) if bm.next then insert_formatted(res, '/Next %s 0 R', bm.next.outline_obj) end if bm.prev then insert_formatted(res, '/Prev %s 0 R', bm.prev.outline_obj) end if bm.struct and bm.struct.objnum then @@ -945,13 +1006,13 @@ end, 'protected') function M.mark_discretionaries(head, gc) if not spaces_enabled() then return end - for disc in node.traverse_id(7, head) do + for disc in node.traverse_id(DISC_NODE, head) do if disc.subtype ~= 2 then -- ‘automatic’ (exclude explicit hyphens) local pre, post, replace = disc.pre, disc.post, disc.replace local se, order = disc[current_struct], disc[current_order] -- get the replacement text local actual = { } - for c in node.traverse_id(29, replace) do + for c in node.traverse_id(GLYPH_NODE, replace) do table.insert(actual, c.char) end -- special case: a single hyphen @@ -982,7 +1043,7 @@ local space_attr = alloc.new_attribute('space insertion marker') function M.mark_spaces(head, gc) if not spaces_enabled() then return end - for g in node.traverse_id(12, head) do -- glue + for g in node.traverse_id(GLUE_NODE, head) do if g.prev and g.subtype == 13 or g.subtype == 14 then -- (x)spaceskip local p = g.prev p[space_attr] = p.id == GLYPH_NODE and p.font @@ -1011,7 +1072,7 @@ end function M.insert_spaces(head, gc) if not spaces_enabled() then return end - for line in node.traverse_id(0, head) do + for line in node.traverse_id(HLIST_NODE, head) do insert_spaces(line.head) end return true @@ -1085,10 +1146,10 @@ local function write_output_intents() attr = string.format('/N %d', oi.N), immediate = true } insert_formatted(res, '<< /Type/OutputIntent /DestOutputProfile %d 0 R', p) - insert_formatted(res, '/S/%s /OutputConditionIdentifier (%s)', oi.subtype, oi.id) - if oi.info then insert_formatted(res, '/Info (%s)', oi.info) end - if oi.condition then insert_formatted(res, '/OutputCondition (%s)', oi.condition) end - if oi.registry then insert_formatted(res, '/RegistryName (%s)', oi.registry) end + insert_formatted(res, '/S/%s /OutputConditionIdentifier %s', oi.subtype, pdf_string(oi.id)) + if oi.info then insert_formatted(res, '/Info %s', pdf_string(oi.info)) end + if oi.condition then insert_formatted(res, '/OutputCondition %s', pdf_string(oi.condition)) end + if oi.registry then insert_formatted(res, '/RegistryName %s', pdf_string(oi.registry)) end table.insert(res, '>>') end table.insert(res, ']') @@ -1121,9 +1182,31 @@ end alloc.luadef('minim:default:rgb:profile', function() M.add_default_rgb_output_intent() end) alloc.luadef('minim:default:cmyk:profile', function() M.add_default_cmyk_output_intent() end) +-- 1 pdf/ua + +alloc.luadef('tagging:enablepdfua', function() + add_to_catalog('/ViewerPreferences << /DisplayDocTitle true >>') + local pageattr = pdf.getpageattributes() + if pageattr then + pdf.setpageattributes(pageattr .. ' /Tabs /S') + else + pdf.setpageattributes('/Tabs /S') + end +end) + +local function perform_pdfua_checks() + local xmp = require 'minim-xmp' + if xmp.get_metadata('dc:title') == '' then + alloc.err('PDF/UA error: no dc:title metadata') + end +end + -- -cb.register ('finish_pdffile', function() +cb.register('finish_pdffile', function() + if tex.count['pdfuaconformancelevel'] > 0 then + perform_pdfua_checks() + end if tagging_enabled() then write_language() write_structure() diff --git a/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.tex b/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.tex index d8b06255bab..8a5f738063f 100644 --- a/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.tex +++ b/Master/texmf-dist/tex/luatex/minim-pdf/minim-pdf.tex @@ -167,8 +167,8 @@ \newcount\tagging:tablecolspan \tagging:tablecolspan 1 \newcount\tagging:tablerowspan \tagging:tablerowspan 1 \newcount\tagging:i -\def\tagging:TH{TH} -\def\tagging:TD{TD} +\def\tagging:TH{{TH}\markcolumnhead} +\def\tagging:TD{{TD}} % initialising variables \def\marktable{\startelement{Table}% \global\advance\tagging:tablec1 @@ -205,7 +205,7 @@ \def\marktablecell{% \global\advance\tagging:tablecol1 \continueelementfrom\tagging:row - \startelement{\tagging:cell}% + \expandafter\startelement\tagging:cell% \tagging:assignheaders} % spans @@ -301,13 +301,27 @@ \def\marktocentry#1#2#3#4#5{% \ifx\marktocentry#1\marktocentry \def\marktocentry:link##1{##1}\else - \def\marktocentry:link##1{\hyperlink dest{#1}##1\endlink}\fi + \def\marktocentry:link##1{\hyperlink + alt{\csname minim:str:page\endcsname#5} + dest{#1}##1\endlink}\fi \markelement{TOCI}{\nextpartag{}\quitvmode \ifx\marktocentry#2\marktocentry\else \markelement{Lbl}{\marktocentry:link{#2}}\fi \markelement{Reference}{\marktocentry:link{#3% \ifx\marktocentry#4\marktocentry\else \markartifact{Layout}{#4}\fi#5}}}} +\def\minim:str:page{Page } + +% \marknoteref {*} +\def\marknoteref#1{% + \startelement ref{\newdestinationname}{Reference}% + {\stopformulatagging#1}\stopelement{Reference}} +% \marknotelbl {*} +\def\marknotelbl#1{% + \startelement id{\lastdestinationname}{Note}% + \aftergroup\tagging:ensurestopNote + \markelement{Lbl}{{\stopformulatagging#1}}} +\def\tagging:ensurestopNote{\ensurestopelement{Note}} % 1 tagging formulas @@ -351,7 +365,7 @@ \def\formulasource:set#1{% \begingroup % strip \setalttext, \setactualtext - \def\setalttext#1{}\def\setactualtext#1{}% + \def\setalttext##1{}\def\setactualtext##1{}% \xdef\formulasource:thesource{#1}% \endgroup \expandafter\splitcommalist @@ -370,6 +384,7 @@ relation Source desc {Equation source} name {\formulafilename.tex} string {\tagging:formulasource{#1}}\fi} +\def\formulasource:none#1{} \newtoks\includeformulasources \includeformulasources{actualtext,attachment} @@ -390,17 +405,18 @@ \def\linkdest:h#1{\vadjust pre{\linkdest:v{#1}}} \def\linkdest:v#1{\pdfextension dest name {#1} xyz\nobreak} -% \hyperlink dest {name} ... \endlink -% \hyperlink url {url} ... \endlink +% \hyperlink ... \endlink +% args: alt {...} attr {...} +% type: dest {name} | url {url} | next | prev | first | last \protected\def\endlink{\pdfextension endlink\stopelement{Link}\relax} \protected\def\startlink{\startelement{Link}\pdfextension startlink} -\def\hyperlink#1#{\quitvmode\hyperlink:rmspace#1 \hyperlink:rmspace} -\def\hyperlink:rmspace#1 #2\hyperlink:rmspace{% - \startlink attr {\minim:linkattr}% - \csname hyperlink:#1\endcsname} -\def\hyperlink:dest#1{user {/Subtype/Link /F 4 /A <>}} -\def\hyperlink:url#1{user {/Subtype/Link /F 4 /A <>}} -\def\minim:linkattr{/Border [0 0 0]} +\protected\def\hyperlink{\quitvmode\startlink\hyper:makelink} + +% \hyperlinkstyle { ... } +\def\minim:linkattr{\the\hyperlinkstyle\the\minim:pdfalinkattr} +\newtoks\hyperlinkstyle +\newtoks\minim:pdfalinkattr +\hyperlinkstyle{/Border [0 0 0]} % 1 languages @@ -449,9 +465,12 @@ \newcount \pdfaconformancelevel \pdfaconformancelevel = 0 +\newcount \pdfuaconformancelevel +\pdfuaconformancelevel = 0 % \pdfalevel 2b \def\pdfalevel#1#2{% + \minim:pdfalinkattr{ /F 4}% hyperlinks must be printed \global\pdfaconformancelevel=#1\relax \ifcsname minim:pdfa:#1#2\endcsname \lastnamedcs\else \errmessage{Unknown pdf/a standard pdf/a-#1}\fi} @@ -474,6 +493,12 @@ \expandafter\def\csname minim:pdfa:3b\endcsname{\minim:pdfasettings 7B3} \expandafter\def\csname minim:pdfa:3u\endcsname{\minim:pdfasettings 7U3} +% \pdfualavel 1 +\def\pdfualevel{\input minim-xmp + \setmetadata pdfuaid:part {1} + \tagging:enablepdfua + \global\pdfuaconformancelevel= } + %  \catcode`\: = \minimpdfloaded diff --git a/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua index d6867a4c9bf..e3b7a8de70c 100644 --- a/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua +++ b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua @@ -158,6 +158,12 @@ M.add_namespace('PDF/A Identification', 'pdfaid', 'http://www.aiim.org/pdfa/ns/i part = { 'Integer', 'Version identifier', true }, }, nil, true) +M.add_namespace('PDF/UA Identification', 'pdfuaid', 'http://www.aiim.org/pdfua/ns/id/', { + amd = { 'Text', 'Amendment identifier', true }, + corr = { 'Text', 'Corrigendum identifier', true }, + part = { 'Integer', 'Version identifier', true }, +}) + M.add_namespace('Dublin Core', 'dc', 'http://purl.org/dc/elements/1.1/', { contributor = { 'Bag ProperName', 'Other contributors' }, coverage = { 'Text', 'Extent or scope' }, diff --git a/Master/texmf-dist/tex/luatex/minim/minim-doc.sty b/Master/texmf-dist/tex/luatex/minim/minim-doc.sty index 97b173beac0..93d807f16a3 100644 --- a/Master/texmf-dist/tex/luatex/minim/minim-doc.sty +++ b/Master/texmf-dist/tex/luatex/minim/minim-doc.sty @@ -9,6 +9,7 @@ %\decompressedpdf \pdfalevel 3a +\pdfualevel 1 \overfullrule = 0pt % 1 page layout @@ -124,8 +125,22 @@ endfig;} % page artifacts \edef\tmp{\markartifact{Pagination /Subtype/Footer}{\the\footline}} \footline\expandafter{\tmp} -\def\footnoterule{\markartifact{Layout}{\kern-3\p@ - \hrule width 2truein \kern 2.6\p@}} % the \hrule is .4pt high + +% footnotes +\edef\footnoterule{\markartifact{Layout}\footnoterule} +\catcode`\@=11 +\def\footnote#1{\let\@sf\empty % parameter #2 (the text) is read later + \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\/\fi + \marknoteref{#1}\@sf\vfootnote{#1}} +\def\vfootnote#1{% + \insert\footins\bgroup + \interlinepenalty\interfootnotelinepenalty + \splittopskip\ht\strutbox % top baseline for broken footnotes + \splitmaxdepth\dp\strutbox \floatingpenalty\@MM + \leftskip\z@skip \rightskip\z@skip \spaceskip\z@skip \xspaceskip\z@skip + \nextpartag{}\marknotelbl{#1}\enspace\startelement{P}% + \footstrut\futurelet\next\fo@t} +\catcode`\@=12 % \startlist \item x. ... \stoplist \def\listskip{\vskip 3pt plus 2pt\vskip-\parskip} @@ -141,22 +156,26 @@ endfig;} \protected\def\chapter#1 \par{% \vfil\break \ensurestopelement{Section}% - \startelement{Chapter}% + \global\advance\chapterno1 \global\sectionno0 + \startelement title{Chapter \the\chapterno}{Chapter}% \outline open {#1}% \addtotoc{\chapter{#1}{\lastdestinationname}}% \nextpartag{H}\quitvmode \red{\Title#1\hfill\copy\notehead}\bigskip\nobreak} +\newcount \chapterno % \section Title \par \addstructuretype Sect Section \protected\def\section#1 \par{% \bigskip\penalty-50\relax - \startelement{Section}% + \global\advance\sectionno1 + \startelement title{Section \the\chapterno.\the\sectionno}{Section}% \outline closed {#1}% \addtotoc{\section{#1}{\lastdestinationname}}% \nextpartag{H}\quitvmode \red{\title#1}% \par\nobreak} +\newcount \sectionno % table of contents \newtoks\toc \newif\iftoc \toctrue @@ -186,7 +205,9 @@ endfig;} \tocfalse \chapter \getmetadata title \hfill \tenrm version \getmetadata version \par\endgroup - \marktableaslist \halign {\strut + \marktableaslist + \tagattribute List ListNumbering /None + \halign {\strut \qquad##\quad&##\hfil\cr author&\getmetadata author\cr contact&{\def\tmp{@}\def\TMP{.}% @@ -207,8 +228,9 @@ Licence (EUPL) version 1.2 or later. An english version of this licence has been included as an attachment to this file; copies in other languages can be obtained at \stopformulatagging$$\hbox -{\hyperlink url {https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12}% -https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\endlink}$$\startformulatagging} +{\hyperlink alt{Link to the website of the EUPL.} + url {https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12}% + https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\endlink}$$\startformulatagging} % for identifying which file we are typesetting \edef\thejobname{\expandafter\scantextokens\expandafter{\jobname}} diff --git a/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.lua b/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.lua index e6a06e783e1..468da03b485 100644 --- a/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.lua +++ b/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.lua @@ -50,7 +50,7 @@ function M.use_resource(kind, name) return res._entry_ end --- global resources are mainly for pgf compatibility: contains adds entries to +-- global resources are mainly for pgf compatibility: it contains entries to -- the resource dictionaries that will be added for every page. -- local global_resources = init_resources() -- name ↦ '/Key ' @@ -87,6 +87,10 @@ function M.use_resource_node(kind, name) return n end +alloc.luadef('withpdfresource', function() + node.write(M.use_resource_node(token.scan_string(), token.scan_string())) +end, 'protected') + -- construction and caching of resource dictionaries. -- local previous_dicts = init_resources() -- pdf dict ↦ objnum diff --git a/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.tex b/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.tex index 6b8dc29577f..57e9d8f012f 100644 --- a/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.tex +++ b/Master/texmf-dist/tex/luatex/minim/minim-pdfresources.tex @@ -13,6 +13,7 @@ % 1 pgf compatibility % this ballet inserts our fix directly at the end of pgfsys-luatex.def +% (see the \ProvidesFile redefinition in minim-alloc.tex) \expandafter\def\csname minim:intercept:pgfsys-luatex.def\endcsname {\wlog{minim: applying pgf patches...}\newtoks\minim:pgf:fix:toks \minim:pgf:fix:toks\csname pgfutil@everybye\endcsname -- cgit v1.2.3