summaryrefslogtreecommitdiff
path: root/macros/luatex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-01-09 03:01:30 +0000
committerNorbert Preining <norbert@preining.info>2024-01-09 03:01:30 +0000
commit140e2f7f1509acd423172e2895d0d7839e5b88c6 (patch)
treeb95babe2ddbb2ed3cb8f1aee35286b5218e5e4d1 /macros/luatex
parentafbba3b1d12f17572ca4745766f2e0d55a6f58f3 (diff)
CTAN sync 202401090301
Diffstat (limited to 'macros/luatex')
-rw-r--r--macros/luatex/generic/minim-math/README6
-rw-r--r--macros/luatex/generic/minim-math/minim-math.doc8
-rw-r--r--macros/luatex/generic/minim-math/minim-math.pdfbin112093 -> 114067 bytes
-rw-r--r--macros/luatex/generic/minim-math/minim-math.tex2
-rw-r--r--macros/luatex/generic/minim-mp/README11
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.doc72
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.lua340
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.mp216
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.pdfbin109855 -> 120017 bytes
-rw-r--r--macros/luatex/generic/minim-mp/minim-mp.sty19
-rw-r--r--macros/luatex/generic/minim-mp/minim.mp4
-rw-r--r--macros/luatex/generic/minim-pdf/README16
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.doc99
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.lua135
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.pdfbin116670 -> 126824 bytes
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.tex53
-rw-r--r--macros/luatex/generic/minim-xmp/README8
-rw-r--r--macros/luatex/generic/minim-xmp/minim-xmp.doc7
-rw-r--r--macros/luatex/generic/minim-xmp/minim-xmp.lua6
-rw-r--r--macros/luatex/generic/minim-xmp/minim-xmp.pdfbin72075 -> 73080 bytes
-rw-r--r--macros/luatex/generic/minim/README7
-rw-r--r--macros/luatex/generic/minim/minim-alloc.doc42
-rw-r--r--macros/luatex/generic/minim/minim-doc.sty36
-rw-r--r--macros/luatex/generic/minim/minim-pdfresources.lua6
-rw-r--r--macros/luatex/generic/minim/minim-pdfresources.tex1
-rw-r--r--macros/luatex/generic/minim/minim.doc8
-rw-r--r--macros/luatex/generic/minim/minim.pdfbin338726 -> 365466 bytes
27 files changed, 895 insertions, 207 deletions
diff --git a/macros/luatex/generic/minim-math/README b/macros/luatex/generic/minim-math/README
index bb7786d6cb..24fe3397f1 100644
--- a/macros/luatex/generic/minim-math/README
+++ b/macros/luatex/generic/minim-math/README
@@ -1,4 +1,4 @@
-Version: 2023/1.3
+Version: 2024/1.4
SUMMARY
@@ -18,6 +18,10 @@ In order to obtain the typeset manual of this package, simply say:
HISTORY
+2024/1.4 (5/1/2024)
+
+ * Add \floor and \ceil macros
+
2023/1.3 (20/10/2023)
No notable changes.
diff --git a/macros/luatex/generic/minim-math/minim-math.doc b/macros/luatex/generic/minim-math/minim-math.doc
index 2a44dc72f4..3648458b57 100644
--- a/macros/luatex/generic/minim-math/minim-math.doc
+++ b/macros/luatex/generic/minim-math/minim-math.doc
@@ -8,8 +8,8 @@
\else \startmetadata
author {Esger Renkema}
title {minim-math}
- date {2023-10-20}
- version {2023/1.3}
+ date {2024-01-05}
+ version {2024/1.4}
keywords {LuaTeX; Plain TeX; Unicode mathematics}
stopmetadata
\maketitle \fi
@@ -64,6 +64,7 @@ For nonmathematical text, you should use ⟦*\text⟧ instead of ⟦\mup⟧.
The default properties of characters can be set with one of the following three
commands:
\startlist
+\tagattribute List ListNumbering /Unordered
\item\ignore. ⟦*\mathmap {character list} {style}⟧
\item\ignore. ⟦*\mathclass {character list} {class}⟧
\item\ignore. ⟦*\mathfam {character list} nr⟧
@@ -72,6 +73,7 @@ There, ⟦style⟧ is one of the above and ⟦class⟧ is the name of a class as
Finally, the ⟦character list⟧ should be a comma-separated list with elements of
one of the following forms:
\startlist
+\tagattribute List ListNumbering /Decimal
{\def\⟦#1{⟦[\uppercase{#1}#1]\kern-1pt}
\item1. a list of characters, like ⟦abc⟧ or ⟦\partial⟧ or $ℝ$;
\item2. a character range, like ⟦`A-`Z⟧, ⟦65-90⟧ or ⟦"41-"5A⟧;
@@ -188,6 +190,7 @@ Shorthand & Result \cr
⟦\bra x, \ket y⟧ & $\bra x, \ket y$ \cr
⟦\braket x y⟧ & $\braket x y$ \cr
⟦\norm x, \Norm x⟧ & $\norm x, \Norm x$ \cr
+⟦\floor x, \ceil x⟧ & $\floor x, \ceil x$ \cr
⟦x \stackrel ?= y⟧ & $x \stackrel ?= y$ \cr
⟦x \stackbin a+ y⟧ & $x \stackbin a+ y$ \cr
⟦f\inv⟧ & $f\inv$ \quad (cf. $f^{-1}$) \cr
@@ -233,6 +236,7 @@ contents of this package; I have included them because I find them hard to
remember.
\startlist
+\tagattribute List ListNumbering /Decimal
\item1. ⟦*\eqalign⟧ gives a vertically centered box and can occur many times in
an equation, while ⟦*\eqalignno⟧ and ⟦*\leqalignno⟧ span whole lines (put the
equation numbers in the third column). All assume the relation (or operator)
diff --git a/macros/luatex/generic/minim-math/minim-math.pdf b/macros/luatex/generic/minim-math/minim-math.pdf
index 30e2129fcf..9544068d92 100644
--- a/macros/luatex/generic/minim-math/minim-math.pdf
+++ b/macros/luatex/generic/minim-math/minim-math.pdf
Binary files differ
diff --git a/macros/luatex/generic/minim-math/minim-math.tex b/macros/luatex/generic/minim-math/minim-math.tex
index 1a8f1157c1..7029952418 100644
--- a/macros/luatex/generic/minim-math/minim-math.tex
+++ b/macros/luatex/generic/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/macros/luatex/generic/minim-mp/README b/macros/luatex/generic/minim-mp/README
index d712a7f11c..6c10eeabb2 100644
--- a/macros/luatex/generic/minim-mp/README
+++ b/macros/luatex/generic/minim-mp/README
@@ -1,4 +1,4 @@
-Version: 2023/1.3
+Version: 2024/1.4
SUMMARY
@@ -19,6 +19,12 @@ In order to obtain the typeset manual of this package, simply say:
HISTORY
+2024/1.4 (5/1/2024)
+
+ * Support the extended graphics state (extgstate)
+ * Allow box resource (xform) creation directly from metapost
+ * Support transparency and transparency groups
+
2023/1.3 (20/10/2023)
* Various runscript improvements:
@@ -30,13 +36,11 @@ HISTORY
* Improve font handling in minim-lamp.ini, and provide \begin{document}.
* Add a minim-mp.sty package file for LaTeX users.
-
2023/1.2 (3/3/2023)
* Add a few more metapost macros.
* Various small bug fixes.
-
2022/1.1 (3/3/2022)
New features:
@@ -57,7 +61,6 @@ HISTORY
into raw pdf code.
* No longer write out superfluous line widths.
-
2021/1.0 (1/6/2021)
This was the original release.
diff --git a/macros/luatex/generic/minim-mp/minim-mp.doc b/macros/luatex/generic/minim-mp/minim-mp.doc
index 2f7541ba75..9429544d8e 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 {2023-10-20}
- version {2023/1.3}
+ date {2024-01-05}
+ version {2024/1.4}
keywords {LuaTeX; Plain TeX; MetaPost; mplib}
stopmetadata
\maketitle \fi
@@ -147,7 +147,7 @@ endfig;⟧
\vskip-2\parskip plus 2\parskip
\quitvmode\hfil
\startelement alt {The text ‘Running TeX from within MetaPost’ set along a half-circle.} {Figure}%
-\stoptagging\directmetapost{%
+\startsinglecontentitem\directmetapost{%
beginfig(1)
save t, w, r, a; picture t;
t = "Running TeX from within MetaPost" infont "tenrm";
@@ -158,7 +158,7 @@ beginfig(1)
draw c rotatedaround((x,0), a)
shifted (-r*sind(a)-x, r*cosd(a));
endfor
-endfig;}\starttagging \stopelement{Figure}
+endfig;}\stopsinglecontentitem \stopelement{Figure}
By default, the ⟦maketext⟧ operator is used for typesetting labels. You can,
however, order de ⟦label⟧ macro to use ⟦infont⟧ instead by setting
@@ -199,9 +199,9 @@ 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 }
+\markelement{Span}{\setactualtext{example }\startsinglecontentitem\space
\directmetapost{beginfig(1) multidraw (contours "example" of "tenbf")
-withpen currentpen scaled 1/10; baseline 0; endfig;}}
+withpen currentpen scaled 1/10; baseline 0; endfig;}\stopsinglecontentitem}
in a thin outline.
Finally, two handy clipping macros have been added:
@@ -233,9 +233,9 @@ If your lua snippet returns nothing, the ⟦runscript⟧ call will be invisible
metapost. If on the other hand it does return a value, that value will have to
be translated to metapost. Numbers and strings will be returned as they are
(so make sure the string is surrounded by quotes if you want to return
-a metapost string). You can return a point or colour by returning an array of
-two to four elements. For other return values, ⟦tostring()⟧ will be called.
-
+a metapost string). You can return a point, colour or transform by returning an
+array of two to six elements (excluding five). For other return values,
+⟦tostring()⟧ will be called.
\section Passing values to lua
@@ -275,7 +275,7 @@ and have ⟦texvariable jobname⟧ work as expected.
For accessing count, dimen, attribute or toks registers, the macros are
⟦*tex.count⟧ ⟦[number]⟧ or ⟦*tex.count⟧⟦.name⟧ [etc. etc.] for getting and
-⟦*set tex.count⟧ ⟦[number] = value⟧ or ⟦*tex.count⟧⟦.name = value⟧ etc. for
+⟦*set tex.count⟧ ⟦[number] = value⟧ or ⟦*set tex.count⟧⟦.name = value⟧ etc. for
setting values.
@@ -305,7 +305,7 @@ information on these parameters.
You can use text inside patterns, as in this example:
\hfill \vbox to 0pt{
\startelement alt{A circle filled with red As in a rectangular pattern.}{Figure}
-\directmetapost{
+\startsinglecontentitem \directmetapost{
% define the pattern
picture letter; letter = maketext("a");
beginpattern(a)
@@ -316,7 +316,8 @@ endpattern(12pt,12pt);
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
+endfig;}\stopsinglecontentitem
+\stopelement{Figure}\vss}\hskip10pt\strut
\par\nobreak
⟦% define the pattern
picture letter; letter = maketext("a");
@@ -333,6 +334,41 @@ endfig;⟧
A small pattern library is available in the ⟦*minim-hatching.mp⟧ file; see the
accompanying documentation sheet for an overview of patterns.
+\section Advanced PDF graphics % 1
+
+You can use ⟦*savegstate⟧ and ⟦*restoregstate⟧ for inserting the ⟦q⟧ and ⟦Q⟧
+operators; these must always be paired, or horrible errors will occur. You may
+need them if you use ⟦*setgstate(<params>)⟧ for modifying the extended
+graphical state (ExtGState). The ⟦params⟧ must be a comma-separated
+⟦Key=value⟧ list, with all ⟦value⟧s being suffixes. The latter restriction may
+require the use of additional variables, but as this is a very low-level
+command, it is best to wrap it in a more specialised macro anyway.
+The ⟦*withgstate (<params>)⟧ can be added to a drawing statement and includes
+saving/restoring the graphical state.
+
+Note that while you could try and use ⟦setgstate⟧ for modifying variables like
+the line cap or dash pattern, the result of doing so will be unpredictable,
+since such changes will be invisible to metapost. Its intended use is
+restricted to graphics parameters outside the scope of metapost.
+
+For applying transparency, ⟦*setalpha(a)⟧ updates the ⟦CA⟧ and ⟦ca⟧ parameters
+as a stand-alone command and ⟦*withalpha(a)⟧ can be used in a drawing statement
+where it will save/restore the graphical state around it. For applying
+transparency to an ensemble of drawing statements, ⟦*transparent (a) <picture>⟧
+will create and insert the proper transparency group. The transparency group
+attributes can be set with the string internal ⟦transparency_group_attrs⟧,
+while for all three macros the blend mode can be set with the string internal
+⟦blend_mode⟧ (it will be added whenever set).
+
+A transparency group is a special kind of XForm, and these can be created from
+withing metapost: ⟦<id> =⟧ ⟦*saveboxresource (<attributes>) <picture>⟧ returns
+a number identifying the resource and can be fed attributes in the same way as
+⟦setgstate⟧. XForms defined through metapost are available to other metapost
+instances, but not to tex, though the macro painting them, ⟦*boxresource <id>⟧,
+also accepts identifiers of tex-defined box resources. There remains a subtle
+difference, however: metapost-defined box resources are placed at their
+original origin.
+
\section Other metapost extensions % 1
@@ -342,7 +378,9 @@ 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.
-You can write to tex’s log directly with ⟦*texmessage "hello"⟧.
+You can write to tex’s log directly with ⟦*texmessage "hello";⟧. You can feed
+it a comma-separated list of strings and numbers, which will be passed through
+⟦string.format()⟧ first.
You can write direct pdf statements with ⟦*special "pdf: statements"⟧ and you
can add comments to the pdf file with ⟦*special "pdfcomment: comments"⟧.
@@ -381,7 +419,9 @@ Say ⟦*nr_remaining = M.left(nr)⟧ for the number of remaining images.
Each metapost instance is a table containing the following entries:
\smallskip\smallskip
-\marktableaslist \halign{\qquad#\quad\hfil&#\hfil\cr
+\marktableaslist
+\tagattribute List ListNumbering /Description
+\halign{\qquad#\quad\hfil&#\hfil\cr
⟦jobname⟧&The jobname.\cr
⟦instance⟧&The primitive metapost instance.\cr
⟦results⟧&A linked list of unretrieved images.\cr
@@ -424,7 +464,9 @@ processing.
Specials can store information in the ⟦user⟧ table of the picture that is being
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.
+properly-dimensioned boxes. If a ⟦user.save_fn⟧ function is defined, it will
+replace the normal saving of the image to the image list and the image node
+list will be flushed.
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
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
diff --git a/macros/luatex/generic/minim-mp/minim-mp.mp b/macros/luatex/generic/minim-mp/minim-mp.mp
index 654449da03..2fd2db1074 100644
--- a/macros/luatex/generic/minim-mp/minim-mp.mp
+++ b/macros/luatex/generic/minim-mp/minim-mp.mp
@@ -2,7 +2,7 @@
delimiters ();
% This file is specific to the minim metapost processor (minim-mp) and cannot
-% be used with luaotfload or other metapost implementations. See minim.mp for
+% be used with luamplib or other metapost implementations. See minim.mp for
% general-purpose extensions.
message "Setting up the minim MetaPost extensions";
@@ -10,13 +10,14 @@ message "Setting up the minim MetaPost extensions";
def save_picture text t = save t; picture t; enddef;
def save_boolean text t = save t; boolean t; enddef;
def save_string text t = save t; string t; enddef;
+def save_path text t = save t; path t; enddef;
% 1 lua scripts, functions and variables
def luafunction = gobble inner_luafunction enddef;
vardef inner_luafunction @# (text args) = endgroup
% we remove the grouping so that the function may return any fragment of
- % lua code, in particular lists to-be-parsed as text parameters
+ % metapost code, in particular lists to-be-parsed as text parameters
runscript ("return " & (str @#) & build_lua_arguments(args))
gobble begingroup enddef;
@@ -38,7 +39,7 @@ vardef setluavariable @# expr val=
vardef lua_string(expr s) =
runscript ("[[quote_for_lua]]"&s) enddef;
-vardef hexadecimal expr n =
+vardef hexadecimal tertiary n =
% TODO: support other number systems
save d, a, m; a = ASCII("a"); m := abs n;
save_string res; res =
@@ -49,7 +50,7 @@ vardef hexadecimal expr n =
if i = 3: & "." fi
endfor; res enddef;
-vardef scaledpoints expr n =
+vardef scaledpoints tertiary n =
% use the fact that 800bp = 803pt
save a, m; m := abs n;
a1 = m div (1025/1024); % 1025/1024 = 800epsilon * 82
@@ -64,32 +65,36 @@ vardef scaledpoints expr n =
& decimal(a3) & ")";
res enddef;
-vardef quote_for_lua expr a =
+vardef quote_for_lua tertiary a =
save_string res; res =
if string a:
lua_string(a)
elseif numeric a:
hexadecimal(a)
elseif pair a:
- "{"&hexadecimal(xpart a) &
- ","&hexadecimal(ypart a) & "}"
+ "{"& (hexadecimal xpart a) &
+ ","& (hexadecimal ypart a) & "}"
elseif boolean a:
if a: "true" else: "false" fi
elseif color a:
- "{"&hexadecimal(redpart a) &
- ","&hexadecimal(greenpart a) &
- ","&hexadecimal(bluepart a) & "}"
+ "{"& (hexadecimal redpart a) &
+ ","& (hexadecimal greenpart a) &
+ ","& (hexadecimal bluepart a) & "}"
elseif cmykcolor a:
- "{"&hexadecimal(cyanpart a) &
- ","&hexadecimal(magentapart a) &
- ","&hexadecimal(yellowpart a) &
- ","&hexadecimal(blackpart a) & "}"
+ "{"& (hexadecimal cyanpart a) &
+ ","& (hexadecimal magentapart a) &
+ ","& (hexadecimal yellowpart a) &
+ ","& (hexadecimal blackpart a) & "}"
elseif pen a:
hide(errmessage("I cannot pass a pen value to lua");)
"nil"
elseif transform a:
- hide(errmessage("I cannot pass a transform value to lua");)
- "nil"
+ "{"& (hexadecimal xxpart a) &
+ ","& (hexadecimal xypart a) &
+ ","& (hexadecimal yxpart a) &
+ ","& (hexadecimal yypart a) &
+ ","& (hexadecimal xpart a) &
+ ","& (hexadecimal ypart a) & "}"
elseif picture a:
hide(errmessage("I cannot pass a picture value to lua");)
"nil"
@@ -97,14 +102,33 @@ vardef quote_for_lua expr a =
"nil"
fi; res enddef;
+% constructing lua tables
+
+def make_lua_dict (text t) = begingroup
+ % Note that though the argument may contain = signs and commas, the rest of
+ % the arguments must consist of suffixes.
+ save_boolean first; first := true;
+ "{ " forsuffixes kv =
+ hide(let = _EQ_ undefined)
+ t hide(let = _EQ_ _EQ_) : &
+ if first: hide(first := false;) else: ", "& fi
+ begingroup make_lua_keyval kv endgroup
+ endfor & " }" endgroup
+enddef;
+let _EQ_ = =;
+
+vardef make_lua_keyval @# expr e =
+ (str @#) & " = " & quote_for_lua e
+enddef;
+
% tex registers
string _SUFFIX_HACK_[];
vardef index_or_suffix (suffix s) =
save_string res; res =
- if string _SUFFIX_HACK_ s: % this is a number
+ if string _SUFFIX_HACK_ s: % s is a number
"["&decimal(s)&"]"
- else: % this is a suffix
+ else: % s is a suffix
"."&(str s)
fi ; res enddef;
@@ -128,20 +152,33 @@ vardef set tex.toks @# expr val =
% 1 typesetting
+boolean debug_tex_bboxes;
+debug_tex_bboxes := false;
+
let textext = maketext;
let TEX = maketext;
+def _set_maketext_result_ (expr nr)(text tr) = image (
+ fill unitsquare withprescript "TEXBOX:"&decimal nr;
+ save_path bb; bb := unitsquare transformed tr;
+ if debug_tex_bboxes: draw bb dashed evenly; fi
+ setbounds currentpicture to bb;) enddef;
+
% baseline pair or numeric -> fill statement
vardef baseline expr o =
fill if numeric o : (0,o) else: o fi
-- cycle withprescript "BASELINE:"; enddef;
-% boxresource number -> fill statement + setbounds
-vardef boxresource expr nr = image(
- fill unitsquare withprescript "BOXRESOURCE:" & decimal nr ;
- setbounds currentpicture to unitsquare transformed runscript
- ("return { 'box_size', tex.getboxresourcedimensions(" & decimal nr & ") }");
- ) enddef;
+vardef find_baseline expr p =
+ save_pair bl, ibl;
+ for c within p:
+ if clipped c or bounded c:
+ ibl := find_baseline c;
+ if known ibl: bl := ibl; fi
+ elseif "BASELINE:" = substring (0,9) of prescriptpart c:
+ bl := point 0 of pathpart c;
+ fi
+ endfor bl enddef;
% actual typesetting
@@ -168,6 +205,129 @@ vardef thelabel@#(expr s,z) = % Position s near z
+ (1-labxf@#-labyf@#)*llcorner p
) ) enddef;
+% 1 box resources (xforms)
+
+% boxresource number -> fill statement + setbounds
+vardef boxresource primary nr = image(
+ fill unitsquare withprescript "BOXRESOURCE:" & decimal nr ;
+ save btf; transform btf;
+ btf = luafunction get_boxresource_dimensions (nr);
+ save_path bb; bb = unitsquare transformed btf;
+ if debug_tex_bboxes: draw bb dashed evenly; fi
+ setbounds currentpicture to bb;
+ currentpicture := currentpicture
+ shifted luafunction get_boxresource_center (nr);
+ ) enddef;
+
+% id = saveboxresource (attributes) image ( ... );
+vardef saveboxresource (text attrs) expr p =
+ save id; id = luafunction reserve_xform_id();
+ % save the dimensions
+ save t, o; transform t; pair o;
+ (0,0) transformed t = llcorner p;
+ (1,0) transformed t = lrcorner p;
+ (0,1) transformed t = ulcorner p;
+ save bl; bl = -ypart find_baseline p;
+ o = (-xpart llcorner p, if known bl: bl else: 0 fi);
+ luafunction set_boxresource_dimensions (id, t shifted o, -o);
+ % shipout the xform
+ shipout image(draw p;
+ special "XFORMATTRS:" & make_lua_dict (attrs);
+ special "XFORMINDEX:" & decimal id;
+ ); id enddef;
+
+% 1 extended graphics state
+
+vardef modify_scripts text t =
+ % Note: this is a no-op when the picture is empty
+ save_picture curr;
+ save_string pre, post;
+ % extract script parts
+ curr := image(drawdot origin t;);
+ pre = prescriptpart curr;
+ post = postscriptpart curr;
+ % add the prescript
+ if pre <> "":
+ curr := currentpicture; clearit;
+ draw curr withprescript pre;
+ fi
+ % add the postscript
+ if post <> "":
+ % here we find the reason for this routine: metapost *should* apply the
+ % postscript to the last element of a picture, but chooses the first
+ % instead.
+ curr := currentpicture; clearit;
+ setbounds curr to llcorner curr -- lrcorner curr --
+ urcorner curr -- ulcorner curr -- cycle;
+ % these bounds force an extra nesting level of the next function
+ draw with_postscript_to_last(curr, post);
+ fi
+enddef;
+
+vardef with_postscript_to_last (expr p, script) =
+ image( save i; i := 0;
+ for c within p:
+ if incr i < length(p): draw c;
+ elseif clipped c:
+ save_picture q; q:= with_postscript_to_last(c, script);
+ clip q to pathpart c;
+ addto currentpicture also q;
+ elseif bounded c:
+ save_picture q; q:= with_postscript_to_last(c, script);
+ setbounds q to pathpart c;
+ addto currentpicture also q;
+ else:
+ draw c withpostscript script;
+ fi
+ endfor) enddef;
+
+def append_postscript expr s =
+ if length(currentpicture) = 0: special s;
+ else: modify_scripts withpostscript s;
+ fi enddef;
+
+def savegstate =
+ append_postscript "gstate:save"; enddef;
+def restoregstate =
+ append_postscript "gstate:restore"; enddef;
+def setgstate =
+ append_postscript "extgstate:" & make_lua_dict enddef;
+
+def withgstate (text t) =
+ withprescript "extgstate:" & make_lua_dict (t)
+ withprescript "gstate:save"
+ withpostscript "gstate:restore" enddef;
+
+% 1 transparency
+
+newinternal string blend_mode;
+newinternal string transparency_group_attrs;
+
+% setalpha (α)
+vardef setalpha expr alpha =
+ save a; a = alpha;
+ save_string bm; bm = "/" & blend_mode;
+ setgstate(CA=a, ca=a if bm <> "/":, BM=bm fi) enddef;
+
+% <drawing_cmd> withalpha (α)
+def withalpha = gobble _withalpha_ enddef;
+string _blend_mode_;
+vardef _withalpha_ (expr alpha) =
+ _with_alpha_ := alpha;
+ _blend_mode_ := "/" & blend_mode;
+ endgroup
+ withgstate(CA=_with_alpha_,ca=_with_alpha_ if _blend_mode_ <> "/":, BM=_blend_mode_ fi)
+ gobble begingroup enddef;
+
+% transparent (α) image (...)
+vardef transparent (expr alpha) expr p =
+ save_string attrs; attrs = "<< /S/Transparency "&transparency_group_attrs&">>";
+ save g; g = saveboxresource (Group=attrs) p;
+ savegstate; setalpha (alpha);
+ draw boxresource g;
+ restoregstate;
+enddef;
+
% 1 even-odd rule and multidraw
def nofill expr c = fill c withprescript "OTYPE:nofill" enddef;
@@ -205,6 +365,9 @@ def beginpattern(suffix s) =
def withcolor = hide(painttype:=1) _withcolor enddef;
save matrix; transform matrix; enddef;
def endpattern (expr xstep, ystep) =
+ modify_scripts
+ withprescript "pdf:/Artifact BMC"
+ withpostscript "pdf:EMC";
if unknown matrix : matrix:=identity; fi
special "definepattern:" for e = charcode, tilingtype, painttype,
xstep, ystep, xxpart matrix, xypart matrix, yxpart matrix, yypart matrix:
@@ -213,12 +376,11 @@ def endpattern (expr xstep, ystep) =
newinternal tilingtype; tilingtype:=1;
_patterns_._last_ := 0;
-
% 1 miscellaneous
-vardef debug_pdf = luafunction enable_debugging () enddef;
+def debug_pdf = luafunction enable_debugging(); enddef;
-def texmessage text msg = luafunction texio.write_nl (msg) enddef;
+vardef texmessage text msg = luafunction texmessage (msg) enddef;
vardef rgb_to_gray expr c =
luafunction rgb_to_gray (redpart c, greenpart c, bluepart c)
diff --git a/macros/luatex/generic/minim-mp/minim-mp.pdf b/macros/luatex/generic/minim-mp/minim-mp.pdf
index aa64541ffe..9ea6a51612 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.sty b/macros/luatex/generic/minim-mp/minim-mp.sty
index ae0312b560..250e86668e 100644
--- a/macros/luatex/generic/minim-mp/minim-mp.sty
+++ b/macros/luatex/generic/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/macros/luatex/generic/minim-mp/minim.mp b/macros/luatex/generic/minim-mp/minim.mp
index 2bcea78343..b0df361b94 100644
--- a/macros/luatex/generic/minim-mp/minim.mp
+++ b/macros/luatex/generic/minim-mp/minim.mp
@@ -70,8 +70,8 @@ def save_string text t = save t; string t; enddef;
% missing trigonometric functions
vardef tand primary x = sind(x)/cosd(x) enddef;
-vardef arcsind primary x = angle((1+-+x, x)) enddef;
-vardef arccosd primary x = angle((x, 1+-+x)) enddef;
+vardef arcsind primary x = angle(1+-+x, x) enddef;
+vardef arccosd primary x = angle(x, 1+-+x) enddef;
vardef arctand primary x = angle(1, x) enddef;
% segments of the circle (counterclockwise)
diff --git a/macros/luatex/generic/minim-pdf/README b/macros/luatex/generic/minim-pdf/README
index eb9a1bd114..0626208f2f 100644
--- a/macros/luatex/generic/minim-pdf/README
+++ b/macros/luatex/generic/minim-pdf/README
@@ -1,4 +1,4 @@
-Version: 2023/1.3
+Version: 2024/1.4
SUMMARY
@@ -27,6 +27,20 @@ In order to obtain the typeset manual of this package, simply say:
HISTORY
+2024/1.4 (5/1/2024)
+
+ Breaking changes:
+
+ * Table cells and headers now connect automatically in \automarktable
+
+ New features:
+
+ * Add support for pdf/ua
+ * Re-write \hyperlink, adding many options
+ * Add ref and title options to structure elements
+ * New helper macros for footnotes
+ * Add \withpdfresource
+
2023/1.3 (20/10/2023)
New features:
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.doc b/macros/luatex/generic/minim-pdf/minim-pdf.doc
index e04deb31ab..111d8c1423 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.doc
+++ b/macros/luatex/generic/minim-pdf/minim-pdf.doc
@@ -3,7 +3,7 @@
\manual{minim-pdf}
-\ifchapter \chapter Hypertext
+\ifchapter \chapter Advanced PDF features
This chapter and the next document the support of the modern pdf features
provided by the minim-pdf package.
@@ -14,8 +14,8 @@ all other features of the package are described here.
\else \startmetadata
author {Esger Renkema}
title {minim-pdf}
- date {2023-10-20}
- version {2023/1.3}
+ date {2024-01-05}
+ version {2024/1.4}
keywords {LuaTeX; Plain TeX; PDF/A; Tagged PDF; accessibility; a11y;
hypertext; bookmarks; document outline; associated files}
stopmetadata
@@ -33,10 +33,16 @@ all other features will be covered first.
\section Hyperlinks % 1
-For most simple cases, you can use
-⟦*\hyperlink [name {...} | url {...}] ... \endlink⟧ for linking to a named
-destination in your own document or to an external hyperlink respectively.
-There is no support for nonsimple cases.
+Hyperlinks can be made with
+⟦*\hyperlink [alt {...}] [attr {...}] <action> ... \endlink⟧, where the
+⟦<action>⟧ must be one of
+⟦name {...} | url {...} | name {...} | next | prev | first | last⟧
+With the ⟦name⟧ action, a named destination must be used (see below), while the
+⟦user⟧ action will be passed directly to the back-end (as with the pdftex
+primitive).
+The ⟦*\hyperlinkstyle⟧ token list can be used so set common (pdf) link
+attributes; it defaults to ⟦/Border [0 0 0]⟧. The contents of the optional
+⟦attr⟧ parameter will be appended to these.
A named destination can be created with ⟦*\nameddestination {...}⟧ (also in
horizontal mode, unlike the backend primitive) and if you cannot think of
@@ -47,9 +53,10 @@ generated name.
\section Bookmarks % 1
-Bookmarks can be added with ⟦*\outline [open|closed] [dest {name}] {title}⟧.
-Add ⟦open⟧ or ⟦closed⟧ to have the bookmark appear initially open or
-closed (default), and
+Bookmarks (also known as outlines) can be added with ⟦*\outline [open|closed]
+[dest {name}] {title}⟧.
+Add ⟦open⟧ or ⟦closed⟧ to have the bookmark appear initially open or closed
+(default), and
say ⟦dest {name}⟧ for having it refer to a specific named destination
(otherwise, a new one will be created where the ⟦\outline⟧ command appears).
@@ -101,6 +108,48 @@ are enabled by default.
You can disable them by setting ⟦*\writehyphensandspaces⟧ to a nonpositive value.
+\section PDF/UA % 1
+
+You can claim pdf/ua conformance with ⟦*\pdfualevel 1⟧.
+By itself, this will do very little:
+
+\startlist
+\tagattribute List ListNumbering /Decimal
+\item 1. The ⟦pdfuaid:part⟧ metadata key will be set.
+\item 2. A conforming ⟦ViewerPreferences⟧ dictionary will be added to the document catalog.
+\item 3. The ⟦/Suspects⟧ key of the ⟦MarkInfo⟧ dictionary will be set to ⟦false⟧.
+\item 4. ⟦/Tab /S⟧ will be added to the page attributes.
+\stoplist
+
+Also making your document pdf/a-compliant, however, will relieve you of a few
+additional worries:
+
+\startlist
+\tagattribute List ListNumbering /Decimal
+\tagattribute List ContinuedList true
+\item 5. Fonts will be included properly.
+\item 6. The (natural) language of every element will be known.
+\item 7. Headings will be strongly-structured.
+\item 8. Table headers will have their ⟦Scope⟧ set properly.
+\item 9. A document outline will be generated automatically.
+\stoplist
+
+This leaves the following for you to provide before your document can be
+pdf/ua-compliant:
+
+\startlist
+\tagattribute List ListNumbering /Decimal
+\tagattribute List ContinuedList true
+\item 10. Figure and Formula structure elements must have alt texts.
+\item 11. Hyperlinks must have alternate descriptions.
+\item 12. Lists must have the ⟦ListNumbering⟧ attribute set.
+\item 13. Tables must have headers that are tagged as such.
+\item 14. Page headers and footers must be marked as header or footer artifacts.
+\item 15. Document section structure elements should have their ⟦title⟧ set.
+\item 16. All embedded files must have a description.
+\stoplist
+
+
\section Embedded files % 1
You can attach (associate) files with ⟦*\embedfile <options>⟧.
@@ -110,7 +159,9 @@ document catalog.
Arguments consisting of a single word can be given without braces and
exactly one of the options ⟦file⟧ or ⟦string⟧ must be present.
-\smallskip\marktableaslist \halign{#\quad\hfil&#\hfil\cr
+\smallskip\marktableaslist
+\tagattribute List ListNumbering /Description
+\halign{#\quad\hfil&#\hfil\cr
⟦file ⟧\hfill⟦{...}⟧ & The file to embed.\cr
⟦string ⟧\hfill⟦{...}⟧ & The string to embed.\cr
⟦global⟧ & Attach to the document catalog.\cr
@@ -166,6 +217,7 @@ You should always check the result in an external application.
This is the full list of limitations, pitfalls and shortcomings:
\startlist
+\tagattribute List ListNumbering /Decimal
\item1. Document content must be seen by tex in its logical order (although you
can mark out-of-order content explicitly if you know what you are doing; see
below).
@@ -342,9 +394,16 @@ Note that for the ⟦owner⟧ and ⟦key⟧ the initial slash must be omitted; t
⟦value⟧ on the other hand will be written to the pdf verbatim.
Any number of attributes can be added.
-An identifier can be set with the ⟦id⟧ option, or after the fact with
+An identifier can be set with the ⟦id {...}⟧ option, or after the fact with
⟦*\settagid {...}⟧. This identifier will be added to the ⟦IDTree⟧ and is
entirely optional; you will probably already know when you need it.
+The ⟦ref {...}⟧ option lets a structure element refer to another (the ⟦/Ref⟧
+option in the structure element dictionary). Its argument should be the ⟦id⟧ of
+the other structure element.
+
+The title of the structure element (corresponding to the ⟦/T⟧ entry in the
+structure element dictionary) can be set with the ⟦title {...}⟧ option. The
+pdf/ua standard requires this key for all document sections.
Finally, structure element classes can be given with the
⟦class <classname>⟧ keyword, which can be repeated.
@@ -450,12 +509,13 @@ sort (and indeed, no general logic can be given in the latter case). Always proc
with caution when using cells spanning multiple rows, and inspect the resulting
structure carefully.
-Marking a table header (either manually or with ⟦\automarktable⟧) will not
-connect normal cells with their headers; you will have to connect these
-manually by including ⟦*\markcolumnhead⟧ or ⟦*\markrowhead⟧ in the appropriate
-header cells. This must be done \emph{after} ⟦\markcolumnspan⟧ if the latter
-appplies. If properly setup like this, other cells of the table (including
-header cells) will be assigned to matching row or column headers automatically.
+Marking up a table header (except if done through ⟦\automarktable⟧)
+will not connect normal table cells with their headers; you will have to
+connect these manually by including ⟦*\markcolumnhead⟧ or ⟦*\markrowhead⟧ in
+the appropriate header cells. This must be done \emph{after} ⟦\markcolumnspan⟧
+if the latter appplies. If properly setup like this, other cells of the table
+(including header cells) will be assigned to matching row or column headers
+automatically.
\section Other helper macros % 1
@@ -465,6 +525,9 @@ For marking up an entry in a table of contents, you can use the macro
tags in the correct way. (The ⟦dest⟧ is a link destination and can be empty;
the ⟦lbl⟧ is a section number and can also be empty.)
+For tagging (foot)notes, ⟦*\marknoteref{*}⟧ and ⟦*\marknotelbl{*}⟧, when placed
+around the footnote markers, will insert the proper ⟦Ref⟧, ⟦Note⟧ and ⟦Lbl⟧
+tags.
% 
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.lua b/macros/luatex/generic/minim-pdf/minim-pdf.lua
index 74d56eeaac..875fd89659 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.lua
+++ b/macros/luatex/generic/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 <</S/GoTo /D %s>>', pdf_string(token.scan_argument()))
+end
+function link_types.url()
+ return string.format('/Subtype/Link /A <</S/URI /URI %s>>', pdf_string(token.scan_argument()))
+end
+
+function link_types.user() return token.scan_argument() end
+function link_types.next() return '/Subtype/Link /A <</S/Named /N/NextPage>>' end
+function link_types.prev() return '/Subtype/Link /A <</S/Named /N/PrevPage>>' end
+function link_types.first() return '/Subtype/Link /A <</S/Named /N/FirstPage>>' end
+function link_types.last() return '/Subtype/Link /A <</S/Named /N/LastPage>>' 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/macros/luatex/generic/minim-pdf/minim-pdf.pdf b/macros/luatex/generic/minim-pdf/minim-pdf.pdf
index a6e22ee194..2b2ea69c42 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.pdf
+++ b/macros/luatex/generic/minim-pdf/minim-pdf.pdf
Binary files differ
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.tex b/macros/luatex/generic/minim-pdf/minim-pdf.tex
index d8b06255ba..8a5f738063 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.tex
+++ b/macros/luatex/generic/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 <args> ... \endlink
+% args: alt {...} attr {...} <type>
+% 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 <</S/GoTo /D (#1)>>}}
-\def\hyperlink:url#1{user {/Subtype/Link /F 4 /A <</S/URI /URI (#1)>>}}
-\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/macros/luatex/generic/minim-xmp/README b/macros/luatex/generic/minim-xmp/README
index 4b983bddeb..15878c6e17 100644
--- a/macros/luatex/generic/minim-xmp/README
+++ b/macros/luatex/generic/minim-xmp/README
@@ -1,4 +1,4 @@
-Version: 2023/1.2
+Version: 2024/1.3
SUMMARY
@@ -21,6 +21,12 @@ In order to obtain the typeset manual of this package, simply say:
HISTORY
+2024/1.3 (5/1/2024)
+
+ New features:
+
+ * Add the pdfuaid namespace.
+
2023/1.2 (3/3/2022)
No new features.
diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.doc b/macros/luatex/generic/minim-xmp/minim-xmp.doc
index 651373e2b6..250edb18af 100644
--- a/macros/luatex/generic/minim-xmp/minim-xmp.doc
+++ b/macros/luatex/generic/minim-xmp/minim-xmp.doc
@@ -8,8 +8,8 @@
\else \startmetadata
author {Esger Renkema}
title {minim-xmp}
- date {2023-03-03}
- version {2023/1.2}
+ date {2024-01-05}
+ version {2024/1.3}
keywords {LuaTeX; Plain TeX; XMP; metadata; PDF/A;}
stopmetadata
\maketitle \fi
@@ -85,7 +85,8 @@ the value of a single field and ⟦\getmetadata key⟧ will return all fields as
\section Supported metadata keys
Initially, the ⟦\setmetadata⟧ and ⟦\getmetadata⟧ recognise all pdf/a compatible
-fields in the ⟦pdf⟧, ⟦pdfaid⟧, ⟦dc⟧, ⟦xmp⟧, ⟦xmpMM⟧ and ⟦xmpRights⟧ namespaces.
+fields in the ⟦pdf⟧, ⟦pdfaid⟧, ⟦pdfuaid⟧, ⟦dc⟧, ⟦xmp⟧, ⟦xmpMM⟧ and ⟦xmpRights⟧
+namespaces.
Keys should be prefixed with their namespace, e.g. ⟦dc:creator⟧ or
⟦xmp:CreatorTool⟧.
Note that the ⟦dc⟧ namespace has lower-case fields; field names are
diff --git a/macros/luatex/generic/minim-xmp/minim-xmp.lua b/macros/luatex/generic/minim-xmp/minim-xmp.lua
index d6867a4c9b..e3b7a8de70 100644
--- a/macros/luatex/generic/minim-xmp/minim-xmp.lua
+++ b/macros/luatex/generic/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/macros/luatex/generic/minim-xmp/minim-xmp.pdf b/macros/luatex/generic/minim-xmp/minim-xmp.pdf
index b5011cd8e0..fdf38f3a2c 100644
--- a/macros/luatex/generic/minim-xmp/minim-xmp.pdf
+++ b/macros/luatex/generic/minim-xmp/minim-xmp.pdf
Binary files differ
diff --git a/macros/luatex/generic/minim/README b/macros/luatex/generic/minim/README
index 36fe201f7c..4aa90c7cd8 100644
--- a/macros/luatex/generic/minim/README
+++ b/macros/luatex/generic/minim/README
@@ -1,4 +1,4 @@
-Version: 2023/1.3
+Version: 2024/1.4
SUMMARY
@@ -31,6 +31,11 @@ the packages mentioned above.
HISTORY
+2024/1.4 (5/1/2024)
+
+ * Documented the pdf resource management module
+ * Updated the manual to pdf/ua
+
2023/1.3 (20/10/2023)
No changes.
diff --git a/macros/luatex/generic/minim/minim-alloc.doc b/macros/luatex/generic/minim/minim-alloc.doc
index 4aec2d39fc..5a343c4b85 100644
--- a/macros/luatex/generic/minim/minim-alloc.doc
+++ b/macros/luatex/generic/minim/minim-alloc.doc
@@ -25,7 +25,7 @@ load both packages should not matter.
\section Format files
A major motivation for writing this module (and not, instead, depending on
-⟦ltluatex.tex⟧) is the ability to write lua-heavy code that can be safely
+ltluatex) is the ability to write lua-heavy code that can be safely
included in format files.
For this purpose, the register allocation functions described below allow
ensuring that the allocation is made only once.
@@ -171,6 +171,46 @@ If you create a new callback with a number for a name, that callback will
replace the ⟦*process_rule⟧ callback when its number matches the index property
of the rule.
+\section PDF resources
+
+This package can perform sophisticated pdf resource management, assigning to
+every page a resource object containing only the resources referenced on that
+page. pdf resources are ⟦ExtGstate⟧, ⟦ColorSpace⟧, ⟦Pattern⟧ and ⟦Shading⟧
+objects that have to be referenced by name (⟦/name⟧) instead of with the usual
+object references (⟦n 0 R⟧).\footnote
+ {*}{This is of course also the case for ⟦Font⟧ resources, but those are
+ already managed by luatex’s pdf backend.}
+Currently, the only other package managing pdf resources for plain tex is
+tikz/pgf, and the latter does so by collecting all resources in a single
+(global) resource object. That approach is not wrong per se, but may cause
+other tools processing the resulting pdf to retain unneeded resources.
+Both implementations can safely be used together, but since pgf does not keep
+track of actual resource use, any resources defined through pgf will be added
+to the resource dictionary of every subsequent page.
+
+The resource management is implemented in minim-pdfresources.lua and
+minim-pdfresources.tex, of which the tex file currently only includes pgf
+compatibility code (and may thus be omitted in the absence of pgf). In the
+following, ⟦R = require 'minim-pdfresources'⟧ is understood.
+
+You must register resources before you use them. This can be done with
+⟦*R.add_resource(kind, name, resource)⟧, where ⟦kind⟧ is one of the resource
+types, ⟦name⟧ is the name you want to use for it (without a preceding slash)
+and ⟦resource⟧ is a table that may contain any data you want to associate with
+the resource. In the ⟦resource⟧ table, either the key ⟦value⟧ must be present
+(containing the (raw) contents of the resource; will be written to the pdf
+as-is) or the key ⟦write⟧ (which must be a function that will be called once,
+to generate the ⟦value⟧). Registered resources can be retrieved again with
+⟦*R.get_resource(kind, name)⟧.
+
+You can refer to registered resources with the ⟦name⟧ you used to register
+them. Whenever you do so, however, you must mark the reference with a special
+⟦late_lua⟧ node that will tell minim to include the resource in the resource
+list for the page it appears on. These nodes can be created from lua with
+⟦*R.use_resource_node(kind, name)⟧ or directly inserted by tex with
+⟦*\withpdfresource {kind} {name}⟧ (the braces are optional).
+
+
\section Programming helpers
Optional keyword arguments to tex macros can be defined with help of
diff --git a/macros/luatex/generic/minim/minim-doc.sty b/macros/luatex/generic/minim/minim-doc.sty
index 97b173beac..93d807f16a 100644
--- a/macros/luatex/generic/minim/minim-doc.sty
+++ b/macros/luatex/generic/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/macros/luatex/generic/minim/minim-pdfresources.lua b/macros/luatex/generic/minim/minim-pdfresources.lua
index e6a06e783e..468da03b48 100644
--- a/macros/luatex/generic/minim/minim-pdfresources.lua
+++ b/macros/luatex/generic/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 <value>'
@@ -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/macros/luatex/generic/minim/minim-pdfresources.tex b/macros/luatex/generic/minim/minim-pdfresources.tex
index 6b8dc29577..57e9d8f012 100644
--- a/macros/luatex/generic/minim/minim-pdfresources.tex
+++ b/macros/luatex/generic/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
diff --git a/macros/luatex/generic/minim/minim.doc b/macros/luatex/generic/minim/minim.doc
index bf56d89747..e975718b1e 100644
--- a/macros/luatex/generic/minim/minim.doc
+++ b/macros/luatex/generic/minim/minim.doc
@@ -4,8 +4,8 @@
\startmetadata
author {Esger Renkema}
title {minim}
- date {2023-10-20}
- version {2023/1.3}
+ date {2024-01-05}
+ version {2024/1.4}
keywords {LuaTeX; Plain TeX; MetaPost; PDF/A; Tagged PDF; accessibility; a11y;
Unicode mathematics; XMP; metadata; hypertext; bookmarks}
stopmetadata
@@ -20,7 +20,9 @@ a basis for your own formats.
Most features included in the format are provided by separate packages that can
be used on their own; see the packages
-\smallskip\marktableaslist \halign {\strut
+\smallskip\marktableaslist
+\tagattribute List ListNumbering /Description
+\halign {\strut
\vrule height10ptdepth2ptwidth0pt\qquad
\red{#}\quad\hfil&#\hfil\cr
minim-mp&for mplib (MetaPost) support\cr
diff --git a/macros/luatex/generic/minim/minim.pdf b/macros/luatex/generic/minim/minim.pdf
index b6e05b6cc8..3848cf5103 100644
--- a/macros/luatex/generic/minim/minim.pdf
+++ b/macros/luatex/generic/minim/minim.pdf
Binary files differ