summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/minim-pdf
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-03-04 03:01:04 +0000
committerNorbert Preining <norbert@preining.info>2022-03-04 03:01:04 +0000
commit575ab7d0e7534edd510bbc89a2ef4b04582af591 (patch)
tree9fc4cf55b7ca8b168bdfd747be21de4c9ef2b30e /macros/luatex/generic/minim-pdf
parent6b9ed20cb01cb52c7aaf2a48202f5d8aa588ff79 (diff)
CTAN sync 202203040301
Diffstat (limited to 'macros/luatex/generic/minim-pdf')
-rw-r--r--macros/luatex/generic/minim-pdf/README27
-rw-r--r--macros/luatex/generic/minim-pdf/minim-languagecodes.lua8
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.doc86
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.lua294
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.pdfbin108362 -> 110458 bytes
-rw-r--r--macros/luatex/generic/minim-pdf/minim-pdf.tex62
6 files changed, 239 insertions, 238 deletions
diff --git a/macros/luatex/generic/minim-pdf/README b/macros/luatex/generic/minim-pdf/README
index 1d1f02a913..d8f2b19c6e 100644
--- a/macros/luatex/generic/minim-pdf/README
+++ b/macros/luatex/generic/minim-pdf/README
@@ -1,4 +1,4 @@
-Version: 2021/1.0
+Version: 2022/1.1
SUMMARY
@@ -25,9 +25,32 @@ In order to obtain the typeset manual of this package, simply say:
luatex minim-pdf.doc
+HISTORY
+
+2022/1.1 (3/3/2022)
+
+ New features:
+
+ * Added support for structure element attributes.
+ * Added support for structure element attribute classes.
+ * Improved tagging of equations; added configuration.
+ * Made tagged artifacts more robust: they can now occur across page
+ breaks.
+ * The lua interface should now be more-or-less stable.
+
+ Minor changes:
+
+ * PDF strings will be encoded in a more clever (and readable) way.
+ * Structure element tags will have special characters encoded properly.
+
+2021/1.0 (1/6/2021)
+
+ This was the original release.
+
+
COPYING
-(c) 2021 Esger Renkema
+(c) 2022 Esger Renkema
These files may be distributed under the terms of the European Union Public
Licence (EUPL) version 1.2 or later. A copy can be obtained at:
diff --git a/macros/luatex/generic/minim-pdf/minim-languagecodes.lua b/macros/luatex/generic/minim-pdf/minim-languagecodes.lua
index df3e95a65e..6a8b6f3b0b 100644
--- a/macros/luatex/generic/minim-pdf/minim-languagecodes.lua
+++ b/macros/luatex/generic/minim-pdf/minim-languagecodes.lua
@@ -78,6 +78,7 @@ return {
latvian = 'lv',
lithuanian = 'lt',
liturgicallatin = 'la-x-liturgic',
+ macedonian = 'mk',
malayalam = 'ml',
marathi = 'mr',
medievalgreek = 'gkm',
@@ -94,8 +95,10 @@ return {
norwegian = 'nb',
nynorsk = 'nn',
occitan = 'oc',
+ oldgerman = 'de',
oriya = 'or',
pali = 'pi',
+ panjabi = 'pa',
patois = 'fr',
persian = 'fa',
piedmontese = 'pms',
@@ -111,6 +114,7 @@ return {
sahidic = 'cop-x-sahidic',
samaritan = 'smp',
sanskrit = 'sa',
+ schoolfinnish = 'fi',
serbian = 'sr-latn',
serbianc = 'sr-cyrl',
slovak = 'sk',
@@ -129,11 +133,11 @@ return {
turkish = 'tr',
turkmen = 'tk',
ugaritic = 'uga',
- uncoded = 'mis',
- undetermined = 'und',
ukenglish = 'en-UK',
UKenglish = 'en-UK',
ukrainian = 'uk',
+ uncoded = 'mis',
+ undetermined = 'und',
uppersorbian = 'hsb',
usenglish = 'en-US',
USenglish = 'en-US',
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.doc b/macros/luatex/generic/minim-pdf/minim-pdf.doc
index 2c06dd25b0..71e8cea89c 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.doc
+++ b/macros/luatex/generic/minim-pdf/minim-pdf.doc
@@ -14,8 +14,8 @@ all other features of the package are described here.
\else \startmetadata
author {Esger Renkema}
title {minim-pdf}
- date {2021-06-01}
- version {2021/1.0}
+ date {2022-03-03}
+ version {2022/1.1}
keywords {LuaTeX; Plain TeX; PDF/A; Tagged PDF; accessibility; a11y;
hypertext; bookmarks; document outline; associated files}
stopmetadata
@@ -47,8 +47,9 @@ generated name.
\section Bookmarks % 1
-Bookmarks can be added with ⟦*\outline [open] [dest {name}] {title}⟧.
-Add ⟦open⟧ to have the bookmark appear initially open and
+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
say ⟦dest {name}⟧ for having it refer to a specific named destination
(otherwise, a new one will be created where the ⟦\outline⟧ command appears).
@@ -129,11 +130,10 @@ With the minim-xmp package, a useful setting is
\section Lua module % 1
The interface of the lua module (available via
-⟦local M = require('minim-pdf')⟧) is not stable yet, and may change.
-One function of interest, however, is
-⟦M.pdf_string(...)⟧, wich converts a lua string to a pdf string. The
-surrounding ⟦<>⟧ or ⟦()⟧ characters are included in the return value.
-
+⟦local M = require('minim-pdf')⟧) should be stable by now.
+Though it contains lua equivalents for most tex commands described here, using
+them directly is not very ergonomical and not recommended.
+Please consult the source if you do want to use them anyway.
% 
@@ -160,7 +160,7 @@ be seen by tex’s stomach in the \emph{logical} order.
Furthermore, while the macros in this package are sophisticated enough that
tagging can be done without any manual intervantion, it is quite possible and
-rather easy to generate the wrong document structore, or even cause syntax
+rather easy to generate the wrong document structure, or even cause syntax
errors in the resulting pdf code.
You should always check the result in an external application.
@@ -169,21 +169,17 @@ This is the full list of limitations, pitfalls and shortcomings:
\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).
-\item2. Artifacts cannot be split across pages. A pagebreak inmidst an artifact
-will cause incorrect pdf without error or warning.
-\item3. The contents of ⟦\localleftbox⟧ and ⟦\localrightbox⟧ must be marked
+\item2. The contents of ⟦\localleftbox⟧ and ⟦\localrightbox⟧ must be marked
manually, probably as artifact.
-\item4. You must mark page header, page footer and footnote rule yourself; no
+\item3. You must mark page header, page footer and footnote rule yourself; no
default is set.
-\item5. There currently is no way of marking xforms or other pdf objects as
+\item4. There currently is no way of marking xforms or other pdf objects as
content items of themselves.
-\item6. The content of xforms (i.e. pdf objects created by ⟦\useboxresource⟧)
+\item5. The content of xforms (i.e. pdf objects created by ⟦\useboxresource⟧)
should not contain tagging commands.
-\item7. Likewise, you should be careful with box reuse: it might work, but you
+\item6. Likewise, you should be careful with box reuse: it might work, but you
should check.
-\item8. The use of structure element attributes is currently not supported
-except in a limited number of cases.
-\item9. This package currently only supports pdf~1.7 tagging and is not yet
+\item7. This package currently only supports pdf~1.7 tagging and is not yet
ready for use with pdf~2.0.
\stoplist
@@ -234,7 +230,7 @@ structure element instead.
The begining and ending of a content item can be forced with
⟦*\startcontentitem⟧ and ⟦*\stopcontentitem⟧, while ⟦*\ensurecontentitem⟧ will
only open a new content item if you are currently outside any.
-If you need some part to be a single content item, that can use
+If you need some part to be a single content item, you can use
⟦*\startsinglecontentitem ... \stopsinglecontentitem⟧.
This will disable all SE and MCI tagging inside.
@@ -249,8 +245,6 @@ Artifacts can be marked in two ways: with ⟦*\markartifact {type} {...}⟧ or w
The ⟦type⟧ is written to the pdf attribute dictionary directly, so that if you
need a subtype, you can write e.g.
⟦\startartifact {Pagination /Subtype/Header} etc⟧.
-Do make sure your artifact does not contain a page break, as this will result
-in invalid output.
Inside artifacts, other structure content markers will be ignored. Furthermore,
this package makes sure artifacts are never part of marked content items,
@@ -281,11 +275,11 @@ perfect nor complete. You should always check the results in an external
application.
Particular care should be taken when ‘skipping’ structure levels: the sequence
chapter – subsection – section will result in the section beneath the subsection.
-If you are in doubt about an element being closed already, you can use
+If you are in doubt whether an element has been closed already, you can use
⟦*\ensurestopelement {Tag}⟧ instead of ⟦\stopelement⟧
to prevent an error being raised.
-All these helpful features can also be disabled by setting
+All these helpful features can be disabled by setting
⟦*\strictstructuretagging⟧ to a positive value. Then, every structure element
will have to be closed by an explicit closing tag, as in xml.
In this case, ⟦\stopelement⟧ and ⟦\ensurestopelement⟧ will be equivalent.
@@ -293,6 +287,8 @@ In this case, ⟦\stopelement⟧ and ⟦\ensurestopelement⟧ will be equivalent
By default, ⟦P⟧ structure elements are inserted automatically at the start of
every paragraph. The tag can be changed with ⟦*\nextpartag {Tag}⟧; leaving the
argument empty will prevent marking the next paragraph.
+Keep in mind that the reassignment is local: if a paragraph marked with
+⟦\nextpartag⟧ starts inside a group, it will not reset.
Auto-marking paragraphs can be (locally) disabled or enabled by saying
⟦*\markparagraphsfalse⟧ or ⟦*\markparagraphstrue⟧.
@@ -326,20 +322,28 @@ in more complicated situations you should therefore use
⟦*\savecurrentelementto\name ... \continueelementfrom\name⟧ which restores the
current SE from a named identifier ⟦\name⟧.
-
\section Structure element options % 1
-The ⟦\startcontentitem⟧ command allows a few options that are not mentioned
-above: its full syntax is ⟦*\startcontentitem <options> {Tag}⟧.
+The ⟦\startelement⟧ command allows a few options that are not mentioned
+above: its full syntax is ⟦*\startelement <options> {Tag}⟧.
The three most useful options are ⟦alt⟧ for setting an alt-text (the ⟦/Alt⟧
entry in the structure element dicionary), ⟦actual⟧ for a text replacement
(⟦/ActualText⟧) and ⟦lang⟧ for the language (⟦/Lang⟧; see the next section).
The alternative and actual texts can also be given after the fact with
-⟦*\setalttext{...}⟧ and ⟦*\setactualtext{...}⟧. These apply to the current
+⟦*\setalttext{...}⟧ and ⟦*\setactualtext{...}⟧; these apply to the current
structure element.
-Setting structure element attributes is not supported at this moment, except
-the placement attributes ⟦block⟧ and ⟦inline⟧, which can be given as options.
+Structure element attributes can be given with
+⟦attr <owner> <key> <value>⟧, e.g. ⟦attr Layout Placement /Inline⟧.
+Note that for the ⟦owner⟧ and ⟦key⟧ the initial slash must be omitted; the
+⟦value⟧ on the other hand will be written to the pdf verbatim.
+Any number of attributes can be added.
+
+Finally, structure element classes can be given with the
+⟦class <classname>⟧ keyword, which can be repeated.
+Classes can be defined with
+⟦*\newattributeclass classname <attributes>⟧ where ⟦<attributes>⟧ can be any
+number of ⟦attr⟧ statements as above.
\section Languages % 1
@@ -358,7 +362,7 @@ separate codes to dialects.
There is a small set of default language code associations, which can be found
in the file ⟦minim-languagecodes.lua⟧.
It covers most languages defined by the hyph-utf8 package, as well as (due to
-their ubiquitous use) many ancient languages.
+their ubiquitous use) some ancient languages.
An actual language change introduced by ⟦\uselanguage⟧ will not otherwise be
acted upon by this package. Therefore, you will probably want to add
@@ -381,7 +385,7 @@ This package ensures the existence of the ⟦nohyph⟧, ⟦nolang⟧, ⟦uncoded
⟦undetermined⟧ dummy languages, all without hyphenation.
-\section Helper macros
+\section Helper macros % 1
For marking up an entry in a table of contents, you can use the macro
⟦*\marktocentry {dest} {lbl} {title} {filler} {pageno}⟧, which should insert all
@@ -410,14 +414,18 @@ are used for lay-out and should not be marked as such.
After the latter command, auto-tagging can be switched off and on with
⟦*\stopformulatagging⟧ and ⟦*\startformulatagging⟧.
-Both alt and actualtext of the ⟦Formula⟧ structure element will be set to the
-(unexpanded) source code of the equation, surrounded by the appropriate number
-of dollar signs.
-Furthermore, if ⟦\pdfaconformancelevel⟧ equals three, the source of the formula
-will be attached in an embedded file with the ⟦/AFRelation⟧ set to ⟦Source⟧.
+The source of the equation can be associated with the ⟦Formula⟧ structure
+element in various ways, which can be configured with ⟦*\includeformulasources
+{option}⟧, where the ⟦option⟧ must be one of ⟦none⟧, ⟦actualtext⟧, ⟦attachment⟧
+or ⟦both⟧.
+The ⟦actualtext⟧ option will use the unexpanded source code of the equation,
+surrounded by the appropriate number of dollar signs.
+The ⟦attachment⟧ option will only work if ⟦\pdfaconformancelevel⟧ equals three:
+then the source of the formula will be attached in an embedded file with the
+⟦/AFRelation⟧ set to ⟦Source⟧.
The name of this file can be changed by redefining ⟦*\formulafilename⟧ inside
the equation.
-
+The default value is ⟦both⟧.
% 
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.lua b/macros/luatex/generic/minim-pdf/minim-pdf.lua
index ffe845061c..96725523d2 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.lua
+++ b/macros/luatex/generic/minim-pdf/minim-pdf.lua
@@ -2,6 +2,7 @@
local M = { }
local alloc = require('minim-alloc')
local cb = require('minim-callbacks')
+alloc.remember('minim-hooks')
alloc.remember('minim-pdf')
-- 1 helper functions
@@ -18,9 +19,27 @@ local function add_to_names(...)
pdf.setnames((pdf.getnames() or '') .. string.format(...) .. ' ')
end
+local pdf_name = alloc.pdf_name
+local pdf_string = alloc.pdf_string
+local options_scanner = alloc.options_scanner
+
+-- has this table just one element?
+local function singleton(t)
+ local one = false
+ for _, _ in pairs(t) do
+ if one then
+ return false
+ else
+ one = true
+ end
+ end
+ return one
+end
+
-- in-depth node list traversal
-- returns current and parent node
--- only dives into hbox and vbox nodes
+-- only dives into hbox, vbox and ins nodes
+-- returns node and enclosing box
local function full_traverse(head)
return function(stack, last)
local next = last.next
@@ -36,103 +55,8 @@ local function full_traverse(head)
end, { { } }, { next = head }
end
--- re-encode to utf-16 and surround by <>
-local function pdf_hex_string(text)
- local str = { [1] = '<feff' }
- for i in text:utfvalues() do
- if i < 0xFFFF then
- insert_formatted(str, '%04x', i)
- else
- i = i - 0x10000
- m = math.floor(i/0x400) + 0xD800
- n = ( i % 0x400 ) + 0xDC00
- insert_formatted(str, '%04x%04x' ,m, n)
- end
- end
- table.insert(str, '>')
- return table.concat(str,'')
-end
-
--- try and produce a () string first, then fall back to <>
-function M.pdf_string(text)
- local str = { '(' }
- for i in text:utfvalues() do
- if i > 0x7E then
- return pdf_hex_string(text)
- elseif i < 0x20 then
- insert_formatted(str, '\\%03o', i)
- elseif i == 0x28 or i == 0x29 or i == 0x5c then
- insert_formatted(str, '\\%c', i)
- else
- insert_formatted(str, '%c', i)
- end
- end
- table.insert(str, ')')
- return table.concat(str,'')
- end
-
--- make available as \pdfstring{...}
-alloc.luadef('pdfstring', function() M.pdf_string(token.scan_string()) end)
-
--- try and produce a date of the format (D:YYYYMMDD)
-function M.pdf_date(text)
- local y, m, d = string.match(text, '^([12][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$')
- if not y then d, m, y = string.match(text, '^([0-9][0-9]?)-([0-9][0-9]?)-([12][0-9][0-9][0-9])$') end
- if y then
- return string.format('(D:%4d%02d%02d)', y, m, d)
- else
- return string.format('(D:%s)', text)
- end
-end
-
-function M.options_scanner()
- return { scanners = { },
- add = function(this, name, scanner)
- this.scanners[name] = scanner
- return this
- end,
- keyword = function(this, name)
- return this:add(name, function() return true end)
- end,
- int = function(this, name)
- return this:add(name, token.scan_int)
- end,
- glue = function(this, name)
- return this:add(name, token.scan_glue)
- end,
- dimen = function(this, name)
- return this:add(name, token.scan_dimen)
- end,
- string = function(this, name)
- return this:add(name, token.scan_string)
- end,
- argument = function(this, name)
- return this:add(name, token.scan_argument)
- end,
- word = function(this, name)
- return this:add(name, token.scan_word)
- end,
- list = function(this, name)
- return this:add(name, token.scan_list)
- end,
- scan = function(this, defaults)
- local res = defaults or { }
- repeat
- local matched = false
- for name, scanner in pairs(this.scanners) do
- if token.scan_keyword(name) then
- matched, this.scanners[name] = true, nil
- res[name] = scanner()
- end
- end
- until not matched
- return res
- end
- }
-end
-
local function pdf_err(n, msg)
- local m = node.new(8,16)
+ local m = node.new(8,16) -- pdf_literal
m.mode, m.token = 2, '%% Warning: '..msg
node.insert_after(n, n, m)
end
@@ -158,10 +82,10 @@ local structure = { {
local parent_tree = { } -- list of list of structure elements
-- Our four helper attributes
-local current_struct = alloc.new_attribute('tagging:current:se')
-local current_order = alloc.new_attribute('tagging:element:order')
-local current_status = alloc.new_attribute('tagging:current:status')
-local current_lang = alloc.new_attribute('tagging:current:language')
+local current_struct = alloc.registernumber('tagging:current:se')
+local current_order = alloc.registernumber('tagging:element:order')
+local current_status = alloc.registernumber('tagging:current:status')
+local current_lang = alloc.registernumber('tagging:current:language')
local function current_structure_element()
return structure[tex.attribute[current_struct]]
@@ -242,7 +166,7 @@ local structure_types = alloc.saved_table('structure types', {
Note = { type = 'inline' },
Reference = { type = 'inline' },
BibEntry = { type = 'inline' },
- Code = { type = 'inline' },
+ Code = { type = 'inline', attributes = { ['CSS-2.00'] = { ['white-space'] = '(pre)' } } },
Link = { type = 'inline' }, -- contains link objects
Annot = { type = 'inline' }, -- contains other annotations
-- ruby/warichu
@@ -318,7 +242,6 @@ alloc.luadef('struct:addalias', function()
M.add_structure_alias(stype, alias, settings)
end, 'protected')
-
-- 1 writing the document structure
local function stable_sort_on_order_field(unsorted)
@@ -373,7 +296,7 @@ local function make_rolemap()
local aliases = { }
for k, v in pairs(structure_types) do
if v.aliasof and v.inuse and v.aliasof ~= k then
- insert_formatted(aliases, '/%s/%s', k, v.aliasof)
+ insert_formatted(aliases, '%s%s', pdf_name(k), pdf_name(v.aliasof))
end
end
if #aliases > 0 then
@@ -382,6 +305,33 @@ local function make_rolemap()
return ''
end
+local function make_attributes(res, t)
+ -- is there just one attribute?
+ local list = not singleton(t)
+ if list then table.insert(res, '[') end
+ for ao, ac in pairs(t) do
+ insert_formatted(res, '<< /O %s', pdf_name(ao))
+ for k,v in pairs(ac) do
+ insert_formatted(res, '%s %s', pdf_name(k), v)
+ end
+ table.insert(res, '>>')
+ end
+ if list then table.insert(res, ']') end
+end
+
+local attribute_classes = alloc.saved_table('attribute classes')
+local function make_classmap()
+ local classes = { }
+ for c, as in pairs(attribute_classes) do
+ insert_formatted(classes, '\n%s', pdf_name(c))
+ make_attributes(classes, as)
+ end
+ if #classes > 0 then
+ return '\n/ClassMap << ' .. table.concat(classes, ' ') .. ' >>'
+ end
+ return ''
+end
+
local function write_structure()
if #structure == 1 then return end
-- reserve object numbers, prepare for writing
@@ -395,22 +345,29 @@ local function write_structure()
add_to_catalog('/StructTreeRoot %s 0 R', root_obj)
-- write the structure tree root
pdf.immediateobj(root_obj, string.format(
- '<< /Type/StructTreeRoot /K %d 0 R /ParentTree %d 0 R%s >>',
- structure[1].objnum, parent_tree_obj, make_rolemap()))
+ '<< /Type/StructTreeRoot /K %d 0 R /ParentTree %d 0 R%s%s >>',
+ structure[1].objnum, parent_tree_obj, make_rolemap(), make_classmap()))
-- write structure elements
for _, se in ipairs(structure) do
if not se.hidden then
local res = { '<<' }
- insert_formatted(res, '/Type/StructElem /S/%s /P %d 0 R', se.struct, se.parent.objnum)
+ insert_formatted(res, '/Type/StructElem /S%s /P %d 0 R', pdf_name(se.struct), se.parent.objnum)
if se.lang and se.lang ~= se.parent.lang then insert_formatted(res, '/Lang (%s)', se.lang) end
- if se.alt then insert_formatted(res, '/Alt %s', M.pdf_string(se.alt)) end
- if se.actual then insert_formatted(res, '/ActualText %s', M.pdf_string(se.actual)) 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.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
+ table.insert(res, '/C')
+ if #se.class > 1 then table.insert(res, '[') end
+ for _, c in ipairs(se.class) do
+ insert_formatted(res, '%s', pdf_name(c))
+ end
+ if #se.class > 1 then table.insert(res, ']') end
+ end
if se.attributes then
- table.insert(res, '/A <<')
- for k,v in pairs(se.attributes) do insert_formatted(res, '/%s %s', k, v) end
- table.insert(res, '>>')
+ table.insert(res, '/A')
+ make_attributes(res, se.attributes)
end
if se.files then
table.insert(res, '/AF [')
@@ -487,11 +444,10 @@ end, 'protected')
-- \uselanguage patch; provide default document language
-- and associate names with numbers
-alloc.luadef('minim:apply:language:code', function()
- local name = token.scan_string()
+cb.register('uselanguage', function(name)
local nr = language_codes[name] or alloc.err('No language code known for ‘%s’', name) and 1
if not structure[1].lang then M.set_document_language(language_codes[nr]) end
- tex.sprint(nr)
+ tex.attribute['tagging:current:language'] = nr
end)
local function write_language()
@@ -536,24 +492,20 @@ end, 'protected')
function M.open_structure_node(n)
local info = structure_types[n.struct]
if not info then
- alloc.err('Unknown structure type %s replaced by NonStruct', n.struct)
+ alloc.err('Unknown structure type %s replaced with NonStruct', n.struct)
n.struct, info = 'NonStruct', structure_types.NonStruct
end
info.inuse = true
if info.attributes then
- n.attributes = n.attributes or { }
- for k,v in pairs(info.attributes) do
- n.attributes[k] = n.attributes[k] or v
+ n.attributes = n.attr or { }
+ for k1,v1 in pairs(info.attributes) do
+ n.attributes[k1] = n.attributes[k1] or { }
+ for k2, v2 in pairs(v1) do
+ n.attributes[k1][k2] = n.attributes[k1][k2] or v2
+ end
end
- end
- if n.block then
- n.type = 'block'
- n.attributes = n.attributes or { }
- n.attributes.Placement = '/Block'
- elseif n.inline then
- n.type = 'inline'
- n.attributes = n.attributes or { }
- n.attributes.Placement = '/Inline'
+ else
+ n.attributes = n.attr
end
n.index = #structure + 1
n.children = { }
@@ -573,13 +525,13 @@ function M.open_structure_node(n)
end
alloc.luadef('tagging:startelement', function()
- local s = M.options_scanner()
+ local s = options_scanner()
:string('type') -- 'section', 'group', 'block' etc.
:argument('alt')
:argument('actual')
:string('lang')
- :keyword('block')
- :keyword('inline')
+ :subtable('attr')
+ :string('class', true)
:scan()
s.struct = token.scan_string()
M.open_structure_node(s)
@@ -593,6 +545,11 @@ alloc.luadef('tagging:actual', function()
current_structure_element().actual = token.scan_string()
end)
+alloc.luadef('newattributeclass', function()
+ local s = options_scanner():subtable('attr')
+ attribute_classes[token.scan_string()] = s:scan().attr
+end, 'protected')
+
-- 1 marking content items
@@ -613,7 +570,7 @@ local function clear_page_tagging_parameters()
_open_mci_node_ = function (se_num, order)
pageobj = pageobj or pdf.getpageref(status.total_pages + 1)
local se = structure[se_num]
- pdf.print(string.format('/%s <</MCID %d>> BDC ', se.struct, #mcid_list))
+ pdf.print(string.format('%s <</MCID %d>> BDC ', pdf_name(se.struct), #mcid_list))
table.insert(se.children, { mcid = #mcid_list, order = order, pageobj = pageobj })
table.insert(mcid_list, se)
-- unhide hidden parents (done here to preserve the correct order)
@@ -631,17 +588,13 @@ local function new_open_mci_node(se, order)
return n
end
-local function new_open_art_node(atype)
+local function pdf_literal(token)
local n = node.new(8,16) -- pdf_literal
- n.mode, n.token = 1, string.format('/Artifact << /Type/%s >> BDC', atype)
+ n.mode, n.token = 1, token
return n
end
-local function new_emc_node()
- local n = node.new(8,16) -- pdf_literal
- n.mode, n.token = 1, 'EMC'
- return n
-end
+local open_artifacts = { }
cb.register('finish_pdfpage', function(shippingout)
if shippingout then
@@ -655,17 +608,17 @@ cb.register('finish_pdfpage', function(shippingout)
end
end)
--- 1 content item boundaries and linking
+-- 1 content item boundaries and linking (in pre-shipout)
function M.mark_content_items(box)
- local se, order, open
+ local se, order, art, open
local start_node, end_node, parent_node
local pageobj = pdf.getpageref(status.total_pages + 1)
- -- inserting mci markers
+ -- helper functions for inserting mci markers
local insert_tags = function(end_parent)
if not open then return end
parent_node.list = node.insert_before(parent_node.list, start_node, open)
- node.insert_after(end_parent.list, end_node, new_emc_node())
+ node.insert_after(end_parent.list, end_node, pdf_literal('EMC'))
start_node, end_node, parent_node, open = nil, nil, nil, nil
end
local start_content = function(n, b)
@@ -676,18 +629,31 @@ function M.mark_content_items(box)
for n, b in full_traverse(box) do
local marker = n.id == 8 and n.subtype == 8
and n.user_id == marker_whatsit and n.value
- if marker and marker.what == 'art_start' then
+ local stat = n[current_status]
-- first we start with marking artifacts
+ if marker and marker.what == 'art_start' then
insert_tags(b);
start_content(n, b)
- open = new_open_art_node(marker.it)
+ local a = string.format('/Artifact << /Type/%s >> BDC', marker.it)
+ open_artifacts[stat], open, art = a, pdf_literal(a), stat
elseif marker and marker.what == 'art_stop' then
end_node = n
insert_tags(b);
- elseif n[current_status] then
- -- inside artifact, do nothing
- elseif n.id == 8 and n.subtype == 19 then -- pdf_start_link
+ open_artifacts[art], art = nil, nil
+ elseif stat and stat >10 and stat ~=art then -- broken artifact
+ insert_tags(b);
+ start_content(n, b)
+ local a = open_artifacts[stat]
+ if not a then
+ alloc.err('Encountered tail of unknown artifact (see pdf)')
+ pdf_err(n, 'unknown artifact (this should not be possible)')
+ a = '/Artifact << /Type/Unknown >> BDC'
+ end
+ open, art = pdf_literal(a), stat
+ elseif stat then -- inside artifact or tagging disabled
+ end_node = n
-- then attach links to Link elements
+ elseif n.id == 8 and n.subtype == 19 then -- pdf_start_link
local _se, _order = n[current_struct], n[current_order]
local link = structure[_se]
if link.struct == 'Link' then
@@ -698,16 +664,16 @@ function M.mark_content_items(box)
alloc.err('Link found outside Link structure element (see pdf)')
pdf_err(n, 'unmarked link')
end
+ -- handle explicit start and stop nodes
elseif marker and marker.what == 'mci_start' then
- -- explicit start and stop nodes
insert_tags(b);
start_content(n, b)
open = new_open_mci_node(se, order)
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 == 2 or n.id == 29 -- rule, glyph or content marker
or marker and marker.what == 'content' then
- -- now see if we need to intervene between content nodes
local _se, _order = n[current_struct], n[current_order]
if n.id == 2 and (n.width == 0 or n.height == 0 and n.depth == 0) then
-- ignore invisible rules
@@ -720,8 +686,7 @@ function M.mark_content_items(box)
insert_tags(b);
start_content(n, b)
open = new_open_mci_node(se, order)
- else
- -- nothing changed: continue current mci
+ else -- nothing changed: continue current mci
end_node = n
end
elseif n.id == 12 and n.subtype > 2 and n.subtype < 8 then
@@ -730,10 +695,6 @@ function M.mark_content_items(box)
end
end
-- close the last mci
- if start_node and start_node.user_id and start_node.value
- and start_node.value.what == 'art_start' then
- alloc.err('Page %d ends with a broken artifact', status.total_pages+1)
- end
insert_tags(box)
end
@@ -786,7 +747,7 @@ 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)', M.pdf_string(bm.title),
+ 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)
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
@@ -827,9 +788,8 @@ function M.add_bookmark(bm)
end
alloc.luadef('outline', function()
- local s = M.options_scanner()
- :keyword('open')
- :keyword('closed') -- default; ignored
+ local s = options_scanner()
+ :keyword('open', 'closed')
:string('dest')
:scan()
s.title = token.scan_string()
@@ -866,8 +826,8 @@ function M.embed_file(t)
end
-- embed the file
local attr = { '/Type/EmbeddedFile' }
- if t.mimetype then insert_formatted(attr, '/Subtype/%s', string.gsub(t.mimetype, '/', '#2F')) end
- if t.moddate then insert_formatted(attr, '/Params << /ModDate %s >>', M.pdf_date(t.moddate)) end
+ if t.mimetype then insert_formatted(attr, '/Subtype%s', pdf_name(t.mimetype)) end
+ if t.moddate then insert_formatted(attr, '/Params << /ModDate %s >>', alloc.pdf_date(t.moddate)) end
local ef = pdf.obj {
immediate = true,
compresslevel = t.uncompressed and 0 or nil,
@@ -878,7 +838,7 @@ function M.embed_file(t)
-- write the filespec
local res = { '<< /Type/Filespec' }
insert_formatted(res, '/F %s /UF %s /EF << /F %d 0 R /UF %d 0 R >>', t.name, t.name, ef, ef)
- if t.desc then insert_formatted(res, '/Desc %s', M.pdf_string(t.desc)) end
+ if t.desc then insert_formatted(res, '/Desc %s', pdf_string(t.desc)) end
if t.relation then insert_formatted(res, '/AFRelationship /%s', t.relation) end
table.insert(res, '>>')
local fs = pdf.immediateobj(table.concat(res, ' '))
@@ -887,7 +847,7 @@ function M.embed_file(t)
end
alloc.luadef('embedfile', function()
- local t = M.options_scanner()
+ local t = options_scanner()
:string('desc')
:string('file')
:string('string')
@@ -901,7 +861,7 @@ alloc.luadef('embedfile', function()
if not t.name then
t.name = t.file or aloc.err('No name specified for embedded file stream')
end
- t.name = M.pdf_string(t.name or '(unnamed)')
+ t.name = pdf_string(t.name or '(unnamed)')
local fs, ef = M.embed_file(t, t.global)
-- where to attach?
if t.global then
@@ -1036,7 +996,7 @@ local function write_pagelabels()
end
local res = { '/PageLabels << /Nums [' }
for _, l in ipairs(pagelabels) do
- local s, p = l.s and '/S/'..l.s, l.p and ' /P '..M.pdf_string(l.p)
+ local s, p = l.s and '/S/'..l.s, l.p and ' /P '..pdf_string(l.p)
table.insert(res, string.format('%d << /St %d %s%s >>', l.start, l.st, s or '', p or ''))
end
table.insert(res, ']')
@@ -1083,7 +1043,7 @@ function M.add_default_rgb_output_intent(t)
N = 3, path = 'sRGB.icc' }
end
-function M.add_default_cmyk_output_intents(t)
+function M.add_default_cmyk_output_intent(t)
t = t or {}
M.add_output_intent {
subtype = t.subtype or 'GTS_PDFA1',
diff --git a/macros/luatex/generic/minim-pdf/minim-pdf.pdf b/macros/luatex/generic/minim-pdf/minim-pdf.pdf
index b460bdf4ed..414e577186 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 f8075ae6e4..630c374d41 100644
--- a/macros/luatex/generic/minim-pdf/minim-pdf.tex
+++ b/macros/luatex/generic/minim-pdf/minim-pdf.tex
@@ -5,6 +5,9 @@
\chardef\minimpdfloaded = \catcode`\:
\catcode`\: = 11
+\input minim-alloc
+\input minim-hooks
+
% Abbreviations used throughout this document:
% se structure element
% mci marked content item
@@ -19,9 +22,6 @@
% 1 the attributes
-\input minim-alloc
-\input minim-hooks
-
% By three attributes do we determine the document structure.
%
% One for marking the current structure element:
@@ -37,7 +37,8 @@
%
% One for marking the status:
% - assignments are generally local
-% - if set, disables tagging and content marking
+% - if set, disables tagging and content marking in pre_shipout
+% - values >10 indicate artifacts
\newattribute \tagging:current:status \tagging:current:status = \unset
%
% A fourth attribute keeps track of the current language
@@ -53,7 +54,7 @@
% disables marking structure elements
\newif\iftagging:enabled \tagging:enabledtrue
\protected\def\starttagging{\tagging:enabledtrue
- \ifnum1=\tagging:current:status
+ \ifnum0<\tagging:current:status
\tagging:current:status\unset\fi}
\protected\def\stoptagging{\tagging:enabledfalse
\ifnum\unset=\tagging:current:status
@@ -62,9 +63,12 @@
% \markartifact {Layout} {...}
% \startartifact {Pagination /Subtype/Header} ... \stopartifact
\long\def\markartifact#1#2{\startartifact{#1}#2\stopartifact}
-\protected\def\startartifact{\begingroup\stoptagging
+\protected\def\startartifact{\begingroup\tagging:enabledfalse
+ \tagging:current:status=\tagging:current:artifact
+ \global\advance\tagging:current:artifact1\relax
\tagging:mci:incr\tagging:art:markstart}
\protected\def\stopartifact{\tagging:art:markstop\tagging:mci:incr\endgroup}
+\newcount\tagging:current:artifact \tagging:current:artifact = 11
% \startcontentitem ... \stopcontentitem
\protected\def\startcontentitem{\iftagging:enabled\tagging:mci:incr\tagging:mci:markstart\fi}
@@ -223,20 +227,36 @@
\def\tagging:startdisplay\fi\fi\fi#1$${\tagging:makeformula{#1}{$$}}
\def\tagging:makeformula#1#2{\fi\fi\fi
\global\advance\tagging:formulanr1\relax
- \startelement\iftagging:indisplay block\else inline\fi{Formula}%
- \setalttext{\tagging:formulasource{#1}}%
- \setactualtext{\tagging:formulasource{#1}}%
- \ifnum3=\pdfaconformancelevel
- \embedfile mimetype text/x-tex
- relation Source desc {Equation source}
- name {\formulafilename.tex}
- string {\tagging:formulasource{#1}}\fi
+ \startelement attr Layout Placement \iftagging:indisplay/Block\else/Inline
+ % some html converters ignore Placement attributes
+ attr CSS-2.00 display (inline)\fi{Formula}%
+ \formulasource:set{#1}%
\scantextokens{#1}#2}
\def\tagging:formulasource#1{%
$\iftagging:indisplay$\fi
\unexpanded{#1}%
$\iftagging:indisplay$\fi}
+% \includeformulasources {none|actualtext|attachment|both}
+\def\includeformulasources#1{%
+ \expandafter\let
+ \expandafter\formulasource:set
+ \csname formulasource:#1\endcsname}
+\def\formulasource:none#1{}
+\def\formulasource:actualtext#1{%
+ \setactualtext{\tagging:formulasource{#1}}}
+\def\formulasource:attachment#1{%
+ \ifnum3=\pdfaconformancelevel
+ \embedfile mimetype text/x-tex
+ relation Source desc {Equation source}
+ name {\formulafilename.tex}
+ string {\tagging:formulasource{#1}}\fi}
+\def\formulasource:both#1{%
+ \formulasource:actualtext{#1}%
+ \formulasource:attachment{#1}}
+\includeformulasources{both}
+
+
% 1 hyperlinks
% provided by the lua module:
@@ -265,20 +285,6 @@
% provided by the lua module:
% \setdocumentlanguage {name or code}
% \setlanguagecode {name} {code}
-% \minim:apply:language:code{name} → index
-
-% patch in language codes to \uselanguage
-\ifcsname uselanguage@hook\endcsname
- \expandafter\let
- \expandafter\minim:uselanguagehook
- \lastnamedcs \fi
-\expandafter\edef\csname uselanguage@hook\endcsname#1{%
- % tagging support
- \noexpand\tagging:current:language
- \noexpand\minim:apply:language:code{#1}%
- % previous definitions
- \ifdefined\minim:uselanguagehook
- \noexpand\minim:uselanguagehook{#1}\fi}
% \newnamedlanguage {name} {lhm} {rhm}
\def\newnamedlanguage#1#2#3{%