% \iffalse meta-comment % %% File: latex-lab-math.dtx % % Copyright (C) 2022-2024 The 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\ltlabmathdate{2024-11-26} \def\ltlabmathversion{0.6j} % %<*driver> \documentclass{l3doc} \EnableCrossrefs \CodelineIndex \usepackage{todonotes} \begin{document} \DocInput{latex-lab-math.dtx} \end{document} % % % \fi % % % \title{The \texttt{latex-lab-math} code\thanks{}} % \author{Frank Mittelbach, Joseph Wright, \LaTeX{} Project} % \date{v\ltlabmathversion\ \ltlabmathdate} % % \maketitle % % \newcommand\NEW[1]{\marginpar{\mbox{}\hfill\fbox{New: #1}}} % \providecommand\class[1]{\texttt{#1.cls}} % \providecommand\pkg[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}}} % % \NewDocElement[printtype=\textit{socket},idxtype=socket,idxgroup=Sockets]{Socket}{socketdecl} % \NewDocElement[printtype=\textit{hook},idxtype=hook,idxgroup=Hooks]{Hook}{hookdecl} % \NewDocElement[printtype=\textit{plug},idxtype=plug,idxgroup=Plugs]{Plug}{plugdecl} % % ^^A \car {...} for marginal comments % ^^A \car*{...} for longer inline comments % % \NewDocumentCommand\car{sO{}m} % {\IfBooleanTF{#1}{\todo[inline,color=blue!10,#2]{#3}}^^A % {\todo[color=blue!10,#2]{#3}}} % % \NewDocumentCommand\fmi{sO{}m} % {\IfBooleanTF{#1}{\todo[inline,#2]{#3}}^^A % {\todo[#2]{#3}}} % % \begin{abstract} % This is an experimental prototype. It captures math material % (basically okay, but the interfaces for packages aren't yet % there) and tags the material (which is not yet anywhere near the % final state). That part is provided for experimentation and to % gather feedback, etc. % \end{abstract} % % \tableofcontents % % \section{Introduction} % \car*{Todo: update all the documentation! Both here and % (what little there is!) in the implementation section.} % % Tagging math involves a variety of tasks that require that math is captured before the % typesetting % \begin{itemize} % \item When typesetting the math MC-tags and structure commands must % be inserted at the begin and the end, and perhaps also around lines % or other subparts of the equation. % \item The source and/or a mathml-representation of the source must be available % so that it can be (perhaps after some preprocessing) be used in an associated file % or in an alternate text % \item It must be possible to measure the math for, e.g., a bbox setting. % \end{itemize} % % This file implements capture of all math mode material at the outer % level, i.e., a formula is captured in its entirety with inner text % blocks (possibly containing further math) absorbed as part of the % formula. For example, %\begin{verbatim} % \[ a \in A \text{ for all $a<5$} \] %\end{verbatim} % would only result in a single capture of the tokens % ``\verb*/a \in A \text{ for all $a<5$}/''. % % % \section{Math capture} \label{sec:mathcapture} % In the current setup % \begin{itemize} % \item |$|, |\(...\)| and |$$| grab (through a command in \cs{everymath}/cs{everydisplay}) % if the boolean \cs{l_@@_collected_bool} is false. % If the boolean is true they behave normally and can for example contain verbatim. % % \item All (registered) environments grab their body % regardless of the state of the boolean. For % |equation|, |equation*| and |math| this is a change as they no longer can % contain verbatim. % % \item BUG: |\[...\]| grabs if \cs{l_@@_collected_bool} is false. If it is % true it falls back to |equation*| and then errors because this can't find the end. % \end{itemize} % % \subsection{Code level interfaces} % % \begin{function}{\math_register_env:n, \math_register_env:nn} % \begin{syntax} % \cs{math_register_env:n} \Arg{env} % \cs{math_register_env:nn} \Arg{env} \Arg{options} % \end{syntax} % Registers the \meta{env} as a math environment which should be captured % and made available. This is necessary for all top-level math mode % environments: low-level errors may result if these are not correct % set up. One or more key--value \meta{options} may also be given: % \begin{itemize} % \item[\texttt{arg-spec}] The argument specification taken by the % beginning of the environment; this is used to remove non-mathematical % material. % \end{itemize} % \end{function} % % \begin{function}{\math_processor:n} % \begin{syntax} % \cs{math_processor:n} \Arg{tokens} % \end{syntax} % Declares that the captured math content should be passed to the % \meta{tokens}, which will receive the environment type as |#1| and % the content as |#2|. The processing is done before the typesetting. It is not % applied if \cs{ifmeasuring@} is true. % \end{function} % % \subsection{Document level interfaces} % % \begin{function}{\RegisterMathEnvironment} % \begin{syntax} % \cs{RegisterMathEnvironment} \oarg{options} \Arg{env} % \end{syntax} % Registers the \meta{env} as a math environment which should be captured % and made available. This is necessary for all top-level math mode % environments: low-level errors may result if these are not correct % set up. One or more key--value \meta{options} may also be given: % \begin{itemize} % \item[\texttt{arg-spec}] The argument specification taken by the % beginning of the environment; this is used to remove non-mathematical % material. % \end{itemize} % \end{function} % % \section{Math tagging} % % \subsection{Code requirements} % The tagging code has to handle % \begin{itemize} % \item the embedding into the surrounding. This means % \begin{itemize} % \item closing and reopening MC-chunks % \item closing and reopening text/P-structures % \item handling interferences of the tagging code with penalties and spacing. % \end{itemize} % \item the actual tagging which means to do some or all of the following tasks: % \begin{itemize} % \item setup content for an associated source file % \item setup content for an associated mathml file % \item setup content for the /Alt key % \item setup content for the /ActualText key % \item setup attributes % \item add associated files % \item add a Formula structure % \item surround subparts (e.g., lines) with Formula sub structures % (perhaps with their own set of additional content) % \item surround elements of the equation with mathml structure elements % (currently only luatex with luamml) % \end{itemize} % \end{itemize} % % \subsection{Inline math} % % The embedding code is added through % the sockets % \begin{itemize} % \item |tagsupport/math/inline/begin| % \item |tagsupport/math/inline/end| % \end{itemize} % The sockets simply push and pop the MC currently. Without % tagging they use the noop-plug. % % The actual tagging is in done through the sockets % \begin{itemize} % \item |tagsupport/math/inline/formula/begin| % This socket takes the math as second argument and its code % should output it for typesetting. % The |default| plug of the socket calls these three internal sockets % for the tagging support: % \begin{itemize} % \item |tagsupport/math/content| This should set up the various % content variables (empty variables are ignored by the structure code % and so can be used to suppress a setting). % \item |tagsupport/math/struct/begin| This calls \cs{tag_struct_begin:n}. % It should also write the associated files if needed. % \item |tagsupport/math/substruct/begin| this handles subparts. % TODO: does it really make sense in inline math to have that?? % \end{itemize} % \item |tagsupport/math/inline/formula/end| % This socket ends the formula structure(s). The |default| % plug calls these internal sockets: % \begin{itemize} % \item |tagsupport/math/substruct/end| % \item |tagsupport/math/struct/end| % \end{itemize} % \end{itemize} % % \subsection{Display math} % % \textit{to be written} % % \subsection{Associated Files} % % The current code allows the attachment of two types of associated file to the % Formula structure: % the \LaTeX\ source and a MathML representation. % Technically both can be attached---AF is an array % of file references-----in practice there can be problems with PDF consumers: % e.g., ngpdf used both and so showed the equation twice % (this has been corrected in the newest version) and % Foxit seems to see only the first AF in the array (so we attach the % mathml as first file). % % The \LaTeX\ source can be (and is) attached automatically. % It can be suppressed by an option with % \texttt{math/tex/AF=false}, see below. % % The MathML is attached if the files |\jobname-mathml.html| and/or % |\jobname-luamml-mathml.html| are found % and if they contains a suitable MathML snippet for the current formula. % If the files contain more than one suitable snippet (as identified by the hash) % the first one is used. % |\jobname-luamml-mathml.html| is automatically generated (see below section~\ref{sec:luamml}) % and read after |\jobname-mathml.html|. This means that |\jobname-mathml.html| can contain % improved versions of a formula. % % The MathML processing can be suppressed globally by emptying the list of % mathml files with |math/mathml/sources=|. Locally for a formula |math/mathml/AF=false| % can be used. % % For a MathML representation a file with such representations must be provided. % If the equation is numbered the numbering should be part of the MathML as % the |Lbl| substructure is ignored if an MathML is used (see https://github.com/foxitsoftware/PDF_UA-2). % % The MathML representation is given in a special format. % It is meant to be a valid html file % that can be viewed in a browser. % For this it can start with || and end with || % It should have the extension \texttt{.html}. The \meta{mathml} content % is read with special catcodes, so can contain ambersands, hashes, comment chars % and unmatched braces such as |{| % % The file should contain a number of representations in this format: % \begin{quote} % |
| \\ % |

\mml| \meta{key}|

|\\ % |

|\meta{source}|

| \\ % |

|\meta{hash}|

| \\ % | |\\ % \meta{mathml}\\ % | |\\ % |
| % \end{quote} % The keywords |
|, |

\mml|, |

|, || |

| are required as % they are used to delimit the arguments by the \LaTeX{} code. % % \meta{key} and \meta{source} are only used for debugging, they help to identify % the equation referred by this representation. The source should be used correctly escaped % |&| and |<| so that if gives valid html! % % \meta{attributes} is not required either, but can, e.g., contain attributes % to improve the display in a browser: % \begin{verbatim} % % \end{verbatim} % It can also contain the name space declaration: |xmlns="http://www.w3.org/1998/Math/MathML"|% % \footnote{But it is probably not needed and only blows up the PDF.} % % % By default the code tries at the begin of the document % to read a file |\jobname-mathml.html| in the |html|-format. % The file name can be changed with |mathml/setfiles={filename1,filename2}| % (without extension, |html| is added automatically). % If there is a list, all files are loaded. % If a file doesn't exist it is ignored, only an info is written to the log. % % Currently every MathML-snippet from a file is embedded into the PDF, % it is not checked first if it is actually used (simply writing everything to the PDF % is a bit easier than keeping everything in memory and also means that % the snippets are one after the other in the PDF). % % As mentioned above the MathML-AF can be suppressed for the equations in a group with % |math/mathml/AF=false|, or % completely by setting |math/mathml/sources=| in the preamble. % % Files embedded in a PDF can be listed in the attachments panel of a PDF viewer. % This is probably not so useful for lots of small files (but one could create % collections), but as long as PDF editors or viewers don't offer % proper support to access the AF it can help so have them there. The MathML are % added by default, but the \LaTeX{} source not. This can be changed with % |viewer/pane/mathsource=true| (anywhere in the document) and |viewer/pane/mathml=false| (in the % preamble, before the external file is read). % % % \subsection{Automatic mathml creation with luamml}\label{sec:luamml} % % If lualatex and the package \pkg{unicode-math} is used % the package \texttt{luamml} is loaded and % will automatically generate the file |\jobname-luamml-mathml.html| % with mathml representations of all math formulas. % This file is then used in subsequent compilations and works also with % pdflatex. % % The generation of the file can be suppressed (in the preamble) % with |math/mathml/luamml/write=false|. % % If the package \pkg{unicode-math} is not used, % the loading of \pkg{luamml} and with it the generation of the file can be forced % with |math/mathml/luamml/load=true| or |math/mathml/luamml/write=true| % but be aware that it is then possible that various symbols % are mapped to the wrong Unicode code points. % % The package \pkg{luamml} is still quite experimental and the output should be checked. % The |\jobname-luamml-mathml.html| file may be previewed in a browser although % you may need to add additional css or javascript declarations % to enable browser support for all mathml constructs. % % % \subsection{Summary of math options} % The following options exist to make math more accessible: % \begin{description} % \item[ActualText] An \texttt{ActualText} can be placed on structure elements, % but can also be added in the stream on a \texttt{BDC} marker with a \texttt{Span} % tag (normally an independant marker without an MCID number, it is not clear yet % if it can be used on a MC-chunk). % The content is a text string, typically one or a few Unicode characters. % \texttt{ActualText} is meant to replaces the content % and should only be used on small entities, % e.g., to define the semantic or the Unicode code point of a symbol. % \texttt{ActualText} is not supported by all PDF reader. % It is also unknown where it should be used at best (in a structure element, % or on an independent Span-BDC) and what happens if it is used in more than % one place. % \begin{description} % \item[enabled by default?] False % \item[how to enable/disable] No interface yet. % \texttt{ActualText} can only be added on the Formula structure element by % changing the \texttt{tagsupport/math/content} or some other socket. % For a BDC marker one can, e.g., use % \begin{verbatim} % \pdf_string_from_unicode:nnN{utf16/hex}{€}\l_tmpa_tl % \pdf_bdc:ee{Span}{/ActualText\l_tmpa_tl}content\pdf_emc: % \end{verbatim} % There should be no pagebreak in the \meta{content} and the BDC should be correctly % nested into tagging, so, e.g., a \cs{leavevmode} should be issued before the bdc command. % \item[Consumer support] in part and in part buggy, needs tests \ldots % \end{description} % % \item[Alt] Like \texttt{ActualText} the \texttt{Alt} key can be used on % structure elements and on \texttt{Span} in the stream. It should contain a description % of the content and is mainly meant for images. PDF/UA-1, % which views math formulas as illustrations, mandates the key % also for \texttt{Formula} structure elements. % \begin{description} % \item[enabled by default?] false unless PDF/UA-1 is detected, % then it is enabled in the begindocument/end hook % (this will reconsidered when it is clear, that % the use of \texttt{Alt} does not shadow mathml). It can be enabled for % all engines and PDF versions. % \item[enable/disable] \verb+\tagpdfsetup{math/alt/use}+ (local boolean, % so can be used on individual equations) % \item[default value] A template text (stored in \cs{l_@@_content_template_tl}) % starting with \texttt{LaTeX formula starts}. % \item[user value] No interface currently provided. This needs optional arguments % or an external setup command. % See \url{https://github.com/latex3/tagging-project/discussions/717}. % % \end{description} % % \item[source-AF] The \LaTeX{}-source of the equation can % be attached as an associated file with mime-type % application/Fx-tex. The \texttt{AFRelationship} is \texttt{Source}. % The source is embedded without expansion. This means that targets of % references and macros are not resolved. % The files are by default not shown in the EmbeddedFiles pane, % this can be enabled with |viewer/pane/mathsource=true|. % If an A-standard is used, it must be one that allows embedded files, e.g., A-4f. % % \begin{description} % \item[enabled by default?] true for all engines and PDF versions % \item[enable/disable] \verb+\tagpdfsetup{math/tex/AF}+ (local boolean, so can % be used on individual equations) % \item[default value] source code including dollars or environment name. % \item[consumer support] Currently only ngpdf makes % use of it: if there is no mathml it passes the source to mathjax. % \end{description} % % \item[luamml] The following options make (with lualatex) use % of the \pkg{luamml} package. \pkg{luamml} is currently automatically % loaded (at the end of the preamble) if \pkg{unicode-math} has been detected. % The loading can be forced or suppressed % with \verb+\tagpdfsetup{math/mathml/luamml/load=true/false}+. % % \pkg{luamml} affects all math, locally it can be stopped with |math/mathml/ignore|, % or by using the commands described in the package. % % \item[mathml-AF] A mathml representation of the equation can be attached % to the structure. The configuration possibilities are rather complex as the % keys have to control three different tasks: % The \emph{generation} of the file with the mathml fragments, % the \emph{reading} and \emph{embedding} of the mathml fragments, % and the \emph{association} of a mathml fragment to a specific equation. % % \begin{description} % \item[generation] % With pdf\LaTeX{} mathml fragments can not be generated automatically, % but a file with dummy fragments for every equation will be written if % \verb+\tagpdfsetup{math/mathml/write-dummy}+ is issued in the preamble. % % With lua\LaTeX{} a file with mathml fragments will be created automatically % if the package \pkg{luamml} has been loaded (see above). % % \item[reading and embedding] % By default the code will read and embed % mathml from |\jobname-mathml.html| and |\jobname-luamml-mathml.html| in this order and % the first fragment with a new hash value will be inserted.% % The list of sources and their order can be changed with the key % |math/mathml/sources|, setting that to an empty value suppresses % the loading mathml associated files completely. For efficiency reasons % it embeds math fragments directly, there is no check yet if the fragment is % actually used. % % The files are by default shown in the EmbeddedFiles pane, % this can be disabled with |viewer/pane/mathml=false|. % % \item[attaching] A mathml fragment is currently % attached as an associated file to an Formula if the hash of % the source matches the hash of the fragment. This is not a perfect test: % equations with the same source and so the same hash % can have different mathml representation, e.g., % if there are references or commands or counters in the equation. This % will change in a feature version. % The attachment can be suppressed locally with |math/mathml/AF=false|. % The mathml fragment will still be embedded in the PDF! % % % TODO: adapt test % \end{description} % % \item[mathml structure elements] % Mathml structure elements can be used in PDF~2.0 directly. % In PDF~1.7. one could theoretically % use them if one declares a role mapping first, % (this can be done with \verb+\tagpdfsetup{role/mathml-tags}+) % which maps all to \texttt{Span}. But such a role mapping currently breaks reading, % e.g. in Adobe, and so it is not recommended. % % Automatic generation of structure elements is only possible with % lualatex. It requires that the packages \pkg{luamml} and \pkg{tagpdf} % have been loaded. % \begin{description} % \item[enabled by default?] false % \item[enable/disable] \verb+\tagpdfsetup{math/mathml/structelem}+ % (local setting, so can be used with grouping on individual equations). % \item[consumer support] Needs more tests. % \end{description} % % \end{description} % % % % \section{Known current bugs, etc.} % % \subsection{Capture/grabbing problems} % % \begin{enumerate} % \item Incorrect grabbing of |$|-math when there is also % explicit |$|-math within a \textit{text environment} % that is itself within the math that should all be grabbed. % For example, % \begin{verbatim} % $a\begin{minipage}{1cm}$b$\end{minipage}$ % \end{verbatim} % would only result in the capture of the tokens % ``\verb*/a\begin {minipage}{1cm}/''. % This can be avoided by an additional brace group: % \begin{verbatim} % $a{\begin{minipage}{1cm}$b$\end{minipage}}$ % \end{verbatim} % % \item Similar incorrect grabbing with |$$| also. % % \item The grabbing, for all the display environments (and |\) \]|), needs % to deal with nesting: \pkg{amsmath} contains code for this. % % \item The math can't contain verbatim and verbatim-like commands. This is % nothing new for the \pkg{amsmath} environments but changes |$| and |\[\]| % and |equation*| (see, e.g., tagging-project issue \#30). % % \item Begin and end of the math or math environment can not be hidden in commands. % For example \verb+>{$}l<{$}+ in a tabular would lead to errors. % Defining |\[| to fall back to |equation*| doesn't work if |equation*| is % a grabbing environment. % % \item The behaviour of |\[...\]| is faulty. See above. % \end{enumerate} % % \subsection{Fake math} % In a number of places in \LaTeX{} math commands (mainly |$|) is used % only for technical reason, e.g., to access a math font, to setup a symbol % or to use \cs{vcenter}. % % The code identifies such fake math mostly by making use of the \cs{m@th} command % where two methods are used for the automatic detection: % % \begin{itemize} % \item After grabbing math content the code checks if the content contains the token % \cs{m@th} and if yes it doesn't call the processor before reinserting % the content and perhaps adding tagging code. % This method requires that the math can be grabbed (e.g. that the end dollar is visible) % and that the \cs{m@th} is visible. It applies for example in \cs{@iiiparbox} where the % code from |$\vcenter| to |\m@th$| is grabbed an put back. It does not work for % example for |tabular| where the dollars and the \cs{m@th} token are spread around % over three commands. |tabular| needs therefore manual intervention. % % A look in the list of usages (in \texttt{usage-of-m@th.md}) justifies this approach. % All usages are either not math at all, or related to small elements that probably % shouldn't be grabbed and processed on their own. % % \item \cs{m@th} is redefined so that it sets the boolean \cs{l_@@_collected_bool} % to true. If \cs{m@th} is used inside math that has been grabbed % this doesn't change much as the boolean is set by the grabbing anyway. For usages % outside math the benefit is not so clear: The setting avoids that in \cs{LaTeXe} % the epsilon is processed as math, but it also prevents that the content of the amsmath % command \cs{boxed} is processed as math. % It means that if one wants to reenable math processing inside some (fake) math % one has to do it after \cs{m@th} calls. % \end{itemize} % % \subsubsection{Open problems} % % \begin{enumerate} % \item The grabbing code doesn't pass the info that it detected a \cs{m@th} token. % This means that the tagging code has to do the same check (and doesn't do this % in all cases yet). % % \item Commands are missing to locally disable the grabbing and processing, e.g., % to handle |tabular|. % % \item It must be checked if setting the boolean in \cs{m@th} really makes sense % or if commands like \cs{LaTeXe} should be handled manually. % % \end{enumerate} % % \subsection{Processor} % % The grabbed math is at first passed to the processor. The processor is not called % in a measuring phase (from the amsmath \cs{ifmeasuring@}) and if the \cs{m@th} % token is detected. % It is not quite clear what purpose the processor has. As it is a public interface % it can't be used for internal code. And typesetting happens later and the processor % can't really change this. Currently it is mostly used for debugging and messages. % If the \cs{m@th} is found the \cs{l_@@_fakemath_bool} is set, so if the code % is changed this must be preserved. % % \subsection{Other problems} % % \begin{enumerate} % \item % The presence of \cs{m@th} in association with \cs{ensuremath} % does not necessarily indicate fakemath. This is because % wanting mathsurround to be zero is very reasonable and common, % \emph{even when the math is genuine} (and hence needs to be collected). % % TODO: this claim needs some examples. % % \item User-defined environments can create problems; but this area, of % new, copied and changed environments, has not yet been developed. % % \car*{Joseph wrote, inter alia:\\ % My thinking [regarding] \cs{RegisterMathEnvironment}\\ % - (New) Math environments should not be created-then-patched, but only % generated by a [(future)] dedicated command (\cs{DeclareMathEnvironment}, % presumably)\\ % - Math environments created with \pkg{ltcmd} [commands] should not be copied, . . .\\ % - Package authors should be able to manually set up math environments with a public boolean.} % \end{enumerate} % % % \subsection{Other ToDos} % % \begin{enumerate} % \item Add (some of) the math display commands that were \enquote{lifted from % plain}, e.g., \cs{displaylines} \cs{eqalign}(??). % \item The breqn packages changes catcodes and that isn't yet covered % by our mechanism. % \item \cs{intertext} is not correctly taken into account by the % code splitting multiline math into subformulas. % \end{enumerate} % % % \car*{\cs{MaybeStop} (temporarily) not executed, as it is unknown on Chris' system.} % \iffalse % \MaybeStop{\setlength\IndexMin{200pt} \PrintIndex } % \else % \StopEventually{\setlength\IndexMin{200pt} \PrintIndex } % \fi % % \section{The Implementation} % % \begin{macrocode} %<@@=math> % \end{macrocode} % % \begin{macrocode} %<*kernel> % \end{macrocode} % % \subsection{File declaration} % % \car{Change description here?} % \begin{macrocode} \ProvidesFile{latex-lab-math.ltx} [\ltlabmathdate\space v\ltlabmathversion\space Grab all the math(s) and tag it (experiments)] % \end{macrocode} % % Temp loading \ldots % \begin{macrocode} \AddToHook{begindocument/before}{\RequirePackage{latex-lab-testphase-block}} % \end{macrocode} % % \begin{macrocode} \ExplSyntaxOn % \end{macrocode} % % \subsection{Setup} % % Loading \pkg{amsmath} is an absolute requirement: this avoids needing to % have conditional definitions and deals with how to define \cs{[}/\cs{]} % neatly. % \begin{macrocode} \AddToHook{begindocument/before}{ \RequirePackage { amsmath } } % \end{macrocode} % % % \subsection{Data structures} % % \begin{variable}{\l_@@_collected_bool} % Tracks whether math mode material has been collected, which happens inside % \pkg{amsmath} environments as well as those handled directly here. % If true following math will not grab and/or process. % See \ref{sec:mathcapture} for details. % \begin{macrocode} \bool_new:N \l_@@_collected_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_fakemath_bool} % Tracks whether math mode material has been identified as fake math during % the grabbing phase, which happens currently if the % grabbed contents contains the \cs{m@th} token. % % \begin{macrocode} \bool_new:N \l_@@_fakemath_bool % \end{macrocode} % \end{variable} % % % \car{Change first tl name below: `env' $=>$ `info'?\\ % Or do we need an extra storage tl?} % % \begin{variable}{\g_@@_grabbed_env_tl, \g_@@_grabbed_math_tl} % \cs{g_@@_grabbed_env_tl} contains the name of the math environment % (\texttt{math} in the case of inline math, % \cs{g_@@_grabbed_math_tl} the math content. % \begin{macrocode} \tl_new:N \g_@@_grabbed_env_tl \tl_new:N \g_@@_grabbed_math_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_tmpa_tl,\l_@@_tmpa_skip,\l_@@_tmpa_str} % Temporary variables % \begin{macrocode} \tl_new:N \l_@@_tmpa_tl \skip_new:N \l_@@_tmpa_skip \str_new:N \l_@@_tmpa_str % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_content_alt_tl, % \l_@@_content_actual_tl, % \l_@@_content_AF_tl} % Temporary variables to hold math content that should % be used in actual or alt text and stored as AF. % \begin{macrocode} \tl_new:N \l_@@_content_alt_tl \tl_new:N \l_@@_content_actual_tl \tl_new:N \l_@@_content_AF_source_tl \tl_new:N \l_@@_content_AF_source_tmpa_tl \tl_new:N \l_@@_content_AF_mathml_tl % \end{macrocode} % \end{variable} % % \subsection{Tagging tools} % The following commands implement small tagging code chunks. % This should probably be collected and moved into tagpdf later. % \begin{macro}{\__tag_tool_close_P:} % This closes a P/text-chunk, both the MC and the structure and % increases the counter manually. % \begin{macrocode} \cs_new_protected:Npn \__tag_tool_close_P: { \tag_if_active:T { \tag_mc_end: %end P-chunk, should perhaps be \tag_mc_end_push: ... \__tag_gincr_para_end_int: \__tag_check_para_end_show:nn{red}{} %debug: show para \tag_struct_end: } } % \end{macrocode} % \end{macro} % % We add also an attribute. % \begin{macrocode} \tl_new:N\l_@@_attribute_class_tl \tagpdfsetup {role/new-attribute = {inline} {/O /Layout /Placement/Inline}, role/new-attribute = {display} {/O /Layout /Placement/Block}, } % \end{macrocode} % % \subsection{Code related to AF} % Booleans to handle the options. % \begin{variable}{ % \l__tag_math_texsource_AF_bool, % \l__tag_math_texsource_pane_bool, % \l__tag_math_mathml_AF_bool, % \g__tag_math_mathml_AF_bool, % \l__tag_math_mathml_pane_bool, % \l__tag_math_alt_bool, % \g__tag_math_luamml_tl, % } % The variable \cs{g__tag_math_luamml_tl} is initially 0 and % the user key can set it to -1 or 1. This allows to distinguish % the unset case from a value set by the user. % \begin{macrocode} \bool_new:N\l__tag_math_texsource_AF_bool \bool_new:N\l__tag_math_texsource_pane_bool \bool_new:N\l__tag_math_mathml_AF_bool \bool_new:N\g__tag_math_mathml_AF_bool \bool_new:N\l__tag_math_mathml_pane_bool \bool_new:N\l__tag_math_alt_bool \tl_new:N\g__tag_math_luamml_tl \tl_gset:Nn\g__tag_math_luamml_tl {0} % \end{macrocode} % \end{variable} % % \begin{variable}{ % \g_@@_mathml_total_int, % \g_@@_mathml_int, % \g_@@_math_total_int, % \g_@@_mathml_AF_found_int, % \g_@@_mathml_AF_attached_int, % } % \cs{g_@@_mml_total_int} records the mathml fragments read in. % \cs{g_@@_mml_int} records the mathml fragments read in with a different hash. % \cs{g_@@_AF_total_int} records the number of math structures that try to % attach a mathml AF. % \cs{g_@@_AF_found_int} records the number of math structures for which a fitting % mathml is found. % \cs{g_@@_AF_attached_int} records the number of math structures which got a mathml fragment % (if mathml-AF are not disabled locally this should be the equal to the previous number. % % \begin{macrocode} \int_new:N\g_@@_mathml_total_int \int_new:N\g_@@_mathml_int \int_new:N\g_@@_math_total_int \int_new:N\g_@@_mathml_AF_found_int \int_new:N\g_@@_mathml_AF_attached_int % \end{macrocode} % \end{variable} % % \begin{variable}{\l__tag_math_mathml_files_clist} % A sequence to store the file list for the mathml. % We also check the luamml file. % \begin{macrocode} \clist_new:N\l__tag_math_mathml_files_clist \clist_put_right:Ne\l__tag_math_mathml_files_clist {\c_sys_jobname_str-mathml,\c_sys_jobname_str-luamml-mathml} % \end{macrocode} % \end{variable} % % This is the internal variant of the \cs{mml} command. % \begin{macro}{\@@_AF_mml:nnnn} % \begin{macrocode} \cs_new_protected:Npn \@@_AF_mml:nnnn #1 #2 #3 #4 %#1 number, #2 tex source for debugging, #3 hash, #4 mathml { \int_gincr:N \g_@@_mathml_total_int % \end{macrocode} % mathml with the same hash should be included only once: % \begin{macrocode} \tl_if_exist:cF { g_@@_mathml_#3_tl } { \int_gincr:N \g_@@_mathml_int % \end{macrocode} % a simple Desc key, take care that it is a valid string! % \begin{macrocode} \pdfdict_put:nne {l_pdffile/Filespec} {Desc}{(mathml-#1)} \pdffile_embed_stream:nnN {#4}{mathml-#1.xml}\l_@@_tmpa_tl % \end{macrocode} % not strictly necessary but makes the files visible in the file attachment % page % \begin{macrocode} \bool_if:NT \l__tag_math_mathml_pane_bool {\pdfmanagement_add:nne {Catalog/Names}{EmbeddedFiles}{\l_@@_tmpa_tl}} \tl_new:c{g_@@_mathml_#3_tl} \tl_gset_eq:cN{g_@@_mathml_#3_tl}\l_@@_tmpa_tl } } % \end{macrocode} % \end{macro} % % The html reader. % \begin{macrocode} \cs_new_protected:Npn \@@_AF_html_reader:w#1#2

#3

#4

#5

#6{ \endgroup \@@_AF_mml:nnnn{#1}{#2}{#3}{#4} } % \end{macrocode} % % As with luatex we write two files we define a few constants for % the shared texts. % % \begin{macro} % {\c_@@_mathml_write_init_tl,\l_@@_mathml_write_before_tl, % \c_@@_mathml_write_after_tl,\c_@@_mathml_write_final_tl} % \begin{macrocode} \tl_const:Nn \c_@@_mathml_write_init_tl { \iow_newline: \iow_newline: } \tl_new:N \l_@@_mathml_write_before_tl \tl_const:Nn \c_@@_mathml_write_after_tl { \iow_newline: \iow_newline: } \tl_const:Nn \c_@@_mathml_write_final_tl { } % \end{macrocode} % \end{macro} % \begin{socketdecl}{tagsupport/math/mathml/write/prepare} % To prepare the hash and the starting command we use a socket, so % that both the dummy and luamml can make use of it. % \begin{macrocode} \socket_new:nn {tagsupport/math/mathml/write/prepare}{0} % \end{macrocode} % \end{socketdecl} % % \begin{plugdecl}{On} % \begin{macrocode} \socket_new_plug:nnn{tagsupport/math/mathml/write/prepare}{On} { \str_set:NV\l_@@_tmpa_str\l_@@_content_AF_source_tl \str_replace_all:Nnn\l_@@_tmpa_str{&}{&} \str_replace_all:Nnn\l_@@_tmpa_str{<}{<} \tl_set:Nn \l_@@_mathml_write_before_tl {
\iow_newline:

\c_backslash_str mml\c_space_tl \int_use:N \g_@@_math_total_int

\iow_newline:

\l_@@_tmpa_str

\iow_newline:

\l_@@_content_hash_tl

\iow_newline: } } % \end{macrocode} % \end{plugdecl} % % With luatex we automatically generate mathml with \pkg{luamml} if the package % can be loaded and \pkg{unicode-math} is detected. % We start the process in the begindocument/end hook % so that the reading from a previous compilation can happen before! % % For other engines, for future name changes % and in case luamml is not loaded we provide % some commands % \begin{macrocode} \cs_new_protected:Npn\@@_provide_luamml_commands: { \providecommand\luamml_flag_structelem:{} \cs_if_free:NT \luamml_structelem: { \cs_set_eq:NN\luamml_structelem:\luamml_flag_structelem: } \providecommand\luamml_flag_process:{} \cs_if_free:NT \luamml_process: { \cs_set_eq:NN\luamml_process:\luamml_flag_process: } \providecommand\luamml_flag_ignore:{} \cs_if_free:NT \luamml_ignore: { \cs_set_eq:NN\luamml_ignore:\luamml_flag_ignore: } } % \end{macrocode} % \begin{macrocode} \sys_if_engine_luatex:TF { % \end{macrocode} % Temporary (!) fixes for endarray % \begin{macrocode} \cs_new_protected:Npn \@@_correct_luamml_array_patches: { \AddToHook{package/array/after} { \cs_set:Npn \endarray { \tbl_crcr:n{endarray} \__luamml_array_save_array: \egroup \UseTaggingSocket{tbl/finalize} \tbl_restore_outer_cell_data: \egroup \mode_if_math:T { \__luamml_array_finalize_array: } \@arrayright \gdef \@preamble {} } \cs_set:Npn \@classz { \@classx \@tempcnta \count@ \prepnext@tok \@addtopreamble { \ifcase \@chnum \hfil \hskip 1sp \d@llarbegin \cs_if_eq:NNTF \d@llarbegin \begingroup { \insert@column \d@llarend } { \__luamml_array_init_col: \insert@column \luamml_flag_save:nn {} {mtd} \d@llarend \__luamml_array_finalize_col:w 0~ } \do@row@strut \hfil \or \hskip 1sp \d@llarbegin \cs_if_eq:NNTF \d@llarbegin \begingroup { \insert@column \d@llarend } { \__luamml_array_init_col: \insert@column \luamml_flag_save:nn {} {mtd} \d@llarend \__luamml_array_finalize_col:w 1~ } \do@row@strut \hfil \or \hfil \hskip 1sp \d@llarbegin \cs_if_eq:NNTF \d@llarbegin \begingroup { \insert@column \d@llarend } { \__luamml_array_init_col: \insert@column \luamml_flag_save:nn {} {mtd} \d@llarend \__luamml_array_finalize_col:w 2~ } \do@row@strut \or \setbox \ar@mcellbox \vbox \@startpbox { \@nextchar } \insert@pcolumn \@endpbox \ar@align@mcell \do@row@strut \or \vtop \@startpbox { \@nextchar } \insert@pcolumn \@endpbox \do@row@strut \or \vbox \@startpbox { \@nextchar } \insert@pcolumn \@endpbox \do@row@strut \fi } \prepnext@tok } } } % \end{macrocode} % % \begin{macrocode} \AddToHook{begindocument/before} { \str_case:on \g_@@_luamml_load_tl { { 1 } { \RequirePackage { luamml } \@@_correct_luamml_array_patches: \AddToHook{begindocument/end} { \@@_luamml_activate_write: } } {-1 } { \AddToHook{begindocument/end} { \msg_note:nnnn { tag } { luamml-status }{ disabled }{ not~create } } } { 0 } { \@ifpackageloaded { unicode-math } { \RequirePackage { luamml } \@@_correct_luamml_array_patches: \AddToHook{begindocument/end} { \@@_luamml_activate_write: } } { \msg_warning:nn { tag }{ unicode-math-missing } } } } \@@_provide_luamml_commands: } } { \@@_provide_luamml_commands: } \msg_new:nnn { tag }{ luamml-status } { luamml~has~been~#1~and~will~#2~an~MathML~file. } \msg_new:nnn { tag }{ unicode-math-missing } { The~package~unicode-math~is~missing\\ luamml~will~not~create~an~MathML~file.\\ To~avoid~this~warning~load~unicode-math~\\ or~disable~luamml~with~\\ \tl_to_str:n{\tagpdfsetup{math/mathml/luamml/load=false}}\\ or~force~luamml~with~\\ \tl_to_str:n{\tagpdfsetup{math/mathml/luamml/load=true}} } \cs_new_protected:Npn \@@_luamml_activate_write: { \bool_if:NT \g_@@_luamml_write_bool { % \end{macrocode} % to avoid that nothing is written in the first run, we must activate the sockets: % \begin{macrocode} \bool_gset_true:N\g__tag_math_mathml_AF_bool \AssignSocketPlug{tagsupport/math/struct/begin}{mathml-AF} \AssignSocketPlug{tagsupport/math/struct/end}{mathml-AF} \AssignSocketPlug{tagsupport/math/substruct/begin}{single} \AssignSocketPlug{tagsupport/math/substruct/end}{single} \int_set:Nn \l__luamml_pretty_int { 7 } \RegisterFamilyMapping\symsymbols{oms} \RegisterFamilyMapping\symletters{oml} \AssignSocketPlug{tagsupport/math/mathml/write/prepare}{On} \iow_new:N \g_@@_luamml_iow \iow_open:Nn \g_@@_luamml_iow {\c_sys_jobname_str-luamml-mathml.html} \iow_now:Ne \g_@@_luamml_iow { \c_@@_mathml_write_init_tl } \cs_new:Npn \@@_luamml_output_hook:n ##1 { \tl_if_empty:NF \l_@@_mathml_write_before_tl { \iow_now:Ne \g_@@_luamml_iow { \l_@@_mathml_write_before_tl ##1 \c_@@_mathml_write_after_tl } } } \__luamml_register_output_hook:N \@@_luamml_output_hook:n % \end{macrocode} % At the end of the document we must finish and close the file: % \begin{macrocode} \AddToHook{enddocument/afterlastpage} { \iow_now:Ne \g_@@_luamml_iow { \c_@@_mathml_write_final_tl } \iow_close:N \g_@@_luamml_iow } \msg_note:nnnn { tag } { luamml-status }{ enabled }{ create } } } % \end{macrocode} % And now keys to activate/deactivate luamml feature % \begin{variable}{\g_@@_luamml_load_tl} % This variable will be used to suppress the loading of luamml % altogether. % \begin{macrocode} \tl_new:N \g_@@_luamml_load_tl \tl_gset:Nn \g_@@_luamml_load_tl {0} % \end{macrocode} % \end{variable} % \begin{variable}{\g_@@_luamml_write_bool} % This variable decides if luamml writes a mathml % altogether. % \begin{macrocode} \bool_new:N \g_@@_luamml_write_bool \bool_gset_true:N \g_@@_luamml_write_bool % \end{macrocode} % \end{variable} % \begin{macro}{\@@_luamml_ignore:,\@@_luamml_structelem:} % Internal variants of the luamml commands, that can be remapped if needed. % \begin{macrocode} \cs_new:Npn\@@_luamml_structelem:{} \cs_new:Npn\@@_luamml_ignore:{} % \end{macrocode} % \end{macro} % % \begin{macrocode} \msg_new:nnn { tag }{ PDF-2.0-recommended } { The~key~#1~will~not~work~properly~with~PDF~#2.\\ Switching~to~PDF~2.0~is~recommended. } \keys_define:nn { __tag / setup } { % \end{macrocode} % At first a key to suppress the loading altogether % \begin{macrocode} math/mathml/luamml/load .choice: , math/mathml/luamml/load/true .code:n = {\tl_gset:Nn \g_@@_luamml_load_tl{1}}, math/mathml/luamml/load/false .code:n = {\tl_gset:Nn \g_@@_luamml_load_tl{-1}}, math/mathml/luamml/load .default:n = true, math/mathml/luamml/load .usage:n=preamble, % \end{macrocode} % A key to activate math structure elements. % \changes{v0.6j}{2024-11-19}{no longer enable globally for better fake math handling, issue \#764} % \begin{macrocode} math/mathml/structelem .choice:, math/mathml/structelem/true .code:n = { \pdf_version_compare:NnT < {2.0} { \msg_warning:nnne { tag }{ PDF-2.0-recommended } { math/mathml/structelem }{ \pdf_version: } } \cs_set:Npn\@@_luamml_structelem:{\luamml_structelem:} \cs_set:Npn\@@_luamml_ignore:{\luamml_ignore:} }, math/mathml/structelem/false .code:n = { \cs_set_eq:NN\@@_luamml_structelem:\prg_do_nothing: \cs_set_eq:NN\@@_luamml_ignore:\prg_do_nothing: }, math/mathml/structelem .default:n = true, % \end{macrocode} % and a key to call the ignore flag. This should only be used locally. % \begin{macrocode} math/mathml/ignore .code:n = {\luamml_ignore:}, % \end{macrocode} % \begin{macrocode} math/mathml/luamml/write .choice:, math/mathml/luamml/write/true .code:n = { \tl_gset:Nn \g_@@_luamml_load_tl{1} \bool_gset_true:N \g_@@_luamml_write_bool }, math/mathml/luamml/write/false .code:n = { \bool_gset_false:N \g_@@_luamml_write_bool }, math/mathml/luamml/write .default:n = true, math/mathml/luamml/write .usage:n=preamble, % \end{macrocode} % alias keys for compatibility % \begin{macrocode} math/mathml/luamml .bool_gset:N = \g_@@_luamml_write_bool, math/mathml/luamml .usage:n=preamble } % \end{macrocode} % \begin{socketdecl}{tagsupport/math/mathml/write} % This writes a html-dummy with the hash and the math content. % This should be optional, so it uses a socket that can be disabled % % \begin{macrocode} \socket_new:nn {tagsupport/math/mathml/write}{0} % \end{macrocode} % \end{socketdecl} % % \begin{plugdecl}{On} % \begin{macrocode} \socket_new_plug:nnn{tagsupport/math/mathml/write}{On} { \iow_now:Ne \g_@@_writedummy_iow { \l_@@_mathml_write_before_tl \c_@@_mathml_write_after_tl } } % \end{macrocode} % \end{plugdecl} % And now a key to activate the socket. % \begin{macrocode} \keys_define:nn { __tag / setup } { math/mathml/write-dummy .code:n = { \bool_gset_true:N \g__tag_math_mathml_AF_bool \tl_if_exist:NF\g_@@_writedummy_iow { \iow_new:N \g_@@_writedummy_iow \iow_open:Nn \g_@@_writedummy_iow { \c_sys_jobname_str-mathml-dummy.html } \iow_now:Ne \g_@@_writedummy_iow { \c_@@_mathml_write_init_tl } \AssignSocketPlug {tagsupport/math/mathml/write/prepare}{On} \AssignSocketPlug {tagsupport/math/mathml/write}{On} \AddToHook{enddocument/afterlastpage} { \iow_now:Ne \g_@@_writedummy_iow { \c_@@_mathml_write_final_tl } \iow_close:N \g_@@_writedummy_iow } } }, math/mathml/write-dummy .usage:n=preamble } % \end{macrocode} % % \begin{macro}{\@@_AF_process_mathml_files:} % \begin{macrocode} \box_new:N\l_@@_tmpa_box \cs_new_protected:Npn \@@_AF_process_mathml_files: { \hbox_set:Nn \l_@@_tmpa_box { \pdfdict_put:nnn { l_pdffile/Filespec }{AFRelationship} { /Supplement } \pdfdict_put:nne { l_pdffile }{Subtype} { \pdf_name_from_unicode_e:n{application/mathml+xml} } \char_set_catcode_other:N \# \cs_set_eq:NN\mml \@@_AF_html_reader:w \clist_map_inline:Nn \l__tag_math_mathml_files_clist { \file_if_exist:nTF {##1.html} { \typeout{Info:~reading~mathml~file~##1} \file_input:n {##1.html} \bool_gset_true:N\g__tag_math_mathml_AF_bool } { \typeout{Info:~mathml~file~##1~does~not~exist}%info message } } } \bool_if:NT\g__tag_math_mathml_AF_bool { \typeout{Info:~Activating~mathml~support} \AssignSocketPlug{tagsupport/math/struct/begin}{mathml-AF} \AssignSocketPlug{tagsupport/math/struct/end}{mathml-AF} % \end{macrocode} % mathml handling doesn't like subparts, so we disable them for now: % \begin{macrocode} \AssignSocketPlug{tagsupport/math/substruct/begin}{single} \AssignSocketPlug{tagsupport/math/substruct/end}{single} \AddToHook{enddocument/info} { \iow_term:n{MathML~statistic} \iow_term:n{================} \iow_term:e{==>~\int_use:N\g_@@_mathml_total_int\c_space_tl MathML~fragments~read} \iow_term:e{==>~\int_use:N\g_@@_mathml_int\c_space_tl different~MathML~fragments} \iow_term:e{==>~\int_use:N\g_@@_math_total_int\c_space_tl math~fragments~found} \iow_term:e{==>~\int_use:N\g_@@_mathml_AF_found_int\c_space_tl fitting~MathML~AF~found} \iow_term:e{==>~\int_use:N\g_@@_mathml_AF_attached_int\c_space_tl MathML~AF~attached} } } } \AddToHook{begindocument}{\@@_AF_process_mathml_files:} % \end{macrocode} % \end{macro} % % \subsection{Mathstyle detection} % In some cases we need to detect the mathstyle used in a \cs{mathchoice} % command and to disable/enable tagging in the unused branches. % This is currently only used in the amstext command \cs{text} % but is perhaps also needed in other cases, so we create a general command. % %\begin{macro}{\l_@@_mathstyle_int,\g_@@_mathchoice_int,mathstyle} % \begin{macrocode} \int_new:N \l_@@_mathstyle_int \int_new:N \g_@@_mathchoice_int \property_new:nnnn{mathstyle}{now}{-1}{\int_use:N \l_@@_mathstyle_int } % \end{macrocode} %\end{macro} % For now internal, but perhaps will need a public version. % The command should be used in every branch of a \cs{mathchoice} % (with the correct mathstyle number) and with an unique label (which should % be the same in every branch). % \cs{g_@@_mathchoice_int} can be, e.g., increased before the mathchoice and % then used. % \begin{macro}{\@@_tag_if_mathstyle:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_tag_if_mathstyle:nn #1 #2 %#1 refers to label %#2 is a number for the mathstyle (typically 0,2,4,6) { \int_set:Nn \l_@@_mathstyle_int {#2} \property_record:nn {#1} { mathstyle } \int_compare:nNnTF { \property_ref:nn {#1}{ mathstyle} } = { #2 } { \tag_resume:n{\mathchoice} }{ \tag_suspend:n{\mathchoice} } } \cs_generate_variant:Nn \@@_tag_if_mathstyle:nn {en} % \end{macrocode} % \end{macro} % % \subsection{Tagging options} % \begin{macrocode} \keys_define:nn { __tag / setup } { math/mathml/sources .clist_set:N = \l__tag_math_mathml_files_clist, math/alt/use .bool_set:N = \l__tag_math_alt_bool, viewer/pane/mathml .bool_set:N = \l__tag_math_mathml_pane_bool, viewer/pane/mathml .initial:n = true, viewer/pane/mathsource .bool_set:N = \l__tag_math_texsource_pane_bool, math/mathml/AF .bool_set:N = \l__tag_math_mathml_AF_bool, math/mathml/AF .initial:n = true, math/tex/AF .bool_set:N = \l__tag_math_texsource_AF_bool, math/tex/AF .initial:n = true } % \end{macrocode} % alt is required for pdf/UA-1. % TODO: l3pdfmeta should support this test. % \begin{macrocode} \AddToHook{begindocument/end} { \str_if_eq:eeT {1} { \exp_last_unbraced:Ne\use_i:nn {\GetDocumentProperties{document/pdfstandard-UA}} \c_empty_tl\c_empty_tl } { \bool_if:NF \l__tag_math_alt_bool { \typeout{PDF/UA-1~detected.~Enabling~alt~text~on~Formula} } \bool_set_true:N\l__tag_math_alt_bool } } % \end{macrocode} % % \subsection{Sockets} % \subsubsection{Main inline math sockets} % % \begin{socketdecl} % { % tagsupport/math/inline/begin, % tagsupport/math/inline/end, % tagsupport/math/inline/formula/begin, % tagsupport/math/inline/formula/end, % } % The first two sockets are meant to embed inline % math into the surrounding (so to close/reopen, e.g., MC-chunks). % The other two implement the actual formula structure. % The formula sockets are despite their naming not symmetric: % the begin socket is issued after the math has started, while % the end socket is after the math! % \changes{v0.6j}{2024-11-19}{change the socket to two arguments so that it can % be used as tagging socket} % \begin{macrocode} \socket_new:nn {tagsupport/math/inline/begin}{0} \socket_new:nn {tagsupport/math/inline/end}{0} \socket_new:nn {tagsupport/math/inline/formula/begin}{2} % \socket_new:nn {tagsupport/math/inline/formula/end}{0} % \end{macrocode} %\end{socketdecl} % % % \begin{plugdecl}{MC} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/inline/begin} {MC} {\tag_mc_end_push:} \socket_new_plug:nnn {tagsupport/math/inline/end} {MC} {\tag_mc_begin_pop:n{}} % \end{macrocode} % \end{plugdecl} % % We probably will want to test different tagging recipes. % \begin{plugdecl}{default} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/inline/formula/begin} {default} % \end{macrocode} % \changes{v0.6g}{2024-10-02}{disable paratagging, issue \#711} % \changes{v0.6j}{2024-11-19}{activate structelem locally issue \#764} % \changes{v0.6j}{2024-11-19}{change to two arguments} % \begin{macrocode} { \tagpdfparaOff \@@_luamml_structelem: \tag_socket_use:n{math/content} \tag_socket_use:n{math/struct/begin} % \end{macrocode} % TODO: does inline math need subformula handling? % \begin{macrocode} % inner formula if multiple parts (not really implemented yet) \tag_socket_use:n{math/substruct/begin} #2 \tag_socket_use:n{math/end} } \socket_new_plug:nnn {tagsupport/math/inline/formula/end} {default} { \socket_use:n{tagsupport/math/substruct/end} \socket_use:n{tagsupport/math/struct/end} } % \end{macrocode} % \end{plugdecl} % % \subsubsection{Main display math sockets} % % \begin{socketdecl} % { % tagsupport/math/display/begin, % tagsupport/math/display/end, % tagsupport/math/display/formula/begin, % tagsupport/math/display/formula/end, % } % The first two sockets are meant to embed display % math into the surrounding (so to close/reopen, e.g., MC-chunks and % P-structure). % The other two implement the actual formula structure. % The formula sockets are despite their naming not symmetric: % the begin socket is issued after the math has started, while % the end socket is after the math! % \changes{v0.6j}{2024-11-19}{change number of arguments of formula/begin socket} % \begin{macrocode} \socket_new:nn {tagsupport/math/display/begin}{0} \socket_new:nn {tagsupport/math/display/end}{0} \socket_new:nn {tagsupport/math/display/formula/begin}{2} % \socket_new:nn {tagsupport/math/display/formula/end}{0} % \end{macrocode} %\end{socketdecl} % \begin{plugdecl}{default} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/display/begin} {default} { \__tag_tool_close_P: } \socket_new_plug:nnn {tagsupport/math/display/end} {default} { } % \end{macrocode} % \end{plugdecl} % \begin{plugdecl}{default} % \changes{v0.6j}{2024-11-19}{moved \cs{tagpdfparaOff} into the socket, tagging/765} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/display/formula/begin} {default} { \tagpdfparaOff \@@_luamml_structelem: \tag_socket_use:n{math/content} \tag_socket_use:n{math/struct/begin} \tag_socket_use:n{math/substruct/begin} #2 \tag_socket_use:n{math/end} } \socket_new_plug:nnn {tagsupport/math/display/formula/end} {default} { \socket_use:n{tagsupport/math/substruct/end} \socket_use:n{tagsupport/math/struct/end} } % \end{macrocode} % \end{plugdecl} % % \subsubsection{Internal sockets} % % \begin{variable}{\l_@@_content_template_tl} % The default text used as alt or actual text. % \begin{macrocode} \tl_new:N\l_@@_content_template_tl \tl_set:Nn \l_@@_content_template_tl { LaTeX~ formula~ starts~ \exp_not:N\begin{\g_@@_grabbed_env_tl} \c_space_tl \exp_not:V\g_@@_grabbed_math_tl \c_space_tl \exp_not:N\end{\g_@@_grabbed_env_tl} \c_space_tl LaTeX~ formula~ ends~ } % \end{macrocode} % \end{variable} % \begin{variable}{\l_@@_texsource_template_tl} % The default text used as texsource % \begin{macrocode} \tl_new:N\l_@@_texsource_template_tl \tl_const:Nn\c_@@_inline_env_tl {math} \tl_set:Nn \l_@@_texsource_template_tl { \tl_if_eq:NNTF\g_@@_grabbed_env_tl\c_@@_inline_env_tl { $ \exp_not:V\g_@@_grabbed_math_tl $ } { \exp_not:N\begin{\g_@@_grabbed_env_tl} \exp_not:V\g_@@_grabbed_math_tl \exp_not:N\end{\g_@@_grabbed_env_tl} } } % \end{macrocode} % \end{variable} % % \begin{socketdecl}{tagsupport/math/content} % The math content is stored in associated files and used for % actual and alternative text. As the exact text is still % unclear we use a socket to be able to test variants. % The socket should set all four tl vars above, if needed % to identical values. It can use the two variables % \cs{g_@@_grabbed_env_tl} and \cs{g_@@_grabbed_math_tl} % \begin{macrocode} \socket_new:nn {tagsupport/math/content}{0} % \end{macrocode} % \end{socketdecl} % % Some default sockets to set the contents. % TODO: think about naming convention. % TODO: think how this should organized so that one % has options to change from the outside and so that % there are less repetitions. % \begin{plugdecl}{actual+source} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/content} {actual+source} { \tl_set:Ne\l_@@_content_actual_tl { \l_@@_content_template_tl } \tl_set:Ne \l_@@_content_AF_source_tl { \l_@@_texsource_template_tl } \tl_set:Nn \l_@@_content_AF_mathml_tl {} \tl_set:Nn \l_@@_content_alt_tl {} } % \end{macrocode} % \end{plugdecl} % % \begin{plugdecl}{alt+source} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/content} {alt+source} { \tl_set:Ne\l_@@_content_alt_tl { \l_@@_content_template_tl } \tl_set:Ne \l_@@_content_AF_source_tl { \l_@@_texsource_template_tl } \tl_set:Nn \l_@@_content_AF_mathml_tl {} \tl_set:Nn \l_@@_content_actual_tl {} } % \end{macrocode} % \end{plugdecl} % \begin{macrocode} \socket_assign_plug:nn {tagsupport/math/content}{alt+source} % \end{macrocode} % % \begin{socketdecl}{tagsupport/math/struct/begin, % tagsupport/math/struct/end} % For the main structure we use a socket too. % This allows, e.g., to create a special one for luamml % which setups additional objects. % The begin socket can use the two variables % \cs{g_@@_grabbed_env_tl} and \cs{g_@@_grabbed_math_tl} % \begin{macrocode} \socket_new:nn {tagsupport/math/struct/begin}{0} \socket_new:nn {tagsupport/math/struct/end}{0} % \end{macrocode} % \end{socketdecl} % % \begin{plugdecl}{default} % TODO: think about some naming convention ... % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/struct/begin} {default} { \bool_if:NTF\l__tag_math_texsource_AF_bool { \tl_set_eq:NN \l_@@_content_AF_source_tmpa_tl \l_@@_content_AF_source_tl } { \tl_clear:N \l_@@_content_AF_source_tmpa_tl } \tl_if_eq:NnTF\g_@@_grabbed_env_tl {math} { \tl_set:Nn\l_@@_attribute_class_tl{inline} } { \tl_set:Nn\l_@@_attribute_class_tl{display} } \bool_if:NF\l__tag_math_alt_bool { \tl_set:Nn \l_@@_content_alt_tl{} } \tag_struct_begin:n { tag=Formula, attribute-class=\l_@@_attribute_class_tl, texsource = \l_@@_content_AF_source_tmpa_tl, title-o = \g_@@_grabbed_env_tl, actualtext = \l_@@_content_actual_tl, alt = \l_@@_content_alt_tl } } \socket_new_plug:nnn {tagsupport/math/struct/end} {default} { \tag_struct_end: } \socket_assign_plug:nn {tagsupport/math/struct/begin}{default} \socket_assign_plug:nn {tagsupport/math/struct/end}{default} % \end{macrocode} % \end{plugdecl} % % \begin{plugdecl}{mathml-AF} % This socket tries to add a mathml-AF to formula. % It is activated if a mathml.html has been found and loaded. % As it disturbs the reading of the AF % it currently deactivates the /Alt key, % unless it has been reenabled with |math/alt/use=true| % \begin{macrocode} \cs_generate_variant:Nn \str_mdfive_hash:n {o} \tl_new:N\l_@@_content_hash_tl % \end{macrocode} % we need to save the grabbed math: % \begin{macrocode} \tl_new:N\l_@@_grabbed_math_tl % \end{macrocode} % the socket definition % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/struct/begin} {mathml-AF} { \int_gincr:N\g_@@_math_total_int \tl_set:Ne\l_@@_content_hash_tl {\str_mdfive_hash:o { \l_@@_content_AF_source_tl }} \tl_set_eq:NN\l_@@_grabbed_math_tl\g_@@_grabbed_math_tl \tl_if_eq:NnTF\g_@@_grabbed_env_tl {math} { \tl_set:Nn\l_@@_attribute_class_tl{inline} } { \tl_set:Nn\l_@@_attribute_class_tl{display} } \bool_if:NF\l__tag_math_alt_bool { \tl_set:Nn \l_@@_content_alt_tl{} } % \end{macrocode} % debugging option. TODO: hide in debug key. % \begin{macrocode} \tl_if_exist:cTF { g_@@_mathml_ \l_@@_content_hash_tl _tl } { \int_gincr:N\g_@@_mathml_AF_found_int \bool_if:NTF \l__tag_math_mathml_AF_bool { \int_gincr:N\g_@@_mathml_AF_attached_int \typeout {Inserting~mathml~with~Hash~\l_@@_content_hash_tl} } { \typeout {Ignoring~mathml~with~Hash~\l_@@_content_hash_tl} } } { \bool_if:NT \l__tag_math_mathml_AF_bool { \typeout {WARNING:~mathml~missing~for~hash~\l_@@_content_hash_tl} } } \socket_use:n {tagsupport/math/mathml/write/prepare} \socket_use:n {tagsupport/math/mathml/write} % write hash if request \bool_if:NTF\l__tag_math_texsource_AF_bool { \tl_set_eq:NN \l_@@_content_AF_source_tmpa_tl \l_@@_content_AF_source_tl } { \tl_clear:N \l_@@_content_AF_source_tmpa_tl } \tag_struct_begin:n { tag=Formula, attribute-class=\l_@@_attribute_class_tl, % AFref = \bool_if:NT\l__tag_math_mathml_AF_bool { \cs_if_exist_use:c {g_@@_mathml_ \l_@@_content_hash_tl _tl} }, texsource = \l_@@_content_AF_source_tmpa_tl, % should be after mathml AF! title-o = \g_@@_grabbed_env_tl, % alt = \l_@@_content_alt_tl } } % \end{macrocode} % not really needed but looks more symmetric: % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/struct/end} {mathml-AF} { \tag_struct_end: } % \end{macrocode} % \end{plugdecl} % % \begin{socketdecl}{tagsupport/math/substruct/begin, % tagsupport/math/substruct/end} % This holds the code to handle subparts of the formula. % \begin{macrocode} \socket_new:nn {tagsupport/math/substruct/begin}{0} \socket_new:nn {tagsupport/math/substruct/end}{0} % \end{macrocode} % \end{socketdecl} % % \begin{plugdecl}{default} % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/substruct/begin} {default} { \grabaformulapartandstart } \socket_new_plug:nnn {tagsupport/math/substruct/end} {default} { \tagmcend \if@subformulas \tagstructend \fi } \socket_assign_plug:nn {tagsupport/math/substruct/begin}{default} \socket_assign_plug:nn {tagsupport/math/substruct/end}{default} % \end{macrocode} % \end{plugdecl} % \begin{plugdecl}{single} % We need an option to disable subparts as it is unclear % if consumers can handle them: % \begin{macrocode} \socket_new_plug:nnn {tagsupport/math/substruct/begin} {single} { \typeout{====>subpart~splitting~deactivated} \typeout{====>grabbed~math=\meaning\g_@@_grabbed_math_tl} \tag_mc_begin:n{} } \socket_new_plug:nnn {tagsupport/math/substruct/end} {single} { \tag_mc_end:} % \end{macrocode} % \end{plugdecl} % % \begin{socketdecl}{tagsupport/math/end} % A socket used at the end of the math (before the closing dollar(s)) % which can, e.g., set a flag for luamml. % \begin{macrocode} \socket_new:nn {tagsupport/math/end}{0} % \end{macrocode} % \end{socketdecl} % % % \changes{v0.6j}{2024-11-19}{removed enable/disable command and assign tagging sockets directly} % \begin{macrocode} \socket_assign_plug:nn {tagsupport/math/inline/begin}{MC} \socket_assign_plug:nn {tagsupport/math/inline/end}{MC} \socket_assign_plug:nn {tagsupport/math/inline/formula/begin}{default} \socket_assign_plug:nn {tagsupport/math/inline/formula/end}{default} \socket_assign_plug:nn {tagsupport/math/display/begin}{default} \socket_assign_plug:nn {tagsupport/math/display/end}{default} \socket_assign_plug:nn {tagsupport/math/display/formula/begin}{default} \socket_assign_plug:nn {tagsupport/math/display/formula/end}{default} % \end{macrocode} % % % \subsection{Interface commands} % % \begin{macro} % {\@@_process:nn, \@@_process:Vn, \@@_process_auxi:nn, \@@_process_auxii:nn} % A no-op place-holder; the internal wrapper means that it does not need to % be concerned with internals. % \begin{macrocode} \cs_new_protected:Npn \@@_process:nn #1#2 { \legacy_if:nF { measuring@ } { \tl_if_in:nnTF {#2} { \m@th } { \bool_set_true:N\l_@@_fakemath_bool } { \tl_trim_spaces_apply:nN {#2} \@@_process_auxi:nn {#1} } } } \cs_generate_variant:Nn \@@_process:nn { V } \cs_new_protected:Npn \@@_process_auxi:nn #1#2 { \tl_gset:Nn \g_@@_grabbed_env_tl {#2} \tl_gset:Nn \g_@@_grabbed_math_tl {#1} \@@_process_auxii:nn {#2} {#1} } \cs_new_protected:Npn \@@_process_auxii:nn #1#2 { } % \end{macrocode} % \end{macro} % % \begin{macro}{\math_processor:n} % A simple installer % \begin{macrocode} \cs_new_protected:Npn \math_processor:n #1 { \cs_set_protected:Npn \@@_process_auxii:nn ##1##2 {#1} } % \end{macrocode} % \end{macro} % % \subsection{Content grabbing} % % \begin{macro}{\@@_grab_dollar:w} % \begin{macro}{\@@_grab_dollar:n} % \changes{v0.6c}{2024-08-22}{Correct handling of empty math segments} % Top-level function to handle grabbing of inline math mode delimited by % |$| tokens. We provide two different ways to do that: a token-by-token % one that can be used everywhere, and a fast delimited one that does not % work anywhere that the end |$| token may be hidden, most obviously in % tabulars. The function here is therefore set up as a variable starting % point. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_dollar:w { \@@_grab_dollar_delim:w } % \end{macrocode} % After grabbing inline math material, there is again common processing % independent of mechanism of collection. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_dollar:n #1 { % \end{macrocode} % We need to do processing first as this picks up \enquote{fake} math mode: % that information is needed below. % \begin{macrocode} \@@_process:nn { math } {#1} % \end{macrocode} % We do not want math tagging in fakemath or when measuring, % We also do not want math tagging if tagging has been suspended. % \begin{macrocode} \bool_lazy_any:nTF { {\legacy_if_p:n { measuring@ }} { \l_@@_fakemath_bool } { \tl_if_blank_p:n {#1} } } { \@@_luamml_ignore: #1 $ % $ } { \tag_socket_use:n {math/inline/begin} %end P-MC % \end{macrocode} % We do no use a tagging socket here, so that the argument (the % math) is not lost, tagging-project issue 661. % \changes{v0.6j}{2024-11-19}{change socket to tagging socket} % \begin{macrocode} \tag_socket_use:nnn {math/inline/formula/begin}{}{#1} $ % $ \tag_socket_use:n {math/inline/formula/end} \tag_socket_use:n {math/inline/end} % restart P-MC } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_grab_dollar_delim:w} % Grab up to a single |$|, for inline math mode, suppressing % any processing if the token is \tn{m@th} found in the content. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_dollar_delim:w #1 $ % $ { \@@_grab_dollar:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_grab_dollardollar:w} % And for the classical \TeX{} display structure. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_dollardollar:w % $$ #1 $$ { \tl_if_blank:nF {#1} { \@@_process:nn { equation* } {#1} \tag_socket_use:n {math/display/begin} \tag_socket_use:nn{math/display/formula/begin}{}{#1} } $$ } % \end{macrocode} % % The end code is added through a \cs{aftergroup} so we % store it inside a command. % \changes{v0.6j}{2024-11-19}{removed unneeded \cs{tagpdfparaOn}} % \begin{macrocode} \cs_new_protected:Npn \@@_tag_dollardollar_display_end: { % \typeout{== tag dollarldollar display end} % \ShowTagging{struct-stack} \para_raw_end: % \end{macrocode} % The \cs{postdisplaypenalty} was temporarily set to 10000 inside % the display and the \cs{belowdisplayskip} and the % \cs{belowdisplayshortskip} was negated, so whatever was inserted % it should have been a negative skip. Whatever skip was added we % pick it ups value up here, so that we can correct the spacing % after the tagging code was inserted. % \begin{macrocode} \l_@@_tmpa_skip \lastskip \tag_socket_use:n{math/display/formula/end} % \end{macrocode} % Now we add a skip without indroducing a page break possibility, % that should bring the current vertical position back to the point % where \TeX{} would add the penalty and the \enquote{below skip}. % \changes{v0.6f}{2024-09-30}{Correct logic for inserting below skips % after displays (tagging/721)} % \begin{macrocode} \nobreak \skip_vertical:n { -\l_@@_tmpa_skip } % remove the negative belowdisplayskip % \end{macrocode} % Then we finally add the real stuff: % \begin{macrocode} \penalty \postdisplaypenalty \skip_vertical:n { -\l_@@_tmpa_skip } % insert the correct skip \@doendpe % this has no \end{...} to take care of it } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_grab_inline:w} % Collect inline math content and deal with the need to move to math mode. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_inline:w % \( #1 \) { \tl_if_blank:nF {#1} { $ #1 $ } \bool_set_false:N \l_@@_collected_bool } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_grab_eqn:w} % For the most common use of \cs{[}/\cs{]}: turn into an environment. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_eqn:w % \[ #1 \] { % \typeout{collected? = \bool_if:NTF \l_@@_collected_bool {true}{false}} \begin { equation* } #1 \end { equation* } } % \end{macrocode} % \end{macro} % % \subsection{Token-by-token inline grabbing} % % Grabbing inline math token-by-token is more involved. The mechanism here % is essentially a simplified version of that originally seen in % \pkg{collcell} and refined in \pkg{siunitx}. We make use of the fact that % in math mode spaces are ignored, so we have to deal with only \texttt{N}-type % tokens and groups. Furthermore, there is no need to look inside groups, so % the only special cases are a small selection of \texttt{N}-type tokens. % % \begin{variable}{\l_@@_grabbed_tl} % For collection of the material piecewise. % \begin{macrocode} \tl_new:N \l_@@_grabbed_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_grab_env_int} % Needed to count up the number of nested environments encountered. % \begin{macrocode} \int_new:N \l_@@_grab_env_int % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_grab_dollar_loop:} % \begin{macro}{\@@_grab_loop:} % The lead-off here establishes a group: we need that as we will have to % be careful in the way \tn{cr} is handled and ensure this is only % manipulated whilst grabbing. The main loop is then started. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_dollar_loop: { \group_begin: \tl_clear:N \l_@@_grabbed_tl \@@_grab_loop: } \cs_new_protected:Npn \@@_grab_loop: { \peek_remove_spaces:n { \peek_meaning:NTF \c_group_begin_token { \@@_grab_loop_group:n } { \@@_grab_loop_token:N } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_grab_loop_group:n} % \begin{macro}{\@@_grab_loop_store:n} % Handling of grabbed groups is pretty easy. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_loop_group:n #1 { \@@_grab_loop_store:n { {#1} } } \cs_new_protected:Npn \@@_grab_loop_store:n #1 { \tl_put_right:Nn \l_@@_grabbed_tl {#1} \@@_grab_loop: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_grab_loop_token:N} % \begin{macro} % { % \@@_grab_loop_$: , % \@@_grab_loop_\\: , % \@@_grab_loop_\begin: , % \@@_grab_loop_\end: , % \@@_grab_loop_\ignorespaces: , % \@@_grab_loop_\unskip: , % \@@_grab_loop_\textonly@unskip: % } % Filter out the special cases: for performance reasons, use a hash table % approach rather than a loop (\emph{cf.}~\pkg{collcell}). % \begin{macrocode} \cs_new_protected:Npn \@@_grab_loop_token:N #1 { \cs_if_exist_use:cF { @@_grab_loop_ \token_to_str:N #1 : } { \@@_grab_loop_store:n {#1} } } \cs_new_protected:cpn { @@_grab_loop_ \token_to_str:N $ : } { \@@_grab_loop_end: } \cs_new_protected:cpn { @@_grab_loop_ \token_to_str:N \\ : } { \int_compare:nNnTF \l_@@_grab_env_int = 0 { \@@_grab_loop_newline: } { \@@_grab_loop_store:n { \\ } } } % \end{macrocode} % In contrast to \pkg{collcell}, nesting is tracked by counting % \cs{begin}/\cs{end} pairs: this is needed in case there is a tabular-like % construct containing |\\| inside a cell. As a result, the end-of-tabular % can be detected without checking the name argument: if \cs{end} is % encountered at nesting level~0, we've hit the end of a cell. In that case, % end the row and leave the environment to clean up. % \begin{macrocode} \cs_new_protected:cpn { @@_grab_loop_ \token_to_str:N \begin : } { \int_incr:N \l_@@_grab_env_int \@@_grab_loop_store:n { \begin } } \cs_new_protected:cpn { @@_grab_loop_ \token_to_str:N \end : } { \int_compare:nNnTF \l_@@_grab_env_int = 0 { \@@_grab_loop_newline: \end } { \int_decr:N \l_@@_grab_env_int \@@_grab_loop_store:n { \end } } } \tl_map_inline:nn { \ignorespaces \unskip \textonly@unskip } { \cs_new_protected:cpn { @@_grab_loop_ \token_to_str:N #1 : } { \@@_grab_loop: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_grab_loop_newline:} % To allow collection of tokens in the part of the \tn{halign} template after % |#|, we need \TeX{} to see the primitive with the loop token in the right % place. That is done by re-defining \tn{cr} at present. Ideally there would % be a socket in the definition of \texttt{tabular}, etc., to handle this: % there is also the need to examine in interaction with \pkg{longtable}, which % also redefines \tn{cr}. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_loop_newline: { \if_false: { \fi: \cs_set_protected:Npn \cr { \@@_grab_loop: \tex_cr:D } \if_false: } \fi: \\ } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_grab_loop_end:} % Clean up and pass on. % \begin{macrocode} \cs_new_protected:Npn \@@_grab_loop_end: { \exp_args:NNV \group_end: \@@_grab_dollar:n \l_@@_grabbed_tl } % \end{macrocode} % \end{macro} % % \subsection{Marking math environments} % % A general mechanism for math mode environments that do not grab their % content (\emph{cf.}~most \pkg{amsmath} environments). % % \begin{variable}{\l_@@_env_name_tl} % To allow us to carry out \enquote{special effects} % \begin{macrocode} \tl_new:N \l_@@_env_name_tl % \end{macrocode} % \end{variable} % % Here we set up specialised handling of environments. The idea for the % \texttt{arg-spec} key is that if an environment takes arguments, we % don't worry during the main grabbing. Rather, we remove the arguments % from the grabbed content and forward only the payload. That is done by % (ab)using \pkg{ltcmd}. % \begin{macrocode} \keys_define:nn { @@ } { arg-spec .code:n = { \ExpandArgs { c } \DeclareDocumentCommand { @@_env \l_@@_env_name_tl _aux: } {#1} { \@@_env_forward:w } } } % \end{macrocode} % % \begin{macro}{\math_register_env:nn} % \begin{macro}{\math_register_env:n} % \begin{macro}{\RegisterMathEnvironment} % Set up to capture environment content and make available. % % \begin{macrocode} \cs_new_protected:Npn \math_register_env:nn #1#2 { \tl_set:Nn \l_@@_env_name_tl {#1} \keys_set:nn { @@ } {#2} \cs_gset_eq:cc { @@_env_ #1 _begin: } {#1} \cs_gset_eq:cc { @@_env_ #1 _end: } { end #1 } % \ExpandArgs { nne } \RenewDocumentEnvironment {#1} { b } { \exp_not:N \bool_if:NTF \exp_not:N \l_@@_collected_bool { % \typeout{===>B1} } { % \typeout{===>B2} \cs_if_exist:cTF { @@_env #1 _aux: } { \exp_not:c { @@_env #1 _aux: } ##1 \exp_not:N \@@_env_end: {#1} } { \exp_not:N \@@_process:nn {#1} {##1} } \exp_not:n { \@kernel@math@registered@begin } \bool_set_true:N \exp_not:N \l_@@_collected_bool } % \exp_not:N \tracingall \exp_not:c { @@_env_ #1 _begin: } ##1 \exp_not:c { @@_env_ #1 _end: } % \exp_not:N \tracingnone } { } } \cs_new_protected:Npn \math_register_halign_env:nn #1#2 { \tl_set:Nn \l_@@_env_name_tl {#1} \keys_set:nn { @@ } {#2} \cs_gset_eq:cc { @@_env_ #1 _begin: } {#1} \cs_gset_eq:cc { @@_env_ #1 _end: } { end #1 } % \ExpandArgs { nnee } \RenewDocumentEnvironment {#1} { b } { \exp_not:N \bool_if:NTF \exp_not:N \l_@@_collected_bool { % \typeout{===>B1} } { % \typeout{===>B2} \cs_if_exist:cTF { @@_env #1 _aux: } { \exp_not:c { @@_env #1 _aux: } ##1 \exp_not:N \@@_env_end: {#1} } { \exp_not:N \@@_process:nn {#1} {##1} } \exp_not:n { \@kernel@math@registered@begin } \bool_set_true:N \exp_not:N \l_@@_collected_bool } % \exp_not:N \tracingall \exp_not:c { @@_env_ #1 _begin: } ##1 % \exp_not:N \tracingnone } { \exp_not:c { @@_env_ #1 _end: } } } % \end{macrocode} % TODO: the following command is neither documented nor used. Is is needed? % \begin{macrocode} \cs_new_protected:Npn \math_register_odd_env:nn #1#2 { \tl_set:Nn \l_@@_env_name_tl {#1} \keys_set:nn { @@ } {#2} \cs_gset_eq:cc { @@_env_ #1 _begin: } {#1} \cs_gset_eq:cc { @@_env_ #1 _end: } { end #1 } % \ExpandArgs { nnee } \RenewDocumentEnvironment {#1} { b } { \exp_not:N \bool_if:NTF \exp_not:N \l_@@_collected_bool { % \typeout{===>B1} } { % \typeout{===>B2} \cs_if_exist:cTF { @@_env #1 _aux: } { \exp_not:c { @@_env #1 _aux: } ##1 \exp_not:N \@@_env_end: {#1} } { \exp_not:N \@@_process:nn {#1} {##1} } \exp_not:n { \@kernel@math@registered@begin } \bool_set_true:N \exp_not:N \l_@@_collected_bool } % \exp_not:N \tracingall \exp_not:c { @@_env_ #1 _begin: } ##1 } { \exp_not:c { @@_env_ #1 _end: } % needed if we don't have $$...$$ % \exp_not:n { \typeout{---> @kernel@math@registered@end }} \exp_not:n { \@kernel@math@registered@end } } } % FMi: compare with block change! % % \DeclareRobustCommand*\begin[1]{% % \UseHook{env/#1/before}% % \@ifundefined{#1}% % {\def\reserved@a{\@latex@error{Environment #1 undefined}\@eha}}% % {\def\reserved@a{\def\@currenvir{#1}% % \edef\@currenvline{\on@line}% % \@execute@begin@hook{#1}% % \csname #1\endcsname}}% % \@ignorefalse % \begingroup % \@endpefalse % tmp!!! is it ok to drop this here? % \reserved@a} \cs_new:Npn \@kernel@math@registered@begin { % \ShowTagging{struct-stack} %\typeout{==>A1}\ShowTagging{struct-stack,mc-current} \mode_if_vertical:TF { % \legacy_if:nTF { @endpe } % { \legacy_if_set_false:n { @endpe } } % { \__block_list_beginpar_vmode: } % % \typeout{==>~ at:~ \g__tag_struct_tag_tl} % \tag_if_active:T { \exp_args:Noo\str_if_eq:nnF \g__tag_struct_tag_tl { \l__tag_para_main_tag_tl } % needs correction! { % \typeout{==>A2} \__block_beginpar_vmode: } % needs correction! } } { % \typeout{==>A3} \__tag_tool_close_P: } \tag_socket_use:nn{math/display/formula/begin}{}{} % \typeout{==>MC1}\ShowTagging{mc-current} } \cs_new:Npn \@kernel@math@registered@end { % \typeout{==>MC2}\ShowTagging{mc-current} \para_raw_end: \tagpdfparaOn \tag_socket_use:n{tagsupport/math/display/formula/end} % \typeout{==>MC3}\ShowTagging{mc-current} \@endpetrue } \cs_new_protected:Npn \math_register_env:n #1 { \math_register_env:nn {#1} { } } \NewDocumentCommand \RegisterMathEnvironment { O{} m } { \math_register_env:nn {#2} {#1} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_env_forward:w} % \begin{macrocode} \cs_new_protected:Npn \@@_env_forward:w #1 \@@_env_end: #2 { \@@_process:nn {#2} {#1} } % \end{macrocode} % \end{macro} % % \subsection{Document commands} % % \car*{Add one more here: \texttt{displaymath}, which % is equivalent to \cs{[} , \cs{]}\\ % and hence to the basic \texttt{equation*}.\\ % Added in more recent branch.} % % \begin{macro} % {\equation, \@@_equation_begin:, \equation*, \@@_equation_star_begin:} % \begin{macro} % {\endequation, \@@_equation_end:, \endequation*, \@@_equation_star_end:} % These environments are not set up by \pkg{amsmath} to collect their body, % so we do that here. This has to be done \emph{after} we can be sure % \pkg{amsmath} is loaded. % % \car*{Note that with \pkg{amsmath} loaded, \texttt{equation*} and \texttt{equation}\\ % are the two basics: they are used to define the other single-row\\ % display environments, etc.} % % \begin{macrocode} \tl_gput_right:Nn \@kernel@before@begindocument { \math_register_env:n { equation } \math_register_env:n { equation* } % at the moment register_env can only do display math % \math_register_env:n { math } \RenewDocumentEnvironment{math} {b}{$#1$}{} % and this one doesn't work either % \math_register_env:n { displaymath } \RenewDocumentEnvironment{displaymath} {b}{\[#1\]}{} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\(, \)} % If math mode has not been collected, we need to do that; otherwise, worry % about whether we are in math mode or not. The closing command here can only % occur inside a collected math block: otherwise it will be simply used as % a delimiter. % \begin{macrocode} \cs_gset_protected:Npn \( % \) { \bool_if:NTF \l_@@_collected_bool { \mode_if_math:TF { \@badmath } { $ } } { \@@_grab_inline:w } } % \( \cs_gset_protected:Npn \) { \mode_if_math:TF { $ } { \@badmath } } % \end{macrocode} % \end{macro} % % \begin{macro}{\[, \]} % Again, we need to watch for when \pkg{amsmath} is loaded after this code. % The flag usage here is to cover the case where \cs{[}/\cs{]} is hidden % inside another environment. In this case the grabbing happens on % the outer level and should not be repeated. % \begin{macrocode} \tl_gput_right:Nn \@kernel@before@begindocument { \cs_gset_protected:Npn \[ % \] { \@@_grab_eqn:w % \bool_if:NTF \l_@@_collected_bool % { \begin { equation* } } % { \@@_grab_eqn:w } } % \[ \cs_gset_protected:Npn \] { \@badmath % \bool_if:NTF \l_@@_collected_bool % { \end{ equation* } } % { \@badmath } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\ensuremath} % A bit of nesting fun to make sure we collect only if required. % \fmi{why does ensuremath need handling at all?} % % \car{Indeed! Currently, this is setup to process the math that % it has anyways already captured as its argument; thus it is more % efficient than leaving the capture to be repeated by the \cs{everymath}} % % \begin{macrocode} %\cs_gset_protected:Npn \ensuremath #1 % { % \mode_if_math:TF % {#1} % { % \bool_if:NTF \l_@@_collected_bool % { \@ensuredmath {#1} } % { % \bool_set_true:N \l_@@_collected_bool % \@@_process:nn { math } {#1} % \@ensuredmath {#1} % \bool_set_false:N \l_@@_collected_bool % } % } % } % \end{macrocode} % \end{macro} % % \subsection{\cs{everymath} and \cs{everydisplay}} % % The business end for grabbing inline math and \enquote{raw} \TeX{} % display. Most display math mode is actually handled elsewhere, as we % have macro control. % \begin{macrocode} \exp_args:No \tex_everymath:D { \tex_the:D \tex_everymath:D \bool_if:NF \l_@@_collected_bool { \bool_set_true:N \l_@@_collected_bool \@@_grab_dollar:w } } \exp_args:No \tex_everydisplay:D { \tex_the:D \tex_everydisplay:D \iftrue % this may have to be a settable flag! % \typeout{==>~ in~ everydisplay} % \end{macrocode} % flipping the \cs{belowdisplay} values is done so that we get (assumption) % a negative skip and not make the page bigger then we take that out, % then we add the tagging code (in \cs{@@_tag_dollardollar_display_end} ) and % then we put a real \cs{postdisplaypenalty} in and % the right skip (of which we don't know if it is short or a % normal \cs{belowdisplayskip}). This might need some refinement if that skip % is actually negative from the start % (not sure it ever is and is worth bothering about) % \begin{macrocode} \skip_set:Nn \belowdisplayskip {-\belowdisplayskip} \skip_set:Nn \belowdisplayshortskip {-\belowdisplayshortskip} \int_set:Nn \postdisplaypenalty {10000} \group_insert_after:N \@@_tag_dollardollar_display_end: \fi \bool_if:NF \l_@@_collected_bool { \bool_set_true:N \l_@@_collected_bool \@@_grab_dollardollar:w } } % \end{macrocode} % % \subsection{Modifying kernel environments} % % We need to cover this even though it is, of course, not encouraged. % \begin{macrocode} \math_register_env:n { eqnarray } \math_register_env:n { eqnarray* } % \end{macrocode} % % Tabulars currently contain a \$ that shouldn't trigger math % tagging. % \begin{macrocode} \RequirePackage{array} \tl_if_in:NnT\@tabular{$} { \def\@tabular{% \leavevmode \UseTaggingSocket{tbl/hmode/begin}% \hbox \bgroup \bool_set_true:N \l_@@_collected_bool $ \bool_set_false:N \l_@@_collected_bool \col@sep\tabcolsep \let\d@llarbegin\begingroup \let\d@llarend\endgroup % \end{macrocode} % A proper switching mechanism is needed: for the present, do directly. % \begin{macrocode} \cs_set_protected:Npn \@@_grab_dollar:w { \@@_grab_dollar_loop: } \@tabarray} } % \end{macrocode} % % \begin{macro}{\@@_m@th:, \m@th} % Handle non-math use of math mode. At present nesting isn't supported as % \cs{m@th} pops up in a few places that \emph{are} math mode! % \begin{macrocode} \cs_new_eq:NN \@@_m@th: \m@th \cs_gset_protected:Npn \m@th { \bool_set_true:N \l_@@_collected_bool \@@_m@th: } % \end{macrocode} % \end{macro} % % \subsection{Disable math grabbing in the begindocument hook} % For example amsart uses math to measure text there. % % \begin{macrocode} \tl_gput_right:Nn\@kernel@before@begindocument { \bool_set_true:N\l_@@_collected_bool } \tl_gput_right:Nn\@kernel@after@begindocument { \bool_set_false:N\l_@@_collected_bool } % \end{macrocode} % % \subsection{Modifying \pkg{amsmath}} % % \begin{macro}{\@@_amsmath_align@:nn} % \begin{macro} % { % \@@_amsmath_gather@:n , % \@@_amsmath_multline@:n % } % \begin{macro}{\align@} % \begin{macro}{\gather@, \multline@} % Mark up all of the display environments as the content is captured anyway. % We then use an internal macro in each environment type to insert the % processing code. Each of these is slightly different, so we cannot use a % simple loop here. The test for \cs{split@tag} is required as the % \texttt{split} environment internally uses \texttt{gather} \emph{when not % within an \pkg{amsmath}} environment, for example inside \texttt{equation}. % Without the precaution, we'd get two copies of the grabbed math, the second % of which would start with \cs{split@tag}. % \begin{macrocode} \tl_gput_right:Nn \@kernel@before@begindocument { % \renewenvironment{gather*}{% \start@gather\st@rredtrue } {% % this redirection doesn't work if we alter "gather"! % \endgather % so replace it with its real meaning \math@cr \black@\totwidth@ \egroup $$\ignorespacesafterend } % \end{macrocode} % % \begin{macrocode} \def\common@align@ending { \math@cr \black@\totwidth@ \egroup \ifingather@ \restorealignstate@ \egroup \nonumber \ifnum0=`{\fi\iffalse}\fi \else $$% \fi \ignorespacesafterend } \renewenvironment{alignat}{% \start@align\z@\st@rredfalse }{% \common@align@ending } \renewenvironment{alignat*}{% \start@align\z@\st@rredtrue }{% \common@align@ending } \renewenvironment{xalignat}{% \start@align\@ne\st@rredfalse }{% \common@align@ending } \renewenvironment{xalignat*}{% \start@align\@ne\st@rredtrue }{% \common@align@ending } \renewenvironment{xxalignat}{% \start@align\tw@\st@rredtrue }{% \common@align@ending } \renewenvironment{align}{% \start@align\@ne\st@rredfalse\m@ne }{% \common@align@ending } \renewenvironment{align*}{% \start@align\@ne\st@rredtrue\m@ne }{% \common@align@ending } \renewenvironment{flalign}{% \start@align\tw@\st@rredfalse\m@ne }{% \common@align@ending } \renewenvironment{flalign*}{% \start@align\tw@\st@rredtrue\m@ne }{% \common@align@ending } % \renewenvironment{multline*}{\start@multline\st@rredtrue} {% \iftagsleft@ \@xp\lendmultline@ \else \@xp\rendmultline@ \fi \ignorespacesafterend } % \end{macrocode} % Also for "false?" % \begin{macrocode} \def\measuring@true{\let\ifmeasuring@\iftrue\tag_suspend:n{\measuring}} % \end{macrocode} % % \begin{macrocode} % \math_register_halign_env:nn {align}{} \math_register_halign_env:nn {align*}{} \math_register_halign_env:nn {alignat}{} \math_register_halign_env:nn {alignat*}{} \math_register_halign_env:nn {flalign}{} \math_register_halign_env:nn {flalign*}{} \math_register_halign_env:nn {gather}{} \math_register_halign_env:nn {gather*}{} \math_register_halign_env:nn {multline}{} \math_register_halign_env:nn {multline*}{} \math_register_halign_env:nn {xalignat}{} \math_register_halign_env:nn {xalignat*}{} \math_register_halign_env:nn {xxalignat}{} % \@namedef{maketag @ @ @} #1{% % \typeout{--->maketag @ @ @} \ifmeasuring@ \hbox{\m@th\normalfont#1}% \else \tagmcend \tagstructbegin{tag=Lbl}% \tagmcbegin{tag=Lbl}% \hbox{\m@th\normalfont#1}% \tagmcend \tagstructend \tagmcbegin{}% \fi } % \end{macrocode} % % % \begin{macrocode} \@namedef{math@cr @ @ @ gather}{% \ifst@rred\nonumber\fi &\relax \make@display@tag % \maybestartnewformulatag % \ifst@rred\else\global\@eqnswtrue\fi \global\advance\row@\@ne \cr } % \end{macrocode} % % \begin{macrocode} \@namedef{math@cr @ @ @ align}{% \ifst@rred\nonumber\fi \if@eqnsw \global\tag@true \fi \global\advance\row@\@ne \add@amps\maxfields@ \omit \kern-\alignsep@ \iftag@ \setboxz@h{\@lign\strut@{\make@display@tag}}% \place@tag \fi % \maybestartnewformulatag % \ifst@rred\else\global\@eqnswtrue\fi \global\lineht@\z@ \cr } % \end{macrocode} % % \begin{macrocode} \def\restore@math@cr{\@namedef{math@cr @ @ @}{ % \maybestartnewformulatag % \cr}} \restore@math@cr % \end{macrocode} % % \begin{macrocode} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % % % \begin{macro}{\@@_split_at_nl:NN} % This splits grabbed math at newlines. % % \begin{macrocode} \cs_new:Npn \@@_split_at_nl:NN #1#2 { \tl_set:Nf \l_@@_tmpa_tl { \exp_after:wN \@@_split_at_nl_first:w #1 \\ \q_nil \\ \s_stop } \exp_after:wN \@@_split_at_nl_aux:nnNN \l_@@_tmpa_tl #1 #2 } % \end{macrocode} % and the auxiliary commands % \begin{macrocode} \cs_new:Npn \@@_split_at_nl_first:w #1 \\ #2 \\ #3 \s_stop { \quark_if_nil:nTF {#2} { {#1} { } } { \@@_split_chk_if_begin:ww #1 \begin \q_nil \s_mark #2 \\ #3 \s_stop } } \cs_new_protected:Npn \@@_split_at_nl_aux:nnNN #1 #2 #3 #4 { \tl_gset:Nn #4 {#1} \tl_gset:Nn #3 {#2} } \cs_new:Npn \@@_split_chk_if_begin:ww #1 \begin #2 #3 \s_mark #4 \\ \q_nil \\ \s_stop { \quark_if_nil:nTF {#2} { {#1} {#4} } { \exp_after:wN \@@_split_collect_one_end:w \@@_split_cleanup_begin_q_nil:w #1 \begin{#2} #3 \\ #4 \s_stop { } { 1 } } } \cs_new:Npn \@@_split_cleanup_begin_q_nil:w #1 \begin \q_nil {#1} \cs_new:Npn \@@_split_collect_one_end:w #1 \end #2 #3 \s_stop #4 #5 { \exp_args:Nf \@@_split_check_count_begins:nnnn { \@@_split_count_begins:n { #4 #1 } } {#5} { #4 #1 \end{#2} } {#3} } \cs_new:Npn \@@_split_count_begins:n #1 { \int_eval:n { 0 \@@_split_count_begins:w #1 \begin \q_nil } } \cs_new:Npn \@@_split_count_begins:w #1 \begin #2 { \quark_if_nil:nF {#2} { +1 \@@_split_count_begins:w } } \cs_new:Npn \@@_split_check_count_begins:nnnn #1 #2 #3 #4 { \int_compare:nNnTF {#1} = {#2} { \exp_last_unbraced:Nf \@@_split_final_cleanup:nn { \@@_split:n { \@@_split_guard:n {#3} #4 } } } { \exp_args:No \use_ii_i:nn { \exp_after:wN { \int_value:w \int_eval:n { #2 + 1 } } } { \@@_split_collect_one_end:w #4 \s_stop {#3} } } } \cs_new:Npn \@@_split_final_cleanup:nn #1 #2 { \exp:w \@@_split_final_cleanup:w #1 \@@_split_guard:n \q_nil \s_mark { } {#2} } \cs_new:Npn \@@_split_final_cleanup:w #1 \@@_split_guard:n #2 #3 \s_mark #4 { \quark_if_nil:nTF {#2} { \exp_end: { #4 #1 } } { \@@_split_final_cleanup:w #3 \s_mark { #4 #1 #2 } } } \cs_new:Npn \@@_split:n #1 { \@@_split_at_nl_first:w #1 \\ \q_nil \\ \s_stop } % this looks unused. %\NewDocumentCommand \splitnl { mm +m } % { % \tl_set:Nf \l_@@_tmpa_tl { \split:n {#3} } % \show \l_@@_tmpa_tl % \exp_after:wN \__splitnl_aux:nnNN \l_@@_tmpa_tl #1 #2 % } % \end{macrocode} % \end{macro} % % % \begin{macro}{\maybestartnewformulatag} % % \begin{macrocode} \newif\if@subformulas \tl_new:N \result \cs_new_protected:Npn\grabaformulapartandstart { \@@_split_at_nl:NN \g_@@_grabbed_math_tl \result \typeout{====>first-result=\meaning\result} \typeout{====>first-tmpmathcontent=\meaning\g_@@_grabbed_math_tl} \tl_if_empty:NTF \g_@@_grabbed_math_tl { \typeout{====>formula~ has~ no~ subparts} \global\@subformulasfalse } { \typeout{====>formula~ has~ subparts} \global\@subformulastrue \edef\resulttitle{\g_@@_grabbed_env_tl\space (part)} \tagstructbegin{tag=Formula, % \end{macrocode} % For now we don't put real content in /alt or /ActualText on subformulas % but we add a short text to satisfy the pdf/ua-2 validator % \begin{macrocode} % alt=\result, alt = subformula, title-o=\resulttitle } } \tagmcbegin{} } \cs_new_protected:Npn\grabaformulapartandmayberestart { \@@_split_at_nl:NN \g_@@_grabbed_math_tl \result \typeout{====>result=\meaning\result} \typeout{====>tmpmathcontent=\meaning\g_@@_grabbed_math_tl} % \tl_if_empty:NTF \g_@@_grabbed_math_tl % { % \typeout{====>tmpmathcontent=empty} % } % { % \typeout{====>tmpmathcontent=not-empty} \edef\resulttitle{\g_@@_grabbed_env_tl\space (part)} \tagstructbegin{tag=Formula, alt=\result, title-o=\resulttitle } % } \tagmcbegin{} } % \end{macrocode} % \end{macro} % % % % % \begin{macrocode} \def\maybestartnewformulatag { \if@subformulas \ifmeasuring@\else % \tl_if_empty:NF \g_@@_grabbed_math_tl { \tagmcend \tagstructend \grabaformulapartandmayberestart } \fi \fi } % \end{macrocode} % % The breqn packages changes catcodes and that isn't yet covered % by our mechanism. % \begin{macrocode} %\AddToHook{package/breqn/after}{ % \typeout{===>~ in~ hook} % \math_register_halign_env:nn {dmath}{} % \math_register_halign_env:nn {dgroup*}{} %} % \end{macrocode} % % \begin{macrocode} \ExplSyntaxOff % \end{macrocode} % % \begin{macrocode} %<@@=> % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \Finale % %