summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx')
-rw-r--r--Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx789
1 files changed, 789 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx b/Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx
new file mode 100644
index 00000000000..09ea3511882
--- /dev/null
+++ b/Master/texmf-dist/source/latex/tagpdf/tagpdf-struct.dtx
@@ -0,0 +1,789 @@
+% \iffalse meta-comment
+%
+%% File: tagpdf-struct.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>
+%<*struct>
+\ProvidesExplPackage {tagpdf-struct-code} {2021/02/23} {0.80}
+ {part of tagpdf - code related to storing structure}
+
+% I will use a latex counter for the structure count
+% to have a chance to avoid double structures in align etc
+
+\newcounter { g_@@_struct_abs_int }
+\int_gzero:N \c@g_@@_struct_abs_int
+
+% ref code is in tagpdf.dtx
+
+% a sequence stores structnum -> the obj numbers
+% to allow easy mapping over the structures
+
+\@@_seq_new:N \g_@@_struct_objR_seq
+
+% a sequence for the structure stack. When a sequence is opened it's number is put on the stack.
+\seq_new:N \g_@@_struct_stack_seq
+\seq_gpush:Nn \g_@@_struct_stack_seq {0}
+
+%this variables will hold the top entry and the parent on the stack
+\tl_new:N \l_@@_struct_stack_parent_tmp_tl
+\tl_new:N \g_@@_struct_stack_current_tl
+
+% I need at least one structure: the StructTreeRoot
+% normally it should have only one kid, e.g. the document element.
+
+% The data of the StructTreeRoot and the StructElem are in properties:
+% \g_@@_struct_0_prop for the root
+% \g_@@_struct_N_prop, N >=1
+% they have all the keys
+% objnum - number,
+% Type - StructTreeRoot or StructElem
+% num - number (identical to the num in the name, or 0 for the root)
+% and the keys from the two following lists
+% (the root has a special set of properties).
+% the values of the prop should be already escaped properly
+% when the entries are created (title,lange,alt,E,actualtext)
+
+
+\seq_const_from_clist:Nn \c_@@_struct_StructTreeRoot_entries_seq
+ {%p. 857/858
+ Type, % always /StructTreeRoot
+ K, % kid, dictionary or array of dictionaries
+ IDTree, % currently unused
+ ParentTree, % required,obj ref to the parent tree
+ ParentTreeNextKey, %optional
+ RoleMap,
+ ClassMap
+ }
+
+\seq_const_from_clist:Nn \c_@@_struct_StructElem_entries_seq
+ {%p 858 f
+ Type, %always /StructElem
+ S, %tag/type
+ P, %parent
+ ID, %optional
+ Ref, %optional, pdf 2.0 Use?
+ Pg, %obj num of starting page, optional
+ K, %kids
+ A, %attributes, probably unused
+ C, %class ""
+ %R,
+ T, %title, value in () or <>
+ Lang, %language
+ Alt, % value in () or <>
+ E, %abreviation
+ ActualText,
+ AF, %pdf 2.0, array of dict, associated files
+ NS, %pdf 2.0, dict, namespace
+ PhoneticAlphabet, %pdf 2.0
+ Phoneme %pdf 2.0
+ }
+
+% I need an output handler for each prop, to get expandable output
+% see https://tex.stackexchange.com/questions/424208/expandable-version-of-a-expl3-command/424213#424213
+
+\cs_new:Nn \@@_struct_output_prop_aux:nn %#1 num, #2 key
+ {
+ \prop_if_in:cnT
+ { g_@@_struct_#1_prop }
+ { #2 }
+ {
+ \c_space_tl/#2~ \prop_item:cn{ g_@@_struct_#1_prop } { #2 }
+ }
+ }
+
+\cs_new:Nn \@@_new_output_prop_handler:n
+ {
+ \cs_new:cn { @@_struct_output_prop_#1:n }
+ {
+ \@@_struct_output_prop_aux:nn {#1}{##1}
+ }
+ }
+
+
+% the first one, the StructTreeRoot is special, so
+% created manually:
+\@@_prop_new:c { g_@@_struct_0_prop }
+\@@_new_output_prop_handler:n {0}
+\tl_gset:Nn \g_@@_struct_stack_current_tl {0}
+
+\@@_seq_new:c { g_@@_struct_kids_0_seq }
+
+% deprecated
+%\@@_prop_gput:cno
+% { g_@@_struct_0_prop }
+% { objnum}
+% { \c_@@_tree_obj_structtreeroot_tl }
+
+% new
+\@@_prop_gput:cno
+ { g_@@_struct_0_prop }
+ { objref}
+ { \pdf_object_ref:n { c_@@_struct_0_obj } }
+
+\@@_prop_gput:cno
+ { g_@@_struct_0_prop }
+ { Type }
+ { /StructTreeRoot }
+
+% the constants are defined in the tree code.
+\@@_prop_gput:cnx
+ { g_@@_struct_0_prop }
+ { ParentTree }
+ { \pdf_object_ref:n { c_@@_tree_parenttree_obj } }
+
+\@@_prop_gput:cnx
+ { g_@@_struct_0_prop }
+ { RoleMap }
+ { \pdf_object_ref:n { c_@@_tree_rolemap_obj } }
+
+\@@_prop_gput:cno
+ { g_@@_struct_0_prop }
+ { entries }
+ { StructTreeRoot }
+
+\@@_prop_gput:cno
+ { g_@@_struct_0_prop }
+ { num }
+ { 0 }
+
+% commands to store the kids
+% I don't compare the page objects number yet, but always add the /Pg key, perhaps later
+
+\cs_new:Nn \@@_struct_kid_mc_gput_right:nn %#1 structure num, #2 MCID absnum%
+ {
+ %\@@_store_pdfpageref:Nn \l_tmpa_tl { \@@_ref_value:enn{mcid-#2}{tagabspage}{1} }
+ \@@_seq_gput_right:cx
+ { g_@@_struct_kids_#1_seq }
+ {
+ <<
+ /Type \c_space_tl /MCR \c_space_tl
+ /Pg %\c_space_tl \l_tmpa_tl \c_space_tl 0 \c_space_tl R \c_space_tl
+ \c_space_tl
+ \pdf_pageobject_ref:n { \@@_ref_value:enn{mcid-#2}{tagabspage}{1} }
+ /MCID \c_space_tl \@@_ref_value:enn{mcid-#2}{tagmcid}{1}
+ >>
+ }
+ }
+
+\cs_new:Nn\@@_struct_kid_struct_gput_right:nn %#1 num of parent struct, #2 kid struct
+ {
+ \@@_seq_gput_right:cx
+ { g_@@_struct_kids_#1_seq }
+ {
+ \prop_item:cn
+ { g_@@_struct_#2_prop }
+ { objref }
+ }
+ }
+
+\cs_new:Nn\@@_struct_kid_link_gput_right:nn %#1 num of parent struct, #2 obj reference
+ {
+ \pdf_object_unnamed_write:nx
+ { dict }
+ {
+ /Type \c_space_tl /OBJR \c_space_tl
+ /Obj~#2
+ }
+ \@@_seq_gput_right:cx
+ { g_@@_struct_kids_#1_seq }
+ {
+ \pdf_object_ref_last:
+ }
+ }
+
+\cs_generate_variant:Nn\@@_struct_kid_link_gput_right:nn { nx }
+
+\cs_new:Nn\@@_struct_exchange_kid_command:N %N= seq
+ {
+ \seq_gpop_left:NN #1 \l_tmpa_tl
+ \regex_replace_once:nnN
+ { \c{\@@_mc_insert_mcid_kids:n} }
+ { \c{\@@_mc_insert_mcid_single_kids:n} }
+ \l_tmpa_tl
+ \seq_gput_left:NV #1 \l_tmpa_tl
+ }
+
+\cs_generate_variant:Nn\@@_struct_exchange_kid_command:N { c }
+
+\cs_new:Nn \@@_struct_fill_kid_key:n %#1 is the struct num
+ {
+ \int_case:nnF
+ {
+ \seq_count:c
+ {
+ g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
+ }
+ }
+ {
+ { 0 }
+ { } %no kids, do nothing
+ { 1 } % 1 kid, insert
+ {
+ % in this case we need a special command in
+ %luamode to get the array right. See issue #13
+ \bool_if:NT\g_@@_mode_lua_bool
+ {
+ \@@_struct_exchange_kid_command:c
+ {g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq}
+ }
+ \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K}
+ {
+ \seq_item:cn
+ {
+ g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
+ }
+ {1}
+ }
+ } %
+ }
+ { %many kids, use an array
+ \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K}
+ {
+ [
+ \seq_use:cn
+ {
+ g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
+ }
+ {
+ \c_space_tl
+ }
+ ]
+ }
+ }
+ }
+
+% this command can be used for roots and structure elements
+% #1 is a num
+
+\tl_new:N \l_@@_struct_dict_content_tl
+
+\cs_new:Nn \@@_struct_get_dict_content:n
+ {
+ %\tl_set:Nn \l_@@_struct_dict_content_tl {<<}
+ \tl_clear:N \l_@@_struct_dict_content_tl
+ \seq_map_inline:cn
+ {
+ c_@@_struct_\prop_item:cn{ g_@@_struct_#1_prop }{entries}_entries_seq
+ }
+ {
+ \tl_put_right:Nx
+ \l_@@_struct_dict_content_tl
+ {
+ \prop_if_in:cnT
+ { g_@@_struct_#1_prop }
+ { ##1 }
+ {
+ \c_space_tl/##1~\prop_item:cn{ g_@@_struct_#1_prop } { ##1 }
+ }
+ }
+ }
+ %\tl_put_right:Nn \l_@@_struct_dict_content_tl { >> }
+ }
+
+
+% #1 is the struct num
+\cs_new:Nn \@@_struct_write_obj:n
+ {
+ \prop_if_in:cnTF
+ { g_@@_struct_#1_prop }
+ { objref }
+ {
+ \@@_struct_fill_kid_key:n { #1 }
+ %\prop_show:c { g_@@_struct_#1_prop }
+ \@@_struct_get_dict_content:n { #1 }
+ \exp_args:Nx
+ \pdf_object_write:nx
+ { c_@@_struct_#1_obj }
+ {
+ \l_@@_struct_dict_content_tl
+ }
+ }
+ {
+ \msg_error:nnn { tag } { struct-no-objnum } { #1}
+ }
+ }
+
+% keys for the user commands
+% should I pass the values (e.g. the tag) through an escape command?
+\keys_define:nn { @@ / struct }
+ {
+ label .tl_set:N = \l_@@_struct_key_label_tl,
+ stash .bool_set:N = \l_@@_struct_elem_stash_bool,
+ tag .code:n = % S property
+ {%%????????? \pdfescapename??
+ \tl_set:Nx \l_@@_tmpa_tl { #1 }
+ \bool_if:NT \g_@@_check_tags_bool
+ {
+ \@@_check_structure_tag:N \l_@@_tmpa_tl
+ }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { S }
+ { /\exp_not:V\l_@@_tmpa_tl }
+ },
+ title .code:n = % T property
+ {
+ \str_set_convert:Nnon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { T }
+ { <\l_@@_tmpa_str> }
+ },
+ title-o .code:n = % T property
+ {
+ \str_set_convert:Nnon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { T }
+ { <\l_@@_tmpa_str> }
+ },
+ alttext .code:n = % Alt property
+ {
+ \str_set_convert:Nnon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { Alt }
+ { <\l_@@_tmpa_str> }
+ },
+ alttext-o .code:n = % Alt property
+ {
+ \str_set_convert:Noon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { Alt }
+ { <\l_@@_tmpa_str> }
+ },
+ actualtext .code:n = % ActualText property
+ {
+ \str_set_convert:Nnon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { ActualText }
+ { <\l_@@_tmpa_str>}
+ },
+ actualtext-o .code:n = % ActualText property
+ {
+ \str_set_convert:Noon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { ActualText }
+ { <\l_@@_tmpa_str>}
+ },
+ lang .code:n = % Lang property
+ {
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { Lang }
+ { (#1) }
+ },
+ ref .code:n = % Lang property
+ {
+ \tl_clear:N\l_@@_tmpa_tl
+ \clist_map_inline:nn {#1}
+ {
+ \tl_put_right:Nx \l_@@_tmpa_tl
+ {~\ref_value:nn{tagpdfstruct-##1}{tagstructobj} }
+ }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { Ref }
+ { [\l_@@_tmpa_tl] }
+ },
+ E .code:n = % E property
+ {
+ \str_set_convert:Nnon
+ \l_@@_tmpa_str
+ { #1 }
+ { default }
+ { utf16/hex }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { E }
+ { <\l_@@_tmpa_str> }
+ },
+ }
+
+% keys for AF keys, they rely on l3pdffile!
+% The stream variants use txt as extension to get the mimetype.
+\keys_define:nn { @@ / struct }
+ {
+ AF .code:n = % T property
+ {
+ \pdf_object_if_exist:nTF {#1}
+ {
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { AF }
+ { \pdf_object_ref:n {#1} }
+ }
+ {
+
+ }
+ },
+ ,AFinline .code:n =
+ {
+ \group_begin:
+ \exp_args:Ne
+ \pdf_object_if_exist:nF {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
+ {
+ \pdffile_embed_stream:nxx
+ {#1}
+ {tag-AFfile\int_use:N\c@g_@@_struct_abs_int.txt}
+ {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
+ }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_use:N\c@g_@@_struct_abs_int _prop }
+ { AF }
+ { \pdf_object_ref:e {@@/fileobj\int_use:N\c@g_@@_struct_abs_int } }
+ \group_end:
+ }
+ ,AFinline-o .code:n =
+ {
+ \group_begin:
+ \exp_args:Ne
+ \pdf_object_if_exist:nF {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
+ {
+ \pdffile_embed_stream:oxx
+ {#1}
+ {tag-AFfile\int_use:N\c@g_@@_struct_abs_int.txt}
+ {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
+ }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_use:N\c@g_@@_struct_abs_int _prop }
+ { AF }
+ { \pdf_object_ref:e {@@/fileobj\int_use:N\c@g_@@_struct_abs_int } }
+ \group_end:
+ }
+ }
+
+\cs_new_protected:Nn \tag_struct_begin:n
+ {
+ \group_begin:
+ \int_gincr:N \c@g_@@_struct_abs_int
+ \@@_prop_new:c { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ \@@_new_output_prop_handler:n {\int_eval:n { \c@g_@@_struct_abs_int }}
+ \@@_seq_new:c { g_@@_struct_kids_\int_eval:n { \c@g_@@_struct_abs_int }_seq}
+ %\@@_pdfreserveobjnum:N \l_tmpa_tl
+ \exp_args:Ne
+ \pdf_object_new:nn
+ { c_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_obj }
+ { dict }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ { objref}
+ {
+ \exp_args:Ne
+ \pdf_object_ref:n
+ {c_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_obj}
+ }
+% \prop_show:c { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ { num}
+ { \int_eval:n { \c@g_@@_struct_abs_int } }
+ \@@_prop_gput:cno
+ { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ { Type }
+ { /StructElem }
+ \@@_prop_gput:cno
+ { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
+ { entries }
+ { StructElem }
+ \keys_set:nn { @@ / struct} { #1 }
+ \@@_check_structure_has_tag:n { \int_eval:n {\c@g_@@_struct_abs_int} }
+ \tl_if_empty:NF
+ \l_@@_struct_key_label_tl
+ {
+ \@@_ref_label:en{tagpdfstruct-\l_@@_struct_key_label_tl}{struct}
+ }
+ %get the potential parent from the stack:
+ \seq_get:NNF
+ \g_@@_struct_stack_seq
+ \l_@@_struct_stack_parent_tmp_tl
+ {
+ \msg_error:nn { tag } { struct-faulty-nesting }
+ }
+ \seq_gpush:NV \g_@@_struct_stack_seq \c@g_@@_struct_abs_int
+ \tl_gset:NV \g_@@_struct_stack_current_tl \c@g_@@_struct_abs_int
+ %\seq_show:N \g_@@_struct_stack_seq
+ \bool_if:NF
+ \l_@@_struct_elem_stash_bool
+ {%set the parent
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { P }
+ {
+ \prop_item:cn
+ { g_@@_struct_\l_@@_struct_stack_parent_tmp_tl _prop}
+ { objref }
+ }
+ %record this structure as kid:
+ %\tl_show:N \g_@@_struct_stack_current_tl
+ %\tl_show:N \l_@@_struct_stack_parent_tmp_tl
+ \@@_struct_kid_struct_gput_right:nn
+ { \l_@@_struct_stack_parent_tmp_tl }
+ { \g_@@_struct_stack_current_tl }
+ %\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop }
+ %\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmp_tl _seq}
+ }
+ %\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop }
+ %\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmp_tl _seq}
+ \group_end:
+ }
+
+%deprecated
+\cs_set_eq:NN \uftag_struct_begin:n \tag_struct_begin:n
+
+\cs_new_protected:Nn \tag_struct_end:
+ { %take the current structure num from the stack:
+ %the objects are written later, lua mode hasn't all needed info yet
+ %\seq_show:N \g_@@_struct_stack_seq
+ \seq_gpop:NNTF \g_@@_struct_stack_seq \l_tmpa_tl
+ {
+ \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
+ {
+ \@@_check_info_closing_struct:o { \g_@@_struct_stack_current_tl }
+ }
+ }
+ { \@@_check_no_open_struck: }
+ % get the previous one, shouldn't be empty as the root should be there
+ \seq_get:NNTF \g_@@_struct_stack_seq \l_tmpa_tl
+ {
+ \tl_gset:NV \g_@@_struct_stack_current_tl \l_tmpa_tl
+ }
+ {
+ \@@_check_no_open_struck:
+ }
+ }
+
+%deprecated
+\cs_set_eq:NN \uftag_struct_end: \tag_struct_end:
+
+\cs_new_protected:Nn \tag_struct_use:n %#1 is the label
+ {
+ \prop_if_exist:cTF
+ { g_@@_struct_\@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{unknown}_prop } %??????????
+ {
+ \@@_check_struct_used:n {#1}
+ %add the label structure as kid to the current structure (can be the root)
+ \@@_struct_kid_struct_gput_right:nn
+ { \g_@@_struct_stack_current_tl }
+ { \@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{0} }
+ %add the current structure to the labeled one as parents
+ \@@_prop_gput:cnx
+ { g_@@_struct_\@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{0}_prop }
+ { P }
+ {
+ \prop_item:cn
+ { g_@@_struct_\g_@@_struct_stack_current_tl _prop}
+ { objref }
+ }
+ }
+ {
+ \msg_warning:nnn{ tag }{struct-label-unknown}{#1}
+ }
+ }
+
+\cs_set_eq:NN \uftag_struct_use:n \tag_struct_use:n
+
+
+%%%% Code to tag links
+%%%% this works for url, see exp-link.pdf.
+%%%% it must be checked for other links
+
+\cs_new_protected:Nn \@@_struct_finish_link:
+ {
+ \bool_if:NT \g_@@_active_struct_bool
+ {
+ %get the number of the parent link structure:
+ \seq_get:NNF
+ \g_@@_struct_stack_seq
+ \l_@@_struct_stack_parent_tmp_tl
+ {
+ \msg_error:nn { tag } { struct-faulty-nesting }
+ }
+ %put the obj number of link annot in the kid entry:
+ \@@_struct_kid_link_gput_right:nx
+ {
+ \l_@@_struct_stack_parent_tmp_tl
+ }
+ {
+ \pdfannot_link_ref_last:
+ }
+ % add the parent obj number to the parent tree:
+ \@@_parenttree_add_objr:nn
+ {
+ \int_use:N\c@g_@@_parenttree_obj_int
+ }
+ {
+ \prop_item:cn
+ { g_@@_struct_\l_@@_struct_stack_parent_tmp_tl _prop }
+ { objref }
+ }
+ % increase the int:
+ \stepcounter{ g_@@_parenttree_obj_int }
+ }
+ }
+
+%</struct>
+
+% \end{macrocode}
+% \begin{macrocode}
+%<*attr>
+\ProvidesExplPackage {tagpdf-attr-code} {2021/02/23} {0.80}
+ {part of tagpdf - code related to attributes and attribute classes}
+
+% the obj is written in tagpdf-tree-code.
+
+\seq_new:N \g_@@_attr_class_used_seq
+\prop_new:N \g_@@_attr_objref_prop %will contain obj num of used attributes
+
+\prop_new:N \g_@@_attr_entries_prop
+\tl_new:N \g_@@_attr_class_content_tl
+\tl_new:N \l_@@_attr_objtmp_tl
+\tl_new:N \l_@@_attr_value_tl
+
+
+\cs_new_protected:Nn \@@_attr_new_entry:nn %#1:name, #2: content
+ {
+ \prop_gput:Nnn \g_@@_attr_entries_prop
+ {#1}{#2}
+ }
+
+\keys_define:nn { @@ / setup }
+ {
+ newattribute .code:n =
+ {
+ \@@_attr_new_entry:nn #1
+ }
+ }
+
+
+% the key for the structure:
+\keys_define:nn { @@ / struct }
+ {
+ attribute-class .code:n =
+ {
+ \clist_set:No \l_tmpa_clist { #1 }
+ \seq_set_from_clist:NN \l_tmpa_seq \l_tmpa_clist
+ \seq_map_inline:Nn \l_tmpa_seq
+ {
+ \prop_if_in:NnF \g_@@_attr_entries_prop {##1}
+ {
+ \msg_error:nnn { tag } { attr-unknown } { ##1 }
+ }
+ \seq_gput_left:Nn\g_@@_attr_class_used_seq { ##1}
+ }
+ \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq
+ {
+ /##1
+ }
+ \tl_set:Nx \l_tmpa_tl
+ {
+ \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{[}
+ \seq_use:Nn \l_tmpb_seq { \c_space_tl }
+ \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{]}
+ }
+ \int_compare:nT { \seq_count:N \l_tmpa_seq > 0 }
+ {
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { C }
+ { \l_tmpa_tl }
+ %\prop_show:c { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ }
+ }
+ }
+
+\keys_define:nn { @@ / struct }
+ {
+ attribute .code:n = % A property (attribute, value currently a dictionary)
+ {
+ \clist_set:No \l_tmpa_clist { #1 }
+ \seq_set_from_clist:NN \l_tmpa_seq \l_tmpa_clist
+ \tl_set:Nx \l_@@_attr_value_tl
+ {
+ \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{[}%]
+ }
+ \seq_map_inline:Nn \l_tmpa_seq
+ {
+ \prop_if_in:NnF \g_@@_attr_entries_prop {##1}
+ {
+ \msg_error:nnn { tag } { attr-unknown } { ##1 }
+ }
+ \prop_if_in:NnF \g_@@_attr_objref_prop {##1}
+ {%\prop_show:N \g_@@_attr_entries_prop
+ \pdf_object_unnamed_write:nx
+ { dict }
+ {
+ \prop_item:Nn\g_@@_attr_entries_prop {##1}
+ }
+ \prop_gput:Nnx \g_@@_attr_objref_prop {##1} {\pdf_object_ref_last:}
+ }
+ \tl_put_right:Nx \l_@@_attr_value_tl
+ {
+ \c_space_tl
+ \prop_item:Nn \g_@@_attr_objref_prop {##1}
+ }
+ % \tl_show:N \l_@@_attr_value_tl
+ }
+ \tl_put_right:Nx \l_@@_attr_value_tl
+ { %[
+ \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{]}%
+ }
+ % \tl_show:N \l_@@_attr_value_tl
+ \@@_prop_gput:cnx
+ { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
+ { A }
+ { \l_@@_attr_value_tl }
+ },
+ }
+%</attr>
+% \end{macrocode}