% \iffalse meta-comment % %% File: tagpdf.dtx % % Copyright (C) 2019-2020 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 "tagpdf 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/tagpdf % % for those people who are interested. % % \fi % % \begin{macrocode} %<@@=tag> %<*package> \ProvidesExplPackage {tagpdf} {2021/05/14} {0.81} { A package to experiment with pdf tagging } \bool_if:nF { \bool_lazy_and_p:nn {\cs_if_exist_p:N \pdfmanagement_if_active_p:} { \pdfmanagement_if_active_p: } } { %error for now, perhaps warning later. \PackageError{tagpdf} { PDF~resource~management~is~no~active!\MessageBreak tagpdf~will~no~work. } { Activate~it~with \MessageBreak \string\RequirePackage{pdfmanagement-testphase}\MessageBreak \string\DeclareDocumentMetadata{}\MessageBreak before~\string\documentclass } } %\debug_on:n{check-declarations,check-expressions,deprecation} %map internal tag to package name \prop_if_exist:NT \g_msg_module_name_prop { \prop_gput:Nnn \g_msg_module_name_prop { tag }{ tagpdf } } % storing internal names to my name space: \cs_set_eq:NN \@@_tex_global:D \tex_global:D %%% package options \bool_new:N\g_@@_mode_lua_bool \DeclareOption {luamode} { \sys_if_engine_luatex:T { \bool_gset_true:N \g_@@_mode_lua_bool } } \DeclareOption {genericmode}{ \bool_gset_false:N\g_@@_mode_lua_bool } \ExecuteOptions{luamode} \ProcessOptions %%% some packages %references use l3ref-tmp \RequirePackage{l3ref-tmp} %================ % temporary code % =============== % faking the LastPage label: \cs_new_protected:Npn \@@_lastpagelabel: { \legacy_if:nT { @filesw } { \exp_args:NNnx \exp_args:NNx\iow_now:Nn \@auxout { \token_to_str:N \newlabeldata {@@_LastPage} { {abspage} { \int_use:N \g_shipout_readonly_int} {tagmcabs}{ \int_use:N \c@g_@@_MCID_abs_int } } } } } \AddToHook{enddocument/afterlastpage} {\@@_lastpagelabel:} % a command to override the general default. See issue in ref \cs_if_exist:NF \ref_value:nnn { \cs_new:Npn \ref_value:nnn #1#2#3 { \exp_args:Nee \__ref_value:nnn { \tl_to_str:n {#1} } { \tl_to_str:n {#2} } {#3} } \cs_new:Npn \__ref_value:nnn #1#2#3 { \tl_if_exist:cTF { g__ref_label_ #1 _ #2 _tl } { \tl_use:c { g__ref_label_ #1 _ #2 _tl } } { % test if attribute exist at all? #3 } } } %============= % attributes %============= \ref_attribute_gset:nnnn {tagstruct} {0}{now} { \int_use:N \c@g_@@_struct_abs_int } \ref_attribute_gset:nnnn {tagstructobj} {}{now} { \exp_args:Ne \pdf_object_if_exist:nT {@@/struct/\int_use:N \c@g__tag_struct_abs_int} { \pdf_object_ref:e{@@/struct/\int_use:N \c@g_@@_struct_abs_int} } } % replace by abspage?? \ref_attribute_gset:nnnn {tagabspage}{0}{shipout}{ \int_use:N \g_shipout_readonly_int } \ref_attribute_gset:nnnn {tagmcabs } {0}{now} { \int_use:N \c@g_@@_MCID_abs_int } \ref_attribute_gset:nnnn {tagmcid } {0}{now} { \int_use:N \g_@@_MCID_tmp_bypage_int } %============ % lists %============ \clist_const:Nn \c_@@_refmc_clist {tagabspage,tagmcabs,tagmcid} %mc is new!! \clist_const:Nn \c_@@_refstruct_clist {tagstruct,tagstructobj} %================= % command to set a label %================ \cs_generate_variant:Nn \ref_label:nn { nv } \cs_new_protected:Npn \@@_ref_label:nn #1 #2 %#1 label, #2 name of list mc or struct { \@bsphack \ref_label:nv {#1}{c_@@_ref#2_clist} \@esphack } %============== % command to retrieve the value %=============== \cs_new:Npn \@@_ref_value:nnn #1 #2 #3 %#1 label, #2 attribute, #3 default { \ref_value:nnn {#1}{#2}{#3} } %=============== % the LastPage label has a different name, so we need a special command for now % ============= \cs_new:Npn \@@_ref_value_lastpage:nn #1 #2 { \ref_value:nnn {@@_LastPage}{#1}{#2} } % as ref doesn't expand label names we need variants: \cs_generate_variant:Nn \@@_ref_label:nn {en} \cs_generate_variant:Nn \@@_ref_value:nnn {enn} \RequirePackage{etoolbox} %\RequirePackage{l3pdf} %\RequirePackage{pdfescape} %check if needed %\RequirePackage{pdftexcmds}%check if needed (expandable commands!) %\RequirePackage{l3str-convert} \cs_generate_variant:Nn \str_set_convert:Nnnn {Nonn, Noon, Nnon } %%% tagpdfsetup, %%% TODO: checks need to be improved \int_new:N \l_@@_loglevel_int \tl_new:N \l_@@_tree_tabs_order_tl \keys_define:nn { @@ / setup } { activate-mc .bool_gset:N = \g_@@_active_mc_bool, activate-tree .bool_gset:N = \g_@@_active_tree_bool, activate-struct .bool_gset:N = \g_@@_active_struct_bool, activate-all .meta:n ={activate-mc,activate-tree,activate-struct}, check-tags .bool_gset:N = \g_@@_check_tags_bool, check-tags .initial:n = true, log .choice:, log / none .code:n = {\int_set:Nn \l_@@_loglevel_int { 0 }}, log / v .code:n = {\int_set:Nn \l_@@_loglevel_int { 1 }}, log / vv .code:n = {\int_set:Nn \l_@@_loglevel_int { 2 }}, log / vvv .code:n = {\int_set:Nn \l_@@_loglevel_int { 3 }}, log / all .code:n = {\int_set:Nn \l_@@_loglevel_int { 10 }}, tagunmarked .bool_gset:N = \g_@@_tagunmarked_bool, tagunmarked .initial:n = true, tabsorder .choice:, tabsorder / row .code:n = %{\tl_set:Nn \l_@@_tree_tabs_order_tl {/Tabs/R}}, \pdfmanagement_add:nnn { Page } {Tabs}{/R}, tabsorder / column .code:n = %{\tl_set:Nn \l_@@_tree_tabs_order_tl {/Tabs/C}}, \pdfmanagement_add:nnn { Page } {Tabs}{/C}, tabsorder / structure .code:n = %{\tl_set:Nn \l_@@_tree_tabs_order_tl {/Tabs/S}}, \pdfmanagement_add:nnn { Page } {Tabs}{/S}, tabsorder / none .code:n = %{\tl_set:Nn \l_@@_tree_tabs_order_tl {}}, \pdfmanagement_remove:nn {Page} {Tabs}, tabsorder .initial:n = structure, uncompress .code:n = {\pdf_uncompress: }, global-mc .code:n = { \cs_set_eq:NN\@@_attribute_set:Nn \@@_attribute_gset:Nn \cs_set_eq:NN\@@_attribute_unset:N\@@_attribute_gunset:N } } %need to think about the right place ... %\AtBeginDocument \hook_gput_code:nnn{begindocument}{tagpdf} { \bool_if:NT \g_@@_active_struct_bool { \pdfmanagement_add:nnn { Catalog / MarkInfo } { Marked } { true } } \keys_define:nn { @@ / setup } { global-mc .code:n={} } } % escape commands are a bit engine specific so have been moved to the backend files. % commands to escape strings so that they can be safely used in pdf % currently not much used. But will be needed later, when alt and actualtext are added. % we probably need the equivalent \pdfescapestring, \pdfescapename \pdfescapehex % the commands of pdfescape adds an additional layout to allow for babel shorthands %\cs_set_eq:NN \@@_pdf_escape_string:Nn \EdefEscapeString %\cs_set_eq:NN \@@_pdf_escape_name:Nn \EdefEscapeName %\cs_set_eq:NN \@@_pdf_escape_hex:Nn \EdefEscapeHex %\cs_set_eq:NN \@@_pdf_escape_string:n \EdefEscapeString %\cs_set_eq:NN \@@_pdf_escape_name:Nn \EdefEscapeName %escape command. They are backend specific as \pdf@escapehex would ignore most chars %outside the "pdflatex" range % a hook for later code and an absolute page counter % should be executed before counters are resetted. % is it used? \cs_new:Nn \@@_finish_page_hook: { } %\AtBeginShipout \hook_gput_code:nnn { shipout/before } { tagpdf/cnt } { \@@_finish_page_hook: %\int_gincr:N \g_@@_abspage_int } %testing the engines and loading the backend files \sys_if_engine_xetex:T { %lets try with xelatex ... %\PackageError { tagpdf } { xelatex~is~not~supported~-~aborting } {} %\tex_endinput:D \file_input:n {tagpdf-pdftex.def} } \sys_if_engine_luatex:T { \file_input:n {tagpdf-luatex.def} } \sys_if_engine_pdftex:T { \file_input:n {tagpdf-pdftex.def} } \sys_if_output_dvi:T { %\PackageError { tagpdf } { dvi~output~is~not~supported~-~aborting }{} %\tex_endinput:D } \cs_generate_variant:Nn \@@_prop_gput:Nnn { Nxn , Nxx, Nnx , cnn, cxn, cnx, cno} \cs_generate_variant:Nn \@@_seq_gput_right:Nn { Nx , No, cn, cx } \cs_generate_variant:Nn \@@_prop_new:N { c } \cs_generate_variant:Nn \@@_seq_new:N { c } \cs_generate_variant:Nn \@@_seq_show:N { c } \cs_generate_variant:Nn \@@_prop_show:N { c } \cs_generate_variant:Nn \prop_gput:Nnn {Nxx} \cs_generate_variant:Nn \prop_put:Nnn {Nxx} \cs_generate_variant:Nn \pdffile_embed_stream:nnn {nxx,oxx} \cs_generate_variant:Nn \pdf_object_ref:n {e} % few temp tl \tl_new:N \g_@@_tmpa_tl \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl \tl_new:N \l_@@_tmpc_tl \tl_new:N \l_@@_tmpd_tl \tl_new:N \l_@@_tmpe_tl \str_new:N \l_@@_tmpa_str % helper function to get the propcount. % use as \prop_map_function:NN PROP { \@@_prop_count:nn -1 } \cs_new:Nn\@@_prop_count:nn { + 1 } %% Loading the tagpdf sub packages \RequirePackage { tagpdf-checks-code } \RequirePackage { tagpdf-user } \RequirePackage { tagpdf-tree-code } \RequirePackage { tagpdf-roles-code } \RequirePackage { tagpdf-attr-code } % mc-code is split: \RequirePackage { tagpdf-mc-code-shared } \bool_if:NTF \g_@@_mode_lua_bool { \RequirePackage {tagpdf-mc-code-lua} } { \RequirePackage { tagpdf-mc-code-generic } % } \RequirePackage { tagpdf-struct-code } \RequirePackage { tagpdf-space-code } % % \end{macrocode} % % \begin{macrocode} %<*user> \ProvidesExplPackage {tagpdf-user} {2021/05/14} {0.81} {tagpdf - user commands} % \end{macrocode} % \begin{macro}{\tagpdfsetup} % \begin{macrocode} \NewDocumentCommand \tagpdfsetup { m } { \keys_set:nn { @@ / setup } { #1 } } \cs_set_eq:NN\tagpdfifluatexTF \sys_if_engine_luatex:TF \cs_set_eq:NN\tagpdfifluatexT \sys_if_engine_luatex:T \cs_set_eq:NN\tagpdfifpdftexT \sys_if_engine_pdftex:T % \end{macrocode} % \end{macro} % We need a command to stop tagging in some places. % % \begin{macro}{ \tag_stop_group_begin:, \tag_stop_group_end: } % \begin{macrocode} \bool_new:N \l_@@_stop_bool \prg_new_conditional:Nnn \@@_if_stop: {p,T,F,TF} { \bool_if:NTF \l_@@_stop_bool { \prg_return_true: } { \prg_return_false: } } \prg_new_eq_conditional:NNn \tag_if_stop: \@@_if_stop: {p,T,F,TF} \cs_new_protected:Npn \@@_stop: { \bool_set_true:N \l_@@_stop_bool \cs_set_eq:NN \tag_mc_begin:n \use_none:n \cs_set_eq:NN \tag_mc_end: \prg_do_nothing: \cs_set_eq:NN \tag_mc_use:n \use_none:n \cs_set_eq:NN \tag_struct_begin:n \use_none:n \cs_set_eq:NN \tag_struct_end: \prg_do_nothing: \cs_set_eq:NN \tag_struct_use:n \use_none:n } \cs_new_protected:Npn \tag_stop_group_begin: { \group_begin: \@@_stop: } \cs_set_eq:NN \tag_stop_group_end: \group_end: % \end{macrocode} % \end{macro} % We need a command to stop tagging in some places. % % \begin{macro}{ \tagpdfget, \tag_get:n } % This is a generic command to retrieve data. Currently % the only sensible value for the argument is |mc_tag| % \begin{macrocode} \cs_new:Npn \tagpdfget #1 { \use:c {@@_get_data_#1: } } \cs_new:Npn \tag_get:n #1 { \use:c {@@_get_data_#1: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\tagmcifinTF} % This is a test if an mc is open or not. It is mostly of % importance for pdflatex as lualatex doesn't mind much if a mc tag is not % correctly closed. % \begin{macrocode} \NewDocumentCommand \tagmcifinTF { m m } { \@@_mc_if_in:TF { #1 } { #2 } } % \end{macrocode} % \end{macro} % % \begin{macro}{tagmcartifact} % \begin{macrocode} \NewDocumentEnvironment{tagmcartifact}{m} { \tag_mc_artifact_group_begin:n { #1}\ignorespaces } { \tag_mc_artifact_group_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\tagmcbegin,\tagmcend,\tagmcuse } % \begin{macrocode} \NewDocumentCommand \tagmcbegin { m } { \tag_mc_begin:n {#1}\ignorespaces } \NewDocumentCommand \tagmcend { } { \if_mode_horizontal: \unskip \fi: % \tag_mc_end: } \NewDocumentCommand \tagmcuse { m } { \tag_mc_use:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\tagstructbegin,\tagstructend,\tagstructuse } % % These are structure related user commands % \begin{macrocode} \NewDocumentCommand \tagstructbegin { m } { \tag_struct_begin:n {#1} } \NewDocumentCommand \tagstructend { } { \tag_struct_end: } \NewDocumentCommand \tagstructuse { m } { \tag_struct_use:n {#1} } % \end{macrocode} % \end{macro} % % \section{Debugging} % % \begin{macro}{\showtagpdfmcdata,\showtagpdfattributes } % This commands are lua only % \begin{macrocode} \NewDocumentCommand\showtagpdfmcdata { O {\@@_tag_get_mc_abs_cnt:} } { \bool_if:NT \g_@@_mode_lua_bool { \sys_if_engine_luatex:T { \directlua{ltx.@@.trace.show_all_mc_data(#1)} } } } \NewDocumentCommand\showtagpdfattributes { } { \bool_if:NT \g_@@_mode_lua_bool { \sys_if_engine_luatex:T { \directlua { ltx.@@.trace.log ( "showtagpdfattributes: MC=>abscnt=\@@_get_mc_abs_cnt: =>attr=\the\l_@@_mc_cnt_attr=>tag=" .. tostring(ltx.@@.func.get_tag_from (\the\l_@@_mc_type_attr)) .. "=\the\l_@@_mc_type_attr",0 ) } \ignorespaces } } } \NewDocumentCommand\showtagstack {} { \seq_show:N \g_@@_struct_tag_stack_seq } % \end{macrocode} % \end{macro} % % \begin{macro}{ } % \begin{macrocode} \sys_if_engine_luatex:T { \NewDocumentCommand\pdffakespace { } { \@@_fakespace: } } % \end{macrocode} % \end{macro} % \section{Paratagging} % The following are some simple commands to enable/disable paratagging. % Probably one should add some checks if we are already in a paragraph. % \begin{macrocode} \bool_new:N \l_@@_para_bool \bool_new:N \l_@@_para_show_bool \int_new:N \g_@@_para_int \keys_define:nn { @@ / setup } { paratagging .bool_set:N = \l_@@_para_bool, paratagging-show .bool_set:N = \l_@@_para_show_bool, } \AddToHook{para/begin} { \int_gincr:N \g_@@_para_int \bool_if:NT \l__tag_para_bool { \tag_struct_begin:n {tag=P} \bool_if:NT \l_@@_para_show_bool { \tag_mc_begin:n{artifact} \llap{\color_select:n{red}\tiny\int_use:N\g_@@_para_int\ } \tag_mc_end: } \tag_mc_begin:n {tag=P} } } \AddToHook{para/end} { \bool_if:NT \l__tag_para_bool { \tag_mc_end: \bool_if:NT \l_@@_para_show_bool { \tag_mc_begin:n{artifact} \rlap{\color_select:n{red}\tiny\ \int_use:N\g_@@_para_int} \tag_mc_end: } \tag_struct_end: } } \newcommand\tagpdfparaOn {\bool_set_true:N \l__tag_para_bool} \newcommand\tagpdfparaOff{\bool_set_false:N \l__tag_para_bool} % \end{macrocode} % \section{Links} % We need to close and reopen mc-chunks around links. % With luatex the push/pop command must be outside groups, % so we add also code to the generic commands in the hyperref driver. % \begin{macrocode} \cs_generate_variant:Nn \pdfannot_dict_put:nnn {nnx} \hook_gput_code:nnn {pdfannot/link/URI/before} {tagpdf} { \bool_if:NT \g_@@_active_struct_bool { \tag_mc_end_push: \tag_struct_begin:n { tag=Link } \tag_mc_begin:n { tag=Link } \pdfannot_dict_put:nnx { link/URI } { StructParent } { \int_use:N\c@g_@@_parenttree_obj_int } } } \hook_gput_code:nnn {pdfannot/link/URI/after} {tagpdf} { \bool_if:NT \g_@@_active_struct_bool { \@@_struct_finish_link: \tag_mc_end: \tag_struct_end: \tag_mc_begin_pop:n{} } } \hook_gput_code:nnn {pdfannot/link/GoTo/before} {tagpdf} { \bool_if:NT \g_@@_active_struct_bool { \tag_mc_end_push: \tag_struct_begin:n{tag=Link} \tag_mc_begin:n{tag=Link} \pdfannot_dict_put:nnx { link/GoTo } { StructParent } { \int_use:N\c@g_@@_parenttree_obj_int } } } \hook_gput_code:nnn {pdfannot/link/GoTo/after} {tagpdf} { \bool_if:NT \g_@@_active_struct_bool { \@@_struct_finish_link: \tag_mc_end: \tag_struct_end: \tag_mc_begin_pop:n{} } } \hook_gput_code:nnn {cmd/url/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/url/after} {tagpdf} {\tag_mc_begin_pop:n{}} \hook_gput_code:nnn {cmd/href/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/href/after} {tagpdf} {\tag_mc_begin_pop:n{}} \hook_gput_code:nnn {cmd/hrefurl/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/hrefurl/after} {tagpdf} {\tag_mc_begin_pop:n{}} %this will go when references are improved \hook_gput_code:nnn {cmd/T@ref/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/T@ref/after} {tagpdf} {\tag_mc_begin_pop:n{}} \hook_gput_code:nnn {cmd/T@pageref/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/T@pageref/after} {tagpdf} {\tag_mc_begin_pop:n{}} \hook_gput_code:nnn {cmd/T@nameref/before} {tagpdf} {\tag_mc_end_push:} \hook_gput_code:nnn {cmd/T@nameref/after} {tagpdf} {\tag_mc_begin_pop:n{}} % "alternative descriptions " for PAX3. How to get better text here?? \pdfannot_dict_put:nnn { link/URI } { Contents } { (url) } \pdfannot_dict_put:nnn { link/GoTo } { Contents } { (ref) } % \end{macrocode} % %