% \iffalse meta-comment % % File: newpax.dtx % Copyright 2006-2008, 2011, 2012 Heiko Oberdiek (original pax.sty) % Copyright (C) 2021--2023 Ulrike Fischer % % 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 "newpax 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/u-fischer/newpax % % for those people who are interested. % % \fi % % \begin{macrocode} %<*package> %<@@=newpax> \NeedsTeXFormat{LaTeX2e}[2022-11-01] \ProvidesPackage{newpax}% [2023-11-08 v0.55 Annotation support for PDF graphics based on pax.sty adapted by (UF)]% % \end{macrocode} % Test if the pdfmanagement is loaded: % \begin{macrocode} \ExplSyntaxOn \IfDocumentMetadataTF {} { %error for now, perhaps warning later. \PackageError{newpax} { PDF~resource~management~code~not~found!\MessageBreak newpax~will~no~work. } { Load~it~with \MessageBreak \string\DocumentMetadata{}\MessageBreak before~loading~the~class } } \ExplSyntaxOff \RequirePackage{graphicx} \RequirePackage{etoolbox} \ExplSyntaxOn % \end{macrocode} % \subsection{Variables} % Variables inherited from the pax code use \cs{NEWPAX} % as prefix. % \begin{variable}{\l_@@_tmpa_box} % \begin{macrocode} \box_new:N \l_@@_tmpa_box % \end{macrocode} % \end{variable} % \begin{variable}{ \l_@@_use_attributes_bool} % Used by the \texttt{usefileattributes} key. % \begin{macrocode} \bool_new:N \l_@@_use_attributes_bool % \end{macrocode} % \end{variable} % % \begin{variable}{ \l_@@_addannots_bool} % Used by the \texttt{addannots} key. % \begin{macrocode} \bool_new:N \l_@@_addannots_bool % \end{macrocode} % \end{variable} % % \begin{variable}{ \l_@@_dests_all_bool} % Used by the \texttt{dests} key. % if true newpax will insert more destinations. % \begin{macrocode} \bool_new:N \l_@@_dests_all_bool % \end{macrocode} % \end{variable} % \begin{variable}{ \l_@@_destsuffic_tl} % Used by the \texttt{addannots} key. % \begin{macrocode} \tl_new:N \l_@@_destsuffix_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\NEWPAX@fileextension} % the extension of the generated file, the default it newpax. % \begin{macrocode} \tl_new:N \NEWPAX@fileextension % \end{macrocode} % \end{variable} % % \begin{variable}{\NEWPAX@Gin@box@opts} % this hold the options of a annotation % \begin{macrocode} \tl_new:N \NEWPAX@Gin@box@opts % \end{macrocode} % \end{variable} % % \begin{macro}{\ifNEWPAX@Gin@clip} % \begin{macrocode} \newif\ifNEWPAX@Gin@clip % \end{macrocode} % \end{macro} % % \subsection{Variants} % % \begin{macro}{\newpax@str@if@eq@@@@nnT} % a latex2e variant of \cs{str_if_eq:nnT} to replace % \cs{pdf@strcmp} % \begin{macrocode} \cs_set_eq:NN \newpax@str@if@eq@@@@nnT\str_if_eq:nnT % \end{macrocode} % \end{macro} % % \begin{macrocode} \cs_generate_variant:Nn \pdfannot_dict_put:nnn {nnx,xnx,xnn} \cs_generate_variant:Nn \pdfannot_link_goto_begin:nw {xw} \cs_generate_variant:Nn \pdf_destination:nn {xx} % \end{macrocode} % % \subsection{User setup command} % \begin{macrocode} \NewDocumentCommand\newpaxsetup { m } { \keys_set:nn {newpax}{ #1} } \keys_define:nn {newpax} { usefileattributes .bool_set:N = \l_@@_use_attributes_bool, destsuffix .code:n = {\tl_set:Nn \l_@@_destsuffix_tl{@#1}}, addannots .bool_set:N = \l_@@_addannots_bool, addannots .default:n = true, addannots .initial:n = true, paxextension .choices:nn = {pax,newpax} {\tl_set:Nn \NEWPAX@fileextension {#1}}, paxextension .initial:n = newpax, dests .choice:, dests/all .code:n = {\bool_set_true:N \l_@@_dests_all_bool}, dests/used .code:n = {\bool_set_false:N \l_@@_dests_all_bool}, dests .initial:n = used } % \end{macrocode} % \subsection{Helper commands to create the annotations} % % \begin{macro}{\@NEWPAX@setattributes@n} % This adds or removes attributes from the annot dictionaries. % The argument is a link type like URI or Goto. % \begin{macrocode} \cs_new_protected:Npn \@NEWPAX@setattributes@n #1 %link type { \bool_if:NT \l_@@_use_attributes_bool { \tl_if_empty:NTF \NEWPAX@key@C { \pdfannot_dict_remove:nn {link/#1} { C } } { \pdfannot_dict_put:nnx {link/#1} { C } { \NEWPAX@key@C } } \tl_if_empty:NTF \NEWPAX@key@Border { \pdfannot_dict_remove:nn {link/#1} { Border } } { \pdfannot_dict_put:nnx {link/#1} { Border } { \NEWPAX@key@Border } } \tl_if_empty:NTF \NEWPAX@key@BS { \pdfannot_dict_remove:nn {link/#1} { BS } } { \pdfannot_dict_put:nnx {link/#1} { BS } { \NEWPAX@key@BS } } \tl_if_empty:NTF \NEWPAX@key@H { \pdfannot_dict_remove:nn {link/#1} { H } } { \pdfannot_dict_put:nnx {link/#1} { H } { \NEWPAX@key@H } } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@NEWPAX@linkgoto@xnn} % this creates a goto link to the destination given in the first argument, % and the size given in the second and third argument. % \begin{macrocode} \cs_new_protected:Npn \@NEWPAX@linkgoto@xnn #1 #2 #3 %#1 dest #2 width #3 height { \group_begin: \@NEWPAX@setattributes@n {GoTo} \leavevmode \pdfannot_link_goto_begin:xw { #1 } \@NEWPAX@ensurelinkbox@n{\hbox_to_wd:nn {#2}{ { \rule{0pt}{#3} }\hfill}} \pdfannot_link_goto_end: \group_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@NEWPAX@link@setaction@nn} % This puts the action into the dict % if the annot is a link. \#1 is a type like % URI or Goto, \#2 the actual action. % \begin{macrocode} \cs_new_protected:Npn \@NEWPAX@link@setaction@nn #1 #2 { \pdfannot_dict_put:nnn{link/#1}{Subtype}{/Link} \pdfannot_dict_put:nnx{link/#1}{A} {<>} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@NEWPAX@annotboxlink@nnn} % This creates an annotation link box. % \begin{macrocode} \cs_new_protected:Npn \@NEWPAX@annotboxlink@nnn #1 #2 #3 %#1 type, #2 width #3 height { \group_begin: \@NEWPAX@setattributes@n {#1} \leavevmode \pdfannot_box:nnnx {#2}{#3}{0pt} {\pdfannot_dict_use:n{link/#1}} \group_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@NEWPAX@destination@xx} % This creates a destination. The first argument is the % name, the second the placement type (e.g. fit or xyz). % \begin{macrocode} \cs_new_protected:Npn \@NEWPAX@destination@xx #1 #2 { \pdf_destination:xx {#1}{#2} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@NEWPAX@ensurelinkbox@n} % XeTeX needs in some places a specific box to ensure the size. % TODO: check the mailing list for the alternative. % \begin{macrocode} \cs_new_eq:NN \@NEWPAX@ensurelinkbox@n \use:n \AddToHook{package/hyperref/after} { \sys_if_engine_xetex:T { \cs_set_eq:NN \@NEWPAX@ensurelinkbox@n \XeTeXLinkBox } } % \end{macrocode} % \end{macro} % % \subsection{Handling graphics commands} % We must redefine \cs{includegraphics} to inject the code which % reinserts the annotation. This can't be done with hooks as the % standard code splits everything too much. % % \begin{macro}{\NEWPAX@ORG@includegraphics} % At first we redefine \cs{includegraphics} to use our new command. % \begin{macrocode} \NewCommandCopy\NEWPAX@ORG@includegraphics\includegraphics \RenewDocumentCommand\includegraphics{sO{}m} { \IfBooleanTF {#1} { \NEWPAX@includegraphics[clip,#2]{#3} } { \NEWPAX@includegraphics[#2]{#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\NEWPAX@includegraphics} % \begin{macrocode} \def\NEWPAX@includegraphics[#1]#2{% \group_begin: \hbox_set:Nn \l_@@_tmpa_box {\NEWPAX@ORG@includegraphics[{#1}]{#2}}% \tl_set:Nx \NEWPAX@inc@width { \dim_eval:n { \box_wd:N \l_@@_tmpa_box } }% \tl_set:Nx \NEWPAX@inc@height { \dim_eval:n { \box_dp:N \l_@@_tmpa_box + \box_ht:N \l_@@_tmpa_box } }% \mode_leave_vertical: \hbox_to_wd:nn { \box_wd:N \l_@@_tmpa_box } { \hbox_overlap_right:n { \box_use:N \l_@@_tmpa_box } \bool_if:nT { \l_@@_addannots_bool && ( \int_compare_p:nNn { \cs_if_exist:NTF\pdfpages@includegraphics@status { \pdfpages@includegraphics@status }{0} } < {2} ) } { \box_move_down:nn {\box_dp:N \l_@@_tmpa_box } { \hbox:n { \NEWPAX@AddAnnots{#1}{#2}% } } } \hfill } \group_end: } % \end{macrocode} % \end{macro} % % \subsection{Handling the newpax file} % The following keys were previously defined % with kvoptions, the names of the internal commands are kept for now. % The keys are used in the optional argument of \cs{includegraphics} % and extend their functions. % \begin{macrocode} \keys_define:nn {newpax/Gin} { ,page .tl_set:N = \NEWPAX@Gin@page ,page .initial:n = 1 ,angle .tl_set:N = \NEWPAX@Gin@angle ,angle .initial:n = 0 ,clip .legacy_if_set:n = NEWPAX@Gin@clip ,viewport .code:n = { \tl_put_right:Nn\NEWPAX@Gin@box@opts {\NEWPAX@viewport#1\\} } ,trim .code:n = { \tl_put_right:Nn\NEWPAX@Gin@box@opts {\NEWPAX@trim#1\\} } ,unknown .code:n = {} } \ExplSyntaxOff % \end{macrocode} % % Calculate coordinates % \begin{macro}{\NEWPAX@viewport,\NEWPAX@trim,\NEWPAX@defaultbp,\NEWPAX@def@bp} % \begin{macrocode} \def\NEWPAX@viewport#1 #2 #3 #4\\{% \NEWPAX@defaultbp\NEWPAX@vllx{#1}% \NEWPAX@defaultbp\NEWPAX@vlly{#2}% \NEWPAX@defaultbp\NEWPAX@vurx{#3}% \NEWPAX@defaultbp\NEWPAX@vury{#4}% \edef\NEWPAX@page@llx{\dimexpr\NEWPAX@page@llx+\NEWPAX@vllx\relax}% \edef\NEWPAX@page@lly{\dimexpr\NEWPAX@page@lly+\NEWPAX@vlly\relax}% \edef\NEWPAX@page@urx{\dimexpr\NEWPAX@page@llx+\NEWPAX@vlly\relax}% \edef\NEWPAX@page@ury{\dimexpr\NEWPAX@page@lly+\NEWPAX@vury\relax}% } \def\NEWPAX@trim#1 #2 #3 #4\\{% \NEWPAX@defaultbp\NEWPAX@tllx{#1}% \NEWPAX@defaultbp\NEWPAX@tlly{#2}% \NEWPAX@defaultbp\NEWPAX@turx{#3}% \NEWPAX@defaultbp\NEWPAX@tury{#4}% \edef\NEWPAX@page@llx{\dimexpr\NEWPAX@page@llx+\NEWPAX@tllx\relax}% \edef\NEWPAX@page@lly{\dimexpr\NEWPAX@page@lly+\NEWPAX@tlly\relax}% \edef\NEWPAX@page@urx{\dimexpr\NEWPAX@page@urx-\NEWPAX@turx\relax}% \edef\NEWPAX@page@ury{\dimexpr\NEWPAX@page@ury-\NEWPAX@tury\relax}% } \def\NEWPAX@defaultbp#1#2{% \afterassignment\NEWPAX@def@bp\dimen@#2bp\relax{#1}{#2}% } \def\NEWPAX@def@bp#1\relax#2#3{% \if!#1!% \edef#2{#3bp}% \else \edef#2{#3}% \fi } % \end{macrocode} % \end{macro} % % \begin{macro}{\NEWPAX@AddAnnots} % This command is the main command that reads and parses the newpax file. % \begin{macrocode} \def\NEWPAX@AddAnnots#1#2{% \SetKeys[newpax/Gin]{#1}% \Grot@setangle{\NEWPAX@Gin@angle}% % a little careful, is type of angle int or real? \loop \ifdim\NEWPAX@Gin@angle\p@<360\p@ \else \edef\NEWPAX@Gin@angle{\the\numexpr-360+\number\NEWPAX@Gin@angle}% \repeat \loop \ifdim\NEWPAX@Gin@angle\p@<\z@ \edef\NEWPAX@Gin@angle{\strip@pt\dimexpr\NEWPAX@Gin@angle\p@+360\p@}% \repeat \ifcase0\ifnum\NEWPAX@Gin@angle=0 1\fi \ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=180 1\fi \ifnum\NEWPAX@Gin@angle=270 1\fi \PackageWarning{newpax}{Unsupported value for option angle}% \fi \filename@parse{#2}% \def\NEWPAX@file{%\filename@area \filename@base.\NEWPAX@fileextension }% \let\[\NEWPAX@parser \def\<{<}% \def\>{>}% \endlinechar=-1 % \begingroup \catcode`\#=12 \catcode`\%=12 \InputIfFileExists\NEWPAX@file{}{\typeout{* Missing: \NEWPAX@file}}\endgroup } % \end{macrocode} % \end{macro} % The parsing commands % With \cs{NEWPAX@parser} normally \cs{NEWPAX@cmd@XXX} is called, where % XXX is file, pagenum, annot, page or dest. % \begin{macrocode} \def\NEWPAX@parser#1{\NEWPAX@call{cmd}{#1}{}}% % \end{macrocode} % \begin{macrocode} \def\NEWPAX@call#1#2#3{% \@ifundefined{NEWPAX@#1@#2}\NEWPAX@skip{#3\csname NEWPAX@#1@#2\endcsname}% } \def\NEWPAX@skip#1\\{} \def\NEWPAX@stop#1\\{} % \end{macrocode} % The command to handle the page entry. % \begin{macrocode} \def\NEWPAX@cmd@page#1#2{% \NEWPAX@filter@page{#1}{% \NEWPAX@getrect{page}#2\@nil \NEWPAX@Gin@box@opts \ifcase0\ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=270 1\fi \else \let\NEWPAX@temp\NEWPAX@inc@width \let\NEWPAX@inc@width\NEWPAX@inc@height \let\NEWPAX@inc@height\NEWPAX@temp \fi \Gscale@div\NEWPAX@scale@x\NEWPAX@inc@width{% \dimexpr\NEWPAX@page@urx-\NEWPAX@page@llx\relax }% \Gscale@div\NEWPAX@scale@y\NEWPAX@inc@height{% \dimexpr\NEWPAX@page@ury-\NEWPAX@page@lly\relax }% \NEWPAX@skip }% } \def\NEWPAX@filter@page#1{% \ifnum\NEWPAX@Gin@page=#1 % \expandafter\@firstofone \else \ifnum\NEWPAX@Gin@page<#1 % \csname fi\endcsname \csname fi\endcsname \expandafter\NEWPAX@stop\@gobblefour \fi \expandafter\NEWPAX@skip \fi } \def\NEWPAX@getrect#1#2 #3 #4 #5\@nil{% \@namedef{NEWPAX@#1@llx}{#2bp}% \@namedef{NEWPAX@#1@lly}{#3bp}% \@namedef{NEWPAX@#1@urx}{#4bp}% \@namedef{NEWPAX@#1@ury}{#5bp}% } \def\NEWPAX@cmd@annot#1#2{% \NEWPAX@filter@page{#1}{% \NEWPAX@call{annot}{#2}{}% }% } \def\NEWPAX@annot@Link#1#2#3{% \def\NEWPAX@link@type{#2}% \NEWPAX@call{link}{#2}{% \begingroup \NEWPAX@getrect{annot}#1\@nil \SetKeys[newpax/key]{#3}% }% \NEWPAX@skip } % \end{macrocode} % Now the main command which inserts annotations. % \begin{macro}{\NEWPAX@pdf@annot} % \begin{macrocode} \newif\ifNEWPAX@ok \NEWPAX@oktrue \newif\ifNEWPAX@GoTo \def\NEWPAX@pdf@annot#1{% \ifNEWPAX@Gin@clip \ifdim\NEWPAX@annot@llx<\NEWPAX@page@llx \let\NEWPAX@annot@llx\NEWPAX@page@llx \fi \ifdim\NEWPAX@annot@lly<\NEWPAX@page@lly \let\NEWPAX@annot@lly\NEWPAX@page@lly \fi \ifdim\NEWPAX@annot@urx>\NEWPAX@page@urx \let\NEWPAX@annot@urx\NEWPAX@page@urx \fi \ifdim\NEWPAX@annot@ury>\NEWPAX@page@ury \let\NEWPAX@annot@ury\NEWPAX@page@ury \fi \NEWPAX@okfalse \ifdim\NEWPAX@annot@llx<\NEWPAX@annot@urx\relax \ifdim\NEWPAX@annot@lly<\NEWPAX@annot@ury\relax \NEWPAX@oktrue \fi \fi \else \NEWPAX@oktrue \fi \ifNEWPAX@ok \ifcase 0\ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=180 2\fi \ifnum\NEWPAX@Gin@angle=270 3\fi\space % angle = 0 \def\NEWPAX@raise{% \NEWPAX@scale@y\dimexpr\NEWPAX@annot@lly-\NEWPAX@page@lly\relax }% \def\NEWPAX@right{% \NEWPAX@scale@x\dimexpr\NEWPAX@annot@llx-\NEWPAX@page@llx\relax }% \or % angle = 90 \def\NEWPAX@raise{% \NEWPAX@scale@x\dimexpr\NEWPAX@annot@llx-\NEWPAX@page@llx\relax }% \def\NEWPAX@right{% \NEWPAX@scale@y\dimexpr\NEWPAX@page@ury-\NEWPAX@annot@ury\relax }% \or % angle = 180 \def\NEWPAX@raise{% \NEWPAX@scale@y\dimexpr\NEWPAX@page@ury-\NEWPAX@annot@ury\relax }% \def\NEWPAX@right{% \NEWPAX@scale@x\dimexpr\NEWPAX@page@urx-\NEWPAX@annot@urx\relax }% \or % angle = 270 \def\NEWPAX@raise{% \NEWPAX@scale@x\dimexpr\NEWPAX@page@urx-\NEWPAX@annot@urx\relax }% \def\NEWPAX@right{% \NEWPAX@scale@y\dimexpr\NEWPAX@annot@lly-\NEWPAX@page@lly\relax }% \fi \@namedef{% NEWPAX@% \ifcase0\ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=270 1\fi\space width% \else height% \fi }{% \NEWPAX@scale@x\dimexpr\NEWPAX@annot@urx-\NEWPAX@annot@llx\relax }% \@namedef{% NEWPAX@% \ifcase0\ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=270 1\fi\space height% \else width% \fi }{% \NEWPAX@scale@y\dimexpr\NEWPAX@annot@ury-\NEWPAX@annot@lly\relax }% \raise\NEWPAX@raise\hb@xt@\z@{% \kern\NEWPAX@right \ifNEWPAX@GoTo %additional box for lualatex ... \hbox{% \@NEWPAX@linkgoto@xnn {\NEWPAX@file @\NEWPAX@DestName}% {\NEWPAX@width}% {\NEWPAX@height}% }% \else \hbox{% \expandafter\@NEWPAX@link@setaction@nn\expandafter{\NEWPAX@link@type}{#1}% \expandafter \@NEWPAX@annotboxlink@nnn \expandafter {\NEWPAX@link@type}{\NEWPAX@width}{\NEWPAX@height}{}}% \fi \hss }% \fi \endgroup } % \end{macrocode} % \end{macro} % % What do they do?? % \begin{macrocode} \def\NEWPAX@htype@GoToR{file} \def\NEWPAX@htype@GoTo{link} \def\NEWPAX@htype@Named{link} \def\NEWPAX@htype@URI{url} % \end{macrocode} % % \begin{macrocode} \ExplSyntaxOn \def\NEWPAX@link@URI{% \NEWPAX@pdf@annot{% /URI\tl_to_str:V\NEWPAX@key@URI }% } \def\NEWPAX@link@Named{% \NEWPAX@pdf@annot{% /N \pdf_name_from_unicode_e:n{\NEWPAX@key@Name} %the value is from a pdf so we can assume it is correctly escaped?? }% } \ExplSyntaxOff \def\NEWPAX@link@GoToR{% \NEWPAX@pdf@annot{% /F\NEWPAX@key@File /D% \ifx\NEWPAX@key@DestName\@empty [\NEWPAX@key@DestPage\space\NEWPAX@key@DestView]% \else \NEWPAX@key@DestName \fi }% } % \end{macrocode} % % Goto links are the most challenging: here it is not enough to create an % link annotation, one also have to ensure that the destination they point to exist % and are correctly created. The destinations can be on previous (or later) % pages that means that one has to use the aux to record what exists and what % is needed. % % At first ensure that the commands are defined to avoid errors if newpax is removed % \begin{macrocode} \AddToHook{begindocument} { \immediate\write\@mainaux{\string\providecommand{\string\NEWPAX@DestReq}[2]{}} \immediate\write\@mainaux{\string\providecommand{\string\NEWPAX@DestProv}[2]{}} } \AddToHook {include/before} { \immediate\write\@partaux{\string\providecommand{\string\NEWPAX@DestReq}[2]{}} \immediate\write\@partaux{\string\providecommand{\string\NEWPAX@DestProv}[2]{}} } % \end{macrocode} % % After the aux file has been read the commands should do nothing: % \begin{macrocode} \AtBeginDocument{% \let\NEWPAX@DestReq\@gobbletwo \let\NEWPAX@DestProv\@gobbletwo } % \end{macrocode} % \begin{macro}{\NEWPAX@DestReq} % This command records the destinations that are required and should be reinserted. % \begin{macrocode} \def\NEWPAX@DestReq#1#2{% \expandafter\gdef\csname NEWPAX@REQ@#1@#2\endcsname{}% } % \end{macrocode} % \end{macro} % \begin{macro}{\NEWPAX@DestProv} % This records the destinations that are provided. % \begin{macrocode} \def\NEWPAX@DestProv#1#2{% \expandafter\gdef\csname NEWPAX@PROV@#1@#2\endcsname{}% } % \end{macrocode} % \end{macro} % \begin{macro}[EXP]{\NEWPAX@DestName} % This command expands either to the number or the name if it exists. % \begin{macrocode} \ExplSyntaxOn \cs_new:Npn \NEWPAX@DestName { \tl_if_empty:eTF { \NEWPAX@key@DestName } { \NEWPAX@key@DestLabel} { \NEWPAX@key@DestName } \l_@@_destsuffix_tl } % \cs_set:Npn \NEWPAX@DestName { \NEWPAX@key@DestLabel } % \end{macrocode} % \end{macro} % \begin{macrocode} \def\NEWPAX@link@GoTo{% \ifnum0<0\NEWPAX@key@DestLabel\relax \expandafter\@firstofone \else \endgroup \expandafter\@gobble \fi {% \if@filesw \protected@write\@auxout{}{% \string\NEWPAX@DestReq{\NEWPAX@file}{\NEWPAX@DestName}% }% \fi % Generate link, if destination exists \@ifundefined{NEWPAX@PROV@\NEWPAX@file @\NEWPAX@DestName}{% \endgroup }{% \NEWPAX@GoTotrue \NEWPAX@pdf@annot{}% }% }% } % \end{macrocode} % This is the command which reads the destination info. % \begin{macrocode} \def\NEWPAX@cmd@dest#1#2#3#4{% \def\NEWPAX@key@DestLabel{#2}% \def\NEWPAX@key@DestName{}% \keys_set_groups:nnn {newpax/key} {destinit} {#4} \NEWPAX@filter@page{#1}{% \if@filesw \protected@write\@auxout{}{% \string\NEWPAX@DestProv{\NEWPAX@file}{\NEWPAX@DestName}% }% \bool_if:NT\l_@@_dests_all_bool { \protected@write\@auxout{} { \string\NEWPAX@DestReq{\NEWPAX@file}{\NEWPAX@DestName}% } } \fi \@ifundefined{NEWPAX@REQ@\NEWPAX@file @\NEWPAX@DestName}{% }{% \begingroup \let\NEWPAX@key@DestY\NEWPAX@page@ury \let\NEWPAX@key@DestX\NEWPAX@page@llx \keys_set_filter:nnn {newpax/key} {destinit} {#4} \let\NEWPAX@dest@llx\NEWPAX@key@DestX \let\NEWPAX@dest@urx\NEWPAX@key@DestX \let\NEWPAX@dest@lly\NEWPAX@key@DestY \let\NEWPAX@dest@ury\NEWPAX@key@DestY \ifx\NEWPAX@key@DestRect\@empty \else \def\NEWPAX@temp{dest}% \expandafter\NEWPAX@getrect\expandafter\NEWPAX@temp \NEWPAX@key@DestRect\@nil \fi \ifNEWPAX@Gin@clip \ifx\NEWPAX@dest@llx<\NEWPAX@page@llx \let\NEWPAX@dest@llx\NEWPAX@page@llx \fi \ifx\NEWPAX@dest@lly<\NEWPAX@page@lly \let\NEWPAX@dest@lly\NEWPAX@page@lly \fi \ifx\NEWPAX@dest@urx>\NEWPAX@page@urx \let\NEWPAX@dest@urx\NEWPAX@page@urx \fi \ifx\NEWPAX@dest@ury>\NEWPAX@page@ury \let\NEWPAX@dest@ury\NEWPAX@page@ury \fi % at least prevent destinations outside the window \ifx\NEWPAX@dest@llx>\NEWPAX@page@urx \NEWPAX@dest@llx\NEWPAX@page@urx \fi \ifx\NEWPAX@dest@lly>\NEWPAX@page@ury \NEWPAX@dest@lly\NEWPAX@page@ury \fi \ifx\NEWPAX@dest@urx<\NEWPAX@page@llx \NEWPAX@dest@urx\NEWPAX@page@llx \fi \ifx\NEWPAX@dest@ury<\NEWPAX@page@lly \NEWPAX@dest@ury\NEWPAX@page@lly \fi \fi % I don't know, what is the best idea for rotated stuff, % perhaps using the corner llx/ury \ifcase 0\ifnum\NEWPAX@Gin@angle=90 1\fi \ifnum\NEWPAX@Gin@angle=180 2\fi \ifnum\NEWPAX@Gin@angle=270 3\fi\space % angle = 0 \def\NEWPAX@raise{% \NEWPAX@scale@y\dimexpr\NEWPAX@dest@lly-\NEWPAX@page@lly\relax }% \def\NEWPAX@right{% \NEWPAX@scale@x\dimexpr\NEWPAX@dest@llx-\NEWPAX@page@llx\relax }% \or % angle = 90 \def\NEWPAX@raise{% \NEWPAX@scale@x\dimexpr\NEWPAX@dest@llx-\NEWPAX@page@llx\relax }% \def\NEWPAX@right{% \NEWPAX@scale@y\dimexpr\NEWPAX@page@ury-\NEWPAX@dest@ury\relax }% \or % angle = 180 \def\NEWPAX@raise{% \NEWPAX@scale@y\dimexpr\NEWPAX@page@ury-\NEWPAX@dest@ury\relax }% \def\NEWPAX@right{% \NEWPAX@scale@x\dimexpr\NEWPAX@page@urx-\NEWPAX@dest@urx\relax }% \or % angle = 270 \def\NEWPAX@raise{% \NEWPAX@scale@x\dimexpr\NEWPAX@page@urx-\NEWPAX@dest@urx\relax }% \def\NEWPAX@right{% \NEWPAX@scale@y\dimexpr\NEWPAX@dest@lly-\NEWPAX@page@lly\relax }% \fi \edef\NEWPAX@name {\NEWPAX@file @\NEWPAX@DestName}% \let\NEWPAX@type\@empty \newpax@str@if@eq@@@@nnT{#3}{FITR}{\def\NEWPAX@type{xyz}}%too lazy for now for better fitr \newpax@str@if@eq@@@@nnT{#3}{XYZ} {% \def\NEWPAX@type{xyz}% \ifx\NEWPAX@key@DestZoom\@empty \else \edef\NEWPAX@type{\fpeval{\NEWPAX@key@DestZoom *100}}% \fi } \newpax@str@if@eq@@@@nnT{#3}{FIT}{\def\NEWPAX@type{fit}} \newpax@str@if@eq@@@@nnT{#3}{FITB}{\def\NEWPAX@type{fitb}} \newpax@str@if@eq@@@@nnT{#3}{FITH}{\def\NEWPAX@type{fith}} \newpax@str@if@eq@@@@nnT{#3}{FITBH}{\def\NEWPAX@type{fitbh}} \newpax@str@if@eq@@@@nnT{#3}{FITV}{\def\NEWPAX@type{fitv}} \newpax@str@if@eq@@@@nnT{#3}{FITBV}{\def\NEWPAX@type{fitbv}} \ifx\NEWPAX@type\@empty \def\NEWPAX@type{xyz}% \fi \raise\NEWPAX@raise\hb@xt@\z@{% \kern\NEWPAX@right \hbox{\@NEWPAX@destination@xx {\NEWPAX@name}{\NEWPAX@type}}% \hss }% \endgroup % \end{macrocode} % we undefine the REQ-command to avoid that the dest is set again. % \begin{macrocode} \cs_undefine:c{NEWPAX@REQ@\NEWPAX@file @\NEWPAX@DestName} }% \NEWPAX@skip }% } % \end{macrocode} % These are keys used in the newpax file. The command names are inherited from kvoptions. % \begin{macrocode} \keys_define:nn {newpax/key} { ,URI .tl_set:N = \NEWPAX@key@URI ,Name .tl_set:N = \NEWPAX@key@Name ,DestName .tl_set:N = \NEWPAX@key@DestName ,DestName .groups:n = {destinit} ,DestPage .tl_set:N = \NEWPAX@key@DestPage ,DestView .tl_set:N = \NEWPAX@key@DestView ,File .tl_set:N = \NEWPAX@key@File ,C .tl_set:N = \NEWPAX@key@C ,Border .tl_set:N = \NEWPAX@key@Border ,BS .tl_set:N = \NEWPAX@key@BS ,H .tl_set:N = \NEWPAX@key@H ,DestLabel .tl_set:N = \NEWPAX@key@DestLabel ,DestLabel .groups:n = {destinit} ,DestRect .tl_set:N = \NEWPAX@key@DestRect ,DestZoom .tl_set:N = \NEWPAX@key@DestZoom ,DestX .code:n = { \NEWPAX@defaultbp\NEWPAX@key@DestX{#1} } ,DestY .code:n = { \NEWPAX@defaultbp\NEWPAX@key@DestY{#1} } } \ExplSyntaxOff % % \end{macrocode} % \begin{macrocode} %<*lua> local ProvidesLuaModule = { name = "newpax", version = "0.55", --TAGVERSION date = "2023-11-08", --TAGDATE description = "newpax lua code", license = "The LATEX Project Public License 1.3c" } if luatexbase and luatexbase.provides_module then luatexbase.provides_module (ProvidesLuaModule) end local OPEN = pdfe.open local GETSIZE = pdfe.getsize local GETINFO = pdfe.getinfo local GETPAGE = pdfe.getpage local GETNAME = pdfe.getname local GETARRAY = pdfe.getarray local GETDICTIONARY = pdfe.getdictionary local GETFROMDICTIONARY = pdfe.getfromdictionary local GETFROMARRAY = pdfe.getfromarray local PAGESTOTABLE = pdfe.pagestotable local DICTIONARYTOTABLE = pdfe.dictionarytotable local ARRAYTOTABLE = pdfe.arraytotable local TYPE = pdfe.type local GETFROMREFERENCE = pdfe.getfromreference local FILENAMEONLY = file.nameonly local strENTRY_BEG = "\\[" local strENTRY_END = "\\\\\n" local strCMD_BEG = "{" local strCMD_END = "}" local strARG_BEG = "{" local strARG_END = "}" local strKVS_BEG = "{" local strKVS_END = "\n}" local strKVS_EMPTY = "{}" local strKV_BEG = "\n " local strKV_END = "," local strKEY_BEG = "" local strKEY_END = "" local strVALUE_BEG = "={" local strVALUE_END = "}" local strHEX_STR_BEG = "\\<" local strHEX_STR_END = "\\>" local strLIT_STR_BEG = "(" local strLIT_STR_END = ")" local strDICT_BEG= "<<" local strDICT_END= ">>" local strNAME= "/" local strARRAY_BEG= "[" local strARRAY_END= "]" local strARRAY_SEP = " " local strRECT_SEP = " " local constCMD_ANNOT = "annot" local constCMD_BASEURL = "baseurl" -- unused local constCMD_DEST = "dest" local constCMD_FILE = "file" local constCMD_INFO = "info" -- unused local constCMD_PAGENUM = "pagenum" local constCMD_PAGE = "page" local constCMD_PAX = "pax" -- cmd file local constKEY_SIZE = "Size" local constKEY_DATE = "Date" -- cmd annot attributes local constKEY_C = "C" local constKEY_BORDER = "Border" local constKEY_BS = "BS" local constKEY_H = "H" -- cmd annot/link/URI local constKEY_URI = "URI" local constKEY_IS_MAP = "IsMap" -- not handled by pax, consider later -- cmd annot/link/GoToR local constKEY_FILE = "File" local constKEY_DEST_NAME = "DestName" -- handle?? local constKEY_DEST_PAGE = "DestPage" --ok local constKEY_DEST_VIEW = "DestView" --ok -- cmd annot/link/GoTo local constKEY_DEST_RECT = "DestRect" local constKEY_DEST_X = "DestX" local constKEY_DEST_Y = "DestY" local constKEY_DEST_ZOOM = "DestZoom" local constKEY_DEST_LABEL = "DestLabel" --ok -- cmd annot/link/Named local constKEY_NAME = "Name" -- destination views local constDEST_XYZ = "XYZ" local constDEST_FIT = "Fit" local constDEST_FITH = "FitH" local constDEST_FITV = "FitV" local constDEST_FITR = "FitR" local constDEST_FITB = "FitB" local constDEST_FITBH = "FitBH" local constDEST_FITBV = "FitBV" -- get/build data -- returns table,pagecount where table objref ->page number local function getpagesdata (pdfedoc) local type,pagecount,detail = GETFROMDICTIONARY (pdfedoc.Catalog.Pages,"Count") local pagestable = PAGESTOTABLE (pdfedoc) local pagereftonum ={} for i=1,#pagestable do pagereftonum[pagestable[i][3]]=i end return pagereftonum, pagecount end -- builds a table destination name -> obj reference (pdfedict) from the Names tree local function processnamesarray (pdfearray,targettable) local tkey={} for i=1,#pdfearray do local type,value,detail= GETFROMARRAY(pdfearray,i) if (i % 2 == 1) then tkey = value else tvalue = value targettable[tkey]=tvalue end end end local function findallnamesarrays (pdfedict,deststable) local namesarray = GETARRAY(pdfedict,"Names") if namesarray then processnamesarray (namesarray,deststable) else local kidsarray = GETARRAY(pdfedict,"Kids") if kidsarray then for i=1,#kidsarray do findallnamesarrays (kidsarray[i],deststable) end end end end -- returns destnames -> objref table local function getdestreferences (pdfedoc) local deststable= {} local catnames = GETDICTIONARY (pdfedoc.Catalog, "Names") if catnames then local destsdict = GETDICTIONARY (catnames, "Dests") if destsdict then findallnamesarrays (destsdict,deststable) end end return deststable end -- destinations can be an dict (/D [array]) or only an array! local function getdestdata (name,pagereftonum,destnamestoref) local destref local type,ref,pagenum,destx,desty = nil, nil, 1,0,0 local data = {{0,0}, {5,constDEST_XYZ}} -- if we get directly an array if (TYPE(name) == "pdfe.array") then data = ARRAYTOTABLE(name) type, ref, pageref = GETFROMARRAY(name,1) pagenum = pagereftonum[pageref] else destref = destnamestoref[name] end if destref then type,value,detail = GETFROMREFERENCE(destref) if TYPE(value) == "pdfe.dictionary" then local destarray = GETARRAY(value,"D") if destarray then data = ARRAYTOTABLE(destarray) type, ref, pageref = GETFROMARRAY(destarray,1) pagenum = pagereftonum[pageref] end elseif TYPE(value) == "pdfe.array" then data = ARRAYTOTABLE(value) type, ref, pageref = GETFROMARRAY(value,1) pagenum = pagereftonum[pageref] end end -- print("XXXXXXXXXX",table.serialize(data),pagenum) return pagenum, data end -- output functions -- write the info -- XXXXXX encode/escape the file name? local function outputENTRY_file (file, pdfedoc) local bytes = GETSIZE(pdfedoc) local date = (GETINFO(pdfedoc) and GETINFO(pdfedoc).CreationDate) or "D:22222222222222" -- file local a = strENTRY_BEG a = a .. strCMD_BEG .. constCMD_FILE .. strCMD_END a = a .. strARG_BEG .. strLIT_STR_BEG .. file .. strLIT_STR_END .. strARG_END a = a .. strKVS_BEG a = a .. strKV_BEG .. constKEY_SIZE .. strVALUE_BEG .. bytes .. strVALUE_END .. strKV_END a = a .. strKV_BEG .. constKEY_DATE .. strVALUE_BEG .. date .. strVALUE_END.. strKV_END a = a .. strKVS_END a = a .. strENTRY_END return a end local function outputENTRY_pagenum (pages) local a = strENTRY_BEG a = a .. strCMD_BEG .. constCMD_PAGENUM .. strCMD_END a = a .. strARG_BEG .. pages .. strARG_END a = a .. strENTRY_END return a end local function outputENTRY_page (pdfedoc,page) -- page=integer -- trimbox, bleedbox, cropbox,artbox,rotate etc could be put in -- the second argument as keyval: -- TrimBox={0 0 300 350}, -- but pax skips the argument anyway, so not really useful local mediabox = pdfe.getbox(GETPAGE(pdfedoc,page),"MediaBox") local a="" if mediabox then a = strENTRY_BEG a = a .. strCMD_BEG .. constCMD_PAGE .. strCMD_END a = a .. strARG_BEG .. page .. strARG_END a = a .. strARG_BEG a = a .. mediabox[1] for j = 2, 4 do a = a .. strRECT_SEP.. mediabox[j] end a = a .. strARG_END a = a .. strKVS_EMPTY a = a .. strENTRY_END end return a end local function outputCMD_annot (pdfedict,page,type) local rectangle = ARRAYTOTABLE(GETARRAY(pdfedict,"Rect")) local a = strCMD_BEG .. constCMD_ANNOT .. strCMD_END a = a .. strARG_BEG .. page .. strARG_END a = a .. strARG_BEG .. GETNAME(pdfedict,"Subtype") .. strARG_END a = a .. strARG_BEG a = a .. rectangle[1][2] for k = 2, 4 do a = a.. strRECT_SEP..rectangle[k][2] end a = a .. strARG_END a = a .. strARG_BEG .. type .. strARG_END return a end local function outputKV_color (pdfedict) local color = GETARRAY(pdfedict,constKEY_C) local a ="" if color then local colortable = ARRAYTOTABLE(color) a = strKV_BEG .. constKEY_C .. strVALUE_BEG .. strARRAY_BEG for i=1,#colortable do a = a.. colortable[i][2] .. strARRAY_SEP end a = a .. strARRAY_END .. strVALUE_END .. strKV_END end return a end local function outputKV_key (pdfedict,key) local name = GETNAME(pdfedict,key) local a = "" if name then a = strKV_BEG .. key .. strVALUE_BEG .. strNAME .. name .. strVALUE_END .. strKV_END end return a end -- XXX handle fourth argument local function outputKV_Border (pdfedict) local border = GETARRAY(pdfedict,constKEY_BORDER) local a ="" if border then local bordertable = ARRAYTOTABLE(border) a = strKV_BEG .. constKEY_BORDER .. strVALUE_BEG .. strARRAY_BEG for i=1,3 do a = a .. bordertable[i][2] .. strARRAY_SEP end -- fourth argument later, it is an array (type 7) a = a ..strARRAY_END .. strVALUE_END .. strKV_END end return a end local function outputKV_BS (pdfedict) local bsstyle = GETDICTIONARY(pdfedict,constKEY_BS) local a ="" if bsstyle then local bsstyledict = DICTIONARYTOTABLE(bsstyle) a = strKV_BEG .. constKEY_BS .. strVALUE_BEG ..strDICT_BEG for k,v in pairs (bsstyledict) do a = a .. strNAME.. k if v[1]== 5 then a = a .. strNAME .. v[2] else a = a ..strRECT_SEP .. v[2] end end a = a .. strDICT_END .. strVALUE_END .. strKV_END end return a end local function outputKV_uri (pdfedict) local type, value, hex = GETFROMDICTIONARY(pdfedict,constKEY_URI) if TYPE(value) == "pdfe.reference" then type,value,hex = GETFROMREFERENCE(value) end local a= strKV_BEG .. constKEY_URI .. strVALUE_BEG if hex then a = a .. strHEX_STR_BEG .. value .. strHEX_STR_END else a = a .. strLIT_STR_BEG ..value .. strLIT_STR_END end a = a .. strVALUE_END .. strKV_END return a end local function outputKV_N (pdfedict) local name = GETNAME(pdfedict,"N") local a= strKV_BEG .. constKEY_NAME .. strVALUE_BEG .. name .. strVALUE_END .. strKV_END return a end -- if a gotoR has a filespec filespec we use this -- to output the reference. It is rather crude and handles only names and strings local function outputDICT (dictionary) local tkeys = {} local dict = DICTIONARYTOTABLE(dictionary) for name,value in pairs(dict) do table.insert(tkeys,name) end table.sort(tkeys) local a = "<<" for _,k in ipairs (tkeys) do v=dict[k] a = a .. strNAME.. k if v[1]== 5 then -- it is a name b = string.gsub(v[2], "/", "#2F") a = a .. strNAME .. b elseif v[1] == 6 then -- it is a string local b if v[3] then b = "<" .. v[2] .. ">" else b = "(" .. v[2] .. ")" end a = a ..strRECT_SEP .. b -- everything else is ignored for now! end end a = a .. ">>" return a end local function outputKV_gotor (pdfedict) -- action dictionary local type, value, hex = GETFROMDICTIONARY(pdfedict,"F") local desttype, destvalue, destdetail = GETFROMDICTIONARY(pdfedict,"D") local a = strKV_BEG .. constKEY_FILE .. strVALUE_BEG if TYPE(value) == "pdfe.reference" then local x,dictionary = GETFROMREFERENCE(value) if TYPE(dictionary) == "pdfe.dictionary" then a = a .. outputDICT (dictionary) else print("ERROR!? this is not a dictionary!!") end else if hex then a = a .. strHEX_STR_BEG .. value .. strHEX_STR_END else a = a .. strLIT_STR_BEG .. value .. strLIT_STR_END end end a = a .. strVALUE_END .. strKV_END if desttype == 7 then local type, pagenum = GETFROMARRAY(GETARRAY (pdfedict,"D"),1) local type, fittype = GETFROMARRAY(GETARRAY (pdfedict,"D"),2) a = a .. strKV_BEG .. constKEY_DEST_PAGE .. strVALUE_BEG .. pagenum .. strVALUE_END .. strKV_END a = a .. strKV_BEG .. constKEY_DEST_VIEW .. strVALUE_BEG .. strNAME .. fittype .. strVALUE_END .. strKV_END elseif desttype == 6 then a = a .. strKV_BEG .. constKEY_DEST_NAME .. strVALUE_BEG .. strLIT_STR_BEG .. destvalue .. strLIT_STR_END .. strVALUE_END .. strKV_END end return a end -- this outputs the key DestLabel with a count local function outputKV_goto (count) local a = strKV_BEG .. constKEY_DEST_LABEL .. strVALUE_BEG .. count .. strVALUE_END .. strKV_END return a end -- this outputs the key DestName with a name local function outputKV_goto_name (name) local a = strKV_BEG .. constKEY_DEST_NAME .. strVALUE_BEG .. name .. strVALUE_END .. strKV_END return a end local function outputENTRY_dest (destcount,name,pagereftonum,destnamestoref,pdfedoc) local pagenum, data = getdestdata(name,pagereftonum,destnamestoref) local mediabox = pdfe.getbox(GETPAGE(pdfedoc,pagenum),"MediaBox") local a = strENTRY_BEG a = a .. strCMD_BEG .. constCMD_DEST .. strCMD_END a = a .. strARG_BEG .. pagenum .. strARG_END a = a .. strARG_BEG .. destcount .. strARG_END -- name a = a .. strARG_BEG .. data[2][2] .. strARG_END a = a .. strKVS_BEG if data[2][2] == constDEST_XYZ then if data[3] and data[3][2] then a = a .. strKV_BEG .. constKEY_DEST_X .. strVALUE_BEG .. data[3][2] .. strVALUE_END .. strKV_END else a = a .. strKV_BEG .. constKEY_DEST_X .. strVALUE_BEG .. mediabox[1] .. strVALUE_END .. strKV_END end if data[4] and data[4][2] then a = a .. strKV_BEG .. constKEY_DEST_Y .. strVALUE_BEG .. data[4][2] .. strVALUE_END .. strKV_END else a = a .. strKV_BEG .. constKEY_DEST_Y .. strVALUE_BEG .. mediabox[4] .. strVALUE_END .. strKV_END end if data[5] and data[5][2] then a = a .. strKV_BEG .. constKEY_DEST_ZOOM .. strVALUE_BEG .. data[5][2] .. strVALUE_END .. strKV_END end elseif data[2][2] == constDEST_FIT then -- nothing to do elseif data[2][2] == constDEST_FITB then -- nothing to do elseif data[2][2] == constDEST_FITH then if data[3] and data[3][2] then a = a .. strKV_BEG .. constKEY_DEST_Y .. strVALUE_BEG .. data[3][2] .. strVALUE_END .. strKV_END end elseif data[2][2] == constDEST_FITBH then if data[3] and data[3][2] then a = a .. strKV_BEG .. constKEY_DEST_Y .. strVALUE_BEG .. data[3][2] .. strVALUE_END .. strKV_END end elseif data[2][2] == constDEST_FITV then if data[3] and data[3][2] then a = a .. strKV_BEG .. constKEY_DEST_X .. strVALUE_BEG .. data[3][2] .. strVALUE_END .. strKV_END end elseif data[2][2] == constDEST_FITBV then if data[3] and data[3][2] then a = a .. strKV_BEG .. constKEY_DEST_X .. strVALUE_BEG .. data[3][2] .. strVALUE_END .. strKV_END end elseif data[2][2] == constDEST_FITR and data[6] then a = a .. strKV_BEG .. constKEY_DEST_RECT .. strVALUE_BEG a = a .. data[3][2] .. strRECT_SEP a = a .. data[4][2] .. strRECT_SEP a = a .. data[5][2] .. strRECT_SEP a = a .. data[6][2] .. strVALUE_END .. strKV_END end -- output also the name. This perhaps need a check if the name exists -- print("DEBUG TYPE name", TYPE(name)) if not (TYPE(name) == "pdfe.array") then a = a .. strKV_BEG .. constKEY_DEST_NAME .. strVALUE_BEG .. name .. strVALUE_END .. strKV_END end a = a .. strKVS_END .. strENTRY_END return a end -- the main function local function __writepax (ext,file) local fileVAR = assert(kpse.find_file(file ..".pdf"),"file `"..file..".pdf` not found") local filebaseVAR = FILENAMEONLY(fileVAR) -- getting the data for the concrete document: local writeVAR = io.open (filebaseVAR .."."..ext,"w") -- always in current directory local function WRITE(content) writeVAR:write(content) end local docVAR = OPEN (fileVAR) local pagereftonumVAR, pagecountVAR = getpagesdata (docVAR) local destcountVAR = 0 -- build from names table: local destnamestorefVAR = getdestreferences (docVAR) local collected_destinations = {} local useddestnames = {} -- output ... WRITE(strENTRY_BEG .. "{pax}{0.1l}" .. strENTRY_END) WRITE(outputENTRY_file(fileVAR,docVAR)) WRITE(outputENTRY_pagenum(pagecountVAR)) for i=1, pagecountVAR do WRITE(outputENTRY_page(docVAR,i)) local annots=GETPAGE(docVAR,i).Annots if annots then for j = 0,#annots-1 do local annot = GETDICTIONARY (annots,j) annottable = DICTIONARYTOTABLE (annot) if annottable.Dest then destcountVAR=destcountVAR + 1 WRITE (strENTRY_BEG) WRITE (outputCMD_annot(annot,i,"GoTo")) WRITE (strKVS_BEG) -- begin KVS data WRITE ( outputKV_color(annot) ) WRITE ( outputKV_key(annot,constKEY_H) ) WRITE ( outputKV_Border (annot) ) WRITE ( outputKV_BS (annot) ) WRITE ( outputKV_goto (destcountVAR) ) WRITE(strKVS_END) -- end KVS WRITE(strENTRY_END) -- end annot data local type,annotgoto,hex = GETFROMDICTIONARY(annot,"Dest") table.insert(collected_destinations, outputENTRY_dest(destcountVAR, annotgoto,pagereftonumVAR,destnamestorefVAR,docVAR)) else local annotaction = GETDICTIONARY(annot,"A") -- print("DEBUG A:",table.serialize(DICTIONARYTOTABLE(annotaction))) local annotactiontype ="" if annotaction then annotactiontype = GETNAME(annotaction,"S") WRITE (strENTRY_BEG) WRITE (outputCMD_annot(annot,i,annotactiontype)) WRITE (strKVS_BEG) -- begin KVS data WRITE ( outputKV_color(annot) ) WRITE ( outputKV_key(annot,constKEY_H) ) WRITE ( outputKV_Border (annot) ) WRITE ( outputKV_BS (annot) ) if annotactiontype == constKEY_URI then WRITE ( outputKV_uri(annotaction) ) elseif annotactiontype =="GoTo" then local desttype, destname, destdetail = GETFROMDICTIONARY(annotaction,"D") -- print("DEBUG annotaction", desttype, destname, destdetail) destcountVAR=destcountVAR + 1 WRITE ( outputKV_goto (destcountVAR) ) -- this needs perhaps a check if destname actually exists. if desttype == 6 then WRITE ( outputKV_goto_name(destname) ) end elseif annotactiontype=="GoToR" then WRITE ( outputKV_gotor(annotaction) ) elseif annotactiontype=="Named" then WRITE ( outputKV_N (annotaction) ) end WRITE(strKVS_END) -- end KVS WRITE(strENTRY_END) -- end annot data if annotactiontype =="GoTo" then local type,annotactiongoto,hex = GETFROMDICTIONARY(annotaction,"D") if type==6 then -- record used name useddestnames[annotactiongoto] = 1 end table.insert(collected_destinations, outputENTRY_dest(destcountVAR,annotactiongoto,pagereftonumVAR,destnamestorefVAR,docVAR)) end end end end end end for i=1,#collected_destinations do WRITE (collected_destinations[i]) end -- write out the rest of the destinations. -- We order first the table so that the newpax file doesn't change between -- compilations, see issue #25 and PR #25 local destnamessorted = {} for k,v in pairs (destnamestorefVAR) do -- ignore use dests and page destinations. i=string.find(k,"page.",1) if not useddestnames[k] and not i then table.insert (destnamessorted,k) end end table.sort(destnamessorted) for i = 1, #destnamessorted do k=destnamessorted[i] destcountVAR=destcountVAR + 1 WRITE(outputENTRY_dest(destcountVAR,k,pagereftonumVAR,destnamestorefVAR,docVAR)) end io.close(writeVAR) end local function writepax (file) __writepax ("pax",file) end local function writenewpax (file) __writepax ("newpax",file) end newpax = {} newpax.writepax = writepax newpax.writenewpax = writenewpax return newpax % % \end{macrocode}