% \iffalse meta-comment % %% File: l3pdffile.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 package 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=l3pdffile (LaTeX PDF management testphase bundle)} \usepackage{caption} \providecommand\potentialclash{\noindent\llap{\dbend\ }} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3pdffile} module\\ Embedding and referencing files in a PDF ^^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.96o, released 2024-12-20} % % \maketitle % \begin{documentation} % % \section{\pkg{l3pdffile} documentation} % \subsection{Introduction} % \subsubsection{Background} % External files can be referenced from a PDF in three ways: % \begin{enumerate} % \item through an annotation of type Link, % \item by referencing a local file in the file system, % \item by embedding the file directly into the PDF % \end{enumerate} % Case 1 (Links) are created with the \cs{pdfannot} commands. % This module handles the two other cases. Actually from the view % of the PDF format they are quite similar: Case 2 is case 3 without the stream % object and without the /EF entry in the /Filespec dictionary (this points to the % stream object of the file). Not embedding the file makes the PDF smaller. But it is % also less portable: the files can only be found if they are in the right location % relative to the PDF. The normal case is to embed the file. % % The tasks to embed and reference such a file are % \begin{enumerate} % \item Embed the file in a stream. % \item Create a Filespec dictionary which references the stream object in the /EF % dictionary: % \begin{verbatim} % << % /Type /Filespec % /F (l3pdffile.dtx) % /UF (l3pdffile.dtx) % /AFRelationship /Source % /EF <> %case 3, embedded file % >> % \end{verbatim} % The file names in the /UF and /F value don't need to be identical to the % name of file on the disc. It is quite possible to embed a \texttt{zzz.tex} % and name it \texttt{blub.tex}. The second name is then what the user will see % in the attachment list or in the properties of an annotation. % %\item Reference the Filespec dictionary so that the user can access the file. % This can be done in various way: % \begin{enumerate} % \item With an annotation (/Subtype/FileAttachment). This is done by % \pkg{attachfile}, \pkg{attachfile2} and \pkg{intopdf}. % Typical entries of such an annotation are: % % \medskip % \begin{tabular}{lll} % key & value type & notes\\\hline % /FS & object reference &(Filespec dictionary)\\ % /Name & name & /Graph, /PushPin, /Paperclip, /Tag\\ % /Contents & text string & optional but recommended\\ % /F & integer & Flags\\ % /AP & dictionary & Appearance (required if rectangle >0) \\ % /AS & name\\ % \end{tabular} % % The |/AP| takes precedence over Border and similar keys. % \item Through an entry in the |/EmbeddedFiles| name tree. % This is what \pkg{embedfiles} does. % \begin{verbatim} % 20 0 obj %Document Name tree % <> % endobj % 21 0 obj %Embedded Files Name dictionary % <> % endobj % \end{verbatim} % The strings (keys) in the |/Names| dictionary must be sorted lexically. % But they don't have to be the file name or anything related to % the file name. The resource management code uses l3ef0001, l3ef0002~\ldots, % which allows up to 9999 files. % The key can be needed to identify the start file in a collection, % so their relation to the files are stored in a property list. % % \item Through the |/AF| key in various objects (pdf 2.0). % The value is normally an array of object % references, but it can also be a name which is mapped to an array in /Properties: % \begin{verbatim} % /AF /NamedAF BDC % /Properties <> % } % {example-image.eps} % } % \tl_set:Ne \l_my_fileobj_tl {\pdf_object_ref_last:} % \end{verbatim} % % \begin{itemize} % \item The |/Params| dictionary is not always required, but the commands of % these module will prefill them as shown in the examples. A |/CreationDate| entry % has to be added explicitly as there is no sensible way % to retrieve this automatically. % \item The mimetype (in the |/Subtype|) should be properly escaped. % So for example % \begin{verbatim} % \pdfdict_put:nne{l_pdffile}{Subtype}{/application\string#2Fx-tex} % \end{verbatim} % But while it is possible to set the subtype like this, it is normally better % to rely on the file extension and to let the code autodetect the subtype. % This module contains a property list with maps a number of file extensions % to mimetypes and the commands try to detect and fill the mimetype automatically. % \item The dictionary can contain additional keys (|/Filter|, |/DecodeParms|), % see the pdf reference. % \end{itemize} % % \subsubsection{Task 2: Creating the \texttt{/Filespec} dictionary} % The |/Filespec| dictionary is a simple dictionary object, and can also % be created in various ways. If it refers to an embedded file it should % reference it in the |/EF| key. % % \subsubsection{Task 3: Referencing the \texttt{/Filespec} dictionary} % % Using the dictionary reference in annotations and |/AF| keys is unproblematic. % % \potentialclash % But to add it to the |/EmbeddedFiles| name tree so that it appears in the % attachment panel requires special care: % This name tree is a global resource and uncoordinated access can lead to % clashes and files that are not visible or inaccessible. % The access here is managed by the \pkg{l3pdfmanagement} module:\\[\smallskipamount]% % |\pdfmanagement_add:nne{Catalog/Names}{EmbeddedFiles}{|\meta{objref}|}| % % \subsection{Commands and tools of these module} % \begin{function}{file, file/Params, file/streamParams,file/Filespec} % The module predefines and uses a number of local dictionaries for the % components of the stream and the |/Filespec| object. These dictionaries are % then used by the \cs{pdffile_embed_XX}. % The content of these dictionaries can be changed by users with the commands % from the \pkg{l3pdfdict} module, but it should be done only locally % to avoid side effects on uses by other packages/commands. % The preset values are of these dictionaries are shown in table~\ref{tab:filedict}. % \end{function} % \begin{table} % \caption{Preset values in the file dictionaries\label{tab:filedict}} % \hspace*{-3cm}\begin{tabular}{lll} % dictionary & key & value \\\hline % l\_pdffile & Type & /EmbeddedFile\\ % l\_pdffile/Params& Size & |\file_size:n{\l_pdffile_source_name_str}|\\ % l\_pdffile/Params& ModDate & |(\file_timestamp:n {\l_pdffile_source_name_str})|\\ % l\_pdffile/Params& CheckSum & |(\file_mdfive_hash:n{\l_pdffile_source_name_str})|\\ % l\_pdffile/streamParams& & a /ModDate entry with year/month/date \\ % & & (used with \cs{pdffile_embed_stream:nnn})\\ % l\_pdffile/Filespec & Type & /Filespec\\ % l\_pdffile/Filespec & AFRelationship &Unspecified % \end{tabular} % \end{table} % \begin{function}{\pdffile_embed_file:nnn} % \begin{syntax} % \cs{pdffile_embed_file:nnn} \Arg{source filename} \Arg{target filename} \Arg{object name } % \end{syntax} % This commands embeds the file \meta{source filename} in the PDF, % and creates a |/Filespec| dictionary object named \meta{object name}. % The object name must be unique, it should start with the module name, so % e.g. \texttt{module/name}. % The command uses the content of the local % dictionaries |l_pdffile|, |l_pdffile/Params| and |l_pdffile/Filespec| % to setup the dictionary entries of the stream object and the % |/Filespec| dictionary. The |/F| and |/UF| entry are filled % with \meta{target filename}. % % It is an error if both \meta{target filename} and \meta{source filename} % are empty. % % If \meta{target filename} is empty \meta{source filename} is used instead. % % If \meta{source filename} is empty, only a |/Filespec| dictionary is % created. % % If the |l_pdffile| dictionary doesn't contain a % Subtype entry with the mimetype, the command tries to guess it % from the file extension of \meta{source filename}. Unknown file extensions can be % added (or known extension be changed) by adding to or changing the value in % the property \cs{g_pdffile_mimetypes_prop}, see below. % % When using \texttt{dvips} and \texttt{pstopdf} the actual embedding is % done by \texttt{pstopdf}. \texttt{pstopdf} will embed files % only if used with the option \texttt{-dNOSAFER} and will not be able % to use files which are found with \texttt{kpathsea}. % % \meta{target filename} doesn't need to be a file name with an extension, % but it is recommended as security settings in the pdf % viewer can restrict access to known file types. % \end{function} % % \begin{NOTE}{UF} % we should perhaps also consider the chunk method see pdfbase and % https://chat.stackexchange.com/transcript/message/54181193#54181193 % \end{NOTE} % % \begin{function}{\pdffile_embed_stream:nnn,\pdffile_embed_stream:nnN} % \begin{syntax} % \cs{pdffile_embed_stream:nnn} \Arg{content} \Arg{target filename} \Arg{object name}\\ % \cs{pdffile_embed_stream:nnN} \Arg{content} \Arg{target filename} \Arg{tl var} % \end{syntax} % This commands embeds the \meta{content} in the PDF in a stream objects and % creates either a |/Filespec| dictionary object named \meta{object name}, or stores % the object reference (what you would get with \cs{pdf_object_ref:n}) in \meta{tl var}. % \meta{content} is wrapped in a \cs{exp_not:n}. % The object name must be unique. The command uses the content of the local % dictionaries |l_pdffile|, |l_pdffile/streamParams| and |l_pdffile/Filespec| % to setup the dictionary entries of the stream object and the /Filespec dictionary. % The /F and /UF entry are filled with \meta{target filename}. % If \meta{target filename} is empty the fix name \texttt{stream.txt} % is used instead. % % If the |l_pdffile| dictionary doesn't contain a % Subtype entry with the mimetype, the command tries to guess it % from the file extension of \meta{target filename}. % % \meta{target filename} doesn't need to be a file name with an extension, % but it is recommended as security settings in the pdf % viewer can restrict access to known file types. % % The stream should not be too long, at least PS imposes a size limit for strings. % \end{function} % % \begin{function}{\pdffile_filespec:nnn,\pdffile_filespec:nne} % \begin{syntax} % \cs{ pdffile_filespec:nnn }\Arg{object name}\Arg{file name}{stream object reference} % \end{syntax} % The previous commands are fine if stream and filespec dictionary can be created together. % But there are cases where the |filespec| dictionary should be referenced when the % stream object doesn't exist yet. For example in the |AF| key of a structure at % the begin of an environment where the stream is created from the body. % % This command allows to write a filespec dictionary alone and reference a previously % created stream. % % \begin{verbatim} % \pdf_object_new:n {module/filespec/A} % a new filespec object % \pdf_object_ref:n {module/filespec/A} % reference it somewhere, e.g. in AF % % now write the stream % \pdf_object_unnamed_write:nn { stream }{ {...}{content} } % % and fill and write the filespec dictionary: % \pdffile_filespec:nnn {module/filespec/A}{A.xml}{\pdf_object_ref_last:} % \end{verbatim} % % \end{function} % % \begin{variable}{\g_pdffile_mimetypes_prop} % This property contains a list of extensions and their mimetypes. % Values can be added or changed with the standard commands: % % |\prop_gput:Nnn \g_pdffile_mimetypes_prop {.abc}{text/plain}| % % The extension should start with a period, the mimetype should be given % as plain text (it will be escaped internally). Extensions with two periods % are not supported. % \end{variable} % % \begin{variable} {\g_pdffile_embed_pdfa_int,\g_pdffile_embed_nonpdfa_int} % These two integers hold the number of embedded files in PDF/A format % and non-PDF/A format and can be used for a rough test for the requirements % in l3pdfmeta |no_embed_content| (both should be zero) % and |only_pdfa_embed_content| (the second should be zero). % The commands |\pdffile_embed_stream:...| and |\pdffile_embed_file:...| % increase the integers. As the code can currently not detect if an embedded % file follows a PDF/A standard it simply goes by the extension: files embedded % as |.pdf| increase the first integer. % % |\pdffile_filespec:nnn| does \emph{not} increase the integers, % if this command is used it lies in the responsability of the % author to adjust the integers. % % The integers are public so that user % can query and adjust the values, e.g. in tests for a standard compliancy. % \end{variable} % % \begin{variable}{\l_pdffile_source_name_str} % This variable is set at the begin of \cs{pdffile_embed_file:nnn}. It can be % (and is) used in the file dictionaries, see table~\ref{tab:filedict} for examples. % \end{variable} % % \begin{variable} {\g_pdffile_embed_prop} % This property holds a list of embedded files. It is used by the following % show command. The keys are the object names, the argument holds a key word, % the source file name and the target file name. % \end{variable} % % \begin{function}{\pdffile_embed_show:} % This shows the embedded files with their source and target name. % \end{function} % % \subsection{Example} % \begin{verbatim} % \group_begin: % %set the relationship: % \pdfdict_put:nnn {l_pdffile/Filespec} {AFRelationship}{/Source} % %set the description key. The text must first be converted: % \pdf_string_from_unicode:nnN {utf16/string} % {this~is~an~odd~description~with~öäü} % \l_tmpa_str % \pdfdict_put:nne {l_pdffile/Filespec} {Desc}{\l_tmpa_str} % %embeds testinput.txt and calls it grüße.txt % \pdffile_embed_file:nnn {testinput.txt}{grüße.txt}{mymodule/example1} % %reference it in the panel % \pdfmanagement_add:nne % {Catalog/Names} % {EmbeddedFiles} % {\pdf_object_ref:n{mymodule/example1}} % \group_end: % \end{verbatim} % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3pdffile} implementation} % % \begin{macrocode} %<*header> \ProvidesExplPackage{l3pdffile}{2024-12-20}{0.96o} {embedding and referencing files in PDF---LaTeX PDF management testphase bundle} \RequirePackage{l3pdftools} %temporarily!! % % \end{macrocode} % % \begin{macrocode} %<*package> %<@@=pdffile> \cs_new_protected:Npn \@@_filename_convert_to_print:nN #1 #2 {\pdf_string_from_unicode:nnN {utf16/hex}{#1}{#2}} % \end{macrocode} % \subsection{Messages} % \begin{macrocode} \msg_new:nnn { pdffile } { file-not-found } { File~'\tl_to_str:n{#1}'~not~found } \msg_new:nnn { pdffile } { mimetype-missing } { Mime~type~not~set~for~file~'\tl_to_str:n{#1}' } \msg_new:nnn { pdffile } { target-name-missing } { a~target~name~for~the~/Filespec~dictionary~is~missing. } \msg_new:nnn { pdffile } { object-exists } { object~name~'#1'~is~already~used. } \msg_new:nnn { pdffile } { show-files } { The~following~files~have~been~embedded\\ #1 } % \end{macrocode} % % \subsection{Variables} % \begin{variable} % { % \l_@@_tmpa_tl, % \l_@@_tmpb_tl, % \g_@@_tmpa_tl, % \l_@@_tmpa_str, % \l_@@_tmpb_str, % \l_@@_ext_str, % \l_@@_automimetype_tl, % \l_@@_embed_ref_tl % } % temporary variables: generic, for extension, subtype, to store the ref. % \end{variable} % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl \tl_new:N \g_@@_tmpa_tl \str_new:N \l_@@_tmpa_str \str_new:N \l_@@_tmpb_str \str_new:N \l_@@_ext_str \tl_new:N \l_@@_automimetype_tl \tl_new:N \l_@@_embed_ref_tl % \end{macrocode} % \begin{variable} {\g_pdffile_mimetypes_prop} % This variable holds common mimetypes. The key is an extension with (one) period, % the value the description, e.g. \texttt{text/csv}. % \end{variable} % \begin{macrocode} \prop_new:N \g_pdffile_mimetypes_prop \prop_gset_from_keyval:Nn \g_pdffile_mimetypes_prop { ,.css = text/css ,.csv = text/csv ,.html= text/html ,.dtx = text/plain %or application/x-tex, not in iana.org list ,.eps = application/postscript ,.jpg = image/jpeg ,.mp4 = video/mp4 ,.pdf = application/pdf ,.png = image/png ,.tex = application/x-tex %not in iana.org list but probably better ,.txt = text/plain ,.sty = text/plain ,.xml = application/xml } % \end{macrocode} % % \begin{variable} {\g_pdffile_embed_pdfa_int,\g_pdffile_embed_nonpdfa_int} % These two integers hold the number of embedded files in PDF/A format % and non-PDF/A format and can be used for a rough test for the requirements % in l3pdfmeta |no_embed_content| (both should be zero) % and |only_pdfa_embed_content| (the second should be zero). % The commands |\pdffile_embed_stream:...| and |\pdffile_embed_file:...| % increase the integers. As the code can currently not detect if an embedded % file follows a PDF/A standard it simply goes by the extension: files embedded % as |.pdf| increase the first integer. % % |\pdffile_filespec:nnn| does \emph{not} increase the integers, % if this command is used it lies in the responsability of the % author to adjust the integers. % % The integers are public so that user % can query and adjust the values, e.g. in tests for a standard compliancy. % \begin{macrocode} \int_new:N\g_pdffile_embed_pdfa_int \int_new:N\g_pdffile_embed_nonpdfa_int % \end{macrocode} % \end{variable} % \begin{variable} % { % \l_pdffile_source_name_str % } % \cs{l_pdffile_source_name_str} will be set at the begin of the command and % contains the full file name and can be used e.g. with \cs{file_timestamp:n}. % \end{variable} % \begin{macrocode} \str_new:N \l_pdffile_source_name_str % \end{macrocode} % Here we define and setup the local dictionaries. % We add a ModDate to ensure that there is an entry if % associated files are used. % \begin{macrocode} \pdfdict_new:n { l_pdffile } \pdfdict_put:nnn { l_pdffile }{Type}{/EmbeddedFile} \pdfdict_new:n { l_pdffile/Params } \pdfdict_put:nnn { l_pdffile/Params } {ModDate} { (\file_timestamp:n { \l_pdffile_source_name_str }) } \pdfdict_put:nnn { l_pdffile/Params } {Size} { \file_size:n { \l_pdffile_source_name_str } } \pdfdict_put:nnn { l_pdffile/Params } {CheckSum} { (\file_mdfive_hash:n { \l_pdffile_source_name_str }) } \pdfdict_new:n { l_pdffile/streamParams } \pdfdict_put:nnn { l_pdffile/streamParams } {ModDate} { ( D:\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 ) } \pdfdict_new:n { l_pdffile/Filespec } \pdfdict_put:nnn { l_pdffile/Filespec } {Type} { /Filespec } \pdfdict_put:nnn { l_pdffile/Filespec } {AFRelationship} { /Unspecified } % \end{macrocode} % \begin{variable}{\g_pdffile_embed_prop} % we record here the relation\\% % \meta{object name} $\Rightarrow$ % \Arg{file/stream or empty}\Arg{sourcename}\Arg{targetname} % % \begin{macrocode} \prop_new:N \g_pdffile_embed_prop % \end{macrocode} % \end{variable} % \begin{macro}{\pdffile_embed_show:} % \begin{macrocode} \cs_new_protected:Npn \pdffile_embed_show: { \msg_show:nne {pdffile}{show-files} { \prop_map_function:NN {\g_pdffile_embed_prop} \msg_show_item:nn } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdffile_embed_file:nnn, \pdffile_embed_stream:nnn, \pdffile_embed_stream:nnN} % At first a command to set the mimetype. It either uses the current value % in the file dictionary, or tries to guess it from the extension. % % \begin{macro}{\@@_mimetype_set:nNN,\@@_mimetype_set:VNN} % \begin{macro}{\@@_fstream_write:nN, \@@_fstream_write:VN} % \begin{macro}{\@@_stream_write:nN, \@@_stream_write:VN} % \begin{macrocode} %#1 file name, %#2 tl to return the (printed) value for the guessed mimetype %#3 tl to return the file extension (that is a string) \cs_new_protected:Npn \@@_mimetype_set:nNN #1 #2 #3 { \file_parse_full_name:nNNN {#1} \l_@@_tmpa_str %unused \l_@@_tmpb_str %unused \l_@@_ext_str %check if Subtype has been set \pdfdict_get:nnN { l_pdffile}{Subtype}\l_@@_tmpa_tl %if not look up in the prop: \quark_if_no_value:NT \l_@@_tmpa_tl { \prop_get:NVNTF \g_pdffile_mimetypes_prop \l_@@_ext_str \l_@@_tmpb_tl { \tl_set:Ne #2 {/Subtype~\pdf_name_from_unicode_e:V \l_@@_tmpb_tl} } { \msg_warning:nne { pdffile }{ mimetype-missing} {#1} \tl_clear:N #2 } } \tl_set_eq:NN #3 \l_@@_ext_str } \cs_generate_variant:Nn \@@_mimetype_set:nNN {VNN} % #1 tl containing a file extension \cs_new_protected:Npn \@@_count_embed:N #1 { \str_if_eq:VnTF #1 {.pdf} {\int_gincr:N \g_pdffile_embed_pdfa_int } {\int_gincr:N \g_pdffile_embed_nonpdfa_int } } %#1 file name, %#2 tl, should be empty or contain /Subtype /mimetype % e.g. result from \@@_mimetype_set:nNN \cs_new_protected:Npn \@@_fstream_write:nN #1 #2 { \pdf_object_unnamed_write:ne { fstream } { { #2 \pdfdict_use:n { l_pdffile} \pdfdict_if_empty:nF { l_pdffile/Params} { /Params << \pdfdict_use:n { l_pdffile/Params} >> } } { #1 } } \tl_clear:N \l_@@_automimetype_tl } \cs_generate_variant:Nn \@@_fstream_write:nN {VN} %#1 file content %#2 tl, should be empty or contain /Subtype /mimtype % e.g. result from \@@_mimetype_set:nNN \cs_new_protected:Npn \@@_stream_write:nN #1 #2 { \pdf_object_unnamed_write:ne { stream } { { #2 \pdfdict_use:n { l_pdffile} \pdfdict_if_empty:nF { l_pdffile/streamParams} { /Params << \pdfdict_use:n { l_pdffile/streamParams} >> } } { \exp_not:n { #1 } } } \tl_clear:N \l_@@_automimetype_tl } \cs_generate_variant:Nn \@@_stream_write:nN {VN} %#1 symbolic name of dict object %#2 target file name, %#3 object ref of the file stream. \cs_new_protected:Npn \@@_filespec_write:nnn #1 #2 #3 { \tl_if_blank:nTF { #2 } { \msg_error:nn {pdffile}{target-name-missing} } { \group_begin: \pdf_string_from_unicode:nnN {utf8/string}{#2}\l_@@_tmpa_str \pdfdict_put:nne {l_pdffile/Filespec}{F} { \l_@@_tmpa_str } \@@_filename_convert_to_print:nN { #2 } \l_@@_tmpa_str \pdfdict_put:nne {l_pdffile/Filespec}{UF}{ \l_@@_tmpa_str } \pdf_object_write:nne { #1 } { dict } { \pdfdict_use:n { l_pdffile/Filespec} \tl_if_empty:nF { #3 } { /EF <> } } \group_end: } } %#1 target file name #2 object ref of file stream #3 reference of object \cs_new_protected:Npn \@@_filespec_write:nnN #1 #2 #3 { \tl_if_blank:nTF { #1 } { \msg_error:nn {pdffile}{target-name-missing} } { \group_begin: \pdf_string_from_unicode:nnN {utf8/string}{#1}\l__pdffile_tmpa_str \pdfdict_put:nne {l_pdffile/Filespec}{F} { \l_@@_tmpa_str } \@@_filename_convert_to_print:nN { #1 } \l_@@_tmpa_str \pdfdict_put:nne {l_pdffile/Filespec}{UF}{ \l_@@_tmpa_str } \pdf_object_unnamed_write:ne {dict} { \pdfdict_use:n { l_pdffile/Filespec} \tl_if_empty:nF { #2 } { /EF <> } } \tl_gset:Ne\g_@@_tmpa_tl{\pdf_object_ref_last:} \group_end: \tl_set_eq:NN#3\g_@@_tmpa_tl } } \cs_set_eq:NN \pdffile_filespec:nnn \@@_filespec_write:nnn \cs_generate_variant:Nn \pdffile_filespec:nnn {nne,nnx} %#1 {source filename} %#2 {target filename} %#3 { filespec object name } (will internally get a prefix! ??) \cs_new_protected:Npn \pdffile_embed_file:nnn #1 #2 #3 { % if #1 empty => only filespec % if #2 empty => = #1 \pdf_object_if_exist:nTF { #3 } { \msg_error:nnn { pdffile }{ object-exists } { #3 } } { \tl_if_blank:nTF { #1 } { \tl_set:Nn \l_@@_embed_ref_tl {} } { \file_get_full_name:nNTF {#1} \l_pdffile_source_name_str { \@@_mimetype_set:VNN \l_pdffile_source_name_str \l_@@_automimetype_tl \l_@@_tmpa_tl \@@_count_embed:N \l_@@_tmpa_tl \@@_fstream_write:VN \l_pdffile_source_name_str \l_@@_automimetype_tl \tl_set:Ne \l_@@_embed_ref_tl { \pdf_object_ref_last: } } { \msg_error:nnn { pdffile }{ file-not-found }{ #1 } } } \prop_gput:Nne \g_pdffile_embed_prop { #3 } { { \tl_if_blank:nTF { #1 } {filespec}{file} } {\l_pdffile_source_name_str} { \tl_if_blank:nTF { #2 } { \l_pdffile_source_name_str } { \tl_to_str:n{#2}} } } \tl_if_blank:nTF { #2 } { \pdf_object_new:n { #3 } \exp_args:Nnne \@@_filespec_write:nnn %#1 dict, #2 target file name, #3 object ref { #3 } { #1 } {\l_@@_embed_ref_tl} } { \pdf_object_new:n { #3 } \exp_args:Nnne \@@_filespec_write:nnn %#1 dict, #2 target file name, #3 object ref { #3 } { #2 } {\l_@@_embed_ref_tl} } } } %#1{stream content} %#2{target filename} %#3{file object name } \cs_new_protected:Npn \pdffile_embed_stream:nnn #1 #2 #3 { % if #2 empty => error \pdf_object_if_exist:nTF { #3 } { \msg_error:nnn { pdffile }{ object-exists } { #3 } } { \prop_gput:Nne \g_pdffile_embed_prop { #3 } {{stream}{}{\tl_if_blank:nTF {#2}{stream.txt}{\exp_not:n{#2}}}} \tl_if_blank:nTF {#2} { \@@_mimetype_set:nNN {stream.txt}\l_@@_automimetype_tl \l_@@_tmpa_tl} { \@@_mimetype_set:nNN { #2 } \l_@@_automimetype_tl \l_@@_tmpa_tl } \@@_count_embed:N \l_@@_tmpa_tl \@@_stream_write:nN { #1 } \l_@@_automimetype_tl \tl_set:Ne \l_@@_embed_ref_tl { \pdf_object_ref_last: } \pdf_object_new:n { #3 } \exp_args:Nnee \@@_filespec_write:nnn %#1 dict, #2 target file name, #3 object ref { #3 } { \tl_if_blank:nTF {#2}{stream.txt}{\exp_not:n{#2}} } {\l_@@_embed_ref_tl} } } \cs_new_protected:Npn \pdffile_embed_stream:nnN #1 #2 #3 { \tl_if_blank:nTF {#2} { \@@_mimetype_set:nNN {stream.txt}\l_@@_automimetype_tl \l_@@_tmpa_tl} { \@@_mimetype_set:nNN { #2 } \l_@@_automimetype_tl \l_@@_tmpa_tl } \@@_count_embed:N\l_@@_tmpa_tl \@@_stream_write:nN { #1 } \l_@@_automimetype_tl \tl_set:Ne \l_@@_embed_ref_tl { \pdf_object_ref_last: } \exp_args:Nee \@@_filespec_write:nnN %#1 target file name, #2 object ref of stream, #3 object ref of filespec { \tl_if_blank:nTF {#2}{stream.txt}{\exp_not:n{#2}} } {\l_@@_embed_ref_tl} #3 \prop_gput:Nee \g_pdffile_embed_prop { #3 } {{stream}{}{\tl_if_blank:nTF {#2}{stream.txt}{\exp_not:n{#2}}}} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex