\ProvidesExplPackage {luatagging} {2026-09-20} {0.1a} { LuaLaTeX code for PDF tagging } \RequirePackage{iftex} \RequireLuaTeX \RequirePackage{luatagging-lua3keyval} \lua_load_module:n { luatagging } \tl_new:N \l__luatag_commands_tl \tl_new:N \l__luatag_parent_tl \bool_new:N \l__luatag_artifact_bool \bool_new:N \l__luatag_firstkid_bool \hook_gput_code:nnn{package/tagpdf/after}{luatagging}{ \cs_set_protected:Npn \tag_struct_begin:n %#1 key-val { \__tag_check_if_active_struct:TF { \StructBeginKV } { \use_none:n } } % Note: This removes adding a reference to the StructTreeRoot, but does not disable writing the StructTreeRoot from % tagpdf. Ideally that would change in the longer term \hook_gremove_code:nn {shipout/lastpage} {tagpdf}% We probably want to readd the open action handling. \msg_new:nnn {luatag} {begin-pop-parameter-not-empty} {\string\tag_mc_begin_pop:n~is~only~supported~with~empty~argument.} \msg_new:nnn {luatag} {unsupported} {Luatagging~does~not~support~#1.} \cs_new:Npn \__luatag_unsupported:n { \msg_error:nnn {luatag} {unsupported} } \cs_set_protected:Npn \tag_struct_end: { \__tag_check_if_active_struct:T { \StructEnd } } \cs_set_protected:Npn \tag_mc_begin:n { \__tag_check_if_active_mc:TF { \BeginMc } { \use_none:n } } \cs_set_protected:Nn \tag_mc_end: { \__tag_check_if_active_mc:T { \ActivateMcArtifact } } \cs_set_protected:Nn \tag_mc_begin_pop:n { \__tag_check_if_active_mc:T { \tl_if_empty:nF {#1} { \msg_error:nn {luatag} {begin-pop-parameter-not-empty} } \PopMc } } \cs_set_protected:Nn \tag_mc_end_push: { \__tag_check_if_active_mc:T { \PushMc } } \ActivateMcArtifact \cs_set_eq:NN \__tag_attr_new_entry:nn \RegisterAttribute \cs_set_eq:NN \__tag_attr_new_used_entry:nn \RegisterAttribute \cs_undefine:N \__tag_role_check_parent_child:nnN \cs_undefine:N \__tag_role_check_parent_child:nnnnN \cs_set_protected:Nn \__tag_role_NS_new:nnn { \errmessage{Unsupported~\noexpand \__tag_role_NS_new:nnn \unexpanded{{#1}{#2}{#3}}} } \cs_set_protected:Nn \__tag_role_alloctag:nnn { \errmessage{Unsupported~\noexpand \__tag_role_alloctag:nnn \unexpanded{{#1}{#2}{#3}}} } \cs_set_protected:Nn \__tag_role_add_tag:nn { \errmessage{Unsupported~\noexpand \__tag_role_add_tag:nn \unexpanded{{#1}{#2}}} } \cs_set_protected:Nn \__tag_role_get:nnNN { \errmessage{Unsupported~\noexpand \__tag_role_get:nnNN \unexpanded{{#1}{#2}#3#4}} } \cs_set_protected:Nn \__tag_role_add_tag:nnnn { \AddTagNS{#1}{#2}{#3}{#4} } % FIXME: Real implementation \prg_gset_conditional:Npnn \tag_check_child:nn #1 #2 {TF, T, F} { \prg_return_true: } \socket_new_plug:nnn { tag/struct/tag }{ lua-latex-tags } { \prop_get:NeNTF \g__tag_role_tags_NS_prop {#1} \l__tag_tmp_unused_tl { \seq_set_split:Nne \l__tag_tmpa_seq { / } {#1/\l__tag_tmp_unused_tl} } { \seq_set_split:Nne \l__tag_tmpa_seq { / } {#1/} } \tl_gset:Ne \g__tag_struct_tag_tl { \seq_item:Nn\l__tag_tmpa_seq {1} } \tl_gset:Ne \g__tag_struct_tag_NS_tl{ \seq_item:Nn\l__tag_tmpa_seq {2} } % \@@_check_structure_tag:N \g__tag_struct_tag_tl } \socket_assign_plug:nn { tag/struct/tag } { lua-latex-tags } \cs_gset:Npn \tag_struct_recordtarget: { \SetStructProperty {Dest} {\@currentHref} } % Could be supported, but is currently only used in already replaced code. \cs_gset:Npn \__tag_get_data_mc_tag: { \get_mc_tag_is_not_supported } % Would be awkward to support and is not used anywhere. \cs_gset:Npn \__tag_get_data_mc_counter: { \get_mc_counter_is_not_supported } % We don't assign IDs, so this would be tricky. It's also not used. \cs_gset:Npn \__tag_get_data_struct_id: { \get_struct_id_is_not_supported } % Sadly used rather often. \cs_gset:Npn \__tag_get_data_struct_num: { \GetStructNum } \cs_set_eq:NN \g_@@_struct_stack_current_tl \GetStructNum \cs_gset:Npn \__tag_get_data_struct_tag: { \GetCurrentStructTag } \cs_gset:Npn \__tag_get_data_struct_C:n #1 { \GetStructClassClist \int_eval:n {#1} ~ } % The way the Lua interaction works there's really no % reason to differentiate `\tag_get:nN` and `\tag_get:n`, % so we just implement one in terms of the other. \cs_set_protected:Npn \tag_get:nN #1#2 { \tl_set:Nx #2 { \tag_get:n { #1 } } } % For \tag_get:nnN there is no \tag_get:nn variant, so we define it. \cs_new:Npn \tag_get:nn #1 #2 { \cs_if_exist_use:cTF { __tag_get_data_#2:n } { { #1 } } { \__luatag_unsupported:n {\tag_get:nnN{#1}{?}{?}} } } \cs_set_protected:Npn \tag_get:nnN #1#2#3 { \tl_set:Nx #3 { \tag_get:nn { #1 } { #2 } } } \cs_set_protected:Npn \tag_struct_gput:nnn #1 #2 #3 { \tl_if_eq:nnTF { \tag_get:n { struct_num } } { #1 } { \SetStructProperty } { \SetStructPropertyForStructNum #1 } \str_case:nnF { #2 } { { ref_dest_parent } { { Ref-dest-parent } { #3 } } { ref_dest } { { Ref-dest } { #3 } } { ref_num } { { Ref-num } { #3 } } { attribute } { { Attribute-inline }{ #3 } } { tag } { { Tag }{ \tl_tail:n { #3 } } } % \tl_tail:n to strip a leading / { C } { { Attribute-class-list }{ #3 } } } { {}{} \__luatag_unsupported:n {\tag_struct_gput:nnn{?}{#2}{#3}} } } \prg_set_conditional:Npnn \tag_if_tag_known:n #1 {TF, T, F} { \IfTagKnown {#1} } \cs_set:Npn \tag_struct_parent_int: { \AssignAndGetStructParent } \cs_set_protected:Npn \tag_struct_use_num:n #1 { \UseStructByNum \tex_numexpr:D #1 \scan_stop: } \cs_set:Npn \tag_struct_insert_annot:nn { \RecordObjectForStructParent } \cs_set_protected:Npn \tag_mc_reset_box:N { \ResetStructureAttributeToCurrentValueForBox } \prg_set_conditional:Npnn \tag_if_struct_exist:n #1 {TF, T, F} { \IfStructNumDefined #1 ~ } \keys_define:nn { __tag / setup } { role/user-NS .code:n = { \pdf_version_compare:NnF < {2.0} { \SetUserNamespace {https://www.latex-project.org/ns/local/#1} } } } \hook_gput_code:nnn{begindocument}{luatagging}{ \lua_now:n { luatexbase.remove_from_callback('pre_shaping_filter', 'tagpdf.rewrite-softhyphen') luatexbase.remove_from_callback('post_shaping_filter', 'tagpdf.rewrite-softhyphen') } } \hook_gset_rule:nnnn{begindocument}{luatagging}{voids}{tagpdf/mc} \hook_gput_code:nnn{begindocument/before}{luatagging}{ \bool_lazy_and:nnT { \g__tag_active_struct_dest_bool } { \g__tag_active_struct_bool } { \tl_set:Nn \l_pdf_current_structure_destination_tl { luatagging_current_structure } \pdf_activate_structure_destination: } } \hook_gset_rule:nnnn{begindocument/before}{luatagging}{voids}{tagpdf} \__pdf_object_record:nN {luatagging_current_structure} \c_one_int \cs_set_protected:Npn \c__pdf_object_luatagging_current_structure_int { \GetCurrentStructureReference } } \hook_gput_code:nnn{package/latex-lab-testphase-marginpar/after}{luatagging}{ \socket_new_plug:nnn { tagsupport/marginpar/begin }{ lua-latex-tags } { \if_mode_horizontal: \tag_mc_end: \tag_struct_begin:e{tag=\UseStructureName{marginnote},stash}% \AttachToMarginparParent \else: \tag_struct_begin:n{tag=\UseStructureName{marginnote}}% \fi: } \socket_assign_plug:nn { tagsupport/marginpar/begin } { lua-latex-tags } }