% \iffalse meta-comment % % Copyright (C) 2023-2024 % The LaTeX Project and any individual authors listed elsewhere % in this file. % % This file is part of the LaTeX base system. % ------------------------------------------- % % It may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3c % of this license or (at your option) any later version. % The latest version of this license is in % https://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2008 or later. % % This file has the LPPL maintenance status "maintained". % % The list of all files belonging to the LaTeX base distribution is % given in the file `manifest.txt'. See also `legal.txt' for additional % information. % % The list of derived (unpacked) files belonging to the distribution % and covered by LPPL is defined by the unpacking scripts (with % extension .ins) which are part of the distribution. % % \fi % % \iffalse %%% From File: lttagging.dtx % %<*driver> % \fi \ProvidesFile{lttagging.dtx} [2023/12/19 v1.0a LaTeX Kernel (tagging support)] % \iffalse \documentclass{l3doc} \GetFileInfo{lttagging.dtx} \title{\filename} \date{\filedate} \author{\LaTeX{} project} \begin{document} % \MaintainedByLaTeXTeam{latex} % should be added again the moment % it is supported by l3doc \maketitle \DocInput{\filename} \end{document} % % \fi % % \providecommand\env[1]{\texttt{#1}} % % \providecommand\hook[1]{\texttt{#1\DescribeHook[noprint]{#1}}} % \providecommand\socket[1]{\texttt{#1\DescribeSocket[noprint]{#1}}} % \providecommand\plug[1]{\texttt{#1\DescribePlug[noprint]{#1}}} % % \let\ProvideDocElement\NewDocElement % % \ProvideDocElement[printtype=\textit{socket},idxtype=socket,idxgroup=Sockets]{Socket}{socketdecl} % \ProvideDocElement[printtype=\textit{hook},idxtype=hook,idxgroup=Hooks]{Hook}{hookdecl} % \ProvideDocElement[printtype=\textit{plug},idxtype=plug,idxgroup=Plugs]{Plug}{plugdecl} % % % \section{} % % % % \MaybeStop{} % % \begin{macrocode} %<*2ekernel|latexrelease> % \end{macrocode} % % \begin{macrocode} \ExplSyntaxOn % \end{macrocode} % % % \section{General support for tagged output} % % \DescribeMacro\SuspendTagging % \DescribeMacro\ResumeTagging % % The are places in code where it is import top stop any tagging % activities, e.g., when we are doing trial typesetting that it is % done several times. In such a case one must tag only the final % version that is actually used, otherwise tagging structures are % allowed which then do not end up in the PDF and confuse the % mechanism. For this we have two commands that can be used in % packages: \cs{SuspendTagging} and \cs{ResumeTagging}. They are % available as part of the \LaTeX{} kernel, so that they can be % safely used in packages whether or not tagging is requested .They % both take string argument that is used for debugging to easily % identify why tagging was suspended or restarted, for example, in % \pkg{tabularx} you find \verb=\SuspendTagging{tabularx}=. By default % they two commands do nothing. % % TODO: the corresponding L3 layer commands should also have a dummy % definition in the kernel! % % \DescribeMacro\UseTaggingSocket % \DescribeMacro\tag_socket_use:n % \DescribeMacro\tag_socket_use:nn % To support tagging in packages we use sockets with names starting % with \texttt{tagsupport/}. Usually, these sockets have exactly two % plugs defined: % \plug{noop} (when no tagging is requested or tagging is not wanted % for some reason) and a second plug that enables the tagging. There % may be more, e.g., tagging with special debugging, etc., but right % now it is usually just on or off. % % Given that we sometimes have to suspend tagging, it would be fairly % inefficient to put different plugs into these sockets whenever that % happens. We therefore offer \cs{UseTaggingSocket} which is like % \cs{UseSocket} except that the socket name is specified without % \texttt{tagsupport/}, i.e., % \begin{quote} % \verb=\UseTaggingSocket{foo}= $\to$ % \verb=\UseSocket{tagsupport/foo}= % \end{quote} % Beside being slightly shorter, the big advantage is that this way % we can change \cs{UseTaggingSocket} to do nothing when tagging is % suspended with \cs{SuspendTagging} instead of changing the plugs of % the tagging support sockets back and forth. % % It is possible to use the tagging support sockets with % \cs{UseSocket} directly, but in this case the socket remains active % if \cs{SuspendTagging} is in force. There my be reasons for doing % that but in general we expect to always use \cs{UseTaggingSocket}. % % The L3 programming layer versions \cs{tag_socket_use:n} and % \cs{tag_socket_use:nn} are slightly more efficient than % \cs{UseTaggingSocket} because they do not have to determine how % many arguments the socket takes when disabling it, so in code that % is using the L3 programming layer we recommend to use them instead % of the CamelCase command. % % % % \begin{macro}{\SuspendTagging,\ResumeTagging} % % In the kernel, these two commands get dummy definitions so that % they can be used without harm in packages. The real definition is % used when tagging gets enabled. % \begin{macrocode} \cs_new_eq:NN \SuspendTagging \use_none:n \cs_new_eq:NN \ResumeTagging \use_none:n % \end{macrocode} % % A simplified version of this defnition should move to % \pkg{tagpdf} and dropped here, eventually. % \begin{macrocode} \AddToHook{begindocument/before}{ \cs_if_exist:NT \tag_stop:n { \cs_set:Npn \SuspendTagging #1 { % \end{macrocode}% % This stops tagging and also disables all tagging sockets so we are done. % \begin{macrocode} \tag_stop:n {#1} } % \end{macrocode} % % \begin{macrocode} \cs_set:Npn \ResumeTagging #1 { \tag_start:n {#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\tag_socket_use:n, % \tag_socket_use:nn, % \UseTaggingSocket, % } % Again this is not the final definition for the kernel; it is just % a version to get going while some parts of the kernel support are % still missing. % \begin{macrocode} \AddToHook{begindocument}[kernel]{ \cs_if_exist:NF \tag_if_active:T { \prg_new_conditional:Npnn \tag_if_active: { p , T , TF, F } { \prg_return_false: } } } % \end{macrocode} % % Dummy definitions in the kernel. % These definitions will get updated in \pkg{tagpdf}. % \begin{macrocode} \cs_new_protected:Npn \tag_socket_use:n #1 { } \cs_new_protected:Npn \tag_socket_use:nn #1#2 { } % \end{macrocode} % The default in the kernel is just to get rid of the argument: % \begin{macrocode} \cs_new_protected:Npn \UseTaggingSocket #1 { \int_case:nnF { \int_use:c { c__socket_tagsupport/#1_args_int } } { 0 \prg_do_nothing: 1 \use_none:n 2 \use_none:nn % \end{macrocode} % We do not expect tagging sockets with more than one or two % arguments, so for now we only provide those. % \begin{macrocode} } \ERRORusetaggingsocket % that should get a proper error message } \ExplSyntaxOff % \end{macrocode} % % \end{macro} % % % % % % % \subsection{Tagging support for table/tabular packages} % % The code uses a number of sockets to inject the tagging % commands. These can be easily set to a noop-plug in case the % automated tagging is not wanted. % % \begin{socketdecl}{tagsupport/tbl/cell/begin, % tagsupport/tbl/cell/end, % tagsupport/tbl/pcell/end, % tagsupport/tbl/pcell/end, % tagsupport/tbl/row/begin, % tagsupport/tbl/row/end, % } % At first sockets for the begin and end of cells and table rows: % \begin{macrocode} \NewSocket{tagsupport/tbl/cell/begin}{0} \NewSocket{tagsupport/tbl/cell/end}{0} % \end{macrocode} % % \begin{macrocode} \NewSocket{tagsupport/tbl/row/begin}{0} \NewSocket{tagsupport/tbl/row/end}{0} % \end{macrocode} % Multi-line cells have their own sockets (as they start out in % vertical mode and need different treatment). % \begin{macrocode} \NewSocket{tagsupport/tbl/pcell/begin}{0} \NewSocket{tagsupport/tbl/pcell/end}{0} % \end{macrocode} % \end{socketdecl} % % \begin{socketdecl}{tagsupport/tbl/init} % This socket should be at the begin of the table, inside a group. % It is used for settings such as disabling para-tagging inside the % table. This socket % can perhaps be merged later into the begin-sockets. % \begin{macrocode} \NewSocket{tagsupport/tbl/init}{0} % \end{macrocode} % \end{socketdecl} % % % \begin{socketdecl}{tagsupport/tbl/finalize} % To fine tune the structure (change cells to header cells, remove % unwanted structures, move a foot to the end, etc.). We also need a % socket that is executed at the end of the table but \emph{before} % all the variables are restored to the outer or default values. % The code in the socket can make assignments, but probably % shouldn't do typesetting and not write whatsits. % \begin{macrocode} \NewSocket{tagsupport/tbl/finalize}{0} % \end{macrocode} % \end{socketdecl} % % \begin{socketdecl}{tagsupport/tbl/colspan} % This socket is used to manage spanning cells, e.g., a % \cs{multicolumn}. It expects one argument (the number of cells % spanned) and if tagging is enabled set appropriate tag attributes % in the background. We probably need a similar socket for row % spans eventually. % \begin{macrocode} \NewSocket{tagsupport/tbl/colspan}{1} % \end{macrocode} % \end{socketdecl} % % \begin{socketdecl}{tagsupport/tbl/hmode/begin, % tagsupport/tbl/hmode/end, % tagsupport/tbl/vmode/begin, % tagsupport/tbl/vmode/end % } % % These sockets are used in the begin and end code of environments, % to allow a fast enabling and disabling of the tagging. We % distinguish between tables that can be used inside paragraphs and % standalone tables such as \env{longtable} that are always in % vertical mode. % \begin{macrocode} \NewSocket{tagsupport/tbl/hmode/begin}{0} \NewSocket{tagsupport/tbl/hmode/end}{0} \NewSocket{tagsupport/tbl/vmode/begin}{0} \NewSocket{tagsupport/tbl/vmode/end}{0} % \end{macrocode} % \end{socketdecl} % % % % \begin{socketdecl}{tagsupport/tbl/longtable/init, % tagsupport/tbl/longtable/finalize} % \env{longtable} needs its own sockets to fine tune the structure. % Simply switching the plug in the previous socket interferes with % enabling/disabling the tagging. % \begin{macrocode} \NewSocket{tagsupport/tbl/longtable/init}{0} \NewSocket{tagsupport/tbl/longtable/finalize}{0} % \end{macrocode} % \end{socketdecl} % % \begin{socketdecl}{tagsupport/tbl/longtable/head, % tagsupport/tbl/longtable/foot} % Header and footer boxes need special handling because they are repeatedly % used. % \begin{macrocode} \NewSocket{tagsupport/tbl/longtable/head}{0} \NewSocket{tagsupport/tbl/longtable/foot}{0} % \end{macrocode} % \end{socketdecl} % % % % % % \section{For lttab.dtx parked here for now} % % % \begin{macrocode} %<@@=tbl> \ExplSyntaxOn % \end{macrocode} % % % \subsection{Variables for row, column and span counting} % % This part needs a decision on names for various integer registers % as well as a decision if those should be also made available for % \LaTeXe{}-style packages in form of 2e names and or as % non-internals for the L3 programming layer. % % At the moment they are all internal but this probably has to change. % % \begin{macro}{ % \g_@@_col_int, % \g_@@_row_int, % \g_@@_span_tl, % \g_@@_table_cols_tl} % % \cs{g_@@_row_int} holds the current row number in the table. The % value \texttt{0} means we haven't yet processed the table % preamble (or in case of longtable are just in front of the next % chunk to be processed). It is incremented by every \cs{cr} % including the one ending the table preamble. % % TODO: due to the gymnastics needed inside the longtable code the % row counter is directly exposed there rather than hidden by % interfaces. This needs changing when it is decided how to manage % these counters. % % \cs{g_@@_col_int} holds the current column number. The value % \texttt{0} means we have not yet started the table or just finished a table row % (with \verb=\\= typically); any other positive value means we % are currently typesetting a cell in that column in some row % (denoted by the \cs{g_@@_row_int}). % % In a \cs{multicolumn} it holds the column number of the first % spanned column and \cs{g_@@_span_tl} the info how many cells are % spanned. % % \cs{g_@@_span_tl} is normally \texttt{1} except in a % \cs{multicolumn} cell. % \begin{macrocode} \int_new:N \g_@@_col_int \int_new:N \g_@@_row_int \tl_new:N \g_@@_span_tl \tl_new:N \g_@@_table_cols_tl \tl_gset:Nn \g_@@_span_tl {1} \tl_gset:Nn \g_@@_table_cols_tl {0} % indicates outer level % \end{macrocode} % \end{macro} % % % \begin{macro}{\l_@@_saved_col_tl,\l_@@_saved_row_tl, % \l_@@_saved_span_tl,\l_@@_saved_table_cols_tl} % % Saving the outer values if we are nesting tables is necessary (as % the above variables are globally altered). For this we always use % token lists because they don't change and we do not need to blow % additional integer registers. % \begin{macrocode} \tl_new:N \l_@@_saved_col_tl \tl_new:N \l_@@_saved_row_tl \tl_new:N \l_@@_saved_span_tl \tl_new:N \l_@@_saved_table_cols_tl \tl_set:Nn \l_@@_saved_col_tl{0} \tl_set:Nn \l_@@_saved_row_tl{0} \tl_set:Nn \l_@@_saved_span_tl{1} \tl_set:Nn \l_@@_saved_table_cols_tl{0} % indicates outer level % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_missingcells_int} % This will contain the number of missing cells in a row: % \begin{macrocode} \int_new:N \g_@@_missing_cells_int % \end{macrocode} % \end{macro} % % % % % % \subsection{Tracing/debugging} % % \begin{macro}{\DebugTablesOn,\DebugTablesOff} % % \begin{macrocode} \def\DebugTablesOn{ \cs_set_eq:NN \@@_trace:n \typeout } \def\DebugTablesOff{ \cs_set_eq:NN \@@_trace:n \use_none:n } % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@@_trace:n \use_none:n % \end{macrocode} % \end{macro} % % % % \subsection{Interface commands} % % All interface commands for the cell number determination have to be % public on some level because they are needed in other packages as % well, e.g., longtable. We may or may not also want to provide 2e % style names for them. % % \begin{macro}{\tbl_update_cell_data:} % Updating cell data in columns after the first means we have to % increment the \cs{g_@@_col_int} by the span count of the previous % cell (in case it was a \cs{multicolumn}) and then reset the % \cs{g_@@_span_tl} to one (as the default). % \begin{macrocode} \cs_new_protected:Npn \tbl_update_cell_data: { \int_gadd:Nn \g_@@_col_int { \g_@@_span_tl } \tl_gset:Nn \g_@@_span_tl {1} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\tbl_count_table_cols:} % Current implementation of \cs{@mkpream} uses the scratch counter % \cs{count@} to keep track of the number of toks registers it needs % (2 per column), but this can't be used as it counts also % insertions made with \verb+!{}+ and \verb+@{}+. % So similar as does longtable for \cs{LT@cols} we count the % numbers of ampersands instead. % \begin{macrocode} \cs_new:Npn \tbl_count_table_cols: { \seq_set_split:NnV\l_@@_tmpa_seq {&}\@preamble \tl_gset:Ne \g_@@_table_cols_tl { \seq_count:N \l_@@_tmpa_seq } \@@_trace:n { ==>~ Table~ has~ \g_@@_table_cols_tl \space columns } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\l_@@_tmpa_seq} % % \begin{macrocode} \seq_new:N \l_@@_tmpa_seq % \end{macrocode} % \end{macro} % % % % \begin{macro}{\tbl_count_missing_cells:n} % % We might have the situation that some table package has not % implemented the \cs{tbl_count_table_cols:} in which case % \cs{g_@@_table_cols_tl} would always be zero and we would get an % error below when we try to determine the missing cells, so bypass % that calculation if we aren't doing tagging (there the packages % should have the proper code added). Recall that this is code, % that is called by \verb=\\= and an old table packagee might rely % on whatever the \LaTeX{} kernel offers here. % \begin{macrocode} \cs_new:Npn \tbl_count_missing_cells:n #1 { \tag_if_active:T { \int_compare:nNnT \g_@@_col_int > 0 { \int_gset:Nn \g_@@_missing_cells_int { \g_@@_table_cols_tl - \g_@@_col_int - \g_@@_span_tl + 1 } \int_compare:nNnT \g_@@_missing_cells_int < 0 \ERRORmissingcells % should not happen \@@_trace:n{==>~ (#1)~ This~ row~ needs~ \int_use:N \g_@@_missing_cells_int \space additional~ cell(s) } } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\tbl_save_outer_table_cols:} % % \begin{macrocode} \cs_new_protected:Npn \tbl_save_outer_table_cols: { \tl_set_eq:NN \l_@@_saved_table_cols_tl \g_@@_table_cols_tl } % \end{macrocode} % \end{macro} % % % \begin{macro}{\tbl_init_cell_data_for_table:} % % \begin{macrocode} \cs_new_protected:Npn \tbl_init_cell_data_for_table: { \tl_set:No \l_@@_saved_col_tl {\int_use:N \g_@@_col_int } \tl_set:No \l_@@_saved_row_tl {\int_use:N \g_@@_row_int } \tl_set_eq:NN \l_@@_saved_span_tl \g_@@_span_tl % \@@_trace:n { ==>~ saved~cell~data:~ \l_@@_saved_row_tl, \l_@@_saved_col_tl, \l_@@_saved_span_tl \space ( \int_compare:nNnTF \l_@@_saved_table_cols_tl = 0 { outer~ level } { max:~ \l_@@_saved_table_cols_tl } ) } % \end{macrocode} % These are the initial values when starting a table: % \begin{macrocode} \int_gzero:N \g_@@_row_int \int_gzero:N \g_@@_col_int \tl_gset:Nn \g_@@_span_tl {1} } % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\tbl_update_cell_data_for_next_row:} % \begin{macrocode} \cs_new_protected:Npn \tbl_update_cell_data_for_next_row: { \int_gincr:N \g_@@_row_int % this row about to start \int_gzero:N \g_@@_col_int % we are before first col } % \end{macrocode} % \end{macro} % % % \begin{macro}{\tbl_init_cell_data_for_row:} % If we start processing a cell in the first column we set % \cs{g_@@_col_int} to \texttt{1} as we are no longer "at" but "in" % the first column. We also set \cs{g_@@_span_tl} to its default % value (not spanning cells). % \begin{macrocode} \cs_new_protected:Npn \tbl_init_cell_data_for_row: { \int_gset:Nn \g_@@_col_int {1} \tl_gset:Nn \g_@@_span_tl {1} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\tbl_if_row_was_started:T, % \tbl_if_row_was_started:TF} % We use \cs{g_@@_col_int} equal zero to indicate that we are just % after a TR (i.e.n between rows or at the very beginning of the % table). Using the row % count is not so good as longtable may split the table in chunks. % % These conditionals have to be expandable (i.e., unprotected) as % they are sometimes executed when \TeX{} is scanning inside a table. % \begin{macrocode} \cs_new:Npn \tbl_if_row_was_started:T { \int_compare:nNnT \g_@@_col_int > 0 } \cs_new:Npn \tbl_if_row_was_started:TF { \int_compare:nNnTF \g_@@_col_int > 0 } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\tbl_gzero_row_count:,\tbl_gincr_row_count:,\tbl_gdecr_row_count:} % This here is basically a temporary interface. What it will be in % the end depends on what we decide concerning exposing row and % column counters, if they stay internal we need something like % this here (perhaps using \texttt{gincr} etc, or perhaps some % other names in the first place). % \begin{macrocode} \cs_new_protected:Npn \tbl_gzero_row_count: { \int_gzero:N \g_@@_row_int } \cs_new_protected:Npn \tbl_gincr_row_count: { \int_gincr:N \g_@@_row_int } \cs_new_protected:Npn \tbl_gdecr_row_count: { \int_gdecr:N \g_@@_row_int } % \end{macrocode} % \end{macro} % % % \begin{macro}{\tbl_inbetween_rows:} % Again name is not really brilliant so far. % \begin{macrocode} \cs_new_protected:Npn \tbl_inbetween_rows: { \int_gzero:N \g_@@_col_int } % \end{macrocode} % \end{macro} % % % % % % % \begin{macro}{\tbl_restore_outer_cell_data:} % % \begin{macrocode} \cs_new_protected:Npn \tbl_restore_outer_cell_data: { \int_gset:Nn \g_@@_col_int { \l_@@_saved_col_tl } \int_gset:Nn \g_@@_row_int { \l_@@_saved_row_tl } \tl_gset_eq:NN \g_@@_span_tl \l_@@_saved_span_tl \tl_gset_eq:NN \g_@@_table_cols_tl \l_@@_saved_table_cols_tl \@@_trace:n { ==>~ restored~cell~data:~ \int_use:N \g_@@_row_int, \int_use:N \g_@@_col_int, \l_@@_saved_span_tl \space ( \int_compare:nNnTF \g_@@_table_cols_tl = 0 { outer~ level } { max:~ \g_@@_table_cols_tl } ) } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\tbl_update_multicolumn_cell_data:n} % This macro updates \cs{g_@@_col_int} and \cs{g_@@_span_tl} inside % a \cs{multicolumn} and possibly calls the tagging socket % \texttt{tbl/row/begin}. % \begin{macrocode} \cs_new_protected:Npn \tbl_update_multicolumn_cell_data:n #1 { % \end{macrocode} % We execute socket for tagging only if this \cs{multicolumn} % replaces the preamble of the first column. In that case we also have % to set \cs{g_@@_col_int} to 1 because this is no longer done in the % preamble for the cell either. % \begin{macrocode} \int_compare:nNnTF \g_@@_col_int = 0 { \UseTaggingSocket{tbl/row/begin} \int_gset:Nn \g_@@_col_int {1} } % \end{macrocode} % If we are in a later column we use \cs{g_@@_span_tl} from the % previous column to update. % \begin{macrocode} { \int_gadd:Nn \g_@@_col_int { \g_@@_span_tl } } % \end{macrocode} % Then we set the span value so that it can be use in the next column. % \begin{macrocode} \tl_gset:Nn \g_@@_span_tl {#1} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\tbl_crcr:n} % This macro is used instead of the usual \cs{crcr} at the end of a % table. It is deliberately defined without protection because it % may get expanded by the scanning mechanism of low-level \TeX{} % after a final \cs{cr} (aka \verb=\\=) in the table. In that case % it shouldn't stop the expansion and the conditional inside will % be false, thus it just vanishes without doing anything. If there % are missing cells (in which case we also haven't see \cs{cr} yet) % the macro \cs{tbl_count_missing_cells:n} is executed and % then the row is finished with a final \cs{cr}. % \begin{macrocode} \cs_new:Npn \tbl_crcr:n #1 { \int_compare:nNnT \g_@@_col_int > 0 { \tbl_count_missing_cells:n {#1} \cr } } % \end{macrocode} % \end{macro} % % % % \begin{macrocode} \ExplSyntaxOff %<@@=> % \end{macrocode} % % This is needed for \pkg{longtable} because \cs{refstepcounter} is % setting up a target when \pkg{hyperref} is loaded and we don't % want that in \pkg{longtable}. % % TODO: move to right .dtx file % \begin{macrocode} \let\@kernel@refstepcounter\refstepcounter % \end{macrocode} % Prevent longtable patching by hyperref until hyperref does so automatically: % \begin{macrocode} \def\hyper@nopatch@longtable{} % \end{macrocode} % % % Should there be a module? % % \begin{macrocode} %\NewModuleRelease{2024/06/01}{lttagging} % {Tagging support} % \end{macrocode} % % % % % \begin{macrocode} %\IncludeInRelease{0000/00/00}{lttagging}% % {Undo tagging support} % % % %\EndModuleRelease % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \Finale %