% \iffalse meta-comment % %% File: latex-lab-context.dtx (C) Copyright 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 latex-lab bundle is developed in the LaTeX2e GitHub. % Issues may be reported at % % https://github.com/latex3/latex2e/issues % \def\ltlabcontextversion{0.6a} \def\ltlabcontextdate{2026-05-26} %<*driver> \DocumentMetadata{tagging=on,pdfstandard=ua-2,lang=en} \documentclass{l3in2edoc} \EnableCrossrefs \CodelineIndex \begin{document} \DocInput{latex-lab-context.dtx} \end{document} % % % \fi % % \title{The \textsf{latex-lab-context} package\\ % Providing context for template instances and code that needs % to know where and when it is executed} % % \author{\LaTeX{} Project\thanks{Initial implementation done by Frank Mittelbach}} % \date{v\ltlabcontextversion\ \ltlabcontextdate} % % \maketitle % % \newcommand{\xt}[1]{\textsl{\textsf{#1}}} % \newcommand{\TODO}[1]{\textbf{[TODO:} #1\textbf{]}} % % \providecommand\hook[1]{\texttt{#1}} % % \begin{abstract} % \end{abstract} % % \tableofcontents % % \section{Introduction} % % In this module we implement the concept of \enquote{contexts} % within the document and depending on the current context % formatting behavior might change. The main application for this % is in instances of templates (where things can be easily automated % to react to context), but it is probably also applicable to other % situations (but these might need more thought). % % \subsection{Definition of a \enquote{context}} % % The \enquote{context} is an attribute of every point of the % formatted document, i.e., at each point during the formatting one % can determine in which \enquote{context} the formatting happens % and based on this adjust the formatting method. % % The \enquote{context} is not an entirely flat structure: we % distinguish between the \enquote{primary context}, the % \enquote{secondary context}, and \enquote{tertiary context}, all of % which can be changed individually based on a number of rules as % discussed below. % % Any context is denoted by a name (\verb=[a-z]*= letters only). The % empty name is allowed to ease specification of the common case % (i.e., the default \enquote{primary context} is the main galley % and by default no \enquote{secondary context} and % \enquote{tertiary context} are specified). % % \subsection{The \enquote{primary context}} % % The \enquote{primary context} is described with a fixed (but % extensible?) set of names: % \begin{itemize} % % \item \meta{empty} denotes that we are in \enquote{galley} context % producing text for the page; % % \item \texttt{caption} % denotes that we are typesetting the caption text of a \cs{caption} command % --- this is also used for captions outside of floats, % % \item \texttt{footnote} % denotes that we are typesetting the footnote text; % % \item \texttt{float} % denotes that we are typesetting a float; % % \item \texttt{marginal} % denotes that we are typesetting a marginal; % % \item \texttt{header} % denotes that we are typesetting the running header; % % \item \texttt{footer} % denotes that we are typesetting the running footer. % % \end{itemize} % % When the \enquote{primary context} is set it replaces the current % \enquote{primary context} and resets the \enquote{secondary % context} and \enquote{tertiary context} to \meta{empty}. The % setting is local, i.e., it obeys grouping. % % We may want to require that a context name is declared before its % first use, e.g., via \cs{DeclarePrimaryContext} (right now this is % not done). % % % \subsection{The \enquote{secondary context}} % % The \enquote{secondary context} can be used to further % sub-structure the primary context, e.g., with \texttt{float} as a % primary, \texttt{figure} and \texttt{table} (e.g., the float types % could form a secondary context). % % % % \subsection{The \enquote{tertiary (font size) context}} % % The \enquote{tertiary context} concerns itself only with font size % changes, because that is most commonly a cause for layout changes. % It is described through a fixed (but extensible) set of names: % % \begin{itemize} % \item % \meta{empty} denotes that there is no special tertiary % context, i.e., that we are producing material in \cs{normalsize}; % % \item \texttt{tiny} % denotes that we are typesetting in \cs{tiny} font size; % % \item and similarly for \texttt{scriptsize}, % \texttt{footnotesize}, \texttt{small}, \texttt{large}, % \texttt{Large}, \texttt{LARGE}, \texttt{huge}, and \texttt{Huge}. % \end{itemize} % The above list of tertiary contexts are automatically set by the % standard \LaTeX{} font size commands (as part of % \cs{@setfontsize}). Classes that use additional font size commands % but do not use the \LaTeXe{} command for this need to explicitly % set the tertiary context with \cs{SetTertiaryContext} if they % want their size be recognized as a context. % % The \enquote{tertiary context} serves a double role: on the one % hand it substructures the primary and secondary contexts further, % e.g., % \meta{primary}\texttt{:}\meta{secondary}\texttt{:}\meta{tertiary} % allows you to specify different settings compared to % \meta{primary}\texttt{:}\meta{secondary} % but if there are only instance declarations for % \meta{primary} % or % \meta{primary}\texttt{:}\meta{secondary} % and for % \texttt{::}\meta{tertiary} % then the latter is chosen over the the former. % % % % \section{Setting context} % % \begin{function}{\SetPrimaryContext} % \begin{syntax} % \cs{SetPrimaryContext} \Arg{new context} % \end{syntax} % The \meta{new context} should be a name consisting only of % lowercase letters a--z (and to make sense only a name that is % actually used---typos will go undetected). % % This declaration checks if there is a rule for the combination of % the current context and the requested \meta{new context} (see % \cs{DeclarePrimaryContextRule}) and if so applies it to determine % to which context the \enquote{primary context} should be set. % If there is no such rule then \meta{new context} will be used % unconditionally. % % As a second action the command resets the \enquote{secondary % context} and \enquote{teriary context} to \meta{empty}, i.e., it % clears the lower-level contexts. This is only done for the primary % context. The reason is that when a primary context changes any % sub-division (secondary and tertiary context) is no longer % applicable and should therefore be reset. % % The effects are local, i.e., both context changes revert to the % previous value at the end of the current group. % \end{function} % % % % \begin{function}{\DeclarePrimaryContextRule} % \begin{syntax} % \cs{DeclarePrimaryContextRule} \Arg{current}\Arg{new}\Arg{result} % \end{syntax} % This declaration defines the rule that when the current primary % context is \meta{current} and \meta{new} is requested as the new % context then instead \meta{result} is made the new % \enquote{primary context}. % % If \texttt{*} is used in the first argument then this indicates % any current context and thus \meta{new} is always replaced by % \meta{result}. This can be more concisely written as % \begin{quote} % \cs{DeclarePrimaryContextRule*} \Arg{new}\Arg{result} % \end{quote} % if preferred. % % Declaring such rules is a global operation. % \end{function} % % % % \begin{function}{\SetSecondaryContext} % \begin{syntax} % \cs{SetSecondaryContext} \Arg{new context} % \end{syntax} % The \meta{new context} should be a name consisting only of % lowercase letters a--z (and to make sense only a name that is % actually used---typos will go undetected). % % This declaration checks if there is a rule for the combination of % the current secondary context and the requested \meta{new context} % (see \cs{DeclareSecondaryContextRule}) and if so applies it to % determine to which context the \enquote{secondary context} should % be set. If there is no such rule then \meta{new context} will % be used unconditionally. % % This does not reset the \enquote{tertiary context}. % % The effect is local, i.e., the context change reverts to the % previous value at the end of the current group. % \end{function} % % % % \begin{function}{\DeclareSecondaryContextRule} % \begin{syntax} % \cs{DeclareSecondaryContextRule} \Arg{current}\Arg{new}\Arg{result} % \end{syntax} % This declaration defines the rule that when the current secondary % context is \meta{current} and \meta{new} is requested as the new % context then instead \meta{result} is made the new % \enquote{secondary context}. % % If \texttt{*} is used in the first argument then this indicates % any current context and thus \meta{new} is always replaced by % \meta{result}. % % Declaring such rules is a global operation. % % TODO: we should perhaps provide % \begin{quote} % \cs{DeclareSecondaryContextRule} \Arg{current % primary}\Arg{current secondary}\Arg{new secondary}\Arg{result} % \end{quote} % instead of the above. This would be a breaking change, but as this % is currently only a trial implementation that should be ok. % \end{function} % % % % \section{Context usage in template instances} % % If a template instance is used via % \cs{UseInstance}\Arg{type}\Arg{inst-name} then this normally % results in calling up an instance of type \meta{type} with the % name \meta{inst-name}.\footnote{Such instances are defined with % \cs{DeclareInstance} or \cs{DeclareInstanceCopy}, see the % documentation in \texttt{lttemplates-doc.pdf}.} % % However, when the \enquote{primary context} and/or the % \enquote{secondary context} or \enquote{tertiary context} is % non-empty then \cs{UseInstance} searches for an instance that is % especially tailored to the current context. This works as % follows: % \begin{itemize} % \item % % The string \texttt{:}\meta{primary % context}\texttt{:}\meta{secondary % context}\texttt{:}\meta{tertiary context} is appended to % \meta{inst-name} and if that instance exist it is % used.\footnote{Note that this means that if the \meta{primary % context} and \enquote{secondary context} are empty we % effectively append \texttt{:::}\meta{tertiary context}.} % % \item % % If not then \meta{inst-name}\texttt{:}\meta{primary % context}\texttt{:}\meta{secondary context} is tried next. % % \item % % If not then \meta{inst-name}\texttt{:}\meta{primary context} is % tried next. % % \item % % If that doesn't exist either than the \enquote{tertiary context} % is considered once more on its own by checking for % \meta{inst-name}\texttt{:::}\meta{tertiary context} and using % that if it exist. % % \item % Otherwise \meta{inst-name} is used as usual. % % \end{itemize} % % This means it becomes trivial to alter the behavior of % instances if they appear in a special typesetting context. For % example, in \LaTeXe{} display blocks, e.g., lists, center, etc., all % changed their vertical spacing setup if the surrounding text was % in \cs{small} or in \cs{footnotesize} (did nothing if you % typeset in, say, \cs{Large} which had the strange effect that % lists in \cs{Large} or \cs{huge} got whatever was set by a size % command that changed list parameters). % % With the new context model all you have to do is to provide % additional instances, e.g., if \texttt{itemize-1} is the instance % name for itemized lists on the first level then % \texttt{itemize-1:footnote} would be the instance to be used if an % itemize environment is used within a footnote. % % In addition (or alternatively) you could setup % \texttt{itemize-1:::Large} which would be selected if itemize is % used anywhere and the fontsize is \cs{Large} (and there are no % explicit specifications for the context otherwise). % % % \section{Notes} % % With special classes, e.g., \texttt{ltx-talk}, additional primary % and secondary contexts could be added (e.g., using the modes % there).Could do with some experimentation what works best. % % If the design of a class requires identical behavior in different % contexts, e.g., the same behavior in \texttt{header} and % \texttt{footer}, it is possible to simplify the setup by only % specifying the design for the \texttt{header} context and then % declaring: %\begin{verbatim} % \DeclarePrimaryContextRule{*}{footer}{header} %\end{verbatim} % % One can also add new primary or secondary contexts simply by making % use of \cs{Set...Context} declarations with a new name and then % use these named contexts when setting up special instance % versions. % % Bottom line, what is here is nothing more than a first, or rather % by now, second prototype and likely to change to some extent. % % % % \section{Currently mainly internal interfaces} % % \begin{variable}{\l_context_primary_str} % This variable holds the current primary context name (or is empty % if we are in the context of the main galley). % \end{variable} % % \begin{variable}{\l_context_secondary_str} % A sub-structure of the primary context, e.g., for the % \texttt{float} context the secondary could be the float type. % \end{variable} % % \begin{variable}{\l_context_tertiary_str} % This variable holds the current tertiary (font size) context name (or is % empty if we are typesetting in \cs{normalsize}). It is by default % automatically set by all fontsize commands in core \LaTeX{}. % \end{variable} % \section{Debugging} % % \begin{function}{\DebugContextsOn,\DebugContextsOff, \context_debug_on:, \context_debug_off:} % % These commands enable/disable debugging messages for context related % processing. % % \end{function} % % % \section{Changes to internals of \LaTeX} % % \begin{function}{\@setfontsize} % This command has be augmented to execute \cs{SetSecondaryContext} % with the name of the current fontsize command as the context argument. % \end{function} % % \begin{function}{\@float} % % This command has been augmented to execute % \cs{SetPrimaryContext}\texttt{\{float\}}. This results in the % body of all real floats being typeset in the primary % \texttt{float} context. Not covered are packages or methods that % make floats not using \cs{@float} or making pseudo floats, e.g., % something not floating but having a caption. % \end{function} % % % % % % \section{Implementation} % \begin{macrocode} %<*package> %<@@=tag> % \end{macrocode} % \begin{macrocode} \ProvidesExplPackage {latex-lab-testphase-context} {\ltlabcontextdate} {\ltlabcontextversion} {Providing context for instance, etc.} % \end{macrocode} % % \begin{macrocode} %<*package> %<@@=context> % \end{macrocode} % % \changes{v0.5c}{2026-03-12}{Switch from \texttt{x}- to \texttt{e}-type % expansion in expl3 code} % % % \subsubsection{Debugging} % % % \begin{variable}{\g_@@_debug_bool} % % \begin{macrocode} \bool_new:N \g_@@_debug_bool % \end{macrocode} % \end{variable} % % % \begin{macro}{\@@_debug:n,\@@_debug_typeout:n} % % \begin{macrocode} \cs_new_eq:NN \@@_debug:n \use_none:n \cs_new_eq:NN \@@_debug_typeout:n \use_none:n % \end{macrocode} % \end{macro} % % \begin{macro}{\context_debug_on:,\context_debug_off:, % \@@_debug_gset:} % \begin{macrocode} \cs_new_protected:Npn \context_debug_on: { \bool_gset_true:N \g_@@_debug_bool \@@_debug_gset: } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \context_debug_off: { \bool_gset_false:N \g_@@_debug_bool \@@_debug_gset: } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_debug_gset: { \cs_gset_protected:Npe \@@_debug:n ##1 { \bool_if:NT \g_@@_debug_bool {##1} } \cs_gset_protected:Npe \@@_debug_typeout:n ##1 { \bool_if:NT \g_@@_debug_bool { \typeout{[Context]~ ==>~ ##1} } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\DebugContextsOn,\DebugContextsOff} % % \begin{macrocode} \cs_new_protected:Npn \DebugContextsOn { \context_debug_on: } \cs_new_protected:Npn \DebugContextsOff { \context_debug_off: } % \end{macrocode} % % \begin{macrocode} \DebugContextsOff % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\l_context_primary_str} % Variable to hold the name of the current primary context. % \begin{macrocode} \str_new:N \l_context_primary_str % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\SetPrimaryContext} % Change the context: if the target context is empty, just do % it. Otherwise if we have a rule for the current context and new context % use that; otherwise if there is a star rule apply that; otherwise % set the new value as requested, % \begin{macrocode} \cs_new_protected:Npn \SetPrimaryContext #1 { \str_set:Ne \l_context_primary_str { \tl_if_empty:nF { #1 } { \cs_if_exist_use:cF { g_@@_primary_ \l_context_primary_str _ #1 _tl } { \cs_if_exist_use:cF { g_@@_primary_ * _ #1 _tl } { #1 } } } } \@@_debug_typeout:n{set~primary~ <-~ \l_context_primary_str } % \end{macrocode} % Also reset the secondary and tertiary context. % \begin{macrocode} \SetSecondaryContext {} \SetTertiaryContext {} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\DeclarePrimaryContextRule} % Rules are simply stored in macro names: % \begin{macrocode} \cs_new_protected:Npn \DeclarePrimaryContextRule #1#2#3 { \tl_gclear_new:c { g_@@_primary_ #1 _ #2 _tl } \tl_gset:cn { g_@@_primary_ #1 _ #2 _tl } {#3} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\l_context_secondary_str} % Variable to hold the name of the current secondary context. % \begin{macrocode} \str_new:N \l_context_secondary_str % \end{macrocode} % \end{macro} % % % \begin{macro}{\SetSecondaryContext} % Similar to \cs{SetPrimaryContext} but without resetting the % tertiary context. % \begin{macrocode} \cs_new_protected:Npn \SetSecondaryContext #1 { \str_set:Ne \l_context_secondary_str { \tl_if_empty:nF { #1 } { \cs_if_exist_use:cF { g_@@_secondary_ \l_context_secondary_str _ #1 _tl } { \cs_if_exist_use:cF { g_@@_secondary_ * _ #1 _tl } { #1 } } } } \@@_debug_typeout:n{set~ secondary~ <-~ \l_context_secondary_str } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\DeclareSecondaryContextRule} % % \begin{macrocode} \cs_new_protected:Npn \DeclareSecondaryContextRule #1#2#3 { \tl_gclear_new:c { g_@@_secondary_ #1 _ #2 _tl } \tl_gset:cn { g_@@_secondary_ #1 _ #2 _tl } {#3} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\SetTertiaryContext,\DeclareTertiaryContextRule,\l_context_tertiary_str} % % \changes{v0.6a}{2026-05-26}{Implement tertiary context} % \texttt{e}-type % \begin{macrocode} \cs_new_protected:Npn \SetTertiaryContext #1 { \str_set:Ne \l_context_tertiary_str { \tl_if_empty:nF { #1 } { \cs_if_exist_use:cF { g_@@_tertiary_ \l_context_tertiary_str _ #1 _tl } { \cs_if_exist_use:cF { g_@@_tertiary_ * _ #1 _tl } { #1 } } } } \@@_debug_typeout:n{set~ tertiary~ <-~ \l_context_tertiary_str } } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \DeclareTertiaryContextRule #1#2#3 { \tl_gclear_new:c { g_@@_tertiary_ #1 _ #2 _tl } \tl_gset:cn { g_@@_tertiary_ #1 _ #2 _tl } {#3} } % \end{macrocode} % % \begin{macrocode} \str_new:N \l_context_tertiary_str % \end{macrocode} % \end{macro} % % % % % % % \subsection{Supporting font size changes as a tertiary context} % % % % \begin{macro}{\@setfontsize} % We try to be careful when setting the name as we don't know if % \cs{string} returns a backslash or not. % \begin{macrocode} \def\@setfontsize#1#2#3{\@nomath#1% \ifx\protect\@typeset@protect \let\@currsize#1% \begingroup \escapechar\m@ne \expandafter \endgroup \expandafter \SetTertiaryContext \expandafter {\string#1}% \fi \fontsize{#2}{#3}\selectfont } % \end{macrocode} % % If we are typesetting in \cs{normalsize} we don't want that as % the context, so here is a first application of a rule. % \begin{macrocode} \DeclareTertiaryContextRule{*}{normalsize}{} % \end{macrocode} % \end{macro} % % % % \subsection{Supporting primary context} % % \subsubsection{Float context} % % \begin{macro}{\@float} % This should work with most float pages. There are of course some % classes or packages that produce pseudo-floats without calling % \cs{@float}. These need to be handled separately by adding a % \cs{SetPrimaryContext} in their code. % \begin{macrocode} \AddToHook{cmd/@float/before}{\SetPrimaryContext{float}} % \end{macrocode} % \end{macro} % % % \subsubsection{Caption context} % % \TODO{in latex-lab-float} % \cs{@makecaption} is redefined there and the context should be set in that % command (or its replacement one day). % % \TODO{longtable and anything else that runs around producing \cs{caption}s} % % \TODO{Again, the secondary context chould be the current float type} % % % % \subsubsection{Footnote context} % % \TODO{in latex-lab-footnote} % % % % \subsubsection{Header context} % % \TODO{} % % % % \subsubsection{Footer context} % % \TODO{} % % % % \subsubsection{Marginal context} % % \TODO{} % % % % % % \subsection{Changes to lttemplates.dtx code} % % \begin{macrocode} %<@@=template> % \end{macrocode} % % % % % \begin{macro}{\@@_use_instance_auxii:nn, \@@_use_instance_finally:nn} % % The \cs{@@_use_instance_auxii:nn} is the command that is used by % \cs{UseInstance} to select and execute a requested instance. So % we now have to have a little more logic here. % In the normal case (both primary and secondary context are empty) % we now have 2 tests. We have up to 6 tests if one of the contexts % is non-empty. % \changes{v0.6a}{2026-05-26}{Re-implement logic determining the % instance name with a tertiary context } % \begin{macrocode} \cs_new_eq:NN \@@_use_instance_finally:nn \@@_use_instance_auxii:nn \cs_set_protected:Npn \@@_use_instance_auxii:nn #1#2 { \exp_args:Ne\str_if_empty:nTF { \l_context_primary_str \l_context_secondary_str \l_context_tertiary_str } { \__context_debug_typeout:n{no~ special~ context} \@@_if_instance_exist:nnTF {#1} {#2} { \__context_debug_typeout:n{basic~ instance~ exists} \@@_use_instance_finally:nn {#1} {#2} } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } { \@@_debug:n { \str_if_empty:NF \l_context_primary_str { \@@_debug_typeout:n {primary~ context~ is~ '\l_context_primary_str' } } \str_if_empty:NF \l_context_secondary_str { \@@_debug_typeout:n {secondary~ context~ is~ '\l_context_secondary_str' } } \str_if_empty:NF \l_context_tertiary_str { \@@_debug_typeout:n {tertiary~ context~ is~ '\l_context_tertiary_str' } } } \@@_if_instance_exist:nnTF {#1} { #2 : \l_context_primary_str : \l_context_secondary_str : \l_context_tertiary_str } { \__context_debug_typeout:n{:primary:secondary:tertiary~ exists~ (primary~ or~ secondary~ might~ be~ empty)} \@@_use_instance_finally:nn {#1} { #2 : \l_context_primary_str : \l_context_secondary_str : \l_context_tertiary_str } } { \__context_debug_typeout:n{:primary:secondary:tertiary~ does~ not~ exist} \@@_if_instance_exist:nnTF {#1} { #2 : \l_context_primary_str : \l_context_secondary_str } { \__context_debug_typeout:n{:primary:secondary~ or~ ::secondary~ exists} \@@_use_instance_finally:nn {#1} { #2 : \l_context_primary_str : \l_context_secondary_str } } { \__context_debug_typeout:n{:primary:secondary~ and~ ::secondary~ do~ not~ exist} \@@_if_instance_exist:nnTF {#1} { #2 : \l_context_primary_str } { \__context_debug_typeout:n{:primary~ exists} \@@_use_instance_finally:nn {#1} { #2 : \l_context_primary_str } } { \__context_debug_typeout:n{:primary~ does~ not~ exist} \@@_if_instance_exist:nnTF {#1} { #2 : : : \l_context_tertiary_str } { \__context_debug_typeout:n{:::tertiary~ exists} \@@_use_instance_finally:nn {#1} { #2 : : : \l_context_tertiary_str } } { \@@_if_instance_exist:nnTF {#1} {#2} { \__context_debug_typeout:n{basic~ instance~ exists} \@@_use_instance_finally:nn {#1} {#2} } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } } } } } } % \end{macrocode} % \end{macro} % % \changes{2026-03-19}{v0.5c}{Move template debugging support to kernel} % % \begin{macrocode} % % \end{macrocode}