% \iffalse meta-comment % %% File: l3backend-testphase.dtx % % Copyright (C) 2019-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 % % https://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> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3backend-testphase} package\\ Additional backend PDF features^^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{implementation} % % \section{\pkg{l3backend-testphase} Implementation} % \begin{macrocode} %\ProvidesExplFile %<*dvipdfmx> {l3backend-testphase-dvipdfmx.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: dvipdfmx} % %<*dvips> {l3backend-testphase-dvips.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: dvips} % %<*dvisvgm> {l3backend-testphase-dvisvgm.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: dvisvgm} % %<*luatex> {l3backend-testphase-luatex.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: PDF output (LuaTeX)} % %<*pdftex> {l3backend-testphase-pdftex.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: PDF output (pdfTeX)} % %<*xdvipdfmx> {l3backend-testphase-xetex.def}{2024-12-20}{} {LaTeX~PDF~management~testphase~bundle~backend~support: XeTeX} % % \end{macrocode} % \subsection{Variants} % We need to generate temporarily a few e-types variants of kernel backend commands. % These can be removed once the kernel provides them. % \begin{macrocode} %<@@=pdf> %<*luatex|pdftex> \cs_generate_variant:Nn \__kernel_backend_literal_page:n { e } % %<*dvipdfmx|xdvipdfmx> \cs_generate_variant:Nn \__kernel_backend_literal:n { e } \cs_generate_variant:Nn \@@_backend:n { e } % %<*dvips> \cs_generate_variant:Nn \__kernel_backend_postscript:n { e } \cs_generate_variant:Nn \@@_backend_pdfmark:n { e } % % \end{macrocode} % \subsection{Support for delayed literal and special} % Starting with TeXlive 2023 the engines support a \texttt{shipout} keyword % for \cs{pdfliteral} and \cs{special}. When used the argument is not expanded % when the command is used but only when the page is shipped out. This allows for example % the tagging code to delay the page-wise numbering of MC-chunks until the page is % actually built. For now we test the engine support. The boolean is setup % in pdfmanagement-testphase.dtx. % \begin{macrocode} %<*drivers> % \end{macrocode} % % The following commands provide the needed kernel backend support. This are basically % copies of similar commands of l3backend-basics. % \begin{macro} % { % \__kernel_backend_shipout_literal:e % } % The one shared function for all backends is access to the basic % \tn{special} primitive. % \begin{macrocode} \bool_if:NT \l__pdfmanagement_delayed_shipout_bool { \cs_new_protected:Npn \__kernel_backend_shipout_literal:e #1 { \tex_special:D~shipout { #1} } % % \end{macrocode} % \end{macro} % % \begin{macrocode} %<*luatex|pdftex> % \end{macrocode} % \begin{macro}{\__kernel_backend_shipout_literal_pdf:e} % This is equivalent to \verb|\special{pdf:}| but the engine can % track it. Without the \texttt{direct} keyword everything is kept in % sync: the transformation matrix is set to the current point automatically. % Note that this is still inside the text (\texttt{BT} \dots \texttt{ET} % block). % \begin{macrocode} \cs_new_protected:Npn \__kernel_backend_shipout_literal_pdf:e #1 { %<*luatex> \tex_pdfextension:D ~ literal ~ shipout ~ % %<*pdftex> \tex_pdfliteral:D ~ shipout ~ % { #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_backend_shipout_literal_page:e} % Page literals are pretty simple. % \begin{macrocode} \cs_new_protected:Npn \__kernel_backend_shipout_literal_page:e #1 { %<*luatex> \tex_pdfextension:D ~ literal ~ shipout ~ % %<*pdftex> \tex_pdfliteral:D ~ shipout ~ % page { #1 } } % % } % \end{macrocode} % \end{macro} % \subsection{Crossreferences} % Commands to get a reference for the absolute page counter. % \begin{macrocode} %<*drivers> % \end{macrocode} % \begin{macrocode} \cs_new_protected:Npn \@@_backend_record_abspage:n #1 { \@bsphack \property_record:nn{#1}{abspage} \@esphack } \cs_new:Npn \@@_backend_ref_abspage:n #1 { \property_ref:nn{#1}{abspage} } \cs_generate_variant:Nn \@@_backend_record_abspage:n {e} \cs_generate_variant:Nn \@@_backend_ref_abspage:n {e} % % \end{macrocode} % avoid that destinations names are optimized with xelatex/dvipdfmx % see https://tug.org/pipermail/dvipdfmx/2019-May/000002.html % \begin{macrocode} %<*dvipdfmx|xdvipdfmx> \__kernel_backend_literal:n { dvipdfmx:config~C~ 0x0010 } % % \end{macrocode} % \begin{variable}{\g_@@_tmpa_prop, \l_@@_tmpa_tl, \l_@@_backend_tmpa_box } % Some scratch variables % \begin{macrocode} %<*drivers> \prop_new:N \g_@@_tmpa_prop \tl_new:N \l_@@_tmpa_tl \box_new:N \l_@@_backend_tmpa_box \box_new:N \l_@@_backend_tmpb_box % % \end{macrocode} % \end{variable} % \begin{variable} % {\g_@@_backend_resourceid_int, \g_@@_backend_name_int, \g_@@_backend_page_int} % a counter to create labels for the resources, a counter % to number properties in bdc marks, a counter for the \cs{pdfpageref} implementation. % \begin{macrocode} %<*drivers> \int_new:N \g_@@_backend_resourceid_int \int_new:N \g_@@_backend_name_int \int_new:N \g_@@_backend_page_int % % \end{macrocode} % \end{variable} % % \subsection{luacode} % Load the lua code. % \begin{macrocode} %<*luatex> \directlua { require("l3backend-testphase.lua") } % % \end{macrocode} % \subsection{Converting unicode strings to a pdfname} % dvips needs a special function here, so we add this as backend function. % \begin{macrocode} %<*pdftex|luatex|dvipdfmx|xdvipdfmx|dvisvgm> \cs_new:Npn \__kernel_pdf_name_from_unicode_e:n #1 { / \str_convert_pdfname:e { \text_expand:n { #1 } } } % %<*dvips> \cs_new:Npn \__kernel_pdf_name_from_unicode_e:n #1 { ~ ( \text_expand:n { #1 } ) ~ cvn } % % \end{macrocode} % \subsection{Hooks} % \subsubsection{Add the \enquote{end run} hooks} % Here we add the end run hook to suitable % end hooks. % \begin{macrocode} %<*pdftex|luatex> % put in \@kernel@after@enddocument@afterlastpage \tl_gput_right:Nn \@kernel@after@enddocument@afterlastpage { \g__kernel_pdfmanagement_end_run_code_tl } % %<*dvipdfmx|xdvipdfmx> % put in \@kernel@after@shipout@lastpage \tl_gput_right:Nn \@kernel@after@shipout@lastpage { \g__kernel_pdfmanagement_end_run_code_tl } % %<*dvips> % put in \@kernel@after@shipout@lastpage \tl_gput_right:Nn\@kernel@after@shipout@lastpage { \g__kernel_pdfmanagement_end_run_code_tl } % % \end{macrocode} % \subsubsection{Add the \enquote{shipout} hooks} % Now we add to the shipout hooks the relevant token lists. % We also push the page resources in shipout/firstpage (AtBeginDvi) % as the backend code sets color stack there. The xetex driver needs a rule here. % If it clashes on the first page, we will need a test ... % \begin{macrocode} %<*drivers> \tl_if_exist:NTF \@kernel@after@shipout@background { \g@addto@macro \@kernel@before@shipout@background{\relax} \g@addto@macro \@kernel@after@shipout@background { \g__kernel_pdfmanagement_thispage_shipout_code_tl } } { \hook_gput_code:nnn{shipout/background}{pdf} { \g__kernel_pdfmanagement_thispage_shipout_code_tl } } % % \end{macrocode} % \subsection{ The /Pages dictionary (pdfpagesattr) } % \begin{NOTE}{UF} % path: Pages % pdfpagesattr is a single token register which is used at the end of the compilation. % dvips syntax: \verb+\special{ps: [/ABC /CDE /EFG /FGH /Rotate 90 /PAGES pdfmark}+ % dvipdfmx syntax: \verb+\special{pdf:put @pages <>}+ % both remove duplicate entries automatically, so there is no need to be careful. % \end{NOTE} % \begin{macro}{\@@_backend_Pages_primitive:n} % This is the primitive command to add something to the /Pages dictionary. % It works differently for the backends: pdftex and luatex overwrite existing % content, dvips and dvipdfmx are additive. luatex sets it in lua. % The higher level code has to take this into account. % \begin{macrocode} %<*pdftex> \cs_new_protected:Npn \@@_backend_Pages_primitive:n #1 { \tex_global:D \tex_pdfpagesattr:D { #1 } } % %<*luatex> %luatex: does it in lua \sys_if_engine_luatex:T { \cs_new_protected:Npn \@@_backend_Pages_primitive:n #1 { \tex_directlua:D { pdf.setpagesattributes( \@@_backend_luastring:n { #1 } ) } } } % %<*dvips> \cs_new_protected:Npx \@@_backend_Pages_primitive:n #1 { \tex_special:D{ps:~[#1~/PAGES~pdfmark} %] } % %<*dvipdfmx|xdvipdfmx> \cs_new_protected:Npn \@@_backend_Pages_primitive:n #1 { \@@_backend:n{put~@pages~<<#1>>} } % %<*dvisvgm> \cs_new_protected:Npn \@@_backend_Pages_primitive:n #1 {} % % \end{macrocode} % \end{macro} % % \subsection{\enquote{Page} and \enquote{ThisPage} attributes (pdfpageattr)} % \begin{NOTE}{UF} % path: Page % !!!!!!!!!!!!!!!!!!!!!! % It also depends on code in l3pdfmanagement as the code uses the Core-dictionaries % !!!!!!!!!!!!!!!!!!!!!! % % The engines differ a lot here: pdflatex and lualatex uses a register while with % dvips/dvipdfmx a one-shot-special is used. So for pdflatex and lualatex code % to assemble the content of the register is needed. Specials are used at shipout, % the registers is set directly. With lualatex one can use % \cs{latelua} to delay the setting, with pdflatex one has to use a shipout hook. % To get the code on the correct page one has to use the aux with pdflatex. % In sum this means that quite a lot backend commands are needed to handle % this differences. Simply variants of \cs{pdfpageattr} are not enough ...% % dvips syntax: \special{ps: [{ThisPage}<> /PUT pdfmark}% % There seem to be an in-built management code: multiple uses don't lead to % multiple entries (/Rotate is special: there is always a /Rotate 0 in the dict, % but seems not to do harm). % dvipdfmx syntax: \special{pdf: put @thispage << /Rotate 90 >>}, % like dvips the backend has an in-built management code. % Both change only the current page, so to get the pdftex behavior (which sets % also the following pages) one need to repeat it on every shipout. % \end{NOTE} % \begin{macro} % { % \@@_backend_Page_primitive:n, % \@@_backend_Page_gput:nn, % \@@_backend_Page_gremove:n, % \@@_backend_ThisPage_gput:nn, % \@@_backend_ThisPage_gpush:n % } % \cs{@@_backend_Page_primitive:n} is the primitive command to add % something to the /Page dictionary. % It works differently for the backends: pdftex and luatex overwrite existing % content, dvips and dvipdfmx are additive. luatex sets it in lua. % The higher level code has to take this into account. % \cs{@@_backend_Page_gput:nn} stores default values. % \cs{@@_backend_Page_gremove:n} allows to remove a value. % \cs{@@_backend_ThisPage_gput:nn} adds a value to the current page. % \cs{@@_backend_ThisPage_gpush:n} merges the default and the current page values % and add them to the dictionary of the current page in % \cs{g_@@_backend_thispage_shipout_tl}. % \begin{macrocode} % backend commands %<*pdftex> %the primitive \cs_new_protected:Npn \@@_backend_Page_primitive:n #1 { \tex_global:D \tex_pdfpageattr:D { #1 } } % the command to store default values. % Uses a prop with pdflatex + dvi, % sets a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gput:nn #1 #2 %key,value { \pdfdict_gput:nnn {g_@@_Core/Page}{ #1 }{ #2 } } % the command to remove a default value. % Uses a prop with pdflatex + dvi, % changes a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gremove:n #1 { \pdfdict_gremove:nn {g_@@_Core/Page}{ #1 } } % the command used in the document. % direct call of the primitive special with dvips/dvipdfmx % \latelua: fill a page related table with lualatex, merge it with the page % table and push it directly % write to aux and store in prop with pdflatex \cs_new_protected:Npn \@@_backend_ThisPage_gput:nn #1 #2 { %we need to know the page the resource should be added too. \int_gincr:N\g_@@_backend_resourceid_int \@@_backend_record_abspage:e { l3pdf\int_use:N\g_@@_backend_resourceid_int } \tl_set:Ne \l_@@_tmpa_tl { \@@_backend_ref_abspage:e {l3pdf\int_use:N\g_@@_backend_resourceid_int} } \pdfdict_if_exist:nF { g_@@_Core/backend_Page\l_@@_tmpa_tl} { \pdfdict_new:n { g_@@_Core/backend_Page\l_@@_tmpa_tl} } %backend_Page has no handler. \pdfdict_gput:nnn {g_@@_Core/backend_Page\l_@@_tmpa_tl}{ #1 }{ #2 } } %the code to push the values, used in shipout %merges the two props and then fills the register in pdflatex %merges the two tables and then fills (in lua) in luatex %issues the values stored in the global prop with dvi \cs_new_protected:Npn \@@_backend_ThisPage_gpush:n #1 { \prop_gset_eq:Nc \g_@@_tmpa_prop { \__kernel_pdfdict_name:n { g_@@_Core/Page } } \prop_if_exist:cT { \__kernel_pdfdict_name:n { g_@@_Core/backend_Page#1 } } { \prop_map_inline:cn { \__kernel_pdfdict_name:n { g_@@_Core/backend_Page#1 } } { \prop_gput:Nnn \g_@@_tmpa_prop { ##1 }{ ##2 } } } \@@_backend_Page_primitive:e { \prop_map_function:NN \g_@@_tmpa_prop \pdfdict_item:ne } } % %<*luatex> % do we need to use some escaping for the values????? \cs_new:Npn \@@_backend_luastring:n #1 { "\tex_luaescapestring:D { \tex_unexpanded:D { #1 } }" } %not used, only there for consistency \cs_new_protected:Npn \@@_backend_Page_primitive:n #1 { \tex_latelua:D { pdf.setpageattributes(\@@_backend_luastring:n { #1 }) } } % the command to store default values. % Uses a prop with pdflatex + dvi, % sets a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gput:nn #1 #2 { \tex_directlua:D { ltx.@@.backend_Page_gput ( \@@_backend_luastring:n { #1 }, \@@_backend_luastring:n { #2 } ) } } % the command to remove a default value. % Uses a prop with pdflatex + dvi, % changes a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gremove:n #1 { \tex_directlua:D { ltx.@@.backend_Page_gremove (\@@_backend_luastring:n { #1 }) } } % the command used in the document. % direct call of the primitive special with dvips/dvipdfmx % \latelua: fill a page related table with lualatex, merge it with the page % table and push it directly % write to aux and store in prop with pdflatex \cs_new_protected:Npn \@@_backend_ThisPage_gput:nn #1 #2 { \tex_latelua:D { ltx.@@.backend_ThisPage_gput ( tex.count["g_shipout_readonly_int"], \@@_backend_luastring:n { #1 }, \@@_backend_luastring:n { #2 } ) ltx.@@.backend_ThisPage_gpush (tex.count["g_shipout_readonly_int"]) } } %the code to push the values, used in shipout %merges the two props and then fills the register in pdflatex %merges the two tables (the one is probably still empty) and then fills (in lua) in luatex %issues the values stored in the global prop with dvi \cs_new_protected:Npn \@@_backend_ThisPage_gpush:n #1 { \tex_latelua:D { ltx.@@.backend_ThisPage_gpush (tex.count["g_shipout_readonly_int"]) } } % %<*dvipdfmx|xdvipdfmx> %the primitive \cs_new_protected:Npn \@@_backend_Page_primitive:n #1 { \tex_special:D{pdf:~put~@thispage~<<#1>>} } % the command to store default values. % Uses a prop with pdflatex + dvi, % sets a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gput:nn #1 #2 { \pdfdict_gput:nnn {g_@@_Core/Page}{ #1 }{ #2 } } % the command to remove a default value. % Uses a prop with pdflatex + dvi, % changes a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gremove:n #1 { \pdfdict_gremove:nn {g_@@_Core/Page}{ #1 } } % the command used in the document. % direct call of the primitive special with dvips/dvipdfmx % \latelua: fill a page related table with lualatex, merge it with the page % table and push it directly % write to aux and store in prop with pdflatex \cs_new_protected:Npn \@@_backend_ThisPage_gput:nn #1 #2 { \@@_backend_Page_primitive:n { /#1~#2 } } %the code to push the values, used in shipout %merges the two props and then fills the register in pdflatex %merges the two tables (the one is probably still empty) % and then fills (in lua) in luatex %issues the values stored in the global prop with dvi \cs_new_protected:Npn \@@_backend_ThisPage_gpush:n #1 { \@@_backend_Page_primitive:e { \pdfdict_use:n { g_@@_Core/Page} } } % %<*dvips> \cs_new_protected:Npn \@@_backend_Page_primitive:n #1 { \tex_special:D{ps:~[{ThisPage}<<#1>>~/PUT~pdfmark} %] } % the command to store default values. % Uses a prop with pdflatex + dvi, % sets a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gput:nn #1 #2 { \pdfdict_gput:nnn {g_@@_Core/Page}{ #1 }{ #2 } } % the command to remove a default value. % Uses a prop with pdflatex + dvi, % changes a lua table with lualatex \cs_new_protected:Npn \@@_backend_Page_gremove:n #1 { \pdfdict_gremove:nn {g_@@_Core/Page}{ #1 } } % the command used in the document. % direct call of the primitive special with dvips/dvipdfmx % \latelua: fill a page related table with lualatex, merge it with the page % table and push it directly % write to aux and store in prop with pdflatex \cs_new_protected:Npn \@@_backend_ThisPage_gput:nn #1 #2 { \@@_backend_Page_primitive:n { /#1~#2 } } %the code to push the values, used in shipout %merges the two props and then fills the register in pdflatex %merges the two tables (the one is probably still empty) %and then fills (in lua) in luatex %issues the values stored in the global prop with dvi \cs_new_protected:Npn \@@_backend_ThisPage_gpush:n #1 { \@@_backend_Page_primitive:e { \pdfdict_use:n { g_@@_Core/Page} } } % %<*dvisvgm> % mostly only dummies ... \cs_new_protected:Npn \@@_backend_Page_primitive:n #1 {} % Uses a prop with pdflatex + dvi, \cs_new_protected:Npn \@@_backend_Page_gput:nn #1 #2 { \pdfdict_gput:nnn {g_@@_Core/Page}{ #1 }{ #2 } } % the command to remove a default value. % Uses a prop with pdflatex + dvi, \cs_new_protected:Npn \@@_backend_Page_gremove:n #1 { \pdfdict_gremove:nn {g_@@_Core/Page}{ #1 } } % the command used in the document. \cs_new_protected:Npn \@@_backend_ThisPage_gput:nn #1 #2 {} %the code to push the values, used in shipout \cs_new_protected:Npn \@@_backend_ThisPage_gpush:n #1 {} % %<*drivers> \cs_generate_variant:Nn \@@_backend_Page_primitive:n { e } % % \end{macrocode} % \end{macro} % % \subsection{\enquote{Page/Resources}: ExtGState, ColorSpace, Shading, Pattern} % Path: Page/Resources/ExtGState etc. The actual output of the resources is handled % together with the bdc/Properties. Here is only special code. % \begin{macro}{\c_@@_backend_PageResources_clist} % The names are quite often needed % a similar list is now in l3pdfmanagement. Perhaps it should be merged. % \begin{macrocode} %<*drivers> \clist_const:Nn \c_@@_backend_PageResources_clist { ExtGState, ColorSpace, Pattern, Shading, } % % \end{macrocode} % \end{macro} % % Now the backend commands the command to fill the register % and to push the values. % % \begin{macro}{\@@_backend_PageResources_gput:nnn} % stores values for the page resources. % \begin{arguments} % \item name of the resource (ExtGState, ColorSpace, Shading, Pattern) % \item a pdf name without slash % \item value % \end{arguments} % \begin{macro}{\@@_backend_PageResources_obj_gpush:} % This pushes out the objects. It should be a no-op with xdvipdfmx and dvips % as it currently issued in the end-of-run hook! % create the backend objects: % \begin{macrocode} %<*pdftex|luatex> \clist_map_inline:Nn \c_@@_backend_PageResources_clist { \pdf_object_new:n {@@/Page/Resources/#1} \cs_if_exist:NT \tex_directlua:D { \tex_directlua:D { ltx.@@.object["@@/Page/Resources/#1"] = "\pdf_object_ref:n{@@/Page/Resources/#1}" } } } % % \end{macrocode} % values are only stored in a prop and will be output at end document. % luatex must also trigger the lua side % \begin{macrocode} %<*luatex> \cs_new_protected:Npn \@@_backend_PageResources_gput:nnn #1 #2 #3 { \pdfdict_gput:nnn {g_@@_Core/Page/Resources/#1} { #2 }{ #3 } \tex_latelua:D{ltx.@@.Page.Resources.#1=true} \tex_latelua:D { ltx.pdf.Page_Resources_gpush(tex.count["g_shipout_readonly_int"]) } } % %<*pdftex> \cs_new_protected:Npn \@@_backend_PageResources_gput:nnn #1 #2 #3 { \pdfdict_gput:nnn {g_@@_Core/Page/Resources/#1} { #2 }{ #3 } } % % \end{macrocode} % code for end of document code % \begin{macrocode} %<*pdftex|luatex> \cs_new_protected:Npn \@@_backend_PageResources_obj_gpush: { \clist_map_inline:Nn \c_@@_backend_PageResources_clist { \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/##1} } { \pdf_object_write:nne { @@/Page/Resources/##1 } { dict } { \pdfdict_use:n { g_@@_Core/Page/Resources/##1} } } } } % % \end{macrocode} % xdvipdfmx % \special{pdf:pageresources<<#1>>} doesn't work correctly with object names ... % https://tug.org/pipermail/dvipdfmx/2019-August/000021.html, % so we use \special{pdf:put @resources} % this must be issued on every page! % objects should not only be created but also "initialized" % initialization should be done before anyone tries to write % so we add rules for the backend. % The push command should not be used as it is % in the wrong end document hook. If needed a new command must be added. % \begin{macrocode} %<*dvipdfmx|xdvipdfmx> %\hook_gset_rule:nnnn{shipout/firstpage}{l3backend-xetex}{after}{pdf} %\hook_gset_rule:nnnn{shipout/firstpage}{l3backend-dvipdfmx}{after}{pdf} % \clist_map_inline:Nn \c_@@_backend_PageResources_clist { \pdf_object_new:n { @@/Page/Resources/#1 } \hook_gput_code:nnn {shipout/firstpage} {pdf} {\pdf_object_write:nnn { @@/Page/Resources/#1 } { dict } {}} } \cs_new_protected:Npn \@@_backend_PageResources:n #1 { \@@_backend:n {put~@resources~<<#1>>} } \cs_new_protected:Npn \@@_backend_PageResources_gput:nnn #1 #2 #3 { % this is not used for output, but there is a test if the resource is empty \prop_gput:cne { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/#1} } { \str_convert_pdfname:n {#2} }{ #3 } %objects are not filled with \pdf_object_write as this is not additive! \@@_backend:e { put~\pdf_object_ref:n {@@/Page/Resources/#1}<> } } \cs_new_protected:Npn \@@_backend_PageResources_obj_gpush: {} % % \end{macrocode} % dvips unneeded, or no-op. The push command should not be used as it is % in the wrong end document hook. If needed a new command must be added. % \begin{macrocode} %<*dvips> \cs_new_protected:Npn \@@_backend_PageResources:n #1 {} \cs_new_protected:Npn \@@_backend_PageResources_gput:nnn #1 #2 #3 { %only for the show command TEST!! \pdfdict_gput:nnn {g_@@_Core/Page/Resources/#1} { #2 }{ #3 } } \cs_new_protected:Npn \@@_backend_PageResources_obj_gpush: {} % % \end{macrocode} % dvipsvgm unneeded, or no-op % \begin{macrocode} %<*dvisvgm> \cs_new_protected:Npn \@@_backend_PageResources:n #1 {} \cs_new_protected:Npn \@@_backend_PageResources_gput:nnn #1 #2 #3 { %only for the show command TEST!! \pdfdict_gput:nnn {g_@@_Core/Page/Resources/#1} { #2 }{ #3 } } \cs_new_protected:Npn \@@_backend_PageResources_obj_gpush: {} % % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Page resources /Properties + BDC operators} % \begin{macro} % { % \@@_backend_bdc:nn, % \@@_backend_shipout_bdc:ee, % \@@_backend_bdcobject:nn, % \@@_backend_bdcobject:n, % \@@_backend_bmc:n, % \@@_backend_emc:, % \@@_backend_PageResources_gpush:n % } % \cs{@@_backend_bdc:nn}, \cs{@@_backend_shipout_bdc:ee}, % \cs{@@_backend_bdcobject:nn}, \cs{@@_backend_bdcobject:n}, % \cs{@@_backend_bmc:n} and \cs{@@_backend_emc:} % are the backend command that % create the bdc/emc marker and store the properties. % \cs{@@_backend_PageResources_gpush:n} outputs the /Properties and/or the other % resources for the current page. % % pdftex and luatex (and perhaps dvips ...) need to know if there are in a % xform stream ... % \begin{macrocode} %<*drivers> \bool_new:N \l_@@_backend_xform_bool % % \end{macrocode} % % dvips is easy: create an object, and reference it in the bdc % ghostscript will then automatically replace it by a name % and add the name to the /Properties dict, % special variant von accsupp % \url{https://chat.stackexchange.com/transcript/message/50831812#50831812} % \begin{macrocode} %<*dvips> % \cs_set_protected:Npn \@@_backend_bdc:nn #1 #2 % #1 eg. Span, #2: dict_content { \@@_backend_pdfmark:n{/#1~<<#2>>~/BDC} } % \end{macrocode} % There is not difference here between inline and property BDC, it is always % a property: % \begin{macrocode} \cs_set_eq:NN \@@_backend_bdc_contobj:nn \@@_backend_bdc:nn \cs_set_eq:NN \@@_backend_bdc_contstream:nn \@@_backend_bdc:nn \bool_if:NT\l__pdfmanagement_delayed_shipout_bool { \cs_new_protected:Npn \@@_backend_bdc_shipout:ee #1 #2 % #1 eg. Span, #2: dict_content { \__kernel_backend_shipout_literal:e {ps: SDict ~ begin ~ mark /#1~<<#2>>~/BDC ~ pdfmark ~ end } } } \cs_set_protected:Npn \@@_backend_bdcobject:nn #1 #2 % #1 eg. Span, #2: object name { \@@_backend_pdfmark:e{/#1~\pdf_object_ref:n{#2}~/BDC} } \cs_set_protected:Npn \@@_backend_bdcobject:n #1 % #1 eg. Span, { \@@_backend_pdfmark:e{/#1~\@@_backend_object_last:~/BDC} } \cs_set_protected:Npn \@@_backend_emc: { \@@_backend_pdfmark:n{/EMC} % } \cs_set_protected:Npn \@@_backend_bmc:n #1 { \@@_backend_pdfmark:n{/#1~/BMC} % } \cs_new_protected:Npn \@@_backend_PageResources_gpush:n #1 {} % %<*dvisvgm> % dvisvgm should do nothing % \cs_set_protected:Npn \@@_backend_bdc:nn #1 #2 % #1 eg. Span, #2: dict_content {} \cs_set_eq:NN \@@_backend_bdc_contobj:nn \@@_backend_bdc:nn \cs_set_eq:NN \@@_backend_bdc_contstream:nn \@@_backend_bdc:nn \bool_if:NT\l__pdfmanagement_delayed_shipout_bool { \cs_set_protected:Npn \@@_backend_shipout_bdc:ee #1 #2 % #1 eg. Span, #2: dict_content {} } \cs_set_protected:Npn \@@_backend_bdcobject:nn #1 #2 % #1 eg. Span, #2: object name {} \cs_set_protected:Npn \@@_backend_bdcobject:n #1 % #1 eg. Span, {} \cs_set_protected:Npn \@@_backend_emc: {} \cs_set_protected:Npn \@@_backend_bmc:n #1 {} \cs_new_protected:Npn \@@_backend_PageResources_gpush:n #1 {} % % % xetex has to create the entries in the /Properties manually % (like the other backends) % use pdfbase special % https://chat.stackexchange.com/transcript/message/50832016#50832016 % the property is added to xform resources automatically, % no need to worry about it. %<*dvipdfmx|xdvipdfmx> \cs_set_protected:Npn \@@_backend_bdcobject:nn #1 #2 % #1 eg. Span, #2: object name { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal:e { pdf:code~/#1/l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } \__kernel_backend_literal:e { pdf:put~@resources~ << /Properties~ << /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl \pdf_object_ref:n { #2 } >> >> } } \cs_set_protected:Npn \@@_backend_bdcobject:n #1 % #1 eg. Span { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal:e { pdf:code~/\exp_not:n{#1}/l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } \__kernel_backend_literal:e { pdf:put~@resources~ << /Properties~ << /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl \@@_backend_object_last: >> >> } } \cs_set_protected:Npn \@@_backend_bmc:n #1 { \__kernel_backend_literal:n {pdf:code~/#1~BMC} %pdfbase } %this require management \cs_set_protected:Npn \@@_backend_bdc_contobj:nn #1 #2 { \pdf_object_unnamed_write:nn { dict }{ #2 } \@@_backend_bdcobject:n { #1 } } \cs_set_protected:Npn \@@_backend_bdc_contstream:nn #1 #2 { \__kernel_backend_literal:n {pdf:code~ /#1~<<#2>>~BDC } } \cs_set_protected:Npn \@@_backend_bdc:nn #1 #2 { \bool_if:NTF \g__pdfmanagement_active_bool {\cs_gset_eq:NN \@@_backend_bdc:nn \@@_backend_bdc_contobj:nn} {\cs_gset_eq:NN \@@_backend_bdc:nn \@@_backend_bdc_contstream:nn} \@@_backend_bdc:nn {#1}{#2} } \bool_if:NT\l__pdfmanagement_delayed_shipout_bool { \cs_set_protected:Npn \@@_backend_bdc_shipout_contstream:ee #1 #2 { \__kernel_backend_shipout_literal:e {pdf:code~ /#1~<<#2>>~BDC } } \cs_set_eq:NN \@@_backend_bdc_shipout:ee \@@_backend_bdc_shipout_contstream:ee } \cs_set_protected:Npn \@@_backend_emc: { \__kernel_backend_literal:n {pdf:code~EMC} %pdfbase } % properties are handled automatically, but the other resources should be added % at shipout \cs_new_protected:Npn \@@_backend_PageResources_gpush:n #1 { \clist_map_inline:Nn \c_@@_backend_PageResources_clist { \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/##1} } { \__kernel_backend_literal:e { pdf:put~@resources~ <> } } } } % % luatex + pdftex %<*luatex> \cs_set_protected:Npn \@@_backend_bdcobject:nn #1 #2 % #1 eg. Span, #2: object name { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal_page:e { /#1 ~ /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } \bool_if:NTF \l_@@_backend_xform_bool { \pdfdict_gput:nee { g_@@_Core/Xform/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_name_int } { \pdf_object_ref:n { #2 } } } { \exp_args:Ne \tex_latelua:D { ltx.pdf.Page_Resources_Properties_gput ( tex.count["g_shipout_readonly_int"], "l3pdf\int_use:N\g_@@_backend_name_int", "\pdf_object_ref:n { #2 }" ) } } } \cs_set_protected:Npn \@@_backend_bdcobject:n #1% #1 eg. Span { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal_page:e { /\exp_not:n{#1} ~ /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } \bool_if:NTF \l_@@_backend_xform_bool { \pdfdict_gput:nee %no handler needed { g_@@_Core/Xform/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_name_int } { \@@_backend_object_last: } } { \exp_args:Ne \tex_latelua:D { ltx.pdf.Page_Resources_Properties_gput ( tex.count["g_shipout_readonly_int"], "l3pdf\int_use:N\g_@@_backend_name_int", "\@@_backend_object_last:" ) } } } \cs_set_protected:Npn \@@_backend_bmc:n #1 { \__kernel_backend_literal_page:n { /#1~BMC } } \cs_set_protected:Npn \@@_backend_bdc_contobj:nn #1 #2 { \pdf_object_unnamed_write:nn { dict } { #2 } \@@_backend_bdcobject:n { #1 } } \cs_set_protected:Npn \@@_backend_bdc_contstream:nn #1 #2 { \__kernel_backend_literal_page:n { /#1~<<#2>>~BDC } } % \end{macrocode} % \changes{v0.96n}{2024/10/23}{Use inline dictionaries in bdc always.} % \begin{macrocode} \cs_set_eq:NN \@@_backend_bdc:nn \@@_backend_bdc_contstream:nn \bool_if:NT\l__pdfmanagement_delayed_shipout_bool { \cs_set_protected:Npn \@@_backend_bdc_shipout_contstream:ee #1 #2 { \__kernel_backend_shipout_literal_page:e { /#1~<<#2>>~BDC } } \cs_set_eq:NN \@@_backend_bdc_shipout:ee \@@_backend_bdc_shipout_contstream:ee } \cs_set_protected:Npn \@@_backend_emc: { \__kernel_backend_literal_page:n { EMC } } \cs_new_protected:Npn \@@_backend_PageResources_gpush:n #1 {} % % \end{macrocode} % pdflatex is the most complicated if we want to use properties % as it has to go through the aux ... % the push command is extended to take other resources too % \begin{macrocode} %<*pdftex> \cs_set_protected:Npn \@@_backend_bdcobject:nn #1 #2 % #1 eg. Span, #2: object name { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal_page:e { /#1 ~ /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } % code to set the property .... \int_gincr:N\g_@@_backend_resourceid_int \bool_if:NTF \l_@@_backend_xform_bool { \pdfdict_gput:nee %no handler needed { g_@@_Core/Xform/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_resourceid_int } { \pdf_object_ref:n { #2 } } } { \@@_backend_record_abspage:e {l3pdf\int_use:N\g_@@_backend_resourceid_int} \tl_set:Ne \l_@@_tmpa_tl { \@@_backend_ref_abspage:e{l3pdf\int_use:N\g_@@_backend_resourceid_int} } \pdfdict_if_exist:nF { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } { \pdfdict_new:n { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } } \pdfdict_gput:nee { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_resourceid_int } { \pdf_object_ref:n{#2} } } } \cs_set_protected:Npn \@@_backend_bdcobject:n #1% #1 eg. Span { \int_gincr:N \g_@@_backend_name_int \__kernel_backend_literal_page:e { /\exp_not:n{#1} ~ /l3pdf\int_use:N\g_@@_backend_name_int\c_space_tl BDC } % code to set the property .... \int_gincr:N\g_@@_backend_resourceid_int \bool_if:NTF \l_@@_backend_xform_bool { \pdfdict_gput:nee { g_@@_Core/Xform/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_resourceid_int } { \@@_backend_object_last: } } { \@@_backend_record_abspage:e{l3pdf\int_use:N\g_@@_backend_resourceid_int} \tl_set:Ne \l_@@_tmpa_tl { \@@_backend_ref_abspage:e{l3pdf\int_use:N\g_@@_backend_resourceid_int} } \pdfdict_if_exist:nF { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } { \pdfdict_new:n { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } } \pdfdict_gput:nee { g_@@_Core/backend_Page\l_@@_tmpa_tl/Resources/Properties } { l3pdf\int_use:N\g_@@_backend_resourceid_int } { \@@_backend_object_last: } %\pdfdict_show:n { g_backend_Page\l_@@_tmpa_tl/Resources/Properties } } } \cs_set_protected:Npn \@@_backend_bmc:n #1 { \__kernel_backend_literal_page:n { /#1~BMC } } \cs_set_protected:Npn \@@_backend_bdc_contobj:nn #1 #2 { \pdf_object_unnamed_write:nn { dict } { #2 } \@@_backend_bdcobject:n { #1 } } \cs_set_protected:Npn \@@_backend_bdc_contstream:nn #1 #2 { \__kernel_backend_literal_page:n { /#1~<<#2>>~BDC } } % \end{macrocode} % \changes{v0.96n}{2024/10/23}{Use inline dictionaries in bdc always.} % We use by default the direct BDC. % \begin{macrocode} \cs_set_eq:NN \@@_backend_bdc:nn \@@_backend_bdc_contstream:nn \bool_if:NT\l__pdfmanagement_delayed_shipout_bool { \cs_set_protected:Npn \@@_backend_bdc_shipout_contstream:ee #1 #2 { \__kernel_backend_shipout_literal_page:e { /#1~<<#2>>~BDC } } \cs_set_eq:NN \@@_backend_bdc_shipout:ee \@@_backend_bdc_shipout_contstream:ee } \cs_set_protected:Npn \@@_backend_emc: { \__kernel_backend_literal_page:n { EMC } } \cs_new:Npn \@@_backend_PageResources_gpush_aux:n #1 %#1 ExtGState etc { \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/#1} } { \pdfdict_item:ne { #1 }{ \pdf_object_ref:n {@@/Page/Resources/#1}} } } \cs_new_protected:Npn \@@_backend_PageResources_gpush:n #1 { \exp_args:NNe \tex_global:D \tex_pdfpageresources:D { \prop_if_exist:cT { \__kernel_pdfdict_name:n { g_@@_Core/backend_Page#1/Resources/Properties } } { /Properties~ << \prop_map_function:cN { \__kernel_pdfdict_name:n { g_@@_Core/backend_Page#1/Resources/Properties } } \pdfdict_item:ne >> } %% add ExtGState etc \clist_map_function:NN \c_@@_backend_PageResources_clist \@@_backend_PageResources_gpush_aux:n } } % % \end{macrocode} % \end{macro} % % \subsection{\enquote{Catalog} \& subdirectories (pdfcatalog) } % The backend command is already in the driver: % \cs{@@_backend_catalog_gput:nn} % \subsubsection { Special case: the /Names/EmbeddedFiles dictionary } % Entries to /Names are handled differently, in part (/Desc) it is automatic, for % other special commands like \cs{pdfnames} must be used. For EmbeddedFiles % dvips wants code for every file and then creates the Name tree automatically. % Other name trees are ignored. % TODO: Currently the code for EmbeddedFiles is still a bit % different but this should be merged, all name trees should be handled with the % same code. % \begin{macrocode} % pdflatex %<*pdftex> \cs_new_protected:Npn \@@_backend_Names_gpush:nn #1 #2 %#1 name of name tree, #2 array content { \pdf_object_unnamed_write:nn {dict} {/Names [#2] } \tex_pdfnames:D {/#1~\pdf_object_ref_last:} } % %<*luatex> \cs_new_protected:Npn \@@_backend_Names_gpush:nn #1 #2 %#1 name of name tree, #2 array content { \pdf_object_unnamed_write:nn {dict} {/Names [#2] } \tex_pdfextension:D~names~ {/#1~\pdf_object_ref_last:} } % %<*dvipdfmx|xdvipdfmx> \cs_new_protected:Npn \@@_backend_Names_gpush:nn #1 #2 %#1 name of name tree, #2 array content { \pdf_object_unnamed_write:nn {dict} {/Names [#2] } \@@_backend:e {put~@names~<>} } % %dvips: noop %<*dvips> \cs_new_protected:Npn \@@_backend_Names_gpush:nn #1 #2 {} % %dvisvgm: noop %<*dvisvgm> \cs_new_protected:Npn \@@_backend_Names_gpush:nn #1 #2 {} % % \end{macrocode} % EmbeddedFiles is a bit special. % For once we need backend commands for dvips. % But we want also an option to create the name on the fly. % % \begin{macro}{\@@_backend_NamesEmbeddedFiles_add:nn} % dvips need special backend code to create the name tree. % With the other engines it does nothing. % \begin{macrocode} %<*pdftex|luatex|dvipdfmx|xdvipdfmx> \cs_new_protected:Npn \@@_backend_NamesEmbeddedFiles_add:nn #1 #2 {} % %<*dvips> \cs_new_protected:Npn \@@_backend_NamesEmbeddedFiles_add:nn #1 #2 { \@@_backend_pdfmark:e { /Name~#1~ /FS~#2~ /EMBED } } % %<*dvisvgm> %no op. Or is there any sensible use for it? \cs_new_protected:Npn \@@_backend_NamesEmbeddedFiles_add:nn #1 #2 {} % % \end{macrocode} % \end{macro} % % \subsubsection{Additional annotation commands} % Starting with texlive 2021 pdftex and luatex offer commands to interrupt % a link. That can for example be used to exclude the header and footer from % the link. We add here backend support for this. % \begin{macrocode} %<*drivers> \cs_new_protected:Npn \@@_backend_link_off:{} \cs_new_protected:Npn \@@_backend_link_on: {} % %<*pdftex> \cs_if_exist:NT \pdfrunninglinkoff { \cs_set_protected:Npn \@@_backend_link_off: { \pdfrunninglinkoff } \cs_set_protected:Npn \@@_backend_link_on: { \pdfrunninglinkon } } % %<*luatex> \int_compare:nNnT {\tex_luatexversion:D } > {112} { \cs_set_protected:Npn \@@_backend_link_off: { \pdfextension linkstate 1 } \cs_set_protected:Npn \@@_backend_link_on: { \pdfextension linkstate 0 } } % %<*dvipdfmx|xdvipdfmx> \cs_set_protected:Npn \@@_backend_link_off: { \@@_backend:n { nolink } } \cs_set_protected:Npn \@@_backend_link_on: { \@@_backend:n { link } } % % \end{macrocode} % % \subsubsection{Form XObject / backend } % \begin{macro}{ \@@_backend_xform_new:nnnn } % \begin{arguments} % \item name % \item attributes % \item resources %needed?? or are all resources autogenerated? % \item content, this doesn't need to be a box! % \end{arguments} % \begin{macro}{ \@@_backend_xform_use:n, \@@_backend_xform_ref:n } % \begin{macrocode} %<*pdftex> \cs_new_protected:Npn \@@_backend_xform_new:nnnn #1 #2 #3 #4 % #1 name % #2 attributes % #3 resources % #4 content, not necessarily a box! { \hbox_set:Nn \l_@@_backend_tmpa_box { \bool_set_true:N \l_@@_backend_xform_bool \prop_gclear:c {\__kernel_pdfdict_name:n { g_@@_Core/Xform/Resources/Properties }} #4 } %store the dimensions \tl_const:ce { c_@@_backend_xform_wd_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_wd:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_ht_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_ht:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_dp_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_dp:N \l_@@_backend_tmpa_box } %% do we need to test if #2 and #3 are empty?? \tex_immediate:D \tex_pdfxform:D ~ attr ~ { #2 } %% which other resources should be default? Is an argument actually needed? ~ resources ~ { #3 \int_compare:nNnT { \prop_count:c { \__kernel_pdfdict_name:n { g_@@_Core/Xform/Resources/Properties } } } > { 0 } { /Properties~ << \pdfdict_use:n { g_@@_Core/Xform/Resources/Properties } >> } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/ExtGState } } { /ExtGState~ \pdf_object_ref:n { @@/Page/Resources/ExtGState } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/Pattern } } { /Pattern~ \pdf_object_ref:n { @@/Page/Resources/Pattern } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/Shading } } { /Shading~ \pdf_object_ref:n { @@/Page/Resources/Shading } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/ColorSpace } } { /ColorSpace~ \pdf_object_ref:n { @@/Page/Resources/ColorSpace } } } \l_@@_backend_tmpa_box \int_const:cn { c_@@_backend_xform_ \tl_to_str:n {#1} _int } { \tex_pdflastxform:D } } \cs_new_protected:Npn \@@_backend_xform_use:n #1 { \tex_pdfrefxform:D \int_use:c { c_@@_backend_xform_ \tl_to_str:n {#1} _int } \scan_stop: } \cs_new:Npn \@@_backend_xform_ref:n #1 { \int_use:c { c_@@_backend_xform_ \tl_to_str:n {#1} _int } ~ 0 ~ R } % %<*luatex> %luatex %nearly identical but not completely ... \cs_new_protected:Npn \@@_backend_xform_new:nnnn #1 #2 #3 #4 % #1 name % #2 attributes % #3 resources % #4 content, not necessarily a box! { \hbox_set:Nn \l_@@_backend_tmpa_box { \bool_set_true:N \l_@@_backend_xform_bool \prop_gclear:c { \__kernel_pdfdict_name:n { g_@@_Core/Xform/Resources/Properties } } #4 } \tl_const:ce { c_@@_backend_xform_wd_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_wd:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_ht_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_ht:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_dp_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_dp:N \l_@@_backend_tmpa_box } %% do we need to test if #2 and #3 are empty?? \tex_immediate:D \tex_pdfxform:D ~ attr ~ { #2 } %% which resources should be default? Is an argument actually needed? ~ resources ~ { #3 \int_compare:nNnT {\prop_count:c { \__kernel_pdfdict_name:n { g_@@_Core/Xform/Resources/Properties } }} > { 0 } { /Properties~ << \pdfdict_use:n { g_@@_Core/Xform/Resources/Properties } >> } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/ExtGState } } { /ExtGState~ \pdf_object_ref:n { @@/Page/Resources/ExtGState } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/Pattern } } { /Pattern~ \pdf_object_ref:n { @@/Page/Resources/Pattern } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/Shading } } { /Shading~ \pdf_object_ref:n { @@/Page/Resources/Shading } } \prop_if_empty:cF { \__kernel_pdfdict_name:n { g_@@_Core/Page/Resources/ColorSpace } } { /ColorSpace~ \pdf_object_ref:n { @@/Page/Resources/ColorSpace } } } \l_@@_backend_tmpa_box \int_const:cn { c_@@_backend_xform_ \tl_to_str:n {#1} _int } { \tex_pdflastxform:D } } \cs_new_protected:Npn \@@_backend_xform_use:n #1 %protected as with xelatex { \tex_pdfrefxform:D \int_use:c { c_@@_backend_xform_ \tl_to_str:n {#1} _int } \scan_stop: } \cs_new:Npn \@@_backend_xform_ref:n #1 { \int_use:c { c_@@_backend_xform_ \tl_to_str:n {#1} _int } ~ 0 ~ R } % %<*dvipdfmx|xdvipdfmx> % xetex % it needs a bit testing if it really works to set the box to 0 before the special ... % does it disturb viewing the xobject? % what happens with the resources (bdc)? (should work as they are specials too) % xetex requires that the special is in horizontal mode. This means it affects % typesetting. But we can no delay the whole form code to shipout % as the object reference and the size is often wanted on the current page. % so we need to allocate a box - but probably they won't be thousands xform % in a document so it shouldn't matter. \cs_new_protected:Npn \@@_backend_xform_new:nnnn #1 #2 #3 #4 % #1 name % #2 attributes % #3 resources % #4 content, not necessarily a box! { \int_gincr:N \g_@@_backend_object_int \int_const:cn { c_@@_backend_xform_ \tl_to_str:n {#1} _int } { \g_@@_backend_object_int } \box_new:c { g_@@_backend_xform_#1_box } \hbox_gset:cn { g_@@_backend_xform_#1_box } { \bool_set_true:N \l_@@_backend_xform_bool #4 } \tl_const:ce { c_@@_backend_xform_wd_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_wd:c { g_@@_backend_xform_#1_box } } \tl_const:ce { c_@@_backend_xform_ht_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_ht:c { g_@@_backend_xform_#1_box } } \tl_const:ce { c_@@_backend_xform_dp_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_dp:c { g_@@_backend_xform_#1_box } } \box_set_dp:cn { g_@@_backend_xform_#1_box } { \c_zero_dim } \box_set_ht:cn { g_@@_backend_xform_#1_box } { \c_zero_dim } \box_set_wd:cn { g_@@_backend_xform_#1_box } { \c_zero_dim } \hook_gput_next_code:nn {shipout/background} { \mode_leave_vertical: %needed, the xform disappears without it. \@@_backend:e { bxobj ~ \@@_backend_xform_ref:n { #1 } \c_space_tl width ~ \pdfxform_wd:n { #1 } \c_space_tl height ~ \pdfxform_ht:n { #1 } \c_space_tl depth ~ \pdfxform_dp:n { #1 } } \box_use_drop:c { g_@@_backend_xform_#1_box } \@@_backend:e {put ~ @resources ~<<#3>> } \@@_backend:e { put~ @resources ~ << /ExtGState~ \pdf_object_ref:n { @@/Page/Resources/ExtGState } >> } \@@_backend:e { put~ @resources ~ << /Pattern~ \pdf_object_ref:n { @@/Page/Resources/Pattern } >> } \@@_backend:e { put~ @resources ~ << /Shading~ \pdf_object_ref:n { @@/Page/Resources/Shading } >> } \@@_backend:e { put~ @resources ~ << /ColorSpace~ \pdf_object_ref:n { @@/Page/Resources/ColorSpace } >> } \@@_backend:e {exobj ~<<#2>>} } } \cs_new:Npn \@@_backend_xform_ref:n #1 { @pdf.xform \int_use:c { c_@@_backend_xform_ \tl_to_str:n {#1} _int } } \cs_new_protected:Npn \@@_backend_xform_use:n #1 { \hbox_set:Nn \l_@@_backend_tmpa_box { \@@_backend:e { uxobj~ \@@_backend_xform_ref:n { #1 } } } \box_set_wd:Nn \l_@@_backend_tmpa_box { \pdfxform_wd:n { #1 } } \box_set_ht:Nn \l_@@_backend_tmpa_box { \pdfxform_ht:n { #1 } } \box_set_dp:Nn \l_@@_backend_tmpa_box { \pdfxform_dp:n { #1 } } \box_use_drop:N \l_@@_backend_tmpa_box } % %<*dvisvgm> % unclear what it should do!! \cs_new_protected:Npn \@@_backend_xform_new:nnnn #1 #2 #3 #4 {} \cs_new_protected:Npn \@@_backend_xform_use:n #1 {} \cs_new:Npn \@@_backend_xform_ref:n {} % % \end{macrocode} % The xform code for dvips is based on code from the attachfile2 package % (in atfi-dvips), along with some ideas from pdfbase and has been corrected % with the help of Alexander Grahn. % Details like clipping and landscape will probably be corrected in the future. % We need some temporary variables to store dimensions % \begin{macrocode} %<*dvips> \tl_new:N \l_@@_backend_xform_tmpwd_tl \tl_new:N \l_@@_backend_xform_tmpdp_tl \tl_new:N \l_@@_backend_xform_tmpht_tl % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn\@@_backend_xform_new:nnnn #1 #2 #3 #4 % #1 name, #2 attribute, #4 content { \int_gincr:N \g_@@_backend_object_int \int_const:cn { c_@@_backend_xform_ \tl_to_str:n {#1} _int } { \g_@@_backend_object_int } \hbox_set:Nn \l_@@_backend_tmpa_box { \bool_set_true:N \l_@@_backend_xform_bool \prop_gclear:c {\__kernel_pdfdict_name:n { g_@@_Core/Xform/Resources/Properties }} #4 } %store the dimensions \tl_const:ce { c_@@_backend_xform_wd_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_wd:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_ht_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_ht:N \l_@@_backend_tmpa_box } \tl_const:ce { c_@@_backend_xform_dp_ \tl_to_str:n {#1} _tl } { \tex_the:D \box_dp:N \l_@@_backend_tmpa_box } %store content dimensions in DPI units (Dots) (code from issue 25) \tl_set:Ne\l_@@_backend_xform_tmpwd_tl { \dim_to_decimal_in_sp:n{ \box_wd:N \l_@@_backend_tmpa_box }~ 65536~div~72.27~div~DVImag~mul~Resolution~mul~ } \tl_set:Ne\l_@@_backend_xform_tmpht_tl { \dim_to_decimal_in_sp:n{ \box_ht:N \l_@@_backend_tmpa_box }~ 65536~div~72.27~div~DVImag~mul~VResolution~mul~ } \tl_set:Ne\l_@@_backend_xform_tmpdp_tl { \dim_to_decimal_in_sp:n{ \box_dp:N \l_@@_backend_tmpa_box }~ 65536~div~72.27~div~DVImag~mul~VResolution~mul~ } % mirror the box %\box_scale:Nnn \l_@@_backend_tmpa_box {1} {-1} \hbox_set:Nn\l_@@_backend_tmpb_box { \__kernel_backend_postscript:e { gsave~currentpoint~ initclip~ % restore default clipping path (page device/whole page) clippath~pathbbox~newpath~pop~pop~ \tl_use:N\l_@@_backend_xform_tmpdp_tl~add~translate~ mark~ /_objdef~{ pdf.obj \int_use:N\g_@@_backend_object_int }\c_space_tl~ /BBox[ 0~ \tl_use:N\l_@@_backend_xform_tmpht_tl~ \tl_use:N\l_@@_backend_xform_tmpwd_tl~ \tl_use:N\l_@@_backend_xform_tmpdp_tl~ neg ] \str_if_eq:eeF{#1}{} { product~(Distiller)~search~{pop~pop~pop~#2}{pop}ifelse~ } /BP~pdfmark~1~-1~scale~neg~exch~neg~exch~translate } \box_use_drop:N\l_@@_backend_tmpa_box \__kernel_backend_postscript:n { mark ~ /EP~pdfmark ~ grestore } \str_if_eq:eeF{#1}{} { \__kernel_backend_postscript:e { product~(Ghostscript)~search~ { pop~pop~pop~ mark~ { pdf.obj \int_use:c{c_@@_backend_xform_ \tl_to_str:n {#1} _int} } ~<<#2>>~/PUT~pdfmark }{pop}ifelse } } } \box_set_dp:Nn \l_@@_backend_tmpb_box { \c_zero_dim } \box_set_ht:Nn \l_@@_backend_tmpb_box { \c_zero_dim } \box_set_wd:Nn \l_@@_backend_tmpb_box { \c_zero_dim } \hook_gput_code:nnn {begindocument/end}{pdfxform} { \mode_leave_vertical: \box_use:N\l_@@_backend_tmpb_box } } \cs_new_protected:Npn \@@_backend_xform_use:n #1 { \hbox_set:Nn \l_@@_backend_tmpa_box { \__kernel_backend_postscript:e { gsave~currentpoint~translate~1~-1~scale~ mark~{ pdf.obj \int_use:c{c__pdf_backend_xform_ \tl_to_str:n {#1} _int }}~ /SP~pdfmark ~ grestore } } \box_set_wd:Nn \l_@@_backend_tmpa_box { \pdfxform_wd:n { #1 } } \box_set_ht:Nn \l_@@_backend_tmpa_box { \pdfxform_ht:n { #1 } } \box_set_dp:Nn \l_@@_backend_tmpa_box { \pdfxform_dp:n { #1 } } \box_use_drop:N \l_@@_backend_tmpa_box } \cs_new:Npn \@@_backend_xform_ref:n #1 { { pdf.obj \int_use:c{c_@@_backend_xform_ \tl_to_str:n {#1} _int} } } % %<*drivers> %% all \prg_new_conditional:Npnn \@@_backend_xform_if_exist:n #1 { p , T , F , TF } { \int_if_exist:cTF { c_@@_backend_xform_ \tl_to_str:n {#1} _int } { \prg_return_true: } { \prg_return_false:} } \prg_new_eq_conditional:NNn \pdfxform_if_exist:n\@@_backend_xform_if_exist:n { TF , T , F , p } % % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Structure Destinations} % Standard destinations consist of a reference to a page in the pdf and instructions % how to display it---typically they will put a specific location in the left top corner % of the viewer and so give the impression that a link jumped to the word in this place. % But in reality they are not connected to the content. % % Starting with pdf~2.0 destinations can in a tagged PDF also point to a structure, to a /StructElem object. % GoTo links can then additionally to the \texttt{/D} key pointing to a % page destination also point to such a structure destination with an \texttt{/SD} key. % Programs that e.g. convert such a PDF to html can then create better links. % (According to the reference, PDF-viewer should prefer the structure destination % over the page destination, but as far as it is known this isn't done yet.) % % Currently structure destinations and GoTo links making use of it could natively only % be created with the dvipdfmx backend. With pdftex and lualatex it was only possible to create % a restricted type which used only the \enquote{Fit} mode. Starting with % \TeX{}live 2022 (earlier in miktex) both engine will knew new keywords which allow % to create structure destination easily. % % The following backend code prepares the use of structure destinations. The general idea is % that if structure destinations are used, they should be used always. So we define % alternative commands which can be activated by mapping them to the standard backend commands. % % The needed code differ depending on if structure objects use standard or indexed object names. % At the end we will probably always use indexed objects, but for now we offer % both options. % % \begin{variable}[no-user-doc]{\l_pdf_current_structure_destination_tl} % % This command holds the name of the structure object to use in the following commands % which creates a destination. The code which activates structure destinations % must also ensure that it has a sensible, expandable content. \pkg{tagpdf} for example % will define it as % \begin{verbatim} % \tl_set:Nn \l_pdf_current_structure_destination_tl { __tag/struct/\g__tag_struct_stack_current_tl } % \end{verbatim} % or if indexed structure object names are used % \begin{verbatim} % \tl_set:Nn \l_pdf_current_structure_destination_tl { {__tag/struct}{\g__tag_struct_stack_current_tl} } % \end{verbatim} % \begin{macrocode} %<*drivers> \tl_new:N \l_pdf_current_structure_destination_tl % % \end{macrocode} % \end{variable} % % We will define alternatives for three backend commands: % \begin{verbatim} % \__pdf_backend_destination:nn -> \__pdf_backend_structure_destination:nn % \__pdf_backend_destination:nnnn -> \__pdf_backend_structure_destination:nnnn % \__pdf_backend_link_begin_goto:nnw -> \__pdf_backend_link_begin_structure_goto:nnw % \__pdf_backend_destination:nn -> \__pdf_backend_indexed_structure_destination:nn % \__pdf_backend_destination:nnnn -> \__pdf_backend_indexed_structure_destination:nnnn % \__pdf_backend_link_begin_goto:nnw -> \__pdf_backend_indexed_link_begin_structure_goto:nnw % \end{verbatim} % % Activating means mapping them onto the original commands. Be aware that not % all engines and compilation routes support structure destinations, for them % the command will be a no-op. % % \begin{macro}[no-user-doc]{\pdf_activate_structure_destination:,\pdf_activate_indexed_structure_destination:} % \begin{macrocode} %<*drivers> \cs_new_protected:Npn \pdf_activate_structure_destination: { \cs_gset_eq:NN \@@_backend_destination:nn \@@_backend_structure_destination:nn \cs_gset_eq:NN \@@_backend_destination:nnnn \@@_backend_structure_destination:nnnn \cs_gset_eq:NN \@@_backend_link_begin_goto:nnw \@@_backend_link_begin_structure_goto:nnw } \cs_new_protected:Npn \pdf_activate_indexed_structure_destination: { \cs_gset_eq:NN \@@_backend_destination:nn \@@_backend_indexed_structure_destination:nn \cs_gset_eq:NN \@@_backend_destination:nnnn \@@_backend_indexed_structure_destination:nnnn \cs_gset_eq:NN \@@_backend_link_begin_goto:nnw \@@_backend_link_begin_structure_goto:nnw } % % \end{macrocode} % \end{macro} % Now the driver dependent parts. % By default the new commands are simply copies of the original commands. % We adapt them then for the engines and engine version which provide support % for structure destinations. % % \begin{macrocode} %<*drivers> \cs_set_eq:NN \@@_backend_structure_destination:nn \@@_backend_destination:nn \cs_set_eq:NN \@@_backend_structure_destination:nnnn \@@_backend_destination:nnnn \cs_set_eq:NN \@@_backend_link_begin_structure_goto:nnw \@@_backend_link_begin_goto:nnw \cs_set_eq:NN \@@_backend_indexed_structure_destination:nn \@@_backend_destination:nn \cs_set_eq:NN \@@_backend_indexed_structure_destination:nnnn \@@_backend_destination:nnnn % % \end{macrocode} % \begin{macro}{\@@_backend_structure_destination:nn, % \@@_backend_structure_destination:nnnn, % \@@_backend_link_begin_structure_goto:nnw} % These commands are the backend commands to create a destination. % which create also a structure destination. % At first xetex/dvipdfmx. % The structure destination is an array, so we use obj for it % so that we can reference it: % \begin{macrocode} %<*xdvipdfmx|dvipdfmx> \cs_set_protected:Npn \@@_backend_structure_destination:nn #1#2 { \@@_backend:e { dest ~ ( \exp_not:n {#1} ) [ @thispage \str_case:nnF {#2} { { xyz } { /XYZ ~ @xpos ~ @ypos ~ null } { fit } { /Fit } { fitb } { /FitB } { fitbh } { /FitBH } { fitbv } { /FitBV ~ @xpos } { fith } { /FitH ~ @ypos } { fitv } { /FitV ~ @xpos } { fitr } { /Fit } } { /XYZ ~ @xpos ~ @ypos ~ \fp_eval:n { (#2) / 100 } } ] } % \end{macrocode} % We test if the structure object exist. The object of the structure destination % gets the name \texttt{@pdf.Sdest.\meta{destname}}, where \meta{destname} is the % name of the standard destination so that we can reference it in the GoTo links. % \begin{macrocode} \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \@@_backend:e { obj ~ @pdf.SDest.\exp_not:n{#1} [ \exp_args:Ne \pdf_object_ref:n { \l_pdf_current_structure_destination_tl } \str_case:nnF {#2} { { xyz } { /XYZ ~ @xpos ~ @ypos ~ null } { fit } { /Fit } { fitb } { /FitB } { fitbh } { /FitBH } { fitbv } { /FitBV ~ @xpos } { fith } { /FitH ~ @ypos } { fitv } { /FitV ~ @xpos } { fitr } { /Fit } } { /XYZ ~ @xpos ~ @ypos ~ \fp_eval:n { (#2) / 100 } } ] } } } % \end{macrocode} % The second destination command is for the boxed destination. Here we need to define % an new auxiliary command: % \begin{macrocode} \cs_new_protected:Npn \@@_backend_structure_destination_aux:nnnn #1#2#3#4 { \vbox_to_zero:n { \__kernel_kern:n {#4} \hbox:n { \@@_backend:n { obj ~ @pdf_ #2 _llx ~ @xpos } \@@_backend:n { obj ~ @pdf_ #2 _lly ~ @ypos } } \tex_vss:D } \__kernel_kern:n {#1} \vbox_to_zero:n { \__kernel_kern:n { -#3 } \hbox:n { \@@_backend:n { dest ~ (#2) [ @thispage /FitR ~ @pdf_ #2 _llx ~ @pdf_ #2 _lly ~ @xpos ~ @ypos ] } % \end{macrocode} % Here we add the structure destination to the same box % \begin{macrocode} \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \@@_backend:e { obj ~ @pdf.SDest.\exp_not:n{#2} [ \exp_args:Ne \pdf_object_ref:n { \l_pdf_current_structure_destination_tl } /FitR ~ @pdf_ #2 _llx ~ @pdf_ #2 _lly ~ @xpos ~ @ypos ] } } } \tex_vss:D } \__kernel_kern:n { -#1 } } % \end{macrocode} % And now we redefine the destination command: % \begin{macrocode} \cs_set_protected:Npn \@@_backend_structure_destination:nnnn #1#2#3#4 { \exp_args:Ne \@@_backend_structure_destination_aux:nnnn { \dim_eval:n {#2} } {#1} {#3} {#4} } % \end{macrocode} % At last the goto link. % \begin{macrocode} \cs_set_protected:Npn \@@_backend_link_begin_structure_goto:nnw #1#2 { \@@_backend_link_begin:n { #1 /Subtype /Link /A << /S /GoTo /D ( #2 ) /SD~@pdf.SDest.#2 >> } } % % \end{macrocode} % Now pdftex. We only redefine for version 1.40 revision 24 or later. % \begin{macrocode} %<*pdftex> \bool_lazy_and:nnT { \int_compare_p:nNn {\tex_pdftexversion:D } > {139} } { \int_compare_p:nNn {\tex_pdftexrevision:D } > {23} } { \cs_set_protected:Npn \@@_backend_structure_destination:nn #1#2 { \tex_pdfdest:D name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \tex_pdfdest:D struct~ \int_use:c { c_@@_object_ \exp_args:Ne \tl_to_str:n {\l_pdf_current_structure_destination_tl} _int }~ name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: } } \cs_set_protected:Npn \@@_backend_structure_destination:nnnn #1#2#3#4 { \tex_pdfdest:D name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \tex_pdfdest:D struct~ \int_use:c { c_@@_object_ \exp_args:Ne \tl_to_str:n {\l_pdf_current_structure_destination_tl} _int }~ name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: } } \cs_set_protected:Npn \@@_backend_link_begin_structure_goto:nnw #1#2 { \@@_backend_link_begin:nnnw {#1} { goto~struct~name~{#2}~name } {#2} } } % % \end{macrocode} % luatex is quite similar to pdftex. Mostly the test for the version is different % \begin{macrocode} %<*luatex> \int_compare:nNnT {\directlua{tex.print(status.list()["development_id"])} } > {7468} { \cs_set_protected:Npn \@@_backend_structure_destination:nn #1#2 { \tex_pdfextension:D dest name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \tex_pdfextension:D dest struct~ \int_use:c { c_@@_object_ \exp_args:Ne \tl_to_str:n {\l_pdf_current_structure_destination_tl} _int }~ name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: } } \cs_set_protected:Npn \@@_backend_structure_destination:nnnn #1#2#3#4 { \tex_pdfextension:D dest name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: \exp_args:Ne \pdf_object_if_exist:nT { \l_pdf_current_structure_destination_tl } { \tex_pdfextension:D dest struct~ \int_use:c { c_@@_object_ \exp_args:Ne \tl_to_str:n {\l_pdf_current_structure_destination_tl} _int }~ name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: } } \cs_set_protected:Npn \@@_backend_link_begin_structure_goto:nnw #1#2 { \@@_backend_link_begin:nnnw {#1} { goto~struct~name~{#2}~name } {#2} } } % % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_indexed_structure_destination:nn, % \@@_backend_indexed_structure_destination:nnnn} % This are the indexed variants of the commands to create a destination % and a structure destination. % At first xetex/dvipdfmx. % The structure destination is an array, so we use obj for it % so that we can reference it: % \begin{macrocode} %<*xdvipdfmx|dvipdfmx> \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nn #1#2 { \@@_backend:e { dest ~ ( \exp_not:n {#1} ) [ @thispage \str_case:nnF {#2} { { xyz } { /XYZ ~ @xpos ~ @ypos ~ null } { fit } { /Fit } { fitb } { /FitB } { fitbh } { /FitBH } { fitbv } { /FitBV ~ @xpos } { fith } { /FitH ~ @ypos } { fitv } { /FitV ~ @xpos } { fitr } { /Fit } } { /XYZ ~ @xpos ~ @ypos ~ \fp_eval:n { (#2) / 100 } } ] } % \end{macrocode} % We do not test anymore if the structure object exist. The object of the structure destination % gets the name \texttt{@pdf.Sdest.\meta{destname}}, where \meta{destname} is the % name of the standard destination so that we can reference it in the GoTo links. % \begin{macrocode} \@@_backend:e { obj ~ @pdf.SDest.\exp_not:n{#1} [ \exp_after:wN \pdf_object_ref_indexed:nn \l_pdf_current_structure_destination_tl \str_case:nnF {#2} { { xyz } { /XYZ ~ @xpos ~ @ypos ~ null } { fit } { /Fit } { fitb } { /FitB } { fitbh } { /FitBH } { fitbv } { /FitBV ~ @xpos } { fith } { /FitH ~ @ypos } { fitv } { /FitV ~ @xpos } { fitr } { /Fit } } { /XYZ ~ @xpos ~ @ypos ~ \fp_eval:n { (#2) / 100 } } ] } } % \end{macrocode} % % The second destination command is for the boxed destination. Here we need to define % an new auxiliary command: % \begin{macrocode} \cs_new_protected:Npn \@@_backend_indexed_structure_destination_aux:nnnn #1#2#3#4 { \vbox_to_zero:n { \__kernel_kern:n {#4} \hbox:n { \@@_backend:n { obj ~ @pdf_ #2 _llx ~ @xpos } \@@_backend:n { obj ~ @pdf_ #2 _lly ~ @ypos } } \tex_vss:D } \__kernel_kern:n {#1} \vbox_to_zero:n { \__kernel_kern:n { -#3 } \hbox:n { \@@_backend:n { dest ~ (#2) [ @thispage /FitR ~ @pdf_ #2 _llx ~ @pdf_ #2 _lly ~ @xpos ~ @ypos ] } % \end{macrocode} % Here we add the structure destination to the same box % \begin{macrocode} \@@_backend:e { obj ~ @pdf.SDest.\exp_not:n{#2} [ \exp_after:wN \pdf_object_ref_indexed:nn \l_pdf_current_structure_destination_tl /FitR ~ @pdf_ #2 _llx ~ @pdf_ #2 _lly ~ @xpos ~ @ypos ] } } \tex_vss:D } \__kernel_kern:n { -#1 } } % \end{macrocode} % And now we redefine the destination command: % \begin{macrocode} \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nnnn #1#2#3#4 { \exp_args:Ne \@@_backend_indexed_structure_destination_aux:nnnn { \dim_eval:n {#2} } {#1} {#3} {#4} } % % \end{macrocode} % % Now pdftex. We only redefine for version 1.40 revision 24 or later. % \begin{macrocode} %<*pdftex> \bool_lazy_and:nnT { \int_compare_p:nNn {\tex_pdftexversion:D } > {139} } { \int_compare_p:nNn {\tex_pdftexrevision:D } > {23} } { \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nn #1#2 { \tex_pdfdest:D name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: \tex_pdfdest:D struct~ \exp_after:wN \__kernel_pdf_object_id_indexed:nn \l_pdf_current_structure_destination_tl ~ name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: } \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nnnn #1#2#3#4 { \tex_pdfdest:D name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: \tex_pdfdest:D struct~ \exp_after:wN \__kernel_pdf_object_id_indexed:nn \l_pdf_current_structure_destination_tl ~ name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: } } % % \end{macrocode} % luatex is quite similar to pdftex. Mostly the test for the version is different % \begin{macrocode} %<*luatex> \int_compare:nNnT {\directlua{tex.print(status.list()["development_id"])} } > {7468} { \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nn #1#2 { \tex_pdfextension:D dest name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: \tex_pdfextension:D dest struct~ \exp_after:wN \__kernel_pdf_object_id_indexed:nn \l_pdf_current_structure_destination_tl ~ name {#1} \str_case:nnF {#2} { { xyz } { xyz } { fit } { fit } { fitb } { fitb } { fitbh } { fitbh } { fitbv } { fitbv } { fith } { fith } { fitv } { fitv } { fitr } { fitr } } { xyz ~ zoom \fp_eval:n { #2 * 10 } } \scan_stop: } \cs_set_protected:Npn \@@_backend_indexed_structure_destination:nnnn #1#2#3#4 { \tex_pdfextension:D dest name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: \tex_pdfextension:D dest struct~ \exp_after:wN \__kernel_pdf_object_id_indexed:nn \l_pdf_current_structure_destination_tl~ name {#1} fitr ~ width \dim_eval:n {#2} ~ height \dim_eval:n {#3} ~ depth \dim_eval:n {#4} \scan_stop: } \cs_set_protected:Npn \@@_backend_link_begin_structure_goto:nnw #1#2 { \@@_backend_link_begin:nnnw {#1} { goto~struct~name~{#2}~name } {#2} } } % % \end{macrocode} % \end{macro} % \subsection{Settings for regression tests} % When doing pdf based regression tests some meta data in the pdf should have % fixed values to get identical pdf's. We define here the backend dependent % part. The main command is then in l3pdfmeta % \begin{macrocode} %<*drivers> \cs_new_protected:Npn \@@_backend_set_regression_data: { \sys_gset_rand_seed:n{1000} \pdfmanagement_add:nnn{Info}{Creator}{(TeX)} % %<*dvips> \AddToHook{begindocument}{\pdfmanagement_add:nnn{Info}{Producer}{(pdfTeX+dvips)}} \__kernel_backend_literal:e{!~<>~setpagedevice} \__kernel_backend_literal:e{!~<>~setpagedevice} \str_if_exist:NTF\c_sys_timestamp_str { \pdfmanagement_add:nne{Info}{CreationDate}{(\c_sys_timestamp_str)} \pdfmanagement_add:nne{Info}{ModDate}{(\c_sys_timestamp_str)} } { \pdfmanagement_add:nnn{Info}{CreationDate}{(D:20010101205959-00'00')} \pdfmanagement_add:nnn{Info}{ModDate}{(D:20010101205959-00'00')} } % %<*dvipdfmx> \pdfmanagement_add:nnn{Info}{Producer}{(dvipdfmx)} \__kernel_backend_literal:e {pdf:trailerid [~ <00112233445566778899aabbccddeeff>~ <00112233445566778899aabbccddeeff>~ ]} % %<*xdvipdfmx> \pdfmanagement_add:nnn{Info}{Producer}{(xetex)} \__kernel_backend_literal:e {pdf:trailerid [~ <00112233445566778899aabbccddeeff>~ <00112233445566778899aabbccddeeff>~ ]} % %<*pdftex> \pdfmanagement_add:nnn{Info}{Producer}{(pdfTeX)} \tex_pdfsuppressptexinfo:D 7 \scan_stop: \pdftrailerid{2350CAD05F8A7AF0AA4058486855344F} % %<*luatex> \pdfmanagement_add:nnn{Info}{Producer}{(LuaTeX)} \tex_pdfvariable:D suppressoptionalinfo 7\relax \tex_pdfvariable:D trailerid {[~ <2350CAD05F8A7AF0AA4058486855344F>~ <2350CAD05F8A7AF0AA4058486855344F>~ ]} % %<*drivers> \str_if_exist:NF\c_sys_timestamp_str { \pdfmanagement_add:nnn{Info}{CreationDate}{(D:20010101205959-00'00')} \pdfmanagement_add:nnn{Info}{ModDate}{(D:20010101205959-00'00')} \AddToDocumentProperties[document]{creationdate}{D:20010101205959-00'00'} \AddToDocumentProperties[document]{moddate}{D:20010101205959-00'00'} \AddToDocumentProperties[hyperref]{pdfmetadate}{D:20010101205959-00'00'} \AddToDocumentProperties[hyperref]{pdfdate}{D:20010101205959-00'00'} } \AddToDocumentProperties[hyperref]{pdfinstanceid}{uuid:0a57c455-157a-4141-8c19-6237d832fc80} \AddToDocumentProperties[hyperref]{pdfproducer}{\c_sys_engine_exec_str-NN.NN.NN} } % % \end{macrocode} % % \subsection{Uncompressed metadata object stream} % The xmp metadata should be written \enquote{uncompressed} to pdf. % It is not quite clear what exactly that means. Probably it only % means that there should be no |/Filter| key in the stream, but % packages like \pkg{pdfx} and \pkg{hyperref} try to suppress object % compression too, so we add support for it too. % With luatex this is possible by using the |uncompressed| key word. % With pdftex one can change locally the compresslevel. (x)dvipdfmx does % it automatically and doesn't need some special command. No solution % is known for the dvips route. We need it only once, so we make % it special and probably no public interface is needed. It writes % an unnamed object so should be referenced directly with |\pdf_object_ref_last:| % \begin{macrocode} %<*luatex> \cs_new_protected:Npn \@@_backend_metadata_stream:n #1 { \tex_immediate:D \tex_pdfextension:D obj ~uncompressed~ \@@_backend_object_write:nn {stream} {{/Type~/Metadata~/Subtype~/XML}{#1}} } % %<*pdftex> \cs_new_protected:Npn \@@_backend_metadata_stream:n #1 { \group_begin: \tex_pdfcompresslevel:D 0 \scan_stop: \tex_immediate:D \tex_pdfobj:D \@@_backend_object_write:nn {stream} {{/Type~/Metadata~/Subtype~/XML}{#1}} \group_end: } % %<*xdvipdfmx|dvipdfmx|dvips|dvisvgm> \cs_new_protected:Npn \@@_backend_metadata_stream:n #1 { \pdf_object_unnamed_write:nn {stream}{{/Type~/Metadata~/Subtype~/XML}{#1}} } % % \end{macrocode} % % \subsection{Suppressing deprecated PDF features} % % \texttt{/ProcSet}, \texttt{/CharSet} and the \texttt{/Info} dictionary % are deprecated in PDF 2.0. For the pdf/A-4 standard they must be suppressed. % Not every engine is able to do this, but for pdfTeX and luatex we define suitable % backend command. \texttt{/ProcSet} is suppressed automatically % for pdf version 2.0 starting with in texlive 2023. % \begin{macro}{\@@_backend_omit_charset:n} % The option to omit /Charset exists already for quite some time for the two % engines. % \begin{macrocode} %<*xdvipdfmx|dvipdfmx|dvips|dvisvgm> \cs_new_protected:Npn \@@_backend_omit_charset:n #1 {} %#1 number % %<*pdftex> \cs_new_protected:Npn \@@_backend_omit_charset:n #1 %#1 number { \tex_pdfomitcharset:D = #1 \scan_stop: } % %<*luatex> \cs_new_protected:Npn \@@_backend_omit_charset:n #1 %#1 number { \tex_pdfvariable:D omitcharset = #1 \scan_stop: } % % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_omit_info:n} % The option to suppress the info dictionary will be available in % texlive 2023. % \begin{macrocode} %<*xdvipdfmx|dvipdfmx|dvips|dvisvgm> \cs_new_protected:Npn \@@_backend_omit_info:n #1 {} %#1 number % %<*pdftex> \bool_lazy_and:nnTF { \int_compare_p:nNn {\tex_pdftexversion:D } > {139} } { \int_compare_p:nNn {\tex_pdftexrevision:D } > {24} } { \cs_new_protected:Npn \@@_backend_omit_info:n #1 %#1 number { \pdfomitinfodict = #1 \scan_stop: } } { \cs_new_protected:Npn \@@_backend_omit_info:n #1 {}%#1 number } % %<*luatex> \int_compare:nNnTF {\directlua{tex.print(status.list()["development_id"])} } > {7560} { \cs_new_protected:Npn \@@_backend_omit_info:n #1 %#1 number { \tex_pdfvariable:D omitinfodict = #1 \scan_stop: } } { \cs_new_protected:Npn \@@_backend_omit_info:n #1 {} %#1 number } % % \end{macrocode} % \end{macro} % % With luatex it is for some standards also necessary to suppress the CidSet % entry in the fonts (with xetex there seem to be no problem. % \begin{macro}{\@@_backend_omit_cidset:n} % The option to omit /Charset exists already for quite some time for the two % engines. % \begin{macrocode} %<*xdvipdfmx|dvipdfmx|dvips|dvisvgm|pdftex> \cs_new_protected:Npn \@@_backend_omit_cidset:n #1 {} %#1 number % %<*luatex> \cs_new_protected:Npn \@@_backend_omit_cidset:n #1 %#1 number { \tex_pdfvariable:D omitcidset = #1 \scan_stop: } % % \end{macrocode} % \end{macro} % % \subsection{lua code for lualatex} % \begin{macrocode} %<*lua> ltx= ltx or {} ltx.@@ = ltx.@@ or {} ltx.@@.Page = ltx.@@.Page or {} ltx.@@.Page.dflt = ltx.@@.Page.dflt or {} ltx.@@.Page.Resources = ltx.@@.Resources or {} ltx.@@.Page.Resources.Properties = ltx.@@.Page.Resources.Properties or {} ltx.@@.Page.Resources.List={"ExtGState","ColorSpace","Pattern","Shading"} ltx.@@.object = ltx.@@.object or {} ltx.pdf= ltx.pdf or {} -- for "public" functions local @@ = ltx.@@ local pdf = pdf local function @@_backend_Page_gput (name,value) @@.Page.dflt[name]=value end local function @@_backend_Page_gremove (name) @@.Page.dflt[name]=nil end local function @@_backend_Page_gclear () @@.Page.dflt={} end local function @@_backend_ThisPage_gput (page,name,value) @@.Page[page] = @@.Page[page] or {} @@.Page[page][name]=value end local function @@_backend_ThisPage_gpush (page) local token="" local t = {} local tkeys= {} for name,value in pairs(@@.Page.dflt) do t[name]=value end if @@.Page[page] then for name,value in pairs(@@.Page[page]) do t[name] = value end end -- sort the table to get reliable test files. for name,value in pairs(t) do table.insert(tkeys,name) end table.sort(tkeys) for _,name in ipairs(tkeys) do token = token .. "/"..name.." "..t[name] end return token end function ltx.@@.backend_ThisPage_gput (page,name,value) -- tex.count["g_shipout_readonly_int"] @@_backend_ThisPage_gput (page,name,value) end function ltx.@@.backend_ThisPage_gpush (page) pdf.setpageattributes(@@_backend_ThisPage_gpush (page)) end function ltx.@@.backend_Page_gput (name,value) @@_backend_Page_gput (name,value) end function ltx.@@.backend_Page_gremove (name) @@_backend_Page_gremove (name) end function ltx.@@.backend_Page_gclear () @@_backend_Page_gclear () end local Properties = ltx.@@.Page.Resources.Properties local ResourceList= ltx.@@.Page.Resources.List local function @@_backend_PageResources_gpush (page) local token="" if Properties[page] then -- we sort the table, so that the pdf test works local t = {} for name,value in pairs (Properties[page]) do table.insert (t,name) end table.sort (t) for _,name in ipairs(t) do token = token .. "/"..name.." ".. Properties[page][name] end token = "/Properties <<"..token..">>" end for i,name in ipairs(ResourceList) do if ltx.@@.Page.Resources[name] then token = token .. "/"..name.." "..ltx.pdf.object_ref("@@/Page/Resources/"..name) end end return token end -- the function is public, as I probably need it in tagpdf too ... function ltx.pdf.Page_Resources_Properties_gput (page,name,value) -- tex.count["g_shipout_readonly_int"] Properties[page] = Properties[page] or {} Properties[page][name]=value pdf.setpageresources(@@_backend_PageResources_gpush (page)) end function ltx.pdf.Page_Resources_gpush(page) pdf.setpageresources(@@_backend_PageResources_gpush (page)) end function ltx.pdf.object_ref (objname) if ltx.@@.object[objname] then local ref= ltx.@@.object[objname] return ref else return "false" end end % % \end{macrocode} % \end{implementation} % % \PrintIndex