% \iffalse meta-comment
%
%% File: latex-lab-sec.dtx (C) Copyright 2022-2025 LaTeX Project
%
% 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
%
%
% The development version of the bundle can be found below
%
% https://github.com/latex3/latex2e/required/latex-lab
%
% for those people who are interested or want to report an issue.
%
\def\ltlabsecdate{2025-10-20}
\def\ltlabsecversion{0.84k}
% \end{macrocode}
%<*driver>
\DocumentMetadata{tagging=on,pdfstandard=ua-2}
\documentclass[kernel]{l3doc}
\usepackage{latex-lab-testphase-l3doc}
\EnableCrossrefs
\CodelineIndex
\begin{document}
\DocInput{latex-lab-sec.dtx}
\end{document}
%</driver>
%
% \fi
%
%
% \title{The \textsf{latex-lab-sec} package\\
% Changes related to the tagging of sectioning commands}
% \author{\LaTeX{} Project\thanks{Initial implementation done by Ulrike Fischer}}
% \date{v\ltlabsecversion\ \ltlabsecdate}
%
% \maketitle
%
% \newcommand{\xt}[1]{\textsl{\textsf{#1}}}
% \newcommand{\TODO}[1]{\textbf{[TODO:} #1\textbf{]}}
% \newcommand{\docclass}{document class \marginpar{\raggedright document class
% customizations}}
%
% \providecommand\hook[1]{\texttt{#1}}
%
% \begin{abstract}
% The following code implements a first draft for the tagging of sectioning commands.
% \end{abstract}
%
% \section{Limitations}
%
% Sectioning commands are in general not defined by the format but by the classes.
% Their implementation vary: some are defined with the help of \cs{@startsection},
% some are like \cs{chapter} handcrafted,
% some build with the help of extension packages or as in the KOMA classes
% with class code that extends the \cs{@startsection} functionality.
%
% The following code can therefore currently be used \emph{only} with the standard classes
% or with classes which do not overwrite the changed definitions.
%
%
%
% \section{Introduction}
%
% Tagging of sectioning commands consist of two parts:
%
% \begin{itemize}
% \item The heading/title text of the section should be surrounded by a
% heading tag, typically \texttt{Hn} with some value of \texttt{n}.
% In theory, one could put the number of the section command in an \texttt{Lbl}.
% However, current AT doesn't handle this well, so
% we use a tag \texttt{section-number} that is mapped to \texttt{Span}.
% The number of the \texttt{Hn} tag should reflect the \enquote{natural} level.
% So in an article \cs{section} will use \texttt{H1}, in a book \cs{chapter} will use
% \texttt{H1} and \cs{section} \texttt{H2}.
% Titles of \cs{part} are a bit out of this system as they are normally
% not part of the hierarchy: often only some chapters are grouped under a part.
% Their title is therefore tagged as \texttt{Title}.
% \item
% The whole section should normally be surrounded by
% a \texttt{Sect} tag. Parts should be surrounded by \texttt{Part}.
% It is a bit unclear if the headings should be inside or outside of these
% structures---the best practice guide puts them outside---but on the whole
% it sounds more logical to group the heading with the text inside the \texttt{Sect}.
% For the part this is actually required, as there can be only one \texttt{Title}
% in a structure, so the part title can't be at the same level as the
% document \texttt{Title}.
%
% Starting such an enclosing \texttt{Sect} structure is rather easy,
% but closing it requires code in various place,
% for example the commands \cs{mainmatter}, \cs{backmatter},
% \cs{frontmatter} and \cs{appendix} should typically close everything.
% Following sectioning commands should close all previous structures
% with a level equal or higher than their own level.
% \end{itemize}
%
% \section{Technical details and problems}
%
% The implementation has to take care of various details.
%
% \begin{itemize}
%
% \item As sections in \LaTeX{} are not environments, the
% \texttt{<Sect>} structures can be wrongly nested with other structures. For example
% if a document puts a sectioning command into a list or a trivlist or
% a minipage then it can no longer close previous \texttt{<Sect>} structures correctly.
% The problem can be detected by checking the structure stack
% and a warning can be issued, but the author then has to close the structures
% manually before the list or minipage.
%
% Thus there have to be user interfaces to handle such cases.
% It should also be possible not to create all the \texttt{<Sect>} structures
% automatically but to tag only the headings so that the author can handle special
% cases manually.
%
% \item If hyperref is used, targets for links should be inserted, either with
% \cs{refstepcounter} or manually with \cs{MakeLinkTarget}. These targets must be
% in the correct structure for the structure destinations. They replace some
% of the current patches in hyperref.
%
% \end{itemize}
%
% \subsection{Functions and keys}
%
% \begin{function}{\tag_tool:n,\tagtool}
% \emph{deprecated}, use tagging sockets instead.
% \end{function}
%
% \subsection{TODO}
%
% \begin{itemize}
% \item A dedicated command to close a sectioning unit should be provided.
%
% \item A dedicated command to open a sectioning unit should be provided too.
%
% \item It should also be possible to suppress the sectioning unit in sectioning commands
% to allow e.g. to put an epigraph or similar in front.
%
% \item The number in \cs{part} and \cs{chapter} is currently not correctly
% tagged as a \texttt{section-number} as this requires to redefine the internal (class dependent)
% commands too.
%
% \end{itemize}
%
% \begin{macrocode}
%<*package>
% \end{macrocode}
%
% \section{Implementation}
% \begin{macrocode}
\ProvidesExplPackage {latex-lab-testphase-sec} {\ltlabsecdate} {\ltlabsecversion}
{Code related to the tagging of sectioning commands}
% \end{macrocode}
% \changes{0.84k}{2025-10-20}{Switched to tagging sockets and various corrections}.
% \subsection{Temporary fix}
% Until tagpdf correctly sets the symbolic name (2025-10-06)
% \begin{macrocode}
\tl_set:Nn \l__tag_para_tag_default_tl { \UseStructureName {para/textblock} }
\tl_set:Nn \l__tag_para_main_tag_tl { \UseStructureName {para/semantic} }
% \end{macrocode}
%
% \subsection{Surrounding by \texttt{Sect} structures}
% We use a stack to record the levels of the open \texttt{Sect}. The first item
% has level -100. A sectioning command will take a record from the stack. If its level is
% greater or equal it closes this structure and takes the next record from the stack.
% If the record has a smaller level then it puts it back and stops.
% The stack is compared with the main structure stack, if they don't match
% it means we can't safely close the \texttt{Sect} and so we issue a warning
% and do nothing.
%
% \begin{macrocode}
%</package>
% \end{macrocode}
% \subsubsection{Glyphtounicode improvements}
%
% As lualatex runs with legacy encodings in the test files, we enable and
% load glyphtounicode. For the math we load additional definitions.
%
% \begin{macrocode}
%<*kernelchange>
\ifdefined\directlua
\ifnum\outputmode > 0
\pdfvariable gentounicode =1
\protected\def\pdfglyphtounicode {\pdfextension glyphtounicode }
\protected\edef\pdfgentounicode {\pdfvariable gentounicode}
\input{glyphtounicode}
\fi
\fi
\ifdefined\pdfglyphtounicode
\input{glyphtounicode-cmex}
\fi
%</kernelchange>
% \end{macrocode}
%
% \begin{macrocode}
%<*package>
%<@@=tag>
% \end{macrocode}
%
% \subsubsection{Tagging commands}
%
%
% \begin{variable}{\g_@@_sec_stack_seq}
% The stack holds the tag, the level and the structure number.
% \begin{macrocode}
\seq_new:N \g_@@_sec_stack_seq
\seq_gpush:Nn\g_@@_sec_stack_seq {{Document}{-100}{2}}
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_get_data_current_Sect:}
% This allows to retrieve the number of the current Sect structure (or
% Document if we are outside any Sect) with |\tag_get:n{current_Sect}|
% \begin{macrocode}
\cs_new:Npn \@@_get_data_current_Sect:
{
\exp_last_unbraced:Ne\use_iii:nnn{\seq_item:Nn\g_@@_sec_stack_seq{1}}
}
% \end{macrocode}
% \end{macro}
% \begin{variable}{\l_@@_sec_Sect_bool}
% This boolean controls if a Sect structure is opened.
% \begin{macrocode}
\bool_new:N \l_@@_sec_Sect_bool
\bool_set_true:N\l_@@_sec_Sect_bool
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_sec_begin:nn}
% This starts a sectioning structure (the „Sect environment“).
% Currently the default tag is either Sect or Part, depending on the level,
% but this can be changed by adapting the symbolic structure names which are
% built from the level.
% The second argument allows to add more options but is currently unused.
% \begin{macrocode}
\cs_new_protected:Npn\@@_sec_begin:nn #1 #2 %#1 level #2 keyval
{
\tag_struct_begin:n
{
tag= \UseStructureName{sec/#1}
,#2
}
\seq_gpush:Ne \g_@@_sec_stack_seq
{{\g_@@_struct_tag_tl}{\int_eval:n{#1}}{\g_@@_struct_stack_current_tl}}
}
\cs_generate_variant:Nn \@@_sec_begin:nn {en}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_sec_end:n}
% \begin{macrocode}
\msg_new:nnn { tag } {wrong-sect-nesting}
{
The~structure~#1~can~not~be~closed.\\
It~is~not~equal~to~the~current~structure~#2~on~the~main~stack
}
\cs_new_protected:Npn\@@_sec_end:n #1 % #1 level
{
\seq_get:NN \g_@@_sec_stack_seq \l_@@_tmpa_tl
\int_compare:nNnT {#1}<{\exp_last_unbraced:NV\use_ii:nnn\l_@@_tmpa_tl+1}
{
\seq_get:NN\g_@@_struct_tag_stack_seq \l_@@_tmpb_tl
\exp_args:Nee
\tl_if_eq:nnTF
{\exp_last_unbraced:NV\use_i:nnn\l_@@_tmpa_tl}
{\exp_last_unbraced:NV\use_i:nn\l_@@_tmpb_tl}
{
\seq_gpop:NN \g_@@_sec_stack_seq \l_@@_tmpa_tl
\tag_struct_end:
\@@_sec_end:n {#1}
}
{
\msg_warning:nnee {tag}{wrong-sect-nesting}
{ \exp_last_unbraced:NV\use_i:nnn \l_@@_tmpa_tl }
{ \exp_last_unbraced:NV\use_i:nn \l_@@_tmpb_tl }
}
}
}
% \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_sec_title_split:,\@@_sec_restore_para:}
% Runin-sectioning command must separate the heading from the following text.
% The code is in an \cs{everypar} which is perhaps executed in a group (e.g. when
% a list follows), we have to ensure that the restoring of the para can escape.
% \begin{macrocode}
\cs_new_protected:Npn\@@_sec_restore_para:
{
\UseTaggingSocket {para/restore}
\if_int_compare:w \tex_currentgrouptype:D =14 % semi-simple group
\group_insert_after:N \@@_sec_restore_para:
\else:
\if_int_compare:w \tex_currentgrouptype:D =\c_one_int % simple group
\group_insert_after:N \@@_sec_restore_para:
\fi:
\fi:
}
\cs_new_protected:Npn \@@_sec_title_split:
{
% \end{macrocode}
% This ends the title structure. As the begin is from the
% automatic (flattened) para-tagging we have to increase the counter.
% \begin{macrocode}
\tag_mc_end:
\tag_struct_end:
\@@_gincr_para_end_int:
% \end{macrocode}
% In case something (e.g. a list) did reset the boolean we need to close also a semantic
% paragraph.
% \begin{macrocode}
\bool_if:NF\l__tag_para_flattened_bool
{\UseTaggingSocket{para/semantic/end}{}}
% \end{macrocode}
% Now restore the para-tagging and start a normal paragraph:
% \begin{macrocode}
\@@_sec_restore_para:
\UseTaggingSocket{para/begin}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_sec_title_begin:nn}
% This command is used in the socket at the begin of display sectioning commands
% like part and chapter. It takes two arguments: the level and the title.
% \begin{macrocode}
\cs_new_protected:Npn \@@_sec_title_begin:nn #1 #2 %level, title
{
\tag_struct_begin:n{tag=\UseStructureName{sec/#1/title},title={#2}}
\bool_set_true:N\l_@@_para_flattened_bool
\tl_set:Nn\l_@@_para_tag_tl {\UseStructureName{sec/#1/titleline}}
}
% \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_sec_title_end:}
% \begin{macrocode}
\cs_new_protected:Npn \@@_sec_title_end:
{
\tag_struct_end: %P = Hn
\UseTaggingSocket{para/restore}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_title_hang:nnn}
% To be able to correctly tag the number and insert the link target in the
% title of a sectioning command created with \cs{@startsection}
% we need a special\cs{@hangfrom} variant. This is a bit tricky:
% The argument contains the link target and for a correct structure
% destination it should be typeset \emph{after} the structure has been opened.
% But to measure the hangindent it must be typeset \emph{before} the paragraph is started.
% This means that we have to open the title structure manually
% and then have to suppress the para-tagging.
% Additionally there is an engine difference: with pdftex the literals for the mc are inserted
% with the box after the paragraph has started but luatex sets the attributes before
% and we have to reset them. Hiding all this in a tagging socket is non-trivial.
% The code assumes that we are in vmode!
% Attention: The code opens a structure that it doesn't close (it is closed by the \cs{par}).
% It therefore does not handle the full tagging of the title. In a new implementation
% of the sectioning command this will perhaps have to change.
%
% \begin{macrocode}
\cs_new_protected:Npn \@@_set_title_hang:nNnn #1 #2 #3 #4
%#1 level,
%#2 boolean: nonumber? (will be later \l__head_nonumber_bool)
%#3 formated number /hang space
%#4 title
% \end{macrocode}
% The handling of the title is not perfect. It would be better to pass it through
% something like \cs{GetTitleString}. TODO.
% \begin{macrocode}
{
\tagstructbegin{tag=\UseStructureName{sec/#1/title},title-o={#4}}
\cs_if_exist_use:N \@@_gincr_para_begin_int:
\bool_if:NF #2
{ \tagstructbegin{tag=\UseStructureName{sec/#1/number}} }
\setbox\@tempboxa\hbox{{#3}}
% \end{macrocode}
% We stop paratagging now, to avoid that the \cs{noindent} creates a structure.
% \begin{macrocode}
\bool_set_false:N \l_@@_para_bool
\hangindent \wd\@tempboxa\noindent
% \end{macrocode}
% Restart paratagging and insert the box. If the box has a real content (if there is a
% number) we have to add mc-chunks and reset the attribute of the box.
% \begin{macrocode}
\bool_set_true:N \l_@@_para_bool
\bool_if:NTF #2
{
\box\@tempboxa
}
{
\tagmcbegin{}
% \end{macrocode}
% In lua mode we have to reset the attributes inside the box!
% \begin{macrocode}
\tag_mc_reset_box:N\@tempboxa
\box\@tempboxa
\tagmcend
\tagstructend
}
\tagmcbegin{}
}
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\@@_sec_title_runin_number:nn}
% \begin{macrocode}
\cs_new_protected:Npn \@@_sec_title_runin_number:nNn #1 #2 #3 %#1 level, #2 boolean no number, #3 content
{
\bool_if:NTF #2
{ #3 }
{
\tag_mc_end_push:
\tag_struct_begin:n{tag=\UseStructureName{sec/#1/number}}
\tag_mc_begin:n{}
#3
\tag_mc_end:
\tag_struct_end:
\tag_mc_begin_pop:n{}
}
}
% \end{macrocode}
% \end{macro}
% Open sec structures should be closed at the end of the document. This should
% be done before tagpdf closes the Document structure.
% \begin{macrocode}
\hook_gput_code:nnn
{tagpdf/finish/before}
{tagpdf/sec}
{\AssignTaggingSocketPlug{sec/end}{kernel}\UseTaggingSocket{sec/end}{-10}}
\hook_gset_rule:nnnn {tagpdf/finish/before}{tagpdf/sec}{before}{tagpdf}
% \end{macrocode}
%
% The commands \cs{mainmatter}, \cs{backmatter}, \cs{frontmatter} and
% \cs{appendix} close all \texttt{Sect} and \texttt{Part} structures.
% \changes{v0.84g}{2025/01/05}{ensure that paragraph is ended (tagging/777)}
% \begin{macrocode}
\AddToHook{cmd/frontmatter/before}{\par\UseTaggingSocket{sec/end}{-10}}
\AddToHook{cmd/mainmatter/before} {\par\UseTaggingSocket{sec/end}{-10}}
\AddToHook{cmd/backmatter/before} {\par\UseTaggingSocket{sec/end}{-10}}
\AddToHook{cmd/appendix/before} {\par\UseTaggingSocket{sec/end}{-10}}
% \end{macrocode}
%
% \subsection{Tagging Sockets}
% First the sockets that handle the Sect structures.
%
% The argument of the begin socket consists of two brace groups,
% in the first brace is the level, in the second the keys for the structure.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/begin}{kernel}
{
\@@_sec_begin:en #1
}
\AssignTaggingSocketPlug{sec/begin}{kernel}
% \end{macrocode}
% The end socket takes as argument only the level to close.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/end}{kernel}
{
\@@_sec_end:n {#1}
}
\AssignTaggingSocketPlug{sec/end}{kernel}
% \end{macrocode}
%
% These two sockets handle the tagging of headings with special formatting
% like part and chapter.
% The argument of the begin socket is two brace groups containing the level and
% the title.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/begin}{kernel}
{
\@@_sec_title_begin:nn #1
}
\AssignTaggingSocketPlug{sec/title/begin}{kernel}
% \end{macrocode}
% The end socket does not take an argument. It only closes the structures
% and restores the para settings.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/end}{kernel}
{
\@@_sec_title_end:
}
\AssignTaggingSocketPlug{sec/title/end}{kernel}
% \end{macrocode}
%
%
% The \texttt{sec/title/hang} socket is used to typeset the heading of a title using \cs{@hangfrom}.
% It is the most tricky one. It takes two argument.
% The second argument of this socket will pass the normal \cs{@hangfrom} command
% if tagging is not active. It is not used with tagging.
% The first argument passes four brace groups:
% the level, a boolean for \enquote{nonumber},
% the actual content of the number and the title.
%
% Attention: The socket opens a structure that it doesn't close (it is closed by the \cs{par}).
% It therefore does not handle the full tagging of the title. In a new implementation
% of the sectioning command this will perhaps have to change.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/hang}{kernel}
{
\@@_set_title_hang:nNnn #1
}
\AssignTaggingSocketPlug{sec/title/hang}{kernel}
% \end{macrocode}
%
% The \texttt{sec/title/init} socket is used to do some initialization
% for sectioning commands that are setup with \cs{@startsection}. It sets
% the tag name and flattens the para-tagging. It is mostly needed for run-in
% headings which set the heading inside \cs{everypar}.
% It takes 1 argument, the level.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/init}{kernel}
{
\tl_set:Ne\l_@@_para_tag_tl{\UseStructureName{sec/#1/title}}
\bool_set_true:N \l_@@_para_flattened_bool
}
\AssignTaggingSocketPlug{sec/title/init}{kernel}
% \end{macrocode}
%
% This socket handles the tagging between a run-in heading and the following text.
% It takes no argument.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/split}{kernel}
{
\@@_sec_title_split:
}
\AssignTaggingSocketPlug{sec/title/split}{kernel}
% \end{macrocode}
%
% The following tagging socket command is used to handle the tagging
% of the number in the title of run-in headings. Similar to the hang variant it does not
% handle the full structure but relies in part on the paragraph
% tagging. This again can change if sectioning commands are reimplemented.
% It takes as first argument the level and a boolean for the numbering.
% The second argument contains the formatted number (if numbered) and the destination.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/runin/number}{kernel}
{
\@@_sec_title_runin_number:nNn #1 {#2}
}
\AssignTaggingSocketPlug{sec/title/runin/number}{kernel}
% \end{macrocode}
%
% The following tagging socket command simply tags a number.
% It is not used here, as the legacy code needs a more complicated setup.
%
% It takes the level as first argument.
% The second argument contains the formatted number.
% \begin{macrocode}
\NewTaggingSocketPlug{sec/title/number}{kernel}
{
\tag_mc_end_push:
\tag_struct_begin:n{tag=\UseStructureName{sec/#1/number}}
\tag_mc_begin:n{}
#2
\tag_mc_end:
\tag_struct_end:
\tag_mc_begin_pop:n{}
}
\AssignTaggingSocketPlug{sec/title/number}{kernel}
% \end{macrocode}
%
% \section{Sectioning commands}
%
% \subsection{\cs{part} and \cs{chapter}}
%
% \cs{part} and \cs{chapter} are defined by the classes.
% To tag them we redefine the user commands.
% This will probably break with various classes and with titlesec.
% The tagging inside relies on the para tagging.
% We do not yet use keyval in the optional argument, as this requires latex-dev
% and the naming of the keys and their key family is unclear.
% \changes{v0.84f}{2024/10/04}{Added braces around optional arg (tagging/725)}
% \begin{macrocode}
\AddToHook{class/after}
{
\@ifundefined{chapter}
{
% \end{macrocode}
% This redefines \cs{part} in article class.
% \begin{macrocode}
\@ifundefined{part}{}
{
\RenewDocumentCommand\part{ s O{#3} m }
{
\if@noskipsec \leavevmode \fi
\par
\addvspace{4ex}%
\@afterindentfalse
% \end{macrocode}
% This are the tagging commands needed at the begin. They open a Part structure
% and the structure for the title of the heading.
% \begin{macrocode}
% tagging start commands
\UseTaggingSocket{sec/end}{-1}
\UseTaggingSocket{sec/begin}{{-1}{tag=\UseStructureName{sec/-1}}}
\UseTaggingSocket{sec/title/begin}{{-1}{#2}}
% end tagging start commands
% \end{macrocode}
% This adds a manual target if the part is unnumbered or starred.
% It replaces the hyperref patches.
% \begin{macrocode}
\bool_lazy_any:nT
{
{ #1 }
{
\int_compare_p:nNn {\c@secnumdepth}<{-1}
}
}
{
\MakeLinkTarget[part]{}
}
% \end{macrocode}
% The main call to the underlying commands.
% \begin{macrocode}
\IfBooleanTF
{#1}
{ \@spart {#3} }
{ \@part [{#2}]{#3} }
% \end{macrocode}
% and now the closing command for the tagging of the title.
% \begin{macrocode}
\UseTaggingSocket{sec/title/end}
}
}
}
% \end{macrocode}
% Redefinitions for book and report
% \begin{macrocode}
{
\RenewDocumentCommand\chapter{ s O{#3} m }
{
\if@openright\cleardoublepage\else\clearpage\fi
\thispagestyle{plain}%
\global\@topnum\z@
\@afterindentfalse
% \end{macrocode}
% This are the tagging commands needed at the begin. They open a Sect structure
% and the structure for the title of the heading.
% \begin{macrocode}
\UseTaggingSocket{sec/end}{0}
\UseTaggingSocket{sec/begin}{{0}{tag=\UseStructureName{sec/0}}}
\UseTaggingSocket{sec/title/begin}{{0}{#2}}
% \end{macrocode}
% This adds a manual target if the chapter is unnumbered or starred.
% It replaces the hyperref patches.
% \begin{macrocode}
\bool_lazy_any:nT
{
{ #1 }
{
\int_compare_p:nNn {\c@secnumdepth}<{0}
}
{
%in book target also needed in frontmatter
\bool_lazy_and_p:nn
{ \cs_if_exist_p:c { @mainmattertrue } }
{ ! \legacy_if_p:n { @mainmatter } }
}
}
{
% \end{macrocode}
% The relation target-struct is stored internally by the MakeLinkTarget commands
% \begin{macrocode}
\MakeLinkTarget[chapter]{}
}
% \end{macrocode}
% The main call to the underlying commands.
% \begin{macrocode}
\IfBooleanTF
{#1}
{ \@schapter {#3} }
{ \@chapter [{#2}]{#3} }
% \end{macrocode}
% and now the closing command for the tagging of the title.
% \begin{macrocode}
\UseTaggingSocket{sec/title/end}
}
% \end{macrocode}
% and similar for \cs{part}
% \begin{macrocode}
\RenewDocumentCommand\part{ s O{#3} m }
{
\if@openright
\cleardoublepage
\else
\clearpage
\fi
\thispagestyle{plain}%
\if@twocolumn
\onecolumn
\@tempswatrue
\else
\@tempswafalse
\fi
\null\vfil
% \end{macrocode}
% These are the tagging commands needed at the begin. They open a Part structure
% and the structure for the title of the heading.
% \begin{macrocode}
\UseTaggingSocket{sec/end}{-1}
\UseTaggingSocket{sec/begin}{{-1}{tag=\UseStructureName{sec/-1}}}
\UseTaggingSocket{sec/title/begin}{{-1}{#2}}
% \end{macrocode}
% This adds a manual target if the part is unnumbered or starred.
% It replaces the hyperref patches.
% \begin{macrocode}
\bool_lazy_any:nT
{
{ #1 }
{
\int_compare_p:nNn {\c@secnumdepth}<{-1}
}
{
%in book target also needed in frontmatter
\bool_lazy_and_p:nn
{ \cs_if_exist_p:c { @mainmattertrue } }
{ ! \legacy_if_p:n { @mainmatter } }
}
}
{
\MakeLinkTarget[part]{}
}
% \end{macrocode}
% The main call to the underlying commands.
% \begin{macrocode}
\IfBooleanTF
{#1}
{ \@spart {#3} }
{ \@part [{#2}]{#3} }
% \end{macrocode}
% and now the closing command for the tagging of the title.
% \begin{macrocode}
\UseTaggingSocket{sec/title/end}
}
}
}
% \end{macrocode}
%
% \subsection{Sectioning commands based on \cs{@startsection}}
%
% The tagging relies again on the para tagging:
% we simply exchange the tag name by the one given as \#1.
% This assumes that a tag with the name of the sectioning type is defined.
% We don't try to pass the title, this will be done together with
% the new keyval handling in the user command.
%
% \subsubsection{Hyperref code}
% hyperref has to insert anchors. If the sectioning is numbered this is done by
% \cs{refstepcounter} (and so in vmode). For unnumbered section hyperref
% injects the anchor in hmode before the text, it also inserts a
% kern to compensate the indent.
%
% This means that the target of numbered and unnumbered sectioning commands
% differ, both regarding the location and in relation to the
% tagging structure: The anchor from the \cs{refstepcounter} is outside of
% the structure created by the heading title if the para tags are used,
% while the other anchors are inside and so the structure destinations are different.
%
% We unify this by suppressing the anchor from the refstepcounter.
% Also we only go back if the indent is positive.
%
% At first suppress all hyperref patches related to sectioning:
% \begin{macrocode}
\def\hyper@nopatch@sectioning{}
% \end{macrocode}
%
% \begin{variable}{\l__kernel_sec_nonumber_bool}
% A boolean to keep track if a sectioning command should be numbered or not.
% \begin{macrocode}
\bool_new:N\l__kernel_sec_nonumber_bool
% \end{macrocode}
% \end{variable}
%
% \begin{macro}[no-user-doc]{\@hyp@section@target@nnn}
% A simple internal command. There is no need for something public,
% as packages defining their own version of \cs{@startsection} will
% probably need something slightly different based on \cs{MakeLinkTarget}.
% \changes{v0.84i}{2025/02/13}{Wrapped all use inside \cs{NoCaseChange},
% tagging-project issue \#787}
% \begin{macrocode}
\cs_new_protected:Npn \@hyp@section@target@nnn #1 #2 #3 %#1 optarg #2 name/counter, #3 indent
{
\makebox[0pt][l]
{
\skip_set:Nn \@tempskipa {#3}
\dim_compare:nNnF {\@tempskipa}<{0pt}{\kern-\@tempskipa}
\MakeLinkTarget#1{#2}
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}[no-user-doc]{\@@_makecurrentHref:n}
% In run-in heading we have to set the name of the anchor before it is actually
% created in a everypar. If hyperref is loaded we could use \cs{Hy@MakeCurrentHrefAuto}
% but without it we need a similar command.
% \begin{macrocode}
\cs_new_protected:Npn \@kernel@makecurrentHref #1 %#1 prefix
{
\int_gincr:N\g__kernel_target_int
\tl_gset:Ne \@currentHref {#1.\int_use:N\g__kernel_target_int}
}
% \end{macrocode}
% hyperref uses a different counter so we need to use a different command,
% TODO: merge that.
%
% \begin{macrocode}
\AddToHook{package/hyperref/after}
{
\cs_set_eq:NN \@kernel@makecurrentHref \Hy@MakeCurrentHrefAuto
}
% \end{macrocode}
% \end{macro}
% \subsection{Adaption of the heading commands}
% We add to \cs{@startsection} the commands to open the \texttt{Sect}
% structure and to change the para tag.
%
% We save the current level so that unnumbered sections can use it too.
% \begin{macro}{\@currentseclevel}
% \begin{macrocode}
\newcommand\@currentseclevel{-2}
% \end{macrocode}
% \end{macro}
% \begin{macrocode}
\def\@startsection#1#2#3#4#5#6{%
\def\@currentseclevel{#2}
\if@noskipsec \leavevmode \fi
\par
\@tempskipa #4\relax
\@afterindenttrue
\ifdim \@tempskipa <\z@
\@tempskipa -\@tempskipa \@afterindentfalse
\fi
\if@nobreak
\everypar{}%
\else
\addpenalty\@secpenalty\addvspace\@tempskipa
\fi
\UseTaggingSocket{sec/end}
{#2}
\UseTaggingSocket{sec/begin}
{
{#2}
{tag=\cs_if_exist_use:cF{g_tag_role_#1_tl}{Sect}}
}
% \end{macrocode}
% \changes{0.84j}{2025-10-07}{Moved para-flattened setting}
% This tagging sockets changes the para-tagging: it is flattened and
% the tag is changed. This is actually used only by runin headings,
% but nevertheless this looks like the best place to use it as we can
% not make the changes inside some \cs{everypar} command.
% \begin{macrocode}
\UseTaggingSocket{sec/title/init}{#2}
\@ifstar
{\@ssect{#3}{#4}{#5}{#6}}%
{\@dblarg{\@sect{#1}{#2}{#3}{#4}{#5}{#6}}}}
% \end{macrocode}
%
% \cs{@sect} is only changed to replace the hyperref patches
% and to use the new \cs{@kernel@tag@hangfrom}.
% \begin{macrocode}
%<@@=>
\def\@sect#1#2#3#4#5#6[#7]#8{%
% #1= name, #2= level, #3= indent #4 unused #5 after vspace #6 formatting #7=short title, #8=title
\ifnum #2>\c@secnumdepth
\bool_set_true:N\l__kernel_sec_nonumber_bool
\@kernel@makecurrentHref {#1*}
\def\@svsec{\NoCaseChange{\@hyp@section@target@nnn{*}{\@currentHref}{#3}}}
\else
\bool_set_false:N\l__kernel_sec_nonumber_bool
\LinkTargetOff
\refstepcounter{#1}%
\tl_gset:Ne\@currentHref{#1.\use:c{theH#1}}
\LinkTargetOn
\protected@edef\@svsec{\NoCaseChange{\@hyp@section@target@nnn{}{#1}{#3}}\@seccntformat{#1}\relax}%
\fi
\@tempskipa #5\relax
\ifdim \@tempskipa>\z@
\begingroup
#6{%
% \end{macrocode}
% The formatting can contain a \cs{MakeUppercase}
% so we must protect the name of the socket:
% \begin{macrocode}
\NoCaseChange
{\UseTaggingSocket{sec/title/hang}
{{#2}\l__kernel_sec_nonumber_bool{\hskip #3\relax\@svsec}{#7}}
{\@hangfrom {\hskip #3\relax\@svsec}}}
\interlinepenalty \@M #8\@@par}%
\endgroup
\csname #1mark\endcsname{#7}%
\addcontentsline{toc}{#1}{%
\ifnum #2>\c@secnumdepth \else
\protect\numberline{\csname the#1\endcsname}%
\fi
#7}%
\else
\def\@svsechd{%
#6{\hskip #3\relax
\NoCaseChange{
\UseTaggingSocket{sec/title/runin/number}{{#2}\l__kernel_sec_nonumber_bool}{\@svsec}}
#8}%
\csname #1mark\endcsname{#7}%
\addcontentsline{toc}{#1}{%
\ifnum #2>\c@secnumdepth \else
\protect\numberline{\csname the#1\endcsname}%
\fi
#7}}%
\fi
\@xsect{#5}}
% \end{macrocode}
% similar for \cs{@ssect}
% \begin{macrocode}
\def\@ssect#1#2#3#4#5{%
\@tempskipa #3\relax
\ifdim \@tempskipa>\z@
\begingroup
#4{
\NoCaseChange
{
\UseTaggingSocket{sec/title/hang}
{
{\@currentseclevel}
\c_true_bool
{\hskip #1\relax\NoCaseChange{\@hyp@section@target@nnn{[section]}{}{#1}}}
{#5}
}
{\@hangfrom{\hskip #1\relax\NoCaseChange{\@hyp@section@target@nnn{[section]}{}{#1}}}}
}
\interlinepenalty \@M #5\@@par}%
\endgroup
\else
\@kernel@makecurrentHref{section*}
\def\@svsechd{#4{\hskip #1\relax\NoCaseChange{\@hyp@section@target@nnn{*}{\@currentHref}{#3}}\relax #5}}%
\fi
\@xsect{#3}}
% \end{macrocode}
% At last \cs{@xsect} needs code in two places. For display headings it has to
% restore the default para code, for run in headings it has to separated the
% heading from the following text.
% \begin{macrocode}
\def\@xsect#1{%
\@tempskipa #1\relax
\ifdim \@tempskipa>\z@
\par \nobreak
\vskip \@tempskipa
\UseTaggingSocket {para/restore}
\@afterheading
\else
\@nobreakfalse
\global\@noskipsectrue
\everypar{%
\if@noskipsec
\global\@noskipsecfalse
{\setbox\z@\lastbox}%
\clubpenalty\@M
\begingroup \@svsechd \endgroup
\unskip
\UseTaggingSocket{sec/title/split}
\@tempskipa #1\relax
\hskip -\@tempskipa
\else
\clubpenalty \@clubpenalty
\everypar{}%
\fi}%
\fi
\ignorespaces}
% \end{macrocode}
%
% \subsection{Keys for \cs{tagpdfsetup}}
% We need to provide user and package level commands
% \changes{0.84k}{2025/04/09}{Add braces around key values, tagging issue \#830}
% \changes{0.84j}{2025-10-11}{add \cs{tagpdfsetup} keys for two user facing keys}
%
% \begin{macrocode}
\keys_define:nn{__tag / setup}
{
,sec/end .code:n =
{
\par
\UseTaggingSocket{sec/end}{\int_eval:n{\cs_if_exist_use:c{toclevel@#1}+0}}
}
,sec/end .value_required:n = true
,sec/grouping .choice:,
,sec/grouping / true .code:n =
{
\AssignTaggingSocketPlug{sec/begin}{kernel}
\AssignTaggingSocketPlug{sec/end}{kernel}
}
,sec/grouping / false .code:n =
{
\AssignTaggingSocketPlug{sec/begin}{noop}
\AssignTaggingSocketPlug{sec/end}{noop}
}
,sec/grouping .default:n = true
}
% \end{macrocode}
%
% \subsubsection{Tagging tools (deprecated)}
% \cs{tag_tool:n} is deprecated.
% \begin{macrocode}
\cs_if_free:NT \tag_tool:n
{
\cs_new_protected:Npn \tag_tool:n #1
{
\tag_if_active:T { \keys_set:nn {tag / tool}{#1} }
}
\cs_set_eq:NN\tagtool\tag_tool:n
}
\keys_define:nn { tag / tool}
{
,sec-start-part .code:n =
{
\UseTaggingSocket{sec/end}{-1}
\UseTaggingSocket{sec/begin}{{-1}{tag=\UseStructureName{sec/-1}}}
\UseTaggingSocket{sec/title/begin}{{-1}{#1}}
% \end{macrocode}
% We remap here the text-unit from the paragraph to NonStruct.
% It would be better to suppress it completely as with the other
% sectioning commands, but this would require to redefine \cs{@spart}
% and \cs{@part}, as there is the grouping, and these commands are
% all slightly different in the standard classes. So this is delayed
% to the time when sectioning commands are redefined with templates.
% \begin{macrocode}
}
,sec-stop-part .code:n = {\UseTaggingSocket{sec/title/end}}
,sec-start-chapter .code:n =
{
\UseTaggingSocket{sec/end}{0}
\UseTaggingSocket{sec/begin}{{0}{tag=\UseStructureName{sec/0}}}
\UseTaggingSocket{sec/title/begin}{{0}{#1}}
}
,sec-stop-chapter .meta:n = { sec-stop-part}
,sec-start .code:n = % #1 is a name like "section"
{
\UseTaggingSocket{sec/end} {\int_eval:n{\cs_if_exist_use:c{toclevel@#1}+0}}
\UseTaggingSocket{sec/begin}
{
{\int_eval:n{\cs_if_exist_use:c{toclevel@#1}+0}}
{tag=\cs_if_exist_use:cF{g_tag_role_#1_tl}{Sect}}
}
\tl_set:Nn\l_@@_para_tag_tl{#1}
}
,sec-start .value_required:n = true
,sec-split-para .code:n = {\UseTaggingsocket{sec/title/split}}
,restore-para .code:n = {\UseTaggingSocket{para/restore}}%
,sec-stop .code:n =
{
\par
\UseTaggingSocket{sec/end}{\int_eval:n{\cs_if_exist_use:c{toclevel@#1}+0}}
}
,sec-stop .value_required:n = true
,sec-add-grouping .choice:,
,sec-add-grouping / true .code:n =
{
\AssignTaggingSocketPlug{sec/begin}{kernel}
\AssignTaggingSocketPlug{sec/end}{kernel}
}
,sec-add-grouping / false .code:n =
{
\AssignTaggingSocketPlug{sec/begin}{noop}
\AssignTaggingSocketPlug{sec/end}{noop}
}
,sec-add-grouping .default:n = true
}
%</package>
% \end{macrocode}
% \begin{macrocode}
%<*latex-lab>
\ProvidesFile{sec-latex-lab-testphase.ltx}
[\ltlabsecdate\space v\ltlabsecversion\space latex-lab wrapper sec]
\RequirePackage{latex-lab-testphase-sec}
%</latex-lab>
% \end{macrocode}