diff options
Diffstat (limited to 'macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx')
-rw-r--r-- | macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx | 1578 |
1 files changed, 1569 insertions, 9 deletions
diff --git a/macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx b/macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx index d284c399a9..8e83708d88 100644 --- a/macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx +++ b/macros/latex/contrib/pdfmanagement-testphase/l3pdfmeta.dtx @@ -23,10 +23,9 @@ % for those people who are interested. % %<*driver> -\RequirePackage{pdfmanagement-testphase} \DocumentMetadata{pdfstandard=A-2b} \documentclass[full]{l3doc} -\usepackage{array,booktabs,hyperxmp} +\usepackage{array,booktabs} \hypersetup{pdfauthor=The LaTeX Project,pdftitle=l3pdfmeta (LaTeX PDF management testphase bundle)} \begin{document} @@ -50,7 +49,7 @@ % }^^A % } % -% \date{Version 0.95r, released 2022-08-24} +% \date{Version 0.95s, released 2022-09-26} % % \maketitle % \begin{documentation} @@ -372,6 +371,193 @@ % It also sets the seed for random functions. % \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{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 datas 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{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 should be added manually. +% +% \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. +% \end{function} +% % \end{documentation} % % \begin{implementation} @@ -381,7 +567,7 @@ % \begin{macrocode} %<@@=pdfmeta> %<*header> -\ProvidesExplPackage{l3pdfmeta}{2022-08-24}{0.95r} +\ProvidesExplPackage{l3pdfmeta}{2022-09-26}{0.95s} {PDF-Standards---LaTeX PDF management testphase bundle} %</header> % \end{macrocode} @@ -395,11 +581,15 @@ \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} +% \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 +\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)} @@ -889,8 +1079,8 @@ { \pdf_object_if_exist:nF { __color_icc_ #1 } { - \pdf_object_new:nn { __color_icc_ #1 }{fstream} - \pdf_object_write:nx { __color_icc_ #1 } + \pdf_object_new:n { __color_icc_ #1 } + \pdf_object_write:nnx { __color_icc_ #1 } { fstream } { {/N\c_space_tl \prop_item:cn{c_@@_colorprofile_#1}{N} @@ -982,6 +1172,1376 @@ \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:nnx {Info}{Producer}{(\c_sys_engine_exec_str-\c_sys_engine_version_str)} + \pdfmanagement_add:nnx {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}}, + xmp .bool_gset:N = \g_@@_xmp_bool + } +% \end{macrocode} +% \subsection{Messages} +% \begin{macrocode} +\msg_new:nnn{pdfmeta}{namespace-defined}{The~xmlns~namespace~`#1`~is~already~declared} +% \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 splitted 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 splitted date + { + \tl_set:Nx #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:Nx#2 {\str_lowercase:f{\tex_mdfivesum:D{#1}}} + \str_set:Nx#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:Nx \l_@@_tmpa_tl { \text_purify:n {#1} } + \str_gset:Nx \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:Nx #2 {\seq_item:Nn\l_@@_tmpa_seq{2}} + \tl_set:Nx #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:Nx\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:Nx#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 {</#1:#2>} + } +% \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</#1:#2>} + } + } +\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</#1:#2>} #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</#1:#2>} + } + } +\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). +% 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 + \@@_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:Nx \l_@@_xmp_doclang_tl {\GetDocumentProperties{document/lang}} + \tl_set:Nx \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 + {<?xpacket~begin="\@@_xmp_generate_bom:"~id="W5M0MpCehiHzreSzNTczkc9d"?>} + \@@_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_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 {<?xpacket~end="w"?>} + } +% \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,\@@_xmp_xmlns_new:nx} +% \begin{macrocode} +\cs_new_protected:Npn \@@_xmp_xmlns_new:nn #1 #2 + { + \prop_gput:Nnn \g_@@_xmp_xmlns_prop {#1}{#2} + \tl_gput_right:Nx \g_@@_xmp_xmlns_tl + { + \@@_xmp_indent:n{4} xmlns:\exp_not:n{#1="#2"} + } + } +\cs_generate_variant:Nn \@@_xmp_xmlns_new:nn {nx} +% \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:nx {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:nx {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:nx {pdfaSchema}{http://www.aiim.org/pdfa/ns/schema\c_hash_str} +\@@_xmp_xmlns_new:nx {pdfaProperty}{http://www.aiim.org/pdfa/ns/property\c_hash_str} +\@@_xmp_xmlns_new:nx {pdfaType} {http://www.aiim.org/pdfa/ns/type\c_hash_str} +\@@_xmp_xmlns_new:nx {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 + { + \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:nnn} +% This adds a property to a schema. +% \begin{macrocode} +\cs_new_protected:Npn \@@_xmp_property_new:nnnnn #1 #2 #3 #4 #5 % + %#1 schema #2 name, #3 type, #4 category #5 description + { + \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} + } + } +% \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. We ignore OriginalDocumentID until requested. +% \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}{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} +% \end{macrocode} +% \end{macro} +% \item[pdfuaid] here we need to declare the property \enquote{part}. +% \begin{macro}{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} +% \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}{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}{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{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} +% \item[jav]: currently ignored +% +% \end{description} +% \subsection{The actual user / document data} + +% \subsubsection{pdf} +% This builds pdf related the data with the (prefix \enquote{pdf}). +% +% \begin{macro}{\@@_xmp_build_pdf:} +% \begin{macro}{ +% 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}{\@@_xmp_build_xmp:} +% \begin{macro}{ +% 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/pdfcreationdate}\l_@@_tmpa_tl\l_@@_tmpa_seq + \@@_xmp_add_packet_line:nne{xmp}{CreateDate}{\@@_xmp_print_date:N\l_@@_tmpa_seq} + \pdfmanagement_add:nnx{Info}{CreationDate}{(\l_@@_tmpa_tl)} +% \end{macrocode} +% ModifyDate +% \begin{macrocode} + \@@_xmp_date_get:nNN + {document/pdfmoddate}\l_@@_tmpa_tl\l_@@_tmpa_seq + \@@_xmp_add_packet_line:nne{xmp}{ModifyDate}{\@@_xmp_print_date:N\l_@@_tmpa_seq} + \pdfmanagement_add:nnx{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}} + \@@_xmp_add_packet_line:nne {pdfaid}{year} {\pdfmeta_standard_item:n{year}} + \@@_xmp_add_packet_line:nne + {pdfxid}{GTS_PDFXVersion}{\GetDocumentProperties{document/pdfstandard-X}} + \@@_xmp_add_packet_line:nne + {pdfuaid}{part}{\GetDocumentProperties{document/pdfstandard-UA}} + } +% \end{macrocode} +% \end{macro} +% + +% \subsubsection{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:Nx\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:Nx\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}{\@@_xmp_build_dc:} +% \begin{macro}{ +% 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} + \tl_if_blank:eTF { \GetDocumentProperties{hyperref/pdftype} } + { + \@@_xmp_add_packet_list:nnne {dc}{type}{Bag}{Text} + } + { + \@@_xmp_add_packet_list:nnne {dc}{type}{Bag} + { { \GetDocumentProperties{hyperref/pdftype} }} + } +% \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:nnne {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: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}} + } +% \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:Nx\g_@@_xmp_packet_tl { \l_@@_xmp_iptc_data_tl } + \@@_xmp_add_packet_close:nn + {Iptc4xmpCore}{CreatorContactInfo} + } + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Prism} +% \begin{macro}{\@@_xmp_build_prism:} +% \begin{macro} +% { +% 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:Nx \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 + { + \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:nnx {Catalog} {Metadata}{\pdf_object_ref_last:} + } + } +% \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:nnn{pdfmeta}{namespace-defined}{#1}} + {\@@_xmp_xmlns_new:nn {#1}{#2}} + } +% \end{macrocode} +% \end{macro} % \begin{macrocode} %</package> % \end{macrocode} |