% \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.96n, released 2024-10-27}
%
% \maketitle
%
%
% \begin{implementation}
%
% \section{\pkg{l3backend-testphase} Implementation}
% \begin{macrocode}
%\ProvidesExplFile
%<*dvipdfmx>
{l3backend-testphase-dvipdfmx.def}{2024-10-27}{}
{LaTeX~PDF~management~testphase~bundle~backend~support: dvipdfmx}
%
%<*dvips>
{l3backend-testphase-dvips.def}{2024-10-27}{}
{LaTeX~PDF~management~testphase~bundle~backend~support: dvips}
%
%<*dvisvgm>
{l3backend-testphase-dvisvgm.def}{2024-10-27}{}
{LaTeX~PDF~management~testphase~bundle~backend~support: dvisvgm}
%
%<*luatex>
{l3backend-testphase-luatex.def}{2024-10-27}{}
{LaTeX~PDF~management~testphase~bundle~backend~support: PDF output (LuaTeX)}
%
%<*pdftex>
{l3backend-testphase-pdftex.def}{2024-10-27}{}
{LaTeX~PDF~management~testphase~bundle~backend~support: PDF output (pdfTeX)}
%
%<*xdvipdfmx>
{l3backend-testphase-xetex.def}{2024-10-27}{}
{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}<#2~#3>>
}
}
\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~
<##1~\pdf_object_ref:n {@@/Page/Resources/##1}>>
}
}
}
}
%
% 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~<#1~\pdf_object_ref_last: >>}
}
%
%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