diff options
author | Karl Berry <karl@freefriends.org> | 2021-06-11 17:59:01 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-06-11 17:59:01 +0000 |
commit | 254343ee44e44c9d66f46afdea93f9da5a1d9a6a (patch) | |
tree | 6d6937b7345dd630bef8a38cf1575ce965370937 /Master | |
parent | a9720158b7aef38d87261f3b46621aa7a992833b (diff) |
minim-xmp (10jun21)
git-svn-id: svn://tug.org/texlive/trunk@59553 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rw-r--r-- | Master/texmf-dist/doc/luatex/minim-xmp/EUPL-1.2-EN.txt | 287 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/minim-xmp/README | 30 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.doc | 196 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.pdf | bin | 0 -> 61507 bytes | |||
-rw-r--r-- | Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua | 583 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.tex | 34 | ||||
-rwxr-xr-x | Master/tlpkg/bin/tlpkg-ctan-check | 2 | ||||
-rwxr-xr-x | Master/tlpkg/libexec/ctan2tds | 1 | ||||
-rw-r--r-- | Master/tlpkg/tlpsrc/collection-luatex.tlpsrc | 1 | ||||
-rw-r--r-- | Master/tlpkg/tlpsrc/minim-xmp.tlpsrc | 0 |
10 files changed, 1133 insertions, 1 deletions
diff --git a/Master/texmf-dist/doc/luatex/minim-xmp/EUPL-1.2-EN.txt b/Master/texmf-dist/doc/luatex/minim-xmp/EUPL-1.2-EN.txt new file mode 100644 index 00000000000..4153cd37753 --- /dev/null +++ b/Master/texmf-dist/doc/luatex/minim-xmp/EUPL-1.2-EN.txt @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. diff --git a/Master/texmf-dist/doc/luatex/minim-xmp/README b/Master/texmf-dist/doc/luatex/minim-xmp/README new file mode 100644 index 00000000000..0383085ee2b --- /dev/null +++ b/Master/texmf-dist/doc/luatex/minim-xmp/README @@ -0,0 +1,30 @@ +Version: 2021/1.0 + +SUMMARY + +This package enables the inclusion of XMP (eXtensible Metadata Platform) data +in the pdf output generated by (plain) LuaTeX. The use of XMP is required by +PDF standards such as PDF/A. + +In-line definition of new metadata schemas is supported, as well as setting +and querying metadata values programmatically. For PDF/A documents, the +necessary PDF/A extension schema definitions will be generated and included +automatically. + + +DOCUMENTATION + +In order to obtain the typeset manual of this package, simply say: + + luatex minim-xmp.doc + + +COPYING + +(c) 2021 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: + +https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + diff --git a/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.doc b/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.doc new file mode 100644 index 00000000000..01d2084f6ce --- /dev/null +++ b/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.doc @@ -0,0 +1,196 @@ + +\input minim-doc.sty + +\manual{minim-xmp} + +\ifchapter \chapter Metadata + +\else \startmetadata + author {Esger Renkema} + title {minim-xmp} + date {2021-06-01} + version {2021/1.0} + keywords {LuaTeX; Plain TeX; XMP; metadata; PDF/A;} + stopmetadata +\maketitle \fi + +This package enables simple XMP (eXtensible Metadata Platform) packet inclusion +and will automatically generate pdf/a extension schemas. Use it by saying +⟦\input minim-xmp.tex⟧. +Thereafter, you can use ⟦*\setmetadata key {value}⟧ and ⟦*\getmetadata key⟧ for +setting and retrieving document-level metadata values. + +You do not need this package if you have your metadata ready-made in a separate +file, for then you can simply say +\startelement{Code} +\startsinglecontentitem$$\halign{#\hfil\cr +⟦\immediate\pdfextension obj uncompressed⟧ \cr +\qquad⟦stream attr {/Type/Metadata /Subtype/XML}⟧ \cr +\qquad⟦file {your-file.xmp}⟧ \cr +⟦\pdfextension catalog⟧ \cr +\qquad⟦{/Metadata \pdffeedback lastobj 0 R}⟧\cr +}$$\stopsinglecontentitem +\vskip-\belowdisplayskip + +\section Setting metadata + +Metadata fields that contain (ordered or unordered) lists will be split on the +⟦*\metadataseparator⟧ character; this is a semicolon by default. +Alternatively, you can just make multiple assignments: these will be +concatenated. + +Where applicable, language alternatives can be given like +⟦\setmetadata /de⟧ ⟦dc:title {...}⟧ or +⟦\setmetadata /{de_DE} dc:title {...}⟧. +Braces are necessary in the second case because the catcode of the underscore +is not 11~or~12. +When no alternative is given, the value ⟦*x-default⟧ will be used. + +Instead of using ⟦*\setmetadata⟧, multiple fields can be set in one go with +⟦*\startmetadata⟧. +This way is particularly useful when assigning structured data to a key (see +later on). +In this example, ⟦key1⟧ contains a normal value, ⟦key2⟧ language alternatives +and ⟦key3⟧ structured data: + +⟦\startmetadata + key1 {...} + key2 {... (default) ...} + /alt1 {...} + /alt2 {...} + key3 + /field1 {...} + /field2 {...} + stopmetadata⟧ + +Since metadata values are read by lua as text, linebreaks and paragraphs are +not preserved. You can work around this by saying +⟦{\def\par{\Uchar"A\Uchar"A} \setmetadata abstract {...}}⟧. + +\section Getting metadata + +Metadata values can be retrieved again with ⟦*\getmetadata key⟧. This command +is fully expandible. + +If the data is a list, it will be returned according to the current value of +⟦\metadataseparator⟧. +If the data has language alternatives, the ⟦x-default⟧ value will be returned: +the alternatives are accessible by ⟦*\getmetadata /lang key⟧. + +For structured types (discussed below), ⟦\getmetadata /field key⟧ will return +the value of a single field and ⟦\getmetadata key⟧ will return all fields as +⟦/{field1} {value1} /{field2} {value2} ...⟧ (this can be used again as input to +⟦\startmetadata⟧). + +\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. +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 +case-sensitive. + +Because the precise details of the above metadata namespaces can be confusing, +you might want use one of the aliases +⟦author⟧ (dc:creator), ⟦title⟧ (dc:title), ⟦date⟧ (dc:date and xmp:CreateDate), +⟦language⟧ (dc:language), ⟦keywords⟧ (dc:subject and pdf:Keywords), ⟦publisher⟧ +(dc:publisher), ⟦abstract⟧ (dc:description), ⟦copyright⟧ (dc:rights), ⟦version⟧ +(xmpMM:VersionID) and ⟦identifier⟧ (dc:identifier). +New aliases can be defined in the ⟦aliases⟧ table of the lua module. + + +\section Adding new keys and schemas + +\directlua{require('minim-xmp').add_namespace( + 'Example namespace', 'colours', + 'http://example.com/minim/colours/', { + Favourite = { 'Colour', 'The author’s favourite colour' }, + }, { + Colour = { 'RGB Colour', { + R = { 'Integer', 'Red component' }, + G = { 'Integer', 'Green component' }, + B = { 'Integer', 'Blue component' } + }, prefix = 'c' }, + })} +\startmetadata colours:Favourite /R 0 /G 0 /B 255 stopmetadata + +New metadata namespaces can be added in the following way: + +⟦require('minim-xmp').add_namespace( + 'Example namespace', 'colours', + 'http://example.com/minim/colours/', { + -- metadata keys + Favourite = { 'Colour', 'The author’s favourite colour' }, + }, { + -- value types + Colour = { 'RGB Colour', { + R = { 'Integer', 'Red component' }, + G = { 'Integer', 'Green component' }, + B = { 'Integer', 'Blue component' } + }, prefix = 'c' }, + })⟧ + +This will setup a namespace with prefix ⟦colours⟧ and one key: ⟦Favourite⟧, of +type ⟦Colour⟧. That value type happens to be a structured type with three +fields, which are also described. +You can now use this namespace as + +⟦\startmetadata colours:Favourite /R 0 /G 0 /B 255 stopmetadata⟧ + +or the equivalent but more verbose + +⟦\setmetadata/R colours:Favourite 0 +\setmetadata/G colours:Favourite 0 +\setmetadata/B colours:Favourite 255⟧ + +after which the generated XMP code will be + +⟦ <rdf:Description rdf:about="" + xmlns:colours="http://example.com/minim/colours/" + xmlns:c="http://example.com/minim/colours/"> + <colours:Favourite rdf:parseType="Resource"> + <c:B>255</c:B> + <c:G>0</c:G> + <c:R>0</c:R> + </colours:Favourite> + </rdf:Description>⟧ + +You probably will not need defining your own value types, +so in most cases you should omit the fifth argument to ⟦add_namespace⟧. +If you do define a new value type, you can specify its prefix if it differs +from the namespace prefix (as is done above) and likewise its ⟦uri⟧ identifier +if it differs from the namespace URI. + +List types can be given as ⟦'Bag TypeName'⟧ or ⟦'Seq TypeName'⟧; language +alternatives as ⟦'Lang Alt'⟧. All other types will be treated as ⟦'Text'⟧, +though for ⟦'Boolean'⟧, ⟦'Integer'⟧ and ⟦'Date'⟧ some validation is performed +when setting values. + +Additional metadata value type handling can be defined in the ⟦getters⟧ and +⟦setters⟧ tables of the lua module. +Appropriate entries to those tables will be generated automatically for new +structured types (which is why you could set the colour like we did above). +Value types without fields, however, will be stored and retrieved as if they +were ⟦Text⟧ until you provide another way. + + +\section Generated XMP + +All metadata will be written to the PDF file uncompressed. + +The ⟦*\metadatamodification⟧ setting controls whether XMP packets will be +marked as read-only (value~0; default) or writeable (value~1). +Writeable XMP packets will be padded with about 2\thinspace kB of whitespace. + +If the document-level metadata contains values in the ⟦pdfaid⟧ namespace, +metadata extension schemas will be appended to the document-level metadata +packet automatically. +These extension schema’s will only include keys that have been set somewhere, +though they need not have been set in the document-level metadata. +No extension schemas are generated for the built-in namespaces, as they are +already included in the pdf/a standards. + + +\endmanual + diff --git a/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.pdf b/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.pdf Binary files differnew file mode 100644 index 00000000000..fdf048a8575 --- /dev/null +++ b/Master/texmf-dist/doc/luatex/minim-xmp/minim-xmp.pdf diff --git a/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua new file mode 100644 index 00000000000..ba01344ccd5 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.lua @@ -0,0 +1,583 @@ + +local M = { } +local alloc = require('minim-alloc') +alloc.remember('minim-xmp') + +local function sorted_pairs(t) + local keys, i = { }, 0 + for k, _ in pairs(t) do table.insert(keys, k) end + table.sort(keys) + return function() + i = i + 1 + local k = keys[i] + if k then + return k, t[k] + end + end +end + +-- 1 Reading and writing metadata values + +M.getters = { + Text = function(v) return v or '' end, + ['Lang Alt'] = function(t, a) return t[a or 'x-default'] or '' end, +} + +M.setters = { + Text = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return new + end, + ['Lang Alt'] = function(value, orig, lang) + orig = orig or { } + orig[lang or 'x-default'] = value + return orig + end, + Integer = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return tonumber(new) and new or alloc.err('Not a number: %s', new) + end, + Date = function(new, orig, opt, k) + if opt then alloc.msg('Warning: no alternative for %s', k) end + if orig then alloc.msg('Warning: overwriting metadata field %s', k) end + return string.match(new, '^%d%d%d%d') + and new or alloc.err('Date must start with year') + end, + Boolean = function(s) + return s == 'True' + or s == 'False' + and s or alloc.err('Boolean value must be True or False') + end, +} + +-- 1 Namespace definitions + +local namespace_info = alloc.saved_table ('metadata namespace info') + +function M.add_key(prefix, name, t) + namespace_info[prefix].keys[name] = { + type = t.type or t[1], + description = t.description or t[2], + isInternal = t.isInternal or t[3] or false, + inUse = false, + } +end + +function M.add_valuetype(prefix, name, t) + local ns = namespace_info[prefix] + ns.valuetypes = ns.valuetypes or { } + ns.valuetypes[name] = { + description = t.description or t[1], + uri = t.uri or ns.uri, + prefix = t.prefix or ns.prefix, + inUse = false, + } + t.fields = t.fields or t[2] + if t.fields then + local fields = { } + ns.valuetypes[name].fields = fields + for n, f in pairs(t.fields) do + fields[n] = { type = f.type or f[1], description = f.description or f[2] } + end + M.getters[name] = function(t, k) + if k then + return (fields[k] or alloc.err('Unknown field ‘%s’', k)) + and t and t[k] or 'not found' + else + local res = { } + for k, v in sorted_pairs(t) do + table.insert(res, string.format('/{%s} {%s}', k, v)) + end + return table.concat(res, ' ') + end + end + M.setters[name] = function(value, orig, key, k) + if not key then + alloc.err('Missing key for metadata field %s', k) + elseif not fields[key] then + alloc.err('Unknown field ‘%s’', key) + else + orig = orig or { } + local setter = M.setters[fields[key].type] or M.setters.Text + orig[key] = setter(value, orig[key], nil, k) + end + return orig + end + end +end + +function M.add_namespace(description, prefix, uri, keys, valuetypes, predefined) + namespace_info[prefix] = { + uri = uri, + description = description, + keys = { }, + predefined = predefined or false, + inUse = false, + } + for name, t in pairs(keys) do + M.add_key(prefix, name, t) + end + for name, t in pairs(valuetypes or { }) do + M.add_valuetype(prefix, name, t) + end +end + +local function get_resource_type(ns, vtype) + local vtypes = namespace_info[ns].valuetypes + return vtypes and vtypes[vtype] and vtypes[vtype].fields + and vtypes[vtype] or false +end + +local function parse_type_name(vtype) + local c = string.sub(vtype, 1, 4) + local t = string.sub(vtype, 5) + if c == 'Bag ' then + return t, 'Bag' + elseif c == 'Seq ' then + return t, 'Seq' + else + return vtype, false + end +end + +-- 1 Built-in namespaces + +M.add_namespace('Adobe PDF', 'pdf', 'http://ns.adobe.com/pdf/1.3/', { + Keywords = { 'Text', 'Keywords' }, + PDFVersion = { 'Text', 'PDF version ', true}, + Producer = { 'AgentName', 'Creator tool', true }, +}, nil, true) + +M.add_namespace('PDF/A Identification', 'pdfaid', 'http://www.aiim.org/pdfa/ns/id/', { + amd = { 'Text', 'Amendment identifier', true }, + corr = { 'Text', 'Corrigendum identifier', true }, + conformance = { 'Text', 'Conformance level', true }, + part = { 'Integer', 'Version identifier', true }, +}, nil, true) + +M.add_namespace('Dublin Core', 'dc', 'http://purl.org/dc/elements/1.1/', { + contributor = { 'Bag ProperName', 'Other contributors' }, + coverage = { 'Text', 'Extent or scope' }, + creator = { 'Seq ProperName', 'Authors' }, + date = { 'Seq Date', 'Relevant dates' }, + description = { 'Lang Alt', 'Description of contents' }, + format = { 'MIMEType', 'File format (application/pdf)', true }, + identifier = { 'Text', 'Unique identifier' }, + language = { 'Bag Locale', 'Used languages', true }, + publisher = { 'Bag ProperName', 'Publishers' }, + relation = { 'Bag Text', 'Relationships to other documents', true }, + rights = { 'Lang Alt', 'Copyright statement' }, + source = { 'Text', 'Document source unique identifier' }, + subject = { 'Bag Text', 'Keywords' }, + title = { 'Lang Alt', 'Title' }, + type = { 'Bag Text', 'Document type' }, +}, nil, true) + +M.add_namespace('XMP Basic', 'xmp', 'http://ns.adobe.com/xap/1.0/', { + BaseUrl = { 'URL', 'Base for relative URLs in the document', true }, + CreateDate = { 'Date', 'Creation date' }, + CreatorTool = { 'AgentName', 'Creator tool', true }, + Identifier = { 'Bag Text', 'Multiple identifiers' }, + MetadataDate = { 'Date', 'Metadata modification date', true }, + ModifyDate = { 'Date', 'Document modification date', true }, + Nickname = { 'Text', 'Informal name' }, +}, nil, true) + +M.add_namespace('XMP Rights Management', 'xmpRights', 'http://ns.adobe.com/xap/1.0/rights/', { + Certificate = { 'URL', 'Online rights management cerfificate' }, + Marked = { 'Boolean', 'Indicates this is a rights-managed resource' }, + Owner = { 'Bag ProperName', 'Right holders' }, + UsageTerms = { 'Lang Alt', 'Instructions on usage' }, + WebStatement = { 'URL', 'Location of rights statement' }, +}, nil, true) + +M.add_namespace('XMP Media Managament', 'xmpMM', 'http://ns.adobe.com/xap/1.0/mm/', { + DerivedFrom = { 'ResourceRef', 'Reference to original document', true }, + DocumentID = { 'URI', 'UUID-based identifier across versions', true }, + History = { 'Seq ResourceEvent', 'Human-readable history of changes', true }, + ManagedFrom = { 'ResourceRef', 'Reference to unmanaged document', true }, + Manager = { 'AgentName', 'Asset management system', true }, + ManagerVariant = { 'Text', 'Particular variant', true }, + ManageTo = { 'URI', 'Managed resource identifier', true }, + ManageUI = { 'URI', 'Web browser interface', true }, + RenditionClass = { 'RenditionClass', 'Rendition class name', true }, + RenditionParams = { 'Text', 'Additional rendition parameters', true }, + VersionID = { 'Text', 'Document version identifier', true }, + Versions = { 'Seq Version', 'Version history', true }, +}, { + ResourceEvent = { 'Reference to a resource', { + action = { 'Text', 'Action that occurred' }, + instanceID = { 'URI', 'Instance ID of modifier resource' }, + parameters = { 'Text', 'Additional description' }, + softwareAgent = { 'AgentName', 'Software agent performing the action' }, + when = { 'Date', 'Optional timestamp of the action' }, + }, prefix = 'stEvt', uri = 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#' }, + ResourceRef = { 'Reference to a resource', { + instanceID = { 'URI', 'referenced xmpMM:instanceID' }, + documentID = { 'URI', 'referenced xmpMM:documentID' }, + versionID = { 'Text', 'referenced xmpMM:versionID' }, + renditionClass = { 'RenditionClass', 'referenced xmpMM:renditionClass' }, + renditionParams = { 'Text', 'referenced xmpMM:renditionParams' }, + manager = { 'AgentName', 'referenced xmpMM:manager' }, + managerVariant = { 'Text', 'referenced xmpMM:managerVariant' }, + manageTo = { 'URI', 'referenced xmpMM:manageTo' }, + manageUI = { 'URI', 'referenced xmpMM:manageUI' }, + }, prefix = 'stRef', uri = 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#' }, + Version = { 'Document version', { + comments = { 'Text', 'Description of changes' }, + event = { 'Text', 'Description of changes' }, + modifyDate = { 'Date', 'Check-in date' }, + modifier = { 'ProperName', 'Person making the modifications' }, + version = { 'Text', 'New version number' }, + }, prefix = 'stVer', uri = 'http://ns.adobe.com/xap/1.0/sType/Version#' }, +}, true) + +-- 1 Writing metadata packets + +local XMP = alloc.saved_table('metadata values') +XMP[1] = XMP[1] or { dc = { format = 'application/pdf' } } -- document-level metadata + +function M.push_metadata() + XMP[#XMP+1] = { } +end + +local function get_xmp() + return XMP[#XMP] +end + +local function escape_xml_content(s) + -- NB not to be used for attribute values + s = string.gsub(s, '&', '&') + return string.gsub(s, '<', '<') +end + +-- 2 extension schemas + +local function get_metadata_extensions() + local namespaces, anyInUse = { }, false + for prefix, ns in pairs(namespace_info) do + if not ns.predefined and ns.inUse then + namespaces[prefix] = ns + anyInUse = true + end + end + if not anyInUse then return false end + local rv = { [[ <rdf:Description rdf:about="" + xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" + xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" + xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" + xmlns:pdfaType="http://www.aiim.org/pdfa/ns/type#" + xmlns:pdfaField="http://www.aiim.org/pdfa/ns/field#" > + <pdfaExtension:schemas> + <rdf:Bag>]] } + local add = function (...) table.insert(rv, string.format(...)) end + for prefix, ns in sorted_pairs(namespaces) do + add(' <rdf:li rdf:parseType="Resource">') + add(' <pdfaSchema:schema>%s</pdfaSchema:schema>', ns.description) + add(' <pdfaSchema:namespaceURI>%s</pdfaSchema:namespaceURI>', ns.uri) + add(' <pdfaSchema:prefix>%s</pdfaSchema:prefix>', prefix) + table.insert(rv, ' <pdfaSchema:property>\n <rdf:Seq>') + for name, key in sorted_pairs(ns.keys) do if key.inUse then + add(' <rdf:li rdf:parseType="Resource">') + add(' <pdfaProperty:name>%s</pdfaProperty:name>', name) + add(' <pdfaProperty:valueType>%s</pdfaProperty:valueType>', key.type) + add(' <pdfaProperty:category>%s</pdfaProperty:category>', key.isInternal and 'internal' or 'external') + add(' <pdfaProperty:description>%s</pdfaProperty:description>', key.description) + add(' </rdf:li>') + end end + add(' </rdf:Seq>\n </pdfaSchema:property>') + if ns.valuetypes then + add(' <pdfaSchema:valueType>\n <rdf:Seq>') + for name, vtype in sorted_pairs(ns.valuetypes) do + if vtype.inUse then + add(' <rdf:li rdf:parseType="Resource">') + add(' <pdfaType:type>%s</pdfaType:type>', name) + if vtype.fields then + add(' <pdfaType:namespaceURI>%s</pdfaType:namespaceURI>', vtype.uri) + add(' <pdfaType:prefix>%s</pdfaType:prefix>', vtype.prefix) + end + add(' <pdfaType:description>%s</pdfaType:description>', vtype.description) + if vtype.fields then + add(' <pdfaType:field>\n <rdf:Seq>') + for name, field in sorted_pairs(vtype.fields) do + add(' <rdf:li rdf:parseType="Resource">') + add(' <pdfaField:name>%s</pdfaField:name>', name) + add(' <pdfaField:valueType>%s</pdfaField:valueType>', field.type) + add(' <pdfaField:description>%s</pdfaField:description>', field.description) + add(' </rdf:li>') + end + add(' </rdf:Seq>\n </pdfaType:field>') + end + add(' </rdf:li>') + end + end + add(' </rdf:Seq>\n </pdfaSchema:valueType>') + end + add(' </rdf:li>') + end + add(' </rdf:Bag>\n </pdfaExtension:schemas>\n </rdf:Description>') + return table.concat(rv, '\n') +end + +-- 2 main xmp packet + +local function write_fields(add, res, value, pre, name, indent) + indent = indent or '' + if res then + add('%s <%s:%s rdf:parseType="Resource">', indent, pre, name) + for k, _ in sorted_pairs(res.fields) do + if value[k] then + add('%s <%s:%s>%s</%s:%s>', indent, res.prefix, k, value[k], res.prefix, k) + end + end + add('%s </%s:%s>', indent, pre, name) + else + add('%s <%s:%s>%s</%s:%s>', indent, pre, name, escape_xml_content(value), pre, name) + end +end + +local function make_xmp_packet(xmp) + local rv = { } ; local add = function(...) table.insert(rv, string.format(...)) end + -- wrapper and opening tags + add('<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>') + add('<x:xmpmeta xmlns:x="adobe:ns:meta/">') + add('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">') + -- namespaces + for id, keys in sorted_pairs(xmp) do + local info = namespace_info[id] + -- xml namespaces in enclosing description element + add(' <rdf:Description rdf:about="" xmlns:%s="%s"', id, info.uri) + local xmlns_uris = { } + for _, vt in pairs(info.valuetypes or {}) do + if vt.fields and vt.inUse + and (vt.prefix ~= id or vt.uri ~= info.uri) then + xmlns_uris[vt.prefix] = vt.uri + end + end + for pre, uri in pairs(xmlns_uris) do + add(' xmlns:%s="%s"', pre, uri) + end + rv[#rv] = rv[#rv] .. '>' + -- metadata namespace contents + for key, value in sorted_pairs(keys) do + local vtype, lst = parse_type_name(info.keys[key].type) + local res = get_resource_type(id, vtype) + -- list values + if lst then + add(' <%s:%s>\n <rdf:%s>', id, key, lst) + for _, v in ipairs(value) do + write_fields(add, res, v, 'rdf', 'li', ' ') + end + add(' </rdf:%s>\n </%s:%s>', lst, id, key) + -- language alternatives + elseif vtype == 'Lang Alt' then + add(' <%s:%s>\n <rdf:Alt>', id, key) + for lang, v in sorted_pairs(value) do + add(' <rdf:li xml:lang="%s">%s</rdf:li>', lang, escape_xml_content(v)) + end + add(' </rdf:Alt>\n </%s:%s>', id, key) + -- simple values + else + if lst then value = value[1] end + write_fields(add, res, value, id, key) + end + end + add(' </rdf:Description>') + end + -- extension schemas + if #XMP == 1 and xmp.pdfaid then + local schemas = get_metadata_extensions() + if schemas then add(schemas) end + end + -- closing tags and wrapper + local editable = tex.count['metadatamodification'] > 0 + add('</rdf:RDF>\n</x:xmpmeta>') + if editable then add(string.rep(string.rep(' ', 80), 25, '\n')) end + add('<?xpacket end="%s"?>', editable and 'w' or 'r') + return table.concat(rv, '\n') +end + +-- 2 + +function M.write_metadata() + local xmp = make_xmp_packet(XMP[#XMP]) + local nr = pdf.obj { + type = 'stream', + attr = '/Type/Metadata /Subtype/XML', + immediate = true, + compresslevel = 0, + string = xmp, + } + XMP[#XMP] = nil + return nr +end + +-- 1 Getting and setting metadata values + +local function get_metadata_info(fullkey) + local id, k = string.match(fullkey, '([^:]+):(.+)') + if not id then return alloc.err('Malformed metadata key %s', fullkey) end + local ns = namespace_info[id] + if not ns then return alloc.err('Unknown namespace %s', id) end + local key = ns.keys[k] + if not key then return alloc.err('Unknown key %s in namespace %s', k, id) end + local vtype, lst = parse_type_name(key.type) + ns.inUse, key.inUse = true, true + if ns.valuetypes and ns.valuetypes[vtype] then ns.valuetypes[vtype].inUse = true end + return id, k, lst, vtype +end + +local function get_separator() + return string.utfcharacter(tex.count['metadataseparator']) +end + +function M.get_metadata(k, a) + local xmp = get_xmp() + local ns, key, lst, vtype = get_metadata_info(k) + local getter = M.getters[vtype] or M.getters.Text + if not ns or not xmp[ns] then + return '' + elseif lst then + local rv = { } + for _, v in ipairs(xmp[ns][key]) do + table.insert(rv, getter(v, a)) + end + return table.concat(rv, get_separator()) + else + return getter(xmp[ns][key], a) + end +end + +local function split_metadata_input(s) + local rv = string.explode(s, get_separator()) + for i = 1, #rv do + rv[i] = string.gsub(rv[i], '^ *', '') + rv[i] = string.gsub(rv[i], ' *$', '') + end + return rv +end + +function M.set_metadata(k, v, o) + local ns, key, lst, vtype = get_metadata_info(k) + if not ns then return end -- an error has already occurred + local xmp = get_xmp() + xmp[ns] = xmp[ns] or { } + local setter = M.setters[vtype] or M.setters.Text + if not v or v == '' then + xmp[ns][key] = nil + elseif lst then + local l = xmp[ns][key] or { } + xmp[ns][key] = l + local nst = namespace_info[ns].valuetypes + local f = nst and nst[vtype] and nst[vtype].fields + if o and #l>0 and f and f[o] and not l[#l][o] then + -- special case: add to property type + l[#l] = setter(v, l[#l], o, l) + else + for _, val in ipairs(split_metadata_input(v)) do + table.insert(l, setter(val, nil, o, k)) + end + end + else + xmp[ns][key] = setter(v, xmp[ns][key], o, k) + end +end + +-- 1 Callbacks and tex interface + +M.aliases = { + author = { 'dc:creator' }, + title = { 'dc:title' }, + date = { 'dc:date', 'xmp:CreateDate', 'xmp:ModifyDate' }, + language = { 'dc:language' }, + keywords = { 'dc:subject', + also = function(values) + local kw = M.get_metadata('pdf:Keywords') + if kw ~= '' then kw = kw..', ' end + M.set_metadata('pdf:Keywords') -- prevent warning + M.set_metadata('pdf:Keywords', kw..table.concat(split_metadata_input(values), ', ')) + end }, + publisher = { 'dc:publisher' }, + abstract = { 'dc:description' }, + copyright = { 'dc:rights' }, + identifier = { 'dc:identifier' }, + version = { 'xmpMM:VersionID' }, +} + +callback.register('finish_pdffile', function() + if #XMP > 1 then + alloc.err('Not all metadata has been written out.') + XMP = { [1] = XMP[1] } + elseif #XMP > 0 then + local metadata_obj = M.write_metadata() + local catalog = pdf.getcatalog() or '' + pdf.setcatalog(catalog..string.format('/Metadata %s 0 R', metadata_obj)) + end +end) + +alloc.luadef('getmetadata', function() + local option = token.scan_keyword('/') and token.scan_string() + local keyname = token.scan_word() + local alias = M.aliases[keyname] + local key = alias and alias[1] or keyname + tex.sprint(M.get_metadata(key, option)) +end) + +local function set_aliased_metadata(keyname, value, option) + local keys = M.aliases[keyname] or { keyname } + for _, key in ipairs(keys) do + M.set_metadata(key, value, option) + end + if keys.also then keys.also(value, option) end +end + +alloc.luadef('setmetadata', function() + local option = token.scan_keyword('/') and token.scan_string() + local keyname = token.scan_word() + local value = token.scan_string() + set_aliased_metadata(keyname, value, option) +end, 'protected') + +alloc.luadef('startmetadata', function() + local key = token.scan_word() + local opt = token.scan_keyword('/') and token.scan_string() + repeat + repeat + set_aliased_metadata(key, token.scan_string(), opt) + opt = token.scan_keyword('/') and token.scan_string() + until not opt + key = token.scan_word() + opt = token.scan_keyword('/') and token.scan_string() + until key == 'stopmetadata' +end) + +alloc.luadef('newmetadata', function() + M.push_metadata() +end, 'protected') + +alloc.luadef('writemetadata', function() + tex.sprint(M.write_metadata()) +end) + +alloc.luadef('includemetadata', function() + local file = kpse.find_file(token.scan_string()) + if not file then + alloc.err('Metadata file not found'); + tex.sprint(0); + else + tex.sprint( pdf.obj { + type = 'stream', + attr = '/Type/Metadata /Subtype/XML', + immediate = true, + compresslevel = 0, + file = file, + }) + end +end) + +-- + +return M + diff --git a/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.tex b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.tex new file mode 100644 index 00000000000..2ec4adc8958 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/minim-xmp/minim-xmp.tex @@ -0,0 +1,34 @@ + +\ifdefined\minimxmploaded + \message{(skipped)} + \expandafter\endinput\fi +\chardef\minimxmploaded=\catcode`\: +\catcode`\:=11 + +\input minim-alloc +\directlua { require('minim-xmp') } + +% do not add pdf metadata by default +\pdfvariable suppressoptionalinfo = 255 + +% \metadatamodification +\newcount \metadatamodification +\metadatamodification = 0 + +% \metadataseparator +\newcount \metadataseparator +\metadataseparator = `\; + +% Defined from the lua side: +% +% \getmetadata ns:key +% \setmetadata ns:key {value} +% \startmetadata +% +% idem, but undocumented as of yet: +% \newmetadata +% \writemetadata (expands to pdf object number) +% \includemetadata{filename} (expands to pdf object number) + +\catcode`\:=\minimxmploaded + diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check index aa48c32c386..cd0cbaa12dc 100755 --- a/Master/tlpkg/bin/tlpkg-ctan-check +++ b/Master/tlpkg/bin/tlpkg-ctan-check @@ -516,7 +516,7 @@ my @TLP_working = qw( mhequ miama mi-solns microtype microtype-de midnight midpage miller milog milsymb mindflow minibox minidocument minifp - minim minim-hatching minim-math minim-mp minim-pdf + minim minim-hatching minim-math minim-mp minim-pdf minim-xmp minimalist minipage-marginpar miniplot minitoc minorrevision minted mintspirit minutes mismath missaali diff --git a/Master/tlpkg/libexec/ctan2tds b/Master/tlpkg/libexec/ctan2tds index d9ff1c0e8bd..a3d514dea9c 100755 --- a/Master/tlpkg/libexec/ctan2tds +++ b/Master/tlpkg/libexec/ctan2tds @@ -2084,6 +2084,7 @@ $standardtex 'minim-math', '\.(lua|tex)$', 'minim-mp', '\.(lua|tex|ini)$', 'minim-pdf', '\.(lua|tex)$', + 'minim-xmp', '\.(lua|tex)$', 'minitoc', '\.ml[do]|' . $standardtex, 'misc', '\.sty|\.ltx|\.cls', 'mkpattern', 'mkpatter.tex', diff --git a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc index 1b3481560a7..df19437c63a 100644 --- a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc +++ b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc @@ -55,6 +55,7 @@ depend minim depend minim-math depend minim-mp depend minim-pdf +depend minim-xmp depend newpax depend nodetree depend odsfile diff --git a/Master/tlpkg/tlpsrc/minim-xmp.tlpsrc b/Master/tlpkg/tlpsrc/minim-xmp.tlpsrc new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/Master/tlpkg/tlpsrc/minim-xmp.tlpsrc |