% \iffalse meta-comment
%
%% File: tagpdf-struct.dtx
%
% Copyright (C) 2019-2024 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/latex3/tagpdf
%
% for those people who are interested.
%<*driver>
\DocumentMetadata{}
\documentclass{l3doc}
\usepackage{array,booktabs,caption}
\hypersetup{pdfauthor=Ulrike Fischer,
pdftitle=tagpdf-tree module (tagpdf)}
\input{tagpdf-docelements}
\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.99j, released 2024-11-22}
% \maketitle
% \begin{documentation}
% \section{Public Commands}
% \begin{function}{\tag_struct_begin:n,\tag_struct_end:,\tag_struct_end:n}
% \begin{syntax}
% \cs{tag_struct_begin:n}\Arg{key-values}\\
% \cs{tag_struct_end:}\\
% \cs{tag_struct_end:n}\Arg{tag}
% \end{syntax}
% These commands start and end a new structure.
% They don't start a group. They set all their values globally.
% \cs{tag_struct_end:n} does nothing special normally (apart from
% swallowing its argument, but if \texttt{tagpdf-debug} is loaded,
% it will check if the \Arg{tag} (after expansion)
% is identical to the current structure on the stack. The tag is not role mapped!
% \end{function}
% \begin{function}{\tag_struct_use:n,\tag_struct_use_num:n}
% \begin{syntax}
% \cs{tag_struct_use:n}\Arg{label}\\
% \cs{tag_struct_use_num:n}\Arg{structure number}
% \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}
% \begin{function}{\tag_struct_object_ref:n,\tag_struct_object_ref:e}
% \begin{syntax}
% \cs{tag_struct_object_ref:n}\Arg{struct number}
% \end{syntax}
% This is a small wrapper around |\pdf_object_ref:n| to retrieve the
% object reference of the structure with the number \meta{struct number}.
% This number can be retrieved and stored for the current structure
% for example with \cs{tag_get:n}\Arg{struct_num}. Be aware that it can only
% be used if the structure has already been created and that it doesn't check
% if the object actually exists!
% \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 annotation.
% 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}
%
% \begin{function}{\tag_struct_gput:nnn}
% \begin{syntax}
% \cs{tag_struct_gput:nnn}\Arg{structure number}\Arg{keyword}\Arg{value}
% \end{syntax}
% This is a command that allows to update the data of a structure.
% This often can't done simply by replacing the value, as we have to
% preserve and extend existing content. We use therefore dedicated functions
% adjusted to the key in question.
% The first argument is the number of the structure,
% the second a keyword referring to a function,
% the third the value. Currently the only keyword is \texttt{ref} which updates
% the Ref key (an array)
% \end{function}
%
% \begin{function}{\tag_struct_gput_ref:nnn}
% \begin{syntax}
% \cs{tag_struct_gput_ref:nnn}\Arg{structure number}\Arg{keyword}\Arg{value}
% \end{syntax}
% This is an user interface to add a Ref key to an
% existing structure. The target structure doesn't have to exist yet
% but can be addressed by label, destname or even num.
% \meta{keyword} is currently either \texttt{label}, \texttt{dest}
% or \texttt{num}. The value is then either a label name, the name of a destination
% or a structure number.
% \end{function}
%
% \section{Public keys}
% \subsection{Keys for the structure commands}
% \begin{structkeydecl}{tag}
% This is required. The value of the key is normally one of the
% standard types listed in the main tagpdf documentation.
% 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{structkeydecl}
% \begin{structkeydecl}{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{structkeydecl}
% \begin{structkeydecl}{label}
% This key sets a label by which
% one can refer to the structure. It is e.g.
% used by \cs{tag_struct_use:n} (where a real label is actually not
% needed as you can only use structures already defined), and by the
% |ref| key (which can refer to future structures).
% Internally the label name will start with \texttt{tagpdfstruct-} and it stores
% the two attributes |tagstruct| (the structure number) and |tagstructobj| (the
% object reference).
% \end{structkeydecl}
% \begin{structkeydecl}{parent}
% By default a structure is added as kid to the currently active structure.
% With the parent key one can choose another parent. The value is a structure number which
% must refer to an already existing, previously created structure. Such a structure
% number can for example be have been stored with \cs{tag_get:n}, but one can also use
% a label on the parent structure and then use
% \cs{property_ref:nn}|{tagpdfstruct-label}{tagstruct}| to retrieve it.
% \end{structkeydecl}
% \begin{structkeydecl}{firstkey}
% If this key is used the structure is added at the left of the kids of
% the parent structure (if the structure is not stashed).
% This means that it will be the first kid of the structure
% (unless some later structure uses the key too).
% \end{structkeydecl}
% \begin{structkeydecl}{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{structkeydecl}
%
% \begin{structkeydecl}{alt}
% This key inserts an \texttt{/Alt} value in the dictionary of structure object.
% The value is handled as verbatim string and hex encoded.
% The value will be expanded first once. If it is empty, nothing will happen.
% \end{structkeydecl}
% \begin{structkeydecl}{actualtext}
% This key inserts an \texttt{/ActualText} value in the dictionary of structure object.
% The value is handled as verbatim string and hex encoded.
% The value will be expanded first once. If it is empty, nothing will happen.
% \end{structkeydecl}
% \begin{structkeydecl}{lang}
% This key allows to set the language for a structure element. The value should be a bcp-identifier,
% e.g. |de-De|.
% \end{structkeydecl}
% \begin{structkeydecl}{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{structkeydecl}
% \begin{structkeydecl}{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{structkeydecl}
% \begin{structkeydecl}{AF,AFref,
% AFinline,AFinline-o,texsource,mathml}
% These keys handle associated files in the structure element.
%
% \begin{syntax}
% AF = \meta{object name}\\
% AFref = \meta{object reference}\\
% AF-inline = \meta{text content}\\
% \end{syntax}
%
% 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.
%
% |texsource| is a special variant of |AF-inline-o| which embeds the content
% as |.tex| source with the |/AFrelationship| key set to |/Source|.
% It also sets the |/Desc| key to a (currently) fix text.
%
% |mathml| is a special variant of |AF-inline-o| which embeds the content
% as |.xml| file with the |/AFrelationship| key set to |/Supplement|.
% It also sets the |/Desc| key to a (currently) fix text.
%
% The argument of |AF| is an object name referring an embedded file as declared
% for example with
% \cs{pdf_object_new:n} or with the l3pdffile module. |AF| expands its argument
% (this allows e.g. to use some variable for automatic numbering)
% and can be used more than once, to associate more than one file.
%
% The argument of |AFref| is an object reference to an embedded file
% or a variable expanding to such a object reference in the format
% as you would get e.g. from \cs{pdf_object_ref_last:} or \cs{pdf_object_ref:n}
% (and which is different for the various engines!). The key allows to make
% use of anonymous objects. Like |AF| the |AFref| key expands its argument
% and can be used more than once, to associate more than one file. \emph{It
% does not check if the reference is valid!}
%
% The inline keys can be used only once per structure. Additional calls are ignored.
% \end{structkeydecl}
%
% \begin{structkeydecl}{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{structkeydecl}
%
% \begin{structkeydecl}{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{structkeydecl}
%
% \subsection{Setup keys}
% \begin{function}{role/new-attribute (setup-key), newattribute (deprecated)}
% \begin{syntax}
% role/new-attribute = \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
% {
% role/new-attribute =
% {TH-col}{/O /Table /Scope /Column},
% role/new-attribute =
% {TH-row}{/O /Table /Scope /Row},
% }
% \end{verbatim}
%
% \end{function}
% \begin{setupkeydecl}{root-AF}
% \begin{syntax}
% root-AF = \meta{object name}
% \end{syntax}
% This key can be used in the setup command \cs{tagpdfsetup} and allows
% to add associated files to the root structure. Like |AF| it can be used more than
% once to add more than one file.
% \end{setupkeydecl}
% \end{documentation}
% \begin{implementation}
% \begin{macrocode}
%<@@=tag>
%<*header>
\ProvidesExplPackage {tagpdf-struct-code} {2024-11-22} {0.99j}
{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}
%\newcounter { g_@@_struct_abs_int }
%\int_gset:Nn \c@g_@@_struct_abs_int { 1 }
% \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}
%<*package>
\@@_seq_new:N \g_@@_struct_objR_seq
% \end{macrocode}
% \end{variable}
% \begin{variable}{\c_@@_struct_null_tl}
% In lua mode we have to test if the kids a null
% \begin{macrocode}
\tl_const:Nn\c_@@_struct_null_tl {null}
% \end{macrocode}
% \end{variable}
% \begin{variable}{\g_@@_struct_cont_mc_prop}
% in generic mode it can happen after
% a page break that we have to inject into a structure
% sequence an additional mc after. We will store this additional
% info in a property. The key is the absolute mc num, the value the pdf directory.
% \begin{macrocode}
\@@_prop_new:N \g_@@_struct_cont_mc_prop
% \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 {1}
% \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}{StructTreeRoot}}
% \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. It is already
% defined in \texttt{tagpdf-base}.
% 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_gset:Nn \g_@@_struct_stack_current_tl {\int_use:N\c@g_@@_struct_abs_int}
%<*package>
\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_1_prop| for the root and
% |\g_@@_struct_N_prop|, $N \geq =2$ 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,
AF %pdf 2.0
}
\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, % abbreviation
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,
% \l_@@_struct_roletag_tl,\g_@@_struct_roletag_NS_tl}
% Use by the tag key to store the tag and the namespace.
% The role tag variables will hold locally rolemapping info needed
% for the parent-child checks
% \begin{macrocode}
\tl_new:N \g_@@_struct_tag_tl
\tl_new:N \g_@@_struct_tag_NS_tl
\tl_new:N \l_@@_struct_roletag_tl
\tl_new:N \l_@@_struct_roletag_NS_tl
% \end{macrocode}
% \end{variable}
% \begin{variable}{\g_@@_struct_label_num_prop}
% This will hold for every structure label the associated
% structure number. The prop will allow to
% fill the /Ref key directly at the first compilation if the ref
% key is used.
% \begin{macrocode}
\prop_new_linked:N \g_@@_struct_label_num_prop
% \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}
%
% \begin{variable}{\l_@@_struct_addkid_tl}
% This decides if a structure kid is added at the left or right of the parent.
% The default is \texttt{right}.
% \begin{macrocode}
\tl_new:N \l_@@_struct_addkid_tl
\tl_set:Nn \l_@@_struct_addkid_tl {right}
% \end{macrocode}
% \end{variable}
% \subsection{Variables used by tagging code of basic elements}
%
% \begin{variable}{\g_@@_struct_dest_num_prop}
% This variable records for (some or all, not clear yet)
% destination names the related structure number to allow
% to reference them in a Ref. The key is the destination.
% It is currently used by the toc-tagging and sec-tagging code.
% \begin{macrocode}
%
%\prop_new_linked:N \g_@@_struct_dest_num_prop
%<*package>
% \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_struct_ref_by_dest_prop}
% This variable contains structures whose Ref key should be updated
% at the end to point to structured related with this destination.
% As this is probably need in other places too, it is not only a toc-variable.
% TODO: remove after 11/2024 release.
% \begin{macrocode}
\prop_new_linked:N \g_@@_struct_ref_by_dest_prop
% \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}
%
% \begin{macro}{\@@_struct_prop_gput:nnn}
% The structure props must be filled in various places.
% For this we use a common command which also takes care of the debug package:
% \begin{macrocode}
%<*package|debug>
%\cs_new_protected:Npn \@@_struct_prop_gput:nnn #1 #2 #3
%\cs_set_protected:Npn \@@_struct_prop_gput:nnn #1 #2 #3
{
\@@_prop_gput:cnn
{ g_@@_struct_#1_prop }{#2}{#3}
%\prop_gput:cnn { g_@@_struct_debug_#1_prop } {#2} {#3}
}
\cs_generate_variant:Nn \@@_struct_prop_gput:nnn {onn,nne,nee,nno}
%
% \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/1| 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}
%<*package>
\tl_gset:Nn \g_@@_struct_stack_current_tl {1}
% \end{macrocode}
% \begin{macro}{\@@_pdf_name_e:n}
% \begin{macrocode}
\cs_new:Npn \@@_pdf_name_e:n #1{\pdf_name_from_unicode_e:n{#1}}
%
% \end{macrocode}
% \end{macro}
%
% \begin{variable}{g_@@_struct_1_prop,g_@@_struct_kids_1_seq}
% \begin{macrocode}
%<*package>
\@@_prop_new:c { g_@@_struct_1_prop }
\@@_new_output_prop_handler:n {1}
\@@_seq_new:c { g_@@_struct_kids_1_seq }
\@@_struct_prop_gput:nne
{ 1 }
{ Type }
{ \pdf_name_from_unicode_e:n {StructTreeRoot} }
\@@_struct_prop_gput:nne
{ 1 }
{ S }
{ \pdf_name_from_unicode_e:n {StructTreeRoot} }
\@@_struct_prop_gput:nne
{ 1 }
{ rolemap }
{ {StructTreeRoot}{pdf} }
\@@_struct_prop_gput:nne
{ 1 }
{ parentrole }
{ {StructTreeRoot}{pdf} }
% \end{macrocode}
% Namespaces are pdf 2.0.
% If the code moves into the kernel, the setting must be probably delayed.
% \begin{macrocode}
\pdf_version_compare:NnF < {2.0}
{
\@@_struct_prop_gput:nne
{ 1 }
{ Namespaces }
{ \pdf_object_ref:n { @@/tree/namespaces } }
}
%
% \end{macrocode}
% In debug mode we have to copy the root manually as it is already setup:
% \begin{macrocode}
%\prop_new:c { g_@@_struct_debug_1_prop }
%\seq_new:c { g_@@_struct_debug_kids_1_seq }
%\prop_gset_eq:cc { g_@@_struct_debug_1_prop }{ g_@@_struct_1_prop }
%\prop_gremove:cn { g_@@_struct_debug_1_prop }{Namespaces}
% \end{macrocode}
% \end{variable}
%
% \subsection{Adding the /ID key}
% Every structure gets automatically an ID which is currently
% simply calculated from the structure number.
% \begin{macro}{\@@_struct_get_id:n}
% \begin{macrocode}
%<*package>
\cs_new:Npn \@@_struct_get_id:n #1 %#1=struct num
{
(
ID.
\prg_replicate:nn
{ \int_abs:n{\g_@@_tree_id_pad_int - \tl_count:e { \int_to_arabic:n { #1 } }} }
{ 0 }
\int_to_arabic:n { #1 }
)
}
% \end{macrocode}
% \end{macro}
%
% \subsection{Filling in the tag info}
% \begin{macro}{\@@_struct_set_tag_info:nnn }
% This adds or updates the tag info to a structure given by a number.
% We need also the original data, so we store both.
% \begin{macrocode}
\pdf_version_compare:NnTF < {2.0}
{
\cs_new_protected:Npn \@@_struct_set_tag_info:nnn #1 #2 #3
%#1 structure number, #2 tag, #3 NS
{
\@@_struct_prop_gput:nne
{ #1 }
{ S }
{ \pdf_name_from_unicode_e:n {#2} } %
}
}
{
\cs_new_protected:Npn \@@_struct_set_tag_info:nnn #1 #2 #3
{
\@@_struct_prop_gput:nne
{ #1 }
{ S }
{ \pdf_name_from_unicode_e:n {#2} } %
\prop_get:NnNT \g_@@_role_NS_prop {#3} \l_@@_get_tmpc_tl
{
\@@_struct_prop_gput:nne
{ #1 }
{ NS }
{ \l_@@_get_tmpc_tl } %
}
}
}
\cs_generate_variant:Nn \@@_struct_set_tag_info:nnn {eVV}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_struct_get_parentrole:nNN}
% We also need a way to get the tag info needed for parent child
% check from parent structures.
% \begin{macrocode}
\cs_new_protected:Npn \@@_struct_get_parentrole:nNN #1 #2 #3
%#1 struct num, #2 tlvar for tag , #3 tlvar for NS
{
\prop_get:cnNTF
{ g_@@_struct_#1_prop }
{ parentrole }
\l_@@_get_tmpc_tl
{
\tl_set:Ne #2{\exp_last_unbraced:NV\use_i:nn \l_@@_get_tmpc_tl}
\tl_set:Ne #3{\exp_last_unbraced:NV\use_ii:nn \l_@@_get_tmpc_tl}
}
{
\tl_clear:N#2
\tl_clear:N#3
}
}
\cs_generate_variant:Nn\@@_struct_get_parentrole:nNN {eNN}
% \end{macrocode}
% \end{macro}
% \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,\@@_struct_kid_mc_gput_right:ne}
% 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.
% In generic mode the page break code will perhaps to have to insert
% an additional mcid after an existing one. For this we use a property list
% At first an auxiliary to write the MCID dict. This should normally be expanded!
% \begin{macrocode}
\cs_new:Npn \@@_struct_mcid_dict:n #1 %#1 MCID absnum
{
<<
/Type \c_space_tl /MCR \c_space_tl
/Pg
\c_space_tl
\pdf_pageobject_ref:n { \property_ref:enn{mcid-#1}{tagabspage}{1} }
/MCID \c_space_tl \property_ref:enn{mcid-#1}{tagmcid}{1}
>>
}
%
% \end{macrocode}
% \begin{macrocode}
%<*package|debug>
%\cs_new_protected:Npn \@@_struct_kid_mc_gput_right:nn #1 #2
%\cs_set_protected:Npn \@@_struct_kid_mc_gput_right:nn #1 #2
%#1 structure num, #2 MCID absnum%
{
\@@_seq_gput_right:ce
{ g_@@_struct_kids_#1_seq }
{
\@@_struct_mcid_dict:n {#2}
}
% \seq_gput_right:cn
% { g_@@_struct_debug_kids_#1_seq }
% {
% MC~#2
% }
\@@_seq_gput_right:cn
{ g_@@_struct_kids_#1_seq }
{
\prop_item:Nn \g_@@_struct_cont_mc_prop {#2}
}
}
%\cs_generate_variant:Nn \@@_struct_kid_mc_gput_right:nn {ne}
% \end{macrocode}
% \end{macro}
% \begin{macro}
% {
% \@@_struct_kid_struct_gput_right:nn,\@@_struct_kid_struct_gput_right:ee
% }
% 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
%\cs_set_protected:Npn\@@_struct_kid_struct_gput_right:nn #1 #2
%%#1 num of parent struct, #2 kid struct
{
\@@_seq_gput_right:ce
{ g_@@_struct_kids_#1_seq }
{
\pdf_object_ref_indexed:nn { @@/struct }{ #2 }
}
% \seq_gput_right:cn
% { g_@@_struct_debug_kids_#1_seq }
% {
% Struct~#2
% }
}
%\cs_generate_variant:Nn \@@_struct_kid_struct_gput_right:nn {ee}
% \end{macrocode}
% \end{macro}
% \begin{macro}
% {
% \@@_struct_kid_struct_gput_left:nn,\@@_struct_kid_struct_gput_left:ee
% }
% This commands adds a structure as kid one the left, so as first
% kid. We only need to record the object reference in the sequence.
% \begin{macrocode}
%\cs_new_protected:Npn\@@_struct_kid_struct_gput_left:nn #1 #2
%\cs_set_protected:Npn\@@_struct_kid_struct_gput_left:nn #1 #2
%%#1 num of parent struct, #2 kid struct
{
\@@_seq_gput_left:ce
{ g_@@_struct_kids_#1_seq }
{
\pdf_object_ref_indexed:nn { @@/struct }{ #2 }
}
% \seq_gput_left:cn
% { g_@@_struct_debug_kids_#1_seq }
% {
% Struct~#2
% }
}
%\cs_generate_variant:Nn \@@_struct_kid_struct_gput_left:nn {ee}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}
% {\@@_struct_kid_OBJR_gput_right:nnn,\@@_struct_kid_OBJR_gput_right:eee}
% 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. The last argument is the page
% object reference
%
% \begin{macrocode}
%\cs_new_protected:Npn\@@_struct_kid_OBJR_gput_right:nnn #1 #2 #3
%
%
%\cs_set_protected:Npn\@@_struct_kid_OBJR_gput_right:nnn #1 #2 #3
%%#1 num of parent struct,#2 obj reference,#3 page object reference
{
\pdf_object_unnamed_write:nn
{ dict }
{
/Type/OBJR/Obj~#2/Pg~#3
}
\@@_seq_gput_right:ce
{ g_@@_struct_kids_#1_seq }
{
\pdf_object_ref_last:
}
% \seq_gput_right:ce
% { g_@@_struct_debug_kids_#1_seq }
% {
% OBJR~reference
% }
}
%
%<*package>
\cs_generate_variant:Nn\@@_struct_kid_OBJR_gput_right:nnn { eee }
% \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.
% Change 2024-03-19: don't use a regex - that is slow.
% \begin{macrocode}
\cs_new_protected:Npn\@@_struct_exchange_kid_command:N #1 %#1 = seq var
{
\seq_gpop_left:NN #1 \l_@@_tmpa_tl
\tl_replace_once:Nnn \l_@@_tmpa_tl
{\@@_mc_insert_mcid_kids:n}
{\@@_mc_insert_mcid_single_kids:n}
\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
{
\bool_if:NF\g_@@_mode_lua_bool
{
\seq_clear:N \l_@@_tmpa_seq
\seq_map_inline:cn { g_@@_struct_kids_#1_seq }
{ \seq_put_right:Ne \l_@@_tmpa_seq { ##1 } }
%\seq_show:c { g_@@_struct_kids_#1_seq }
%\seq_show:N \l_@@_tmpa_seq
\seq_remove_all:Nn \l_@@_tmpa_seq {}
%\seq_show:N \l_@@_tmpa_seq
\seq_gset_eq:cN { g_@@_struct_kids_#1_seq } \l_@@_tmpa_seq
}
\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:NTF\g_@@_mode_lua_bool
{
\@@_struct_exchange_kid_command:c
{g_@@_struct_kids_#1_seq}
% \end{macrocode}
% check if we get null
% \begin{macrocode}
\tl_set:Ne\l_@@_tmpa_tl
{\use:e{\seq_item:cn {g__tag_struct_kids_#1_seq} {1}}}
\tl_if_eq:NNF\l__tag_tmpa_tl \c_@@_struct_null_tl
{
\@@_struct_prop_gput:nne
{#1}
{K}
{
\seq_item:cn
{
g_@@_struct_kids_#1_seq
}
{1}
}
}
}
{
\@@_struct_prop_gput:nne
{#1}
{K}
{
\seq_item:cn
{
g_@@_struct_kids_#1_seq
}
{1}
}
}
} %
}
{ %many kids, use an array
\@@_struct_prop_gput:nne
{#1}
{K}
{
[
\seq_use:cn
{
g_@@_struct_kids_#1_seq
}
{
\c_space_tl
}
]
}
}
}
% \end{macrocode}
% \end{macro}
% \subsection{Output of the object}
% \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.
% This is used a lot so should be rather fast.
% \begin{macrocode}
\cs_new_protected:Npn \@@_struct_get_dict_content:nN #1 #2 %#1: structure num
{
\tl_clear:N #2
\prop_map_inline:cn { g_@@_struct_#1_prop }
{
% \end{macrocode}
% Some keys needs the option to format the value, e.g. add brackets for an
% array, we also need the option to ignore some entries in the properties.
% \begin{macrocode}
\cs_if_exist_use:cTF {@@_struct_format_##1:nnN}
{
{##1}{##2}#2
}
{
\tl_put_right:Ne #2 { \c_space_tl/##1~##2 }
}
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_struct_format_rolemap:nnN,\@@_struct_format_parentrole:nnN}
% This two entries should not end in the PDF.
% \begin{macrocode}
\cs_new:Nn\@@_struct_format_rolemap:nnN{}
\cs_new:Nn\@@_struct_format_parentrole:nnN{}
% \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_struct_format_Ref:nnN}
% Ref is an array, we store values as a clist of commands that must
% be executed here, the formatting has to add also brackets.
%
% \begin{macrocode}
\cs_new_protected:Nn\@@_struct_format_Ref:nnN
{
\tl_put_right:Nn #3 { ~/#1~[ } %]
\clist_map_inline:nn{ #2 }
{
##1 #3
}
\tl_put_right:Nn #3
{ %[
\c_space_tl]
}
}
% \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
{
\prop_if_exist:cTF { g_@@_struct_#1_prop }
{
% \end{macrocode}
% It can happen that a structure is not used and so has not parent.
% Simply ignoring it is problematic as it is also recorded in
% the IDTree, so we make an artifact out of it.
% \begin{macrocode}
\prop_get:cnNF { g_@@_struct_#1_prop } {P}\l_@@_tmpb_tl
{
\prop_gput:cne { g_@@_struct_#1_prop } {P}
{\pdf_object_ref_indexed:nn { @@/struct }{1}}
\prop_gput:cne { g_@@_struct_#1_prop } {S}{/Artifact}
\seq_if_empty:cF {g_@@_struct_kids_#1_seq}
{
\msg_warning:nnee
{tag}
{struct-orphan}
{ #1 }
{\seq_count:c{g_@@_struct_kids_#1_seq}}
}
}
\@@_struct_fill_kid_key:n { #1 }
\@@_struct_get_dict_content:nN { #1 } \l_@@_tmpa_tl
\pdf_object_write_indexed:nnne
{ @@/struct }{ #1 }
{dict}
{
\l_@@_tmpa_tl\c_space_tl
/ID~\@@_struct_get_id:n{#1}
}
}
{
\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:nne
% { 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
\@@_property_record:nn {@tag@objr@page@#2 }{ tagabspage }
\@@_struct_kid_OBJR_gput_right:eee
{
\l_@@_struct_stack_parent_tmpa_tl
}
{
#1 %
}
{
\pdf_pageobject_ref:n
{ \property_ref:nnn {@tag@objr@page@#2 }{ tagabspage }{1} }
}
% add the parent obj number to the parent tree:
\exp_args:Nne
\@@_parenttree_add_objr:nn
{
#2
}
{
\pdf_object_ref_indexed:nn
{ @@/struct }{ \l_@@_struct_stack_parent_tmpa_tl }
}
% increase the int:
\int_gincr:N \c@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|.
% \begin{macrocode}
\cs_new:Npn \@@_get_data_struct_tag:
{
\exp_args:Ne
\tl_tail:n
{
\prop_item:cn {g_@@_struct_\g_@@_struct_stack_current_tl _prop}{S}
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_data_struct_id:}
% this command allows \cs{tag_get:n} to get the current
% structure id with the keyword |struct_id|.
% \begin{macrocode}
\cs_new:Npn \@@_get_data_struct_id:
{
\@@_struct_get_id:n {\g_@@_struct_stack_current_tl}
}
%
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_data_struct_num:}
% this command allows \cs{tag_get:n} to get the current
% structure number with the keyword |struct_num|. We will need to handle nesting
% \begin{macrocode}
%<*base>
\cs_new:Npn \@@_get_data_struct_num:
{
\g_@@_struct_stack_current_tl
}
%
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_data_struct_counter:}
% this command allows \cs{tag_get:n} to get the current
% state of the structure counter with the keyword |struct_counter|.
% By comparing the numbers it can be used to check the number of
% structure commands in a piece of code.
% \begin{macrocode}
%<*base>
\cs_new:Npn \@@_get_data_struct_counter:
{
\int_use:N \c@g_@@_struct_abs_int
}
%
% \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.
%
% This socket is used by the tag key. It allows to switch between
% the latex-tabs and the standard tags.
% \begin{macrocode}
%<*package>
\socket_new:nn { tag/struct/tag }{1}
\socket_new_plug:nnn { tag/struct/tag }{ latex-tags }
{
\seq_set_split:Nne \l_@@_tmpa_seq { / }
{#1/\prop_item:Ne\g__tag_role_tags_NS_prop{#1}}
\tl_gset:Ne \g_@@_struct_tag_tl { \seq_item:Nn\l_@@_tmpa_seq {1} }
\tl_gset:Ne \g_@@_struct_tag_NS_tl{ \seq_item:Nn\l_@@_tmpa_seq {2} }
\@@_check_structure_tag:N \g_@@_struct_tag_tl
}
\socket_new_plug:nnn { tag/struct/tag }{ pdf-tags }
{
\seq_set_split:Nne \l_@@_tmpa_seq { / }
{#1/\prop_item:Ne\g_@@_role_tags_NS_prop{#1}}
\tl_gset:Ne \g_@@_struct_tag_tl { \seq_item:Nn\l_@@_tmpa_seq {1} }
\tl_gset:Ne \g_@@_struct_tag_NS_tl{ \seq_item:Nn\l_@@_tmpa_seq {2} }
\@@_role_get:VVNN
\g_@@_struct_tag_tl \g_@@_struct_tag_NS_tl \l_@@_tmpa_tl \l_@@_tmpb_tl
\tl_gset:Ne \g_@@_struct_tag_tl {\l_@@_tmpa_tl}
\tl_gset:Ne \g_@@_struct_tag_NS_tl{\l_@@_tmpb_tl}
\@@_check_structure_tag:N \g_@@_struct_tag_tl
}
\socket_assign_plug:nn { tag/struct/tag } {latex-tags}
% \end{macrocode}
% \begin{structkeydecl}
% {
% label,
% stash,
% parent,
% firstkid,
% tag,
% title,
% title-o,
% alt,
% actualtext,
% lang,
% ref,
% E
% }
% \begin{macrocode}
\keys_define:nn { @@ / struct }
{
label .code:n =
{
\prop_gput:Nee\g_@@_struct_label_num_prop
{#1}{\int_use:N \c@g_@@_struct_abs_int}
\@@_property_record:eV
{tagpdfstruct-#1}
\c_@@_property_struct_clist
},
stash .bool_set:N = \l_@@_struct_elem_stash_bool,
parent .code:n =
{
\bool_lazy_and:nnTF
{
\prop_if_exist_p:c { g_@@_struct_\int_eval:n {#1}_prop }
}
{
\int_compare_p:nNn {#1}<{\c@g_@@_struct_abs_int}
}
{ \tl_set:Ne \l_@@_struct_stack_parent_tmpa_tl { \int_eval:n {#1} } }
{
\msg_warning:nnee { tag } { struct-unknown }
{ \int_eval:n {#1} }
{ parent~key~ignored }
}
},
parent .default:n = {-1},
firstkid .code:n = { \tl_set:Nn \l_@@_struct_addkid_tl {left} },
tag .code:n = % S property
{
\socket_use:nn { tag/struct/tag }{#1}
},
title .code:n = % T property
{
\str_set_convert:Nnnn
\l_@@_tmpa_str
{ #1 }
{ default }
{ utf16/hex }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ T }
{ <\l_@@_tmpa_str> }
},
title-o .code:n = % T property
{
\str_set_convert:Nonn
\l_@@_tmpa_str
{ #1 }
{ default }
{ utf16/hex }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ T }
{ <\l_@@_tmpa_str> }
},
alt .code:n = % Alt property
{
\tl_if_empty:oF{#1}
{
\str_set_convert:Noon
\l_@@_tmpa_str
{ #1 }
{ default }
{ utf16/hex }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ Alt }
{ <\l_@@_tmpa_str> }
}
},
alttext .meta:n = {alt=#1},
actualtext .code:n = % ActualText property
{
\tl_if_empty:oF{#1}
{
\str_set_convert:Noon
\l_@@_tmpa_str
{ #1 }
{ default }
{ utf16/hex }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ ActualText }
{ <\l_@@_tmpa_str>}
}
},
lang .code:n = % Lang property
{
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ Lang }
{ (#1) }
},
}
% \end{macrocode}
% \end{structkeydecl}
% Ref is rather special as it values are often
% known only at the end of the document.
% It therefore stores it values as
% clist of commands which are executed at the end of the document,
% when the structure elements are written.
% \begin{macro}{\@@_struct_Ref_obj:nN,\@@_struct_Ref_label:nN,
% \@@_struct_Ref_dest:nN,\@@_struct_Ref_num:nN}
% this commands are helper commands that are stored as clist
% in the Ref key of a structure. They are executed when the structure
% elements are written in \cs{@@_struct_write_obj}. They are used
% in \cs{@@_struct_format_Ref}. They allow to add a Ref by object reference,
% label, destname and structure number
% \begin{macrocode}
\cs_new_protected:Npn \@@_struct_Ref_obj:nN #1 #2 %#1 a object reference
{
\tl_put_right:Ne#2
{
\c_space_tl#1
}
}
\cs_new_protected:Npn \@@_struct_Ref_label:nN #1 #2 %#1 a label
{
\prop_get:NnNTF \g_@@_struct_label_num_prop {#1} \l_@@_tmpb_tl
{
\tl_put_right:Ne#2
{
\c_space_tl\tag_struct_object_ref:e{ \l_@@_tmpb_tl }
}
}
{
\msg_warning:nnn {tag}{struct-Ref-unknown}{Label~'#1'}
}
}
\cs_new_protected:Npn \@@_struct_Ref_dest:nN #1 #2 %#1 a dest name
{
\prop_get:NnNTF \g_@@_struct_dest_num_prop {#1} \l_@@_tmpb_tl
{
\tl_put_right:Ne#2
{
\c_space_tl\tag_struct_object_ref:e{ \l_@@_tmpb_tl }
}
}
{
\msg_warning:nnn {tag}{struct-Ref-unknown}{Destination~'#1'}
}
}
\cs_new_protected:Npn \@@_struct_Ref_num:nN #1 #2 %#1 a structure number
{
\tl_put_right:Ne#2
{
\c_space_tl\tag_struct_object_ref:e{ #1 }
}
}
% \end{macrocode}
% \end{macro}
% \begin{structkeydecl}{ref,E,}
% \begin{macrocode}
\keys_define:nn { @@ / struct }
{
ref .code:n = % ref property
{
\clist_map_inline:on {#1}
{
\tag_struct_gput:nne
{\int_use:N \c@g_@@_struct_abs_int}{ref_label}{ ##1 }
}
},
E .code:n = % E property
{
\str_set_convert:Nnon
\l_@@_tmpa_str
{ #1 }
{ default }
{ utf16/hex }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ E }
{ <\l_@@_tmpa_str> }
},
}
% \end{macrocode}
% \end{structkeydecl}
%
% \begin{structkeydecl}{AF, AFref,AFinline,AFinline-o,texsource,mathml}
% 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.
% AF/AFref is an array and can be used more than once, so we store it in a tl.
% which is expanded.
% AFinline currently uses the fix extension txt.
% texsource is a special variant which creates a tex-file, it expects a
% tl-var as value (e.g. from math grabbing)
%
% \begin{variable}{\g_@@_struct_AFobj_int}
% This variable is used to number the AF-object names
% \begin{macrocode}
\int_new:N\g_@@_struct_AFobj_int
% \end{macrocode}
% \end{variable}
%
% \begin{macrocode}
\cs_generate_variant:Nn \pdffile_embed_stream:nnN {neN}
\cs_new_protected:Npn \@@_struct_add_inline_AF:nn #1 #2
% #1 content, #2 extension
{
\tl_if_empty:nF{#1}
{
\group_begin:
\int_gincr:N \g_@@_struct_AFobj_int
\pdffile_embed_stream:neN
{#1}
{tag-AFfile\int_use:N\g_@@_struct_AFobj_int.#2}
\l_@@_tmpa_tl
\@@_struct_add_AF:ee
{ \int_use:N \c@g_@@_struct_abs_int }
{ \l_@@_tmpa_tl }
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ AF }
{
[
\tl_use:c
{ g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_AF_tl }
]
}
\group_end:
}
}
\cs_generate_variant:Nn \@@_struct_add_inline_AF:nn {on}
% \end{macrocode}
%
% \begin{macrocode}
\cs_new_protected:Npn \@@_struct_add_AF:nn #1 #2
% #1 struct num #2 object reference
{
\tl_if_exist:cTF
{
g_@@_struct_#1_AF_tl
}
{
\tl_gput_right:ce
{ g_@@_struct_#1_AF_tl }
{ \c_space_tl #2 }
}
{
\tl_new:c
{ g_@@_struct_#1_AF_tl }
\tl_gset:ce
{ g_@@_struct_#1_AF_tl }
{ #2 }
}
}
\cs_generate_variant:Nn \@@_struct_add_AF:nn {en,ee}
\keys_define:nn { @@ / struct }
{
AF .code:n = % AF property
{
\pdf_object_if_exist:eTF {#1}
{
\@@_struct_add_AF:ee
{ \int_use:N \c@g_@@_struct_abs_int }{\pdf_object_ref:e {#1}}
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ AF }
{
[
\tl_use:c
{ g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_AF_tl }
]
}
}
{
% message?
}
},
AFref .code:n = % AF property
{
\tl_if_empty:eF {#1}
{
\@@_struct_add_AF:ee { \int_use:N \c@g_@@_struct_abs_int }{#1}
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ AF }
{
[
\tl_use:c
{ g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_AF_tl }
]
}
}
},
,AFinline .code:n =
{
\@@_struct_add_inline_AF:nn {#1}{txt}
}
,AFinline-o .code:n =
{
\@@_struct_add_inline_AF:on {#1}{txt}
}
,texsource .code:n =
{
\group_begin:
\pdfdict_put:nnn { l_pdffile/Filespec } {Desc}{(TeX~source)}
\pdfdict_put:nnn { l_pdffile/Filespec }{AFRelationship} { /Source }
\@@_struct_add_inline_AF:on {#1}{tex}
\group_end:
}
,mathml .code:n =
{
\group_begin:
\pdfdict_put:nnn { l_pdffile/Filespec } {Desc}{(mathml~representation)}
\pdfdict_put:nnn { l_pdffile/Filespec }{AFRelationship} { /Supplement }
\@@_struct_add_inline_AF:on {#1}{xml}
\group_end:
}
}
% \end{macrocode}
% \end{structkeydecl}
% \begin{setupkeydecl}{root-AF}
% The root structure can take AF keys too, so we provide a key for it.
% This key is used with |\tagpdfsetup|, not in a structure!
% \begin{macrocode}
\keys_define:nn { @@ / setup }
{
root-AF .code:n =
{
\pdf_object_if_exist:nTF {#1}
{
\@@_struct_add_AF:ee { 1 }{\pdf_object_ref:n {#1}}
\@@_struct_prop_gput:nne
{ 1 }
{ AF }
{
[
\tl_use:c
{ g_@@_struct_1_AF_tl }
]
}
}
{
}
},
}
% \end{macrocode}
% \end{setupkeydecl}
% \section{User commands}
% We allow to set a language by default
% \begin{macro}{\l_@@_struct_lang_tl}
% \begin{macrocode}
\tl_new:N \l_@@_struct_lang_tl
%
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\tag_struct_begin:n,\tag_struct_end:}
% \begin{macrocode}
%\cs_new_protected:Npn \tag_struct_begin:n #1 {\int_gincr:N \c@g_@@_struct_abs_int}
%\cs_new_protected:Npn \tag_struct_end:{}
%\cs_new_protected:Npn \tag_struct_end:n{}
%<*package|debug>
%\cs_set_protected:Npn \tag_struct_begin:n #1 %#1 key-val
%\cs_set_protected:Npn \tag_struct_begin:n #1 %#1 key-val
{
%\@@_check_if_active_struct:T
%\@@_check_if_active_struct:TF
{
\group_begin:
\int_gincr:N \c@g_@@_struct_abs_int
\@@_prop_new:c { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
% \prop_new:c { g_@@_struct_debug_\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}
% \seq_new:c { g_@@_struct_debug_kids_\int_eval:n {\c@g_@@_struct_abs_int}_seq }
\pdf_object_new_indexed:nn { @@/struct }
{ \c@g_@@_struct_abs_int }
\@@_struct_prop_gput:nnn
{ \int_use:N \c@g_@@_struct_abs_int }
{ Type }
{ /StructElem }
\tl_if_empty:NF \l_@@_struct_lang_tl
{
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ Lang }
{ (\l_@@_struct_lang_tl) }
}
\@@_struct_prop_gput:nnn
{ \int_use:N \c@g_@@_struct_abs_int }
{ Type }
{ /StructElem }
\tl_set:Nn \l_@@_struct_stack_parent_tmpa_tl {-1}
\keys_set:nn { @@ / struct} { #1 }
% \end{macrocode}
% \begin{macrocode}
\@@_struct_set_tag_info:eVV
{ \int_use:N \c@g_@@_struct_abs_int }
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
\@@_check_structure_has_tag:n { \int_use:N \c@g_@@_struct_abs_int }
% \end{macrocode}
% The structure number of the parent is either taken from the stack or
% has been set with the parent key.
% \begin{macrocode}
\int_compare:nNnT { \l_@@_struct_stack_parent_tmpa_tl } = { -1 }
{
\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
\@@_role_get:VVNN
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
\l_@@_struct_roletag_tl
\l_@@_struct_roletag_NS_tl
% \end{macrocode}
% to target role and role NS
% \begin{macrocode}
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ rolemap }
{
{\l_@@_struct_roletag_tl}{\l_@@_struct_roletag_NS_tl}
}
% \end{macrocode}
% we also store which role to use for parent/child test. If the role is
% one of Part, Div, NonStruct we have to retrieve it from the parent.
% If the structure is stashed, this must be updated!
% \begin{macrocode}
\str_case:VnTF \l_@@_struct_roletag_tl
{
{Part} {}
{Div} {}
{NonStruct} {}
}
{
\prop_get:cnNT
{ g_@@_struct_ \l_@@_struct_stack_parent_tmpa_tl _prop }
{ parentrole }
\l_@@_get_tmpc_tl
{
\@@_struct_prop_gput:nno
{ \int_use:N \c@g_@@_struct_abs_int }
{ parentrole }
{
\l_@@_get_tmpc_tl
}
}
}
{
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ parentrole }
{
{\l_@@_struct_roletag_tl}{\l_@@_struct_roletag_NS_tl}
}
}
% \end{macrocode}
% \begin{macrocode}
\seq_gpush:Ne \g_@@_struct_tag_stack_seq
{{\g_@@_struct_tag_tl}{\l_@@_struct_roletag_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
{
% \end{macrocode}
% check if the tag can be used inside the parent. It only makes sense,
% if the structure is actually used here, so it is guarded by the stash boolean.
% For now we ignore the namespace!
% \begin{macrocode}
\@@_struct_get_parentrole:eNN
{\l_@@_struct_stack_parent_tmpa_tl}
\l_@@_get_parent_tmpa_tl
\l_@@_get_parent_tmpb_tl
\@@_check_parent_child:VVVVN
\l_@@_get_parent_tmpa_tl
\l_@@_get_parent_tmpb_tl
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
\l_@@_parent_child_check_tl
\int_compare:nNnT {\l_@@_parent_child_check_tl}<0
{
\prop_get:cnN
{ g_@@_struct_ \l_@@_struct_stack_parent_tmpa_tl _prop}
{S}
\l_@@_tmpa_tl
\quark_if_no_value:NT\l_@@_tmpa_tl{\tl_set:Nn \l_@@_tmpa_tl{UNKNOWN}}
\msg_warning:nneee
{ tag }
{role-parent-child}
{ \l_@@_get_parent_tmpa_tl/\l_@@_get_parent_tmpb_tl }
{ \g_@@_struct_tag_tl/\g_@@_struct_tag_NS_tl }
{ not~allowed~
(struct~\l_@@_struct_stack_parent_tmpa_tl,~\l_@@_tmpa_tl
\c_space_tl-->~struct~\int_eval:n {\c@g_@@_struct_abs_int})
}
\cs_set_eq:NN \l_@@_role_remap_tag_tl \g_@@_struct_tag_tl
\cs_set_eq:NN \l_@@_role_remap_NS_tl \g_@@_struct_tag_NS_tl
\@@_role_remap:
\cs_gset_eq:NN \g_@@_struct_tag_tl \l_@@_role_remap_tag_tl
\cs_gset_eq:NN \g_@@_struct_tag_NS_tl \l_@@_role_remap_NS_tl
\@@_struct_set_tag_info:eVV
{ \int_use:N \c@g_@@_struct_abs_int }
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
}
% \end{macrocode}
% Set the Parent.
% \begin{macrocode}
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ P }
{
\pdf_object_ref_indexed:nn { @@/struct} { \l_@@_struct_stack_parent_tmpa_tl }
}
% \end{macrocode}
% \begin{macrocode}
%record this structure as kid:
%\tl_show:N \g_@@_struct_stack_current_tl
%\tl_show:N \l_@@_struct_stack_parent_tmpa_tl
\use:c { @@_struct_kid_struct_gput_ \l_@@_struct_addkid_tl :ee }
{ \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}
}
% \end{macrocode}
% the debug mode stores in second prop and replaces value with more suitable ones.
% (If the structure is updated later this gets perhaps lost, but well ...)
% This must be done outside of the stash boolean.
% \begin{macrocode}
% \prop_gset_eq:cc
% { g_@@_struct_debug_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
% { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
% \prop_gput:cne
% { g_@@_struct_debug_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
% { P }
% {
% \bool_if:NTF \l_@@_struct_elem_stash_bool
% {no~parent:~stashed}
% {
% parent~structure:~\l_@@_struct_stack_parent_tmpa_tl\c_space_tl =~
% \prop_item:cn{ g__tag_struct_\l_@@_struct_stack_parent_tmpa_tl _prop }{S}
% }
% }
% \prop_gput:cne
% { g_@@_struct_debug_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
% { NS }
% { \g_@@_struct_tag_NS_tl }
% \end{macrocode}
% \begin{macrocode}
%\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop }
%\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmpa_tl _seq}
% \@@_debug_struct_begin_insert:n { #1 }
\group_end:
}
%{ \@@_debug_struct_begin_ignore:n { #1 }}
}
%\cs_set_protected:Nn \tag_struct_end:
%\cs_set_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
%\@@_check_if_active_struct:TF
{
\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:Ne \g_@@_struct_tag_tl
{ \exp_last_unbraced:NV\use_i:nn \l_@@_tmpa_tl }
\prop_get:NVNT\g_@@_role_tags_NS_prop \g_@@_struct_tag_tl\l_@@_tmpa_tl
{
\tl_gset:Ne \g_@@_struct_tag_NS_tl { \l_@@_tmpa_tl }
}
}
%\@@_debug_struct_end_insert:
}
%{\@@_debug_struct_end_ignore:}
}
\cs_set_protected:Npn \tag_struct_end:n #1
{
% \@@_check_if_active_struct:T{\@@_debug_struct_end_check:n{#1}}
\tag_struct_end:
}
%
% \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:Npn \tag_struct_use:n #1 {}
%<*package|debug>
\cs_set_protected:Npn \tag_struct_use:n #1 %#1 is the label
{
\@@_check_if_active_struct:T
{
\prop_if_exist:cTF
{ g_@@_struct_\property_ref: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:ee
{ \g_@@_struct_stack_current_tl }
{ \property_ref:enn{tagpdfstruct-#1}{tagstruct}{1} }
%add the current structure to the labeled one as parents
\@@_prop_gput:cne
{ g_@@_struct_\property_ref:enn{tagpdfstruct-#1}{tagstruct}{1}_prop }
{ P }
{
\pdf_object_ref_indexed:nn { @@/struct } { \g_@@_struct_stack_current_tl }
}
% \end{macrocode}
% debug code
% \begin{macrocode}
% \prop_gput:cne
% { g_@@_struct_debug_\property_ref:enn{tagpdfstruct-#1}{tagstruct}{1}_prop }
% { P }
% {
% parent~structure:~\g_@@_struct_stack_current_tl\c_space_tl=~
% \g_@@_struct_tag_tl
% }
% \end{macrocode}
% check if the tag is allowed as child. Here we have to retrieve the
% tag info for the child, while the data for the parent is in
% the global tl-vars:
% \begin{macrocode}
\@@_struct_get_parentrole:eNN
{\property_ref:enn{tagpdfstruct-#1}{tagstruct}{1}}
\l_@@_tmpa_tl
\l_@@_tmpb_tl
\@@_check_parent_child:VVVVN
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
\l_@@_tmpa_tl
\l_@@_tmpb_tl
\l_@@_parent_child_check_tl
\int_compare:nNnT {\l_@@_parent_child_check_tl}<0
{
\cs_set_eq:NN \l_@@_role_remap_tag_tl \g_@@_struct_tag_tl
\cs_set_eq:NN \l_@@_role_remap_NS_tl \g_@@_struct_tag_NS_tl
\@@_role_remap:
\cs_gset_eq:NN \g_@@_struct_tag_tl \l_@@_role_remap_tag_tl
\cs_gset_eq:NN \g_@@_struct_tag_NS_tl \l_@@_role_remap_NS_tl
\@@_struct_set_tag_info:eVV
{ \int_use:N \c@g_@@_struct_abs_int }
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
}
}
{
\msg_warning:nnn{ tag }{struct-label-unknown}{#1}
}
}
}
%
% \end{macrocode}
% \end{macro}
% \begin{macro}{\tag_struct_use_num:n}
% This command allows to use a stashed structure in another place.
% differently to the previous command it doesn't use a label but directly
% a structure number to find the parent.
% TODO: decide how it should be guarded. Probably by the struct-check.
% \begin{macrocode}
%\cs_new_protected:Npn \tag_struct_use_num:n #1 {}
%<*package|debug>
\cs_set_protected:Npn \tag_struct_use_num:n #1 %#1 is structure number
{
\@@_check_if_active_struct:T
{
\prop_if_exist:cTF
{ g_@@_struct_#1_prop } %
{
\prop_get:cnNT
{g_@@_struct_#1_prop}
{P}
\l_@@_tmpa_tl
{
\msg_warning:nnn { tag } {struct-used-twice} {#1}
}
%add the \#1 structure as kid to the current structure (can be the root)
\@@_struct_kid_struct_gput_right:ee
{ \g_@@_struct_stack_current_tl }
{ #1 }
%add the current structure to \#1 as parent
\@@_struct_prop_gput:nne
{ #1 }
{ P }
{
\pdf_object_ref_indexed:nn { @@/struct }{ \g_@@_struct_stack_current_tl }
}
% \prop_gput:cne
% { g_@@_struct_debug_#1_prop }
% { P }
% {
% parent~structure:~\g_@@_struct_stack_current_tl\c_space_tl=~
% \g_@@_struct_tag_tl
% }
% \end{macrocode}
% check if the tag is allowed as child. Here we have to retrieve the
% tag info for the child, while the data for the parent is in
% the global tl-vars:
% \begin{macrocode}
\@@_struct_get_parentrole:eNN
{#1}
\l_@@_tmpa_tl
\l_@@_tmpb_tl
\@@_check_parent_child:VVVVN
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
\l_@@_tmpa_tl
\l_@@_tmpb_tl
\l_@@_parent_child_check_tl
\int_compare:nNnT {\l_@@_parent_child_check_tl}<0
{
\cs_set_eq:NN \l_@@_role_remap_tag_tl \g_@@_struct_tag_tl
\cs_set_eq:NN \l_@@_role_remap_NS_tl \g_@@_struct_tag_NS_tl
\@@_role_remap:
\cs_gset_eq:NN \g_@@_struct_tag_tl \l_@@_role_remap_tag_tl
\cs_gset_eq:NN \g_@@_struct_tag_NS_tl \l_@@_role_remap_NS_tl
\@@_struct_set_tag_info:eVV
{ \int_use:N \c@g_@@_struct_abs_int }
\g_@@_struct_tag_tl
\g_@@_struct_tag_NS_tl
}
}
{
\msg_warning:nnn{ tag }{struct-label-unknown}{#1}
}
}
}
%
% \end{macrocode}
% \end{macro}
% \begin{macro}[EXP]{\tag_struct_object_ref:n}
% This is a command that allows to reference a structure. The argument is the
% number which can be get for the current structure with |\tag_get:n{struct_num}|
% TODO check if it should be in base too.
% \begin{macrocode}
%<*package>
\cs_new:Npn \tag_struct_object_ref:n #1
{
\pdf_object_ref_indexed:nn {@@/struct}{ #1 }
}
\cs_generate_variant:Nn \tag_struct_object_ref:n {e}
%
% \end{macrocode}
%
% \end{macro}
%
% \begin{macro}{\tag_struct_gput:nnn}
% This is a command that allows to update the data of a structure.
% This often can't done simply by replacing the value, as we have to
% preserve and extend existing content. We use therefore dedicated functions
% adjusted to the key in question.
% The first argument is the number of the structure,
% the second a keyword referring to a function,
% the third the value. Currently the existing keywords are all related
% to the \texttt{Ref} key (an array).
% The keyword \texttt{ref} takes as value an explicit object reference to
% a structure. The keyword \texttt{ref_label} expects as value a label name (from
% a label set in a \cs{tagstructbegin} command). The keyword \texttt{ref_dest}
% expects a destination name set with \cs{MakeLinkTarget}. It then will refer to
% the structure in which this \cs{MakeLinkTarget} was used. At last
% the keyword \texttt{ref_num} expects a structure number.
% \begin{macrocode}
%\cs_new_protected:Npn \tag_struct_gput:nnn #1 #2 #3{}
%<*package>
\cs_set_protected:Npn \tag_struct_gput:nnn #1 #2 #3
{
\cs_if_exist_use:cF {@@_struct_gput_data_#2:nn}
{ %warning??
\use_none:nn
}
{#1}{#3}
}
\cs_generate_variant:Nn \tag_struct_gput:nnn {ene,nne}
%
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_struct_gput_data_ref_aux:nnn}
% \begin{macrocode}
%<*package>
\cs_new_protected:Npn \@@_struct_gput_data_ref_aux:nnn #1 #2 #3
% #1 receiving struct num, #2 key word #3 value
{
\prop_get:cnNTF
{ g_@@_struct_#1_prop }
{Ref}
\l_@@_get_tmpc_tl
{
\tl_put_right:No \l_@@_get_tmpc_tl
{\cs:w @@_struct_Ref_#2:nN \cs_end: {#3},}
}
{
\tl_set:No \l_@@_get_tmpc_tl
{\cs:w @@_struct_Ref_#2:nN \cs_end: {#3},}
}
\@@_struct_prop_gput:nno
{ #1 }
{ Ref }
{ \l_@@_get_tmpc_tl }
}
\cs_new_protected:Npn \@@_struct_gput_data_ref:nn #1 #2
{
\@@_struct_gput_data_ref_aux:nnn {#1}{obj}{#2}
}
\cs_new_protected:Npn \@@_struct_gput_data_ref_label:nn #1 #2
{
\@@_struct_gput_data_ref_aux:nnn {#1}{label}{#2}
}
\cs_new_protected:Npn \@@_struct_gput_data_ref_dest:nn #1 #2
{
\@@_struct_gput_data_ref_aux:nnn {#1}{dest}{#2}
}
\cs_new_protected:Npn \@@_struct_gput_data_ref_num:nn #1 #2
{
\@@_struct_gput_data_ref_aux:nnn {#1}{num}{#2}
}
\cs_generate_variant:Nn \@@_struct_gput_data_ref:nn {ee,no}
% \end{macrocode}
% \end{macro}
% \begin{macro}
% {
% \tag_struct_insert_annot:nn,
% \tag_struct_insert_annot:ee,
% \tag_struct_insert_annot:ee
% }
% \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,ee}
\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} {2024-11-22} {0.99j}
{part of tagpdf - code related to attributes and attribute classes}
%
% \end{macrocode}
% \subsection{Variables}
% \begin{variable}
% {
% ,\g_@@_attr_entries_prop
% ,\g_@@_attr_class_used_prop
% ,\g_@@_attr_objref_prop
% ,\l_@@_attr_value_tl
% }
% |\g_@@_attr_entries_prop| will store attribute names and their dictionary content.\\
% |\g_@@_attr_class_used_prop| will hold the attributes which have been used as
% class name.
% |\l_@@_attr_value_tl| is used to build the attribute array or key.
% Every time 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
\prop_new_linked:N \g_@@_attr_class_used_prop
\tl_new:N \l_@@_attr_value_tl
\prop_new:N \g_@@_attr_objref_prop %will contain obj num of used attributes
% \end{macrocode}
% This seq is currently kept for compatibility with the table code.
% \begin{macrocode}
\seq_new:N\g_@@_attr_class_used_seq
% \end{macrocode}
% \end{variable}
% \subsection{Commands and keys}
% \begin{macro}{\@@_attr_new_entry:nn,role/new-attribute (setup-key), newattribute (deprecated)}
% This allows to define attributes. Defined attributes
% are stored in a global property. |role/new-attribute| expects
% two brace group, the name and the content. The content typically
% needs an |/O| key for the owner. An example look like
% this.
%
% TODO: consider to put them directly in the ClassMap, that is perhaps
% more effective.
% \begin{verbatim}
% \tagpdfsetup
% {
% role/new-attribute =
% {TH-col}{/O /Table /Scope /Column},
% role/new-attribute =
% {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:Nen \g_@@_attr_entries_prop
{\pdf_name_from_unicode_e:n{#1}}{#2}
}
\cs_generate_variant:Nn \__tag_attr_new_entry:nn {ee}
\keys_define:nn { @@ / setup }
{
role/new-attribute .code:n =
{
\@@_attr_new_entry:nn #1
}
% \end{macrocode}
% deprecated name
% \begin{macrocode}
,newattribute .code:n =
{
\@@_attr_new_entry:nn #1
},
}
% \end{macrocode}
% \end{macro}
%
% \begin{structkeydecl}{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:Ne \l_@@_tmpa_clist { #1 }
\seq_set_from_clist:NN \l_@@_tmpb_seq \l_@@_tmpa_clist
% \end{macrocode}
% we convert the names into pdf names with slash
% \begin{macrocode}
\seq_set_map_e:NNn \l_@@_tmpa_seq \l_@@_tmpb_seq
{
\pdf_name_from_unicode_e:n {##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_gput:Nnn\g_@@_attr_class_used_prop { ##1} {}
}
\tl_set:Ne \l_@@_tmpa_tl
{
\int_compare:nT { \seq_count:N \l_@@_tmpa_seq > 1 }{[}
\seq_use:Nn \l_@@_tmpa_seq { \c_space_tl }
\int_compare:nT { \seq_count:N \l_@@_tmpa_seq > 1 }{]}
}
\int_compare:nT { \seq_count:N \l_@@_tmpa_seq > 0 }
{
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ C }
{ \l_@@_tmpa_tl }
%\prop_show:c { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
}
}
}
% \end{macrocode}
% \end{structkeydecl}
% \begin{structkeydecl}{attribute}
% \begin{macrocode}
\keys_define:nn { @@ / struct }
{
attribute .code:n = % A property (attribute, value currently a dictionary)
{
\clist_set:Ne \l_@@_tmpa_clist { #1 }
\clist_if_empty:NF \l_@@_tmpa_clist
{
\seq_set_from_clist:NN \l_@@_tmpb_seq \l_@@_tmpa_clist
% \end{macrocode}
% we convert the names into pdf names with slash
% \begin{macrocode}
\seq_set_map_e:NNn \l_@@_tmpa_seq \l_@@_tmpb_seq
{
\pdf_name_from_unicode_e:n {##1}
}
\tl_set:Ne \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:ne
{ dict }
{
\prop_item:Nn\g_@@_attr_entries_prop {##1}
}
\prop_gput:Nne \g_@@_attr_objref_prop {##1} {\pdf_object_ref_last:}
}
\tl_put_right:Ne \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:Ne \l_@@_attr_value_tl
{ %[
\int_compare:nT { \seq_count:N \l_@@_tmpa_seq > 1 }{]}%
}
% \tl_show:N \l_@@_attr_value_tl
\@@_struct_prop_gput:nne
{ \int_use:N \c@g_@@_struct_abs_int }
{ A }
{ \l_@@_attr_value_tl }
}
},
}
%
% \end{macrocode}
% \end{structkeydecl}
% \end{implementation}