% \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 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\ltlabcontextdate{2025-07-11} \def\ltlabcontextversion{0.5a} %<*driver> \DocumentMetadata{tagging=on,pdfstandard=ua-2,lang=en} \documentclass{l3doc} \usepackage{latex-lab-testphase-l3doc} \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 are 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} % % \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 % are instances of templates (where things can be easily automated % to react to context, but is probably also applicate to other % situations (but there 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} and the % \enquote{secondary context} both 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} is 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{footnote} % denotes that we are typesetting the footnote text % % \item \texttt{float} % denotes that we are typesetting a floats % % \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} to \meta{empty}. The setting is local, i.e., it obeys % grouping. % % It would be possible to be more granular, i.e., differentiate % between types of floats etc. But for now I suggest to start out % with this small set. % % % \subsection{The \enquote{secondary context}} % % The \enquote{secondary context} concerns itself (for now) 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 secondary % 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 secondary contexts are automatically set by the % standard \LaTeX{} font size commands (as part of \cs{}). Classes % that use additional font size commands but do not use the % \LaTeXe{} command \cs{} need to explicitly set the secondary % context with \cs{SetSecondaryContext} if they want their size been % recognized as a context. % % % % \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 % to. If there is no such rule then \meta{new context} will be used % unconditionally. % % As a second action the commands executes % \cs{SetSecondaryContext}\texttt{\{\}}, i.e., it clears the % \enquote{secondary context}. % % 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 written shorter 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 to. If there is no such rule then \meta{new context} will % be used unconditionally. % % 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}. This can be written shorter as % \begin{quote} % \cs{DeclareSecondaryContextRule*} \Arg{new}\Arg{result} % \end{quote} % if preferred. % % Declaring such rules is a global operation. % \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} 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} 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} is empty we effectively append % \texttt{::}\meta{secondary context}.} % \item % If not then \meta{inst-name}\texttt{:}\meta{primary context} is % tried next. % \item % If that doesn't exist either then \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} (but 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::footnotesize} which would be selected if the % itemize is in the main galley (empty primary context) and the % fontsize at the outside is \cs{footnotesize}. % % % \section{Notes} % % With special classes, e.g., \texttt{ltx-talk}, additional primaries % could be added (and secondaries could be using the modes rather % than or in addition to the fontsizes). Or the modes could be part % of the primary names \dots. 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. % % What is currently not so easy is to get rid of context names that % have been set up, e.g., to use your own \texttt{wipple} and % \texttt{wopple} as secondary contexts and make sure that fontsize % commands to not overwrite the context. Right now that would % require a lot of rules like %\begin{verbatim} % \DeclareSecondaryContextRule{wipple}{tiny}{wipple} % \DeclareSecondaryContextRule{wipple}{footnotesize}{wipple} % \DeclareSecondaryContextRule{wipple}{scriptsize}{wipple} % ... %\end{verbatim} % so perhaps this interface needs changes or some augmentation, if % such thing turn out to be useful, e.g., supporting % \verb=\DeclareSecondaryContextRule{*}{tiny}{*}= to indicate this. % % Another possibility is to support several independent dimensions % as secondary contexts (then \enquote{fontsize} could be one and % the \enquote{wipple-wopple} one the other. which of these are used % when instances are selected could then (perhaps) be a function of % the template type but for a heading type looking at % front/main/back matter would be more approriate.\footnote{Then, of % course, a primary context change would not reet any secondary % context.} % % Bottom line, what is here is nothing more than a first prototype % and likely to change a lot. % % \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} % This variable holds the current secondary 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{}. If % additional secondary contexts are used then the variable may not % be empty even if we are typesetting in \cs{normalsize} % \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} % % \begin{function}{\DebugTemplatesOn,\DebugTemplatesOff, \template_debug_on:, \template_debug_off:} % % These commands enable/disable debugging messages for template related % processing. (Belongs into \file{lttemplates.dtx} one day.) % % \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 be augmented to execute \cs{SetPrimaryContext}\texttt{\{float\}}. % This results in the body of all real floats to be typeset in the % primary \texttt{float} context. Not covered are packages or % methods that make floats not using \cs{@float} or making speudo % float, e.g., somehting 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} % % \begin{macrocode} \RequirePackage{latex-lab-testphase-block} % \end{macrocode} % % % % % % \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:Npx \@@_debug:n ##1 { \bool_if:NT \g_@@_debug_bool {##1} } \cs_gset_protected:Npx \@@_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 rule for current context and new context % use that; otherwise if there is a star rule aply 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 context. % \begin{macrocode} \SetSecondaryContext {} } % \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. % \TODO{perhaps to be replaced by a multi-dimensional solution} % \begin{macrocode} \str_new:N \l_context_secondary_str % \end{macrocode} % \end{macro} % % % \begin{macro}{\SetSecondaryContext} % Similar to \cs{SetPrimaryContext}. % \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} % % % % \subsection{Supporting font size changes as a secondary context} % % % % \begin{macro}{\@setfontsize} % We try to be careful with 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 \SetSecondaryContext \expandafter {\string#1}% \fi \fontsize{#2}{#3}\selectfont } % \end{macrocode} % If we are typesetting in \cs{normalsize} we don't want that as as % the context, so here is a first application of a rule. % \begin{macrocode} \DeclareSecondaryContextRule{*}{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} % % \TODO{longtable and anything else that runs around producing \cs{caption}s} % % % \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_aux:nn} % This is the command that is use 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 have now 3 tests. We have up to 4 tests if only primary is % non-empty and up to 5 tests if secondary is non-empty. % \begin{macrocode} \cs_set_protected:Npn \@@_use_instance_aux:nn #1#2 { \@@_debug:n { \str_if_empty:NF \l_context_primary_str { \@@_debug_typeout:n {primary~ context~ is~ '\l_context_primary_str' } } } \@@_debug:n { \str_if_empty:NF \l_context_secondary_str { \@@_debug_typeout:n {secondary~ context~ is~ '\l_context_secondary_str' } } } \tl_if_empty:NTF \l__context_secondary_tl { \str_if_empty:NTF \l_context_primary_str { \@@_if_instance_exist:nnTF { #1 } { #2 } { \@@_use_existing_instance:nn { #1 } { #2 } } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } { \@@_if_instance_exist:nnTF { #1 } { #2 : \l_context_primary_str } { \@@_use_existing_instance:nn { #1 } { #2 : \l_context_primary_str } } { \@@_if_instance_exist:nnTF { #1 } { #2 } { \@@_use_existing_instance:nn { #1 } { #2 } } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } } } { \@@_if_instance_exist:nnTF { #1 } { #2 : \l_context_primary_str : \l_context_secondary_str } { \@@_use_existing_instance:nn { #1 } { #2 : \l_context_primary_str : \l_context_secondary_str } } { \str_if_empty:NTF \l_context_primary_str { \@@_if_instance_exist:nnTF { #1 } { #2 } { \@@_use_existing_instance:nn { #1 } { #2 } } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } { \@@_if_instance_exist:nnTF { #1 } { #2 : \l_context_primary_str } { \_@@_use_existing_instance:nn { #1 } { #2 : \l_context_primary_str } } { \@@_if_instance_exist:nnTF { #1 } { #2 } { \@@_use_existing_instance:nn { #1 } { #2 } } { \msg_error:nnnn { template } { unknown-instance } {#1} {#2} } } } } } } % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\_@@_use_existing_instance:nn} % We combine debugging info an execution of the instance in one % macro, to be used when we know for sure that this instance % exists. % \begin{macrocode} \cs_new_protected:Npn \_@@_use_existing_instance:nn #1#2 { \@@_debug_typeout:n{use~ '#1'~ instance:~ #2 \on@line } \use:c { \c_@@_instances_root_tl #1 / #2 } } % \end{macrocode} % \end{macro} % % % \subsubsection{Debugging of templates} % % % \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}{\template_debug_on:,\template_debug_off:, % \@@_debug_gset:} % \begin{macrocode} \cs_new_protected:Npn \template_debug_on: { \bool_gset_true:N \g_@@_debug_bool \@@_debug_gset: } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \template_debug_off: { \bool_gset_false:N \g_@@_debug_bool \@@_debug_gset: } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_debug_gset: { \cs_gset_protected:Npx \@@_debug:n ##1 { \bool_if:NT \g_@@_debug_bool {##1} } \cs_gset_protected:Npx \@@_debug_typeout:n ##1 { \bool_if:NT \g_@@_debug_bool { \typeout{[Template]~ ==>~ ##1} } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\DebugTemplatesOn,\DebugTemplatesOff} % % \begin{macrocode} \cs_new_protected:Npn \DebugTemplatesOn { \template_debug_on: } \cs_new_protected:Npn \DebugTemplatesOff { \template_debug_off: } % \end{macrocode} % % \begin{macrocode} \DebugTemplatesOn % \end{macrocode} % \end{macro} % % % % % \begin{macrocode} % % \end{macrocode} % % % % % \begin{macrocode} %<*latex-lab> \ProvidesFile{context-latex-lab-testphase.ltx} [\ltlabcontextdate\space v\ltlabcontextversion\space latex-lab wrapper context] \RequirePackage{latex-lab-testphase-context} % % \end{macrocode}