diff options
author | Norbert Preining <norbert@preining.info> | 2022-03-04 03:01:04 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-03-04 03:01:04 +0000 |
commit | 575ab7d0e7534edd510bbc89a2ef4b04582af591 (patch) | |
tree | 9fc4cf55b7ca8b168bdfd747be21de4c9ef2b30e /macros/luatex/generic/minim-pdf | |
parent | 6b9ed20cb01cb52c7aaf2a48202f5d8aa588ff79 (diff) |
CTAN sync 202203040301
Diffstat (limited to 'macros/luatex/generic/minim-pdf')
-rw-r--r-- | macros/luatex/generic/minim-pdf/README | 27 | ||||
-rw-r--r-- | macros/luatex/generic/minim-pdf/minim-languagecodes.lua | 8 | ||||
-rw-r--r-- | macros/luatex/generic/minim-pdf/minim-pdf.doc | 86 | ||||
-rw-r--r-- | macros/luatex/generic/minim-pdf/minim-pdf.lua | 294 | ||||
-rw-r--r-- | macros/luatex/generic/minim-pdf/minim-pdf.pdf | bin | 108362 -> 110458 bytes | |||
-rw-r--r-- | macros/luatex/generic/minim-pdf/minim-pdf.tex | 62 |
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 Binary files differindex b460bdf4ed..414e577186 100644 --- a/macros/luatex/generic/minim-pdf/minim-pdf.pdf +++ b/macros/luatex/generic/minim-pdf/minim-pdf.pdf 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{% |