% \iffalse meta-comment % %% File: tagpdf-struct.dtx % % Copyright (C) 2019-2021 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. %<*driver> \RequirePackage{pdfmanagement-testphase} \DeclareDocumentMetadata{} \makeatletter \declare@file@substitution{doc.sty}{doc-v3beta.sty} \makeatother \documentclass{l3doc} \usepackage{array,booktabs,caption} \hypersetup{pdfauthor=Ulrike Fischer, pdftitle=tagpdf-tree module (tagpdf)} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % \title{^^A % The \pkg{tagpdf-struct} module\\ Commands to create the structure ^^A % \\ Part of the tagpdf package % } % % \author{^^A % Ulrike Fischer\thanks % {^^A % E-mail: % \href{mailto:fischer@troubleshooting-tex.de} % {fischer@troubleshooting-tex.de}^^A % }^^A % } % % \date{Version 0.91, released 2021-07-03} % \maketitle % \begin{documentation} % \section{Public Commands} % \begin{function}{\tag_struct_begin:n,\tag_struct_end:} % \begin{syntax} % \cs{tag_struct_begin:n}\Arg{key-values}\\ % \cs{tag_struct_end:} % \end{syntax} % These commands start and end a new structure. % They don't start a group. They set all their values globally. % \end{function} % \begin{function}{\tag_struct_use:n} % \begin{syntax} % \cs{tag_struct_use:n}\Arg{label} % \end{syntax} % These commands insert a structure previously stashed away as kid % into the currently active structure. % A structure should be used only once, % if the structure already has a parent a warning is issued. % \end{function} % The following two functions are used to add annotations. They must be used % together and with care to get the same numbers. Perhaps some improvements are needed % here. % \begin{function}{\tag_struct_insert_annot:nn} % \begin{syntax} % \cs{tag_struct_insert_annot:nn}\Arg{object reference}\Arg{struct parent number} % \end{syntax} % This inserts an annotation in the structure. \meta{object reference} % is there reference to the annotation. \meta{struct parent number} % should be the same number as had been inserted with \cs{tag_struct_parent_int:} % as |StructParent| value to the dictionary of the annotion. % The command will increase the value of the counter % used by \cs{tag_struct_parent_int:}. % \end{function} % \begin{function}{\tag_struct_parent_int:} % \begin{syntax} % \cs{tag_struct_parent_int:} % \end{syntax} % This gives back the next free /StructParent number (assuming that it is % together with \cs{tag_struct_insert_annot:nn} which will increase the number. % \end{function} % \section{Public keys} % \subsection{Keys for the structure commands} % \begin{function}{tag} % This is required. The value of the key is normally one of the % standard types listed in section \ref{sec:new-tag}. % It is possible to setup new tags/types. % The value can also be of the form |type/NS|, where |NS| is the % shorthand of a declared name space. % Currently the names spaces |pdf|, |pdf2|, |mathml| and |user| are defined. % This allows to use a different name space than % the one connected by default to the tag. But normally this should not be needed. % \end{function} % \begin{function}{stash} % Normally a new structure inserts itself as a kid % into the currently active structure. This key prohibits this. % The structure is nevertheless from now on % \enquote{the current active structure} % and parent for following marked content and structures. % \end{function} % \begin{function}{label} % This key sets a label by which % one can use the structure later in another structure. % Internally the label name will start with \texttt{tagpdfstruct-}. % \end{function} % \begin{function}{title,title-o} % This keys allows to set the dictionary entry % \texttt{/Title} in the structure object. % The value is handled as verbatim string and hex encoded. % Commands are not expanded. |title-o| will expand the value once. % \end{function} % % \begin{function}{alttext,alttext-o} % This key inserts an \texttt{/Alt} value in the dictionary of structure object. % The value is handled as verbatim string and hex encoded. % |alttext-o| will expand the value once. % \end{function} % \begin{function}{actualtext,actualtext-o} % This key inserts an \texttt{/ActualText} value in the dictionary of structure object. % The value is handled as verbatim string and hex encoded. % |actualtext-o| will expand the value once. % \end{function} % \begin{function}{lang} % This key allows to set the language for a structure element. The value should be a bcp-identifier, % e.g. |de-De|. % \end{function} % \begin{function}{ref} % This key allows to add references to other structure elements, % it adds the |/Ref| array to the structure. % The value should be a comma separated list of structure labels % set with the |label| key. e.g. |ref={label1,label2}|. % \end{function} % \begin{function}{E} % This key sets the |/E| key, the expanded form of an % abbreviation or an acronym % (I couldn't think of a better name, so I sticked to E). % \end{function} % \begin{function}{AF,AFinline,AFinline-o} % \begin{syntax} % AF = \meta{object name}\\ % AF-inline = \meta{text content} % \end{syntax} % These keys allows to reference an associated file in the structure element. % The value \meta{object name} should be the name of an object pointing % to the \texttt{/Filespec} dictionary as expected by % |\pdf_object_ref:n| from a current \texttt{l3kernel}. % % The value |AF-inline| is some text, % which is embedded in the PDF as a text file with mime type text/plain. % |AF-inline-o| is like |AF-inline| but expands the value once. % % Future versions will perhaps extend this to more mime types, but it is % still a research task to find out what is really needed. % % \end{function} % % \begin{function}{attribute} % This key takes as argument a comma list of attribute names % (use braces to protect the commas from the external key-val parser) % and allows to add one or more attribute dictionary entries in % the structure object. As an example % \begin{verbatim} % \tagstructbegin{tag=TH,attribute= TH-row} % \end{verbatim} % Attribute names and their content must be declared first in \cs{tagpdfsetup}. % % \end{function} % % \begin{function}{attribute-class} % This key takes as argument a comma list of attribute class names % (use braces to protect the commas from the external key-val parser) % and allows to add one or more attribute classes to the structure object. % % Attribute class names and their content % must be declared first in \cs{tagpdfsetup}. % \end{function} % \subsection{Setup keys} % \begin{function}{newattribute} % \begin{syntax} % newattribute = \Arg{name}\Arg{Content} % \end{syntax} % This key can be used in the setup command \cs{tagpdfsetup} and allow to declare a % new attribute, which can be used as attribute or attribute class. % The value are two brace groups, the first contains the name, the second the content. % \begin{verbatim} % \tagpdfsetup % { % newattribute = % {TH-col}{/O /Table /Scope /Column}, % newattribute = % {TH-row}{/O /Table /Scope /Row}, % } % \end{verbatim} % % \end{function} % \end{documentation} % \begin{implementation} % \begin{macrocode} %<@@=tag> %<*header> \ProvidesExplPackage {tagpdf-struct-code} {2021-07-03} {0.91} {part of tagpdf - code related to storing structure} % % \end{macrocode} % \section{Variables} % \begin{variable}{\c@g_@@_struct_abs_int} % Every structure will have a unique, absolute number. % I will use a latex counter for the structure count % to have a chance to avoid double structures in align etc. % % \begin{macrocode} %<*package> \newcounter { g_@@_struct_abs_int } \int_gzero:N \c@g_@@_struct_abs_int % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_struct_objR_seq} % a sequence to store mapping between the % structure number and the object number. % We assume that structure numbers are assign % consecutively and so the index of the seq can be used. % A seq allows easy mapping over the structures. % \begin{macrocode} \@@_seq_new:N \g_@@_struct_objR_seq % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_struct_stack_seq} % A stack sequence for the structure stack. % When a sequence is opened it's number is put on the stack. % \begin{macrocode} \seq_new:N \g_@@_struct_stack_seq \seq_gpush:Nn \g_@@_struct_stack_seq {0} % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_struct_tag_stack_seq} % We will perhaps also need the tags. While it is possible to get them from the % numbered stack, lets build a tag stack too. % \begin{macrocode} \seq_new:N \g_@@_struct_tag_stack_seq \seq_gpush:Nn \g_@@_struct_tag_stack_seq {Root} % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_struct_stack_current_tl,\l_@@_struct_stack_parent_tmpa_tl} % The global variable will hold the current structure number. % The local temporary variable will hold the parent when we fetch it from the stack. % \begin{macrocode} \tl_new:N \g_@@_struct_stack_current_tl \tl_new:N \l_@@_struct_stack_parent_tmpa_tl % \end{macrocode} % \end{variable} % % I will 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 and % |\g_@@_struct_N_prop|, $N \geq =1$ for the other. % % This creates quite a number of properties, so perhaps we will have to % do this more efficiently in the future. % % All properties have at least the keys % \begin{description} % \item[Type] StructTreeRoot or StructElem % \end{description} % 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) % \begin{variable} % { % \c_@@_struct_StructTreeRoot_entries_seq, % \c_@@_struct_StructElem_entries_seq % } % These seq contain the keys we support in the two object types. % They are currently no longer used, but are provided as documentation and % for potential future checks. % They should be adapted if there are changes in the PDF format. % \begin{macrocode} \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, Namespaces } \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, %attribute revision number, irrelevant for us as we % don't update/change existing PDF and (probably) % deprecated in PDF 2.0 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 } % \end{macrocode} % \end{variable} % % \subsection{Variables used by the keys} % \begin{variable}{\g_@@_struct_tag_tl,\g_@@_struct_tag_NS_tl} % Use by the tag key to store the tag and the namespace. % \begin{macrocode} \tl_new:N \g_@@_struct_tag_tl \tl_new:N \g_@@_struct_tag_NS_tl % \end{macrocode} % \end{variable} % \begin{variable}{\l_@@_struct_key_label_tl} % This will hold the label value. % \begin{macrocode} \tl_new:N \l_@@_struct_key_label_tl % \end{macrocode} % \end{variable} % \begin{variable}{\l_@@_struct_elem_stash_bool} % This will keep track of the stash status % \begin{macrocode} \bool_new:N \l_@@_struct_elem_stash_bool % \end{macrocode} % \end{variable} % \section{Commands} % % The properties must be in some places handled expandably. % So I need an output handler for each prop, to get expandable output % see \url{https://tex.stackexchange.com/questions/424208}. % There is probably room here for a more efficient implementation. % TODO check if this can now be implemented with the pdfdict commands. % The property contains currently non pdf keys, but e.g. object numbers are % perhaps no longer needed as we have named object anyway. % % \begin{macro}{\@@_struct_output_prop_aux:nn,\@@_new_output_prop_handler:n} % \begin{macrocode} \cs_new:Npn \@@_struct_output_prop_aux:nn #1 #2 %#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_protected:Npn \@@_new_output_prop_handler:n #1 { \cs_new:cn { @@_struct_output_prop_#1:n } { \@@_struct_output_prop_aux:nn {#1}{##1} } } % \end{macrocode} % \end{macro} % % \subsection{Initialization of the StructTreeRoot} % The first structure element, the StructTreeRoot is special, so % created manually. The underlying object is |@@/struct/0| which is currently % created in the tree code (TODO move it here). % The |ParentTree| and |RoleMap| entries are added at begin document % in the tree code as they refer to object which are setup in other parts of the % code. This avoid timing issues. % % \begin{macrocode} \tl_gset:Nn \g_@@_struct_stack_current_tl {0} % \end{macrocode} % % \begin{variable}{g_@@_struct_0_prop,g_@@_struct_kids_0_seq} % \begin{macrocode} \@@_prop_new:c { g_@@_struct_0_prop } \@@_new_output_prop_handler:n {0} \@@_seq_new:c { g_@@_struct_kids_0_seq } \@@_prop_gput:cnn { g_@@_struct_0_prop } { Type } { /StructTreeRoot } % \end{macrocode} % Namespaces are pdf 2.0 but it doesn't harm % to have an empty entry. We could add a test, but if the code moves into % the kernel, timing could get tricky. % \begin{macrocode} \@@_prop_gput:cnx { g_@@_struct_0_prop } { Namespaces } { \pdf_object_ref:n { @@/tree/namespaces } } % \end{macrocode} % \end{variable} % \subsection{Handlings kids} % Commands to store the kids. Kids in a structure can be a reference to a mc-chunk, % an object reference to another structure element, or a object reference to an % annotation (through an OBJR object). % \begin{macro}{\@@_struct_kid_mc_gput_right:nn} % The command to store an mc-chunk, this is a dictionary of type MCR. % It would be possible to write out the content directly as unnamed object % and to store only the object reference, but probably this would be slower, % and the PDF is more readable like this. % The code doesn't try to avoid the use of the /Pg key by checking page numbers. % That imho only slows down without much gain. % \begin{macrocode} \cs_new_protected:Npn \@@_struct_kid_mc_gput_right:nn #1 #2 %#1 structure num, #2 MCID absnum% { \@@_seq_gput_right:cx { g_@@_struct_kids_#1_seq } { << /Type \c_space_tl /MCR \c_space_tl /Pg \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} >> } } % \end{macrocode} % \end{macro} % \begin{macro} % { % \@@_struct_kid_struct_gput_right:nn,\@@_struct_kid_struct_gput_right:xx % } % This commands adds a structure as kid. We only need to record the object % reference in the sequence. % \begin{macrocode} \cs_new_protected:Npn\@@_struct_kid_struct_gput_right:nn #1 #2 %#1 num of parent struct, #2 kid struct { \@@_seq_gput_right:cx { g_@@_struct_kids_#1_seq } { \pdf_object_ref:n { @@/struct/#2 } } } \cs_generate_variant:Nn \@@_struct_kid_struct_gput_right:nn {xx} % \end{macrocode} % \end{macro} % \begin{macro} % {\@@_struct_kid_OBJR_gput_right:nn,\@@_struct_kid_OBJR_gput_right:xx} % At last the command to add an OBJR object. This has to write an object first. % The first argument is the number of the parent structure, the second the % (expanded) object reference of the annotation. % \begin{macrocode} \cs_new_protected:Npn\@@_struct_kid_OBJR_gput_right:nn #1 #2 %#1 num of parent struct, %#2 obj reference { \pdf_object_unnamed_write:nn { dict } { /Type/OBJR/Obj~#2 } \@@_seq_gput_right:cx { g_@@_struct_kids_#1_seq } { \pdf_object_ref_last: } } \cs_generate_variant:Nn\@@_struct_kid_OBJR_gput_right:nn { xx } % \end{macrocode} % \end{macro} % \begin{macro} % {\@@_struct_exchange_kid_command:N, \@@_struct_exchange_kid_command:c} % In luamode it can happen that a single kid in a structure is split at a page % break into two or more mcid. In this case the lua code has to convert % put the dictionary of the kid into an array. See issue 13 at tagpdf repo. % We exchange the dummy command for the kids to mark this case. % \begin{macrocode} \cs_new_protected:Npn\@@_struct_exchange_kid_command:N #1 %#1 = seq var { \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 } % \end{macrocode} % \end{macro} % \begin{macro}{ \@@_struct_fill_kid_key:n } % This command adds the kid info to the K entry. In lua mode the % content contains commands which are expanded later. The argument is the structure % number. % % \begin{macrocode} \cs_new_protected:Npn \@@_struct_fill_kid_key:n #1 %#1 is the struct num { \int_case:nnF { \seq_count:c { g_@@_struct_kids_#1_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_#1_seq} } \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K} { \seq_item:cn { g_@@_struct_kids_#1_seq } {1} } } % } { %many kids, use an array \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K} { [ \seq_use:cn { g_@@_struct_kids_#1_seq } { \c_space_tl } ] } } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_struct_get_dict_content:nN} % This maps the dictionary content of a structure into a tl-var. % Basically it does what |\pdfdict_use:n| does. % TODO!! this looks over-complicated. Check if it can be done with pdfdict now. % \begin{macrocode} \cs_new_protected:Npn \@@_struct_get_dict_content:nN #1 #2 %#1: stucture num { \tl_clear:N #2 \seq_map_inline:cn { c_@@_struct_ \int_compare:nNnTF{#1}={0}{StructTreeRoot}{StructElem} _entries_seq } { \tl_put_right:Nx #2 { \prop_if_in:cnT { g_@@_struct_#1_prop } { ##1 } { \c_space_tl/##1~\prop_item:cn{ g_@@_struct_#1_prop } { ##1 } } } } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_struct_write_obj:n} % This writes out the structure object. % This is done in the finish code, in the tree module and % guarded by the tree boolean. % \begin{macrocode} \cs_new_protected:Npn \@@_struct_write_obj:n #1 % #1 is the struct num { \pdf_object_if_exist:nTF { @@/struct/#1 } { \@@_struct_fill_kid_key:n { #1 } \@@_struct_get_dict_content:nN { #1 } \l_@@_tmpa_tl \exp_args:Nx \pdf_object_write:nx { @@/struct/#1 } { \l_@@_tmpa_tl } } { \msg_error:nnn { tag } { struct-no-objnum } { #1} } } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_struct_insert_annot:nn} % This is the command to insert an annotation into the structure. % It can probably be used for xform too. % % Annotations used as structure content must % \begin{enumerate} % \item add a StructParent integer to their dictionary % \item push the object reference as OBJR object in the structure % \item Add a Structparent/obj-nr reference to the parent tree. % \end{enumerate} % For a link this looks like this % \begin{verbatim} % \tag_struct_begin:n { tag=Link } % \tag_mc_begin:n { tag=Link } % (1) \pdfannot_dict_put:nnx % { link/URI } % { StructParent } % { \int_use:N\c@g_@@_parenttree_obj_int } % link text % (2+3) \@@_struct_insert_annot:nn {obj ref}{parent num} % \tag_mc_end: % \tag_struct_end: % \end{verbatim} % \begin{macrocode} \cs_new_protected:Npn \@@_struct_insert_annot:nn #1 #2 %#1 object reference to the annotation/xform %#2 structparent number { \bool_if:NT \g_@@_active_struct_bool { %get the number of the parent structure: \seq_get:NNF \g_@@_struct_stack_seq \l_@@_struct_stack_parent_tmpa_tl { \msg_error:nn { tag } { struct-faulty-nesting } } %put the obj number of the annot in the kid entry, this also creates %the OBJR object \@@_struct_kid_OBJR_gput_right:xx { \l_@@_struct_stack_parent_tmpa_tl } { #1 % } % add the parent obj number to the parent tree: \exp_args:Nnx \@@_parenttree_add_objr:nn { #2 } { \pdf_object_ref:e { @@/struct/\l_@@_struct_stack_parent_tmpa_tl } } % increase the int: \stepcounter{ g_@@_parenttree_obj_int } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_get_data_struct_tag:} % this command allows \cs{tag_get:n} to get the current % structure tag with the keyword |struct_tag|. We will need to handle nesting % \begin{macrocode} \cs_new:Npn \@@_get_data_struct_tag: { \exp_args:Ne \tl_tail:n { \prop_item:cn {g_@@_struct_\g__tag_struct_stack_current_tl _prop}{S} } } % \end{macrocode} % \end{macro} % % \section{Keys} % This are the keys for the user commands. % we store the tag in a variable. But we should be careful, it is only reliable % at the begin. % \begin{macro} % { % label,stash,tag, % title,title-o, % alttext,alttext-o, % actualtext,actualtext-o % lang,ref,E % } % \begin{macrocode} \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 { \seq_set_split:Nne \l_@@_tmpa_seq { / } {#1/\prop_item:Nn\g__tag_role_tags_NS_prop{#1}} \tl_gset:Nx \g_@@_struct_tag_tl { \seq_item:Nn\l_@@_tmpa_seq {1} } \tl_gset:Nx \g_@@_struct_tag_NS_tl { \seq_item:Nn\l_@@_tmpa_seq {2} } \@@_check_structure_tag:N \g_@@_struct_tag_tl \@@_prop_gput:cnx { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop } { S } { \pdf_name_from_unicode_e:n{ \g_@@_struct_tag_tl} } % \prop_get:NVNT \g_@@_role_NS_prop\g_@@_struct_tag_NS_tl\l_@@_tmpa_tl { \@@_prop_gput:cnx { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop } { NS } { \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> } }, } % \end{macrocode} % \end{macro} % \begin{macro}{AF,AFinline,AFinline-o} % keys for the AF keys (associated files). They use commands from l3pdffile! % The stream variants use txt as extension to get the mimetype. % TODO: check if this should be configurable. For math we will perhaps need another % extension. % \begin{macrocode} \keys_define:nn { @@ / struct } { AF .code:n = % AF 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: \pdf_object_if_exist:eF {@@/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: \pdf_object_if_exist:eF {@@/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: } } % \end{macrocode} % \end{macro} % % \section{User commands} % % \begin{macro}{\tag_struct_begin:n,\tag_struct_end:} % \begin{macrocode} \cs_new_protected:Npn \tag_struct_begin:n #1 %#1 key-val { \@@_check_if_active_struct:T { \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} \exp_args:Ne \pdf_object_new:nn { @@/struct/\int_eval:n { \c@g_@@_struct_abs_int } } { dict } \@@_prop_gput:cno { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop } { Type } { /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_tmpa_tl { \msg_error:nn { tag } { struct-faulty-nesting } } \seq_gpush:NV \g_@@_struct_stack_seq \c@g_@@_struct_abs_int \seq_gpush:NV \g_@@_struct_tag_stack_seq \g_@@_struct_tag_tl \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 } { \pdf_object_ref:e { @@/struct/\l_@@_struct_stack_parent_tmpa_tl } } %record this structure as kid: %\tl_show:N \g_@@_struct_stack_current_tl %\tl_show:N \l_@@_struct_stack_parent_tmpa_tl \@@_struct_kid_struct_gput_right:xx { \l_@@_struct_stack_parent_tmpa_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_tmpa_tl _seq} } %\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop } %\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmpa_tl _seq} \group_end: } } \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 \@@_check_if_active_struct:T { \seq_gpop:NN \g_@@_struct_tag_stack_seq \l_@@_tmpa_tl \seq_gpop:NNTF \g_@@_struct_stack_seq \l_@@_tmpa_tl { \@@_check_info_closing_struct:o { \g_@@_struct_stack_current_tl } } { \@@_check_no_open_struct: } % 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_struct: } \seq_get:NNT \g_@@_struct_tag_stack_seq \l_@@_tmpa_tl { \tl_gset:NV \g_@@_struct_tag_tl \l_@@_tmpa_tl } } } % \end{macrocode} % \end{macro} % \begin{macro}{\tag_struct_use:n} % This command allows to use a stashed structure in another place. % TODO: decide how it should be guarded. Probably by the struct-check. % \begin{macrocode} \cs_new_protected:Nn \tag_struct_use:n %#1 is the label { \@@_check_if_active_struct:T { \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:xx { \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 } { \pdf_object_ref:e { @@/struct/\g_@@_struct_stack_current_tl } } } { \msg_warning:nnn{ tag }{struct-label-unknown}{#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro} % { % \tag_struct_insert_annot:nn, % \tag_struct_insert_annot:xx % } % \begin{macro}[EXP] % { % \tag_struct_parent_int: % } % This are the user command to insert annotations. They must be used % together to get the numbers right. They use a counter to the % |StructParent| and \cs{tag_struct_insert_annot:nn} increases the % counter given back by \cs{tag_struct_parent_int:}. % % It must be used together with |\tag_struct_parent_int:| to insert an % annotation. % TODO: decide how it should be guarded if tagging is deactivated. % \begin{macrocode} \cs_new_protected:Npn \tag_struct_insert_annot:nn #1 #2 %#1 should be an object reference %#2 struct parent num { \@@_check_if_active_struct:T { \@@_struct_insert_annot:nn {#1}{#2} } } \cs_generate_variant:Nn \tag_struct_insert_annot:nn {xx} \cs_new:Npn \tag_struct_parent_int: {\int_use:c { c@g_@@_parenttree_obj_int }} % % \end{macrocode} % \end{macro} % \end{macro} % \section{Attributes and attribute classes} % \begin{macrocode} %<*header> \ProvidesExplPackage {tagpdf-attr-code} {2021-07-03} {0.91} {part of tagpdf - code related to attributes and attribute classes} % % \end{macrocode} % \subsection{Variables} % \begin{variable} % { % ,\g_@@_attr_entries_prop % ,\g_@@_attr_class_used_seq % ,\g_@@_attr_objref_prop % ,\l_@@_attr_value_tl % } % |\g_@@_attr_entries_prop| will store attribute names and their dictionary content.\\ % |\g_@@_attr_class_used_seq| will hold the attributes which have been used as % class name. % |\l_@@_attr_value_tl| is used to build the attribute array or key. % Everytime an attribute is used for the first time, and object is created % with its content, the name-object reference relation is stored in % |\g_@@_attr_objref_prop| % \begin{macrocode} %<*package> \prop_new:N \g_@@_attr_entries_prop \seq_new:N \g_@@_attr_class_used_seq \tl_new:N \l_@@_attr_value_tl \prop_new:N \g_@@_attr_objref_prop %will contain obj num of used attributes % \end{macrocode} % \end{variable} % \subsection{Commands and keys} % \begin{macro}{\@@_attr_new_entry:nn,newattribute} % This allows to define attributes. Defined attributes % are stored in a global property. |newattribute| expects % two brace group, the name and the content. The content typically % needs an |/O| key for the owner. An example look like % this. % \begin{verbatim} % \tagpdfsetup % { % newattribute = % {TH-col}{/O /Table /Scope /Column}, % newattribute = % {TH-row}{/O /Table /Scope /Row}, % } % \end{verbatim} % \begin{macrocode} \cs_new_protected:Npn \@@_attr_new_entry:nn #1 #2 %#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 } } % \end{macrocode} % \end{macro} % \begin{macro}{attribute-class} % attribute-class has to store the used attribute names so that % they can be added to the ClassMap later. % \begin{macrocode} \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 } } } } % \end{macrocode} % \end{macro} % \begin{macro}{attribute} % \begin{macrocode} \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 } }, } % % \end{macrocode} % \end{macro} % \end{implementation}