% \iffalse meta-comment % %% File: l3pdfmeta.dtx % % Copyright (C) 2018-2024 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % http://www.latex-project.org/lppl.txt % % This file is part of the "LaTeX PDF management testphase bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/pdfresources % % for those people who are interested. % %<*driver> \DocumentMetadata{pdfstandard=A-2b} \documentclass[full]{l3doc} \usepackage{array,booktabs} \hypersetup{pdfauthor=The LaTeX Project,pdftitle=l3pdfmeta (LaTeX PDF management testphase bundle)} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3pdfmeta} module\\ PDF standards ^^A % \\ % \LaTeX{} PDF management testphase bundle % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Version 0.96n, released 2024-10-27} % % \maketitle % \begin{documentation} % % \section{\pkg{l3pdfmeta} documentation} % This module sets up some tools and commands needed % for PDF standards in general. % The goal is to collect the requirements and to provide code to check and fulfill them. % % % \subsection{Verifying requirements of PDF standards} % % Standards like pdf/A set requirements on a PDF: Some things have be in the PDF, % e.g. the catalog has to contain a /Lang entry and an colorprofile and % an /OutputIntent, some other things are forbidden or restricted, e.g. % the action dictionary of an annotation should not contain Javascript. % % The \pkg{l3pdfmeta} module collects a number of relevant requirements, % tries to enforce the ones which can be enforced and offers some tools % for package authors to test if an action is allowed in the standard or not. % % This is work in progress and more tests will be added. But it should be noted % that it will probably never be possible to prevent all forbidden actions % or enforce all required ones or even to simply check all of them. % The commands here don't replace a check with an external validator. % % Verifying against a PDF-standard involves two different task: % % \begin{itemize} % \item Check if you are allowed to ignore the requirement. % \item Decide which action to take if the answer to the first question is NO. % \end{itemize} % % The following conditionals address the first task. Because of the second task % a return value |FALSE| means that the standard requires you to do some % special action. |TRUE| means that you can ignore this % requirement.\footnote{One could also make the logic the % other way round---there are arguments for both---but I had to decide.} % % In most cases it only matters if a requirement is in the standard, % for example |Catalog_no_OCProperties| means \enquote{don't use |/OCProperties| % in the catalog}. For a small number of requirements it is also needed to % test a user value against a standard value. For example, |named_actions| % restricts the allowed named actions in an annotation of subtype |/Named|, % in this case it is needed to check not only if the requirement is % in the standard but also if the user value is in the allowed list. % % \begin{function}[EXP,pTF]{\pdfmeta_standard_verify:n} % \begin{syntax} % \cs{pdfmeta_standard_verify:n}\Arg{requirement} % \end{syntax} % % This checks if \meta{requirement} is listed in the standard. % |FALSE| as result means that the requirement is in the standard and % that probably some special action is required---which one depends % on the requirement, see the descriptions below. % |TRUE| means that the requirement is not there and so no special % action is needed. % This check can be used for simple requirements where neither % a user nor a standard value is of importance. % \end{function} % % \begin{function}[TF]{\pdfmeta_standard_verify:nn} % \begin{syntax} % \cs{pdfmeta_standard_verify:nn}\Arg{requirement}\Arg{value} % \end{syntax} % % This checks if \meta{requirement} is listed in the standard, % if yes it tries to find a predefined test handler for % the requirement and passes \meta{value} and the value recorded % in the standard to it. The handler returns |FALSE| if some special % action is needed (e.g. if \meta{value} violates the rule) % and |TRUE| if no special action is needed. If no handler exists % this commands works like \cs{pdfmeta_standard_verify:n}. % \end{function} % % In some cases one needs to query the value in the standard, % e.g. to correct a wrong minimal PDF version you need to know % which version is required by |min_pdf_version|. % For this two commands to access the value are provided: % % \begin{function}[EXP]{\pdfmeta_standard_item:n} % \begin{syntax} % \cs{pdfmeta_standard_item:n}\Arg{requirement} % \end{syntax} % This retrieves the value of \meta{requirement} and leaves it in the input. % If the requirement isn't in the standard the result is empty, % that means that requirements not in the standard and % requirement without values can not be distinguished here. % \end{function} % % % \begin{function}{\pdfmeta_standard_get:nN} % \begin{syntax} % \cs{pdfmeta_standard_get:nN}\Arg{requirement} \meta{tl var} % \end{syntax} % This retrieves the value of \meta{requirement} and stores % it in the \meta{token list variable}. % If the \meta{requirement} is not found the special % value |\q_no_value| is used. % The \meta{token list variable} is assigned locally. % \end{function} % % % The following describe the requirements which can be currently tested. % Requirements with a value should use \cs{pdfmeta_standard_verify:nn} % or \cs{pdfmeta_standard_verify:nnN} to test a local value against the standard. % The rule numbers refer to \url{https://docs.verapdf.org/validation/pdfa-part1/} % % \subsubsection{Simple tests without handler} % % \begin{description} % % \item[|outputintent_A|] requires to embed a color profile and % reference it in a /Outputintent and that all output intents reference % the same colorprofile. The value stores the subtype. % {\em This requirement is detected and fulfilled by \pkg{l3pdfmeta} if the % provided interface in \cs{DocumentMetadata} is used, see below}. % % \item[|annot_flags|] in annotations the |Print| flag should be true, % |Hidden|, |Invisible|, |NoView| should be false. % {\em This requirement is detected and set by \pkg{l3pdfmeta} for annotations % created with the \pkg{l3pdfannot}. % A new check is only needed if the flags are changed % or if links are created by other means.} % % \item[|no_encryption|] don't encrypt % \item[|no_external_content|] no |/F|, |/FFilter|, or |/FDecodeParms| % in stream dictionaries % \item[|no_embed_content|] no |/EF| key in filespec, no |/Type/EmbeddedFiles|. % \emph{This will be checked in future by \pkg{l3pdffiles} % for the files it embeds.} % The restrictment is set for only PDF/A-1b. % PDF/A-2b and PDF/A3-b lifted this restriction: PDF/A-2b allows % to embed other PDF documents conforming to either PDF/A-1 or PDF/A-2, % and PDF/A-3 allows any embedded files. I don't see a way to test the % PDF/A-2b requirement so currently it will simply allow everything. Perhaps % a test for at least the PDF-format will be added in future. % \item[|Catalog_no_OCProperties|] don't add |/OCProperties| to the catalog % {\em l3pdfmeta removes this entry at the end of the document} % \item[|Catalog_OCProperties_no_AS|] % do not use |/AS| optional content configuration dictionary. % \item[|Catalog_EmbeddedFiles|] ensure that an |EmbeddedFiles| name tree is % in the catalog. This is required for PDF/A-4f. % \item[|annot_widget_no_AA|] (rule 6.6.2-1) % no AA dictionary in widget annotation, % this will e.g. be checked by the new hyperref driver. % \item[|annot_widget_no_A_AA|] (rule 6.9-2) no A and AA dictionary in widget. % \item[|form_no_AA|] (6.9-3) no /AA dictionary in form field % \item[|unicode|] that is set in the U-standards, A-2u and A-3u and means that % every text should be in unicode. This is not something that can be enforced or % tested from TeX, but in a current LaTeX normally ToUnicode are set for all fonts. % \item[|tagged|] that is set in A-2a and A-3a and means that the pdf must be % tagged. This is currently neither tested not enforced somewhere. % \item[|no_CharSet|] CharSet is deprecated is pdf 2.0 and should not % be used in A-4. l3pdfmeta will therefore suppress it for the % engines pdftex and luatex (the other engines have no suitable option) % \item[|omit_CID|] This avoids with PDF/A-2 and newer a failure % because of with missing CID identifications % (e.g. from rule ISO 19005-2:2011, Clause: 6.2.11.4.2) % It has only with luatex an effect. % \item[|Trailer_no_Info|] The \texttt{Info} dictionary % has been deprecated since quite some time. Metadata should be set with % XMP-data instead. In PDF A-4 now the \texttt{Info} dictionary % shall not be present in the trailer dictionary at all % (unless there exists a PieceInfo entry in the Catalog). And if it is present % it should only contain the \texttt{/ModDate} entry. In % texlive 2023 the engines pdftex and luatex have primitives % to suppress the dictionary % and l3pdfmeta will make use of it. % \end{description} % % \subsubsection{Tests with values and special handlers} % % \begin{description} % % \item[|min_pdf_version|] stores the minimal PDF version needed for % a standard. % It should be checked against the current PDF version (\cs{pdf_version:}). % A failure means that the version should be changed. % Currently there is only one hard requirement which leads to a failure in % a validator like verapdf: The A-4 standard should use PDF 2.0. % As PDF A-1 is based on PDF 1.4 and PDF A-2 and A-3 are based % on PDF 1.7 \pkg{l3pdfmeta} also sets these versions also as requirements. % These requirements are checked by \pkg{l3pdfmeta} when the version is set with % \cs{DocumentMetadata} and a warning is issued (but the version is % not changed). More checks are only needed if the version is changed later. % % % \item[|max_pdf_version|] stores the maximal PDF version. % It should be checked against the current PDF version (\cs{pdf_version:}). % A failure means that the version should be changed. % The check is currently relevant only for the A-1 to A-3 standards: % PDF 2.0 leads to a failure in a validator like verapdf so the maximal % version should be PDF 1.7. % This requirement is checked by \pkg{l3pdfmeta} when the version is set with % \cs{DocumentMetadata} and a warning is issued (but the version is % not changed). More checks are only needed if the version is changed later. % % \item[|named_actions|] this requirement restricts the list of % allowed named actions to |NextPage|, |PrevPage|, |FirstPage|, |LastPage|. % The check should supply the named action without slash % (e.g. |View| (failure) or |NextPage| (pass)). % % \item[|annot_action_A|] (rule 6.6.1-1) this requirement restricts % the allowed subtypes of the % |/A| dictionary of an action. The check should supply the user % subtype without slash e.g. as |GoTo| (pass) or |Movie| (failure). % \end{description} % % \subsection{Colorprofiles and OutputIntent} % % The pdf/A standards require that a color profile is embedded and % referenced in the catalog in the |/OutputIntent| array. % % The problem is that the pdf/A standards also require, that if the PDF has more then % one entry in the |/OutputIntent| array (which is allowed), their |/DestOutputProfile| % should all reference the same color profile\footnote{see rule 6.2.2-2 at % \url{https://docs.verapdf.org/validation/pdfa-part1/}}. % % Enforcing this fully is impossible if entries are added manually by users or % packages with |\pdfmanagement_add:nnn {Catalog}{OutputIntents}{|\meta{object reference}|}| % as it is difficult to inspect and remove entries from the |/OutputIntent| array. % % So we provide a dedicated interface to avoid the need of manual % user settings and allow the code to handle the requirements of the standard. % The interface doesn't handle yet all finer points for PDF/X standards, e.g. % named profiles, it is meant as a starting point to get at least PDF/A validation % here. % % \begin{NOTE}{UF} % The interface has to handle the following points: % \begin{itemize} % \item We have to assume that some documents wants to add more % than one OutputIntent with varying subtypes. % \item While currently only |/GTS_PDFA1| and |/GTS_PDFX| seem to % be relevant, we have to assume that the list of subtypes is open. % \item But we can imho assume that every subtype is there at most once. % \item The referenced color profile can be used also other means, e.g. an /ICCBased % color space. We must avoid that it is embedded twice in this case. % This will need coordination with l3color. It should probably provide the % code to embed the profile. % \item While we can predeclare some standard icc-profiles, an interface to % setup more is needed. This is currently not handled, as it needs % coordination with a setup in l3color too. % \item The implementation doesn't really handle yet all finer points for pdf/X % see \url{tn0002_color_in_pdfa-1_2008-03-141.pdf} % \end{itemize} % \end{NOTE} % The interface looks like this % % \begin{verbatim} % \DocumentMetadata % { % %other options for example pdfstandard % colorprofiles= % { % A = sRGB.icc, %or a or longer GTS_PDFA1 = sRGB.icc % X = FOGRA39L_coated.icc, % or x or longer GTS_PDFX % ISO_PDFE1 = whatever.icc % } % % } % \end{verbatim} % % |sRGB.icc| and |FOGRA39L_coated.icc| (from the \pkg{colorprofiles} package % are predefined and will work directly\footnote{The \texttt{dvips} route % will require that \texttt{ps2pdf} is called with \texttt{-dNOSAFER}, % and that the color profiles are in the current folder as \texttt{ps2pdf} doesn't % use \texttt{kpathsea} to find them.}. |whatever.icc| will need special setup in % the document preamble to declare the values for the % |OutputIntent| dictionary, but the interface hasn't be added yet. This will be % decided later. % % % If an A-standard is detected or set which requires % that all |/DestOutputProfile| reference the same % color profile, the setting is changed to the equivalent of % % \begin{verbatim} % \DocumentMetadata % { % %other options % pdfstandard=A-2b, % colorprofiles= % { % A = sRGB.icc, %or longer GTS_PDFA1 = sRGB.icc % X = sRGB.icc, % ISO_PDFE1 = sRGB.icc % } % % } % \end{verbatim} % % The pdf/A standards will use |A=sRGB.icc| by default, so this doesn't % need to be declared explicitly. % % \subsection{Regression tests} % When doing regression tests one has to set various metadata to fix values. % \begin{function}{\pdfmeta_set_regression_data:} % \begin{syntax} % \cs{pdfmeta_set_regression_data:} % \end{syntax} % This sets various metadata to values needed by the \LaTeX{} % regression tests. % It also sets the seed for random functions. % If a current l3backend is used and \cs{c_sys_timestamp_str} is available, % the command does not set dates, but % assumes that the environment variable \verb+SOURCE_DATE_EPOCH+ is used. % \end{function} % % \section{XMP-metadata} % XMP-metadata are data in XML format embedded in a stream % inside the PDF and referenced from the |/Catalog|. % Such a XMP-metadata stream contains various document related data, % is required by various PDF standards and can replace % or extend the data in the |/Info| dictionary. % In PDF 2.0 the /Info dictionary is actually deprecated % and only XMP-metadata should be used for the metadata of the PDF. % % The content of a XMP-metadata stream is not a fix set of data. % Typically fields like the title, the author, the language and keywords will % be there. But standards like e.g. ZUGferd (a standard for electronic bills) % can require to add more fields, and it is also possible % to define and add purely local data. % % In some workflows (e.g. if dvips + ghostscript is used) % a XMP-metadata stream with some standard content is added automatically by % the backend, but normally it must be created with code. % % For this task the packages \pkg{hyperxmp}, \pkg{xmpincl} or \pkg{pdfx} % (which uses \pkg{xmpincl}) % can be used, but all these packages are not compatible with the % pdfmanagement\footnote{\pkg{hyperxmp} was partly compatible as the pdfmanagement % contained some patches for it, but these patches have now been removed.}. % The following code is meant as replacement for these packages. % % \pkg{hyperxmp} uses |\hypersetup| as user interface to enter the XMP-metadata. % This syntax is also supported by the new code\footnote{with a number of changes which % are discussed in more details below}, so if \pkg{hyperref} % has been loaded, e.g. |pdftitle=xxx| can be used to set the title. % But XMP-metadata shouldn't require to use \pkg{hyperref} and in a future % version an interface without \pkg{hyperref} will be added. % % There is currently no full user interface command to extend the XMP-metadata % with for example the code needed for ZUGferd, % they will be added in a second step. % % \subsection{Debug option} % % The resulting XMP-packet can be written to an external file by activating a debug % option % % \begin{verbatim} % \DocumentMetadata{debug={xmp-export}} % %or % \DocumentMetadata{debug={xmp-export=true}} % %or % \DocumentMetadata{debug={xmp-export=filename}} % \end{verbatim} % % By default the data are written to \verb+\jobname.xmpi+, if a \texttt{filename} is % given, then \verb+filename.xmpi+ is used instead. \verb+xmp-export=false+ deactivates % the export. % % \subsection{Encoding and escaping} % % XMP-metadata are stored as UTF-8 in the PDF. This mean if you open a PDF in an editor % a content like \enquote{grüße} will be shown probably as \enquote{grüße}. % As XMP-metadata are in XML format special chars like |<|, |>|, and |&| and % \texttt{\textquotestraightdblbase} must be escaped. % % \pkg{hyperxmp} hooks into \pkg{hyperref} and passes all % input through |\pdfstringdef|. This means a word like % \enquote{hallo} is first converted by |\pdfstringdef| into\\ % |\376\377\000h\000a\000l\000l\000o| and then back to UTF-8 by % \pkg{hyperxmp} and in the course of this action % the XML-escapings are applied. % \pkg{pdfx} uses |\pdfstringdef| together with % a special fontencoding (similar to the PU-encoding of \pkg{hyperref}) % for a similar aim. % The code here is based on |\text_purify:n| followed by a few replacements for the % escaping. % % User data should normally be declared in the preamble (or even in the % |\DocumentMetadata| command), and consist of rather simple text; |&| can be entered % as |\&| (but directly |&| will normally work too), % babel shorthands should not be used. Some data are interpreted as comma lists, % in this cases commas which are part of the text should be protected by braces. % In some cases a text in brackets like |[en]| is interpreted as language tag, % if they are part of a text they should be protected by braces too. % XMP-metadata are stored uncompressed in the PDF so if you are unsure % if a value has % been passed correctly, open the PDF in an editor, copy the whole block and % pass it to a validator, e.g. \url{https://www.w3.org/RDF/Validator/}. % % \subsection{User interfaces and differences to \pkg{hyperxmp} } % % \subsubsection{PDF standards} % % The \pkg{hyperxmp}/\pkg{hyperref} keys |pdfapart|, |pdfaconformance|, |pdfuapart|, % |pdfxstandard| and |pdfa| are ignored by this code. Standards must be set with the % |pdfstandard| key of |\DocumentMetadata|. This key can be used more than once, % e.g. \\ % |pdfstandard=A-2b,pdfstandard=X-4,pdfstandard=UA-1|. % \\ Note that using these % keys doesn't mean that the document actually follows the standard. \LaTeX{} % can neither ensure nor check all requirements of a standard, and not everything % it can do theoretically has already been implemented. % When setting an |A| standard, the code will e.g. insert a color profile and % warn if the PDF version doesn't fit, but |X| and |UA| currently only % adds the relevant declarations to the XMP-metadata. % It is up to the author to ensure and validate % that the document actually follows the standard. % % \subsubsection{Declarations} % PDF knows beside standards also a more generic method to declare conformance % to some specification by adding a declaration, % see \url{https://pdfa.org/wp-content/uploads/2019/09/PDF-Declarations.pdf}). % Such declarations can be added as a simple url which identify the specification or % with additional details regarding date and credentials. An example would be % % \begin{verbatim} % \DocumentMetadata{} % \documentclass{article} % \ExplSyntaxOn % \pdfmeta_xmp_add_declaration:e {https://pdfa.org/declarations\c_hash_str iso32005} % \pdfmeta_xmp_add_declaration:ennnn % {https://pdfa.org/declarations\c_hash_str wcag21A}{}{2023-11-20}{}{} % \pdfmeta_xmp_add_declaration:nnnnn % {https://github.com/TikZlings/no-duck-harmed} % {Ulrike~Fischer}{2023-11-20}{Bär}{https://github.com/u-fischer/bearwear} % \pdfmeta_xmp_add_declaration:nnnnn % {https://github.com/TikZlings/no-duck-harmed} % {Ulrike~Fischer}{2023-11-20}{Paulo}{https://github.com/cereda/sillypage} % \ExplSyntaxOff % \begin{document} % text % \end{document} % % \end{verbatim} % % \subsubsection{Dates} % \begin{itemize} % \item % The dates |xmp:CreateDate|, |xmp:ModifyDate|, |xmp:MetadataDate| % are normally set automatically to the current date/time when the compilation % started. If they should be changed % (e.g. for regression tests to produce reproducible documents) they can % be set with |\hypersetup| with the keys % |pdfcreationdate|, |pdfmoddate| and |pdfmetadate|. % % \begin{verbatim} % \hypersetup{pdfcreationdate=D:20010101205959-00'00'} % \end{verbatim} % % The format should be a full date/time in PDF format, so one of these (naturally % the numbers can change): % \begin{verbatim} % D:20010101205959-00'00' % D:20010101205959+00'00' % D:20010101205959Z % \end{verbatim} % % \item The date |dc:date| is an \enquote{author date} and so % should normally be set to the same date as % given by |\date|. This can be done with the key |pdfdate|\footnote{Extracting % the value automatically from \texttt{\textbackslash date} is not really possible % as authors often put formatting or additional info in this command.}. % The value should be a date in ISO 8601 format: % % \begin{verbatim} % 2022 %year % 2022-09-04 %year-month-day % 2022-09-04T19:20 %year-month-day hour:minutes % 2022-09-04T19:20:30 % year-month-day hour:minutes:second % 2022-09-04T19:20:30.45 % year-month-day hour:minutes:second with fraction % 2022-09-04T19:20+01:00 % with time zone designator % 2022-09-04T19:20-02:00 % time zone designator % 2022-09-04T19:20Z % time zone designator % \end{verbatim} % % It is also possible to give the date as a full date in PDF format as described % above. If not set the current date/time is used. %\end{itemize} % % \subsection{Language} % The code assumes that a default language is always declared % (as the pdfmanagement gives the |/Lang| entry in the catalog a default value) % This language can be changed with the |\DocumentMetadata| key |lang| (preferred) % but the \pkg{hyperref} key |pdflang| is also honored. Its value should be a % simple language tag like |de| or |de-DE|. % % The main language is also used in a number of attributes in the XMP data, % if wanted a different language can be set here with the % \pkg{hyperref}/\pkg{hyperxmp} key |pdfmetalang|. % % A number of entries can be given a language tag. Such a language is % given by using an \enquote{optional argument} before the text: % % \begin{verbatim} % \hypersetup{pdftitle={[en]english,[de]deutsch}} % \hypersetup{pdfsubtitle={[en]subtitle in english}} % \end{verbatim} % % \subsection{Rights} % The keys |pdfcopyright| and |pdflicenseurl| work similar as in \pkg{hyperxmp}. % But differently to \pkg{hyperxmp} the code doesn't set the |xmpRights:Marked| % property, as I have some doubts that one deduce its value simply % by checking if the other keys have been used; if needed it can be added by % using one of these settings (true means with copyright, false means public domain). % \begin{verbatim} % \AddToDocumentProperties[document]{copyright}{true} % \AddToDocumentProperties[document]{copyright}{false} % \end{verbatim} % % \subsection{PDF related data} % The PDF producer is for all engines by default built from the engine % name and the engine version and doesn't use the banners as with \pkg{hyperxmp} % and \pkg{pdfx}, it can be set manually with the |pdfproducer| key. % % The key |pdftrapped| is ignored. |Trapped| is deprecated in PDF 2.0. % % \subsection{Document data} % % The authors should be given with the |pdfauthor| key, separated by commas. If an % author contains a comma, protect/hide it by a brace. % \subsection{User commands} % The XMP-meta data are added automatically. This can be suppressed with the % |\DocumentMetadata| key |xmp|. % % \begin{function}{\pdfmeta_xmp_add:n} % \begin{syntax} % \cs{pdfmeta_xmp_add:n}\Arg{XML} % \end{syntax} % With this command additional XML code can be added to the Metadata. % The content is added unchanged, and not sanitized. % \end{function} % \begin{function}{\pdfmeta_xmp_xmlns_new:nn} % \begin{syntax} % \cs{pdfmeta_xmp_xmlns_new:nn}\Arg{prefix}\Arg{uri} % \end{syntax} % With this command a xmlns name space can be added. The \meta{uri} % argument is expanded, a hash can be input with |\c_hash_str|. % \end{function} % % With the two following commands PDF declarations can be added to the XMP metadata % (see \url{https://pdfa.org/wp-content/uploads/2019/09/PDF-Declarations.pdf}). % \begin{function}{\pdfmeta_xmp_add_declaration:n,\pdfmeta_xmp_add_declaration:e} % \begin{syntax} % \cs{pdfmeta_xmp_add_declaration:n}\Arg{uri} % \end{syntax} % This add a PDF declaration with the required |conformsTo| property to the XMP metadata. % \meta{uri} should not be empty and is a URI specifying % the standard or profile referred to by the PDF % Declaration. If the uri contains a hash, use \cs{c_hash_str} to escape it % and use the \texttt{e} variant to expand it. % \end{function} % % \begin{function}{\pdfmeta_xmp_add_declaration:nnnnn, % \pdfmeta_xmp_add_declaration:ennnn, % \pdfmeta_xmp_add_declaration:eeenn} % \begin{syntax} % \cs{pdfmeta_xmp_add_declaration:nnnnn}\Arg{uri}\Arg{By}\Arg{Date}\Arg{Credentials}\Arg{Report} % \end{syntax} % This add a PDF declaration to the XMP metadata similar % to \cs{pdfmeta_xmp_add_declaration:n}. % With \meta{By}, \meta{Date}, \meta{Credentials}, \meta{Report} the optional % fields |claimBy| (text), |claimDate| (iso date), |claimCredentials| (text) and % |claimReport| (uri) of the |claimData| property can be given. % If \cs{pdfmeta_xmp_add_declaration:nnnnn} is used twice with the same \meta{uri} % argument the |claimData| are concatenated. There is no check if the |claimData| are identical. % \end{function} % % The following two commands can be used to extend the schema declarations in % the XMP metadata. This is for example needed to implement a standard like ZUGferd/Factur X % for invoices. A schema declaration should be added only once but as this task % is probably not needed frequently only light guards are there to avoid duplicated entries. % % \begin{function}{\pdfmeta_xmp_schema_new:nnn} % \begin{syntax} % \cs{pdfmeta_xmp_schema_new:nnn}\Arg{text}\Arg{prefix}\Arg{uri} % \end{syntax} % \meta{text} is some string describing the schema, e.g. |PDF/A~Identification~Schema|, % \meta{prefix} is the unique prefix used by the schema. This prefix % must be declared first with |\pdfmeta_xmp_xmlns_new:nn|. If a schema with this prefix % has already been declared, it will currently be ignored with a warning. % The \meta{uri} is expanded, so a % hash can for example be given as |\c_hash_str|. % \end{function} % \begin{function}{\pdfmeta_xmp_property_new:nnnnn} % \begin{syntax} % \cs{pdfmeta_xmp_property_new:nnnnn}\Arg{schema prefix}\Arg{name}\Arg{type}\Arg{category}\Arg{description} % \end{syntax} % If the new property already exists in the schema % (as identified by the combination of \meta{schema prefix} % and \meta{name} the property is silently ignore. % \meta{schema prefix} is the prefix declared with the previous command. % schema, e.g. |PDF/A~Identification~Schema|, % \meta{name} is a short string that identifies the property, e.g. |xmpMM| or |year|. It must be % unique in the properties of a schema. \meta{type} is e.g. |URI| or |Integer| or |Text|, % \meta{category} is e.g. |internal| or |external|, \meta{description} is a free description string. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3pdfmeta} implementation} % % \begin{macrocode} %<@@=pdfmeta> %<*header> \ProvidesExplPackage{l3pdfmeta}{2024-10-27}{0.96n} {PDF-Standards---LaTeX PDF management testphase bundle} % % \end{macrocode} % Message for unknown standards % \begin{macrocode} %<*package> \msg_new:nnn {pdf }{unknown-standard}{The~standard~'#1'~is~unknown~and~has~been~ignored} % \end{macrocode} % Message for not fitting pdf version % \begin{macrocode} \msg_new:nnn {pdf }{wrong-pdfversion} {PDF~version~#1~is~too~#2~for~standard~'#3'.} % \end{macrocode} % \begin{variable}{\l_@@_tmpa_tl,\l_@@_tmpb_tl,\l_@@_tmpa_str, % \g_@@tmpa_str,\l_@@_tmpa_seq,\l_@@_tmpb_seq} % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl \str_new:N \l_@@_tmpa_str \str_new:N \g_@@_tmpa_str \seq_new:N \l_@@_tmpa_seq \seq_new:N \l_@@_tmpb_seq % \end{macrocode} % \end{variable} % \subsection{Standards (work in progress)} % \subsubsection{Tools and tests} % This internal property will contain for now the settings for the document. % \begin{variable}{\g_@@_standard_prop} % \begin{macrocode} \prop_new:N \g_@@_standard_prop % \end{macrocode} % \end{variable} % \subsubsection{Functions to check a requirement} % At first two commands to get the standard value if needed: % \begin{macro}[EXP]{\pdfmeta_standard_item:n} % \begin{macrocode} \cs_new:Npn \pdfmeta_standard_item:n #1 { \prop_item:Nn \g_@@_standard_prop {#1} } % \end{macrocode} % \end{macro} % \begin{macro}{\pdfmeta_standard_get:nN} % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_standard_get:nN #1 #2 { \prop_get:NnN \g_@@_standard_prop {#1} #2 } % \end{macrocode} % \end{macro} % Now two functions to check the requirement. A simple and one value/handler based. % \begin{macro}[pTF]{\pdfmeta_standard_verify:n} % This is a simple test is the requirement is in the prop. % \begin{macrocode} \prg_new_conditional:Npnn \pdfmeta_standard_verify:n #1 {T,F,TF} { \prop_if_in:NnTF \g_@@_standard_prop {#1} { \prg_return_false: } { \prg_return_true: } } % \end{macrocode} % \end{macro} % \begin{macro}[TF]{\pdfmeta_standard_verify:nn} % This allows to test against a user value. It calls a test handler if this % exists and passes the user and the standard value to it. The test % handler should return true or false. % \begin{macrocode} \prg_new_protected_conditional:Npnn \pdfmeta_standard_verify:nn #1 #2 {T,F,TF} { \prop_if_in:NnTF \g_@@_standard_prop {#1} { \cs_if_exist:cTF {@@_standard_verify_handler_#1:nn} { \exp_args:Nnne \use:c {@@_standard_verify_handler_#1:nn} { #2 } { \prop_item:Nn \g_@@_standard_prop {#1} } } { \prg_return_false: } } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % Now we setup a number of handlers. % % The first actually ignores the user values and tests against the % current pdf version. If this is smaller than the minimum we report a failure. % |#1| is the user value, |#2| the reference value from the standard. % \begin{macro}{\@@_standard_verify_handler_min_pdf_version:nn} % \begin{macrocode} % \cs_new_protected:Npn \@@_standard_verify_handler_min_pdf_version:nn #1 #2 { \pdf_version_compare:NnTF < { #2 } {\prg_return_false:} {\prg_return_true:} } % \end{macrocode} % \end{macro} % The next is the counter part and checks that the version is not to high % \begin{macro}{\@@_standard_verify_handler_max_pdf_version:nn} % \begin{macrocode} % \cs_new_protected:Npn \@@_standard_verify_handler_max_pdf_version:nn #1 #2 { \pdf_version_compare:NnTF > { #2 } {\prg_return_false:} {\prg_return_true:} } % \end{macrocode} % \end{macro} % The next checks if the user value is in the list and returns a failure if not. % \begin{macro}{\@@_standard_verify_handler_named_actions:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_standard_verify_handler_named_actions:nn #1 #2 { \tl_if_in:nnTF { #2 }{ #1 } {\prg_return_true:} {\prg_return_false:} } % \end{macrocode} % \end{macro} % The next checks if the user value is in the list and returns a failure if not. % \begin{macro}{\@@_standard_verify_handler_annot_action_A:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_standard_verify_handler_annot_action_A:nn #1 #2 { \tl_if_in:nnTF { #2 }{ #1 } {\prg_return_true:} {\prg_return_false:} } % \end{macrocode} % \end{macro} % This check is probably not needed, but for completeness % \begin{macro}{\@@_standard_verify_handler_outputintent_subtype:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_standard_verify_handler_outputintent_subtype:nn #1 #2 { \tl_if_eq:nnTF { #2 }{ #1 } {\prg_return_true:} {\prg_return_false:} } % \end{macrocode} % \end{macro} % \subsubsection{Enforcing requirements} % A number of requirements can sensibly be enforced by us. % \paragraph{Annot flags} % pdf/A require a number of settings here, we store them in a command which % can be added to the property of the standard: % \begin{macrocode} \cs_new_protected:Npn \@@_verify_pdfa_annot_flags: { \bitset_set_true:Nn \l_pdfannot_F_bitset {Print} \bitset_set_false:Nn \l_pdfannot_F_bitset {Hidden} \bitset_set_false:Nn \l_pdfannot_F_bitset {Invisible} \bitset_set_false:Nn \l_pdfannot_F_bitset {NoView} \pdfannot_dict_put:nnn {link/URI}{F}{ \bitset_to_arabic:N \l_pdfannot_F_bitset } \pdfannot_dict_put:nnn {link/GoTo}{F}{ \bitset_to_arabic:N \l_pdfannot_F_bitset } \pdfannot_dict_put:nnn {link/GoToR}{F}{ \bitset_to_arabic:N \l_pdfannot_F_bitset } \pdfannot_dict_put:nnn {link/Launch}{F}{ \bitset_to_arabic:N \l_pdfannot_F_bitset } \pdfannot_dict_put:nnn {link/Named}{F}{ \bitset_to_arabic:N \l_pdfannot_F_bitset } } % \end{macrocode} % At begin document this should be checked: % \begin{macrocode} \hook_gput_code:nnn {begindocument} {pdf} { \pdfmeta_standard_verify:nF { annot_flags } { \@@_verify_pdfa_annot_flags: } \pdfmeta_standard_verify:nF { Trailer_no_Info } { \__pdf_backend_omit_info:n {1} } \pdfmeta_standard_verify:nF { no_CharSet } { \__pdf_backend_omit_charset:n {1} } \pdfmeta_standard_verify:nF { omit_CID } { \__pdf_backend_omit_cidset:n {1} } \pdfmeta_standard_verify:nnF { min_pdf_version } { \pdf_version: } { \msg_warning:nneee {pdf}{wrong-pdfversion} {\pdf_version:}{low} { \pdfmeta_standard_item:n{type} - \pdfmeta_standard_item:n{level} } } \pdfmeta_standard_verify:nnF { max_pdf_version } { \pdf_version: } { \msg_warning:nneee {pdf}{wrong-pdfversion} {\pdf_version:}{high} { \pdfmeta_standard_item:n{type} - \pdfmeta_standard_item:n{level} } } } % \end{macrocode} % % % \subsubsection{pdf/A} % We use global properties so that follow up standards can be % copied and then adjusted. % Some note about requirements for more standard can % be found in info/pdfstandard.tex. % \begin{variable}{ % \g_@@_standard_pdf/A-1B_prop , % \g_@@_standard_pdf/A-2A_prop , % \g_@@_standard_pdf/A-2B_prop , % \g_@@_standard_pdf/A-2U_prop , % \g_@@_standard_pdf/A-3A_prop , % \g_@@_standard_pdf/A-3B_prop , % \g_@@_standard_pdf/A-3U_prop , % \g_@@_standard_pdf/A-4_prop , % } % \begin{macrocode} \prop_new:c { g_@@_standard_pdf/A-1B_prop } \prop_gset_from_keyval:cn { g_@@_standard_pdf/A-1B_prop } { ,name = pdf/A-1B ,type = A ,level = 1 ,conformance = B ,year = 2005 ,min_pdf_version = 1.4 %minimum ,max_pdf_version = 1.4 %minimum ,no_encryption = ,no_external_content = % no F, FFilter, or FDecodeParms in stream dicts ,no_embed_content = % no EF key in filespec, no /Type/EmbeddedFiles ,max_string_size = 65535 ,max_array_size = 8191 ,max_dict_size = 4095 ,max_obj_num = 8388607 ,max_nest_qQ = 28 ,named_actions = {NextPage, PrevPage, FirstPage, LastPage} ,annot_flags = %booleans. Only the existence of the key matter. %If the entry is added it means a requirements is there %(in most cases "don't use ...") % %=============== % Rule 6.1.13-1 CosDocument, isOptionalContentPresent == false ,Catalog_no_OCProperties = % Rule 6.9-4 The AS key shall not appear in any optional content configuration dictionary. % actually only starting with A-2 but doesn't harm here either ,Catalog_OCProperties_no_AS= %=============== % Rule 6.6.1-1: PDAction, S == "GoTo" || S == "GoToR" || S == "Thread" % || S == "URI" || S == "Named" || S == "SubmitForm" % means: no /S/Launch, /S/Sound, /S/Movie, /S/ResetForm, /S/ImportData, % /S/JavaScript, /S/Hide ,annot_action_A = {GoTo,GoToR,Thread,URI,Named,SubmitForm} %=============== % Rule 6.6.2-1: PDAnnot, Subtype != "Widget" || AA_size == 0 % means: no AA dictionary ,annot_widget_no_AA = %=============== % Rule 6.9-2: PDAnnot, Subtype != "Widget" || (A_size == 0 && AA_size == 0) % (looks like a tightening of the previous rule) ,annot_widget_no_A_AA = %=============== % Rule 6.9-1 PDAcroForm, NeedAppearances == null || NeedAppearances == false ,form_no_NeedAppearances = %=============== %Rule 6.9-3 PDFormField, AA_size == 0 ,form_no_AA = %=============== % to be continued https://docs.verapdf.org/validation/pdfa-part1/ % - Outputintent/colorprofiles requirements % an outputintent should be loaded and is unique. ,outputintent_A = {GTS_PDFA1} % - no Alternates key in image dictionaries % - no OPI, Ref, Subtype2 with PS key in xobjects % - Interpolate = false in images % - no TR, TR2 in ExtGstate } %A-2b ============== \prop_new:c { g_@@_standard_pdf/A-2B_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-2B_prop } { g_@@_standard_pdf/A-1B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-2B_prop }{name}{pdf/A-2B} \prop_gput:cnn { g_@@_standard_pdf/A-2B_prop }{year}{2011} \prop_gput:cnn { g_@@_standard_pdf/A-2B_prop }{level}{2} % embedding files is allowed (with restrictions) \prop_gremove:cn { g_@@_standard_pdf/A-2B_prop } { embed_content} \prop_gput:cnn { g_@@_standard_pdf/A-2B_prop }{max_pdf_version}{1.7} \prop_gput:cnn { g_@@_standard_pdf/A-2B_prop }{omit_CID}{} % OCG layers are allowed (with restrictions) \prop_gremove:cn { g_@@_standard_pdf/A-2B_prop } { Catalog_no_OCProperties } %A-2u ============== \prop_new:c { g_@@_standard_pdf/A-2U_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-2U_prop } { g_@@_standard_pdf/A-2B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-2U_prop }{name}{pdf/A-2U} \prop_gput:cnn { g_@@_standard_pdf/A-2U_prop }{conformance}{U} \prop_gput:cnn { g_@@_standard_pdf/A-2U_prop }{unicode}{} %A-2a ============== \prop_new:c { g_@@_standard_pdf/A-2A_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-2A_prop } { g_@@_standard_pdf/A-2B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-2A_prop }{name}{pdf/A-2A} \prop_gput:cnn { g_@@_standard_pdf/A-2A_prop }{conformance}{A} \prop_gput:cnn { g_@@_standard_pdf/A-2A_prop }{tagged}{} %A-3b ============== \prop_new:c { g_@@_standard_pdf/A-3B_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-3B_prop } { g_@@_standard_pdf/A-2B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-3B_prop }{name}{pdf/A-3B} \prop_gput:cnn { g_@@_standard_pdf/A-3B_prop }{year}{2012} \prop_gput:cnn { g_@@_standard_pdf/A-3B_prop }{level}{3} % embedding files is allowed (with restrictions) \prop_gremove:cn { g_@@_standard_pdf/A-3B_prop } { embed_content} %A-3u ============== \prop_new:c { g_@@_standard_pdf/A-3U_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-3U_prop } { g_@@_standard_pdf/A-3B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-3U_prop }{name}{pdf/A-3U} \prop_gput:cnn { g_@@_standard_pdf/A-3U_prop }{conformance}{U} \prop_gput:cnn { g_@@_standard_pdf/A-3U_prop }{unicode}{} %A-3a ============== \prop_new:c { g_@@_standard_pdf/A-3A_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-3A_prop } { g_@@_standard_pdf/A-3B_prop } \prop_gput:cnn { g_@@_standard_pdf/A-3A_prop }{name}{pdf/A-3A} \prop_gput:cnn { g_@@_standard_pdf/A-3A_prop }{conformance}{A} \prop_gput:cnn { g_@@_standard_pdf/A-3A_prop }{tagged}{} %A-4 ============== \prop_new:c { g_@@_standard_pdf/A-4_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-4_prop } { g_@@_standard_pdf/A-3U_prop } \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{name}{pdf/A-4} \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{level}{4} \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{min_pdf_version}{2.0} \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{year}{2020} \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{no_CharSet}{} \prop_gput:cnn { g_@@_standard_pdf/A-4_prop }{Trailer_no_Info}{} \prop_gremove:cn { g_@@_standard_pdf/A-4_prop }{conformance} \prop_gremove:cn { g_@@_standard_pdf/A-4_prop }{max_pdf_version} \prop_gremove:cn { g_@@_standard_pdf/A-4_prop }{Catalog_OCProperties_no_AS} %A-4f ============== \prop_new:c { g_@@_standard_pdf/A-4F_prop } \prop_gset_eq:cc { g_@@_standard_pdf/A-4F_prop } { g_@@_standard_pdf/A-4_prop } \prop_gput:cnn { g_@@_standard_pdf/A-4F_prop }{conformance}{F} % containsEmbeddedFiles == true ISO 19005-4:2020, Clause: 6.9, Test number: 5 \prop_gput:cnn { g_@@_standard_pdf/A-4F_prop }{Catalog_EmbeddedFiles}{} % \end{macrocode} % \end{variable} % % \subsubsection{Embedded Files} % Standard 4-AF is needed if we add AF files for % tagging but it also requires an EmbeddedFiles name tree, % so we test at the end if the name tree is empty and add a small readme if % yes % \begin{macrocode} \AddToHook{begindocument/end} { \pdfmeta_standard_verify:nF{Catalog_EmbeddedFiles} { \tl_gput_right:Nn\g__kernel_pdfmanagement_end_run_code_tl { \bool_if:NT \g__pdfmanagement_active_bool { \pdfdict_if_empty:nT { g__pdf_Core/Catalog/Names/EmbeddedFiles } { \group_begin: \pdfdict_put:nne {l_pdffile/Filespec} {Desc}{(note~about~PDF/A-4F)} \pdfdict_put:nnn { l_pdffile/Filespec }{AFRelationship} { /Unspecified } \pdffile_embed_stream:nnN {PDF~standard~A-4F~requires~a~file}{readme.txt}\l_@@_tmpa_tl \exp_args:Nne \__pdf_backend_Names_gpush:nn{EmbeddedFiles}{(readme)~\l_@@_tmpa_tl} \group_end: } } } } } % \end{macrocode} % \subsubsection{Colorprofiles and Outputintents} % The following provides a minimum of interface to add a color profile % and an outputintent need for PDF/A for now. There will be need to extend it later, % so we try for enough generality. % % Adding a profile and an intent is technically easy: % \begin{enumerate} % \item Embed the profile as stream with % \begin{verbatim} % \pdf_object_unnamed_write:nn{fstream} {{/N~4}{XXX.icc}} % \end{verbatim} % \item Write a |/OutputIntent| dictionary for this % \begin{verbatim} % \pdf_object_unnamed_write:ne {dict} % { % /Type /OutputIntent % /S /GTS_PDFA1 % or GTS_PDFX or ISO_PDFE1 or ... % /DestOutputProfile \pdf_object_ref_last: % ref the color profile % /OutputConditionIdentifier ... % ... %more info % } % \end{verbatim} % \item Reference the dictionary in the catalog: % \begin{verbatim} % \pdfmanagement_add:nne {Catalog}{OutputIntents}{\pdf_object_ref_last:} % \end{verbatim} % \end{enumerate} % But we need to do a bit more work, to get the interface right. % The object for the profile should be named, to allow l3color to reuse it % if needed. And we need container to store the profiles, to handle the % standard requirements. % % \begin{variable}{\g_@@_outputintents_prop} % This variable will hold the profiles for the subtypes. We assume % that every subtype has only only color profile. % \begin{macrocode} \prop_new:N \g_@@_outputintents_prop % \end{macrocode} % \end{variable} % Some keys to fill the property. % \begin{macrocode} \keys_define:nn { document / metadata } { colorprofiles .code:n = { \keys_set:nn { document / metadata / colorprofiles }{#1} } } \keys_define:nn { document / metadata / colorprofiles } { ,A .code:n = { \tl_if_blank:nF {#1} { \prop_gput:Nnn \g_@@_outputintents_prop { GTS_PDFA1 } {#1} } } ,a .code:n = { \tl_if_blank:nF {#1} { \prop_gput:Nnn \g_@@_outputintents_prop { GTS_PDFA1 } {#1} } } ,X .code:n = { \tl_if_blank:nF {#1} { \prop_gput:Nnn \g_@@_outputintents_prop { GTS_PDFX } {#1} } } ,x .code:n = { \tl_if_blank:nF {#1} { \prop_gput:Nnn \g_@@_outputintents_prop { GTS_PDFX } {#1} } } ,unknown .code:n = { \tl_if_blank:nF {#1} { \exp_args:NNo \prop_gput:Nnn \g_@@_outputintents_prop { \l_keys_key_str } {#1} } } } % \end{macrocode} % At first we setup our two default profiles. This is internal as % the public interface is still undecided. % \begin{macrocode} \pdfdict_new:n {l_pdfmeta/outputintent} \pdfdict_put:nnn {l_pdfmeta/outputintent} {Type}{/OutputIntent} \prop_const_from_keyval:cn { c_@@_colorprofile_sRGB.icc} { ,OutputConditionIdentifier=IEC~sRGB ,Info=IEC~61966-2.1~Default~RGB~colour~space~-~sRGB ,RegistryName=http://www.iec.ch ,N = 3 } \prop_const_from_keyval:cn { c_@@_colorprofile_FOGRA39L_coated.icc} { ,OutputConditionIdentifier=FOGRA39L~Coated ,Info={Offset~printing,~according~to~ISO~12647-2:2004/Amd~1,~OFCOM,~ % paper~type~1~or~2~=~coated~art,~115~g/m2,~tone~value~increase~ curves~A~(CMY)~and~B~(K)} ,RegistryName=http://www.fogra.org ,N = 4 } % \end{macrocode} % \begin{macro}{\@@_embed_colorprofile:n,\@@_write_outputintent:nn} % The commands embed the profile, and write the dictionary and add it to % the catalog. The first command should perhaps be moved to l3color % as it needs such profiles too. We used named objects so that we can % check if the profile is already there. This is not foolproof if paths are % used. % \begin{macrocode} \cs_new_protected:Npn \@@_embed_colorprofile:n #1%#1 file name { \pdf_object_if_exist:nF { __color_icc_ #1 } { \pdf_object_new:n { __color_icc_ #1 } \pdf_object_write:nne { __color_icc_ #1 } { fstream } { {/N\c_space_tl \prop_item:cn{c_@@_colorprofile_#1}{N} } {#1} } } } \cs_new_protected:Npn \@@_write_outputintent:nn #1 #2 %#1 file name, #2 subtype { \group_begin: \pdfdict_put:nne {l_pdfmeta/outputintent}{S}{/\str_convert_pdfname:n{#2}} \pdfdict_put:nne {l_pdfmeta/outputintent} {DestOutputProfile} {\pdf_object_ref:n{ __color_icc_ #1 }} \clist_map_inline:nn { OutputConditionIdentifier, Info, RegistryName } { \prop_get:cnNT { c_@@_colorprofile_#1} { ##1 } \l_@@_tmpa_tl { \pdf_string_from_unicode:nVN {utf8/string}\l_@@_tmpa_tl\l_@@_tmpa_str \pdfdict_put:nne {l_pdfmeta/outputintent}{##1}{\l_@@_tmpa_str} } } \pdf_object_unnamed_write:ne {dict}{\pdfdict_use:n {l_pdfmeta/outputintent} } \pdfmanagement_add:nne {Catalog}{OutputIntents}{\pdf_object_ref_last:} \group_end: } % \end{macrocode} % \end{macro} % Now the verifying code. % If no requirement is set we simply loop over the property % \begin{macrocode} \AddToHook{begindocument/end} { \pdfmeta_standard_verify:nTF {outputintent_A} { \prop_map_inline:Nn \g_@@_outputintents_prop { \prop_if_exist:cTF {c_@@_colorprofile_#2} { \@@_embed_colorprofile:n {#2} \@@_write_outputintent:nn {#2} {#1} } { \msg_warning:nnn{pdfmeta}{colorprofile-undefined}{#2} } } } % \end{macrocode} % If an output intent is required for pdf/A we need to ensure, that the key of % default subtype has a value, as default we take sRGB.icc. % Then we loop but take always the same profile. % \begin{macrocode} { \exp_args:NNe \prop_if_in:NnF \g_@@_outputintents_prop { \pdfmeta_standard_item:n { outputintent_A } } { \exp_args:NNe \prop_gput:Nnn \g_@@_outputintents_prop { \pdfmeta_standard_item:n { outputintent_A } } { sRGB.icc } } \exp_args:NNe \prop_get:NnN \g_@@_outputintents_prop { \pdfmeta_standard_item:n { outputintent_A } } \l_@@_tmpb_tl \prop_if_exist:cTF {c_@@_colorprofile_\l_@@_tmpb_tl} { \exp_args:NV \@@_embed_colorprofile:n \l_@@_tmpb_tl \prop_map_inline:Nn \g_@@_outputintents_prop { \exp_args:NV \@@_write_outputintent:nn \l_@@_tmpb_tl { #1 } } } { \msg_warning:nne{pdfmeta}{colorprofile-undefined}{\l_@@_tmpb_tl} } } } % \end{macrocode} % % \subsection{Regression test} % This is simply a copy of the backend function. % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_set_regression_data: { \__pdf_backend_set_regression_data: } % \end{macrocode} % % \section{XMP-Metadata implementation} % \begin{variable}{\g_@@_xmp_bool} % This boolean decides if the metadata are included % \begin{macrocode} \bool_new:N\g_@@_xmp_bool \bool_gset_true:N \g_@@_xmp_bool % \end{macrocode} % \end{variable} % Preset the two fields to avoid problems with standards. % \begin{macrocode} \hook_gput_code:nnn{pdfmanagement/add}{pdfmanagement} { \pdfmanagement_add:nne {Info}{Producer}{(\c_sys_engine_exec_str-\c_sys_engine_version_str)} \pdfmanagement_add:nne {Info}{Creator}{(LaTeX)} } % \end{macrocode} % \subsection{New document keys} % \begin{macrocode} \keys_define:nn { document / metadata } { _pdfstandard / X-4 .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-4}}, _pdfstandard / X-4p .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-4p}}, _pdfstandard / X-5g .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-5g}}, _pdfstandard / X-5n .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-5n}}, _pdfstandard / X-5pg .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-5pg}}, _pdfstandard / X-6 .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-6p}}, _pdfstandard / X-6n .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-6n}}, _pdfstandard / X-6p .code:n = {\AddToDocumentProperties [document]{pdfstandard-X}{PDF/X-6p}}, _pdfstandard / UA-1 .code:n = { \AddToDocumentProperties [document]{pdfstandard-UA}{{1}{}} \AddToHook{begindocument/before} { \pdf_version_compare:NnF < {2.0} { \msg_warning:nneee {pdf}{wrong-pdfversion} {\pdf_version:}{high}{UA-1} } } }, % \end{macrocode} % currently it is not possible to merge requirements - these need some thoughts as % every standard has some common keys like the name or the yes. % We therefore add some requirements manually. % \begin{macrocode} _pdfstandard / UA-2 .code:n = { \AddToDocumentProperties [document]{pdfstandard-UA}{{2}{2024}} \AddToHook{begindocument/before} {\prop_gput:Nnn \g__pdfmeta_standard_prop {Trailer_no_Info}{}} \AddToHook{begindocument/before} { \@@_xmp_wtpdf_accessibility_declaration: \@@_xmp_wtpdf_reuse_declaration: \pdf_version_compare:NnT < {2.0} { \msg_warning:nneee {pdf}{wrong-pdfversion} {\pdf_version:}{low}{UA-2} } } }, xmp .choice:, xmp / true .code:n = { \bool_gset_true:N \g_@@_xmp_bool }, xmp / false .code:n = { \bool_gset_false:N \g_@@_xmp_bool}, xmp .default:n = true, % \end{macrocode} % These keys allow to disable or force the wtpdf declarations. % Currently the content can not be changed and once they have been disabled there % are gone. This will perhaps change. % \begin{macrocode} xmp / wtpdf .code:n = { \keys_set:nn {@@/xmp}{#1} }, } \keys_define:nn {@@/xmp} { reuse .choice:, reuse / true .code:n = \@@_xmp_wtpdf_reuse_declaration:, reuse / false .code:n = { \cs_set_eq:NN \@@_xmp_wtpdf_reuse_declaration: \prg_do_nothing: }, accessibility .choice:, accessibility / true .code:n = \@@_xmp_wtpdf_accessibility_declaration:, accessibility /false .code:n = { \cs_set_eq:NN \@@_xmp_wtpdf_accessibility_declaration: \prg_do_nothing: }, } % \end{macrocode} % XMP debugging option % \begin{macrocode} \bool_new:N \g_@@_xmp_export_bool \str_new:N \g_@@_xmp_export_str \keys_define:nn { document / metadata } { ,debug / xmp-export .choice: ,debug / xmp-export / true .code:n= { \bool_gset_true:N \g_@@_xmp_export_bool \str_gset_eq:NN \g_@@_xmp_export_str \c_sys_jobname_str } ,debug / xmp-export / false .code:n = { \bool_gset_false:N \g_@@_xmp_export_bool } ,debug / xmp-export /unknown .code:n = { \bool_gset_true:N \g_@@_xmp_export_bool \str_gset:Nn \g_@@_xmp_export_str { #1 } } ,debug / xmp-export .default:n = true } % \end{macrocode} % \subsection{Messages} % \begin{macrocode} \msg_new:nnn{pdfmeta}{xmp-defined}{The~XMP~#1~`#2`~is~already~declared} \msg_new:nnn{pdfmeta}{xmp-undefined}{The~XMP~#1~`#2`~is~undefined} \msg_new:nnn{pdfmeta}{colorprofile-undefined}{The~colorprofile~`#1`~is~unknown} % \end{macrocode} % \subsection{Some helper commands} % \subsubsection{Generate a BOM} % \begin{macro}{\@@_xmp_generate_bom:} % \begin{macrocode} \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_xmp_generate_bom: { \char_generate:nn {"FEFF}{12} } } { \cs_new:Npn \@@_xmp_generate_bom: { \char_generate:nn {"EF}{12} \char_generate:nn {"BB}{12} \char_generate:nn {"BF}{12} } } % \end{macrocode} % \end{macro} % % \subsubsection{Indentation} % We provide a command which indents the xml based % on a counter, and one which accepts a fix number. % The counter can be increased and decreased. % % \begin{variable}{\l_@@_xmp_indent_int} % \begin{macrocode} \int_new:N \l_@@_xmp_indent_int % \end{macrocode} % \end{variable} % \begin{macro}{\@@_xmp_indent:, % \@@_xmp_indent:n, % \@@_xmp_incr_indent:, % \@@_xmp_decr_indent:} % \begin{macrocode} \cs_new:Npn \@@_xmp_indent: { \iow_newline: \prg_replicate:nn {\l_@@_xmp_indent_int}{\c_space_tl} } \cs_new:Npn \@@_xmp_indent:n #1 { \iow_newline: \prg_replicate:nn {#1}{\c_space_tl} } \cs_new_protected:Npn \@@_xmp_incr_indent: { \int_incr:N \l_@@_xmp_indent_int } \cs_new_protected:Npn \@@_xmp_decr_indent: { \int_decr:N \l_@@_xmp_indent_int } % \end{macrocode} % \end{macro} % % \subsubsection{Date and time handling} % If the date is given in PDF format we have to split it to create % the XMP format. We use a precompiled regex for this. % To some extend the regex can also handle incomplete dates. % % \begin{variable}{\l_@@_xmp_date_regex} % \begin{macrocode} \regex_new:N \l_@@_xmp_date_regex \regex_set:Nn \l_@@_xmp_date_regex {D:(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?([Z\+\-])?(?:(\d{2})\')?(?:(\d{2})\')?} % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_xmp_date_split:nN} % This command takes a date in PDF format, splits it with the regex and % stores the captures in a sequence. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_date_split:nN #1 #2 %#1 date, #2 seq { \regex_split:NnN \l_@@_xmp_date_regex {#1} #2 } \cs_generate_variant:Nn \@@_xmp_date_split:nN {VN,eN} % \end{macrocode} % \end{macro} % \begin{macro}[EXP]{\@@_xmp_print_date:N} % This prints the date stored in a sequence as created % by the previous command. % % \begin{macrocode} \cs_new:Npn\@@_xmp_print_date:N #1 % seq { \tl_if_blank:eTF { \seq_item:Nn #1 {1} } { \seq_item:Nn #1 {2} %year - \seq_item:Nn #1 {3} %month - \seq_item:Nn #1 {4} % day \tl_if_blank:eF { \seq_item:Nn #1 {5} } { T \seq_item:Nn #1 {5} } %hour \tl_if_blank:eF { \seq_item:Nn #1 {6} } { : \seq_item:Nn #1 {6} } %minutes \tl_if_blank:eF { \seq_item:Nn #1 {7} } { : \seq_item:Nn #1 {7} } %seconds \seq_item:Nn #1 {8} %Z,+,- \seq_item:Nn #1 {9} \tl_if_blank:eF { \seq_item:Nn #1 {10} } { : \seq_item:Nn #1 {10} } } { \seq_item:Nn #1 {1} } } % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_xmp_currentdate_tl,\l_@@_xmp_currentdate_seq} % The tl var contains the date of the log-file in PDF format, % the seq the result split with the regex. % \begin{macrocode} \tl_new:N \l_@@_xmp_currentdate_tl \seq_new:N \l_@@_xmp_currentdate_seq % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_xmp_date_get:nNN} % This checks a document property and if empty uses the current date. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_date_get:nNN #1 #2 #3 %#1 property, #2 tl var with PDF date, #3 seq for split date { \tl_set:Ne #2 { \GetDocumentProperties{#1} } \tl_if_blank:VTF #2 { \seq_set_eq:NN #3 \l_@@_xmp_currentdate_seq \tl_set_eq:NN #2 \l_@@_xmp_currentdate_tl } { \@@_xmp_date_split:VN #2 #3 } } % \end{macrocode} % \end{macro} % \subsubsection{UUID} % We need a command to generate an uuid % \begin{macro}{\@@_xmp_create_uuid:nN} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_create_uuid:nN #1 #2 { \str_set:Ne#2 {\str_lowercase:f{\tex_mdfivesum:D{#1}}} \str_set:Ne#2 { uuid: \str_range:Nnn #2{1}{8} -\str_range:Nnn#2{9}{12} -4\str_range:Nnn#2{13}{15} -8\str_range:Nnn#2{16}{18} -\str_range:Nnn#2{19}{30} } } % \end{macrocode} % \end{macro} % % \subsubsection{Purifying and escaping of strings} % % \begin{macro}{\@@_xmp_sanitize:nN} % We have to sanitize the user input. For this we pass % it through |\text_purify| and then replace a few special chars. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_sanitize:nN #1 #2 %#1 input string, #2 str with the output { \group_begin: \text_declare_purify_equivalent:Nn \& {\tl_to_str:N & } \text_declare_purify_equivalent:Nn \texttilde {\c_tilde_str} \tl_set:Ne \l_@@_tmpa_tl { \text_purify:n {#1} } \str_gset:Ne \g_@@_tmpa_str { \tl_to_str:N \l_@@_tmpa_tl } \str_greplace_all:Nnn\g_@@_tmpa_str {&}{&} \str_greplace_all:Nnn\g_@@_tmpa_str {<}{<} \str_greplace_all:Nnn\g_@@_tmpa_str {>}{>} \str_greplace_all:Nnn\g_@@_tmpa_str {"}{"} \group_end: \str_set_eq:NN #2 \g_@@_tmpa_str } \cs_generate_variant:Nn\@@_xmp_sanitize:nN {VN} % \end{macrocode} % \end{macro} % % \subsection{Language handling} % The language of the metadata is used in various attributes, so we store it in % command. % \begin{variable}{\l_@@_xmp_doclang_tl,\l_@@_xmp_metalang_tl} % \begin{macrocode} \tl_new:N \l_@@_xmp_doclang_tl \tl_new:N \l_@@_xmp_metalang_tl % \end{macrocode} % \end{variable} % % The language is retrieved at the start of the packet. We assume that % |lang| is always set and so don't use the |x-default| value of \pkg{hyperxmp}. % % \begin{variable}{\l_@@_xmp_lang_regex} % \begin{macrocode} \regex_new:N\l_@@_xmp_lang_regex \regex_set:Nn\l_@@_xmp_lang_regex {\A\[([A-Za-z\-]+)\](.*)} % \end{macrocode} % \end{variable} % % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_lang_get:nNN #1 #2 #3 % #1 text, #2 tl var for lang match (or default), #3 tl var for text { \regex_extract_once:NnN \l_@@_xmp_lang_regex {#1}\l_@@_tmpa_seq \seq_if_empty:NTF \l_@@_tmpa_seq { \tl_set:Nn #2 \l_@@_xmp_metalang_tl \tl_set:Nn #3 {#1} } { \tl_set:Ne #2 {\seq_item:Nn\l_@@_tmpa_seq{2}} \tl_set:Ne #3 {\seq_item:Nn\l_@@_tmpa_seq{3}} } } \cs_generate_variant:Nn \@@_xmp_lang_get:nNN {eNN,VNN} % \end{macrocode} % % \subsection{Filling the packet} % This tl var that holds the whole packet % \begin{variable}{\g_@@_xmp_packet_tl} % \begin{macrocode} \tl_new:N \g_@@_xmp_packet_tl % \end{macrocode} % \end{variable} % % \subsubsection{Helper commands to add lines and lists} % % \begin{macro}{\@@_xmp_add_packet_chunk:n} % This is the most basic command. % It is meant to produce a line and will use the current indent. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_chunk:n #1 { \tl_gput_right:Ne\g_@@_xmp_packet_tl { \@@_xmp_indent: \exp_not:n{#1} } } \cs_generate_variant:Nn \@@_xmp_add_packet_chunk:n {e} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_chunk:nN} % This is the most basic command. % It is meant to produce a line and will use the current indent. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_chunk:nN #1 #2 { \tl_put_right:Ne#2 { \@@_xmp_indent: \exp_not:n{#1} } } \cs_generate_variant:Nn \@@_xmp_add_packet_chunk:nN {eN} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_open:nn} % This commands opens a xml structure and increases the indent. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_open:nn #1 #2 %#1 prefix #2 name { \@@_xmp_add_packet_chunk:n {<#1:#2>} \@@_xmp_incr_indent: } \cs_generate_variant:Nn \@@_xmp_add_packet_open:nn {ne} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_open_attr:nnn} % This commands opens a xml structure too but allows also to give an % attribute. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_open_attr:nnn #1 #2 #3 %#1 prefix #2 name #3 attr { \@@_xmp_add_packet_chunk:n {<#1:#2~#3>} \@@_xmp_incr_indent: } \cs_generate_variant:Nn \@@_xmp_add_packet_open_attr:nnn {nne} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_close:nn} % This closes a structure and decreases the indent. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_close:nn #1 #2 %#1 prefix #2:name { \@@_xmp_decr_indent: \@@_xmp_add_packet_chunk:n {} } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_line:nnn} % This will produce a full line with open and closing xml. % The content is sanitized. We test if there is content to be % able to suppress data which has not be set. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_line:nnn #1 #2 #3 %#1 prefix #2 name #3 content { \tl_if_blank:nF {#3} { \@@_xmp_sanitize:nN {#3}\l_@@_tmpa_str \@@_xmp_add_packet_chunk:e {<#1:#2>\l_@@_tmpa_str} } } \cs_generate_variant:Nn \@@_xmp_add_packet_line:nnn {nne,nnV,nee} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_line:nnnN} % This will produce a full line with open and closing xml and store it in % the given tl-var. This allows to prebuild blocks and then to test if there are empty. % The content is sanitized. We test if there is content to be % able to suppress data which has not be set. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_line:nnnN #1 #2 #3 #4 %#1 prefix #2 name #3 content #4 tl_var to prebuilt. { \tl_if_blank:nF {#3} { \@@_xmp_sanitize:nN {#3}\l_@@_tmpa_str \@@_xmp_add_packet_chunk:eN {<#1:#2>\l_@@_tmpa_str} #4 } } \cs_generate_variant:Nn \@@_xmp_add_packet_line:nnnN {nneN} % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_line_attr:nnnn} % A similar command with attribute % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_line_attr:nnnn #1 #2 #3 #4 %#1 prefix #2 name #3 attribute #4 content { \tl_if_blank:nF {#4} { \@@_xmp_sanitize:nN {#4}\l_@@_tmpa_str \@@_xmp_add_packet_chunk:e {<#1:#2~#3>\l_@@_tmpa_str} } } \cs_generate_variant:Nn \@@_xmp_add_packet_line_attr:nnnn {nnee,nneV} % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_xmp_add_packet_line_default:nnnn} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_line_default:nnnn #1 #2 #3 #4 % #1 prefix #2 name #3 default #4 content { \tl_if_blank:nTF { #4 } { \tl_set:Nn \l_@@_tmpa_tl {#3} } { \tl_set:Nn \l_@@_tmpa_tl {#4} } \@@_xmp_add_packet_line:nnV {#1}{#2}\l_@@_tmpa_tl } \cs_generate_variant:Nn \@@_xmp_add_packet_line_default:nnnn {nnee} % \end{macrocode} % \end{macro} % Some data are stored as unordered (Bag) or ordered lists (Seq) or (Alt). % The first variant are for simple text without language support: % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_list_simple:nnnn #1 #2 #3 #4 %#1 prefix, #2 name, #3 type (Seq/Bag/Alt) #4 a clist { \clist_if_empty:nF { #4 } { \@@_xmp_add_packet_open:nn {#1}{#2} \@@_xmp_add_packet_open:nn {rdf}{#3} \clist_map_inline:nn {#4} { \@@_xmp_add_packet_line:nnn {rdf}{li}{##1} } \@@_xmp_add_packet_close:nn{rdf}{#3} \@@_xmp_add_packet_close:nn {#1}{#2} } } \cs_generate_variant:Nn \@@_xmp_add_packet_list_simple:nnnn {nnnV,nnne} % \end{macrocode} % Here we check also for the language. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_list:nnnn #1 #2 #3 #4 %#1 prefix, #2 name, #3 type (Seq/Bag/Alt) #4 a clist { \clist_if_empty:nF { #4 } { \@@_xmp_add_packet_open:nn {#1}{#2} \@@_xmp_add_packet_open:nn {rdf}{#3} \clist_map_inline:nn {#4} { \@@_xmp_lang_get:nNN {##1}\l_@@_tmpa_tl\l_@@_tmpb_tl % \end{macrocode} % change 2024-02-22. There should be if possible a x-default % entry as some viewers need that. So if the language is equal to the % main language we use that. This assumes that the user hasn't marked % every entry as some other language! % \begin{macrocode} \tl_if_eq:eeTF{\l_@@_tmpa_tl}{\l_@@_xmp_metalang_tl} { \@@_xmp_add_packet_line_attr:nneV {rdf}{li}{xml:lang="x-default" }\l_@@_tmpb_tl } { \@@_xmp_add_packet_line_attr:nneV {rdf}{li}{xml:lang="\l_@@_tmpa_tl" }\l_@@_tmpb_tl } } \@@_xmp_add_packet_close:nn{rdf}{#3} \@@_xmp_add_packet_close:nn {#1}{#2} } } \cs_generate_variant:Nn \@@_xmp_add_packet_list:nnnn {nnne} % \end{macrocode} % % \subsubsection{Building the main packet} % % \begin{macro}{\@@_xmp_build_packet:} % This is the main command to build the packet. % As data has to be set and collected first, it will be expanded % rather late in the document. % % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_packet: { % \end{macrocode} % Get the main languages % \begin{NOTE}{UF} % think if an error is needed for empty lang. % \end{NOTE} % \begin{macrocode} \tl_set:Ne \l_@@_xmp_doclang_tl {\GetDocumentProperties{document/lang}} \tl_set:Ne \l_@@_xmp_metalang_tl {\GetDocumentProperties{hyperref/pdfmetalang}} \tl_if_blank:VT \l_@@_xmp_metalang_tl { \cs_set_eq:NN \l_@@_xmp_metalang_tl\l_@@_xmp_doclang_tl} % \end{macrocode} % we preprocess a number of data to be able to suppress them and their schema % if there are unused. Currently only done for iptc % \begin{macrocode} \@@_xmp_build_iptc_data:N \l_@@_xmp_iptc_data_tl \tl_if_empty:NT \l_@@_xmp_iptc_data_tl { \seq_remove_all:Nn \l_@@_xmp_schema_seq { Iptc4xmpCore } } % \end{macrocode} % The start of the package. % No need to try to juggle with catcode, this is fix text % \begin{macrocode} \@@_xmp_add_packet_chunk:e {} \@@_xmp_add_packet_open:nn{x}{xmpmeta~xmlns:x="adobe:ns:meta/"} \@@_xmp_add_packet_open:ne{rdf} {RDF~xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns\c_hash_str"} % \end{macrocode} % The rdf namespaces % \begin{macrocode} \@@_xmp_add_packet_open_attr:nne {rdf}{Description}{rdf:about="" \g_@@_xmp_xmlns_tl} % \end{macrocode} % The extensions % \begin{macrocode} \@@_xmp_add_packet_open:nn{pdfaExtension}{schemas} \@@_xmp_add_packet_open:nn {rdf}{Bag} \seq_map_inline:Nn \l_@@_xmp_schema_seq { \tl_use:c { g_@@_xmp_schema_##1_tl } } \@@_xmp_add_packet_close:nn {rdf}{Bag} \@@_xmp_add_packet_close:nn {pdfaExtension}{schemas} % \end{macrocode} % Now starts the part with the data. % \begin{macrocode} % data \@@_xmp_build_pdf: \@@_xmp_build_xmpRights: \@@_xmp_build_standards: %pdfaid,pdfxid,pdfuaid \@@_xmp_build_pdfd: \@@_xmp_build_dc: \@@_xmp_build_photoshop: \@@_xmp_build_xmp: \@@_xmp_build_xmpMM: \@@_xmp_build_prism: \@@_xmp_build_iptc: \@@_xmp_build_user: %user additions % end \@@_xmp_add_packet_close:nn {rdf}{Description} \@@_xmp_add_packet_close:nn {rdf}{RDF} \@@_xmp_add_packet_close:nn {x}{xmpmeta} \int_set:Nn \l_@@_xmp_indent_int{20} \prg_replicate:nn{10}{\@@_xmp_add_packet_chunk:n {}} \int_zero:N \l_@@_xmp_indent_int \@@_xmp_add_packet_chunk:n {} } % \end{macrocode} % \end{macro} % \subsection{Building the chunks: rdf namespaces} % This is the list of external names spaces. % They are rather simple, and we store them directly % into a string. Special chars should be escaped properly, % see e.g. |\c_hash_str| for the hash. % \begin{variable}{\g_@@_xmp_xmlns_tl,\g_@@_xmp_xmlns_prop} % The string will hold the prepared chunk, the prop stores the name spaces % so that one can check on the user level for duplicates. % \begin{macrocode} \str_new:N \g_@@_xmp_xmlns_tl \prop_new:N \g_@@_xmp_xmlns_prop % \end{macrocode} % \end{variable} % \begin{macro}{\@@_xmp_xmlns_new:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_xmlns_new:nn #1 #2 { \prop_gput:Nnn \g_@@_xmp_xmlns_prop {#1}{#2} \tl_gput_right:Ne \g_@@_xmp_xmlns_tl { \@@_xmp_indent:n{4} xmlns:\exp_not:n{#1="#2"} } } % \end{macrocode} % \end{macro} % Now we fill the data. The list is more or less the same as in hyperxmp % \begin{macrocode} \@@_xmp_xmlns_new:nn {pdf} {http://ns.adobe.com/pdf/1.3/} \@@_xmp_xmlns_new:nn {xmpRights}{http://ns.adobe.com/xap/1.0/rights/} \@@_xmp_xmlns_new:nn {dc} {http://purl.org/dc/elements/1.1/} \@@_xmp_xmlns_new:nn {photoshop}{http://ns.adobe.com/photoshop/1.0/} \@@_xmp_xmlns_new:nn {xmp} {http://ns.adobe.com/xap/1.0/} \@@_xmp_xmlns_new:nn {xmpMM} {http://ns.adobe.com/xap/1.0/mm/} \@@_xmp_xmlns_new:nn {stEvt} {http://ns.adobe.com/xap/1.0/sType/ResourceEvent\c_hash_str} \@@_xmp_xmlns_new:nn {pdfaid} {http://www.aiim.org/pdfa/ns/id/} \@@_xmp_xmlns_new:nn {pdfuaid} {http://www.aiim.org/pdfua/ns/id/} \@@_xmp_xmlns_new:nn {pdfx} {http://ns.adobe.com/pdfx/1.3/} \@@_xmp_xmlns_new:nn {pdfxid} {http://www.npes.org/pdfx/ns/id/} \@@_xmp_xmlns_new:nn {prism} {http://prismstandard.org/namespaces/basic/3.0/} %\@@_xmp_xmlns_new:nn {jav} {http://www.niso.org/schemas/jav/1.0/} %\@@_xmp_xmlns_new:nn {xmpTPg} {http://ns.adobe.com/xap/1.0/t/pg/} \@@_xmp_xmlns_new:nn {stFnt} {http://ns.adobe.com/xap/1.0/sType/Font\c_hash_str} \@@_xmp_xmlns_new:nn {Iptc4xmpCore}{http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/} \@@_xmp_xmlns_new:nn {pdfaExtension}{http://www.aiim.org/pdfa/ns/extension/} \@@_xmp_xmlns_new:nn {pdfaSchema}{http://www.aiim.org/pdfa/ns/schema\c_hash_str} \@@_xmp_xmlns_new:nn {pdfaProperty}{http://www.aiim.org/pdfa/ns/property\c_hash_str} \@@_xmp_xmlns_new:nn {pdfaType} {http://www.aiim.org/pdfa/ns/type\c_hash_str} \@@_xmp_xmlns_new:nn {pdfaField}{http://www.aiim.org/pdfa/ns/field\c_hash_str} % \end{macrocode} % \subsection{Building the chunks: Extensions} % In this part local name spaces or additional names in a name space can be declared. % A \enquote{schema} declaration consist of the declaration of the name, uri and prefix % which then surrounds a bunch of property declarations. % The current code doesn't support all syntax options but sticks to % what is used in \pkg{hyperxmp} and \pkg{pdfx}. % If needed it can be extended later. % % \begin{variable}{\l_@@_xmp_schema_seq} % This variable will hold the list of prefix so that we can loop % to produce the final XML % \begin{macrocode} \seq_new:N \l_@@_xmp_schema_seq % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_xmp_schema_new:nnn} % With this command a new schema can be declared. The main tl contains the % XML wrapper code, it then includes the list of properties which are % created with the next command. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_schema_new:nnn #1 #2 #3 %#1 name #2 prefix, #3 text { \tl_if_exist:cTF { g_@@_xmp_schema_#2_tl } { \msg_warning:nnnn{pdfmeta}{xmp-defined}{schema}{#2} } { \seq_put_right:Nn \l_@@_xmp_schema_seq { #2 } \tl_new:c { g_@@_xmp_schema_#2_tl } \tl_new:c { g_@@_xmp_schema_#2_properties_tl } \tl_gput_right:cn { g_@@_xmp_schema_#2_tl } { \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn {pdfaSchema}{schema}{#1} \@@_xmp_add_packet_line:nnn {pdfaSchema}{prefix}{#2} \@@_xmp_add_packet_line:nnn {pdfaSchema}{namespaceURI}{#3} \@@_xmp_add_packet_open:nn {pdfaSchema}{property} \@@_xmp_add_packet_open:nn{rdf}{Seq} \tl_use:c { g_@@_xmp_schema_#2_properties_tl } \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn {pdfaSchema}{property} \cs_if_exist_use:c {@@_xmp_schema_#2_additions:} \@@_xmp_add_packet_close:nn{rdf}{li} } } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_xmp_property_new:nnnnn} % This adds a property to a schema. % \begin{macrocode} \prop_new:N\g_@@_xmp_schema_property_prop \cs_new_protected:Npn \@@_xmp_property_new:nnnnn #1 #2 #3 #4 #5 % %#1 schema #2 name, #3 type, #4 category #5 description { \tl_if_exist:cTF { g_@@_xmp_schema_#1_properties_tl } { \prop_get:NeNF \g_@@_xmp_schema_property_prop {#1:#2}\l_@@_tmpa_tl { \prop_gput:Nee \g_@@_xmp_schema_property_prop {#1:#2}{#3} \tl_gput_right:cn { g_@@_xmp_schema_#1_properties_tl } { \@@_xmp_add_packet_open:nn {rdf}{li~rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn {pdfaProperty}{name}{#2} \@@_xmp_add_packet_line:nnn {pdfaProperty}{valueType}{#3} \@@_xmp_add_packet_line:nnn {pdfaProperty}{category}{#4} \@@_xmp_add_packet_line:nnn {pdfaProperty}{description}{#5} \@@_xmp_add_packet_close:nn{rdf}{li} } } } { \msg_warning:nnnn{pdfmeta}{xmp-undefined}{schema}{#1} } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_add_packet_field:nnn} % This adds a field to a schema. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_add_packet_field:nnn #1 #2 #3 % %#1 name #2 valuetype #3 description { \@@_xmp_add_packet_open_attr:nnn {rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn {pdfaField}{name}{#1} \@@_xmp_add_packet_line:nnn {pdfaField}{valueType}{#2} \@@_xmp_add_packet_line:nnn {pdfaField}{description}{#3} \@@_xmp_add_packet_close:nn{rdf}{li} } % \end{macrocode} % \end{macro} % % \subsubsection{The extension data} % % The list of extension has been reviewed and compared with % the list of namespaces which can be used in pdf/A-1\footnote{While % A-1 builds on PDF 1.4 and so it probably no longer relevant, it is % not quite clear if one can remove this for A-2 and newer, so we stay on the % safe side.} % % % [1]~\url{https://www.pdfa.org/wp-content/uploads/2011/08/tn0008_predefined_xmp_properties_in_pdfa-1_2008-03-20.pdf} % and the content of the namespaces as listed here % [2]~\url{https://developer.adobe.com/xmp/docs/XMPNamespaces/pdf/} % % % \begin{description} % \item[pdf] property: Trapped. % We ignore it, it seems to validate without it. % \item[xmpMM] properties DocumentID, InstanceID, VersionID, % Renditionclass declared by hyperxmp. % Properties InstanceID and OriginalDocumentID declared by pdfx (pdfx.xmp) % With the exception of OriginalDocumentID all are already allowed and % predefined. % \begin{macrocode} \@@_xmp_schema_new:nnn {XMP~Media~Management~Schema} {xmpMM} {http://ns.adobe.com/xap/1.0/mm/} \@@_xmp_property_new:nnnnn {xmpMM} {OriginalDocumentID} {URI} {internal} {The~common~identifier~for~all~versions~and~renditions~of~a~document.} % \end{macrocode} % \item[pdfaid] properties part and conformance are declared by hyperxmp, but % no here as already in http://www.aiim.org/pdfa/ns/id/. But we declare % year so that it can be used also with older A-standards. % \begin{macro}[no-user-doc]{pdfaid~(schema)} % \begin{macrocode} \@@_xmp_schema_new:nnn {PDF/A~Identification~Schema} {pdfaid} {http://www.aiim.org/pdfa/ns/id/} \@@_xmp_property_new:nnnnn {pdfaid} {year} {Integer} {internal} {Year~of~standard} \@@_xmp_property_new:nnnnn {pdfaid} {rev} {Integer} {internal} {Revision~year~of~standard} % \end{macrocode} % \end{macro} % \item[pdfuaid] here we need (?) to declare the property % \enquote{part} and \enquote{rev}. % \begin{macro}[no-user-doc]{pdfuaid~(schema)} % \begin{macrocode} \@@_xmp_schema_new:nnn {PDF/UA~Universal~Accessibility~Schema} {pdfuaid} {http://www.aiim.org/pdfua/ns/id/} \@@_xmp_property_new:nnnnn {pdfuaid} {part} {Integer} {internal} {Part~of~ISO~14289~standard} \@@_xmp_property_new:nnnnn {pdfuaid} {rev} {Integer} {internal} {Revision~of~ISO~14289~standard} % \end{macrocode} % \end{macro} % \item[pdfx] According to [1] not an allowed schema, but it seems % to validate and allow to set the pdf/X version, \pkg{hyperxmp} % declares here the properties |GTS_PDFXVersion| and |GTS_PDFXConformance|. % Ignored as only relevant for older pdf/X version not supported by the pdfmanagement. % % \item[pdfxid] we set this so that we % can add the pdf/X version for pdf/X-4 and higher % \begin{macro}[no-user-doc]{pdfxid~(schema)} % \begin{macrocode} \@@_xmp_schema_new:nnn {PDF/X~ID~Schema} {pdfxid} {http://www.npes.org/pdfx/ns/id/} \@@_xmp_property_new:nnnnn {pdfxid} {GTS_PDFXVersion} {Text} {internal} {ID~of~PDF/X~standard} % \end{macrocode} % \end{macro} %\item[Prism] % \begin{macro}[no-user-doc]{prism~(schema)} % \begin{macrocode} \@@_xmp_schema_new:nnn {PRISM~Basic~Metadata} {prism} {http://prismstandard.org/namespaces/basic/3.0/} \@@_xmp_property_new:nnnnn {prism} {complianceProfile} {Text} {internal} {PRISM~specification~compliance~profile~to~which~this~document~adheres} \@@_xmp_property_new:nnnnn {prism} {publicationName} {Text} {external} {Publication~name} \@@_xmp_property_new:nnnnn {prism} {aggregationType} {Text} {external} {Publication~type} \@@_xmp_property_new:nnnnn {prism} {bookEdition} {Text} {external} {Edition~of~the~book~in~which~the~document~was~published} \@@_xmp_property_new:nnnnn {prism} {volume} {Text} {external} {Publication~volume~number} \@@_xmp_property_new:nnnnn {prism} {number} {Text} {external} {Publication~issue~number~within~a~volume} \@@_xmp_property_new:nnnnn {prism} {pageRange} {Text} {external} {Page~range~for~the~document~within~the~print~version~of~its~publication} \@@_xmp_property_new:nnnnn {prism} {issn} {Text} {external} {ISSN~for~the~printed~publication~in~which~the~document~was~published} \@@_xmp_property_new:nnnnn {prism} {eIssn} {Text} {external} {ISSN~for~the~electronic~publication~in~which~the~document~was~published} \@@_xmp_property_new:nnnnn {prism} {isbn} {Text} {external} {ISBN~for~the~publication~in~which~the~document~was~published} \@@_xmp_property_new:nnnnn {prism} {doi} {Text} {external} {Digital~Object~Identifier~for~the~document} \@@_xmp_property_new:nnnnn {prism} {url} {URL} {external} {URL~at~which~the~document~can~be~found} \@@_xmp_property_new:nnnnn {prism} {byteCount} {Integer} {internal} {Approximate~file~size~in~octets} \@@_xmp_property_new:nnnnn {prism} {pageCount} {Integer} {internal} {Number~of~pages~in~the~print~version~of~the~document} \@@_xmp_property_new:nnnnn {prism} {subtitle} {Text} {external} {Document's~subtitle} % \end{macrocode} % \end{macro} % \item[iptc] % \begin{macro}[no-user-doc]{iptc (schema)} % \begin{macrocode} \@@_xmp_schema_new:nnn {IPTC~Core~Schema} {Iptc4xmpCore} {http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/} \@@_xmp_property_new:nnnnn {Iptc4xmpCore} {CreatorContactInfo} {ContactInfo} {external} {Document~creator's~contact~information} \cs_new_protected:cpn { @@_xmp_schema_Iptc4xmpCore_additions: } { \@@_xmp_add_packet_open:nn{pdfaSchema}{valueType} \@@_xmp_add_packet_open:nn{rdf}{Seq} \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn{pdfaType}{type}{ContactInfo} \@@_xmp_add_packet_line:nnn{pdfaType}{namespaceURI} {http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/} \@@_xmp_add_packet_line:nnn{pdfaType}{prefix}{Iptc4xmpCore} \@@_xmp_add_packet_line:nnn{pdfaType}{description} {Basic~set~of~information~to~get~in~contact~with~a~person} \@@_xmp_add_packet_open:nn{pdfaType}{field} \@@_xmp_add_packet_open:nn{rdf}{Seq} \@@_xmp_add_packet_field:nnn{CiAdrCity}{Text} {Contact~information~city} \@@_xmp_add_packet_field:nnn{CiAdrCtry}{Text} {Contact~information~country} \@@_xmp_add_packet_field:nnn{CiAdrExtadr}{Text} {Contact~information~address} \@@_xmp_add_packet_field:nnn{CiAdrPcode}{Text} {Contact~information~local~postal~code} \@@_xmp_add_packet_field:nnn{CiAdrRegion}{Text} {Contact~information~regional~information~such~as~state~or~province} \@@_xmp_add_packet_field:nnn{CiEmailWork}{Text} {Contact~information~email~address(es)} \@@_xmp_add_packet_field:nnn{CiTelWork}{Text} {Contact~information~telephone~number(s)} \@@_xmp_add_packet_field:nnn{CiUrlWork}{Text} {Contact~information~Web~URL(s)} \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn{pdfaType}{field} \@@_xmp_add_packet_close:nn{rdf}{li} \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn{pdfaSchema}{valueType} } % \end{macrocode} % \end{macro} % \item[jav]: currently ignored % % \item[declarations] The PDF Declarations mechanism allows creation and % editing software to declare, via a PDF Declaration, a PDF file to be in % conformance with a 3rd party specification or profile % that may not be related to PDF technology. Their specification is for example % described in \url{https://pdfa.org/wp-content/uploads/2019/09/PDF-Declarations.pdf}. % % If declarations are added to the XMP-metadata they need (for pdf/A compliance) a % schema declaration. We do not add it by default but define here a command to enable % it. (This can be done in the document preamble as xmp is built only at the end.) % % % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_schema_enable_pdfd: { \@@_xmp_xmlns_new:nn {pdfd}{http://pdfa.org/declarations/} \@@_xmp_schema_new:nnn {PDF~Declarations~Schema} {pdfd} {http://pdfa.org/declarations/} \@@_xmp_property_new:nnnnn {pdfd} {declarations} {Bag~declaration} {external} {An~unordered~array~of~PDF~Declaration~entries,~where~each~PDF~Declaration~representing~a~statement~of~conformance~with~ an~identified~external~standard~or~profile,~along~with~optional~information~identifying~the~nature~of~the~claim.} % \end{macrocode} % the values are complicated so we use the additions: method to add them. % \begin{macrocode} \cs_new_protected:cpn { __pdfmeta_xmp_schema_pdfd_additions: } { \@@_xmp_add_packet_open:nn{pdfaSchema}{valueType} \@@_xmp_add_packet_open:nn{rdf}{Seq} \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn{pdfaType}{type}{claim} \@@_xmp_add_packet_line:nnn{pdfaType}{namespaceURI} {http://pdfa.org/declarations/} \@@_xmp_add_packet_line:nnn{pdfaType}{prefix}{pdfd} \@@_xmp_add_packet_line:nnn{pdfaType}{description} {A~structure~describing~properties~of~an~individual claim.} \@@_xmp_add_packet_open:nn{pdfaType}{field} \@@_xmp_add_packet_open:nn{rdf}{Seq} \@@_xmp_add_packet_field:nnn{claimReport}{Text} {A~URL~to~a~report~containing~details~of~the~specific~conformance~claim.} \@@_xmp_add_packet_field:nnn{claimCredentials}{Text} {The~claimant's~credentials.} \@@_xmp_add_packet_field:nnn{claimDate}{Text} {A~date~identifying~when~the~claim~was~made.} \@@_xmp_add_packet_field:nnn{claimBy}{Text} {The~name~of~the~organization~and/or~individual~and/or~software~making~the~claim.} \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn{pdfaType}{field} \@@_xmp_add_packet_close:nn{rdf}{li} \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn{pdfaType}{type}{declaration} \@@_xmp_add_packet_line:nnn{pdfaType}{namespaceURI} {http://pdfa.org/declarations/} \@@_xmp_add_packet_line:nnn{pdfaType}{prefix}{pdfd} \@@_xmp_add_packet_line:nnn{pdfaType}{description} {A~structure~describing~a~single~PDF~ Declaration~asserting~conformance~with~ an~externally-identified~standard~or~ profile.} \@@_xmp_add_packet_open:nn{pdfaType}{field} \@@_xmp_add_packet_open:nn{rdf}{Seq} \@@_xmp_add_packet_field:nnn{conformsTo}{Text} {A~property~containing~a~URI~specifying~the~standard~or~profile~by~the~PDF~Declaration.~This~property~is~ intended~to~mirror~the~Dublin~Core~property~dc:conformsTo.} \@@_xmp_add_packet_field:nnn{claimData}{Bag~claim} {An~unordered~array~of~claim~data,~where~each~claim~identifies~the~nature~of~the~claim.} \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn{pdfaType}{field} \@@_xmp_add_packet_close:nn{rdf}{li} \@@_xmp_add_packet_close:nn{rdf}{Seq} \@@_xmp_add_packet_close:nn{pdfaSchema}{valueType} } % \end{macrocode} % the schema should be added only once so disable it after use: % \begin{macrocode} \cs_gset_eq:NN \@@_xmp_schema_enable_pdfd: \prg_do_nothing: } % \end{macrocode} % % \end{description} % \subsection{The actual user / document data} % \subsubsection{pdf} % This builds pdf related the data with the (prefix \enquote{pdf}). % % \begin{macro}[int]{\@@_xmp_build_pdf:} % \begin{macro}[int]{ % Producer/pdfproducer, % PDFversion} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_pdf: { % \end{macrocode} % At first the producer. If not given manually we build it from the exec string % plus the version number % \begin{macrocode} \@@_xmp_add_packet_line_default:nnee {pdf}{Producer} {\c_sys_engine_exec_str-\c_sys_engine_version_str} {\GetDocumentProperties{hyperref/pdfproducer}} % \end{macrocode} % Now the PDF version % \begin{macrocode} \@@_xmp_add_packet_line:nne{pdf}{PDFVersion}{\pdf_version:} % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{xmp} % This builds the data with the (prefix \enquote{xmp}). % % \begin{macro}[int]{\@@_xmp_build_xmp:} % \begin{macro}[int]{ % CreatorTool/pdfcreator, % BaseUrl/baseurl} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_xmp: { % \end{macrocode} % The creator % \begin{macrocode} \@@_xmp_add_packet_line_default:nnee {xmp}{CreatorTool} {LaTeX} { \GetDocumentProperties{hyperref/pdfcreator} } % \end{macrocode} % The baseurl % \begin{macrocode} \@@_xmp_add_packet_line_default:nnee {xmp}{BaseURL}{} { \GetDocumentProperties{hyperref/baseurl} } % \end{macrocode} % CreationDate % \begin{macrocode} \@@_xmp_date_get:nNN {document/creationdate}\l_@@_tmpa_tl\l_@@_tmpa_seq \@@_xmp_add_packet_line:nne{xmp}{CreateDate}{\@@_xmp_print_date:N\l_@@_tmpa_seq} \pdfmanagement_add:nne{Info}{CreationDate}{(\l_@@_tmpa_tl)} % \end{macrocode} % ModifyDate % \begin{macrocode} \@@_xmp_date_get:nNN {document/moddate}\l_@@_tmpa_tl\l_@@_tmpa_seq \@@_xmp_add_packet_line:nne{xmp}{ModifyDate}{\@@_xmp_print_date:N\l_@@_tmpa_seq} \pdfmanagement_add:nne{Info}{ModDate}{(\l_@@_tmpa_tl)} % \end{macrocode} % MetadataDate % \begin{macrocode} \@@_xmp_date_get:nNN {hyperref/pdfmetadate}\l_@@_tmpa_tl\l_@@_tmpa_seq \@@_xmp_add_packet_line:nne{xmp}{MetadataDate}{\@@_xmp_print_date:N\l_@@_tmpa_seq} % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Standards} % The metadata for standards are taken from the |pdfstandard| key % of |\DocumentMetadata|. % The values for A-standards are taken from the property, X and UA are currently % taken from the document container, this should be changed when merging of % standards are possible. % \begin{macro}{\@@_xmp_build_standards:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_standards: { \@@_xmp_add_packet_line:nne {pdfaid}{part}{\pdfmeta_standard_item:n{level}} \@@_xmp_add_packet_line:nne {pdfaid}{conformance}{\pdfmeta_standard_item:n{conformance}} \int_compare:nNnTF {0\pdfmeta_standard_item:n{level}}<{4} {\@@_xmp_add_packet_line:nne {pdfaid}{year} {\pdfmeta_standard_item:n{year}}} {\@@_xmp_add_packet_line:nne {pdfaid}{rev} {\pdfmeta_standard_item:n{year}}} \@@_xmp_add_packet_line:nne {pdfxid}{GTS_PDFXVersion}{\GetDocumentProperties{document/pdfstandard-X}} \pdfmanagement_get_documentproperties:nNT {document/pdfstandard-UA}\l_@@_tmpa_tl { \@@_xmp_add_packet_line:nne {pdfuaid}{part}{\exp_last_unbraced:No\use_i:nn \l_@@_tmpa_tl} \@@_xmp_add_packet_line:nne {pdfuaid}{rev}{\exp_last_unbraced:No\use_ii:nn \l_@@_tmpa_tl} } } % \end{macrocode} % \end{macro} % % \subsection{Declarations} % See \url{https://pdfa.org/wp-content/uploads/2019/09/PDF-Declarations.pdf} % % \begin{variable}{\g_@@_xmp_pdfd_data_prop} % This holds the data for declarations. % \begin{macrocode} \prop_new:N \g_@@_xmp_pdfd_data_prop % \end{macrocode} % \end{variable} % the main building command used in the xmp generation % \begin{macro}{\@@_xmp_build_pdfd:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_pdfd: { \prop_if_empty:NF\g_@@_xmp_pdfd_data_prop { \@@_xmp_add_packet_open:nn{pdfd}{declarations} \@@_xmp_add_packet_open:nn{rdf}{Bag} \prop_map_inline:Nn \g_@@_xmp_pdfd_data_prop { \@@_xmp_build_pdfd_claim:nn{##1}{##2} } \@@_xmp_add_packet_close:nn{rdf}{Bag} \@@_xmp_add_packet_close:nn{pdfd}{declarations} } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_build_pdfd_claim:nn} % This build the xml for one claim. If there is no % claimData only the conformsTo is output. % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_pdfd_claim:nn #1#2 { \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn{pdfd}{conformsTo}{#1} \tl_if_empty:nF {#2} { \@@_xmp_add_packet_open:nn{pdfd}{claimData} \@@_xmp_add_packet_open:nn{rdf}{Bag} #2 \@@_xmp_add_packet_close:nn{rdf}{Bag} \@@_xmp_add_packet_close:nn{pdfd}{claimData} } \@@_xmp_add_packet_close:nn{rdf}{li} } % \end{macrocode} % \end{macro} % % \subsection{Photoshop} % \begin{macro}{\@@_xmp_build_photoshop:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_photoshop: { % \end{macrocode} % pdfauthortitle/photoshop:AuthorsPosition % \begin{macrocode} \@@_xmp_add_packet_line:nne{photoshop}{AuthorsPosition} { \GetDocumentProperties{hyperref/pdfauthortitle} } % \end{macrocode} % pdfcaptionwriter/photoshop:CaptionWriter % \begin{macrocode} \@@_xmp_add_packet_line:nne{photoshop}{CaptionWriter} { \GetDocumentProperties{hyperref/pdfcaptionwriter} } % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % % \subsection{XMP Media Management} % \begin{macro}{\@@_xmp_build_xmpMM:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_xmpMM: { % \end{macrocode} % pdfdocumentid / xmpMM:DocumentID % \begin{macrocode} \str_set:Ne\l_@@_tmpa_str {\GetDocumentProperties{hyperref/pdfdocumentid}} \str_if_empty:NT \l_@@_tmpa_str { \@@_xmp_create_uuid:nN {\jobname\GetDocumentProperties{hyperref/pdftitle}} \l_@@_tmpa_str } \@@_xmp_add_packet_line:nnV{xmpMM}{DocumentID} \l_@@_tmpa_str % \end{macrocode} % pdfinstanceid / xmpMM:InstanceID % \begin{macrocode} \str_set:Ne\l_@@_tmpa_str {\GetDocumentProperties{hyperref/pdfinstanceid}} \str_if_empty:NT \l_@@_tmpa_str { \@@_xmp_create_uuid:nN {\jobname\l_@@_xmp_currentdate_tl} \l_@@_tmpa_str } \@@_xmp_add_packet_line:nnV{xmpMM}{InstanceID} \l_@@_tmpa_str % \end{macrocode} % pdfversionid/xmpMM:VersionID % \begin{macrocode} \@@_xmp_add_packet_line:nne{xmpMM}{VersionID} { \GetDocumentProperties{hyperref/pdfversionid} } % \end{macrocode} % pdfrendition/xmpMM:RenditionClass % \begin{macrocode} \@@_xmp_add_packet_line:nne{xmpMM}{RenditionClass} { \GetDocumentProperties{hyperref/pdfrendition} } % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % % \subsection{Rest of dublin Core data} % % \begin{macro}[int]{\@@_xmp_build_dc:} % \begin{macro}[int]{ % dc:creator/pdfauthor, % dc:subject/pdfkeywords, % dc:type/pdftype, % dc:publisher/pdfpublisher, % dc:description/pdfsubject, % dc:language/lang/pdflang, % dc:identifier/pdfidentifier, % photoshop:AuthorsPosition/pdfauthortitle, % photoshop:CaptionWriter/pdfcaptionwriter % } % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_dc: { % \end{macrocode} % pdfauthor/dc:creator % \begin{macrocode} \@@_xmp_add_packet_list:nnne {dc}{creator}{Seq} { \GetDocumentProperties{hyperref/pdfauthor} } \int_compare:nNnT {0\pdfmeta_standard_item:n{level}}={1} { \pdfmanagement_remove:nn{Info}{Author} } % \end{macrocode} % pdftitle/dc:title. This is rather complex as we want to support a list % with different languages. % \begin{macrocode} \@@_xmp_add_packet_list:nnne {dc}{title}{Alt} { \GetDocumentProperties{hyperref/pdftitle} } % \end{macrocode} % pdfkeywords/dc:subject % \begin{macrocode} \@@_xmp_add_packet_list:nnne {dc}{subject}{Bag} { \GetDocumentProperties{hyperref/pdfkeywords} } \int_compare:nNnT {0\pdfmeta_standard_item:n{level}}={1} { \pdfmanagement_remove:nn{Info}{Keywords} } % \end{macrocode} % pdftype/dc:type % \begin{macrocode} \pdfmanagement_get_documentproperties:nNTF { hyperref/pdftype } \l_@@_tmpa_tl { \@@_xmp_add_packet_list_simple:nnnV {dc}{type}{Bag}\l_@@_tmpa_tl } { \@@_xmp_add_packet_list_simple:nnnn {dc}{type}{Bag}{Text} } % \end{macrocode} % pdfpublisher/dc:publisher % \begin{macrocode} \@@_xmp_add_packet_list:nnne {dc}{publisher}{Bag} { \GetDocumentProperties{hyperref/pdfpublisher} } % \end{macrocode} % pdfsubject/dc:description % \begin{macrocode} \@@_xmp_add_packet_list:nnne {dc}{description}{Alt} {\GetDocumentProperties{hyperref/pdfsubject}} % \end{macrocode} % lang/pdflang/dc:language % \begin{macrocode} \@@_xmp_add_packet_list_simple:nnnV {dc}{language}{Bag}\l_@@_xmp_doclang_tl % \end{macrocode} % pdfidentifier/dc:identifier % \begin{macrocode} \@@_xmp_add_packet_line:nne{dc}{identifier} { \GetDocumentProperties{hyperref/pdfidentifier} } % \end{macrocode} % pdfdate/dc:date % \begin{macrocode} \@@_xmp_date_get:nNN {hyperref/pdfdate}\l_@@_tmpa_tl\l_@@_tmpa_seq \@@_xmp_add_packet_list_simple:nnne {dc}{date}{Seq}{\@@_xmp_print_date:N\l_@@_tmpa_seq} % \end{macrocode} % The file format % \begin{macrocode} \@@_xmp_add_packet_line:nnn{dc}{format}{application/pdf} % \end{macrocode} % The source % \begin{macrocode} \@@_xmp_add_packet_line_default:nnee {dc}{source} { \c_sys_jobname_str.tex } { \GetDocumentProperties{hyperref/pdfsource} } % \end{macrocode} % \begin{macrocode} \@@_xmp_add_packet_list:nnne{dc}{rights}{Alt} {\GetDocumentProperties{hyperref/pdfcopyright}} % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{xmpRights} % \begin{macro}{\@@_xmp_build_xmpRights:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_xmpRights: { \@@_xmp_add_packet_line:nne {xmpRights} {WebStatement} {\GetDocumentProperties{hyperref/pdflicenseurl}} \@@_xmp_add_packet_line:nne {xmpRights} {Marked} { \str_case:en {\GetDocumentProperties{document/copyright}} { {true}{True} {false}{False} } } } % \end{macrocode} % \end{macro} % % \subsection{IPTC} % We want the block and also the resources only if they are actually used. So we pack % them first in a local variable % \begin{variable}{\l_@@_xmp_iptc_data_tl} % \begin{macrocode} \tl_new:N\l_@@_xmp_iptc_data_tl % \end{macrocode} % \end{variable} % \begin{macro}{\@@_xmp_build_iptc_data:N} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_iptc_data:N #1 { \tl_clear:N #1 \@@_xmp_incr_indent:\@@_xmp_incr_indent:\@@_xmp_incr_indent:\@@_xmp_incr_indent: \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiAdrExtadr} {\GetDocumentProperties{hyperref/pdfcontactaddress}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiAdrCity} {\GetDocumentProperties{hyperref/pdfcontactcity}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiAdrPcode} {\GetDocumentProperties{hyperref/pdfcontactpostcode}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiAdrCtry} {\GetDocumentProperties{hyperref/pdfcontactcountry}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiTelWork} {\GetDocumentProperties{hyperref/pdfcontactphone}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiEmailWork} {\GetDocumentProperties{hyperref/pdfcontactemail}} #1 \@@_xmp_add_packet_line:nneN {Iptc4xmpCore}{CiUrlWork} {\GetDocumentProperties{hyperref/pdfcontacturl}} #1 \@@_xmp_decr_indent:\@@_xmp_decr_indent:\@@_xmp_decr_indent:\@@_xmp_decr_indent: } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_xmp_build_iptc:} % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_iptc: { \tl_if_empty:NF\l_@@_xmp_iptc_data_tl { \@@_xmp_add_packet_open_attr:nnn {Iptc4xmpCore}{CreatorContactInfo}{rdf:parseType="Resource"} \tl_gput_right:Ne\g_@@_xmp_packet_tl { \l_@@_xmp_iptc_data_tl } \@@_xmp_add_packet_close:nn {Iptc4xmpCore}{CreatorContactInfo} } } % \end{macrocode} % \end{macro} % % \subsection{Prism} % \begin{macro}[int]{\@@_xmp_build_prism:} % \begin{macro}[int] % { % complianceProfile, % prism:subtitle/pdfsubtitle, % } % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_prism: { % \end{macrocode} % The compliance profile is a fix value taken from \pkg{hyperxmp} % \begin{macrocode} \@@_xmp_add_packet_line:nnn {prism}{complianceProfile} {three} % \end{macrocode} % the next two values can take an optional language argument. % First subtitle % \begin{macrocode} \@@_xmp_lang_get:eNN {\GetDocumentProperties{hyperref/pdfsubtitle}} \l_@@_tmpa_tl\l_@@_tmpb_tl \@@_xmp_add_packet_line_attr:nneV {prism}{subtitle} {xml:lang="\l_@@_tmpa_tl"} \l_@@_tmpb_tl % \end{macrocode} % Then publicationName % \begin{macrocode} \@@_xmp_lang_get:eNN {\GetDocumentProperties{hyperref/pdfpublication}} \l_@@_tmpa_tl\l_@@_tmpb_tl \@@_xmp_add_packet_line_attr:nneV {prism}{publicationName} {xml:lang="\l_@@_tmpa_tl"} \l_@@_tmpb_tl % \end{macrocode} % Now the rest % \begin{macrocode} \@@_xmp_add_packet_line:nne {prism}{bookEdition} {\GetDocumentProperties{hyperref/pdfbookedition}} \@@_xmp_add_packet_line:nne {prism}{aggregationType} {\GetDocumentProperties{hyperref/pdfpubtype}} \@@_xmp_add_packet_line:nne {prism}{volume} {\GetDocumentProperties{hyperref/pdfvolumenum}} \@@_xmp_add_packet_line:nne {prism}{number} {\GetDocumentProperties{hyperref/pdfissuenum}} \@@_xmp_add_packet_line:nne {prism}{pageRange} {\GetDocumentProperties{hyperref/pdfpagerange}} \@@_xmp_add_packet_line:nne {prism}{issn} {\GetDocumentProperties{hyperref/pdfissn}} \@@_xmp_add_packet_line:nne {prism}{eIssn} {\GetDocumentProperties{hyperref/pdfeissn}} \@@_xmp_add_packet_line:nne {prism}{doi} {\GetDocumentProperties{hyperref/pdfdoi}} \@@_xmp_add_packet_line:nne {prism}{url} {\GetDocumentProperties{hyperref/pdfurl}} % \end{macrocode} % The page count is take from the previous run or from % pdfnumpages. % \begin{macrocode} \tl_set:Ne \l_@@_tmpa_tl { \GetDocumentProperties{hyperref/pdfnumpages} } \@@_xmp_add_packet_line:nne {prism}{pageCount} {\tl_if_blank:VTF \l_@@_tmpa_tl {\PreviousTotalPages}{\l_@@_tmpa_tl}} % \end{macrocode} % \begin{macrocode} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{User additions} % % \begin{variable}{\g_@@_xmp_user_packet_str } % \begin{macrocode} \tl_new:N \g_@@_xmp_user_packet_tl % \end{macrocode} % \end{variable} % \begin{macro}{\@@_xmp_build_user: } % \begin{macrocode} \cs_new_protected:Npn \@@_xmp_build_user: { \int_zero:N \l_@@_xmp_indent_int \g_@@_xmp_user_packet_tl \int_set:Nn \l_@@_xmp_indent_int {3} } % \end{macrocode} % \end{macro} % \subsection{Activating the metadata} % We don't try to get the byte count. So we can put everything % in the |shipout/lastpage| hook % \begin{macrocode} \AddToHook{shipout/lastpage} { \bool_if:NT\g_@@_xmp_bool { \str_if_exist:NTF\c_sys_timestamp_str { \tl_set_eq:NN \l_@@_xmp_currentdate_tl \c_sys_timestamp_str } { \file_get_timestamp:nN{\jobname.log}\l_@@_xmp_currentdate_tl } \@@_xmp_date_split:VN\l_@@_xmp_currentdate_tl\l_@@_xmp_currentdate_seq \@@_xmp_build_packet: \exp_args:No \__pdf_backend_metadata_stream:n {\g_@@_xmp_packet_tl} \pdfmanagement_add:nne {Catalog} {Metadata}{\pdf_object_ref_last:} \bool_if:NT \g_@@_xmp_export_bool { \iow_open:Nn\g_tmpa_iow{\g_@@_xmp_export_str.xmpi} \exp_args:NNo\iow_now:Nn\g_tmpa_iow{\g_@@_xmp_packet_tl} \iow_close:N\g_tmpa_iow } } } % \end{macrocode} % \subsection{User commands} % \begin{macro}{\pdfmeta_xmp_add:n } % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_xmp_add:n #1 { \tl_gput_right:Nn \g_@@_xmp_user_packet_tl { \@@_xmp_add_packet_chunk:n { #1 } } } % \end{macrocode} % \end{macro} % \begin{macro}{\pdfmeta_xmp_xmlns_new:nn } % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_xmp_xmlns_new:nn #1 #2 { \prop_if_in:NnTF \g_@@_xmp_xmlns_prop {#1} {\msg_warning:nnnn{pdfmeta}{xmp-defined}{xmlns~namespace}{#1}} {\@@_xmp_xmlns_new:nn {#1}{#2}} } % \end{macrocode} % \end{macro} % \begin{macro}{\pdfmeta_xmp_schema_new:nnn} % \begin{macrocode} \cs_set_eq:NN \pdfmeta_xmp_schema_new:nnn \@@_xmp_schema_new:nnn % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfmeta_xmp_property_new:nnnnn} % \begin{macrocode} \cs_set_eq:NN \pdfmeta_xmp_property_new:nnnnn \@@_xmp_property_new:nnnnn % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfmeta_xmp_add_declaration:n,\pdfmeta_xmp_add_declaration:e} % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_xmp_add_declaration:n #1 %conformsTo uri { \@@_xmp_schema_enable_pdfd: \prop_gput:Nnn\g_@@_xmp_pdfd_data_prop{#1}{} } \cs_generate_variant:Nn \pdfmeta_xmp_add_declaration:n {e} % \end{macrocode} % \end{macro} % \begin{macro}{\pdfmeta_xmp_add_declaration:nnnnn,\pdfmeta_xmp_add_declaration:ennnn} % \begin{macrocode} \cs_new_protected:Npn \pdfmeta_xmp_add_declaration:nnnnn #1#2#3#4#5 %#1=conformsTo uri, #2 claimBy, #3 claimDate #4 claimCredentials #4 claimReport { \@@_xmp_schema_enable_pdfd: \tl_set:Nn \l_@@_tmpa_tl { \@@_xmp_add_packet_open_attr:nnn{rdf}{li}{rdf:parseType="Resource"} \@@_xmp_add_packet_line:nnn{pdfd}{claimBy}{#2} \@@_xmp_add_packet_line:nnn{pdfd}{claimDate}{#3} \@@_xmp_add_packet_line:nnn{pdfd}{claimCredentials}{#4} \@@_xmp_add_packet_line:nnn{pdfd}{claimReport}{#5} \@@_xmp_add_packet_close:nn{rdf}{li} } \prop_get:NnNT \g_@@_xmp_pdfd_data_prop {#1}\l_@@_tmpb_tl { \tl_concat:NNN \l_@@_tmpa_tl \l_@@_tmpa_tl \l_@@_tmpb_tl } \prop_gput:Nno\g_@@_xmp_pdfd_data_prop{#1} { \l_@@_tmpa_tl } } \cs_generate_variant:Nn\pdfmeta_xmp_add_declaration:nnnnn {e,eee} % \end{macrocode} % \end{macro} % % \subsection{Default declarations} % The two declarations will be required quite often with ua-2, so we % provide some interface. % \begin{macro}{\@@_xmp_wtpdf_reuse_declaration:,\@@_xmp_wtpdf_accessibility_declaration:} % \begin{macrocode} \cs_new:Npn \@@_xmp_iso_today: { \int_use:N\c_sys_year_int- \int_compare:nNnT {\c_sys_month_int} < {10}{0} \int_use:N\c_sys_month_int - \int_compare:nNnT {\c_sys_day_int} < {10}{0} \int_use:N\c_sys_day_int } \cs_new_protected:Npn \@@_xmp_wtpdf_reuse_declaration: { \pdfmeta_xmp_add_declaration:eeenn {http://pdfa.org/declarations/wtpdf\c_hash_str reuse1.0} {LaTeX~Project} {\@@_xmp_iso_today:}{}{} } \cs_new_protected:Npn \@@_xmp_wtpdf_accessibility_declaration: { \pdfmeta_xmp_add_declaration:ennnn {http://pdfa.org/declarations/wtpdf\c_hash_str accessibility1.0} {LaTeX~Project} {\@@_xmp_iso_today:}{}{} } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex