# -*- mode: Perl -*-
# /=====================================================================\ #
# |  LaTeX                                                              | #
# | Implementation for LaTeXML                                          | #
# |=====================================================================| #
# | Part of LaTeXML:                                                    | #
# |  Public domain software, produced as part of work done by the       | #
# |  United States Government & not subject to copyright in the US.     | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use LaTeXML::Util::Pathname;
use List::Util qw(min max);

#**********************************************************************
# Organized following
#  "LaTeX: A Document Preparation System"
#   by Leslie Lamport
#   2nd edition
# Addison Wesley, 1994
# Appendix C. Reference Manual
#**********************************************************************
# NOTE: This will be loaded after TeX.pool.ltxml, so it inherits.
#**********************************************************************

LoadPool('TeX');

# Apparently LaTeX does NOT define \magnification,
# and babel uses that to determine whether we're runing LaTeX!!!
Let('\magnification', '\@undefined');
#**********************************************************************
# Basic \documentclass & \documentstyle

#AssignValue('2.09_COMPATIBILITY'=>0);
DefConditionalI('\if@compatibility', undef, sub { LookupValue('2.09_COMPATIBILITY'); });
DefMacro('\@compatibilitytrue',  '');
DefMacro('\@compatibilityfalse', '');

Let('\@currentlabel', '\@empty');
DefMacro('\@currdir', './');

# Let's try just starting with this set (since we've loaded LaTeX)
AssignValue(inPreamble => 1);    # \begin{document} will clear this.

DefConstructor('\documentclass OptionalSemiverbatim SkipSpaces Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\documentclass'); },
  afterDigest  => sub {
    my ($stomach, $whatsit) = @_;
    my $options = $whatsit->getArg(1);
    my $class   = ToString($whatsit->getArg(2));
    $class =~ s/\s+//g;
    LoadClass($class,
      options => [($options ? split(/\s*,\s*/, ToString($options)) : ())],
      after   => Tokens(T_CS('\AtBeginDocument'), T_CS('\warn@unusedclassoptions')));
    return; });

AssignValue('@unusedoptionlist', []);
DefPrimitiveI('\warn@unusedclassoptions', undef, sub {
    if (my @unused = @{ LookupValue('@unusedoptionlist') }) {
      Info('unexpected', 'options', $_[0], "Unused global options: " . join(',', @unused));
      AssignValue('@unusedoptionlist', []); }
    return; });

DefConstructor('\documentstyle OptionalSemiverbatim SkipSpaces Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1') oldstyle='true'?>",
  beforeDigest => sub {
    Info('unexpected', '\documentstyle', $_[0], "Entering LaTeX 2.09 Compatibility mode");
    AssignValue('2.09_COMPATIBILITY' => 1, 'global');
    onlyPreamble('\documentstyle'); },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $class = ToString($whatsit->getArg(2));
    $class =~ s/\s+//g;
    my $options = $whatsit->getArg(1);
    $options = [($options ? split(/\s*,\s*/, ToString($options)) : ())];
# Watch out; In principle, compatibility mode wants a .sty, not a .cls!!!
# But, we'd prefer .cls, since we'll have better bindings.
# And in fact, nobody's likely to write a binding for a .sty that wants to be a class anyway.
# So, we'll just try for a .cls, punting to OmniBus if needed.
# If we start wanting to read style files by default, we'll still need to handle this
# specially, since class (or sty files pretending to be) cover so much more.
#
# EXCEPTION: of course there is an exception. Older arXiv articles use \documentstyle{aipproc}
# to load a different aipproc.sty than the later evolved aipproc.cls. And of course we need to support that.
    if (FindFile($class, type => 'sty', notex => !LookupValue('INCLUDE_CLASSES'))) {
      InputDefinitions('article', type => 'cls', noerror => 1,
        handleoptions => 1, options => $options);
      RequirePackage($class, options => $options, as_class => 1,
        after => Tokens(T_CS('\compat@loadpackages'))); }
    elsif (FindFile($class, type => 'cls', notex => !LookupValue('INCLUDE_CLASSES'))) {
      LoadClass($class, options => $options,
        after => Tokens(T_CS('\compat@loadpackages'))); }
    else {
      InputDefinitions('OmniBus', type => 'cls', noerror => 1,
        handleoptions => 1, options => $options,
        after         => Tokens(T_CS('\compat@loadpackages')));
      RequirePackage($class, options => $options, as_class => 1); }
    return; });

DefPrimitiveI('\compat@loadpackages', undef, sub {
    my $name       = ToString(Expand(T_CS('\@currname')));
    my $type       = ToString(Expand(T_CS('\@currext')));
    my $hadmissing = 0;
    foreach my $option (@{ LookupValue('@unusedoptionlist') }) {
      if (FindFile($option, type => 'sty')) {
        RequirePackage($option); }
      else {
        $hadmissing = 1;
        Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } }
    # Often, in compatibility mode, the options are used to load what are effectively
    # document classes for specific journals, etc that introduce a bunch of new frontmatter!
    # To try to recover from this, we'll go ahead & load the OmniBus class.
    if ($hadmissing && !LookupValue('OmniBus.cls_loaded')) {
      Info('note', 'OmniBus', $_[0], "Loading OmniBus class to attempt to cover missing options");
      LoadClass('OmniBus'); }
    AssignValue('@unusedoptionlist', []); });

sub onlyPreamble {
  my ($cs) = @_;
  Error('unexpected', $cs, $STATE->getStomach,
    "The current command '" . ToString($cs) . "' can only appear in the preamble")
    unless LookupValue("inPreamble");
  return; }
#**********************************************************************
# C.1.  Commands and Environments.
#**********************************************************************

#======================================================================
# C.1.1 Command Names and Arguments
#======================================================================
# Nothing...

#======================================================================
# C.1.2 Environments
#======================================================================

# In LaTeX, \newenvironment{env} defines \env and \endenv.
# \begin{env} & \end{env} open/close a group, and invoke these.
# In fact, the \env & \endenv don't have to have been created by
# \newenvironment; And in fact \endenv doesn't even have to be defined!
# [it is created by \csname, and equiv to \relax if no previous defn]

# We need to respect these usages here, but we also want to be able
# to define environment constructors that `capture' the body so that
# it can be processed specially, if needed.  These are the magic
# '\begin{env}', '\end{env}' control sequences created by DefEnvironment.

AssignValue(current_environment => '', 'global');
DefMacro('\@currenvir', '');
DefPrimitive('\lx@setcurrenvir{}', sub {
    DefMacroI('\@currenvir', undef, $_[1]);
    AssignValue(current_environment => ToString($_[1])); });
DefMacro('\@checkend{}', '\def\reserved@a{#1}\ifx\reserved@a\@currenvir \else\@badend{#1}\fi}');
Let('\@currenvline', '\@empty');

DefMacro('\begin{}', sub {
    my ($gullet, $env) = @_;
    my $name   = $env && ToString(Expand($env));
    my $before = LookupValue('@environment@' . $name . '@beforebegin');
    my $after  = LookupValue('@environment@' . $name . '@atbegin');
    if (IsDefined("\\begin{$name}")) {
      (($before ? @$before : ()),
        T_CS("\\begin{$name}")); }    # Magic cs!
    else {
      my $token = T_CS("\\$name");
      if (!IsDefined($token)) {
        my $undef = '{' . $name . '}';
        $STATE->noteStatus(undefined => $undef);
        Error('undefined', $undef, $gullet, "The environment " . $undef . " is not defined.");
        $STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($token, undef,
            sub { $_[0]->makeError('undefined', $undef); })); }
      (($before ? @$before : ()),
        T_CS('\begingroup'),
        ($after ? @$after : ()),
        Invocation(T_CS('\lx@setcurrenvir'), $env),
        $token,); } });
#  robust => 1); # Not yet working well enough

DefMacro('\end{}', sub {
    my ($gullet, $env) = @_;
    my $name   = $env && ToString(Expand($env));
    my $before = LookupValue('@environment@' . $name . '@atend');
    my $after  = LookupValue('@environment@' . $name . '@afterend');
    my $t;

    if (IsDefined($t = T_CS("\\end{$name}"))) {
      ($t,
        ($after ? @$after : ())); }    # Magic CS!
    else {
      $t = T_CS("\\end$name");
      (($before ? @$before : ()),
        (IsDefined($t) ? $t : ()),
        T_CS('\endgroup'),
        ($after ? @$after : ())); } });
##  robust => 1);    # Isn't really robust, but something similar(?)

#======================================================================
# C.1.3 Fragile Commands
#======================================================================
# Because of the way we `move information', revertable and pre-processed,
# I don't think we actually need to do anything ...
# [Course that means we're not _really_ TeX!]

# \protect is already in TeX for obscure reasons...
#Let('\@typeset@protect','\relax');
RawTeX(<<'EOTeX');
\def\@ignorefalse{\global\let\if@ignore\iffalse}
\def\@ignoretrue {\global\let\if@ignore\iftrue}
\def\zap@space#1 #2{%
  #1%
  \ifx#2\@empty\else\expandafter\zap@space\fi
  #2}
\def\@unexpandable@protect{\noexpand\protect\noexpand}
\def\x@protect#1{%
   \ifx\protect\@typeset@protect\else
      \@x@protect#1%
   \fi
}
\def\@x@protect#1\fi#2#3{%
   \fi\protect#1%
}
\let\@typeset@protect\relax
\def\set@display@protect{\let\protect\string}
\def\set@typeset@protect{\let\protect\@typeset@protect}
\def\protected@edef{%
   \let\@@protect\protect
   \let\protect\@unexpandable@protect
   \afterassignment\restore@protect
   \edef
}
\def\protected@xdef{%
   \let\@@protect\protect
   \let\protect\@unexpandable@protect
   \afterassignment\restore@protect
   \xdef
}
\def\unrestored@protected@xdef{%
   \let\protect\@unexpandable@protect
   \xdef
}
\def\restore@protect{\let\protect\@@protect}
\set@typeset@protect
\def\@nobreakfalse{\global\let\if@nobreak\iffalse}
\def\@nobreaktrue {\global\let\if@nobreak\iftrue}
\@nobreakfalse

\newif\ifv@
\newif\ifh@
\newif\ifdt@p
\newif\if@pboxsw
\newif\if@rjfield
\newif\if@firstamp
\newif\if@negarg
\newif\if@ovt
\newif\if@ovb
\newif\if@ovl
\newif\if@ovr
\newdimen\@ovxx
\newdimen\@ovyy
\newdimen\@ovdx
\newdimen\@ovdy
\newdimen\@ovro
\newdimen\@ovri
\newif\if@noskipsec \@noskipsectrue

EOTeX

#======================================================================
# C.1.4 Declarations
#======================================================================
# actual implementation later.
#======================================================================
# C.1.5 Invisible Commands
#======================================================================
# actual implementation later.

#======================================================================
# C.1.6 The \\ Command
#======================================================================
# In math, \\ is just a formatting hint, unless within an array, cases, .. environment.
DefConstructor('\lx@newline OptionalMatch:* [Glue]', sub {
    my ($document, $star, $skip, %props) = @_;
    if ($STATE->lookupValue('IN_MATH')) {
      $document->insertElement('ltx:XMHint', undef, name => 'newline'); }
    else {
      my $context = $document->getElement;
      if    (!$context || $context->getAttribute('_vertical_mode_')) { }
      elsif (($document->getNodeQName($context) eq 'ltx:p')
        && $context->parentNode->getAttribute('_vertical_mode_')) {
        $document->maybeCloseElement('ltx:p'); }
      elsif ($document->canContain($context, 'ltx:break')) {
        $document->insertElement('ltx:break'); } } },
  reversion  => Tokens(T_CS("\\\\"), T_CR),
  properties => { isBreak => 1 });
Let('\\\\', '\lx@newline');

DefConstructor('\newline',
  "?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
  reversion  => Tokens(T_CS('\newline'), T_CR),
  properties => { isBreak => 1 });
Let('\@normalcr',      "\\\\");
Let('\@normalnewline', '\newline');
##PushValue(TEXT_MODE_BINDINGS => [
##    T_CS("\\\\"), T_CS('\@normalcr'), T_CS('\newline'), T_CS('\@normalnewline')]);

DefMacro('\@nolnerr', '');
DefMacro('\@centercr', '\ifhmode\unskip\else\@nolnerr\fi'
    . '\par\@ifstar{\nobreak\@xcentercr}\@xcentercr');
DefMacro('\@xcentercr',   '\addvspace{-\parskip}\@ifnextchar[\@icentercr\ignorespaces');
DefMacro('\@icentercr[]', '\vskip #1\ignorespaces');

#**********************************************************************
# C.2. The Structure of the Document
#**********************************************************************
#   prepended files (using filecontents environment)
#   preamble (starting with \documentclass)
#   \begin{document}
#    text
#   \end{document}

DefMacro('\AtBeginDocument{}', sub {
    PushValue('@at@begin@document', $_[1]->unlist); });
DefMacro('\AtEndDocument{}', sub {
    PushValue('@at@end@document', $_[1]->unlist); });

# Like  "<ltx:document xml:id='#id'>#body</ltx:document>",
# But more complicated due to id, at begin/end document and so forth.
# AND, lower-level so that we can cope with common errors at document end.
DefConstructorI(T_CS('\begin{document}'), undef, sub {
    my ($document, %props) = @_;
    my $id = ToString($props{id});
    if (my $docel = $document->findnode('/ltx:document')) {    # Already (auto) created?
      $document->setAttribute($docel, 'xml:id' => $id) if $id; }
    else {
      $document->openElement('ltx:document', 'xml:id' => $id); } },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    $stomach->beginMode('text');
    DefMacroI('\@currenvir', undef, 'document');
    AssignValue(current_environment => 'document');
    $_[1]->setProperty(id => Expand(T_CS('\thedocument@ID')));
    my @boxes = ();
    if (my $ops = LookupValue('@document@preamble@atend')) {
      push(@boxes, $stomach->digest(Tokens(@$ops))); }
    if (my $ops = LookupValue('@at@begin@document')) {
      push(@boxes, $stomach->digest(Tokens(@$ops))); }
    AssignValue(inPreamble => 0);    # atbegin is still (sorta) preamble
    if (my $ops = LookupValue('@document@preamble@afterend')) {
      push(@boxes, $stomach->digest(Tokens(@$ops))); }
    $_[1]->setFont(LookupValue('font'));    # Start w/ whatever font was last selected.
    return @boxes; });

# \document is used directly in e.g. expl3.sty
Let(T_CS('\document'), T_CS('\begin{document}'), 'global');

DefConstructorI(T_CS('\end{document}'), undef, sub {
    my ($document) = @_;
    $document->closeElement('ltx:document'); },
  beforeDigest => sub {
    my ($stomach) = @_;
    my @boxes = ();
    if (my $ops = LookupValue('@at@end@document')) {
      push(@boxes, $stomach->digest(Tokens(@$ops))); }
    # Should we try to indent the last paragraph? If so, it goes like this:
    push(@boxes, $stomach->digest(T_CS('\normal@par')));
    # Now we check whether we're down to the last stack frame.
    # It is common for unclosed { or even environments
    # and we want to at least compress & avoid unnecessary errors & warnings.
    my $nframes = $STATE->getFrameDepth;
    my $ifstack;
    if ($STATE->isValueBound('current_environment', 0)
      && ($STATE->valueInFrame('current_environment', 0) eq 'document')
      && (!($ifstack = $STATE->lookupValue('if_stack')) || !$$ifstack[0])) { }    # OK!
    else {
      my @lines = ();
      while ((!$STATE->isValueBound('current_environment', 0)
          || ($STATE->valueInFrame('current_environment', 0) ne 'document'))
        && ($STATE->getFrameDepth > 0)) {
        # my $nonbox = $STATE->valueInFrame('groupNonBoxing',0) || 0;
        my $tok = $STATE->valueInFrame('groupInitiator',        0) || '<unknown>';
        my $loc = $STATE->valueInFrame('groupInitiatorLocator', 0);
        $loc = defined $loc ? ToString($loc) : '<unknown>';
        my $env = $STATE->isValueBound('current_environment', 0)
          && $STATE->valueInFrame('current_environment', 0);
        if ($env) {
          push(@lines, "Environment $env opened by " . ToString($tok) . ' ' . $loc); }
        else {    # but unclosed { is so common and latex itself doesn't Error!
          push(@lines, "Group opened by " . ToString($tok) . ' ' . $loc); }
        $STATE->popFrame; }
      while (($ifstack = $STATE->lookupValue('if_stack')) && $$ifstack[0]) {
        my $frame = $STATE->shiftValue('if_stack');
        push(@lines, "Conditional " . ToString($$frame{token})
            . "started " . ToString($$frame{start})); }
      Warn('unexpected', '\end{document}', $stomach,
        "Attempt to end document with open groups, environments or conditionals", @lines);
    }
    $stomach->endMode('text');
    # ???? don't push? or what
    #    if (my $ops = LookupValue('@after@end@document')) {
    #      push(@boxes, Digest(Tokens(@$ops))); }
    $stomach->getGullet->flush;
    return @boxes; });

# \enddocument is used directly in e.g. standalone.cls
Let(T_CS('\enddocument'), T_CS('\end{document}'), 'global');

#**********************************************************************
# C.3. Sentences and Paragraphs
#**********************************************************************

#======================================================================
# C.3.1 Making Sentences
#======================================================================
# quotes;  should these be handled in DOM/construction?
# dashes:  We'll need some sort of Ligature analog, or something like
# Omega's OTP, to combine sequences of "-" into endash, emdash,
# Perhaps it also applies more semantically?
# Such as interpreting certain sequences as section headings,
# or math constructs.

# Spacing; in TeX.pool.ltxml

# Special Characters; in TeX.pool.ltxml

# Logos
# \TeX is in TeX.pool.ltxml
DefConstructor('\LaTeX',
  "<ltx:text class='ltx_LaTeX_logo' cssstyle='letter-spacing:-0.2em; margin-right:0.1em'>"
    . "L"
    . "<ltx:text cssstyle='font-variant:small-caps;' yoffset='0.4ex'>a</ltx:text>"
    . "T"
    . "<ltx:text cssstyle='font-variant:small-caps;font-size:120%' yoffset='-0.2ex'>e</ltx:text>"
    . "X"
    . "</ltx:text>",
  sizer => sub { (Dimension('2.6em'), Dimension('1.6ex'), Dimension('0.5ex')); });
DefConstructor('\LaTeXe',
  "<ltx:text class='ltx_LaTeX_logo' cssstyle='letter-spacing:-0.2em; margin-right:0.1em'>"
    . "L"
    . "<ltx:text cssstyle='font-variant:small-caps;' yoffset='0.4ex'>a</ltx:text>"
    . "T"
    . "<ltx:text cssstyle='font-variant:small-caps;font-size:120%' yoffset='-0.2ex'>e</ltx:text>"
    . "X"
    . "\x{2002}2<ltx:text cssstyle='font-style:italic' yoffset='-0.3ex'>\x{03B5}</ltx:text>"
    . "</ltx:text>",
  sizer => sub { (Dimension('3.7em'), Dimension('1.6ex'), Dimension('0.5ex')); });

DefMacroI('\fmtname',    undef, 'LaTeX2e');
DefMacroI('\fmtversion', undef, '2018/12/01');

DefMacroI('\today', undef, sub { ExplodeText(today()); });

# Use fonts (w/ special flag) to propogate emphasis as a font change,
# but preserve it's "emph"-ness.
# Use ltx:emph in text, but use ltx:text within math, since it may collapse within XMText
# (and minimize html within math, which causes MathJax problems)
DefConstructor('\emph{}', sub {
    my ($document, $body, %props) = @_;
    my $tag = ($document->findnodes('ancestor::ltx:Math', $document->getNode) ? 'ltx:text' : 'ltx:emph');
    $document->openElement($tag, _force_font => 1, %props);
    $document->absorb($body);
    $document->maybeCloseElement($tag); },
  mode         => 'text', bounded => 1, font => { emph => 1 }, alias => '\emph',
  beforeDigest => sub {
    DefMacroI('\f@shape', undef,
      (ToString(Tokens(Expand(T_CS('\f@shape')))) eq 'it' ? 'n' : 'it')); });
Tag('ltx:emph', autoClose => 1);

#======================================================================
# C.3.2 Making Paragraphs
#======================================================================
# \noindent, \indent, \par in TeX.pool.ltxml

Let('\@@par', '\par');
DefMacro('\@par',        '\let\par\@@par\par');
DefMacro('\@restorepar', '\def\par{\@par}');
# Style parameters
# \parindent, \baselineskip, \parskip alreadin in TeX.pool.ltxml

DefPrimitive('\linespread{}', undef);

# ?
DefMacro('\@noligs', '');
DefConditional('\if@endpe');
DefMacro('\@doendpe', '');
DefMacro('\@bsphack', '\relax');    # what else?
DefMacro('\@esphack', '\relax');
DefMacro('\@Esphack', '\relax');
#======================================================================
# C.3.3 Footnotes
#======================================================================

NewCounter('footnote');
DefMacroI('\thefootnote', undef, '\arabic{footnote}');
NewCounter('mpfootnote');
DefMacroI('\thempfn',             undef, '\thefootnote');
DefMacroI('\thempfootnote',       undef, '\arabic{mpfootnote}');
DefMacroI('\footnotetyperefname', undef, 'footnote');

sub makeNoteTags {
  my ($counter, $mark, $tag) = @_;
  if ($tag) {
    return (
      RefStepID($counter),
      mark => $mark || $tag,
      tags => Digest(T_BEGIN,
        T_CS('\def'), T_CS('\the' . $counter), T_BEGIN, Revert($tag), T_END,
        T_CS('\def'), T_CS('\typerefnum@' . $counter),
        T_BEGIN,      T_CS('\\' . $counter . 'typerefname'), T_SPACE, Revert($tag), T_END,
        Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
        T_END)); }
  else {
    return (RefStepCounter($counter),
      mark => $mark || DigestText(T_CS('\the' . $counter))); } }

DefMacroI('\ext@footnote', undef, undef);
DefConstructor('\lx@note[]{}[]{}',
  "^<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
    . "#tags"
    . "#4"
    . "</ltx:note>",
  mode         => 'text', bounded => 1,
  sizer        => '#mark',
  beforeDigest => sub { reenterTextMode(1); neutralizeFont(); },
  properties   => sub {
    my $type = ToString($_[2]);
    (role => $type,
      list => DigestText(T_CS('\ext@' . $type)),
      makeNoteTags($type, $_[1], $_[3])); },
  reversion => '');
DefConstructor('\lx@notemark[]{}[]',
  "^<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
    . "#tags"
    . "</ltx:note>",
  mode       => 'text',
  properties => sub {
    my $type = ToString($_[2]);
    (role => $type . 'mark',
      list => DigestText(T_CS('\ext@' . $type)),
      makeNoteTags($type, $_[1], $_[3])); },
  reversion => '');
DefConstructor('\lx@notetext[]{}[]{}',
  "^<ltx:note role='#role' mark='#mark' xml:id='#id'>#4</ltx:note>",
  mode       => 'text',
  properties => sub {
    my $type = ToString($_[2]);
    (role => $type . 'text', makeNoteTags($type, $_[1], $_[3] || Digest(T_CS('\the' . $type)))); },
  reversion => '');

DefMacro('\footnote',      '\lx@note{footnote}',     locked => 1);
DefMacro('\footnotemark',  '\lx@notemark{footnote}', locked => 1);
DefMacro('\footnotetext',  '\lx@notetext{footnote}', locked => 1);
DefMacro('\@footnotetext', '\lx@notetext{footnote}', locked => 1);
# we don't implement the internals directly, so lock them to the latexml variant
Let('\@thefnmark', '\lx@notemark{footnote}', locked => 1);

Tag('ltx:note', afterClose => \&relocateFootnote);

# Find any pairs of footnotemark & footnotetext;
# Move the contents of the text to the mark, removing the text node.
sub relocateFootnote {
  my ($document, $node) = @_;
  if (($node->getAttribute('role') || '') =~ /^(\w+?)text$/) {
    my $notetype = $1;    # Eg "footnote", "endnote",...
    if (my $mark = $node->getAttribute('mark')) {
      foreach my $marknote ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) {
        relocateFootnote_aux($document, $notetype, $marknote, $node); } } }
  elsif (($node->getAttribute('role') || '') =~ /^(\w+?)mark$/) {
    my $notetype = $1;    # Eg "footnote", "endnote",...
    if (my $mark = $node->getAttribute('mark')) {
      foreach my $textnote ($document->findnodes(".//ltx:note[\@role='${notetype}text'][\@mark='$mark']")) {
        relocateFootnote_aux($document, $notetype, $node, $textnote); } } }
  return; }

# Move the contents of the $textnote to the $marknote, remove $textnote.
sub relocateFootnote_aux {
  my ($document, $notetype, $marknote, $textnote) = @_;
  $textnote->parentNode->removeChild($textnote);
  $document->appendClone($marknote, $textnote->childNodes);
  $document->setAttribute($marknote, role => $notetype);
  if (my $labels = $textnote->getAttribute('labels')) {
    GenerateID($document, $marknote);
    $document->setAttribute($marknote, labels => $labels); }
  return; }

# Style parameters
DefRegister('\footnotesep' => Dimension(0));
DefPrimitiveI('\footnoterule', undef, undef);

#======================================================================
# C.3.4 Accents and Special Symbols
#======================================================================
# See TeX.pool.ltxml

# See Section 3.3.2 Mathematical Symbols, below

# Should this be here?
DefMath('\mathring{}', "\x{030A}", operator_role => 'OVERACCENT');

#**********************************************************************
# C.4 Sectioning and Table of Contents
#**********************************************************************

#======================================================================
# C.4.1 Sectioning Commands.
#======================================================================
# Note that LaTeX allows fairly arbitrary stuff in \the<ctr>, although
# it can get you in trouble.  However, in almost all cases, the result
# is plain text.  So, I'm putting refnum as an attribute, where I like it!
# You want something else? Redefine!

# Also, we're adding an id to each, that is parallel to the refnum, but
# valid as an ID.  You can tune the representation by defining, eg. \thesection@ID

# A little more messy than seems necessary:
#  We don't know whether to step the counter and update \@currentlabel until we see the '*',
# but we have to know it before we digest the title, since \label can be there!

# These are defined in terms of \@startsection so that
# casual user redefinitions work, too.
DefMacroI('\chapter', undef, '\@startsection{chapter}{0}{}{}{}{}', locked => 1);
DefMacroI('\part', undef, '\@startsection{part}{-1}{}{}{}{}'); # not locked since sometimes redefined as partition?
DefMacroI('\section',       undef, '\@startsection{section}{1}{}{}{}{}',       locked => 1);
DefMacroI('\subsection',    undef, '\@startsection{subsection}{2}{}{}{}{}',    locked => 1);
DefMacroI('\subsubsection', undef, '\@startsection{subsubsection}{3}{}{}{}{}', locked => 1);
DefMacroI('\paragraph',     undef, '\@startsection{paragraph}{4}{}{}{}{}',     locked => 1);
DefMacroI('\subparagraph',  undef, '\@startsection{subparagraph}{5}{}{}{}{}',  locked => 1);
map { Tag("ltx:$_", autoClose => 1) }
  qw(part chapter section subsection subsubsection paragraph subparagraph);

DefMacro('\secdef {}{} OptionalMatch:*', sub { ($_[3] ? ($_[2]) : ($_[1])); });

DefMacroI('\@startsection@hook', undef, Tokens());
NewCounter('secnumdepth');
SetCounter('secnumdepth', Number(3));
DefMacro('\@startsection{}{}{}{}{}{} OptionalMatch:*', sub {
    my ($gullet, $type, $level, $ignore3, $ignore4, $ignore5, $ignore6, $flag) = @_;
    ### Aside: Guard mode
    # Never start sections in math mode -- this is a good recovery point for broken documents
    if ($STATE->lookupValue('IN_MATH')) {
      my $mode = $STATE->lookupValue('MODE');
      if ($mode =~ /math/) {    # double-check we're really in math
        $STATE->getStomach->endMode($mode); }
      else {                    # otherwise, just unset the flag?
        $STATE->assignValue('IN_MATH' => 0, 'global'); } }
    ### Main logic
    $level = ToString($level);
    if ($flag) {                # No number, not in TOC
      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
        T_BEGIN, $type->unlist, T_END,
        T_BEGIN, T_END); }
    elsif ((($level ne '') && ($level > CounterValue('secnumdepth')->valueOf))
      || LookupValue('no_number_sections')) {
      # No number, but in TOC
      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
        T_BEGIN, $type->unlist,  T_END,
        T_BEGIN, T_OTHER('toc'), T_END); }
    else {                      # Number and in TOC
      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'),
        T_BEGIN, $type->unlist,  T_END,
        T_BEGIN, T_OTHER('toc'), T_END); } },
  locked => 1);

# Not sure if this is best, but if no explicit \section'ing...
#### Tag('ltx:section',autoOpen=>1);

DefConstructor('\@@numbered@section{} Undigested OptionalUndigested Undigested', sub {
    my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
    my $id = $props{id};
    if (my $asif = $props{backmatterelement}) {
      $document->setNode($document->find_insertion_point($asif)); }
    # sanitize the type, we can't open arbitrary elements that are not in the schema
    $type = ToString($type);
    my $tagname = "ltx:" . $type;
    if (!$document->getModel->isKnownTag($tagname)) {
      # one special case from arXiv: app is short for appendix
      if ($tagname eq 'ltx:app') {
        $tagname = "ltx:appendix"; }
      else {
        $tagname = "ltx:section";
        Warn("malformed", "ltx:$type", "Tried to open an unknown tag ltx:$type for numbered section"); } }
    $document->openElement($tagname,
      'xml:id' => CleanID($id),
      inlist   => ToString($inlist),
      (LookupValue('INDENT_FIRST') ? (class => 'ltx_indent_first') : ()));
    if (my $tags = $props{tags}) {
      $document->absorb($tags); }
    $document->insertElement('ltx:title',    $props{title});
    $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
  properties => sub {
    my ($stomach, $type, $inlist, $toctitle, $title) = @_;
    MaybePeekLabel();
    $type = ToString($type);
    my %props     = RefStepCounter($type);
    my $xtitle    = Digest(Invocation(T_CS('\lx@format@title@@'),    $type, $title));
    my $xtoctitle = Digest(Invocation(T_CS('\lx@format@toctitle@@'), $type, $toctitle || $title));
    if ($type eq 'appendix') {
      $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
    $props{title}    = $xtitle;
    $props{toctitle} = $xtoctitle
      if !IsEmpty($xtoctitle) && (ToString($xtoctitle) ne ToString($xtitle));
    return %props; });

# No tags, at all? Consider...
DefConstructor('\@@unnumbered@section{} Undigested OptionalUndigested Undigested', sub {
    my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
    my $id = $props{id};
    if (my $asif = $props{backmatterelement}) {
      $document->setNode($document->find_insertion_point($asif)); }
    # sanitize the type, we can't open arbitrary elements that are not in the schema
    $type = ToString($type);
    my $tagname = "ltx:" . $type;
    if (!$document->getModel->isKnownTag($tagname)) {
      # one special case from arXiv: app is short for appendix
      if ($tagname eq 'ltx:app') {
        $tagname = "ltx:appendix"; }
      else {
        $tagname = "ltx:section";
        Warn("malformed", "ltx:$type", "Tried to open an unknown tag ltx:$type for unnumbered section"); } }
    $document->openElement($tagname,
      'xml:id' => CleanID($id),
      inlist   => ToString($inlist),
      (LookupValue('INDENT_FIRST') ? (class => 'ltx_indentfirst') : ()));
    $document->insertElement('ltx:title',    $props{title});
    $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
  properties => sub {
    my ($stomach, $type, $inlist, $toctitle, $title) = @_;
    MaybePeekLabel();
    $type = ToString($type);
    my %props = RefStepID($type);
    if ($type eq 'appendix') {
      $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
    $props{title} = Digest(T_CS('\@hidden@bgroup'),
      Invocation(T_CS('\lx@format@title@font@@'), $type), $title, T_CS('\@hidden@egroup'));
    $props{toctitle} = $toctitle
      && Digest(T_CS('\@hidden@bgroup'), Invocation(T_CS('\lx@format@toctitle@font@@'), $type),
      $toctitle, T_CS('\@hidden@egroup'));
    return %props; });

#======================================================================
# C.4.2 The Appendix
#======================================================================
# Handled in article,report or book.
DefMacroI('\appendixname',   undef, 'Appendix');
DefMacroI('\appendixesname', undef, 'Appendixes');

# Class files should define \@appendix to call this as startAppendices('section') or chapter...
# counter is also the element name!

sub startAppendices { beginAppendices(@_); return; }

sub beginAppendices {
  my ($counter) = @_;
  Let('\lx@save@theappendex',    '\the' . $counter,         'global');
  Let('\lx@save@theappendex@ID', '\the' . $counter . '@ID', 'global');
  Let('\lx@save@appendix',       T_CS('\\' . $counter),     'global');
  Let('\lx@save@@appendix',      T_CS('\@appendix'),        'global');
  AssignMapping('BACKMATTER_ELEMENT', 'ltx:appendix' => 'ltx:' . $counter);
  if (LookupDefinition(T_CS('\c@chapter'))    # Has \chapter defined
    && ($counter ne 'chapter')) {             # And appendices are below the chapter level.
    NewCounter($counter, 'chapter', idprefix => 'A');
    DefMacroI('\the' . $counter, undef, '\thechapter.\Alph{' . $counter . '}', scope => 'global'); }
  else {
    NewCounter($counter, 'document', idprefix => 'A');
    DefMacroI('\the' . $counter, undef, '\Alph{' . $counter . '}', scope => 'global'); }
  AssignMapping('counter_for_type', appendix => $counter);
  Let(T_CS('\\' . $counter), T_CS('\@@appendix'), 'global');
  Let(T_CS('\@appendix'),    T_CS('\relax'),      'global');
  return; }

sub endAppendices {
  if (my $counter = LookupMapping('BACKMATTER_ELEMENT', 'ltx:appendix')) {
    $counter =~ s/^ltx://;
    Let('\the' . $counter,         '\lx@save@theappendex',    'global');
    Let('\the' . $counter . '@ID', '\lx@save@theappendex@ID', 'global');
    Let(T_CS('\\' . $counter),     '\lx@save@appendix',       'global');
    Let(T_CS('\@appendix'),        '\lx@save@@appendix',      'global'); }
  return; }

DefMacroI('\\@@appendix', undef, '\@startsection{appendix}{0}{}{}{}{}');

#======================================================================
# C.4.3 Table of Contents
#======================================================================
# Insert stubs that will be filled in during post processing.
DefMacroI('\contentsname', undef, 'Contents');
DefConstructorI('\tableofcontents', undef,
  "<ltx:TOC lists='toc' scope='global' select='#select'><ltx:title>#name</ltx:title></ltx:TOC>",
  properties => sub {
    my $td = CounterValue('tocdepth')->valueOf + 1;
    my @s  = (qw(ltx:part ltx:chapter ltx:section ltx:subsection ltx:subsubsection
        ltx:paragraph ltx:subparagraph));
    $td = $#s if $#s < $td;
    @s  = map { $s[$_] } 0 .. $td;
    push(@s, (qw(ltx:appendix ltx:index ltx:bibliography))) if @s;
    (select => join(' | ', @s),
      name => Digest(T_CS('\contentsname'))); });

DefMacroI('\listfigurename', undef, 'List of Figures');
DefConstructorI('\listoffigures', undef,
  "<ltx:TOC lists='lof' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
  properties => sub { (name => Digest(T_CS('\listfigurename'))); });

DefMacroI('\listtablename', undef, 'List of Tables');
DefConstructorI('\listoftables', undef,
  "<ltx:TOC lists='lot' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
  properties => sub { (name => Digest(T_CS('\listtablename'))); });

DefPrimitive('\numberline{}{}',    undef);
DefPrimitive('\addtocontents{}{}', undef);

DefConstructor('\addcontentsline{}{}{}', sub {
    my ($document, $inlist, $type, $title) = @_;
    # Note that the node can be inlist $inlist.
    # Could conceivably want to add $title as toctitle???
    if (my $savenode = $document->floatToLabel) {
      my $node  = $document->getNode;
      my $lists = $node->getAttribute('inlist');
      $inlist = ToString($inlist);
      $document->setAttribute($node, inlist => ($lists ? $lists . ' ' . $inlist : $inlist));
      $document->setNode($savenode); } });

#======================================================================
# C.4.4 Style registers
#======================================================================
NewCounter('tocdepth');

#**********************************************************************
# C.5 Classes, Packages and Page Styles
#**********************************************************************

#======================================================================
# C.5.1 Document Class
#======================================================================
# Style Parameters
DefRegister('\bibindent'     => Dimension(0));
DefRegister('\columnsep'     => Dimension(0));
DefRegister('\columnseprule' => Dimension(0));
DefRegister('\mathindent'    => Dimension(0));

#======================================================================
# C.5.2 Packages
#======================================================================
# We'll prefer to load package.pm, but will try package.sty or
# package.tex (the latter being unlikely to work, but....)
# See Stomach.pm for details
# Ignorable packages ??
# pre-defined packages??

DefMacroI('\@clsextension', undef, 'cls');
DefMacroI('\@pkgextension', undef, 'sty');
Let('\@currext',              '\@empty');
Let('\@currname',             '\@empty');
Let('\@classoptionslist',     '\relax');
Let('\@raw@classoptionslist', '\relax');

# Note that there are variables used in Package.pm for these,
# but they are NOT tied to these macros. Do they need to be?
DefMacroI('\@declaredoptions',  undef, Tokens());
DefMacroI('\@curroptions',      undef, undef);
DefMacroI('\@unusedoptionlist', undef, Tokens());

DefConstructor('\usepackage OptionalSemiverbatim Semiverbatim []',
  "<?latexml package='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\usepackage'); },
  afterDigest  => sub { my ($stomach, $whatsit) = @_;
    my $options  = $whatsit->getArg(1);
    my $packages = $whatsit->getArg(2);
    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
    for my $pkg (split(',', ToString($packages))) {
      $pkg =~ s/\s+//g;
      next if !$pkg || $pkg =~ /^%/;
      RequirePackage($pkg, options => $options); }
    return });

DefConstructor('\RequirePackage OptionalSemiverbatim Semiverbatim []',
  "<?latexml package='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
  afterDigest  => sub { my ($stomach, $whatsit) = @_;
    my $options  = $whatsit->getArg(1);
    my $packages = $whatsit->getArg(2);
    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
    for my $pkg (split(',', ToString($packages))) {
      $pkg =~ s/\s+//g;
      next if !$pkg || $pkg =~ /^%/;
      RequirePackage($pkg, options => $options); }
    return });

DefConstructor('\LoadClass OptionalSemiverbatim Semiverbatim []',
  "<?latexml class='#2' ?#1(options='#1')?>",
  beforeDigest => sub { onlyPreamble('\LoadClass'); },
  afterDigest  => sub { my ($stomach, $whatsit) = @_;
    my $options = $whatsit->getArg(1);
    my $class   = ToString($whatsit->getArg(2));
    $class =~ s/\s+//g;
    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
    LoadClass($class, options => $options);
    return; });

# Related internal macros for package definition
# Internals used in Packages
DefMacro('\NeedsTeXFormat{}[]', Tokens());

DefPrimitive('\ProvidesClass{}[]', sub {
    my ($stomach, $class, $version) = @_;
    DefMacroI("\\ver@" . ToString($class) . ".cls", undef, $version || Tokens(), scope => 'global');
    return; });

# Note that these, like LaTeX, define macros like \var@mypkg.sty to give the version info.
DefMacro('\ProvidesPackage{}[]', sub {
    my ($stomach, $package, $version) = @_;
    DefMacroI("\\ver@" . ToString($package) . ".sty", undef, $version || Tokens(), scope => 'global');
    return; });

DefMacro('\ProvidesFile{}[]', sub {
    my ($stomach, $file, $version) = @_;
    DefMacroI("\\ver@" . ToString($file), undef, $version || Tokens(), scope => 'global');
    return; });

#\DeclareRelease{v4.46}{2020-03-19}{glossaries-2020-03-19.sty}
DefMacro('\DeclareRelease{}{}{}', sub {
    my ($stomach, $version, $date, $name) = @_;
    # anything useful?
    return (); });
#\DeclareCurrentRelease{v4.49}{2021-11-01}
DefMacro('\DeclareCurrentRelease{}{}', sub {
    my ($stomach, $version, $date) = @_;
    # anything useful?
    return (); });
DefMacro('\IncludeInRelease{}{}{} Until:\EndIncludeInRelease', sub {
    # anything useful?
    return (); });
DefMacro('\NewModuleRelease{}{}{} Until:\EndModuleRelease', sub {
    # anything useful?
    return (); });

DefPrimitive('\DeclareOption{}{}', sub {
    my ($stomach, $option, $code) = @_;
    ((ToString($option) eq '*') ?
        DeclareOption(undef,             $code) :
        DeclareOption(ToString($option), $code)); });

DefPrimitive('\PassOptionsToPackage{}{}', sub {
    my ($stomach, $options, $name) = @_;
    $name = ToString($name);
    $name =~ s/\s+//g;
    PassOptions($name, 'sty', split(/\s*,\s*/, ToString(Expand($options)))); });

DefPrimitive('\PassOptionsToClass{}{}', sub {
    my ($stomach, $options, $name) = @_;
    $name = ToString($name);
    $name =~ s/\s+//g;
    PassOptions($name, 'cls', split(/\s*,\s*/, ToString(Expand($options)))); });

DefConstructor('\RequirePackageWithOptions Semiverbatim []',
  "<?latexml package='#1'?>",
  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
  afterDigest  => sub { my ($stomach, $whatsit) = @_;
    my $package = ToString($whatsit->getArg(1));
    $package =~ s/\s+//g;
    RequirePackage($package, withoptions => 1);
    return; });

DefConstructor('\LoadClassWithOptions Semiverbatim []',
  "<?latexml class='#1'?>",
  beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); },
  afterDigest  => sub { my ($stomach, $whatsit) = @_;
    my $class = ToString($whatsit->getArg(1));
    $class =~ s/\s+//g;
    LoadClass($class, withoptions => 1);
    return; });

DefPrimitive('\@onefilewithoptions {} [][] {}', sub {
    my ($stomach, $name, $option1, $option2, $ext) = @_;
    InputDefinitions(ToString(Expand($name)), type => ToString(Expand($ext)), options => $option1);
    return; });

DefMacroI('\CurrentOption', undef, Tokens());

DefPrimitiveI('\OptionNotUsed', undef, sub {
    if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
      my $type = ToString(Expand(T_CS('\@currext')));
      if ($type eq 'cls') {
        PushValue('@unusedoptionlist', $option); } }
    return; });

DefPrimitiveI('\@unknownoptionerror', undef, sub {
    if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
      my $name = ToString(Expand(T_CS('\@currname')));
      my $type = ToString(Expand(T_CS('\@currext')));
      Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); }
    return; });

DefPrimitive('\ExecuteOptions{}', sub {
    my ($gullet, $options) = @_;
    ExecuteOptions(split(/\s*,\s*/, ToString(Expand($options)))); });

DefPrimitive('\ProcessOptions OptionalMatch:*', sub {
    my ($stomach, $star) = @_;
    ProcessOptions(($star ? (inorder => 1) : ())); });
DefMacro('\@options', '\ProcessOptions*');

Let('\@enddocumenthook', '\@empty');
DefMacro('\AtEndOfPackage{}', sub {
    my ($gullet, $code) = @_;
    my $name = ToString(Expand(T_CS('\@currname')));
    my $type = ToString(Expand(T_CS('\@currext')));
    AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); });

DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension');
Let('\ltx@ifpackageloaded', '\@ifpackageloaded');
DefMacro('\@ifclassloaded', '\@ifl@aded\@clsextension');
Let('\ltx@ifclassloaded', '\@ifclassloaded');
DefMacro('\@ifl@aded{}{}', sub {
    my ($gullet, $ext, $name) = @_;
    my $path = ToString(Expand($name)) . '.' . ToString(Expand($ext));
    # If EITHER the raw TeX or ltxml version of this file was loaded.
    if (LookupValue($path . '_loaded') || LookupValue($path . '.ltxml_loaded')) {
      T_CS('\@firstoftwo'); }
    else {
      T_CS('\@secondoftwo'); } });

DefMacro('\@ifpackagewith', '\@if@ptions\@pkgextension');
DefMacro('\@ifclasswith',   '\@if@ptions\@clsextension');
DefMacro('\@if@ptions{}{}{}', sub {
    my ($gullet, $ext, $name, $option) = @_;
    $option = ToString(Expand($option));
    my $values = LookupValue('opt@' . ToString(Expand($name)) . '.' . ToString(Expand($ext)));
    if (grep { $option eq $_ } @$values) {
      T_CS('\@firstoftwo'); }
    else {
      T_CS('\@secondoftwo'); } });

DefMacro('\@ptionlist {}', '\@ifundefined{opt@#1}\@empty{\csname opt@#1\endcsname}');

DefMacro('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); });
DefMacro('\addto@hook DefToken {}',    '#1\expandafter{\the#1#2}');

# Alas, we're not tracking versions, so we'll assume it's "later" & cross fingers....
DefMacro('\@ifpackagelater{}{}{}{}', '#3');
DefMacro('\@ifclasslater{}{}{}{}',   '#3');
Let('\AtEndOfClass', '\AtEndOfPackage');

DefMacro('\AtBeginDvi {}', Tokens());

RawTeX(<<'EoTeX');
\def\@ifl@t@r#1#2{%
  \ifnum\expandafter\@parse@version@#1//00\@nil<%
        \expandafter\@parse@version@#2//00\@nil
    \expandafter\@secondoftwo
  \else
    \expandafter\@firstoftwo
  \fi}
\def\@parse@version@#1{\@parse@version0#1}
\def\@parse@version#1/#2/#3#4#5\@nil{%
\@parse@version@dash#1-#2-#3#4\@nil
}
\def\@parse@version@dash#1-#2-#3#4#5\@nil{%
  \if\relax#2\relax\else#1\fi#2#3#4 }
EoTeX

#======================================================================
# Somewhat related I/O stuff
DefMacro('\filename@parse{}', sub {
    my ($gullet, $pathname) = @_;
    my ($dir, $name, $ext) = pathname_split(ToString(Expand($pathname)));
    $dir .= '/' if $dir;
    DefMacroI('\filename@area', undef, Tokens(ExplodeText($dir)));
    DefMacroI('\filename@base', undef, Tokens(ExplodeText($name)));
    DefMacroI('\filename@ext',  undef, ($ext ? Tokens(ExplodeText($ext)) : T_CS('\relax'))); });

DefMacroI('\@filelist', undef, Tokens());
DefMacro('\@addtofilelist{}', sub {
    DefMacroI('\@filelist', undef,
      Expand(T_CS('\@filelist'), T_OTHER(','), $_[1]->unlist)); });

#======================================================================
# C.5.3 Page Styles
#======================================================================
# Ignored
NewCounter('page');
DefPrimitive('\pagestyle{}',     undef);
DefPrimitive('\thispagestyle{}', undef);
DefPrimitive('\markright{}',     undef);
DefPrimitive('\markboth{}{}',    undef);
DefPrimitiveI('\leftmark',  undef, undef);
DefPrimitiveI('\rightmark', undef, undef);
DefPrimitive('\pagenumbering{}', undef);
DefMacro('\@mkboth', '\@gobbletwo');    # default, just in case
DefMacro('\ps@empty',
  '\let\@mkboth\@gobbletwo\let\@oddhead\@empty\let\@oddfoot\@empty' .
    '\let\@evenhead\@empty\let\@evenfoot\@empty');
DefMacro('\ps@plain', '\let\@mkboth\@gobbletwo' .
    '\let\@oddhead\@empty\def\@oddfoot{\reset@font\hfil\thepage' .
    '\hfil}\let\@evenhead\@empty\let\@evenfoot\@oddfoot');
Let(T_CS('\@leftmark'),  T_CS('\@firstoftwo'));
Let(T_CS('\@rightmark'), T_CS('\@secondoftwo'));
# In normal latex, these should \clearpage; at least we want new paragraph?
# Optional arg is sortof a heading, but w/o any particular styling(?)
DefMacro('\twocolumn[]', '\ifx.#1.\else\par\noindent#1\fi\par');
DefMacro('\onecolumn',   '\par');
DefMacro('\@onecolumna', '', locked => 1);    # this is trouble in arXiv:cond-mat/0002147
DefMacro('\@twocolumna', '', locked => 1);    # this is trouble in arXiv:cond-mat/0002147
# some bits mentioned as "from ltoutput.dtx" in latex.ltx
DefMacro('\@topnewpage{}', '#1');
DefMacro('\@next{}{}{}{}', '\ifx#2\@empty #4\else\expandafter\@xnext #2\@@#1#2#3\fi');
RawTeX('\def\@xnext \@elt #1#2\@@#3#4{\def#3{#1}\gdef#4{#2}}');
Let('\@elt', '\relax');                       # ?
DefMacro('\@freelist',     '');               # ?
DefMacro('\@currbox',      '');
DefMacro('\@toplist',      '');
DefMacro('\@botlist',      '');
DefMacro('\@midlist',      '');
DefMacro('\@currlist',     '');
DefMacro('\@deferlist',    '');
DefMacro('\@dbltoplist',   '');
DefMacro('\@dbldeferlist', '');
DefMacro('\@startcolumn',  '');
# Style parameters from Fig. C.3, p.182
DefRegister('\paperheight'     => Dimension('11in'));
DefRegister('\paperwidth'      => Dimension('8.5in'));
DefRegister('\textheight'      => Dimension('7in'));
DefRegister('\textwidth'       => Dimension('6in'));
DefRegister('\topmargin'       => Dimension(0));
DefRegister('\headheight'      => Dimension(0));
DefRegister('\headsep'         => Dimension(0));
DefRegister('\footskip'        => Dimension(0));
DefRegister('\footheight'      => Dimension(0));
DefRegister('\evensidemargin'  => Dimension(0));
DefRegister('\oddsidemargin'   => Dimension(0));
DefRegister('\marginparwidth'  => Dimension(0));
DefRegister('\marginparsep'    => Dimension(0));
DefRegister('\columnwidth'     => Dimension('6in'));
DefRegister('\linewidth'       => Dimension('6in'));
DefRegister('\baselinestretch' => Dimension(0));

#======================================================================
# C.5.4 The Title Page and Abstract
#======================================================================

# See frontmatter support in TeX.ltxml
Let('\@title', '\@empty');
DefMacro('\title[]{}', '\if.#1.\else\def\shorttitle{#1}\@add@frontmatter{ltx:toctitle}{#1}\fi'
    . '\def\@title{#2}\@add@frontmatter{ltx:title}{#2}', locked => 1);

DefMacro('\@date', '\@empty');
DefMacro('\date{}',
  '\def\@date{#1}'
    . '\@add@frontmatter{ltx:date}[role=creation,'
    . 'name={\@ifundefined{datename}{}{\datename}}]{#1}');

DefConstructor('\person@thanks{}', "^ <ltx:contact role='thanks'>#1</ltx:contact>",
  alias => '\thanks', mode => 'text');
DefConstructor('\@personname{}', "<ltx:personname>#1</ltx:personname>",
  beforeDigest => sub { Let('\thanks', '\person@thanks'); },
  bounded      => 1, mode => 'text');

# Sanitize person names for (obvious) punctuation abuse at start+end
Tag('ltx:personname', afterClose => sub {
    my ($document, $node) = @_;
    if (my $first = $node->firstChild) {
      if ($first->nodeType == XML_TEXT_NODE) {
        my $first_text = $first->data;
        my $new_text   = $first_text;
        $new_text =~ s/^[^\w)(}{\]\[]]+//;
        if ($first_text ne $new_text) {
          $first->setData($new_text); } } }
    if (my $last = $node->lastChild) {
      if ($last->nodeType == XML_TEXT_NODE) {
        my $last_text = $last->data;
        my $new_text  = $last_text;
        $new_text =~ s/[^\w)(}{\]\[]+$//;
        if ($last_text ne $new_text) {
          $last->setData($new_text); } } }
    return; });

DefConstructorI('\and', undef, " and ");

AssignValue(NUMBER_OF_AUTHORS => 0);
DefPrimitive('\lx@count@author', sub {
    AssignValue(NUMBER_OF_AUTHORS => LookupValue('NUMBER_OF_AUTHORS') + 1, 'global') });
DefMacro('\lx@author{}',
  '\lx@count@author'
    . '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}');

DefConstructor('\lx@@@contact{}{}', "^ <ltx:contact role='#1'>#2</ltx:contact>");
DefMacro('\lx@contact{}{}',
  '\@add@to@frontmatter{ltx:creator}{\lx@@@contact{#1}{#2}}');

DefMacro('\lx@author@sep',  '\qquad');
DefMacro('\lx@author@conj', '\qquad');
DefConstructor('\lx@author@prefix', sub {
    my ($document) = @_;
    my $node       = $document->getElement;
    my $nauthors   = LookupValue('NUMBER_OF_AUTHORS');
    my $i          = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') });
    if    ($i <= 1) { }
    elsif ($i == $nauthors) {
      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); }
    else {
      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); }
});

DefMacro('\@author',                 '\@empty');
DefMacro('\author[]{}',              '\def\@author{#2}\lx@make@authors@anded{#2}', locked => 1);
DefMacro('\lx@make@authors@anded{}', sub { andSplit(T_CS('\lx@author'), $_[1]); });
DefPrimitive('\ltx@authors@oneline', sub {
    AssignMapping('DOCUMENT_CLASSES', ltx_authors_1line => 1);
    return; });
DefPrimitive('\ltx@authors@multiline', sub {
    AssignMapping('DOCUMENT_CLASSES', ltx_authors_multiline => 1);
    return; });

DefMacro('\@add@conversion@date', '\@add@frontmatter{ltx:date}[role=creation]{\today}');

# Doesn't produce anything (we're already inserting frontmatter),
# But, it does make the various frontmatter macros into no-ops.
DefMacroI('\maketitle', undef,
  '\lx@frontmatterhere'
    . '\@startsection@hook'
    . '\global\let\thanks\relax'
    . '\global\let\maketitle\relax'
    . '\global\let\@maketitle\relax'
    . '\global\let\@thanks\@empty'
    . '\global\let\@author\@empty'
    . '\global\let\@date\@empty'
    . '\global\let\@title\@empty'
    . '\global\let\title\relax'
    . '\global\let\author\relax'
    . '\global\let\date\relax'
    . '\global\let\and\relax', locked => 1);

DefMacro('\@thanks', '\@empty');
# make a throwaway optional argument available for OmniBus use
DefMacro('\thanks[]{}', '\def\@thanks{#2}\lx@make@thanks{#2}');
DefConstructor('\lx@make@thanks{}', "<ltx:note role='thanks'>#1</ltx:note>");

# Abstract SHOULD have been so simple, but seems to be a magnet for abuse.
# For one thing, we'd like to just write
#   DefEnvironment('{abstract}','<ltx:abstract>#body</ltx:abstract>');
# However, we don't want to place the <ltx:abstract> environment directly where
# we found it, but we want to add it to frontmatter. This requires capturing the
# recently digested list and storing it in the frontmatter structure.

# The really messy stuff comes from the way authors -- and style designers -- misuse it.
# Basic LaTeX wants it to be an environment WITHIN the document environment,
# and AFTER the \maketitle.
# However, since all it really does is typeset "Abstract" in bold, it allows:
#   \abstract stuff...
# without even an \endabstract!  We MUST know when the abstract ends, so we've got
# to recognize when we've moved on to other stuff... \sections at the VERY LEAST.

# Additional complications come from certain other classes and styles that
# redefine abstract to take the text as an argument. And some treat it
# like \title, \author, and such, that are expected to appear in the preamble!!
# The treatment below allows an abstract environment in the preamble,
# (even though straight latex doesn't) but does not cover the 1-arg case in preamble!
#
# Probably there are other places (eg in titlepage?) that should force the close??

DefEnvironment('{abstract}', '',
  afterDigestBegin => sub {
    AssignValue(inPreamble => 0); },
  afterDigest => sub {
    my $frontmatter = LookupValue('frontmatter');
    push(@{ $$frontmatter{'ltx:abstract'} },
      ['ltx:abstract',
        { name => Digest(Tokens(T_CS('\format@title@abstract'),
              T_BEGIN, T_CS('\abstractname'), T_END)) },
        @LaTeXML::LIST]);
    DefMacroI('\maybe@end@abstract', undef, Tokens(), scope => 'global');
    return; },
  afterConstruct => sub { insertFrontMatter(@_); },    # HERE if not already done.
  locked         => 1, mode => 'text');
# If we get a plain \abstract, instead of an environment, look for \abstract{the abstract}
AssignValue('\abstract:locked' => 0);    # REDEFINE the above locked definition!
DefMacro('\abstract', sub {
    my ($gullet) = @_;
    ($gullet->ifNext(T_BEGIN)
      ? (T_CS('\abstract@onearg'))
      : (T_CS('\g@addto@macro'), T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract'),
        T_CS('\begin{abstract}'))); },
  locked => 1);
DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}\let\endabstract\relax');

DefMacroI('\maybe@end@abstract', undef, '\endabstract');

DefMacroI('\abstractname', undef, 'Abstract');
DefMacro('\format@title@abstract{}', '#1');

# Hmm, titlepage is likely to be hairy, low-level markup,
# without even title, author, etc, specified as such!
# Hmm, should this even redefine author, title, etc so that they
# are simply output?
# This is horrible hackery; What we really need, I think, is the
# ability to bind some sort of "Do <this> when we create a text box"...
# ON Second Thought...
# For the time being, ignore titlepage!
# Maybe we could do some of this if there is no title/author
# otherwise defined? Ugh!

#DefEnvironment('{titlepage}','');
# Or perhaps it's better just to ignore the markers?
#DefMacro('\titlepage','');
#DefMacro('\endtitlepage','');

# Or perhaps not....
# There's a title and other stuff in here, but how could we guess?
# Well, there's likely to be a sequence of <p><text font="xx" fontsize="yy">...</text></p>
# Presumably the earlier, larger one is title, rest are authors/affiliations...
# Particularly, if they start with a pseudo superscript or other "marker", they're probably affil!
# For now, we just give an info message
DefEnvironment('{titlepage}', '<ltx:titlepage>#body',
  beforeDigest => sub { Let('\centering', '\relax');
    AssignValue(frontmatter_deferred => 1, 'global');
    AddToMacro(T_CS('\maketitle'), T_CS('\unwind@titlepage'));
    DefEnvironmentI('abstract', undef,
      '<ltx:abstract>#body</ltx:abstract>');
    Info('unexpected', 'titlepage', $_[0],
      "When using titlepage, Frontmatter will not be well-structured");
    return; },
  beforeDigestEnd => sub { Digest(T_CS('\maybe@end@titlepage')); },
  afterConstruct  => sub { insertFrontMatter($_[0]); },
  locked          => 1, mode => 'text');

Tag('ltx:titlepage', autoClose => 1);
DefConstructorI('\maybe@end@titlepage', undef, sub {
    my ($document) = @_;
    $document->maybeCloseElement('ltx:titlepage'); });
DefConstructorI('\unwind@titlepage', undef, sub {
    my ($document) = @_;
    if (my $titlepage = $document->maybeCloseElement('ltx:titlepage')) {
      $document->unwrapNodes($titlepage);
    }
});

DefMacro('\sectionmark{}',       Tokens());
DefMacro('\subsectionmark{}',    Tokens());
DefMacro('\subsubsectionmark{}', Tokens());
DefMacro('\paragraphmark{}',     Tokens());
DefMacro('\subparagraphmark{}',  Tokens());
DefMacroI('\@oddfoot',  undef, Tokens());
DefMacroI('\@oddhed',   undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
#**********************************************************************
# C.6 Displayed Paragraphs
#**********************************************************************

DefEnvironment('{center}', sub {
    $_[0]->maybeCloseElement('ltx:p');                         # this starts a new vertical block
    aligningEnvironment('center', 'ltx_centering', @_); });    # aligning will take care of \\\\ "rows"

# HOWEVER, define a plain \center to act like \centering (?)
DefMacroI('\center',    undef, '\centering');
DefMacroI('\endcenter', undef, '');

DefEnvironment('{flushleft}', sub {
    $_[0]->maybeCloseElement('ltx:p');    # this starts a new vertical block
    aligningEnvironment('left', 'ltx_align_left', @_); });
DefEnvironment('{flushright}', sub {
    $_[0]->maybeCloseElement('ltx:p');    # this starts a new vertical block
    aligningEnvironment('right', 'ltx_align_right', @_); });

# These add an operation to be carried out on the current node & following siblings, when the current group ends.
# These operators will add alignment (class) attributes to each "line" in the current block.
sub setupAligningContext {
  my ($document) = @_;
  my $node = $document->getElement;
  AssignValue(ALIGNING_NODE => [$node, $node->lastChild]) if $node;
  return; }

sub applyAligningContext {
  my ($document, $align, $class) = @_;
  if (my $container = LookupValue('ALIGNING_NODE')) {
    my ($node, $previous) = @$container;
    my @children = $node->childNodes;
    while (my $skip = shift(@children)) {
      last if !$previous || $previous->isSameNode($skip); }
    while (my $child = shift(@children)) {
      setAlignOrClass($document, $child, $align, $class) if $child->nodeType == XML_ELEMENT_NODE; } }
  return; }

DefConstructorI('\centering', undef, \&setupAligningContext,
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@centering')); });
DefConstructorI('\raggedright', undef, \&setupAligningContext,
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedright')); });
DefConstructorI('\raggedleft', undef, \&setupAligningContext,
  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedleft')); });

DefConstructorI('\@add@centering', undef,
  sub { applyAligningContext($_[0], 'center', 'ltx_centering'); });
# Note that \raggedright is essentially align left
DefConstructorI('\@add@raggedright', undef,
  sub { applyAligningContext($_[0], undef, 'ltx_align_left'); });
DefConstructorI('\@add@raggedleft', undef,
  sub { applyAligningContext($_[0], undef, 'ltx_align_right'); });
DefConstructorI('\@add@flushright', undef,
  sub { applyAligningContext($_[0], 'right', 'ltx_align_right'); });
DefConstructorI('\@add@flushleft', undef,
  sub { applyAligningContext($_[0], 'left', 'ltx_align_left'); });

#======================================================================-
# C.6.1 Quotations and Verse
#======================================================================-
Let('\@block@cr', '\lx@newline');    # Obsolete, but in case still used
DefEnvironment('{quote}',
  '<ltx:quote>#body</ltx:quote>',
  mode => 'text');
DefEnvironment('{quotation}',
  '<ltx:quote>#body</ltx:quote>',
  mode => 'text');
DefEnvironment('{verse}',
  '<ltx:quote role="verse">#body</ltx:quote>',
  mode => 'text');

#======================================================================
# C.6.2 List-Making environments
#======================================================================
Tag('ltx:item',        autoClose => 1, autoOpen => 1);
Tag('ltx:inline-item', autoClose => 1, autoOpen => 1);

# These are for the (not quite legit) case where \item appears outside
# of an itemize, enumerate, etc, environment.
# DefConstructor('\item[]',
#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
# DefConstructor('\subitem[]',
#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
# DefConstructor('\subsubitem[]',
#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");

# Or maybe best just to do \par ?
DefMacro('\item[]',       '\par');
DefMacro('\subitem[]',    '\par');
DefMacro('\subsubitem[]', '\par');

AssignValue('@itemlevel' => 0, 'global');
AssignValue('enumlevel'  => 0, 'global');
AssignValue('@desclevel' => 0, 'global');
# protection against lower-level code...
DefConditional('\if@noitemarg');
DefMacro('\@item',      '\item');    # Hopefully no circles...
DefMacro('\@itemlabel', '');         # Maybe needs to be same as \item will be using?

# Prepare for an list (itemize/enumerate/description/etc)
# by determining the right counter (level)
# and binding the right \item ( \$type@item, if $type is defined)
sub beginItemize {
  my ($type, $counter, %options) = @_;
  # The list-type and level of the *containing* list (if any!)
  my $outercounter = LookupValue('itemcounter');
  my $outerlevel   = $outercounter && (LookupValue($outercounter . 'level') || 0);
  $counter = '@item' unless $counter;
  my $listlevel = (LookupValue('itemization_level') || 0) + 1;    # level for this list overall
  my $level     = LookupValue($counter . 'level') || 0;           # level for lists of specific type
  $level++ unless $options{nolevel};
  AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep'));
  AssignValue('itemization_level' => $listlevel);
  AssignValue($counter . 'level'  => $level);
  AssignValue(itemization_items   => 0);
  my $listpostfix = ToString(Tokens(roman($listlevel)));
  my $postfix     = ToString(Tokens(roman($level)));
  my $usecounter  = ($options{nolevel} ? $counter : $counter . $postfix);
  Let('\item' => "\\" . $type . '@item') if defined $type;
  Let('\par', '\normal@par');                                     # In case within odd environment.
  DefMacroI('\@listctr', undef, Tokens(Explode($usecounter)));
  # Now arrange that this list's id's are relative to the current (outer) item (if any)
  # And that the items within this list's id's are relative to this (new) list.
##  if(! LookupDefinition(T_CS('\@listcontext'))){
##    Let(T_CS('\@listcontext'), T_CS('\@currentlabel')); }
  AssignValue(itemcounter => $usecounter);
  my $listcounter = '@itemize' . $listpostfix;
  if (!LookupDefinition(T_CS('\c@' . $listcounter))) {    # Create new list counters as needed
    NewCounter($listcounter); }    #,  $outercounter.ToString(Tokens(roman($outerlevel))),
  if ($outercounter) {             # Make this list's ID relative to outer list's ID
    my $outerusecounter = $outercounter . ToString(Tokens(roman($outerlevel)));
    DefMacroI('\the' . $listcounter . '@ID', undef,
      '\the' . $outerusecounter . '@ID.I' . '\arabic{' . $listcounter . '}');
    # AND reset this list's counter when the outer item is stepped
    my $x;
    AssignValue("\\cl\@$outerusecounter" =>
        Tokens(T_CS($listcounter), (($x = LookupValue('\cl@' . $outerusecounter)) ? $x->unlist : ())),
      'global');
  }
  # format the id of \item's relative to the id of this list
  DefMacroI(T_CS('\the' . $usecounter . '@ID'), undef,
    Tokens(T_CS('\the' . $listcounter . '@ID'), T_OTHER('.i'), T_CS('\@' . $usecounter . '@ID')));

  my $series;
  if ($series = $options{series}) {
    $series = ToString($series); }
  if (my $start = $options{start}) {
    SetCounter($usecounter, $start);
    AddToCounter($usecounter, Number(-1)); }
  elsif (my $s = $options{resume} || $options{'resume*'}) {
    if (($s = ToString($s)) ne 'noseries') {
      $series = ToString($s);
      SetCounter($usecounter,
        LookupValue('enumitem_series_' . $series . '_last') || Number(0)); } }
  else {
    ResetCounter($usecounter); }
##  return RefStepCounter('@itemize' . $listpostfix); }
  return (RefStepCounter('@itemize' . $listpostfix),
    counter => $usecounter, series => $series); }    # So end can save counter value

# These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements
# Only create the 1st level (so that binding style can start numbering 'within' appropriately)
# Additional ones created by need.
NewCounter('@itemizei', 'section', idprefix => 'I');

# Create id, and tags for an itemize type \item
sub RefStepItemCounter {
  my ($tag)   = @_;
  my $counter = LookupValue('itemcounter');
  my $n       = LookupValue('itemization_items');
  AssignValue(itemization_items => $n + 1);
  my %attr = ();
  my $sep  = LookupDimension('\itemsep');
  if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) {
    $attr{itemsep} = $sep; }
  if (defined $tag) {
    my @props = RefStepID($counter);
    if (IsEmpty($tag)) {    # empty tag?
      return (@props); }
    else {
      my $ttag      = (ref $tag              ? $tag                      : T_OTHER($tag));
      my $formatter = ($counter =~ /^\@desc/ ? T_CS('\descriptionlabel') : T_CS('\makelabel'));
      my $typename  = (IsDefined(T_CS('\\' . $counter . 'name'))
        ? T_CS('\\' . $counter . 'name') : T_CS('\itemtyperefname'));
      my $tags = Digest(T_BEGIN,
        T_CS('\let'), T_CS('\the' . $counter),   T_CS('\@empty'),
        T_CS('\def'), T_CS('\fnum@' . $counter), T_BEGIN, $formatter, T_BEGIN, Revert($tag), T_END, T_END,
        T_CS('\def'), T_CS('\typerefnum@' . $counter),
        T_BEGIN,      $typename, T_SPACE, Revert($tag), T_END,
        Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
        T_END);

      return (@props,
        ($tags ? (tags => $tags) : ()),
        %attr); } }
  else {
    return (RefStepCounter($counter), %attr); } }

# The following two aren't used here; they're defined here so they
# can be used in paralist.sty, enumerate.sty (perhaps others?)

# This isn't really satisfactory.
# We should record the marker used for the item,
# but it really should NOT be #refnum (which should be quasi unique)
# and is not \theenumi.. (which should be a counter value)
sub setItemizationStyle {
  my ($stuff, $level) = @_;
  if (defined $stuff) {
    $level = LookupValue('@itemlevel') || 0 unless defined $level;
    $level = ToString(Tokens(roman($level)));
    DefMacroI('\labelitem' . $level, undef, $stuff); }
  return; }

sub setEnumerationStyle {
  my ($stuff, $level) = @_;
  if (defined $stuff) {
    $level = LookupValue('enumlevel') || 0 unless defined $level;
    $level = ToString(Tokens(roman($level)));
    my @in  = $stuff->unlist;
    my @out = ();
    my $ctr = T_OTHER('enum' . $level);

    while (my $t = shift(@in)) {
      if (Equals($t, T_BEGIN)) {
        push(@out, $t);
        my $brlevel = 1;
        while ($brlevel && ($t = shift(@in))) {
          if    (Equals($t, T_BEGIN)) { $brlevel++; }
          elsif (Equals($t, T_END))   { $brlevel--; }
          push(@out, $t); } }
      elsif (Equals($t, T_LETTER('A'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Alph'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('a'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\alph'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('I'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Roman'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_LETTER('i'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\roman'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      elsif (Equals($t, T_OTHER('1'))) {
        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\arabic'), $ctr));
        push(@out, T_CS('\theenum' . $level)); }
      else {
        push(@out, $t); } }
    DefMacroI('\labelenum' . $level, undef, Tokens(T_BEGIN, @out, T_END)); }
  return; }

# End paragraphs, like \par, but only if within an item's text
DefConstructorI('\preitem@par', undef, sub {
    my ($document, %props) = @_;
    if (!$props{inPreamble} && !$document->getNodeQName($document->getElement(), 'ltx:itemize')) {
      $document->maybeCloseElement('ltx:p');
      $document->maybeCloseElement('ltx:para'); } },
  alias => '\par');

# id, but NO refnum (et.al) attributes on itemize \item ...
# unless the optional tag argument was given!
# We'll make the <ltx:tag> from either the optional arg, or from \labelitemi..
DefMacro('\itemize@item', '\preitem@par\itemize@item@');
DefConstructor('\itemize@item@ OptionalUndigested',
  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@itemize@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });

DefMacro('\enumerate@item', '\preitem@par\enumerate@item@');
DefConstructor('\enumerate@item@ OptionalUndigested',
  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@enumerate@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });

DefMacro('\description@item', '\preitem@par\description@item@');
DefConstructor('\description@item@ OptionalUndigested',
  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@description@item OptionalUndigested',
  "<ltx:inline-item xml:id='#id'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });

DefEnvironment('{itemize}',
  "<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
  properties      => sub { beginItemize('itemize', '@item'); },
  beforeDigestEnd => sub { Digest('\par'); },
  locked          => 1, mode => 'text');
DefEnvironment('{enumerate}',
  "<ltx:enumerate xml:id='#id'>#body</ltx:enumerate>",
  properties      => sub { beginItemize('enumerate', 'enum'); },
  beforeDigestEnd => sub { Digest('\par'); },
  locked          => 1, mode => 'text');
DefEnvironment('{description}',
  "<ltx:description  xml:id='#id'>#body</ltx:description>",
  beforeDigest    => sub { Let('\makelabel', '\descriptionlabel'); },
  properties      => sub { beginItemize('description', '@desc'); },
  beforeDigestEnd => sub { Digest('\par'); },
  locked          => 1, mode => 'text');

DefMacro('\makelabel{}', '#1');
DefMacro('\@mklab{}',    '\hfil #1');

#----------------------------------------------------------------------
# Basic itemize bits
# Fake counter for itemize to give id's to ltx:item.
NewCounter('@itemi',   undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('@itemii',  undef, idwithin => '@itemi',    idprefix => 'i');
NewCounter('@itemiii', undef, idwithin => '@itemii',   idprefix => 'i');
NewCounter('@itemiv',  undef, idwithin => '@itemiii',  idprefix => 'i');
NewCounter('@itemv',   undef, idwithin => '@itemiv',   idprefix => 'i');
NewCounter('@itemvi',  undef, idwithin => '@itemv',    idprefix => 'i');
# These are empty to make the 'refnum' go away.
DefMacroI('\the@itemi',   undef, '');
DefMacroI('\the@itemii',  undef, '');
DefMacroI('\the@itemiii', undef, '');
DefMacroI('\the@itemiv',  undef, '');
DefMacroI('\the@itemv',   undef, '');
DefMacroI('\the@itemvi',  undef, '');

# Formatted item tags.
# Really should be in the class file, but already was here.
DefMacroI('\labelitemi',   undef, '\textbullet');
DefMacroI('\labelitemii',  undef, '\normalfont\bfseries \textendash');
DefMacroI('\labelitemiii', undef, '\textasteriskcentered');
DefMacroI('\labelitemiv',  undef, '\textperiodcentered');

# Make the fake counters point to the real labels
DefMacroI('\label@itemi',   undef, '\labelitemi');
DefMacroI('\label@itemii',  undef, '\labelitemii');
DefMacroI('\label@itemiii', undef, '\labelitemiii');
DefMacroI('\label@itemiv',  undef, '\labelitemiv');

# These hookup latexml's tagging to normal latex's \labelitemi...
DefMacroI('\fnum@@itemi',   undef, '{\makelabel{\label@itemi}}');
DefMacroI('\fnum@@itemii',  undef, '{\makelabel{\label@itemii}}');
DefMacroI('\fnum@@itemiii', undef, '{\makelabel{\label@itemiii}}');
DefMacroI('\fnum@@itemiv',  undef, '{\makelabel{\label@itemiv}}');

# These define the typerefnum form, for out-of-context \ref's
# Better would language sensitive!
my @pm_ordinal_suffices = ('th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th');
DefMacro('\lx@poormans@ordinal{}', sub {
    my ($gullet, $ctr) = @_;
    my $n      = CounterValue($ctr)->valueOf;
    my $string = "$n";
    if ($string =~ /.*?(\d)$/) {
      $string .= $pm_ordinal_suffices[$1]; }
    T_OTHER($string); });
DefMacroI('\itemtyperefname', undef, 'item');
DefMacroI('\itemcontext',     undef, '\space in \@listcontext');
DefMacroI('\itemcontext',     undef, '');
# Probably would help to give a bit more context for the ii & higher?
DefMacroI('\typerefnum@@itemi', undef, '\lx@poormans@ordinal{@itemi} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemii', undef, '\lx@poormans@ordinal{@itemii} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemiii', undef, '\lx@poormans@ordinal{@itemiii} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemiv', undef, '\lx@poormans@ordinal{@itemiv} \itemtyperefname \itemcontext');
#----------------------------------------------------------------------
# Basic enumeration bits

# Class file normally would have
#  NewCounter for enumi,...,
#  define \labelenumi,... and probably \p@enumii...
NewCounter('enumi',   undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('enumii',  undef, idwithin => 'enumi',     idprefix => 'i');
NewCounter('enumiii', undef, idwithin => 'enumii',    idprefix => 'i');
NewCounter('enumiv',  undef, idwithin => 'enumiii',   idprefix => 'i');
NewCounter('enumv',   undef, idwithin => 'enumiv',    idprefix => 'i');    # A couple of extra
NewCounter('enumvi',  undef, idwithin => 'enumv',     idprefix => 'i');

# How the refnums look... (probably should be in class file, but already here)
DefMacroI('\p@enumii',  undef, '\theenumi');
DefMacroI('\p@enumiii', undef, '\theenumi(\theenumii)');
DefMacroI('\p@enumiv',  undef, '\p@enumii\theenumiii');

# Formatting of item tags (probably should be in the class file, but already here)
DefMacroI('\labelenumi',   undef, '\theenumi.');
DefMacroI('\labelenumii',  undef, '(\theenumii)');
DefMacroI('\labelenumiii', undef, '\theenumiii.');
DefMacroI('\labelenumiv',  undef, '\theenumiv.');

# These hookup latexml's tagging to normal latex's \labelenummi...
DefMacroI('\fnum@enumi',   undef, '{\makelabel{\labelenumi}}');
DefMacroI('\fnum@enumii',  undef, '{\makelabel{\labelenumii}}');
DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}');
DefMacroI('\fnum@enumiv',  undef, '{\makelabel{\labelenumiv}}');

# These define the typerefnum form, for out-of-context \ref's
DefMacroI('\enumtyperefname',    undef, 'item');
DefMacroI('\typerefnum@enumi',   undef, '\enumtyperefname~\p@enumi\theenumi \itemcontext');
DefMacroI('\typerefnum@enumii',  undef, '\enumtyperefname~\p@enumii\theenumii \itemcontext');
DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\theenumiii \itemcontext');
DefMacroI('\typerefnum@enumiv',  undef, '\enumtyperefname~\p@enumiv\theenumiv \itemcontext');
#DefMacroI('\typerefnum@enumi',   undef, '\enumtyperefname~\p@enumi\labelenumi \itemcontext');
#DefMacroI('\typerefnum@enumii',  undef, '\enumtyperefname~\p@enumii\labelenumii \itemcontext');
#DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\labelenumiii \itemcontext');
#DefMacroI('\typerefnum@enumiv',  undef, '\enumtyperefname~\p@enumiv\labelenumiv \itemcontext');

#----------------------------------------------------------------------
# Basic description list bits
# Fake counter for itemize to give id's to ltx:item.
NewCounter('@desci',   undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('@descii',  undef, idwithin => '@desci',    idprefix => 'i');
NewCounter('@desciii', undef, idwithin => '@descii',   idprefix => 'i');
NewCounter('@desciv',  undef, idwithin => '@desciii',  idprefix => 'i');
NewCounter('@descv',   undef, idwithin => '@desciv',   idprefix => 'i');
NewCounter('@descvi',  undef, idwithin => '@descv',    idprefix => 'i');
# No refnum's here, either
DefMacroI('\the@desci',   undef, '');
DefMacroI('\the@descii',  undef, '');
DefMacroI('\the@desciii', undef, '');
DefMacroI('\the@desciv',  undef, '');
DefMacroI('\the@descv',   undef, '');
DefMacroI('\the@descvi',  undef, '');
# These hookup latexml's numbering to normal latex's
# Umm.... but they're not normally used, since \item usually gets an argument!
DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1');
DefMacroI('\fnum@@desci',   undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@descii',  undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@desciii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@desciv',  undef, '{\descriptionlabel{}}');

DefMacroI('\desctyperefname', undef, 'item');

# Blech
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\itemtyperefname'); }
  qw(@itemi @itemii @itemiii @itemiv @itemv @itemvi);
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\enumtyperefname'); }
  qw(enumi enumii enumiii enumiv);
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\desctyperefname'); }
  qw(@desci @descii @desciii @desciv @descv @descvi);

#======================================================================
# C.6.3 The list and trivlist environments.
#======================================================================
# Generic lists are given a way to format the item label, and presumably
# a counter.

DefConditional('\if@nmbrlist');
DefMacro('\@listctr', '');
DefPrimitive('\usecounter{}', sub {
    my ($stomach, $counter) = @_;
    $counter = ToString(Expand($counter));
    beginItemize('list', $counter, ($counter ? (nolevel => 1) : ()));
    return; });

DefMacro('\list{}{}',
'\let\@listctr\@empty#2\ifx\@listctr\@empty\usecounter{}\fi\expandafter\def\csname fnum@\@listctr\endcsname{#1}\lx@list');
DefMacro('\endlist', '\endlx@list');

# Start an anonymous list (often misused)
DefConstructor('\lx@list',
  "<ltx:itemize>",
  beforeDigest => sub { $_[0]->bgroup; });
# Close the anonymous list if we're still within one.
DefConstructor('\endlx@list', sub {
    $_[0]->maybeCloseElement('ltx:itemize'); },
  beforeDigest => sub { $_[0]->egroup; });

DefConstructor('\list@item OptionalUndigested',
  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
  properties => sub { RefStepItemCounter($_[1]); });

# This isn't quite right, although it seems right for deep, internal uses with a single \item.
# Perhaps we need to check trivlist's afterwards and if they are just a single item,
# reduce it to an ltx:p ??
DefConstructor('\trivlist',
  "<ltx:itemize _autoclose='1'>",
  properties => sub { beginItemize('trivlist'); });
DefConstructor('\endtrivlist', sub {
    $_[0]->maybeCloseElement('ltx:itemize'); },
  beforeDigest => sub { Digest('\par'); });
DefMacro('\trivlist@item', '\preitem@par\trivlist@item@');
DefConstructor('\trivlist@item@ OptionalUndigested',
  "<ltx:item xml:id='#id' itemsep='#itemsep'>"
    . "<ltx:tags><ltx:tag>#tag</ltx:tag></ltx:tags>",    # At least an empty tag! ?
  properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); });
DefMacro('\@trivlist', '\relax', locked => 1);

DefRegister('\topsep'             => Glue(0));
DefRegister('\partopsep'          => Glue(0));
DefRegister('\lx@default@itemsep' => Glue(0));
DefRegister('\itemsep'            => Glue(0));
DefRegister('\parsep'             => Glue(0));
DefRegister('\@topsep'            => Glue(0));
DefRegister('\@topsepadd'         => Glue(0));
DefRegister('\@outerparskip'      => Glue(0));
DefRegister('\leftmargin'         => Dimension(0));
DefRegister('\rightmargin'        => Dimension(0));
DefRegister('\listparindent'      => Dimension(0));
DefRegister('\itemindent'         => Dimension(0));
DefRegister('\labelwidth'         => Dimension(0));
DefRegister('\labelsep'           => Dimension(0));
DefRegister('\@totalleftmargin'   => Dimension(0));
DefRegister('\leftmargini'        => Dimension(0));
DefRegister('\leftmarginii'       => Dimension(0));
DefRegister('\leftmarginiii'      => Dimension(0));
DefRegister('\leftmarginiv'       => Dimension(0));
DefRegister('\leftmarginv'        => Dimension(0));
DefRegister('\leftmarginvi'       => Dimension(0));
DefRegister('\@listdepth'         => Number(0));
DefRegister('\@itempenalty'       => Number(0));
DefRegister('\@beginparpenalty'   => Number(0));
DefRegister('\@endparpenalty'     => Number(0));
DefRegister('\labelwidthi'        => Dimension(0));
DefRegister('\labelwidthii'       => Dimension(0));
DefRegister('\labelwidthiii'      => Dimension(0));
DefRegister('\labelwidthiv'       => Dimension(0));
DefRegister('\labelwidthv'        => Dimension(0));
DefRegister('\labelwidthvi'       => Dimension(0));

DefRegister('\@itemdepth' => Number(0));

#======================================================================
# C.6.4 Verbatim
#======================================================================

# NOTE: how's the best way to get verbatim material through?
#DefEnvironment('{verbatim}',  '<ltx:verbatim>#body</ltx:verbatim>');
#DefEnvironment('{verbatim*}', '<ltx:verbatim>#body</ltx:verbatim>');

DefMacroI('\@verbatim', undef,
  '\par\aftergroup\lx@end@verbatim\lx@@verbatim');    # Close enough?
DefConstructorI('\lx@@verbatim', undef,
  "<ltx:verbatim font='#font'>",
  beforeDigest => sub {
    my ($stomach) = @_;
    StartSemiverbatim('%', '\\', '{', '}');
    MergeFont(family => 'typewriter', series => 'medium', shape => 'upright');
    $STATE->assignCatcode(' ', CC_ACTIVE);    # Do NOT (necessarily) skip spaces after \verb!!!
    Let(T_ACTIVE(' '), T_SPACE); });
DefConstructorI('\lx@end@verbatim', undef,
  "</ltx:verbatim>",
  beforeDigest => sub {
    EndSemiverbatim(); });

# verbatim is a bit of special case;
# It looks like an environment, but it only ends with an explicit "\end{verbatim}" on it's own line.
# So, we'll end up doing things more manually.
# We're going to sidestep the Gullet for inputting,
# and also the usual environment capture.
DefConstructorI(T_CS('\begin{verbatim}'), undef,
  "<ltx:verbatim font='#font'>#body</ltx:verbatim>",
  beforeDigest    => [sub { beforeDigestVerbatim(0, @_); }],
  afterDigest     => [sub { afterDigestVerbatim(0, @_); }],
  beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });

DefConstructorI(T_CS('\begin{verbatim*}'), undef,
  "<ltx:verbatim font='#font'>#body</ltx:verbatim>",
  beforeDigest    => [sub { beforeDigestVerbatim(1, @_); }],
  afterDigest     => [sub { afterDigestVerbatim(1, @_); }],
  beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });

sub beforeDigestVerbatim {
  my ($starred, $stomach) = @_;
  $stomach->bgroup;
  my @stuff = ();
  if (my $b = LookupValue('@environment@verbatim@atbegin')) {
    push(@stuff, Digest(@$b)); }
  AssignValue(current_environment => 'verbatim');
  DefMacroI('\@currenvir', undef, 'verbatim');
  MergeFont(family => 'typewriter');
  # Digest(T_CS('\par')); # NO! See beforeConstruct!
  return @stuff; }

sub afterDigestVerbatim {
  my ($starred, $stomach, $whatsit) = @_;
  #      $stomach->egroup;
  my $font   = $whatsit->getFont;
  my $loc    = $whatsit->getLocator;
  my $end    = $starred ? '\end{verbatim*}' : '\end{verbatim}';
  my $space  = $starred ? "\x{2423}"        : ' ';
  my @lines  = ();
  my $gullet = $stomach->getGullet;
  while (defined(my $line = $gullet->readRawLine)) {
    my ($exiting, $remaining) = (0, undef);
    if ($line =~ /^(.*?)\Q$end\E(.*?)$/) {
      $exiting   = 1;
      $line      = $1;
      $remaining = $2; }
    # The raw chars will still have to be decoded (but not space!!)
    $line = join('', map { ($_ eq ' ' ? $space : FontDecodeString($_, 'OT1_typewriter')) }
        split(//, $line));
    push(@lines, $line . "\n");
    if ($exiting) {
      $gullet->unread(Tokenize($remaining), T_CR);
      last; } }
  pop(@lines) if $lines[-1] eq "\n";
  # Note last line ends up as Whatsit's "trailer"
  if (my $b = LookupValue('@environment@verbatim@atend')) {
    push(@lines, ToString(Digest(@$b))); }
  $stomach->egroup;
  $whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end);
  return; }

DefPrimitiveI('\@vobeyspaces', undef, sub {
    AssignCatcode(" " => 13);
    Let(T_ACTIVE(" "), '\nobreakspace');
    return });
DefMacro('\@xobeysp', '\nobreakspace');

# WARNING: Need to be careful about what catcodes are active here
# And clearly separate expansion from digestion
DefMacroI('\verb', undef, sub {
    my ($gullet) = @_;
    StartSemiverbatim('%', '\\', '{', '}');
    $STATE->assignCatcode(' ', CC_ACTIVE);
    my $init;
    my $skippedSpace = 0;
    # As of texlive 2021, DO skip spaces before delimiter (even tho we've changed catcodes)
    # but if we do skip spaces, * can be the delimiter
    do { $init = $gullet->readToken();
      $skippedSpace = 1 if (defined $init && $init->getString eq ' ');
    } while (defined $init && $init->getString eq ' ');
    my $starred = 0;
    if (T_OTHER('*')->equals($init) && !$skippedSpace) {
      $starred = 1;
      do { $init = $gullet->readToken();
      } while (defined $init && $init->getString eq ' '); }
    if (!$init) {    # typically something read too far got \verb and the content is somewhere else..?
      Error('expected', 'delimiter', $gullet,
        "Verbatim argument lost", "Bindings for preceding code is probably broken");
      EndSemiverbatim();
      return (); }
    my $init_str = $init->getString();
    AssignCatcode($init_str => CC_ACTIVE);
    my $delim = T_ACTIVE($init_str);
    my $body  = $gullet->readUntil($delim);
    EndSemiverbatim();
    Tokens(
      T_CS('\@hidden@bgroup'),
      ($starred ? T_CS('\lx@use@visiblespace') : ()),
      Invocation(T_CS('\@internal@verb'),
        ($starred ? T_OTHER('*') : Tokens()),
        Tokens($init), $body),
      T_CS('\@hidden@egroup'))->unlist; });

DefPrimitive('\lx@use@visiblespace', sub {
    my ($stomach, $star) = @_;
    $STATE->assignCatcode(' ', CC_ACTIVE);      # Do NOT (necessarily) skip spaces after \verb!!!
    Let(T_ACTIVE(' '), T_OTHER("\x{2423}"));    # Visible space
});

# Arrange to digest the body in text mode, to keep (eg) "_" from turning to "\_"
DefMacro('\@internal@verb{}{}{}', '\ifmmode\@internal@math@verb{#1}{#2}{#3}\else\@internal@text@verb{#1}{#2}{#3}\fi');
DefConstructor('\@internal@math@verb{} Undigested {}',
  "<ltx:XMTok font='#font'>#3</ltx:XMTok>",
  mode      => 'text',
  font      => { family => 'typewriter', series => 'medium', shape => 'upright' },
  reversion => '\verb#1#2#3#2');
DefConstructor('\@internal@text@verb{} Undigested {}',
  "<ltx:verbatim font='#font'>#3</ltx:verbatim>",
  font            => { family => 'typewriter', series => 'medium', shape => 'upright' },
  beforeConstruct => sub {
    my ($doc, $whatsit) = @_;
    $doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); },
  reversion => '\verb#1#2#3#2');

# This is defined by the alltt package.
# Environment('alltt', ?);

# Actually, latex sets catcode to 13 ... is this close enough?
DefPrimitiveI('\obeycr',    undef, sub { AssignValue('PRESERVE_NEWLINES' => 1); });
DefPrimitiveI('\restorecr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 0); });

DefMacroI('\normalsfcodes', undef, Tokens());

#**********************************************************************
# C.7 Mathematical Formulas
#**********************************************************************

#======================================================================
# C.7.1 Math Mode Environments
#======================================================================
DefMacroI('\@eqnnum', undef, '(\theequation)', locked => 1);
DefMacro('\fnum@equation', '\@eqnnum');

# Redefined from TeX.pool, since with LaTeX we presumably have a more complete numbering system
DefConstructorI('\@@BEGINDISPLAYMATH', undef,
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  alias        => '$$',
  beforeDigest => sub {
    $_[0]->beginMode('display_math');
    if (my @everymath_toks = $STATE->lookupDefinition(T_CS('\everymath'))->valueOf->unlist()) {
      $_[0]->getGullet->unread(@everymath_toks); }
    if (my @everydisplay_toks = $STATE->lookupDefinition(T_CS('\everydisplay'))->valueOf->unlist()) {
      $_[0]->getGullet->unread(@everydisplay_toks); }
    return; },
  properties  => sub { RefStepID('equation') },
  captureBody => 1);

DefEnvironment('{displaymath}',
  "<ltx:equation xml:id='#id'>"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode       => 'display_math',
  properties => sub { RefStepID('equation') },
  locked     => 1);
DefEnvironment('{math}',
  "<ltx:Math mode='inline'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>",
  mode => 'inline_math',
);

Let('\curr@math@size', '\@empty');

# Equation Numbering turns out to be rather convoluted!
# numbered equations & friends want to IMMEDIATELY increment the equation counter
# (you can see by \theequation)
# However, \nonumber, \tag, set a different number and RETRACT stepping the counter!
NewCounter('subequation', 'equation', idprefix => 'E', idwithin => 'equation');
DefMacro('\thesubequation',   '\theequation\alph{subequation}');
DefMacro('\fnum@subequation', '(\thesubequation)');

# This provides {equation} with the capabilities for tags, nonumber, etc
# even though stock LaTeX provides no means to override them.
#   preset => boolean
#   postset => boolean
#   deferretract=>boolean
sub prepareEquationCounter {
  my (%options) = @_;
  AssignValue(EQUATION_NUMBERING => {%options}, 'global');
  return; }

sub beforeEquation {
  my $numbering = LookupValue('EQUATION_NUMBERING');
  my $numbered  = $$numbering{numbered};
  my $ctr       = $$numbering{counter} || 'equation';
  MaybePeekLabel();
  $$numbering{in_equation} = 1;
  if ($$numbering{preset}) {
    AssignValue(EQUATIONROW_TAGS => {
        preset => 1,
        ($numbered ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
  else {
    AssignValue(EQUATIONROW_TAGS => {}, 'global'); }
  Let('\@@ENDDISPLAYMATH',   '\lx@eDM@in@equation');
  Let('\@@BEGINDISPLAYMATH', '\lx@bDM@in@equation');
  return; }

# Note peculiar usages of \[,\],$$ WITHIN displayed math, like {equation}.
# Authors sometimes use \begin{equation} math \] some text \[ more math \end{equation}
# to create a cheap intertext. And, the refnum appears on the SECOND equation!
# But in redefining these, note that a "valid" \[ math \] might be used within some
# sort of text insertion (eg \footnote)!
# So, we've got to dance around with redefinitions!!
# SIDE NOTE: \[,\] are really just $$ w/ appropriate error checking!
Let('\lx@saved@BEGINDISPLAYMATH', '\@@BEGINDISPLAYMATH');
Let('\lx@saved@ENDDISPLAYMATH',   '\@@ENDDISPLAYMATH');

DefMacro('\lx@bDM@in@equation', '\lx@saved@BEGINDISPLAYMATH\let\@@ENDDISPLAYMATH\lx@saved@ENDDISPLAYMATH');
DefMacro('\lx@eDM@in@equation',
  '\lx@retract@eqnno\lx@begin@fake@intertext'
    . '\let\lx@saved@BEGINDISPLAYMATH\@@BEGINDISPLAYMATH\let\lx@saved@bdm\['
    . '\let\@@BEGINDISPLAYMATH\lx@end@fake@intertext'
    . '\let\[\lx@end@fake@intertext');

DefMacro('\lx@begin@fake@intertext', '\end{equation}');
DefMacro('\lx@end@fake@intertext',
  '\let\@@BEGINDISPLAYMATH\lx@saved@BEGINDISPLAYMATH\let\[\lx@saved@bdm' .
    '\begin{equation}');
DefPrimitive('\lx@retract@eqnno', sub { retractEquation(); });

sub retractEquation {
  my $numbering = LookupValue('EQUATION_NUMBERING');
  # What about scopes? Is that handled automagically?
  # What about \@currentID....
  my $tags = LookupValue('EQUATIONROW_TAGS');
  my $ctr  = $$tags{counter} || $$numbering{counter} || 'equation';
  if ($$tags{preset}) {    # ONLY if the number was preset!
                           # counter (or ID counter) was stepped, so decrement it.
    AddToCounter(($$numbering{numbered} ? $ctr : 'UN' . $ctr), Number(-1)); }
  AssignValue(EQUATIONROW_TAGS => { RefStepID($ctr), reset => 1 }, 'global');
  return; }

# Disable this equation's number
# Ahhhh, but in eqnarray, this isn't effected until \\ !!!!!
DefMacroI('\nonumber', undef, '\lx@equation@nonumber');
DefPrimitiveI('\lx@equation@nonumber', undef, sub {
    my $numbering = LookupValue('EQUATION_NUMBERING');
    if ($$numbering{in_equation}) {
      if ($$numbering{deferretract}) {
        my $tags = LookupValue('EQUATIONROW_TAGS');
        $$tags{retract} = 1; }
      else {
        retractEquation(); } }
    return; });
Let('\@LTX@nonumber', '\lx@equation@nonumber');

# Set this equation's number explicitly (eg ams's \tag)
DefMacroI('\lx@equation@settag', undef, '\lx@equation@retract\lx@equation@settag@');
DefPrimitiveI('\lx@equation@retract', undef, sub { retractEquation(); });
DefPrimitive('\lx@equation@settag@ Digested', sub {
    my $tags = LookupValue('EQUATIONROW_TAGS');
    $$tags{tags} = $_[1];
    return; },
  mode => 'text');

sub afterEquation {
  my ($whatsit) = @_;
  my $numbering = LookupValue('EQUATION_NUMBERING');
  my $tags      = LookupValue('EQUATIONROW_TAGS');
  my $ctr       = $$tags{counter} || $$numbering{counter} || 'equation';
  if (!$$tags{noretract}
    && ($$tags{retract} || ($$numbering{retract} && $$numbering{preset} && $$tags{preset}))) {
    retractEquation(); }
  elsif ($$numbering{postset} && !$$tags{reset}) {
    # my %props = ();
    # if ($$numbering{numbered}) {
    #   %props = RefStepCounter($ctr); }
    # else {
    #   %props = RefStepID($ctr); }
    # AssignValue(EQUATIONROW_TAGS => {%props}, 'global'); }
    AssignValue(EQUATIONROW_TAGS => {
        ($$numbering{numbered} ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
  elsif (!$$tags{reset} && $$numbering{numbered}) {
    $$tags{tags} = Digest(Invocation(T_CS('\lx@make@tags'), $ctr)); }

  # Now install the tags in $whatsit or current Row, as appropriate.
  my $props = LookupValue('EQUATIONROW_TAGS');
  if ($$numbering{aligned}) {
    if (my $alignment = LookupValue('Alignment')) {
      my $row = $alignment->currentRow;
      $$row{id}   = $$props{id};
      $$row{tags} = $$props{tags}; } }
  elsif ($whatsit) {
    $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); }
  $$numbering{in_equation} = 0;
  return; }

# My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...?
DefEnvironment('{equation}',
  "<ltx:equation xml:id='#id'>"
    . "#tags"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode         => 'display_math',
  beforeDigest => sub {
    prepareEquationCounter(numbered => 1, preset => 1);
    beforeEquation(); },
  afterDigestBody => sub {
    afterEquation($_[1]); },
  locked => 1);

# Note: In ams, this DOES get a number if \tag is used!
DefEnvironment('{equation*}',
  "<ltx:equation xml:id='#id'>"
    . "#tags"
    . "<ltx:Math mode='display'>"
    . "<ltx:XMath>"
    . "#body"
    . "</ltx:XMath>"
    . "</ltx:Math>"
    . "</ltx:equation>",
  mode         => 'display_math',
  beforeDigest => sub {
    prepareEquationCounter(numbered => undef, preset => 1);
    beforeEquation(); },
  afterDigestBody => sub {
    afterEquation($_[1]); },
  locked => 1);

DefMacro('\[', '\@@BEGINDISPLAYMATH');
DefMacro('\]', '\@@ENDDISPLAYMATH');
DefMacro('\(', '\@@BEGININLINEMATH');
DefMacro('\)', '\@@ENDINLINEMATH');

# Keep from expanding too early, if in alignments, or such.
DefMacroI('\ensuremath', undef,
  Tokens(T_CS('\protect'), T_CS('\@ensuremath')));
DefMacro('\@ensuremath{}', sub {
    my ($gullet, $stuff) = @_;
    if (LookupValue('IN_MATH')) { $stuff->unlist; }
    else { (T_MATH, $stuff->unlist, T_MATH); } });

# Magic check that math-mode trigger follows
our $MATHENVS = 'displaymath|equation*?|eqnarray*?'
  . '|multline*?|align*?|falign*?|alignat*?|xalignat*?|xxalignat*?|gather*?';
DefMacro('\ensuremathfollows', sub {
    my ($gullet) = @_;
    $gullet->closeMouth unless ($gullet->getMouth->hasMoreInput);
    if (my $tok = $gullet->readToken()) {
      my $csname = $tok->getCSName;
      if ($csname eq '\begin') {
        my $arg = $gullet->readArg();
        $csname = $arg->toString;
        $gullet->unread(T_BEGIN, $arg->unlist, T_END);
      }
      $gullet->unread($tok);
      # We need to determine whether the TeX we're given needs to be wrapped in \(...\)
      # Does it have $'s around it? Does it have a display math environment?
      if ($csname !~ /^Math|\\\(|\\\[|(?:$MATHENVS)/o) {
        AssignValue('automath_triggered' => 1, 'global');
        return T_CS('\\('); } }
    return;
});

DefMacro('\ensuremathpreceeds', sub {
    return LookupValue('automath_triggered') ? T_CS('\\)') : ();
});

# Since the arXMLiv folks keep wanting ids on all math, let's try this!
Tag('ltx:Math', afterOpen => sub { GenerateID(@_, 'm'); });

#======================================================================
# Sub-numbered equations
# Although LaTeX itself doesn't provide such an environment,
# many other packages & classes do, and they seem essentially equivalent.
# They provide an environment to wrap around equations,
# which hijack the equation counter to sub-number within the group.
DefConstructor('\lx@equationgroup@subnumbering@begin',
  "<ltx:equationgroup xml:id='#id'>"
    . "#tags",
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my %eqn   = RefStepCounter('equation');
    my $eqnum = Expand(T_CS('\theequation'));
    AssignValue(SAVED_EQUATION_NUMBER => LookupRegister('\c@equation'));
    $whatsit->setProperties(%eqn);
    ResetCounter('equation');
    DefMacroI('\theequation',    undef, UnTeX($eqnum) . '\alph{equation}');
    DefMacroI('\theequation@ID', undef, UnTeX($eqn{id}) . '.\@equation@ID'); });
Tag('ltx:equationgroup', autoClose => 1);
DefConstructor('\lx@equationgroup@subnumbering@end', sub {
    $_[0]->maybeCloseElement('ltx:equationgroup'); },
  afterDigest => sub {
    AssignRegister('\c@equation', LookupValue('SAVED_EQUATION_NUMBER'), 'global');
  });

#======================================================================
# ========================================
# eqnarray, etal
# Tricky! There's a conflict between a math-level alignment (which
# intermingles non-math things like labels, refnums, intertext),
# and a text-level alignment (which fragments the math's logical structure).
# Our solution is to attempt to synthesize a logical structure of
# an equationgroup containing equations, but using MathFork structures
# to hide the intended aligmnents.
# Then, XSLT can be used in the end to either display in a logical
# (but non-aligned format), or display fully aligned (but hiding the semantics).
#======================================================================
# Equation Groups
#   <equationgroup> representing aligned collections of equations
#   in particular, eqnarray and amsmath's align, ...
# The intended usage is a sequence of patterns like
#     LHS & REL & RHS
# When the LHS is empty, it likely implies a continuation of the previous (or multirelation).
# When both the LHS & REL are empty, it likely implies a continuation of the previous RHS
#======================================================================
# The strategy here is to use the alignment mechanism to construct
# an initial form:
#  <equationgroup>   as the overall container
#    <equation>      for each row; this can receive id, refnum & label attributes
#      <_Capture_>   to capture each columns math.
# After the initial construction (but before any rewriting & parsing)
# we scan through the equations combining the ones that appear (heuristically)
# to be single equations on several rows (either as multi-relations, or
# rhs's of multiple rows)
#  The combinations are represented by a MathFork container which
# holds both the apparently meaningful complete equation, along with
# the rows & columns that show the desired alignment.
# Each of those columns contains a <Math>, thus these can also be parsed,
# and converted to MathML or images.
#
# Thus, both forms are present: A presentation-oriented stylesheet can
# thus represent the eqnarray by a table with aligned math chunks.
# A content-oriented stylesheet can select the composed meaningful pieces.

# ========================================
# The following set deal with numbering the equations (rows) that make up an equationgroup.
# Note EQUATIONGROUP_NUMBER controls the numbering of the equations contained within the equationgroup,
# not the numbering of the equationgroup itself.

DefPrimitive('\@equationgroup@numbering RequiredKeyVals', sub {
    my $kv = GetKeyVals($_[1]);
    prepareEquationCounter(map { ($_ => ToString($$kv{$_})) } keys %$kv); });

# ========================================
# Some special kinds of rows...
DefConditionalI('\if@in@firstcolumn', undef, sub {
    my $alignment = LookupValue('Alignment');
    my $n         = ($alignment ? $alignment->currentColumnNumber : 9);
    return $alignment && (!$$alignment{in_row} ||
      (!$$alignment{in_column} && ($alignment->currentColumnNumber < 2))); });

# A bit more defensiveness, since people abuse eqnarray so badly.
# Eg. &&\lefteqn{...}  whatever?!?!
DefMacro('\lefteqn{}',
'\if@in@firstcolumn\multicolumn{3}{l}{\@ADDCLASS{ltx_eqn_lefteqn}\@@BEGININLINEMATH \displaystyle #1\@@ENDINLINEMATH\mbox{}}'
    . '\else\rlap{\@@BEGININLINEMATH\displaystyle #1\@@ENDINLINEMATH}\fi');
# \intertext (in amsmath)

Let('\displ@y', '\displaystyle');    # good enough?
DefMacro('\@lign', '');              # good enough?
# ========================================
# eqnarray
DefMacroI('\eqnarray', undef,
  '\@eqnarray@bindings\@@eqnarray'
    . '\@equationgroup@numbering{numbered=1,preset=1,deferretract=1,grouped=1,aligned=1}'
    . '\@start@alignment',
  locked => 1);
DefMacroI('\endeqnarray', undef,
  '\cr\@finish@alignment\end@eqnarray',
  locked => 1);
DefMacro('\csname eqnarray*\endcsname',
  '\@eqnarray@bindings\@@eqnarray'
    . '\@equationgroup@numbering{numbered=1,preset=1,retract=1,grouped=1,aligned=1}'
    . '\@start@alignment',
  locked => 1);
DefMacro('\csname endeqnarray*\endcsname',
  '\@finish@alignment\end@eqnarray',
  locked => 1);

DefPrimitive('\@eqnarray@bindings', sub {
    eqnarrayBindings(); });

DefPrimitiveI('\eqnarray@row@before@', undef, sub { beforeEquation(); });
DefPrimitiveI('\eqnarray@row@after@',  undef, sub { afterEquation(); });
DefMacroI('\eqnarray@row@before', undef, '\hidden@noalign{\eqnarray@row@before@}');
DefMacroI('\eqnarray@row@after',  undef, '\hidden@noalign{\eqnarray@row@after@}');

sub eqnarrayBindings {
  my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
    after => Tokens(T_MATH) };
  my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
    after => Tokens(T_MATH, T_CS('\hfil')) };
  my $col3 = { before => Tokens(T_MATH, T_CS('\displaystyle')),
    after => Tokens(T_MATH, T_CS('\hfil')) };

  my %attributes = (
    'class'  => 'ltx_eqn_eqnarray',
    'colsep' => LookupDimension('\arraycolsep')->multiply(2));
  my $cur_jot = LookupDimension('\jot');
  if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
    $attributes{rowsep} = $cur_jot; }

  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
      template      => LaTeXML::Core::Alignment::Template->new(columns => [$col1, $col2, $col3]),
      openContainer => sub { my %attr = RefStepID('@equationgroup');
        $attr{'xml:id'} = $attr{id}; delete $attr{id};
        $attr{class}    = 'ltx_eqn_eqnarray';
        $_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); },
      closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); },
      openRow        => sub {
        my ($doc, %props) = @_;
        my $tags = $props{tags}; delete($props{tags});
        $doc->openElement('ltx:equation', %props);
        $doc->absorb($tags) if $tags; },
      closeRow    => sub { $_[0]->closeElement('ltx:equation'); },
      openColumn  => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); },
      closeColumn => sub { $_[0]->closeElement('ltx:_Capture_'); },
      properties  => { preserve_structure => 1, attributes => {%attributes} }));

  Let("\\\\",                    '\@alignment@newline');
  Let('\lx@intercol',            '\lx@math@intercol');
  Let('\@row@before',            '\eqnarray@row@before');
  Let('\@row@after',             '\eqnarray@row@after');
  Let('\lx@eqnarray@save@label', '\label');
  Let('\label',                  '\lx@eqnarray@label');
  return; }

# A \label preceding \lefteqn throws of the implied \omit,e tc; so wrap in \hidden@align
DefMacro('\lx@eqnarray@label Semiverbatim',
  '\hidden@noalign{\lx@eqnarray@save@label{#1}}');

DefConstructor('\@@eqnarray SkipSpaces DigestedBody',
  '#1',
  beforeDigest   => sub { $_[0]->bgroup; },
  afterConstruct => sub { rearrangeEqnarray($_[0], $_[0]->getNode->lastChild); });
DefPrimitiveI('\end@eqnarray', undef, sub { $_[0]->egroup; });

# ========================================
# Some tools for analyzing the equationgroup after we've constructed it.
# ========================================

# ========================================
# For eqnarray, the "meaningful" unit will be at least one row,
# but often multiple rows.
# When the 1st column is empty, the row is assumed to continue the previous equation (if any!)
# When the 2nd column is also empty, it presumably continues the previous RHS.
# We'll combine these cases into a single equation, but remember the alignment structure.
# However, if more than 1 such row has refnums, we probably don't want to combine;
# But we really need to find a better way of representing the information!
# Note that there are common misuses of eqnarray;
# One type tries to get the equivalent of amsmath's gather environment by
# using a single column for the equations; the equations are right, centered or
# left aligned, depending on which column was used.
# Can we detect continuations, and can we distinguish continuations of equations vs. RHS?
# Probably a similar misuse where only 2 columns are used?
sub rearrangeEqnarray {
  my ($document, $equationgroup) = @_;
  # Scan the "equations" (rows) within the $equationgroup
  # to see what pattern of columns are present.
  my @rows = ();
  foreach my $rownode ($document->findnodes('ltx:equation', $equationgroup)) {
    my @cells = $document->findnodes('ltx:_Capture_', $rownode);    # representing each column.
    push(@rows, { node => $rownode, cols => [@cells],
        L        => ($cells[0] && $cells[0]->hasChildNodes),
        M        => ($cells[1] && $cells[1]->hasChildNodes),
        R        => ($cells[2] && $cells[2]->hasChildNodes),
        numbered => ($document->findnode('ltx:tags', $rownode) ? 1 : 0),
        labelled => $rownode->hasAttribute('label') }); }
  my $nL = scalar(grep { $$_{L} } @rows);
  my $nM = scalar(grep { $$_{M} } @rows);
  my $nR = scalar(grep { $$_{R} } @rows);

  # Only a single column was used.  Remove the empty ones.
  # A heuristic: if any rows begin with a relation, there are probably continuations,
  # but maybe we don't want to try to distinguish the kinds?
  if (($nL && !$nM && !$nR)    # All left column
    || (!$nL && $nM  && !$nR)      # All center column
    || (!$nL && !$nM && $nR)) {    # All right column
                                   # We REALLY should remove the empty columns entirely!
    my $keepcol = ($nL ? 0 : ($nM ? 1 : 2));
    # REMOVE empty columns!
    foreach my $c (2, 1, 0) {
      next if $c == $keepcol;
      foreach my $row (@rows) {
        $document->removeNode($$row{cols}[$c]);
        splice(@{ $$row{cols} }, $c, 1); } }
    # If (some) columns begin with a relation, presumably a single equation?
    # Or maybe we should only connect those to previous row?
    my $t;
    if (grep { ($t = $$_{cols}[0]) && ($t = $document->getFirstChildElement($t))
          && (($t->getAttribute('role') || '') eq 'RELOP') } @rows) {
      equationgroupJoinRows($document, $equationgroup, map { $$_{node} } @rows); }
    else {
      # Really, these shouldn't even end up with MathFork!
      foreach my $row (@rows) {
        equationgroupJoinCols($document, 1, $$row{node}); } }
    return; }
  # What about case where only TWO columns get used? Worth analyzing?
  # Remaining case is when all 3 columns are used.
  my @eqs      = ();
  my $numbered = 0;
  my $oddness  = 0;
  foreach my $row (@rows) {
    my $class = 'unknown';
    if ($$row{L}) {    # 1st column non-empty; Presumably a new "equation"
      $class = 'new'; }
    elsif ($$row{M}) {    # 1st is empty & 2nd non-empty (Rel?); Probably continues
      if (!scalar(@eqs)) {    # But if no previous equation?
        $class = 'odd'; }
      elsif ($numbered && $$row{numbered}) {    # Separately numbered continuation?
        $class = 'new'; }                       # Keep it as separate equation, even though missing LHS
      else {
        $class = 'continue'; } }                # Continues as multiequation
    elsif ($$row{R}) {                          # 1st & 2nd is empty, non empty 3rd
      if (!scalar(@eqs)) {                      # But if no previous equation?
        $class = 'odd'; }
      elsif ($numbered && $$row{numbered}       # Separately numbered AND labeled?
        && $$row{labelled}) {
        $class = 'odd'; }                       # must keep separate, but weird!
      else {
        $class = 'continue'; } }                # Else, continues RHS.
    else {                                      # All columns empty
          # Probably the trailling //, but who knows...
          # Arguably: if it has a number &/or is labelled
          # we should either prserve (as odd) or move info to previous line (if any)
      $class = 'remove'; }

    if ($class eq 'remove') {
      $document->removeNode($$row{node}); }
    elsif (($class eq 'new') || ($class eq 'odd')) {
      $numbered = $$row{numbered};
      push(@eqs, [$$row{node}]);
      $oddness++ if $class eq 'odd'; }
    else {
      $numbered |= $$row{numbered};
      push(@{ $eqs[-1] }, $$row{node}); }
  }

  Warn('unexpected', 'eqnarray', $equationgroup,
    "Unrecognized equation patterns ($oddness) in eqnarray")
    if (scalar(@rows) > 1) && $oddness;
  # Now rearrange things appropriately.
  foreach my $eqset (@eqs) {
    equationgroupJoinRows($document, $equationgroup, @$eqset); }
  return; }

# Style Parameters
#  \abovedisplayskip \abovedisplayshortskip, \jot are in TeX.pool
DefRegister('\mathindent' => Dimension(0));

#======================================================================
# C.7.2 Common Structures
#======================================================================
# sub, superscript and prime are in TeX.pool
# Underlying support in TeX.pool.ltxml
DefConstructor('\frac InFractionStyle InFractionStyle',
  "<ltx:XMApp>"
    . "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='#mathstyle'/>"
    . "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
    . "</ltx:XMApp>",
  sizer      => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); },
  properties => { mathstyle => sub { LookupValue('font')->getMathstyle; } });

# Ellipsis: See TeX.pool

#======================================================================
# C.7.3 Mathematical Symbols
#======================================================================
# See Tables 3.3 through 3.8 (pp 41--44)
# Defined in TeX.pool
# [Possibly some are strictly LaTeX and should be moved here?]

#======================================================================
# C.7.4 Arrays
#======================================================================
#  See Section C.10.2

#======================================================================-
# C.7.5 Delimiters
#======================================================================-
# All this is already in TeX.pool

DefMacro('\stackrel{}{}', '\lx@stackrel{{\scriptstyle #1}}{{#2}}');
DefConstructor('\lx@stackrel{}{}',
  "<ltx:XMApp role='RELOP'>"
    . "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
    . "<ltx:XMArg>#2</ltx:XMArg>"
    . "<ltx:XMArg>#1</ltx:XMArg>"
    . "</ltx:XMApp>",
  reversion  => '\stackrel{#1}{#2}',
  properties => { scriptpos => sub { "mid" . $_[0]->getScriptLevel; } }
);
#======================================================================-
# C.7.6 Putting One Thing Above Another
#======================================================================-
# All this is already in TeX.pool

#======================================================================-
# C.7.7 Spacing
#======================================================================-
# some of this is already in TeX.pool.  the rest was in amsmath, but is now native to LaTeX

DefConstructorI('\thinspace', undef,
  "?#isMath(<ltx:XMHint name='thinspace' width='#width'/>)(\x{2009})",
  properties => { isSpace => 1, width => sub { LookupRegister('\thinmuskip'); } });
DefConstructorI('\negthinspace', undef,
  "?#isMath(<ltx:XMHint name='negthinspace' width='#width'/>)()",
  properties => { isSpace => 1, width => sub { LookupRegister('\thinmuskip')->negate; } });
DefConstructorI('\medspace', undef,
  "?#isMath(<ltx:XMHint name='medspace' width='#width'/>)()",
  properties => { isSpace => 1, width => sub { LookupRegister('\medmuskip'); } });
DefConstructorI('\negmedspace', undef,
  "?#isMath(<ltx:XMHint name='negmedspace' width='#width'/>)()",
  properties => { isSpace => 1, width => sub { LookupRegister('\medmuskip')->negate; } });
DefConstructorI('\thickspace', undef,
  "?#isMath(<ltx:XMHint name='thickspace' width='#width'/>)(\x{2004})",
  properties => { isSpace => 1, width => sub { LookupRegister('\thickmuskip'); } });
DefConstructorI('\negthickspace', undef,
  "?#isMath(<ltx:XMHint name='negthickspace' width='#width'/>)(\x{2004})",
  properties => { isSpace => 1, width => sub { LookupRegister('\thickmuskip')->negate; } });

#======================================================================
# C.7.8 Changing Style
#======================================================================
# For Math style changes, we record the current font, which is then merged
# into the Whatsit's created for letters, etc.  The merging depends on
# the type of letter, greek, symbol, etc.
# Apparently, with the normal TeX setup, these fonts don't really merge,
# rather they override all of family, series and shape.
DefConstructor('\mathrm{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'serif', series => 'medium', shape => 'upright' });
DefConstructor('\mathit{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { shape => 'italic', family => 'serif', series => 'medium' });
DefConstructor('\mathbf{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { series => 'bold', family => 'serif', shape => 'upright' });
DefConstructor('\mathsf{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'sansserif', series => 'medium', shape => 'upright' });
DefConstructor('\mathtt{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'typewriter', series => 'medium', shape => 'upright' });
DefConstructor('\mathcal{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'caligraphic', series => 'medium', shape => 'upright' });
DefConstructor('\mathscr{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'script', series => 'medium', shape => 'upright' });
DefConstructor('\mathnormal{}', '#1', bounded => 1, requireMath => 1, locked => 1,
  font => { family => 'math', shape => 'italic', series => 'medium' });

DefMacroI('\fontsubfuzz',  undef, '.4pt');
DefMacroI('\oldstylenums', undef, Tokens());

DefPrimitiveI('\operator@font', undef, undef,
  font => { family => 'serif', series => 'medium', shape => 'upright' });

#**********************************************************************
# C.8 Definitions, Numbering and Programming
#**********************************************************************

#======================================================================
# C.8.1 Defining Commands
#======================================================================

DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname');

DefPrimitive('\newcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    if (!isDefinable($cs)) {
      Info('ignore', $cs, $stomach,
        "Ignoring redefinition (\\newcommand) of '" . ToString($cs) . "'")
        unless LookupValue(ToString($cs) . ':locked');
      return; }
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

DefPrimitive('\CheckCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', undef);

DefPrimitive('\renewcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

# low-level implementation of both \newcommand and \renewcommand depends on \@argdef
# and robustness upgrades are often realized via redefining \l@ngrel@x
DefPrimitive('\@argdef DefToken []{}', sub {
    DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });
DefPrimitive('\@xargdef DefToken [][]{}', sub {
    DefMacroI($_[1], convertLaTeXArgs($_[2], $_[3]), $_[4]); });
DefPrimitive('\@yargdef DefToken DefToken {}{}', sub {
    DefMacroI($_[1],
      (T_CS('\tw@')->equals($_[2])
        ? convertLaTeXArgs($_[3], Tokens())
        : convertLaTeXArgs($_[3])),
      $_[4]); });
DefPrimitive('\@reargdef DefToken []{}', sub {
    DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });

DefPrimitive('\providecommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    return unless isDefinable($cs);
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });

# Crazy; define \cs in terms of \cs[space] !!!
DefPrimitive('\DeclareRobustCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body, robust => 1); });
DefPrimitive('\MakeRobust DefToken', sub {
    my ($stomach, $cs) = @_;
    my $mungedcs = T_CS($cs->getString . ' ');
    if    (!LookupDefinition($cs))      { }    # Not defined
    elsif (LookupDefinition($mungedcs)) { }    # Already robust
    else {
      Let($mungedcs, $cs);
      DefMacroI($cs, undef, Tokens(T_CS('\protect'), $mungedcs)); } });

# There are a bunch of ways in LaTeX to assign a command to
# a particular point within a font (which has one of many encodings)
# Since we have no practical way of knowing what that point is,
# and we really want to create unicode, we just ignore this stuff (but warn).
sub ignoredDefinition {
  my ($stomach, $command, $cs) = @_;
  #  Warn('ignore',$cs,$stomach,"ignoring ".ToString($command)." definition of ".ToString($cs));
  return; }

#------------------------------------------------------------
# The following commands define encoding-specific expansions
# or glyphs.  The control-sequence is defined to use the expansion for
# the current encoding, if any, or the default expansion (for encoding "?").
# We don't want to redefine control-sequence if it already has a definition:
# It may be that we've already defined it to expand into the above conditional.
# But more importantly, we don't want to override a hand-written definition (if any).
#------------------------------------------------------------
DefPrimitive('\DeclareTextCommand DefToken {}[Number][]{}', sub {
    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (!IsDefined($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion);
    return; });

DefMacro('\DeclareTextCommandDefault DefToken', '\DeclareTextCommand{#1}{?}');

DefPrimitive('\ProvideTextCommand DefToken {}[Number][]{}', sub {
    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (isDefinable($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    if (!IsDefined($ecs)) {    # If not already defined...
      DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); }
    return; });

DefMacro('\ProvideTextCommandDefault DefToken', '\ProvideTextCommand{#1}{?}');

#------------------------------------------------------------

DefPrimitive('\DeclareTextSymbol DefToken {}{Number}', sub {
    my ($gullet, $cs, $encoding, $code) = @_;
    $code = $code->valueOf;
    my $css = ToString($cs);
    $encoding = ToString(Expand($encoding));
    if (isDefinable($cs)) {    # If not already defined...
      DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
    my $ecs = T_CS('\\' . $encoding . $css);
    DefPrimitiveI($ecs, undef, FontDecode($code, $encoding));
    return; });

# hmmm... what needs doing here; basically it means use this encoding as the default for the symbol
DefMacro('\DeclareTextSymbolDefault DefToken {}', '');    # '\DeclareTextSymbol{#1}{?}');

#------------------------------------------------------------
DefPrimitive('\DeclareTextAccent DefToken {}{}', sub {
    ignoredDefinition('DeclareTextAccent', $_[1]); });
DefPrimitive('\DeclareTextAccentDefault{}{}',
  sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); });

#------------------------------------------------------------
# TODO: Need to convert these DefPrimitive's into DefConstroctors
# that add a preable PI!!!!!!!!!

DefPrimitive('\DeclareTextComposite{}{}{}{}',
  sub { ignoredDefinition('DeclareTextComposite', $_[1]); });
DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}',
  sub { ignoredDefinition('DeclareTextCompositeCommand', $_[1]); });

DefPrimitive('\UndeclareTextCommand{}{}', undef);
DefMacro('\UseTextSymbol{}{}', '{\fontencoding{#1}#2}');
DefMacro('\UseTextAccent{}{}', '{\fontencoding{#1}#2{#3}}');

DefPrimitive('\DeclareMathAccent DefToken {}{} {Number}', sub {
    my ($stomach, $cs, $kind, $class, $code) = @_;
    $class = ToString($class);
    my $info  = LookupValue('fontdeclaration@' . $class);
    my $glyph = FontDecode($code->valueOf, ($info ? $$info{encoding} : $class));
    DefMathI($cs, 'Digested', $glyph, operator_role => 'OVERACCENT');
    return AddToPreamble('\DeclareMathAccent', $cs, $kind, $class, $code); });

DefPrimitive('\DeclareMathDelimiter{}{}{}{}',
  sub { ignoredDefinition('DeclareMathDelimiter', $_[1]); });
DefPrimitive('\DeclareMathRadical{}{}{}{}{}',
  sub { ignoredDefinition('DeclareMathRadical', $_[1]); });
DefPrimitive('\DeclareMathVersion{}',          undef);
DefPrimitive('\DeclarePreloadSizes{}{}{}{}{}', undef);

# The next font declaration commands are based on
# http://tex.loria.fr/general/new/fntguide.html
# we ignore font encoding
DefPrimitive('\DeclareSymbolFont{}{}{}{}{}', sub {
    my ($stomach, $name, $enc, $family, $series, $shape) = @_;
    AssignValue('fontdeclaration@' . ToString($name),
      { family => ToString($family),
        series   => ToString($series),
        shape    => ToString($shape),
        encoding => ToString($enc) }); });
DefPrimitive('\DeclareSymbolFontAlphabet{}{}', sub {
    my ($stomach, $cs, $name) = @_;
    my $font = LookupValue('fontdeclarations@' . ToString($name)) || {};
    DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => $font); });

DefPrimitive('\DeclareMathSizes{}{}{}{}', undef);
DefPrimitive('\DeclareMathAlphabet{}{}{}{}{}', sub {
    my ($stomach, $cs, $enc, $family, $series, $shape) = @_;
    my $csname = ToString($cs);
    # We won't override this, e.g. \mathrm by fouriernc.sty
    if (IsDefined($csname)) {
      Info('ignore', $csname, $stomach,
        "Ignoring redefinition (\\DeclareMathAlphabet) of '" . $csname . "'"); }
    else {
      my %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape);
      DefPrimitiveI(T_CS($csname), undef, undef, font => {%font}); }
    return; });

DefMacro('\newmathalphabet{}{}{}', Tokens());    # or expand int DeclareMathAlphabet?
DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareFontFamily{}{}{}',      undef);
DefPrimitive('\DeclareSizeFunction{}{}',      undef);

my $symboltype_roles = {
  '\mathord'  => 'ID',   '\mathop'    => 'BIGOP', '\mathbin'   => 'BINOP', '\mathrel' => 'RELOP',
  '\mathopen' => 'OPEN', '\mathclose' => 'CLOSE', '\mathpunct' => 'PUNCT' };
DefPrimitive('\DeclareMathSymbol DefToken SkipSpaces DefToken {}{Number}', sub {
    my ($stomach, $cs, $type, $font, $code) = @_;
    my $encoding = ToString($font);    # Or maybe just a font name or class?
    if (my $decl = LookupValue('fontdeclaration@' . $encoding)) {
      $encoding = $$decl{encoding} if $$decl{encoding}; }
    my $glyph = FontDecode($code->valueOf, $encoding);
    my $role  = $$symboltype_roles{ ToString($type) };
    DefMathI($cs, undef, $glyph, role => $role);
    return; });

DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', sub { DefMacroI($_[1], undef, T_CS('\relax')); return; });
DefPrimitive('\DeclareErrorFont{}{}{}{}{}', sub { DefMacroI($_[1], undef, T_CS('\relax')); return; });

DefMacroI('\cdp@list', undef, '\@empty');
Let('\cdp@elt', '\relax');
DefPrimitive('\DeclareFontEncoding{}{}{}', sub {
    my ($stomach, $encoding, $x, $y) = @_;
    AddToMacro(T_CS('\cdp@list'), T_CS('\cdp@elt'),
      T_BEGIN, $_[1]->unlist,           T_END,
      T_BEGIN, T_CS('\default@family'), T_END,
      T_BEGIN, T_CS('\default@series'), T_END,
      T_BEGIN, T_CS('\default@shape'),  T_END);
    my $encoding_expanded = Expand($encoding);
    my $encoding_str      = ToString($encoding_expanded);
    DefMacroI('\LastDeclaredEncoding', undef, $encoding_expanded);
    DefMacroI('\T@' . $encoding_str,   undef, $x);
    DefMacroI('\M@' . $encoding_str,   undef, Tokens(T_CS('\default@M'), $y->unlist));
    if (my $path = $encoding_str && FindFile(lc($encoding_str) . "enc", type => "dfu")) {
      InputDefinitions($path);
    }
    return;
});
DefMacroI('\LastDeclaredEncoding', undef, '');
DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef);
DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef);
DefPrimitive('\DeclareEncodingSubset{}{}{}',     undef);
DefMacroI('\LastDeclaredEncoding', undef, Tokens());

DefPrimitive('\SetSymbolFont{}{}{}{}{}{}',   undef);
DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef);
DefPrimitive('\addtoversion{}{}',            undef);
DefPrimitive('\TextSymbolUnavailable{}',     undef);

RawTeX(<<'EoTeX');
\DeclareSymbolFont{operators}   {OT1}{cmr} {m}{n}
\DeclareSymbolFont{letters}     {OML}{cmm} {m}{it}
\DeclareSymbolFont{symbols}     {OMS}{cmsy}{m}{n}
\DeclareSymbolFont{largesymbols}{OMX}{cmex}{m}{n}
EoTeX

# not sure what these should be, related to \nullfont...
DefPrimitiveI('\OMX',      undef, undef, font => { family => 'cmex10' });
DefPrimitiveI('\tenln',    undef, undef, font => { family => 'line10' });
DefPrimitiveI('\tenlnw',   undef, undef, font => { family => 'linew10' });
DefPrimitiveI('\tencirc',  undef, undef, font => { family => 'lcircle10' });
DefPrimitiveI('\tencircw', undef, undef, font => { family => 'lcirclew10' });

# At least all things on uclclist need to be macros
DefPrimitiveI('\lx@utf@OE', undef, "\x{0152}", alias => '\OE');    # LATIN CAPITAL LIGATURE OE
DefPrimitiveI('\lx@utf@oe', undef, "\x{0153}", alias => '\oe');    # LATIN SMALL LIGATURE OE
DefPrimitiveI('\lx@utf@AE', undef, UTF(0xC6),  alias => '\AE');    # LATIN CAPITAL LETTER AE
DefPrimitiveI('\lx@utf@ae', undef, UTF(0xE6),  alias => '\ae');    # LATIN SMALL LETTER AE
DefPrimitiveI('\lx@utf@AA', undef, UTF(0xC5), alias => '\AA'); # LATIN CAPITAL LETTER A WITH RING ABOVE
DefPrimitiveI('\lx@utf@aa', undef, UTF(0xE5), alias => '\aa'); # LATIN SMALL LETTER A WITH RING ABOVE
DefPrimitiveI('\lx@utf@O',  undef, UTF(0xD8),  alias => '\O');  # LATIN CAPITAL LETTER O WITH STROKE
DefPrimitiveI('\lx@utf@o',  undef, UTF(0xF8),  alias => '\o');  # LATIN SMALL LETTER O WITH STROKE
DefPrimitiveI('\lx@utf@L',  undef, "\x{0141}", alias => '\L');  # LATIN CAPITAL LETTER L WITH STROKE
DefPrimitiveI('\lx@utf@l',  undef, "\x{0142}", alias => '\l');  # LATIN SMALL LETTER L WITH STROKE
DefPrimitiveI('\lx@utf@ss', undef, UTF(0xDF),  alias => '\ss'); # LATIN SMALL LETTER SHARP S
DefPrimitiveI('\lx@utf@dh', undef, UTF(0xf0),  alias => '\dh'); # eth
DefPrimitiveI('\lx@utf@DH', undef, UTF(0xd0),  alias => '\DH'); # Eth (looks same as \DJ!)
DefPrimitiveI('\lx@utf@dj', undef, "\x{0111}", alias => '\dj'); # d with stroke
DefPrimitiveI('\lx@utf@DJ', undef, "\x{0110}", alias => '\DJ'); # D with stroke (looks sames as \DH!)
DefPrimitiveI('\lx@utf@ng', undef, "\x{014B}", alias => '\ng');
DefPrimitiveI('\lx@utf@NG', undef, "\x{014A}", alias => '\NG');
DefPrimitiveI('\lx@utf@th', undef, UTF(0xFE),  alias => '\th');
DefPrimitiveI('\lx@utf@TH', undef, UTF(0xDE),  alias => '\TH');

DefMacroI('\OE', undef, '\lx@utf@OE');
DefMacroI('\oe', undef, '\lx@utf@oe');
DefMacroI('\AE', undef, '\lx@utf@AE');
DefMacroI('\ae', undef, '\lx@utf@ae');
DefMacroI('\ae', undef, '\lx@utf@ae');
DefMacroI('\AA', undef, '\lx@utf@AA');
DefMacroI('\aa', undef, '\lx@utf@aa');
DefMacroI('\O',  undef, '\lx@utf@O');
DefMacroI('\o',  undef, '\lx@utf@o');
DefMacroI('\L',  undef, '\lx@utf@L');
DefMacroI('\l',  undef, '\lx@utf@l');
DefMacroI('\ss', undef, '\lx@utf@ss');
DefMacroI('\dh', undef, '\lx@utf@dh');    # in latex?
DefMacroI('\DH', undef, '\lx@utf@DH');
DefMacroI('\dj', undef, '\lx@utf@dj');
DefMacroI('\DJ', undef, '\lx@utf@DJ');
DefMacroI('\ng', undef, '\lx@utf@ng');
DefMacroI('\NG', undef, '\lx@utf@NG');
DefMacroI('\th', undef, '\lx@utf@th');
DefMacroI('\TH', undef, '\lx@utf@TH');

#======================================================================
# C.8.2 Defining Environments
#======================================================================
# Note that \env & \endenv defined by \newenvironment CAN be
# invoked directly.

DefPrimitive('\newenvironment OptionalMatch:* {}[Number][]{}{}', sub {
    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
    $name = ToString(Expand($name));
    if (IsDefined(T_CS("\\$name"))) {
      Info('ignore', $name, $stomach,
        "Ignoring redefinition (\\newenvironment) of Environment '$name'")
        unless (LookupValue('\\' . $name . ':locked')
        || LookupValue('\\begin{' . $name . '}:locked'));
      return; }
    DefMacroI(T_CS("\\$name"),    convertLaTeXArgs($nargs, $opt), $begin);
    DefMacroI(T_CS("\\end$name"), undef,                          $end);
    return; });

DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub {
    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
    $name = ToString(Expand($name));
    if (!LookupValue("\\$name:locked")
      && !LookupValue("\\begin{$name}:locked")) {
      DefMacroI(T_CS("\\$name"),    convertLaTeXArgs($nargs, $opt), $begin);
      DefMacroI(T_CS("\\end$name"), undef,                          $end); }
    return; });

#======================================================================
# C.8.3 Theorem-like Environments
#======================================================================
# The core non-customizable part is defined here.
# For customizable theorems, see amsthm.
AssignValue('thm@swap' => 0);
DefRegister('\thm@style'         => Tokenize('plain'));
DefRegister('\thm@headfont'      => Tokens(T_CS('\bfseries')));
DefRegister('\thm@notefont'      => Tokens(T_CS('\the'), T_CS('\thm@headfont')));
DefRegister('\thm@bodyfont'      => Tokens(T_CS('\itshape')));
DefRegister('\thm@headformatter' => Tokens());
DefRegister('\thm@headpunct'     => Tokens());
DefRegister('\thm@styling'       => Tokens());
DefRegister('\thm@headstyling'   => Tokens());
DefRegister('\thm@prework'       => Tokens());
DefRegister('\thm@postwork'      => Tokens());
DefRegister('\thm@symbol'        => Tokens());
DefRegister('\thm@numbering'     => Tokens(T_CS('\arabic')));

DefPrimitive('\th@plain', sub {
    AssignRegister('\thm@bodyfont'    => T_CS('\itshape'));
    AssignRegister('\thm@headstyling' => T_CS('\lx@makerunin'));
    return; });

DefMacroI('\lx@makerunin',   undef, '\@ADDCLASS{ltx_runin}');
DefMacroI('\lx@makeoutdent', undef, '\@ADDCLASS{ltx_outdent}');

DefMacroI('\@thmcountersep', undef, '.');
DefMacroI('\thm@doendmark',  undef, '');

DefPrimitive('\newtheorem OptionalMatch:* {}[]{}[]', sub {
    my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
    defineNewTheorem($stomach, $flag, $thmset, $otherthmset, $type, $reset);
    # Reset these!
    DefRegister('\thm@prework'  => Tokens());
    DefRegister('\thm@postwork' => Tokens()); },
  locked => 1);

# These record which style parameters CAN BE saved/restored for each style.
# (kinda weird, but different packages use different sets; particularly \thm@headfont)
#  THEOREM_<theoremname>_PARAMETERS will store the actual values for those parameters
AssignValue('SAVABLE_THEOREM_PARAMETERS' => {});

sub setSavableTheoremParameters {
  my (@keys) = @_;    # Noe that these are saved for each theorem type.
  AssignValue('SAVABLE_THEOREM_PARAMETERS' => { map { $_ => 1; } @keys });
  return; }

# Following amsthm's model, the various theorem customizations are stored in token registers
# Some packages may want to add '\thm@headfont' (or exclude it)
setSavableTheoremParameters(qw(
    \thm@bodyfont \thm@headpunct \thm@styling \thm@headstyling thm@swap));

sub useTheoremStyle {
  my ($name) = @_;
  my $savable = LookupValue('SAVABLE_THEOREM_PARAMETERS');
  if (my $params = LookupValue('THEOREM_' . ToString($name) . '_PARAMETERS')) {
    for my $param_key (keys %$savable) {
      if (exists $$params{$param_key}) {
        if ($param_key =~ /^\\/) {
          AssignRegister($param_key => $$params{$param_key}); }
        else {
          AssignValue($param_key => $$params{$param_key}); } } } }
  return; }

sub saveTheoremStyle {
  my ($name, %parameters) = @_;
  $name = ToString($name);
  AssignValue('THEOREM_' . $name . '_PARAMETERS' => {%parameters}, 'global');
  return; }

RawTeX('\th@plain');    # Activate the default style.
# [or just make it's values the defaults...]

Tag('ltx:theorem', autoClose => 1);
Tag('ltx:proof',   autoClose => 1);

sub defineNewTheorem {
  my ($stomach, $flag, $thmset, $otherthmset, $type, $within) = @_;
  $thmset = ToString($thmset);
  my $classname = CleanClassName($thmset);
  my $listname  = "theorem:$thmset";
  # Do we need a Cleaner for constructor bits?
  $listname =~ s/\s+//g;
  $listname =~ s/'/prime/g;
  $listname =~ s/\?/question/g;
  $listname =~ s/\#/hash/g;
  $otherthmset = $otherthmset && ToString($otherthmset);
  $type        = undef if IsEmpty($type);
  $within      = $within ? ToString(Digest($within)) : undef;

  my $counter = $otherthmset || $thmset;
  $counter =~ s/\s+/./;    # convert spaces to period?
  if ($counter ne $thmset) {
    AssignMapping('counter_for_type', $thmset => $counter);
    DefMacroI(T_CS('\the' . $thmset), undef, T_CS('\the' . $counter), scope => 'global');
  }
  my $style     = ToString(LookupRegister('\thm@style'));        # The name of the style
  my $numbering = ToString(LookupRegister('\thm@numbering'));    # The number style macro
  $flag = 1 unless $numbering;
  if (!$otherthmset) {
    my $idprefix = "Thm$classname";
    $idprefix =~ s/\*/./g;

    if (!LookupDefinition(T_CS('\c@' . $counter))) {             # if isn't already defined...
      NewCounter($counter, $within, idprefix => $idprefix); }
    DefMacroI(T_CS("\\the$counter"), undef,
      ($within
        ? "\\csname the" . $within . "\\endcsname\\\@thmcountersep" . $numbering . "{" . $counter . "}"
        : $numbering . "{" . $counter . "}"),
      scope => 'global') if $numbering; }

  my %parameters = %{ LookupValue('SAVABLE_THEOREM_PARAMETERS') };
  my %saved      = ();
  foreach my $key (keys %parameters) {
    $saved{$key} = ($key =~ /^\\/ ? LookupRegister($key) : LookupValue($key)); }
  saveTheoremStyle($thmset, %saved);

  my $thmname = '\\lx@name@' . $thmset;
  DefMacroI($thmname, undef, $type, scope => 'global');
  my $swap = LookupValue('thm@swap');

  DefMacroI('\fnum@' . $thmset, undef,
    Tokens($flag || !$counter
      ? (T_CS($thmname))
      : ($swap
        ? (T_CS('\the' . $counter), ($type ? (T_SPACE) : ()), T_CS($thmname))
        : (T_CS($thmname), ($type ? (T_SPACE) : ()), T_CS('\the' . $counter)))),
    scope => 'global');
  # amsthm allows you to define your own title formatter
  # if there was one defined, use it, else define a new one.
  my $headformatter = LookupRegister('\thm@headformatter');
  DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0),
    (!IsEmpty($headformatter)
      ? Tokens(
        T_CS('\the'), T_CS('\thm@headfont'),
        $headformatter->unlist,
        T_BEGIN, ($type ? $type->unlist : ()), T_END,
        T_CS('\the' . $counter), T_BEGIN, Tokens(T_PARAM, T_OTHER('1')), T_END,
        T_CS('\the'),            T_CS('\thm@headpunct'))
      : '{\the\thm@headfont\lx@tag{\csname fnum@' . $thmset . '\endcsname}'
        . '{' . ($type ? '\ifx.#1.\else\space\the\thm@notefont(#1)\fi' : '#1') . '}'
        . '\the\thm@headpunct}'),

    scope => 'global');

  DefEnvironmentI($thmset, "OptionalUndigested",
    "<ltx:theorem xml:id='#id' inlist='thm $listname' class='ltx_theorem_$classname'>"
      . "#tags"
      . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
      . "#body",
    afterConstruct => sub { $_[0]->maybeCloseElement('ltx:theorem'); },
    beforeDigest   => sub {
      useTheoremStyle($thmset);
      Digest('\normalfont\the\thm@prework'); },
    afterDigestBegin => sub {
      my $name = $_[1]->getArg(1);
      Digest('\the\thm@bodyfont\the\thm@styling'
          . '\def\lx@thistheorem{' . ($name ? UnTeX($name) : '') . '}'); },
    beforeDigestEnd => sub { Digest('\thm@doendmark\the\thm@postwork'); },
    properties      => sub {
      my %ctr = ();
      if ($counter) {
        if ($flag) {
          %ctr = RefStepID($counter);
          # Need(?) the refnum
          if ($type) {
            $ctr{tags} = Digest(T_BEGIN,
              T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'),
              Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)),
              T_END);
        } }
        else {
          %ctr = RefStepCounter($thmset); } }
      my $title = Digest(Tokens(T_BEGIN,
          T_CS('\the'), T_CS('\thm@headstyling'),
          T_CS('\format@title@' . $thmset),    #T_END,
          T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END,
          T_END));
      (%ctr,
        title     => $title,
        titlefont => $title->getFont,
      ); },
    scope => 'global');
  return; }

#======================================================================
# C.8.4 Numbering
#======================================================================
# For LaTeX documents, We want id's on para, as well as sectional units.
# However, para get created implicitly on Document construction, rather than
# explicitly during digestion (via a whatsit), we can't use the usual LaTeX counter mechanism.
Tag('ltx:para', afterOpen => sub { GenerateID(@_, 'p'); });

DefPrimitive('\newcounter{}[]', sub {
    NewCounter(ToString(Expand($_[1])), $_[2] && ToString(Expand($_[2])));
    return; });
Let(T_CS('\@definecounter'), T_CS('\newcounter'));
DefPrimitive('\setcounter{}{Number}',   sub { SetCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\addtocounter{}{Number}', sub { AddToCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\stepcounter{}',          sub { StepCounter(ToString(Expand($_[1])));    return; });
DefPrimitive('\refstepcounter{}',       sub { RefStepCounter(ToString(Expand($_[1]))); return; });

sub addtoCounterReset {
  my ($ctr, $within) = @_;
  my $reg       = "\\cl\@$within";
  my $t         = T_CS($ctr);
  my $unt       = T_CS('UN' . $ctr);
  my $prevreset = LookupValue($reg);
  AssignValue($reg => Tokens($t, $unt, ($prevreset ? $prevreset->unlist : ())), 'global');
  return; }

sub remfromCounterReset {
  my ($ctr, $within) = @_;
  my $reg       = "\\cl\@$within";
  my $t         = T_CS($ctr);
  my $unt       = T_CS('UN' . $ctr);
  my $prevreset = LookupValue($reg);
  AssignValue($reg => Tokens(grep { !$t->equals($_) && !$unt->equals($_) } $prevreset->unlist),
    'global') if $prevreset;
  return; }

sub defCounterID {
  my ($ctr, $within) = @_;
  my $prefix = LookupValue('@ID@prefix@' . $ctr);
  if (defined $prefix) {
    if ($within) {
      DefMacroI(T_CS("\\the$ctr\@ID"), undef,
        "\\expandafter\\ifx\\csname the$within\@ID\\endcsname\\\@empty"
          . "\\else\\csname the$within\@ID\\endcsname.\\fi"
          . " $prefix\\csname \@$ctr\@ID\\endcsname",
        scope => 'global'); }
    else {
      DefMacroI(T_CS("\\the$ctr\@ID"), undef, "$prefix\\csname \@$ctr\@ID\\endcsname",
        scope => 'global'); } }
  return; }

DefPrimitive('\@addtoreset{}{}', sub {
    my ($stomach, $ctr, $within) = @_;
    $ctr    = ToString(Expand($ctr));
    $within = ToString(Expand($within));
    addtoCounterReset($ctr, $within);
    defCounterID($ctr, $within) unless LookupDefinition(T_CS("\\the$ctr\@ID"));
    return; });

DefMacro('\value{}', sub {
    T_CS("\\c@" . ToString(Expand($_[1]))) });
DefMacro('\@arabic{Number}', sub {
    ExplodeText(ToString($_[1]->valueOf)); });
DefMacro('\arabic{}', sub {
    ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
DefMacro('\@roman{Number}', sub {
    ExplodeText(radix_roman(ToString($_[1]->valueOf))); });
DefMacro('\roman{}', sub {
    ExplodeText(radix_roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Roman{Number}', sub {
    ExplodeText(radix_Roman(ToString($_[1]->valueOf))); });
DefMacro('\Roman{}', sub {
    ExplodeText(radix_Roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@alph{Number}', sub {
    ExplodeText(radix_alpha($_[1]->valueOf)); });
DefMacro('\alph{}', sub {
    ExplodeText(radix_alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Alph{Number}', sub {
    ExplodeText(radix_Alpha($_[1]->valueOf)); });
DefMacro('\Alph{}', sub {
    ExplodeText(radix_Alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });

our @fnsymbols = ("*", "\x{2020}", "\x{2021}", UTF(0xA7), UTF(0xB6),
  "\x{2225}", "**", "\x{2020}\x{2020}", "\x{2021}\x{2021}");
DefMacro('\@fnsymbol{Number}', sub {
    ExplodeText(radix_format($_[1]->valueOf, @fnsymbols)); });
DefMacro('\fnsymbol{}', sub {
    ExplodeText(radix_format(CounterValue(ToString(Expand($_[1])))->valueOf, @fnsymbols)); });

# The following two macros were originally defined in chngcntr.sty,
# but were moved to LaTeX core in 2018.
DefPrimitive('\counterwithin OptionalMatch:* {}{}', sub {
    my ($stomach, $starred, $ctr, $within) = @_;
    $ctr    = ToString($ctr);
    $within = ToString($within);
    addtoCounterReset($ctr, $within);
    if (!$starred) {
      DefMacroI(T_CS("\\the$ctr"), undef, "\\the$within.\\arabic{$ctr}", scope => 'global');
      defCounterID($ctr, $within); }
    return; });

DefPrimitive('\counterwithout OptionalMatch:* {}{}', sub {
    my ($stomach, $starred, $ctr, $within) = @_;
    $ctr    = ToString($ctr);
    $within = ToString($within);
    remfromCounterReset($ctr, $within);
    if (!$starred) {
      DefMacroI(T_CS("\\the$ctr"), undef, "\\arabic{$ctr}", scope => 'global');
      defCounterID($ctr); }
    return; });

DefPrimitive('\@removefromreset{}{}', sub {
    my ($stomach, $ctr, $within) = @_;
    $ctr    = ToString($ctr);
    $within = ToString($within);
    remfromCounterReset($ctr, $within);
    # Should \the$ctr@ID be redefined?  It really should track changes to \the$ctr!
    # defCounterID($ctr);
    return; });

DefMacroI('\cl@@ckpt', undef, '\@elt{page}');
#======================================================================
# C.8.5 The ifthen Package.
#======================================================================
# \ifthenelse
# and sundry conditionals...
#
# Yeah, maybe this'll get done someday....

#**********************************************************************
# C.9 Figures and Other Floating Bodies
#**********************************************************************

#======================================================================
# C.9.1 Figures and Tables
#======================================================================

# Note that, the number is associated with the caption.
# (to allow multiple figures per figure environment?).
# Whatever reason, that causes complications: We can only increment
# counters with the caption, but then have to arrange for the counters,
# refnums, ids, get passed on to the figure, table when needed.
# AND, as soon as possible, since other items may base their id's on the id of the table!

# Let the fonts for float be the default for all floats, figures, tables, etc.
DefMacro('\fnum@font@float',         '\@empty');
DefMacro('\format@title@font@float', '\@empty');

DefMacro('\fnum@font@figure',         '\fnum@font@float');
DefMacro('\fnum@font@table',          '\fnum@font@float');
DefMacro('\format@title@font@figure', '\format@title@font@float');
DefMacro('\format@title@font@table',  '\format@title@font@float');

# stubs for the latex float internals
DefEnvironmentI('@float', '[]{}',
  "<ltx:float xml:id='#id' inlist='#inlist' ?#1(placement='#1') class='ltx_float_#2'>"
    . "#body"
    . "</ltx:float>",
  properties        => { layout => 'vertical' },
  beforeDigestBegin => sub { beforeFloat(ToString($_[1]->getArg(2))); },
  afterDigest       => sub { afterFloat($_[1]); });
DefEnvironmentI('@dblfloat', '[]{}',
  "<ltx:float xml:id='#id' inlist='#inlist' ?#1(placement='#1') class='ltx_float_#2'>"
    . "#body"
    . "</ltx:float>",
  properties        => { layout => 'vertical' },
  beforeDigestBegin => sub { beforeFloat(ToString($_[1]->getArg(2)), double => 1); },
  afterDigest       => sub { afterFloat($_[1]); });

# Could perhaps parameterize further with a separator?
DefMacro('\format@title@figure{}', '\lx@tag[][: ]{\lx@fnum@@{figure}}#1');
DefMacro('\format@title@table{}',  '\lx@tag[][: ]{\lx@fnum@@{table}}#1');

DefMacro('\ext@figure', 'lof');
DefMacro('\ext@table',  'lot');

DefConditional('\iflx@donecaption');
DefMacro('\caption',
'\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}');
# First, check for trailing \label, move it into the caption as a standard position
# NOTE: If one day we want to unlock \@caption, make sure to test against arXiv:cond-mat/0001395 for a passing build.
DefMacro('\@caption{}[]{}',
  '\@ifnext\label{\@caption@postlabel{#1}{#2}{#3}}{\@caption@{#1}{#2}{#3}}', locked => 1);

DefMacro('\@caption@postlabel{}{}{} SkipMatch:\label Semiverbatim',
  '\@caption@{#1}{#2}{#3\label{#4}}');

# Now, check for \label(s) inside to (potentially) record them.
DefMacro('\@caption@{}{}{}',
  '\@hack@caption@{#1}{#2}{}#3\label\endcaption');
DefMacro('\@hack@caption@{}{}{} Until:\label Until:\endcaption',
  '\ifx.#5.\@caption@@@{#1}{#2}{#3#4}'    # No more labels
    . '\else\@@@hack@caption@{#1}{#2}{#3#4}#5\endcaption\fi');
DefMacro('\@@@hack@caption@{}{}{} Semiverbatim Until:\label Until:\endcaption',
  '\lx@note@caption@label{#4}' .
    '\@hack@caption@{#1}{#2}{#3\label{#4}#5}\label#6\endcaption');

DefPrimitive('\lx@note@caption@label{}', sub { MaybeNoteLabel($_[1]); });

DefMacro('\@caption@@@{}{}{}',
  '\@@add@caption@counters'
    . '\@@toccaption{\lx@format@toctitle@@{#1}{\ifx.#2.#3\else#2\fi}}'
    . '\@@caption{\lx@format@title@@{#1}{#3}}');

# Note that the counters only get incremented by \caption, NOT by \table, \figure, etc.
DefPrimitive('\@@add@caption@counters', sub {
    my $captype = ToString(Digest(T_CS('\@captype')));
    my $pre     = LookupValue('PREINCREMENTED_' . $captype);    # If pre-incremented by beforeFloat
    my %props   = ($pre ? %$pre : RefStepCounter($captype));
    my $inlist  = ToString(Digest(T_CS('\ext@' . $captype)));
    AssignValue('PREINCREMENTED_' . $captype => undef,        'global');
    AssignValue($captype . "_tags"           => $props{tags}, 'global');
    AssignValue($captype . "_id"             => $props{id},   'global');
    AssignValue($captype . "_inlist"         => $inlist,      'global'); });

sub RescueCaptionCounters {
  my ($captype, $whatsit) = @_;
  if (my $tags = LookupValue($captype . "_tags")) {
    AssignValue($captype . "_tags" => undef, 'global');
    $whatsit->setProperty(tags => $tags); }
  if (my $id = LookupValue($captype . "_id")) {
    AssignValue($captype . "_id" => undef, 'global');
    $whatsit->setProperty(id => $id); }
  if (my $inlist = LookupValue($captype . "_inlist")) {
    AssignValue($captype . "_inlist" => undef, 'global');
    $whatsit->setProperty(inlist => $inlist); }
  return; }

DefConstructor('\@@generic@caption[]{}', "<ltx:text class='ltx_caption'>#2</ltx:text>",
  beforeDigest => sub {
    Error('unexpected', '\caption', $_[0],
      "Use of \\caption outside any known float"); });

our $FIGURE_PANEL_CLASS = 'ltx_figure_panel';

# most block-level elements are expected horizontally standalone in a {figure}.
# (especially ones that directly match LaTeX {envs})
our %standalone_panel_names = map { $_ => 1 }
  qw(ltx:p ltx:listing ltx:math ltx:itemize ltx:enumerate ltx:quote ltx:theorem ltx:proof
  ltx:description ltx:equation ltx:equationgroup ltx:verbatim);

sub arrange_panels_and_breaks {
  my ($document, $node, $whatsit) = @_;
  my $float_width = ($whatsit->getProperty('floatwidth') || Dimension('345pt'))->valueOf();
  # Note that 0.0625x minimal panel width will naturally enforce a limit of max 16 panels per row.
  # except that we are currently under-sizing(?) some individual subfigures, so enforce max 32.
  my $min_panel_width = 0.03125 * $float_width;
  my $current_width   = 0;
  my @all_panels      = ();
  my @all_contents    = ();
  # row contents will bookkeep triples of [node, name(optional), width(optional)]
  my @row_contents      = ();
  my $panels_found      = 0;
  my $panel_break_names = LookupValue('figure_panel_break_names');
  if (!$panel_break_names) {
    # initialize schema-based tag names on first call for a given document;
    my $model = $STATE->getModel;
    $panel_break_names = { map { $_ => 1 } ('ltx:break',
        $model->getSchemaClassNames('Meta'),
        $model->getSchemaClassNames('SectionalFrontMatter'),
        $model->getSchemaClassNames('Caption')) };
    # what to do with footnotes? Move from Meta to Misc class?
    $$panel_break_names{'ltx:note'} = undef;
    AssignValue('figure_panel_break_names', $panel_break_names, 'global'); }
  for my $child (element_nodes($node)) {
    my $child_name = $document->getNodeQName($child);
    if ($$panel_break_names{$child_name}) {
      # reset bookkeeping on prior breaks, be they author-deposited or computed
      # (caption/metadata bits also flush the current line, but we avoid the explicit break element)
      $current_width = 0;
      push(@all_contents, map { $$_[0] } @row_contents);
      push(@all_contents, $child);
      @row_contents = (); }
    else {
      my $is_standalone = $standalone_panel_names{$child_name};
      if ($is_standalone && @row_contents) {
        # if standalone, finalize current row content, and add a break
        my $break = $document->insertElementBefore($child, 'break', 'class' => 'ltx_break');
        push(@all_contents, map { $$_[0] } @row_contents);
        push(@all_contents, $break);
        $current_width = 0;
        @row_contents  = (); }
      my $child_width = $document->getNodeBox($child)->getWidth->valueOf();
      if (@row_contents && ($current_width + $child_width > $float_width)) {
        # break when we exceed the line width, to keep close with the PDF arrangement
        my $break = $document->insertElementBefore($child, 'break', 'class' => 'ltx_break');
        push(@all_contents, map { $$_[0] } @row_contents);
        push(@all_contents, $break);
        $document->addClass($child, $FIGURE_PANEL_CLASS);
        @row_contents = ([$child, $child_name, $child_width]);
        push(@all_panels, $child);
        $current_width = $child_width; }
      else {
        # check if we can merge into the previous element
        if (my $prev_panel = pop(@row_contents)) {
          my $prev_node  = $$prev_panel[0];
          my $prev_width = $$prev_panel[2] || $document->getNodeBox($prev_node)->getWidth->valueOf();
          # Heuristic:
          # Do the widths indicate a large(8x) size discrepancy? Or a very small joint size?
          # If so, contain the two pieces in a single panel.
          my $prev_name = $$prev_panel[1] || $document->getNodeQName($prev_node);
          if (($prev_width > 0 && $child_width > 0 &&
              (max($prev_width, $child_width) / min($prev_width, $child_width) > 8)) ||
            ($prev_width + $child_width < $min_panel_width)) {
            # avoid markup complexity - any time we do a merge, require a ltx:block marked as a panel.
            # if one exists, reuse, otherwise create a wrapping one.
            #my $prev_name = $$prev_panel[1] || $document->getNodeQName($prev_node);
            if ($prev_name eq 'ltx:block') {
              $child->unbindNode;
              $prev_node->appendChild($child);
              push(@row_contents, [$prev_node, $prev_name, $prev_width + $child_width]);
            } elsif ($child_name eq 'ltx:block') {
              $prev_node->unbindNode;
              $child->appendChild($prev_node);
              push(@row_contents, [$child, $child_name, $prev_width + $child_width]);
              push(@all_panels,   $child);
            } else {    # create a new block to hold the two siblings.
              my $block = $document->wrapNodes('ltx:block', $prev_node, $child);
              push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]);
              pop(@all_panels); push(@all_panels, $block);
          } }
          else {
            # otherwise keep the last panel as-is, and append a sibling
            push(@row_contents, $prev_panel);
            push(@row_contents, [$child, $child_name, $child_width]);
            push(@all_panels,   $child); } }
        else {    # no last panel? just add the child
          push(@row_contents, [$child, $child_name, $child_width]);
          push(@all_panels,   $child);
          # yet if standalone, finalize current row content, and add a break
          if ($is_standalone && @row_contents) {
            push(@all_contents, map { $$_[0] } @row_contents);
            if (my $trailer = $child->nextSibling) {
              if (!$$panel_break_names{ $document->getNodeQName($trailer) }) {
                my $break = $document->insertElementBefore($trailer, 'break', 'class' => 'ltx_break');
                push(@all_contents, $break); } }
            @row_contents  = ();
            $current_width = 0; } }
        $current_width += $child_width; } } }
  push(@all_contents, map { $$_[0] } @row_contents);
  # simplify: if we only ever added 1 panel, remove its class (so that we only mark complex figures)
  if (scalar(@all_panels) > 1) {
    for my $panel (@all_panels) {
      $document->addClass($panel, $FIGURE_PANEL_CLASS); } }
  return @all_contents; }

sub BuildPanelsAndID {
  my ($document, $node, $whatsit, $id_kind) = @_;
  # 1. Each figure/table/float is comprised of 1 or more "panels", possibly separated by line-breaks,
  #     where multiple panels tend to correspond to subfigures/subtables/subfloats.
  my @contents = arrange_panels_and_breaks($document, $node, $whatsit);
  # 2. IDs
  # Note that even without \caption, we'd probably like to have xml:id.
  return GenerateID($document, $node, $whatsit, $id_kind); }

Tag('ltx:figure', afterClose => sub { BuildPanelsAndID(@_, 'fig'); });
Tag('ltx:float',  afterClose => sub { BuildPanelsAndID(@_, 'tab'); });
Tag('ltx:table',  afterClose => sub { BuildPanelsAndID(@_, 'tab'); });

# These may need to float up to where they're allowed,
# or they may need to close <p> or similar.
DefConstructor('\@@caption{}', "^^<ltx:caption>#1</ltx:caption>", sizer => 0);
DefConstructor('\@@toccaption{}', "^^<ltx:toccaption>#1</ltx:toccaption>",
  sizer => 0);

sub beforeFloat {
  my ($type, %options) = @_;
  #Debug("FLOAT begin $type". join(',',map { $_."=".ToString($options{$_}); } sort keys %options));
  DefMacroI('\@captype', undef, $type);
  AssignRegister('\hsize' => LookupDimension($options{double} ? '\textwidth' : '\columnwidth'));
  if (my $main = $options{preincrement}) {
    if (($type ne (LookupValue('LAST_FLOATTYPE') || ''))    # first of subtype?
      && !IfCondition('\iflx@donecaption')) {               # If main caption NOT already appeared?
      AssignValue('PREINCREMENTED_' . $main => { RefStepCounter($main) }, 'global'); } }
  return; }

sub afterFloat {
  my ($whatsit) = @_;
  my $type = ToString(Expand(T_CS('\@captype')));
  AssignValue('PREINCREMENTED_' . $type => undef, 'global');
  #Debug("FLOAT end $type");
  $whatsit->setProperty(floatwidth => LookupRegister('\hsize'));
  RescueCaptionCounters($type, $whatsit);
  AssignValue(LAST_FLOATTYPE => $type, 'global');
  return; }

DefEnvironment('{figure}[]',
  "<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
    . "#tags"
    . "#body"
    . "</ltx:figure>",
  properties   => { layout => 'vertical' },
  beforeDigest => sub { beforeFloat('figure'); },
  afterDigest  => sub { afterFloat($_[1]); },
  locked       => 1);

DefEnvironment('{figure*}[]',
  "<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
    . "#tags"
    . "#body"
    . "</ltx:figure>",
  properties   => { layout => 'vertical' },
  beforeDigest => sub { beforeFloat('figure', double => 1); },
  afterDigest  => sub { afterFloat($_[1]); },
  locked       => 1);
DefEnvironment('{table}[]',
  "<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
    . "#tags"
    . "#body"
    . "</ltx:table>",
  properties   => { layout => 'vertical' },
  beforeDigest => sub { beforeFloat('table'); },
  afterDigest  => sub { afterFloat($_[1]); },
  locked       => 1);
DefEnvironment('{table*}[]',
  "<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
    . "#tags"
    . "#body"
    . "</ltx:table>",
  properties   => { layout => 'vertical' },
  beforeDigest => sub { beforeFloat('table', double => 1); },
  afterDigest  => sub { afterFloat($_[1]); },
  locked       => 1);

# There are some floating figure/table packages that define environments
# to allow the figure to float left or right.  It APPEARS that they allow
# authors to either wrap with {figure} or NOT, as they like!
# The result apparently makes sense so long as they only use one \caption.
# If we get a figure containing a single figure, we want to collaps it.
sub collapseFloat {
  my ($document, $float) = @_;
  my $qname  = $document->getNodeQName($float);
  my @inners = $document->findnodes($qname, $float);
  if (scalar(@inners) == 1) {    # Only 1 inner float of same type
    my $inner    = $inners[0];
    my $ocaption = $document->findnodes('ltx:caption', $float);
    my $icaption = $document->findnodes('ltx:caption', $inner);
    if (!($ocaption && $icaption)) {    # If they don't both have captions.
      foreach my $attr (map { $_->nodeName } $inner->attributes) {
        next if $attr eq 'xml:id';
        $document->setAttribute($float, $attr => $inner->getAttribute($attr)); }
      if ($icaption) {
        if (my $id = $inner->getAttribute('xml:id')) {
          $document->unRecordID($id);
          $document->setAttribute($float, 'xml:id' => $id); } }

      # Finally, replace $inner by it's children!
      # Note that since we can only append new stuff, we've got to remove the following first.
      my @save = ();
      my $following;
      while (($following = $float->lastChild) && ($$following != $$inner)) { # Remove & Save following siblings.
        unshift(@save, $float->removeChild($following)); }
      unshift(@save, $inner->childNodes);
      $float->removeChild($inner);
      map { $float->appendChild($_) } @save; }                               # Put these back.
  }
  return; }
Tag('ltx:figure', afterClose => \&collapseFloat);
Tag('ltx:table',  afterClose => \&collapseFloat);
Tag('ltx:float',  afterClose => \&collapseFloat);

DefPrimitive('\flushbottom',      undef);
DefPrimitive('\suppressfloats[]', undef);

NewCounter('topnumber');
DefMacroI('\topfraction', undef, "0.25");
NewCounter('bottomnumber');
DefMacroI('\bottomfraction', undef, "0.25");
NewCounter('totalnumber');
DefMacroI('\textfraction',      undef, "0.25");
DefMacroI('\floatpagefraction', undef, "0.25");
NewCounter('dbltopnumber');
DefMacroI('\dbltopfraction',       undef, "0.7");
DefMacroI('\dblfloatpagefraction', undef, "0.25");
DefRegister('\floatsep'         => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\textfloatsep'     => Glue('20.0pt plus 2.0pt minus 4.0pt'));
DefRegister('\intextsep'        => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\dblfloatsep'      => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\dbltextfloatsep'  => Glue('20.0pt plus 2.0pt minus 4.0pt'));
DefRegister('\@maxsep'          => Dimension(0));
DefRegister('\@dblmaxsep'       => Dimension(0));
DefRegister('\@fptop'           => Glue(0));
DefRegister('\@fpsep'           => Glue(0));
DefRegister('\@fpbot'           => Glue(0));
DefRegister('\@dblfptop'        => Glue(0));
DefRegister('\@dblfpsep'        => Glue(0));
DefRegister('\@dblfpbot'        => Glue(0));
DefRegister('\abovecaptionskip' => Glue(0));
DefRegister('\belowcaptionskip' => Glue(0));
Let('\topfigrule', '\relax');
Let('\botfigrule', '\relax');
Let('\dblfigrule', '\relax');

DefMacroI('\figurename',  undef, 'Figure');
DefMacroI('\figuresname', undef, 'Figures');    # Never used?
DefMacroI('\tablename',   undef, 'Table');
DefMacroI('\tablesname',  undef, 'Tables');

Let('\outer@nobreak', '\@empty');
DefMacro('\@dbflt{}',           '#1');
DefMacro('\@xdblfloat{}[]',     '\@xfloat{#1}[#2]');
DefMacro('\@floatplacement',    '');
DefMacro('\@dblfloatplacement', '');

#======================================================================
# C.9.2 Marginal Notes
#======================================================================

DefConditional('\if@reversemargin');
Let('\reversemarginpar', '\@reversemargintrue');
Let('\normalmarginpar',  '\@reversemarginfalse');
DefConstructor('\marginpar[]{}',
"?#1(<ltx:note role='margin' class='ltx_marginpar_left'><ltx:inline-logical-block>#1</ltx:inline-logical-block></ltx:note>"
    . "?#2(<ltx:note role='margin' class='ltx_marginpar_right'><ltx:inline-logical-block>#2</ltx:inline-logical-block></ltx:note>))"
    . "(<ltx:note role='margin' class='ltx_marginpar'><ltx:inline-logical-block>#2</ltx:inline-logical-block></ltx:note>)");
DefRegister('\marginparpush', Dimension(0));

#**********************************************************************
# C.10 Lining It Up in Columns
#**********************************************************************

#======================================================================
# C.10.1 The tabbing Environment
#======================================================================
DefRegister('\tabbingsep' => Dimension(0));

DefMacroI('\tabbing', undef,
  '\par\@tabbing@bindings\@@tabbing\@start@alignment');
DefMacroI('\endtabbing', undef,
  '\@finish@alignment\@end@tabbing\par');
DefPrimitiveI('\@end@tabbing', undef, sub { $_[0]->egroup; });
DefConstructor('\@@tabbing SkipSpaces DigestedBody',
  '#1',
  reversion    => '\begin{tabbing}#1\end{tabbing}',
  beforeDigest => sub { $_[0]->bgroup; },
  mode         => 'text');

DefMacroI('\@tabbing@tabset',  undef, '\@tabbing@tabset@marker&');
DefMacroI('\@tabbing@nexttab', undef, '\@tabbing@nexttab@marker&');
DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]', '\@tabbing@newline@marker\cr');
DefMacroI('\@tabbing@kill', undef, '\@tabbing@kill@marker\cr\@tabbing@start@tabs');

DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\=',
  properties => { alignmentSkippable => 1 });
DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>',
  properties => { alignmentSkippable => 1 });
DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR));
DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill',
  afterDigest => sub { LookupValue('Alignment')->removeRow; return; },
  properties  => { alignmentSkippable => 1 });

AssignValue(tabbing_start_tabs => Tokens());
DefMacroI('\@tabbing@start@tabs', undef, sub { LookupValue('tabbing_start_tabs')->unlist; });
DefPrimitiveI('\@tabbing@increment', undef, sub {
    my @tabs = LookupValue('tabbing_start_tabs')->unlist;
    AssignValue(tabbing_start_tabs => Tokens(@tabs, T_CS('\>')), 'global'); });
DefPrimitiveI('\@tabbing@decrement', undef, sub {
    my ($ignore, @tabs) = LookupValue('tabbing_start_tabs')->unlist;
    AssignValue(tabbing_start_tabs => Tokens(@tabs), 'global'); });

# NOTE: \< is NOT currently handled!!!
# Ugh!! The way we're setting the initial tabs, we can't really handle this!
DefPrimitiveI('\@tabbing@untab', undef, undef);

# NOTE: \' and \` are NOT currently handled
DefPrimitiveI('\@tabbing@flushright', undef, undef);
DefPrimitiveI('\@tabbing@hfil',       undef, undef);
# NOTE: \pushtabs and \poptabs are NOT currently handled.
DefPrimitiveI('\@tabbing@pushtabs', undef, undef);
DefPrimitiveI('\@tabbing@poptabs',  undef, undef);

DefMacro('\@tabbing@accent{}', sub { T_CS('\@tabbing@' . ToString($_[1])); });

# Should there be some rowsep/colsep set here?
# Note that {tabbign} really shouldn't be handled by a tabular AT ALL....
# Should be recording accumulated widths and wrapping in ltx:text, with specified widths.
sub tabbingBindings {
  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
      template => LaTeXML::Core::Alignment::Template->new(
        repeated => [{ before => Tokens(T_CS('\lx@text@intercol')),
            after => Tokens(T_CS('\hfil'), T_CS('\lx@text@intercol')) }]),
      openContainer  => sub { $_[0]->openElement('ltx:tabular', @_[1 .. $#_]); },
      closeContainer => sub { $_[0]->closeElement('ltx:tabular'); },
      openRow        => sub { $_[0]->openElement('ltx:tr', @_[1 .. $#_]); },
      closeRow       => sub { $_[0]->closeElement('ltx:tr'); },
      openColumn     => sub { $_[0]->openElement('ltx:td', @_[1 .. $#_]); },
      closeColumn    => sub { $_[0]->closeElement('ltx:td'); },
      properties     => { attributes => { 'class' => 'ltx_tabbing' } }));

  Let("\\=",              '\@tabbing@tabset');
  Let("\\>",              '\@tabbing@nexttab');
  Let("\\\\",             '\@tabbing@newline');
  Let('\kill',            '\@tabbing@kill');
  Let("\\+",              '\@tabbing@increment');
  Let("\\-",              '\@tabbing@decrement');
  Let("\\<",              '\@tabbing@untab');
  Let('\@tabbing@' . "'", "\\'");
  Let('\@tabbing@' . "`", "\\`");
  Let('\a',               '\@tabbing@accent');
  Let("\\'",              '\@tabbing@flushright');
  Let("\\`",              '\@tabbing@hfil');
  Let('\pushtabs',        '\@tabbing@pushtabs');
  Let('\poptabs',         '\@tabbing@poptabs');
  return; }

DefMacroI('\pushtabs', undef, Tokens());
DefMacroI('\poptabs',  undef, Tokens());
DefMacroI('\kill',     undef, Tokens());

DefPrimitiveI('\@tabbing@bindings', undef, sub {
    tabbingBindings(); });

# NOTE: Do it!!

# Internals of tabbing, as an experiment (e.g. files using program.sty raw as in cs/0003026)
DefMacro('\@startfield', '\global\setbox\@curfield\hbox\bgroup\color@begingroup');
DefMacro('\@stopfield',  '\color@endgroup\egroup');
DefMacro('\@contfield',  '\global\setbox\@curfield\hbox\bgroup\color@begingroup\unhbox\@curfield');
DefMacro('\@addfield',   '\global\setbox\@curline\hbox{\unhbox\@curline\unhbox\@curfield}');

#======================================================================
# C.10.2 The array and tabular Environments
#======================================================================
# Tabular are a bit tricky in that we have to arrange for tr and td to
# be openned and closed at the right times; the only real markup is
# the & and \\. Also \multicolumn has to be cooperative.
# Along with this, we have to track which column specification applies
# to the current column.
# To simulate LaTeX's tabular borders & hlines, we simply add border
# attributes to all cells.  For HTML, CSS will be necessary to display them.
# [We'll ignore HTML's frame, rules and colgroup mechanisms.]

DefRegister('\lx@arstrut',           Dimension('0pt'));
DefRegister('\lx@default@tabcolsep', Dimension('6pt'));
DefRegister('\tabcolsep',            Dimension('6pt'));
DefMacroI('\arraystretch', undef, "1");
Let('\@tabularcr', '\@alignment@newline');
AssignValue(GUESS_TABULAR_HEADERS => 1)    # Defaults to yes
  unless defined LookupValue('GUESS_TABULAR_HEADERS');

sub tabularBindings {
  my ($template, %properties) = @_;
  $properties{guess_headers} = LookupValue('GUESS_TABULAR_HEADERS')
    unless defined $properties{guess_headers};
  if (!defined $properties{attributes}{colsep}) {
    my $sep = LookupDimension('\tabcolsep');
    if ($sep && ($sep->valueOf != LookupDimension('\lx@default@tabcolsep')->valueOf)) {
      $properties{attributes}{colsep} = $sep; } }
  if (!defined $properties{attributes}{rowsep}) {
    my $str = ToString(Expand(T_CS('\arraystretch')));
    if ($str != 1) {
      $properties{attributes}{rowsep} = Dimension(($str - 1) . 'em'); } }
  if (!defined $properties{strut}) {
    $properties{strut} = LookupRegister('\baselineskip')->multiply(1.5); }    # Account for html space
  alignmentBindings($template, 'text', %properties);
  Let("\\\\",            '\@tabularcr');
  Let('\lx@intercol',    '\lx@text@intercol');
  Let('\tabularnewline', "\\\\");
  # NOTE: Fit this back in!!!!!!!
  # # Do like AddToMacro, but NOT global!
  foreach my $name ('@row@before', '@row@after', '@column@before', '@column@after') {
    my $cs = '\\' . $name;
    DefMacroI($cs, undef,
      Tokens(LookupDefinition(T_CS($cs))->getExpansion->unlist,
        T_CS('\@tabular' . $name))); }
  return; }

# Keyvals are for attributes for the alignment.
# Typical keys are width, vattach,...
DefKeyVal('tabular', 'width', 'Dimension');
DefPrimitive('\@tabular@bindings AlignmentTemplate OptionalKeyVals:tabular', sub {
    my ($stomach, $template, $attributes) = @_;
    my %attr = ($attributes ? $attributes->getPairs : ());
    if (my $va = $attr{vattach}) {
      $attr{vattach} = translateAttachment($va) || ToString($va); }
    tabularBindings($template, attributes => {%attr});
    return; });

DefMacroI('\@tabular@before',        undef, '');
DefMacroI('\@tabular@after',         undef, '');
DefMacroI('\@tabular@row@before',    undef, '');
DefMacroI('\@tabular@row@after',     undef, '');
DefMacroI('\@tabular@column@before', undef, '');
DefMacroI('\@tabular@column@after',  undef, '');

# The Core alignment support is in LaTeXML::Core::Alignment and in TeX.ltxml
##DefMacro('\tabular[]{}', '\@tabular@bindings{#2}\@@tabular[#1]{#2}\@start@alignment');
DefMacro('\tabular[]{}',
  '\@tabular@bindings{#2}[vattach=#1]\@@tabular[#1]{#2}\@start@alignment\@tabular@before',
  locked => 1);
DefMacroI('\endtabular', undef,
  '\@tabular@after\@finish@alignment\@end@tabular',
  locked => 1);
DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; });
#DefMacroI('\@end@tabular', undef, undef);
# Note that the pattern will already have been interpreted by \@tabular@bindings,
# so make it Undigested here!

DefConstructor('\@@tabular[] Undigested DigestedBody',
  '#3',
  reversion    => '\begin{tabular}[#1]{#2}#3\end{tabular}',
  beforeDigest => sub { $_[0]->bgroup; },
  sizer        => '#3',
  afterDigest  => sub {
    my ($stomach, $whatsit) = @_;
    if (my $alignment = LookupValue('Alignment')) {
      my $attr = $alignment->getProperty('attributes');
      $$attr{vattach} = translateAttachment($whatsit->getArg(1)); }
    return; },
  locked => 1,
  mode   => 'text');

DefMacro('\csname tabular*\endcsname{Dimension}[]{}',
###  '\@tabular@bindings{#3}\@@tabular@{#1}[#2]{#3}\@start@alignment');
  '\@tabular@bindings{#3}[width=#1,vattach=#2]\@@tabular@{#1}[#2]{#3}\@start@alignment');
DefMacro('\csname endtabular*\endcsname',
  '\@finish@alignment\@end@tabular@');
DefConstructor('\@@tabular@{Dimension}[] Undigested DigestedBody',
  '#4',
  beforeDigest => sub { $_[0]->bgroup; },
  reversion    => '\begin{tabular*}{#1}[#2]{#3}#4\end{tabular*}',
  mode         => 'text');
DefPrimitive('\@end@tabular@', sub { $_[0]->egroup; });
Let('\multicolumn', '\@multicolumn');

# A weird bit that sometimes gets invoked by Cargo Cult programmers...
# to \noalign in the defn of \hline! Bizarre! (see latex.ltx)
# However, the really weird thing is the way this provides the } to close the argument
DefMacroI('\@xhline', undef, '\ifnum0=`{\fi}');

DefMacro('\cline{}', '\noalign{\@cline{#1}}');
DefConstructor('\@cline{}', '',
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $cols = ToString($whatsit->getArg(1));
    my @cols = ();
    while ($cols =~ s/^,?(\d+)//) {
      my $n = $1;
      push(@cols, ($cols =~ s/^-(\d+)// ? ($n .. $1) : ($n))); }
    my $alignment = LookupValue('Alignment');
    $alignment->addLine('t', @cols) if $alignment;
    return; },
  sizer      => 0, alias => '\cline',
  properties => { isHorizontalRule => 1 });

DefConstructorI('\vline', undef, "",    # ???
  properties => { isVerticalRule => 1 },
  sizer      => 0,
);
DefRegister('\lx@default@arraycolsep', Dimension('5pt'));
DefRegister('\arraycolsep',            Dimension('5pt'));
DefRegister('\arrayrulewidth',         Dimension('0.4pt'));
DefRegister('\doublerulesep',          Dimension('2pt'));
DefMacro('\extracolsep{}', Tokens());
#DefMacroI('\tabularnewline', undef, Tokens());
#Let('\tabularnewline', '\cr');
#DefMacroI('\tabularnewline', undef, '\cr'); # ???
#======================================================================
# Array and similar environments

DefPrimitive('\@array@bindings [] AlignmentTemplate', sub {
    my ($stomach, $pos, $template) = @_;
    my $attr = { vattach => translateAttachment($pos),
      role => 'ARRAY' };
    # Determine column and row separations, if non default
    my $colsep = LookupDimension('\arraycolsep');
    if ($colsep && ($colsep->valueOf != LookupDimension('\lx@default@arraycolsep')->valueOf)) {
      $$attr{colsep} = $colsep; }
    my $str = ToString(Expand(T_CS('\arraystretch')));
    if ($str != 1) {
      $$attr{rowsep} = Dimension(($str - 1) . 'em'); }
    alignmentBindings($template, 'math', attributes => $attr);
    if (my $font = LookupValue('font')) {
      if (($font->getMathstyle || 'text') eq 'display') {
        MergeFont(mathstyle => 'text'); } }
    Let("\\\\",         '\@alignment@newline');
    Let('\lx@intercol', '\lx@math@intercol');
    return; });

DefMacro('\array[]{}',
  '\@array@bindings[#1]{#2}\@@array[#1]{#2}\@start@alignment');
DefMacroI('\endarray', undef,
  '\@finish@alignment\@end@array');
DefPrimitiveI('\@end@array', undef, sub { $_[0]->egroup; });
DefConstructor('\@@array[] Undigested DigestedBody',
  '#3',
  beforeDigest => sub { $_[0]->bgroup; },
  reversion    => '\begin{array}[#1]{#2}#3\end{array}');

DefMacro('\@tabarray', '\m@th\@@array[c]');
#**********************************************************************
# C.11 Moving Information Around
#**********************************************************************

#======================================================================
# C.11.1 Files
#======================================================================
DefPrimitive('\nofiles', undef);

#======================================================================
# C.11.2 Cross-References
#======================================================================

# \label attaches a label to the nearest parent that can accept a labels attribute
# but only those that have an xml:id (but should this require a refnum and/or title ???)
# Note that latex essentially allows redundant labels, but we can record only one!!!
DefConstructor('\label Semiverbatim', sub {
    my ($document, $olabel, %props) = @_;
    my $label = $props{label};
    if (my $savenode = $document->floatToLabel) {
      my $node   = $document->getNode;
      my %labels = map { ($_ => 1) } $label, split(/\s+/, $node->getAttribute('labels') || '');
      $document->setAttribute($node, labels => join(' ', sort keys %labels));
      $document->setNode($savenode); } },
  reversion => sub {    # disappear from tex/content-tex strings, but NOT in general reversion!
    (defined $LaTeXML::DUAL_BRANCH ? () : Tokens(T_CS('\label'), T_BEGIN, Revert($_[1]), T_END)); },
  properties  => { alignmentSkippable => 1, alignmentPreserve => 1 },
  afterDigest => sub {
    MaybeNoteLabel($_[1]->getArg(1));
    my $label = CleanLabel(ToString($_[1]->getArg(1)));
    $_[1]->setProperty(label => $label);
    my $scope = $label; $scope =~ s/^LABEL:/label:/;
    if (my $ctr = LookupValue('current_counter')) {
      unshift(@{ LookupValue('scopes_for_counter:' . $ctr) }, $scope);
      $STATE->activateScope($scope);
      $_[0]->beginMode('text');
      AssignValue('LABEL@' . $label, Digest(T_CS('\@currentlabel')), 'global');
      $_[0]->endMode('text'); }
    return; });

# If a node has been labeled, but still  hasn't yet got an id by afterClose:late,
# we'd better generate an id for it.
Tag('ltx:*', 'afterClose:late' => sub {
    my ($document, $node) = @_;
    if ($node->hasAttribute('labels') && !($node->hasAttribute('xml:id'))) {
      GenerateID($document, $node); } });

# These will get filled in during postprocessing.
# * comes from hyperref
DefConstructor('\ref OptionalMatch:* Semiverbatim', "<ltx:ref ?#1(class='ltx_nolink')() labelref='#label' _force_font='true'/>",
  sizer      => '()',                                     # Don't actually know how big this will be!
  properties => sub { (label => CleanLabel($_[2])); });
# "page" does not make sense in xml.  If the user really wants, they will need:
# \usepackage{latexml} ... \iflatexml alternate\else page \pageref{label}\fi
Let('\pageref', '\ref');

#======================================================================
# C.11.3 Bibliography and Citation
#======================================================================

# Note that it's called \refname in LaTeX's article, but \bibname in report & book.
# And likewise, mixed up in various other classes!

DefMacroI('\thebibliography@ID', undef, Tokens());

# Do this before digesting the body of a bibliography
sub beforeDigestBibliography {
  AssignValue(inPreamble => 0); Digest('\@lx@inbibliographytrue');
  DefMacro('\bibliographystyle{}', '');
  DefMacro('\bibliography {}',     '');
  # avoid \let-based redefinitions of the ending.
  Let('\endthebibliography', '\saved@endthebibliography');
  ResetCounter('@bibitem');
  return; }

# This sub does things that would commonly be needed when starting a bibliography
# setting the ID, etc...
sub beginBibliography {
  my ($whatsit) = @_;
  beginBibliography_clean($whatsit);
  # Fix for missing \bibitems!
  setupPseudoBibitem();
  return; }

sub beginBibliography_clean {
  my ($whatsit) = @_;
  # Check if \bibsection is defined and try to decipher it.
  # Expecting something like \section*{sometext}
  my $bibtitle = undef;
  my $bs       = LookupDefinition(T_CS('\bibsection'));
  # FIXED: Sometimes $bs may be a Primitive.
  # Now guarding for that case or we get a perl:die for ->getExpansion
  if (my @t = $bs && ($bs->isExpandable ? $bs->getExpansion->unlist : $bs)) {
    our %bibunitmap = (
      '\\part'          => 'ltx:part',          '\\chapter'    => 'ltx:chapter',
      '\\section'       => 'ltx:section',       '\\subsection' => 'ltx:subsection',
      '\\subsubsection' => 'ltx:subsubsection', '\\paragraph'  => 'ltx:paragraph',
      '\\subparagraph'  => 'ltx:subparagraph');
    if (my $unit = $bibunitmap{ ToString(shift(@t)) }) {    # Found a sectional unit command.
      AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => $unit);
      if (ToString($t[0]) eq '*') { shift(@t); }
      # Check for balanced? or just take balanced begining?
      $bibtitle = Tokens(@t); } }
  noteBackmatterElement($whatsit, 'ltx:bibliography');
  # Try to compute a reasonable, but unique ID;
  # relative to the document's ID, if any.
  # But also, if there are multiple bibliographies,
  my $bibnumber = LookupValue('n_bibliographies') || 0;
  AssignValue(n_bibliographies => ++$bibnumber, 'global');
  my $docid = ToString(Expand(T_CS('\thedocument@ID')));
  my $bibid = ($docid ? $docid . '.' : '') . 'bib' . radix_alpha($bibnumber - 1);
  DefMacroI(T_CS('\thebibliography@ID'), undef, T_OTHER($bibid), scope => 'global');
  #  $whatsit->setProperty(id=>ToString(Expand(T_CS('\thebibliography@ID'))));
  $whatsit->setProperty(id => $bibid);
  my $title = ($bibtitle ? Digest($bibtitle)
    : DigestIf('\refname') || DigestIf('\bibname'));
  $whatsit->setProperty(title     => $title)          if $title;
  $whatsit->setProperty(titlefont => $title->getFont) if $title;
  $whatsit->setProperty(bibstyle  => LookupValue('BIBSTYLE'));
  $whatsit->setProperty(citestyle => LookupValue('CITE_STYLE'));
  #  $whatsit->setProperty(sort=> ???
  # And prepare for the likely nonsense that appears within bibliographies
  ResetCounter('enumiv');
  return; }

DefMacro('\bibliography Semiverbatim',
  '\lx@ifusebbl{#1}{\input{\jobname.bbl}}{\lx@bibliography{#1}}');

DefMacro('\lx@ifusebbl{}{}{}', sub {
    my ($gullet, $bib_files, $bbl_clause, $bib_clause) = @_;
    $bib_files = ToString(Expand($bib_files));
    return unless $bib_files;
    my $jobname = ToString(Expand(T_CS('\jobname')));

    my $bbl_path     = FindFile($jobname, type => 'bbl');
    my $missing_bibs = '';
    if (LookupValue("NO_BIBTEX")) {
      if (not $bbl_path) {
        Info('expected', "bbl", $_[0], "Couldn't find bbl file, bibliography may be empty.");
        return Tokens(); }
      else {
        return $bbl_clause->unlist; } }
    else {
      for my $bf (split(',', $bib_files)) {
        my $bib_path = FindFile($bf, type => 'bib');
        if (not $bib_path) {
          $missing_bibs .= ',' unless length($missing_bibs) == 0;
          $missing_bibs .= $bf; } }
      if (length($missing_bibs) == 0 or not $bbl_path) {
        return $bib_clause->unlist; }
      else {
        Info('expected', $missing_bibs, $_[0], "Couldn't find all bib files, using " . $jobname . ".bbl instead");
        return $bbl_clause->unlist; } } });

AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => 'ltx:section');
AssignMapping('BACKMATTER_ELEMENT', 'ltx:index'        => 'ltx:section');

sub noteBackmatterElement {
  my ($whatsit, $backelement) = @_;
  $whatsit->setProperties(backmatterelement => LookupMapping('BACKMATTER_ELEMENT', $backelement));
  return; }

sub adjustBackmatterElement {
  my ($document, $whatsit) = @_;
  if (my $asif = $whatsit->getProperty('backmatterelement')) {
    $document->setNode($document->find_insertion_point($asif)); }
  return; }

DefConstructor('\lx@bibliography [] Semiverbatim',
  "<ltx:bibliography files='#2' xml:id='#id' "
    . "bibstyle='#bibstyle' citestyle='#citestyle' sort='#sort' lists='#1'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "</ltx:bibliography>",
  afterDigest => sub { $_[0]->begingroup;    # wrapped so redefns don't take effect!
    beginBibliography($_[1]);
    $_[0]->endgroup; },
  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });

# NOTE: This totally needs to be made extensible (parsing *.bst!?!? OMG!)
our $BIBSTYLES = {
  plain    => { citestyle => 'numbers', sort => 'true' },
  unsrt    => { citestyle => 'numbers', sort => 'false' },
  alpha    => { citestyle => 'AY',      sort => 'true' },
  abbrv    => { citestyle => 'numbers', sort => 'true' },
  plainnat => { citestyle => 'numbers', sort => 'true' },
  unsrtnat => { citestyle => 'numbers', sort => 'false' },
  alphanat => { citestyle => 'AY',      sort => 'true' },
  abbrvnat => { citestyle => 'numbers', sort => 'true' } };

sub setBibstyle {
  my ($style) = @_;
  $style = ToString($style);
  AssignValue(BIBSTYLE => $style);
  if (my $parms = $$BIBSTYLES{$style}) {
    AssignValue(CITE_STYLE => $$parms{citestyle});
    AssignValue(CITE_SORT  => $$parms{sort}); }
  return; }

DefConstructor('\bibstyle{}', sub {
    my ($document, $style) = @_;
    setBibstyle($style);
    # Really ?
    if (my $bib = $document->findnode('//ltx:bibliography')) {
      $document->setAttribute($bib, bibstyle  => LookupValue('BIBSTYLE'));
      $document->setAttribute($bib, citestyle => LookupValue('CITE_STYLE'));
      $document->setAttribute($bib, sort      => LookupValue('CITE_SORT')); }
  },
  afterDigest => sub {
    my $style = ToString($_[1]->getArg(1));
    AssignValue(BIBSTYLE => $style, 'global');
    if (my $parms = $$BIBSTYLES{$style}) {
      AssignValue(CITE_STYLE => $$parms{citestyle}); }
    else {
      Info('unexpected', $style, $_[0], "Unknown bibstyle '$style', it will be ignored"); }
    return; });

DefMacro('\bibliographystyle Semiverbatim', '\bibstyle{#1}');

DefConditional('\if@lx@inbibliography');
# Should be an environment, but people seem to want to misuse it.
DefConstructorI('\thebibliography', undef,
  "<ltx:bibliography xml:id='#id'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "<ltx:biblist>",
  beforeDigest => sub {
    beforeDigestBibliography(); },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    # NOTE that in some perverse situations (revtex?)
    # it seems to be allowable to omit the argument
    # It's ignorable for latexml anyway, so we'll just read it if its there .
    my $gullet = $stomach->getGullet;
    $gullet->skipSpaces;
    $gullet->readArg if $gullet->ifNext(T_BEGIN);
    beginBibliography($_[1]);
  },
  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); },
  locked          => 1);

# Close the bibliography
DefConstructorI('\endthebibliography', undef, sub {
    $_[0]->maybeCloseElement('ltx:biblist');
    $_[0]->maybeCloseElement('ltx:bibliography'); },
  locked => 1);
Let('\saved@endthebibliography', '\endthebibliography');
# auto close the bibliography and contained biblist.
Tag('ltx:biblist',      autoClose => 1);
Tag('ltx:bibliography', autoClose => 1);

# Since SOME people seem to write bibliographies w/o \bibitem,
# just blank lines between apparent entries,
# Making \par do a \bibitem{} works, but screws up valid
# bibliographies with blank lines!
# So, let's do some redirection!
sub setupPseudoBibitem {
  Let('\save@bibitem', '\bibitem');
  Let('\save@par',     '\par');
  # see arXiv:hep-th/0002043 for why the backslash itself needs binding.
  Let('\save@backbackslash', '\\\\');
  Let('\bibitem',            '\restoring@bibitem');
  Let('\par',                '\par@in@bibliography');
  Let('\\\\',                '\par@in@bibliography');
  Let('\vskip',              '\vskip@in@bibliography');
  # Moreover, some people use \item instead of \bibitem
  Let('\item', '\item@in@bibliography');
  # And protect from redefinitions.
  Let('\newblock', '\lx@bibnewblock');
  my $gullet = $STATE->getStomach->getGullet;
  # Risky, but when bibliography immediatesly starts with text (no implied \par)
  if (my $token = $gullet->readNonSpace) {
    $gullet->unread($token);
    if (!$token->isExecutable) {
      $gullet->unread(T_CS('\par')); } }
  return; }

DefMacroI('\par@in@bibliography', undef, sub {
    my ($gullet) = @_;
    $gullet->skipSpaces;
    my $tok = $gullet->readToken;
    # If next token is another \par, or a REAL \bibitem,
    if (Equals($tok, T_CS('\par')) || Equals($tok, T_CS('\bibitem'))) {
      ($tok); }    # then this \par expands into what followed
    else {         # Else, put it back, and start a bibitem.
      $gullet->unread($tok);
      (T_CS('\save@bibitem'), T_BEGIN, T_END); } });

DefMacro('\vskip@in@bibliography Glue', undef);

DefMacroI('\item@in@bibliography', undef, '\save@bibitem{}');

# If we hit a real \bibitem, put \par & \bibitem back to correct defn, and then \bibitem.
# A bibitem with now key or label...
DefMacro('\restoring@bibitem',
  '\let\bibitem\save@bibitem\let\par\save@par\let\\\\\save@backbackslash\bibitem');

NewCounter('@bibitem', 'bibliography', idprefix => 'bib');
DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}');
DefMacro('\@biblabel{}', '[#1]');
DefMacroI('\fnum@@bibitem', undef, '{\@biblabel{\the@bibitem}}');
# Hack for abused bibliographies; see below
DefMacro('\bibitem',
  '\if@lx@inbibliography\else\expandafter\lx@mung@bibliography\expandafter{\@currenvir}\fi'
    . '\lx@bibitem', locked => 1);
DefConstructor('\lx@bibitem[] Semiverbatim',
  "<ltx:bibitem key='#key' xml:id='#id'>"
    . "#tags"
    . "<ltx:bibblock>",
  afterDigest => sub {
    my $tag = $_[1]->getArg(1);
    my $key = CleanBibKey($_[1]->getArg(2));
    if ($tag) {
      $_[1]->setProperties(key => $key,
        RefStepID('@bibitem'),
        tags => Digest(T_BEGIN,
          T_CS('\def'), T_CS('\the@bibitem'), T_BEGIN, Revert($tag), T_END,
          Invocation(T_CS('\lx@make@tags'), T_OTHER('@bibitem')),
          T_END)); }
    else {
      $_[1]->setProperties(key => $key, RefStepCounter('@bibitem')); }
  });

# This attempts to handle the case where folks put \bibitem's within an enumerate or such.
# We try to close the list and open the bibliography
DefMacro('\lx@mung@bibliography{}', sub {
    my ($gullet, $env) = @_;
    my $tag    = ToString($env);
    my @tokens = ();
    # If we're in some sort of list environment, maybe we can recover
    if (($tag eq 'enumerate') || ($tag eq 'itemize') || ($tag eq 'description')) {
      # nDamn! We're in a list {$tag}; try to close it!
      push(@tokens, Invocation('\end', $env),
        T_CS('\let'), T_CS('\end' . $tag),        T_CS('\endthebibliography'),
        T_CS('\let'), T_CS('\end{' . $tag . '}'), T_CS('\end{thebibliography}')); }
    # else ? it probably isn't going to work??
    # Now, try to open {thebibliography}
    push(@tokens, T_CS('\lx@mung@bibliography@pre'), Invocation('\thebibliography'), Tokens());
    return Tokens(@tokens); });
DefConstructor('\lx@mung@bibliography@pre', sub {
    my ($document) = @_;
    my $parent     = $document->getNode;
    my $tag        = $document->getNodeQName($parent);
    if ($tag =~ /^ltx:(?:itemize|enumerate|description)$/) {
      $document->maybeCloseElement($tag); }    # Or even remove (if empty)?
    return; });
DefConstructorI('\lx@bibnewblock', undef, sub {
    $_[0]->openElement('ltx:bibblock') if $_[0]->isOpenable('ltx:bibblock'); });
Let('\newblock', '\lx@bibnewblock');
Tag('ltx:bibitem',  autoOpen => 1, autoClose => 1);
Tag('ltx:bibblock', autoOpen => 1, autoClose => 1);

#----------------------------------------------------------------------
# We've got the same problem as LaTeX: Lather, Rinse, Repeat.
# It would be nice to know the bib info at digestion time
#  * whether author lists will collapse
#  * whether there are "a","b".. extensions on the year.
# We could process the bibliography first, (IF it is a separate *.bib!)
# but won't know which entries are included (and so can't resolve the a/b/c..)
# until we've finished looking at (all of) the source(s) that will refer to them!
#
# We can do this in 2 passes, however
#  (1) convert (latexml) both the source document(s) and the bibliography
#  (2) extract the required bibitems and integrate (latexmlpost) it into the documents.
# [Note that for mult-document sites, step (2) becomes 2 stages: scan and integrate]
#
# Here's the general layout.
#   <ltx:cite> contains everything that the citations produce,
#     including parens, pre-note, punctunation that precede the <ltx:bibcite>
#     and punctuation, post-note, parens, that follow it.
#   <ltx:bibcite show="string" bibrefs="keys" sep="" yysep="">phrases</ltx:bibcite>
#     encodes the actual citation.
#
#     bibrefs : lists the bibliographic keys that will be used
#     show    : gives the pattern for formatting using data from the bibliography
#       It can contain:
#         authors or fullauthors
#         year
#         number
#         phrase1,phrase2,... selects one of the phrases from the content of the <ltx:bibref>
#     This format is used as follows:
#       If author and year is present, and a subset of the citations share the same authors,
#         then the format is used, but the year is repeated for each citation in the subset,
#         as a link to the bib entry.
#       Otherwise, the format is applied to each entry.
#
# The design is intended to support natbib, as well as plain LaTeX.

AssignValue(CITE_STYLE          => 'numbers');
AssignValue(CITE_OPEN           => T_OTHER('['));
AssignValue(CITE_CLOSE          => T_OTHER(']'));
AssignValue(CITE_SEPARATOR      => T_OTHER(','));
AssignValue(CITE_YY_SEPARATOR   => T_OTHER(','));
AssignValue(CITE_NOTE_SEPARATOR => T_OTHER(','));
AssignValue(CITE_UNIT           => undef);

DefMacro('\@cite{}{}', '[{#1\if@tempswa , #2\fi}]');
DefConstructor('\@@cite []{}', "<ltx:cite ?#1(class='ltx_citemacro_#1')>#2</ltx:cite>",
  alias => '\cite',
  mode  => 'text');

# \@@bibref{what to show}{bibkeys}{phrase1}{phrase2}
DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
  "<ltx:bibref show='#1' bibrefs='#bibrefs' inlist='#bibunit'"
    . " separator='#separator' yyseparator='#yyseparator'>#3#4</ltx:bibref>",
  properties => sub { (bibrefs => CleanBibKey($_[2]),
      separator   => ToString(Digest(LookupValue('CITE_SEPARATOR'))),
      yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR'))),
      bibunit     => LookupValue('CITE_UNIT')); });

# Simple container for any phrases used in the bibref
DefConstructor('\@@citephrase{}', "<ltx:bibrefphrase>#1</ltx:bibrefphrase>",
  mode => 'text');

DefMacro('\cite[] Semiverbatim', sub {
    my ($gullet, $post, $keys) = @_;
    my ($style, $open, $close, $ns)
      = map { LookupValue($_) } qw(CITE_STYLE CITE_OPEN CITE_CLOSE CITE_NOTE_SEPARATOR);
    $post = undef if IsEmpty($post);
    Invocation(T_CS('\@@cite'),
      Tokens(Explode('cite')),
      Tokens($open,
        Invocation(T_CS('\@@bibref'), undef, $keys, undef, undef),
        ($post ? ($ns, T_SPACE, $post) : ()), $close)); });

# NOTE: Eventually needs to be recognized by MakeBibliography
# For now, defer until document end.
DefMacro('\nocite{}', sub {
    PushValue('@at@end@document', (T_CS('\lx@mark@nocite'), T_BEGIN, $_[1], T_END));
    return (); });
DefConstructor('\lx@mark@nocite Semiverbatim',
  "<ltx:cite><ltx:bibref show='nothing' bibrefs='#bibrefs' inlist='#bibunit'/></ltx:cite>",
  properties => sub {
    (bibrefs => CleanBibKey($_[1]),
      bibunit => LookupValue('CITE_UNIT')); });
#======================================================================
# C.11.4 Splitting the input
#======================================================================
Let('\@@input', '\input');    # Save TeX's version.
                              # LaTeX's \input is a bit different...
# Input, now
DefPrimitive('\ltx@input {}', sub { Input(Expand($_[1])); });
DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input');
Let('\@iinput', '\ltx@input');
DefMacro('\@input{}',  '\IfFileExists{#1}{\@@input\@filef@und}{\typeout{No file #1.}}');
DefMacro('\@input@{}', '\InputIfFileExists{#1}{}{\typeout{No file #1.}}');

DefMacro('\quote@name{}',          '"\quote@@name#1\@gobble""');
DefMacro('\quote@@name{} Match:"', '#1\quote@@name');
DefMacro('\unquote@name{}',        '\quote@@name#1\@gobble"');

# Note that even excluded files SHOULD have the effects of their inclusion
# simulated by having read the corresponding aux file;
# But we're not bothering with that.
DefPrimitive('\include{}', sub {
    my ($stomach, $path) = @_;
    $path = ToString($path);
    my $table = LookupValue('including@only');
    if (!$table || $$table{$path}) {
      Input($path); }
    return; });
Let('\@include', '\include');

# [note, this will load name.tex, if it exists, else name]
DefPrimitive('\includeonly{}', sub {
    my ($stomach, $paths) = @_;
    $paths = ToString($paths);
    my $table = LookupValue('including@only');
    AssignValue('including@only', $table = {}, 'global') unless $table;
    map { $$table{$_} = 1 } map { /^\s*(.*?)\s*$/ && $1; } split(/,/, $paths);
    return; });

# NOTE: In the long run, we want to SAVE the contents and associate them with the given file name
#  AND, arrange so that when a file is read, we'll use the contents!
DefConstructorI(T_CS("\\begin{filecontents}"), "Semiverbatim",
  '',
  reversion   => '',
  afterDigest => [sub {
      my ($stomach, $whatsit) = @_;
      my $filename = ToString($whatsit->getArg(1));
      my @lines    = ();
      my $gullet   = $stomach->getGullet;
      my $line;
      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents}')) {
        push(@lines, $line); }
      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
      NoteLog("Cached filecontents for $filename (" . scalar(@lines) . " lines)"); }]);
DefConstructorI(T_CS("\\begin{filecontents*}"), "Semiverbatim",
  '',
  reversion   => '',
  afterDigest => [sub {
      my ($stomach, $whatsit) = @_;
      my $filename = ToString($whatsit->getArg(1));
      my @lines    = ();
      my $gullet   = $stomach->getGullet;
      my $line;
      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents*}')) {
        push(@lines, $line); }
      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
      NoteLog("Cached filecontents* for $filename (" . scalar(@lines) . " lines)"); }]);
DefMacro('\endfilecontents', '');
DefPrimitive('\listfiles', undef);

#======================================================================
# C.11.5 Index and Glossary
#======================================================================

# ---- The index commands
# Format of Index entries:
#   \index{entry!entry}  gives multilevel index
# Each entry:
#   foo@bar  sorts on "foo" but prints "bar"
# The entries can end with a |expression:
#   \index{...|(}    this page starts a range for foo
#   \index{...|)}    this page ends a range
#           The last two aren't handled in any particular way.
#           We _could_ mark start & end, and then the postprocessor would
#           need to fill in all likely links... ???
#   \index{...|see{key}}  cross reference.
#   \index{...|seealso{key}}  cross reference.
#   \index{...|textbf}  (etc) causes the number to be printed in bold!
#
# I guess the formula is that
#    \index{foo|whatever{pi}{pa}{po}}  => \whatever{pi}{pa}{po}{page}
# How should this get interpreted??
our %index_style = (textbf => 'bold', bf => 'bold', textrm => '', rm => '',
  textit => 'italic', it => 'italic', emph => 'italic');    # What else?
    # A bit screwy, but....
    # Expand \index{a!b!...} into \@index{\@indexphrase{a}\@indexphrase{b}...}

sub process_index_phrases {
  my ($gullet, $phrases, $inlist) = @_;
  my @expansion = ();
  my @tokens    = $phrases->unlist;
  # check we have a well-formed argument
  return unless @tokens;
  if (!$phrases->isBalanced) {    # if ill-formed, discard;
    Warn("malformed", "indexentry", $gullet,
      'index entry has unbalanced groups, discarding: "' . ToString($phrases) . '"');
    return; }
  # Split the text into phrases, separated by "!"
  push(@tokens, T_OTHER('!')) unless $tokens[-1]->getString eq '!';    # Add terminal !
  my @phrase = ();
  my @sortas = ();
  my $style;
  while (@tokens) {
    my $tok    = shift(@tokens);
    my $string = $tok->getString;
    if ($string eq '"') {
      push(@phrase, shift(@tokens)); }
    elsif ($string eq '@') {
      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
      @sortas = @phrase; @phrase = (); }
    elsif (($string eq '!') || ($string eq '|')) {
      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
      push(@expansion, T_CS('\@indexphrase'),
        (@sortas ? (T_OTHER('['), @sortas, T_OTHER(']')) : ()),
        T_BEGIN, @phrase, T_END)
        if @phrase;
      @phrase = (); @sortas = ();
      if ($string eq '|') {
        pop(@tokens);    # Remove the extra "!" stopbit.
        my $extra = ToString(Tokens(@tokens));
        if    ($extra =~ /^see\s*{/)      { push(@expansion, T_CS('\@indexsee'), @tokens[3 .. $#tokens]); }
        elsif ($extra =~ /^seealso\s*\{/) { push(@expansion, T_CS('\@indexseealso'), @tokens[7 .. $#tokens]); }
        elsif ($extra eq '(')             { $style = 'rangestart'; }                     # ?
        elsif ($extra eq ')')             { $style = 'rangeend'; }                       # ?
        else                              { $style = $index_style{$extra} || $extra; }
        @tokens = (); } }
    elsif (!@phrase && ($string =~ /\s/)) { }    # Skip leading whitespace
    else {
      push(@phrase, $tok); } }
  @expansion = (T_CS('\@index'),
    ($style || $inlist ? (T_OTHER('['), ($style ? T_OTHER($style) : ()), T_OTHER(']')) : ()),
    ($inlist ? (T_OTHER('['), T_OTHER($inlist), T_OTHER(']')) : ()),
    T_BEGIN, @expansion, T_END);
  return @expansion; }

# read verbatim, as if with LaTeX's \@sanitize;
# useful for \index (maybe others?)
DefParameterType('SanitizedVerbatim', sub {
    my ($gullet) = @_;
    $gullet->readUntil(T_BEGIN);
    # crucial: deactivate the backslash to avoid activating command sequences
    # chars switched to CC_OTHER by \@sanitize: ' ', '\\', '$', '&', '#', '^', '_', '%', '~'
    # some of those are already in state's "SPECIALS", so only adding the rest:
    StartSemiverbatim(' ', '\\', '%');
    my $arg = $gullet->readBalanced();
    EndSemiverbatim();
    # now that we have the semiverbatim tokens, retokenize.
    # this may seem like wasted work, but it avoids very unfortunate error propagation in cases
    # where the \index argument was malformed for one reason or another.
    #
    # the strangeness comes from the original TeX workflow requiring multiple conversion calls,
    # alongside a call to the `makeidx` binary, which we don't do in latexml. This parameter type
    # emulates one important aspect implied by those steps.
    $arg = TokenizeInternal(UnTeX($arg));
    return $arg; },
  reversion => sub { (T_BEGIN, Revert($_[0]), T_END); });

# real-world LaTeX \index
DefMacro('\index SanitizedVerbatim', \&process_index_phrases);

Tag('ltx:indexphrase',    afterClose => \&addIndexPhraseKey);
Tag('ltx:glossaryphrase', afterClose => \&addIndexPhraseKey);
### ltx:indexsee does NOT get a key (at this stage)!

sub addIndexPhraseKey {
  my ($document, $node) = @_;
  if (!$node->getAttribute('key')) {
    $node->setAttribute(key => CleanIndexKey($node->textContent)); }
  return; }

DefConstructor('\@index[][]{}', "^<ltx:indexmark style='#1' inlist='#2'>#3</ltx:indexmark>",
  bounded => 1, beforeDigest => sub { reenterTextMode(); neutralizeFont(); },
  mode    => 'text', reversion => '', sizer => 0);

DefConstructor('\@indexphrase[]{}',
  "<ltx:indexphrase key='#key' _standalone_font='true'>#2</ltx:indexphrase>",
  properties => { key => sub { CleanIndexKey($_[1]); } });
DefConstructor('\@indexsee{}',
  "<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
  properties => { name => sub { DigestIf('\seename') } });

DefConstructor('\@indexseealso{}',
  "<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
  properties => { name => sub { DigestIf('\alsoname') } });

DefConstructor('\glossary{}', sub {
    my ($document, $arg, %props) = @_;
    my $current_node      = $document->getNode;
    my $current_node_name = $document->getNodeQName($current_node);
    if ($current_node_name eq '#PCDATA') {
      $current_node_name = $document->getNodeQName($current_node->parentNode); }
    if ($current_node_name =~ /^ltx:(?:p|text)/) {
      Warn("unexpected", "glossary", $document, "glossary support is not yet ready for use in the main text flow.");
    } else {
      $document->insertElement('ltx:glossaryphrase', $arg, role => 'glossary', key => $props{'key'}); }
    return; },
  properties => { key => sub { CleanIndexKey($_[1]); } },
  sizer      => 0);

#======================================================================
# This converts an indexphrase node into a sortable string.
# Seems the XML nodes are the best place to handle it (rather than Boxes),
# although some of the special cases (see, @, may end up tricky)
sub indexify {
  my ($node, $document) = @_;
  my $type = $node->nodeType;
  if ($type == XML_TEXT_NODE) {
    my $string = $node->textContent;
    $string =~ s/\W//g;    # to be safe (if perhaps non-unique?)
    $string =~ s/\s//g;    # Or remove entirely? Eventually worry about many=>1 mapping???
    return $string; }
  elsif ($type == XML_ELEMENT_NODE) {
    if ($document->getModel->getNodeQName($node) eq 'ltx:Math') {
      return indexify_tex($node->getAttribute('tex')); }
    else {
      return join('', map { indexify($_, $document) } $node->childNodes); } }
  elsif ($type == XML_DOCUMENT_FRAG_NODE) {
    return join('', map { indexify($_, $document) } content_nodes($node)); }
  else {
    return ""; } }

# Try to clean up a TeX string into something
# Could walk the math tree and handle XMDual specially, but need to xref args.
# But also we'd have unicode showing up, which we'd like to latinize...
sub indexify_tex {
  my ($string) = @_;
  $string =~ s/(\\\@|\\,|\\:|\\;|\\!|\\ |\\\/|)//g;
  $string =~ s/(\\mathrm|\\mathit|\\mathbf|\\mathsf|\\mathtt|\\mathcal|\\mathscr|\\mbox|\\rm|\\it|\\bf|\\tt|\\small|\\tiny)//g;
  $string =~ s/\\left\b//g; $string =~ s/\\right\b//g;
  $string =~ s/(\\|\{|\})//g;
  $string =~ s/\W//g;           # to be safe (if perhaps non-unique?)
  $string =~ s/\s//g;           # Or remove entirely? Eventually worry about many=>1 mapping???
  return $string; }

# ---- Creating the index itself

AssignValue(INDEXLEVEL => 0);

Tag('ltx:indexentry', autoClose => 1);

sub closeIndexPhrase {
  my ($document) = @_;
  if ($document->isCloseable('ltx:indexphrase')) {
    $document->closeElement('ltx:indexphrase'); }
  return; }

sub doIndexItem {
  my ($document, $level) = @_;
  $document->closeElement('ltx:indexrefs') if $document->isCloseable('ltx:indexrefs');
  closeIndexPhrase($document);
  my $l = LookupValue('INDEXLEVEL');
  while ($l < $level) {
    $document->openElement('ltx:indexlist'); $l++; }
  while ($l > $level) {
    $document->closeElement('ltx:indexlist'); $l--; }
  AssignValue(INDEXLEVEL => $l);
  if ($level) {
    $document->openElement('ltx:indexentry');
    $document->openElement('ltx:indexphrase'); }
  return; }

DefConstructorI('\index@dotfill', undef, sub {
    my ($document) = @_;
    closeIndexPhrase($document);
    $document->openElement('ltx:indexrefs'); });
DefConstructorI('\index@item',       undef, sub { doIndexItem($_[0], 1); });
DefConstructorI('\index@subitem',    undef, sub { doIndexItem($_[0], 2); });
DefConstructorI('\index@subsubitem', undef, sub { doIndexItem($_[0], 3); });
DefConstructorI('\index@done',       undef, sub { doIndexItem($_[0], 0); });

DefMacroI('\indexname', undef, 'Index');
DefEnvironment('{theindex}',
  "<ltx:index xml:id='#id'>"
    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
    . "#body"
    . "</ltx:index>",
  beforeDigest => sub {
    Let('\item',       '\index@item');
    Let('\subitem',    '\index@subitem');
    Let('\subsubitem', '\index@subsubitem');
    Let('\dotfill',    '\index@dotfill'); },
  beforeDigestEnd  => sub { Digest(T_CS('\index@done')); },
  afterDigestBegin => sub {
    my $docid = ToString(Expand(T_CS('\thedocument@ID')));
    my $title = DigestIf('\indexname');
    noteBackmatterElement($_[1], 'ltx:index');
    $_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'),
      title     => $title,
      titlefont => $title->getFont); },
  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });

DefPrimitiveI('\indexspace',   undef, undef);
DefPrimitiveI('\makeindex',    undef, undef);
DefPrimitiveI('\makeglossary', undef, undef);

#======================================================================
# C.11.6 Terminal Input and Output
#======================================================================

DefPrimitive('\typeout{}', sub {
    my ($stomach, $stuff) = @_;
    Note(ToString(Expand($stuff)));
    return; });

DefPrimitive('\typein[]{}', undef);

#**********************************************************************
# C.12 Line and Page Breaking
#**********************************************************************

#======================================================================
# C.12.1 Line Breaking
#======================================================================
DefPrimitive('\linebreak[]',   undef);
DefPrimitive('\nolinebreak[]', undef);
DefPrimitiveI('\-', undef, undef);    # We don't do hyphenation.
                                      # \hyphenation in TeX.pool

DefPrimitiveI('\sloppy', undef, undef);
DefPrimitiveI('\fussy',  undef, undef);
# sloppypar can be used as an environment, or by itself.
DefMacro('\sloppypar',    '\par\sloppy');
DefMacro('\endsloppypar', '\par');
DefMacroI('\nobreakdashes', undef, T_OTHER('-'));

DefMacro('\showhyphens{}', '#1');    # ?
    #======================================================================
    # C.12.2 Page Breaking
    #======================================================================
DefMacro('\pagebreak[Default:4]', sub {
    my ($gullet, $arg) = @_;
    $arg = $arg->toString;
    if ($arg <= 2) {
      return; }
    return Invocation(T_CS('\vadjust'), T_CS('\clearpage')); });
DefPrimitive('\nopagebreak[]', undef);
DefPrimitiveI('\columnbreak', undef, undef);    # latex? or multicol?
DefPrimitive('\enlargethispage OptionalMatch:* {}', undef);

DefMacroI('\clearpage',       undef, '\LTX@newpage');
DefMacroI('\cleardoublepage', undef, '\LTX@newpage');
DefPrimitiveI('\samepage', undef, undef);

#**********************************************************************
# C.13 Lengths, Spaces and Boxes
#**********************************************************************

#####
#####
#  Complete to here
#  [except for NOTE'd entries, of course]
#####
#####

#======================================================================
# C.13.1 Length
#======================================================================
# \fill
DefMacro('\stretch{}', '0pt plus #1fill\relax');

DefPrimitive('\newlength DefToken', sub {
    my ($stomach, $cs) = @_;
    DefRegisterI($cs, undef, Glue(0), allocate => '\skip'); });
DefPrimitive('\setlength {Variable}{Dimension}', sub {
    my ($stomach, $variable, $length) = @_;
    my ($defn, @params) = @$variable;
    if (ref $defn) {
      $defn->setValue($length, undef, @params); }
    return; });
DefPrimitive('\addtolength {Variable}{Dimension}', sub {
    my ($stomach, $variable, $length) = @_;
    my ($defn, @params) = @$variable;
    if (ref $defn) {
      my $oldlength = $defn->valueOf(@params);
      $defn->setValue($defn->valueOf(@params)->add($length), undef, @params); }
    return; });

DefMacro('\@settodim{}{}{}',
  '\setbox\@tempboxa\hbox{{#3}}#2#1\@tempboxa\setbox\@tempboxa\box\voidb@x');
DefMacroI('\settoheight', undef, '\@settodim\ht');
DefMacroI('\settodepth',  undef, '\@settodim\dp');
DefMacroI('\settowidth',  undef, '\@settodim\wd');
DefMacro('\@settopoint{}', '\divide#1\p@\multiply#1\p@');

DefRegister('\fill', Glue('0pt plus 1fill'));

#======================================================================
# C.13.2 Space
#======================================================================
DefPrimitive('\hspace  OptionalMatch:* {Dimension}', sub {
    my ($stomach, $star, $length) = @_;
    my $s = DimensionToSpaces($length);
    return unless defined $s;
    Box($s, undef, undef, Invocation(T_CS('\hskip'), $length),
      width => $length, isSpace => 1); });

DefPrimitive('\vspace OptionalMatch:* {}', undef);
DefPrimitive('\addvspace {}',              undef);
DefPrimitive('\addpenalty {}',             undef);
DefPrimitiveI('\@endparenv');

# \hfill, \vfill

#======================================================================
# C.13.3 Boxes
#======================================================================
# Can't really get these?
DefMacroI('\height',      undef, '0pt');
DefMacroI('\totalheight', undef, '0pt');
DefMacroI('\depth',       undef, '0pt');
DefMacroI('\width',       undef, '0pt');

DefConstructor('\mbox {}',
  "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text', bounded => 1,
  sizer        => '#1',
  robust       => 1,
  beforeDigest => sub { reenterTextMode(); });

our %makebox_alignment = (l => 'left', r => 'right', s => 'justified');
DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox', robust => 1);
DefConstructor('\@makebox[Dimension][]{}',
  "<ltx:text ?#width(width='#width') ?#align(align='#align') _noautoclose='1'>#3</ltx:text>",
  mode         => 'text', bounded => 1, alias => '\makebox', sizer => '#3',
  beforeDigest => sub { reenterTextMode(); },
  properties   => sub {
    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
      ($_[1] ? (width => $_[1]) : ())) });

DefRegister('\fboxrule', Dimension('.4pt'));
DefRegister('\fboxsep',  Dimension('3pt'));

# Peculiar special case!
#  These are nominally text mode macros. However, there is a somewhat common idiom:
#     $ ... \framebox{$operator$} ... $
# in which case the operator gets boxed and really should be treated as a math object.
# (and ultimately converted to mml:menclose)
# So, we need to switch to text mode, as usual, but FIRST note whether we started in math mode!
# Afterwards, if we were in math mode, and the content is math, we'll convert the whole thing
# to a framed math object.
# Second special issue:
#   Although framebox doesn't allow flowed content inside, it is also somewhat common
# to put a vbox or some other block construct inside.
# Seemingly, the ultimate html gets somewhat tangled (browser bugs?)
# At any rate, since we're wrapping with an ltx:text, we'll try to unwrap it,
# if the contents are a single child that can handle the framing.

DefMacro('\fbox{}',   '\@framebox{#1}',                       robust => 1);
DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox', robust => 1);
DefConstructor('\@framebox[Dimension][]{}',
  "?#mathframe(<ltx:XMArg enclose='box'>#inner</ltx:XMArg>)"
    . "(<ltx:text ?#width(width='#width') ?#align(align='#align')"
    . " framed='rectangle' framecolor='#framecolor' cssstyle='#cssstyle'"
    . " _noautoclose='1'>#3</ltx:text>)",
  alias        => '\framebox', sizer => '#3',
  beforeDigest => sub {
    my ($stomach) = @_;
    my $wasmath = LookupValue('IN_MATH');
    $stomach->beginMode('text');
    AssignValue(FRAME_IN_MATH => $wasmath); },
  properties => sub {
    my $sep = LookupRegister('\fboxsep')->toAttribute;
    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
      framecolor => LookupValue('font')->getColor,
      ($sep ne '3.0pt' ? (cssstyle => 'padding:' . $sep) : ()),
      ($_[1]           ? (width    => $_[1])             : ())); },
  afterDigest => sub {
    my ($stomach, $whatsit) = @_;
    my $wasmath = LookupValue('FRAME_IN_MATH');
    my $arg     = $whatsit->getArg(3);
    $stomach->endMode('text');
    if ($wasmath && $arg->isMath) {
      $whatsit->setProperties(mathframe => 1, inner => $arg->getBody); }
    return; },
  afterConstruct => sub {
    my ($document, $whatsit) = @_;
    my $node = $document->getNode->lastChild;
    # If the generated node, has only a single (non space) child
    my @c = grep { ($_->nodeType != XML_TEXT_NODE) || ($_->textContent =~ /[^\s\n]/) }
      $node->childNodes;
    my $model = $document->getModel;
    # and that child can have the framed attribute
    if ((scalar(@c) == 1)
      && $document->canHaveAttribute($model->getNodeQName($c[0]), 'framed')
      && !$c[0]->hasAttribute('framed')) {
      # unwrap, copying the attributes
      $document->unwrapNodes($node);
      foreach my $k (qw(width align framed)) {
        if (my $v = $node->getAttribute($k)) {
          $document->setAttribute($c[0], $k => $v); } } } }
);

DefPrimitive('\newsavebox DefToken', sub {
    my $n = LookupValue('allocated_boxes') + 1;
    AssignValue(allocated_boxes => $n, 'global');
    DefRegisterI($_[1], undef, Number($n));
    AssignValue('box' . $n, List()); });

RawTeX(<<'EOL');
\def\newsavebox#1{\@ifdefinable{#1}{\newbox#1}}
\DeclareRobustCommand\savebox[1]{%
  \@ifnextchar(%)
    {\@savepicbox#1}{\@ifnextchar[{\@savebox#1}{\sbox#1}}}%
\DeclareRobustCommand\sbox[2]{\setbox#1\hbox{%
  \color@setgroup#2\color@endgroup}}
\def\@savebox#1[#2]{%
  \@ifnextchar [{\@isavebox#1[#2]}{\@isavebox#1[#2][c]}}
\long\def\@isavebox#1[#2][#3]#4{%
  \sbox#1{\@imakebox[#2][#3]{#4}}}
\def\@savepicbox#1(#2,#3){%
  \@ifnextchar[%]
    {\@isavepicbox#1(#2,#3)}{\@isavepicbox#1(#2,#3)[]}}
\long\def\@isavepicbox#1(#2,#3)[#4]#5{%
  \sbox#1{\@imakepicbox(#2,#3)[#4]{#5}}}
\def\lrbox#1{%
  \edef\reserved@a{%
    \endgroup
    \setbox#1\hbox{%
      \begingroup\aftergroup}%
        \def\noexpand\@currenvir{\@currenvir}%
        \def\noexpand\@currenvline{\on@line}}%
  \reserved@a
    \@endpefalse
    \color@setgroup
      \ignorespaces}
\def\endlrbox{\unskip\color@endgroup}
\DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax}
EOL

Let('\lx@parboxnewline', '\lx@newline');    # Obsolete, but in case still used
# NOTE: There are 2 extra arguments (See LaTeX Companion, p.866)
# for height and inner-pos.  We're ignoring inner-pos, for now, though.
DefConstructor('\parbox[] [Dimension] OptionalUndigested {Dimension} VBoxContents', sub {
    my ($document, $attachment, $b, $c, $width, $body, %props) = @_;
    insertBlock($document, $body,
      width   => $width,
      vattach => $props{vattach},
      class   => 'ltx_parbox');
    return; },
  sizer      => '#5',
  properties => sub {
    (width => $_[4],
      vattach => translateAttachment($_[1]),
      height  => $_[2]); },
  mode         => 'text', bounded => 1,
  robust       => 1,
  beforeDigest => sub {
    reenterTextMode(1);    # actually vertical mode!
    AssignRegister('\hsize' => $_[4]);
    Let('\\\\', '\lx@newline'); });
DefMacroI('\@parboxrestore', undef, Tokens());

DefConditional('\if@minipage');
DefMacro('\@setminipage', '');
DefEnvironment('{minipage}[][][]{Dimension}', sub {
    my ($document, $attachment, $b, $c, $width, %props) = @_;
    my $vattach = translateAttachment($attachment);
    insertBlock($document, $props{body},
      width   => $width,
      vattach => $vattach,
      class   => 'ltx_minipage');
    return; },
  sizer      => '#body',
  mode       => 'text',
  properties => sub { (
      width   => $_[4],
      vattach => translateAttachment($_[1])); },
  beforeDigest => sub {
    reenterTextMode(1);    # actually vertical mode!
    Digest(T_CS('\@minipagetrue'));
    AssignRegister('\hsize' => $_[4]);
    Let('\\\\', '\lx@newline');
  });

DefConstructor('\rule[Dimension]{Dimension}{Dimension}',
  "<ltx:rule ?#offset(yoffset='#offset') width='#width' height='#height'/>",
  properties => sub { (offset => $_[1], width => $_[2], height => $_[3]) });
DefConstructor('\raisebox{Dimension}[Dimension][Dimension]{}',
  "<ltx:text yoffset='#1' _noautoclose='1'>#4</ltx:text>",
  mode         => 'text', bounded => 1,
  beforeDigest => sub { reenterTextMode(); },
  sizer        => sub { raisedSizer($_[0]->getArg(4), $_[0]->getArg(1)); });

DefMacro('\@finalstrut{}', '\unskip\ifhmode\nobreak\fi\vrule\@width\z@\@height\z@\@depth\dp#1');

#**********************************************************************
# C.14 Pictures and Color
#**********************************************************************

# These are stubs for color or xcolor packages
Let('\set@color',        '\relax');
Let('\color@begingroup', '\relax');
Let('\color@endgroup',   '\relax');
Let('\color@setgroup',   '\relax');
Let('\color@hbox',       '\relax');
Let('\color@vbox',       '\relax');
Let('\color@endbox',     '\relax');

#======================================================================
# C.14.1 The picture environment
#======================================================================

#----------------------------------------------------------------------

sub ResolveReader {
  my ($type) = @_;
  return $type if ref $type eq 'CODE';    # It's already a reader (hopefully).
  $type = ToString($type);
  if (my $descriptor = LookupMapping('PARAMETER_TYPES', $type)) {
    return $$descriptor{reader}; }
  # Otherwise, try to find a function named Read<Type>
  else {
    return LaTeXML::Core::Parameter::checkReaderFunction('Read' . $type); } }

# This defines a Pair parameter type,
# that reads a parenthesized, comma separated pair of subtype.
# By default, the subtype is Float, but you can write Pair:Number, or Pair:Dimension
sub ReadPair {
  my ($gullet, $itemtype, $xarg, $yarg) = @_;
  my $itemreader;
  if   (!$itemtype) { $itemreader = \&ReadFloat; }
  else              { $itemreader = ResolveReader($itemtype); }
  if (!$itemreader) {
    Error('misdefined', $itemtype, $gullet, "Can't find reader for Pair items from '$itemtype'");
    return Pair(Dimension(0), Dimension(0)); }    # Assume something like this?
  $gullet->skipSpaces;
  if ($gullet->ifNext(T_OTHER('('))) {
    $gullet->readToken; $gullet->skipSpaces;
    if ($gullet->ifNext(T_OTHER('!'))) {          # maybe only for pstricks???
      Error('unexpected', 'postscript', $gullet,
        "Cannot process escape to postscript");
      $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
      return Pair(Dimension(0), Dimension(0)); }
    my $x = &$itemreader($gullet, $xarg);
    # This had also recognized ";" as a possible separator; if needed, will need to generalize
    # but we want to make readUntil accept only a single match
    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(',')); $gullet->skipSpaces;
    my $y = &$itemreader($gullet, $yarg);
    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
    return Pair($x, $y); }
  else {
    return; } }

sub ptValue {
  my ($value) = @_;
  return ((defined $value) && (ref $value) ? $value->ptValue : $value); }

sub pxValue {
  my ($value) = @_;
  return ((defined $value) && (ref $value) ? $value->pxValue : $value); }

#----------------------------------------------------------------------
# Picture parameters.
DefRegister('\unitlength', Dimension('1pt'));
DefPrimitiveI('\thinlines',  undef, sub { AssignRegister('\@wholewidth', Dimension('0.4pt')); });
DefPrimitiveI('\thicklines', undef, sub { AssignRegister('\@wholewidth', Dimension('0.8pt')); });
DefRegister('\@wholewidth' => Dimension('0.4pt'));
DefRegister('\@halfwidth'  => Dimension('0.2pt'));
DefMacro('\linethickness{}', '\@wholewidth #1\relax');

DefPrimitive('\arrowlength{Dimension}', sub { AssignValue('arrowlength', $_[1]); });

#----------------------------------------------------------------------
# Picture transformation support
sub slopeToPicCoord {
  my ($slope, $xlength) = @_;
  my ($mx,    $my)      = ($slope->getX, $slope->getY);
  my $s = $mx->sign();
  $xlength = picScale($xlength);
  return Pair($xlength->multiply($s),
    $xlength->multiply(($s == 0) ? $my->sign() :
        $my->valueOf / $mx->absolute->valueOf)); }

# Scale $value by \unitlength
sub picScale {
  my ($value) = @_;
  # Vain attempt at proper type coercion
  my $type = ref $value;
  my $unit = LookupRegister('\unitlength');
  if    (!$value) { }
  elsif ($type eq 'LaTeXML::Common::Number') {
    return $unit->multiply($value); }
  elsif ($type eq 'LaTeXML::Common::Float') {
    return $unit->multiply($value); }
  elsif ($type eq 'LaTeXML::Common::Pair') {
    return Pair($unit->multiply($value->getX), $unit->multiply($value->getY)); }
  return $value->multiply($unit); }

sub picProperties {
  my (%props) = @_;
  if (($props{stroke} || '') ne 'none') {
    $props{thick} = ptValue(LookupRegister('\@wholewidth')); }
  if (my $arrowlength = LookupValue('arrowlength')) {
    $props{arrowlength} = ptValue($arrowlength); }
  $props{color} = Black unless defined $props{color};
  return %props; }

#----------------------------------------------------------------------
# the code
DefMacroI('\qbeziermax', undef, '500');

sub before_picture {
  Let('\raisebox', '\pic@raisebox');    # ? needs special treatment within picture
  return; }

sub after_picture {
  return; }

# Ugh... Is this safe?  Apparently, picture stuff is allowed w/o a {picture} environment???
Tag('ltx:picture', autoOpen => 0.5, autoClose => 1,
  afterOpen  => sub { GenerateID(@_, 'pic'); },
  afterClose => sub {
    my ($document, $node, $whatsit) = @_;
    # Make sure we get a size, in case autoOpen'd
    if ($whatsit) {
      my ($w, $h, $d) = $whatsit->getSize;
      $node->setAttribute(width  => $w->pxValue)          unless $node->hasAttribute('width');
      $node->setAttribute(height => $h->add($d)->pxValue) unless $node->hasAttribute('height'); } });

# Note: Untex should prefix a setting of unitlength!!!
# First pair is (width,height)
# Second pair is the coordinate of the lower-left corner.
# [Note that for SVG the root viewport origin is at the TOP-left corner!
#  but that is currently handled in the SVG postprocessing module]
DefEnvironment('{picture} Pair OptionalPair',
  "<ltx:picture width='#width' height='#height' origin-x='#origin-x' origin-y='#origin-y'"
    . " fill='none' stroke='none' unitlength='#unitlength'>"
    . "?#transform(<ltx:g transform='#transform'>#body</ltx:g>)(#body)"
    . "</ltx:picture>",
  mode         => 'text',
  beforeDigest => \&before_picture,
  properties   => sub {
    my ($stomach, $size, $pos) = @_;
    picProperties(unitlength => LookupRegister('\unitlength'),
      width      => $size && picScale($size->getX),
      height     => $size && picScale($size->getY),
      depth      => Dimension(0),
      'origin-x' => $pos && picScale($pos->getX),
      'origin-y' => $pos && picScale($pos->getY),
      ($pos ? (transform => 'translate(' . picScale($pos->negate)->pxValue . ')') : ())); },
  afterDigest => \&after_picture);

DefMacroI(T_CS('\Gin@driver'), undef, Tokens());

DefMacro('\@killglue', '\unskip\@whiledim \lastskip >\z@\do{\unskip}');
DefMacro('\put SkipSpaces Match:( Until:, Until:){}', '\lx@pic@put(#2,#3){#4\relax}');
DefConstructor('\lx@pic@put Pair{}',
  "<ltx:g transform='#transform'"
    . " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'>#2</ltx:g>",
  properties => sub {
    my ($w, $h, $d) = $_[2]->getSize;
    $w = undef if $w && ($w->ptValue == 0);
    (transform => 'translate(' . picScale($_[1])->pxValue . ')',
      innerwidth => $w, innerheight => $h, innerdepth => $d); },
  alias => '\put',
  mode  => 'text');

DefConstructor('\line Pair:Number {Float}',
  "<ltx:line points='#points' stroke='#color' stroke-width='#thick'/>",
  alias      => '\line',
  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
DefConstructor('\vector Pair:Number {Float}',
  "<ltx:line points='#points' stroke='#color' stroke-width='#thick' terminators='->'"
    . " arrowlength='#arrowlength'/>",
  alias      => '\vector',
  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
DefConstructor('\circle OptionalMatch:* {Float}',
  "<ltx:circle x='0' y='0' r='#radius' fill='#fill' stroke='#stroke' stroke-width='#thick'/>",
  alias      => '\circle',
  properties => sub {
    my ($stomach, $filled, $dia) = @_;
    picProperties(radius => picScale($dia)->multiply(0.5)->pxValue,
      ($filled ? 'fill' : 'stroke') => Black); });

DefConstructor('\oval [Float] Pair []',
  "<ltx:rect x='#x' y='#y' width='#width' height='#height' rx='#radius'"
    . "  stroke='#color' fill='none' part='#3' stroke-width='#thick'/>",
  alias      => '\oval',
  properties => sub {
    my ($stomach, $r, $size, $part) = @_;
    $size = picScale($size);
    my $halfsize = $size->multiply(0.5);
    $r = ($r ? picScale($r) : Dimension('40pt'));
    $r = $r->smaller($halfsize->getX->absolute);
    $r = $r->smaller($halfsize->getY->absolute);
    picProperties(size => $size, radius => Dimension($r->valueOf),
      x      => $halfsize->getX->negate, y => $halfsize->getY->negate,
      width  => Dimension($size->getX->valueOf),
      height => Dimension($size->getY->valueOf),
    ); });

DefConstructor('\qbezier [Number] Pair Pair Pair',
"<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke='#color' stroke-width='#thick' />",
  alias      => '\qbezier',
  properties => sub {
    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });

DefMacro('\bezier Until:(', '\ifx.#1.\lx@pic@bezier{0}(\else\lx@pic@bezier{#1}(\fi');
DefConstructor('\lx@pic@bezier {Number} Pair Pair Pair',
  "<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
  alias      => '\bezier',
  properties => sub {
    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });

# Generic boxing command (frames, dash, etc)
DefConstructor('\pic@makebox@ Undigested RequiredKeyVals Pair []{}',
  "?#framed(<ltx:rect x='0' y='0' width='#fwidth' height='#fheight'"
    . " stroke='#color' stroke-width='#thick' fill='none' stroke-dasharray='#dash'/>)()"
    . "<ltx:g class='makebox' innerwidth='#width' innerheight='#height' innerdepth='#depth'"
    . " transform='translate(#xshift,#yshift)'>#box</ltx:g>",
  reversion    => '#1#3[#4]{#5}',
  beforeDigest => sub { reenterTextMode(); },
  properties   => sub {
    my ($stomach, $cs, $kv, $size, $pos, $box) = @_;
    my ($w, $h, $d)                            = $box->getSize;
    my ($ww, $hh)                              = ($w, $h);
    my ($x, $y)                                = (Dimension(0), Dimension(0));
    my $ht = ($h ? ($d ? $h->add($d) : $h) : ($d ? $d : Dimension(0)));
    if ($size) {    # && (($size->getX->valueOf != 0) || ($size->getY->valueOf != 0))) {
      $ww  = picScale($size->getX);
      $hh  = picScale($size->getY);
      $pos = ($pos ? lc(ToString($pos)) : '');
      if    ($pos =~ /l/) { $x = Dimension(0); }
      elsif ($pos =~ /r/) { $x = $ww->subtract($w); }
      else                { $x = $ww->subtract($w)->divide(2); }
      if ($pos =~ /b/)    { $y = Dimension(0); }
      elsif ($pos =~ /t/) { $y = $hh->subtract($ht); }
      else                { $y = $hh->subtract($ht)->divide(2); } }
    my $fw = ($ww && $ww->valueOf ? $ww : $w);
    my $fh = ($hh && $hh->valueOf ? $hh : $h->add($d));
    picProperties(
      box    => $box,
      width  => $w,                      height  => $h, depth => $d,
      fwidth => $fw,                     fheight => $fh,
      xshift => $x->pxValue,             yshift  => $y->pxValue,
      framed => $kv->getValue('framed'), dash    => $kv->getValue('dash')    # dashed
    ); });

DefMacro('\pic@makebox',           '\pic@makebox@{\makebox}{}');
DefMacro('\pic@framebox',          '\pic@makebox@{\framebox}{framed=true}');
DefMacro('\lx@pic@dashbox{Float}', '\pic@makebox@{\dashbox(#1)}{framed=true,dash={#1}}');
DefMacro('\dashbox Until:(',       '\ifx.#1.\lx@pic@dashbox{0}(\else\lx@pic@dashbox{#1}(\fi');
DefMacro('\frame{}',               '\pic@makebox@{\framebox}{framed=true}(0,0)[bl]{#1}');

DefMacro('\pic@savebox DefToken Pair []{}', '\pic@@savebox{#1}{\pic@makebox #2[#3]{#4}}');
DefPrimitive('\pic@@savebox DefToken {}', sub {
    AssignValue('box' . ToString($_[1]), Digest($_[2])); return; });
DefMacro('\@savepicbox', '\pic@savebox');

DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}',
  "<ltx:g y='#1'>#4</ltx:g>",
  alias => '\raisebox');

our %alignments = (l => 'left', c => 'center', r => 'right');
# Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop)
# This should be fairly compact vertically.
DefConstructor('\@shortstack@cr',
  "</ltx:p><ltx:p>",
  properties   => { isBreak => 1 },
  reversion    => Tokens(T_CS("\\\\"), T_CR),
  beforeDigest => sub { $_[0]->egroup; },
  afterDigest  => sub { $_[0]->bgroup; });

DefConstructor('\shortstack[]{}  OptionalMatch:* [Dimension]',
  "<ltx:inline-block align='#align'><ltx:p>#2</ltx:p></ltx:inline-block>",
  bounded      => 1,
  sizer        => '#2',
  beforeDigest => sub { reenterTextMode();
    # then RE-RE-define this one!!!
    Let("\\\\", '\@shortstack@cr');
    AssignRegister('\baselineskip' => Glue('-1pt'));
    AssignRegister('\lineskip'     => Glue('3pt'));
    $_[0]->bgroup; },
  afterDigest => sub { $_[1]->getSize;    # precompute while binding in effect
    $_[0]->egroup; },
  # Note: does not get layout=vertical, since linebreaks are explicit
  properties => { align => sub { ($_[1] ? $alignments{ ToString($_[1]) } : undef); },
    vattach => 'bottom' },                # for size computation
  mode => 'text');

DefMacro('\multiput Pair Pair {}{}', sub {
    my ($gullet, $pos, $d, $nt, $body) = @_;
    my ($x, $y, $dx, $dy, $n)
      = map { ToString($_) } ($pos->getX, $pos->getY, $d->getX, $d->getY, $nt);
    my @exp = ();
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@exp, T_CS('\put'), T_OTHER('('), Explode($x), T_OTHER(','), Explode($y), T_OTHER(')'),
        T_BEGIN, $body->unlist, T_END);
      $x += $dx; $y += $dy; }
    @exp; });

Tag('ltx:picture',
  afterOpen => sub {
    my ($document, $node, $thing) = @_;
    if ($thing && !$node->getAttribute('tex')) {
      $document->setAttribute($node, tex => UnTeX($thing)); } });

Tag('ltx:g', afterClose => sub {
    my ($document, $node) = @_;
    $node->parentNode->removeChild($node) unless $node->hasChildNodes; });

# \savebox -- already defined differntly in C.13 above ?

#**********************************************************************
# C.15 Font Selection
#**********************************************************************
#======================================================================
# C.15.1 Changing the Type Style
#======================================================================
# Text styles.

DefMacroI('\rmdefault',       undef, 'cmr',  locked => 1);
DefMacroI('\sfdefault',       undef, 'cmss', locked => 1);
DefMacroI('\ttdefault',       undef, 'cmtt', locked => 1);
DefMacroI('\bfdefault',       undef, 'bx',   locked => 1);
DefMacroI('\mddefault',       undef, 'm',    locked => 1);
DefMacroI('\itdefault',       undef, 'it',   locked => 1);
DefMacroI('\sldefault',       undef, 'sl',   locked => 1);
DefMacroI('\scdefault',       undef, 'sc',   locked => 1);
DefMacroI('\updefault',       undef, 'n',    locked => 1);
DefMacroI('\encodingdefault', undef, 'OT1');
DefMacroI('\familydefault',   undef, '\rmdefault');
DefMacroI('\seriesdefault',   undef, '\mddefault');
DefMacroI('\shapedefault',    undef, '\updefault');

Let('\mediumseries', '\mdseries');
Let('\normalshape',  '\upshape');

# ? DefMacro('\f@encoding','cm');
DefMacro('\f@family', 'cmr');
DefMacro('\f@series', 'm');
DefMacro('\f@shape',  'n');
DefMacro('\f@size',   '10');

# These do NOT immediately effect the font!
DefMacro('\fontfamily{}', '\edef\f@family{#1}');
DefMacro('\fontseries{}', '\edef\f@series{#1}');
DefMacro('\fontshape{}',  '\edef\f@shape{#1}');

# For fonts not allowed in math!!!
DefPrimitive('\not@math@alphabet@@ {}', sub {
    if ($STATE->lookupValue('IN_MATH')) {
      my $c = ToString($_[1]);
      Warn('unexpected', $c, $_[0], "Command $c invalid in math mode"); }
    return; });

# These DO immediately effect the font!
DefMacroI('\mdseries', undef, '\not@math@alphabet@@{\mddefault}\fontseries{\mddefault}\selectfont');
DefMacroI('\bfseries', undef, '\not@math@alphabet@@{\bfdefault}\fontseries{\bfdefault}\selectfont');

DefMacroI('\rmfamily', undef, '\not@math@alphabet@@{\rmdefault}\fontfamily{\rmdefault}\selectfont');
DefMacroI('\sffamily', undef, '\not@math@alphabet@@{\sfdefault}\fontfamily{\sfdefault}\selectfont');
DefMacroI('\ttfamily', undef, '\not@math@alphabet@@{\ttdefault}\fontfamily{\ttdefault}\selectfont');

DefMacroI('\upshape', undef, '\not@math@alphabet@@{\updefault}\fontshape{\updefault}\selectfont');
DefMacroI('\itshape', undef, '\not@math@alphabet@@{\itdefault}\fontshape{\itdefault}\selectfont');
DefMacroI('\slshape', undef, '\not@math@alphabet@@{\sldefault}\fontshape{\sldefault}\selectfont');
DefMacroI('\scshape', undef, '\not@math@alphabet@@{\scdefault}\fontshape{\scdefault}\selectfont');

DefMacroI('\normalfont', undef,
  '\fontfamily{\rmdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
DefMacroI('\verbatim@font', undef,
  '\fontfamily{\ttdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');

Let('\reset@font', '\normalfont');
DefMacro('\@fontswitch{}{}', '\ifmmode #2\relax\else #1 \fi');

DefPrimitive('\selectfont', sub {
    my $family = ToString(Expand(T_CS('\f@family')));
    my $series = ToString(Expand(T_CS('\f@series')));
    my $shape  = ToString(Expand(T_CS('\f@shape')));
    if    (my $sh = LaTeXML::Common::Font::lookupFontFamily($family)) { MergeFont(%$sh); }
    elsif (LoadFontMap($family)) {
      # Special case hack: Tentatively treat family as the encoding! (typically "U" encoding)
      MergeFont(encoding => $family); }
    elsif (!LookupValue("reported_unrecognized_font_family_$family")) {
      AssignValue("reported_unrecognized_font_family_$family", 1, 'global');
      Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); }
    if    (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); }
    elsif (!LookupValue("reported_unrecognized_font_series_$series")) {
      AssignValue("reported_unrecognized_font_series_$series", 1, 'global');
      Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); }
    if    (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); }
    elsif (!LookupValue("reported_unrecognized_font_shape_$shape")) {
      AssignValue("reported_unrecognized_font_shape_$shape", 1, 'global');
      Info('unexpected', $shape, $_[0], "Unrecognized font shape '$shape'."); }
    return; });

DefMacro('\usefont{}{}{}{}',
  '\fontencoding{#1}\fontfamily{#2}\fontseries{#3}\fontshape{#4}\selectfont');

# If these series or shapes appear in math, they revert it to roman, medium, upright (?)
DefConstructor('\textmd@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { series => 'medium' }, alias => '\textmd',
  beforeDigest => sub { DefMacro('\f@series', 'm'); });
DefConstructor('\textbf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { series => 'bold' }, alias => '\textbf',
  beforeDigest => sub { DefMacro('\f@series', 'b'); });
DefConstructor('\textrm@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { family => 'serif' }, alias => '\textrm',
  beforeDigest => sub { DefMacro('\f@family', 'cm'); });
DefConstructor('\textsf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { family => 'sansserif' }, alias => '\textsf',
  beforeDigest => sub { DefMacro('\f@family', 'cmss'); });
DefConstructor('\texttt@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { family => 'typewriter' }, alias => '\texttt',
  beforeDigest => sub { DefMacro('\f@family', 'cmtt'); });

DefConstructor('\textup@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { shape => 'upright' }, alias => '\textup',
  beforeDigest => sub { DefMacro('\f@shape', ''); });
DefConstructor('\textit@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { shape => 'italic' }, alias => '\textit',
  beforeDigest => sub { DefMacro('\f@shape', 'it'); });
DefConstructor('\textsl@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { shape => 'slanted' }, alias => '\textsl',
  beforeDigest => sub { DefMacro('\f@shape', 'sl'); });
DefConstructor('\textsc@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded      => 1, font => { shape => 'smallcaps' }, alias => '\textsc',
  beforeDigest => sub { DefMacro('\f@shape', 'sc'); });
DefConstructor('\textnormal@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
  bounded => 1, font => { family => 'serif', series => 'medium', shape => 'upright' }, alias => '\textnormal',
  beforeDigest => sub { DefMacro('\f@family', 'cmtt');
    DefMacro('\f@series', 'm');
    DefMacro('\f@shape',  'n'); });
# These really should be robust! which is a source of expand timing issues!
DefMacro('\textmd{}',     '\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi',       protected => 1);
DefMacro('\textbf{}',     '\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi',       protected => 1);
DefMacro('\textrm{}',     '\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi',       protected => 1);
DefMacro('\textsf{}',     '\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi',       protected => 1);
DefMacro('\texttt{}',     '\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi',       protected => 1);
DefMacro('\textup{}',     '\ifmmode\textup@math{#1}\else{\upshape #1}\fi',        protected => 1);
DefMacro('\textit{}',     '\ifmmode\textit@math{#1}\else{\itshape #1}\fi',        protected => 1);
DefMacro('\textsl{}',     '\ifmmode\textsl@math{#1}\else{\slshape #1}\fi',        protected => 1);
DefMacro('\textsc{}',     '\ifmmode\textsc@math{#1}\else{\scshape #1}\fi',        protected => 1);
DefMacro('\textnormal{}', '\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi', protected => 1);

DefPrimitive('\DeclareTextFontCommand{}{}', sub {
    my ($stomach, $cmd, $font) = @_;
    DefConstructorI($cmd, "{}",
      "?#isMath(<ltx:text _noautoclose='1'>#1</ltx:text>)(#1)",
      mode         => 'text', bounded => 1,
      beforeDigest => sub { Digest($font); (); });
    return; });

DefPrimitive('\mathversion{}', sub {
    my ($stomach, $version) = @_;
    $version = ToString($version);
    if ($version eq 'bold') {
      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 1), 'local'); }
    elsif ($version eq 'normal') {
      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 0), 'local'); }
    else { Error('unexpected', $version, $stomach, "Unknown math verison '$version'"); } });

DefMacro('\not@math@alphabet{}{}', '\relax
  \ifmmode
    \@latex@error{Command \noexpand#1 invalid in math mode}%
    {%
    Please
    \ifx#2\relax
      define a new math alphabet
      if you want to use a special font in math mode%
    \else
      use the math alphabet \noexpand#2 instead of
      the #1 command%
    \fi.
    }%
  \fi');
DefMacro('\math@version', 'normal');

DefPrimitive('\DeclareOldFontCommand{}{}{}', sub {
    my ($stomach, $cmd, $font, $mathcmd) = @_;
    DefMacroI($cmd, undef, sub {
        return (LookupValue('IN_MATH') ? $mathcmd->unlist : $font->unlist); });
    #      Tokens(T_CS('\ifmmode'), $mathcmd->unlist, T_CS('\else'), $font->unlist, T_CS('\fi')));
    return; });

DefMacro('\newfont{}{}', '\font#1=#2\relax');

Let('\normalcolor', '\relax');

#======================================================================
# C.15.2 Changing the Type Size
#======================================================================
# Handled in TeX.pool.ltxml

#======================================================================
# C.15.3 Special Symbol
#======================================================================
DefMacro('\symbol{}', '\char#1\relax');

# These in LaTeX, but not in the book...
DefPrimitiveI('\textdollar',               undef, "\$");
DefPrimitiveI('\textemdash',               undef, "\x{2014}");    # EM DASH
DefPrimitiveI('\textendash',               undef, "\x{2013}");    # EN DASH
DefPrimitiveI('\textexclamdown',           undef, UTF(0xA1));     # INVERTED EXCLAMATION MARK
DefPrimitiveI('\textquestiondown',         undef, UTF(0xBF));     # INVERTED QUESTION MARK
DefPrimitiveI('\textquotedblleft',         undef, "\x{201C}");    # LEFT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedblright',        undef, "\x{201D}");    # RIGHT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedbl',             undef, "\"");          # plain ascii DOUBLE QUOTATION
DefPrimitiveI('\textquoteleft',            undef, "\x{2018}");    # LEFT SINGLE QUOTATION MARK
DefPrimitiveI('\textquoteright',           undef, "\x{2019}");    # RIGHT SINGLE QUOTATION MARK
DefPrimitiveI('\textsterling',             undef, UTF(0xA3));     # POUND SIGN
DefPrimitiveI('\textasteriskcentered',     undef, "*");
DefPrimitiveI('\textbackslash',            undef, UTF(0x5C));     # REVERSE SOLIDUS
DefPrimitiveI('\textbar',                  undef, "|");
DefPrimitiveI('\textbraceleft',            undef, "{");
DefPrimitiveI('\textbraceright',           undef, "}");
DefPrimitiveI('\textbullet',               undef, "\x{2022}");    # BULLET
DefPrimitiveI('\textdaggerdbl',            undef, "\x{2021}");    # DOUBLE DAGGER
DefPrimitiveI('\textdagger',               undef, "\x{2020}");    # DAGGER
DefPrimitiveI('\textparagraph',            undef, UTF(0xB6));     # PILCROW SIGN
DefPrimitiveI('\textsection',              undef, UTF(0xA7));     # SECTION SIGN
DefPrimitiveI('\textless',                 undef, "<");
DefPrimitiveI('\textgreater',              undef, ">");
DefPrimitiveI('\textcopyright',            undef, UTF(0xA9));     # COPYRIGHT SIGN
DefPrimitiveI('\textasciicircum',          undef, "^");
DefPrimitiveI('\textasciitilde',           undef, "~");
DefPrimitiveI('\textcompwordmark',         undef, "");            # ???
DefPrimitiveI('\textcapitalcompwordmark',  undef, "");            # ???
DefPrimitiveI('\textascendercompwordmark', undef, "");            # ???
DefPrimitiveI('\textunderscore',           undef, "_");
DefPrimitiveI('\textvisiblespace', undef, "\x{2423}"); # SYMBOL FOR SPACE;  Not really the right symbol!
DefPrimitiveI('\textellipsis',   undef, "\x{2026}");    # HORIZONTAL ELLIPSIS
DefPrimitiveI('\textregistered', undef, UTF(0xAE));     # REGISTERED SIGN
DefPrimitiveI('\texttrademark',  undef, "\x{2122}");    # TRADE MARK SIGN
DefConstructor('\textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
  mode => 'text');
DefConstructor('\@textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
  mode => 'text', locked => 1);
# This is something coming from xetex/xelatex ? Why define this way?
#DefConstructor('\realsuperscript{}', "<ltx:text yoffset='0.5em' _noautoclose='1'>#1</ltx:text>");
DefConstructor('\realsuperscript{}', "<ltx:sup>#1</ltx:sup>",
  mode => 'text');
DefPrimitiveI('\textordfeminine',  undef, UTF(0xAA));    # FEMININE ORDINAL INDICATOR
DefPrimitiveI('\textordmasculine', undef, UTF(0xBA));    # MASCULINE ORDINAL INDICATOR

our %unicode_enclosed_alphanumerics = (
  0  => "\x{24EA}", 1  => "\x{2460}", 2  => "\x{2461}", 3  => "\x{2462}", 4  => "\x{2463}",
  5  => "\x{2464}", 6  => "\x{2465}", 7  => "\x{2466}", 8  => "\x{2467}", 9  => "\x{2468}",
  10 => "\x{2469}", 11 => "\x{246A}", 12 => "\x{246B}", 13 => "\x{246C}", 14 => "\x{246D}",
  15 => "\x{246E}", 16 => "\x{246F}", 17 => "\x{2470}", 18 => "\x{2471}", 19 => "\x{2472}",
  20 => "\x{2473}",
  a  => "\x{24D0}", b => "\x{24D1}", c   => "\x{24D2}", d   => "\x{24D3}", e   => "\x{24D4}",
  f  => "\x{24D5}", g => "\x{24D6}", h   => "\x{24D7}", i   => "\x{24D8}", j   => "\x{24D9}",
  k  => "\x{24DA}", l => "\x{24DB}", 'm' => "\x{24DC}", n   => "\x{24DD}", o   => "\x{24DE}",
  p  => "\x{24DF}", q => "\x{24E0}", r   => "\x{24E1}", 's' => "\x{24E2}", t   => "\x{24E3}",
  u  => "\x{24E4}", v => "\x{24E5}", w   => "\x{24E6}", x   => "\x{24E7}", y   => "\x{24E8}",
  z  => "\x{24E9}", A => "\x{24B6}", B   => "\x{24B7}", C   => "\x{24B8}", D   => "\x{24B9}",
  E  => "\x{24BA}", F => "\x{24BB}", G   => "\x{24BC}", H   => "\x{24BD}", I   => "\x{24BE}",
  J  => "\x{24BF}", K => "\x{24C0}", L   => "\x{24C1}", 'M' => "\x{24C2}", N   => "\x{24C3}",
  O  => "\x{24C4}", P => "\x{24C5}", Q   => "\x{24C6}", R   => "\x{24C7}", 'S' => "\x{24C8}",
  T  => "\x{24C9}", U => "\x{24CA}", V   => "\x{24CB}", W   => "\x{24CC}", X   => "\x{24CD}",
  Y  => "\x{24CE}", Z => "\x{24CF}");
DefPrimitive('\textcircled {}', sub {
    my ($stomach, $arg) = @_;
    my $text = ToString($arg);
    # if we know of a circled unicode character - use it,
    # if not - use the combining accent.
    # For now supports the range 0 to 20 as well as the latin alphabet.
    my $is_number = $text =~ /^\d+$/;
    my $content   = $unicode_enclosed_alphanumerics{$text} || "$text\x{20DD}";
    my %props     = $STATE->lookupValue('IN_MATH') ?
      (role => ($is_number ? 'NUMBER' : 'UNKNOWN'), meaning => "circled-$text") : ();
    return Box($content, undef, undef, undef, %props); });

DefPrimitiveI('\SS', undef, 'SS',    # ?
  locked => 1);                      # cause it shows up in uclist!
DefMacroI('\dag',  undef, '\ifmmode{\dagger}\else\textdagger\fi');
DefMacroI('\ddag', undef, '\ifmmode{\ddagger}\else\textdaggerdbl\fi');

DefConstructor('\sqrtsign Digested',
  "<ltx:XMApp><ltx:XMTok meaning='square-root'/><ltx:XMArg>#1</ltx:XMArg></ltx:XMApp>");

DefPrimitiveI('\mathparagraph',  undef, UTF(0xB6));
DefPrimitiveI('\mathsection',    undef, UTF(0xA7));
DefPrimitiveI('\mathdollar',     undef, '$');
DefPrimitiveI('\mathsterling',   undef, UTF(0xA3));
DefPrimitiveI('\mathunderscore', undef, '_');
DefPrimitiveI('\mathellipsis',   undef, "\x{2026}");

# Are these glyph "pieces" or use alone?
DefMathI('\arrowvert', undef, "|",        role => 'VERTBAR');
DefMathI('\Arrowvert', undef, "\x{2225}", role => 'VERTBAR');

# The following are glyph "pieces"...
DefPrimitiveI('\braceld', undef, "\x{239D}");    #   left brace down part
DefPrimitiveI('\bracelu', undef, "\x{239B}");    #   left brace up part
DefPrimitiveI('\bracerd', undef, "\x{23A0}");    #   right brace down part
DefPrimitiveI('\braceru', undef, "\x{239E}");    #   right brace up part

DefMathI('\cdotp', undef, "\x{22C5}", role => 'MULOP');
DefMathI('\ldotp', undef, ".",        role => 'MULOP');
DefMathI('\intop', undef, "\x{222B}", role => 'INTOP', meaning => 'integral',
  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
DefMathI('\ointop', undef, "\x{222E}", role => 'INTOP', meaning => 'contour-integral',
  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
# WHat are these? They look like superscripted parentheses, or combining accents!
# \lhook
# \rhook
Let('\gets', '\leftarrow');

DefPrimitiveI('\lmoustache', undef, "\x{23B0}");
DefPrimitiveI('\rmoustache', undef, "\x{23B1}");
DefMathI('\mapstochar', undef, "\x{21A6}", role => 'ARROW', meaning => 'maps-to');
DefMathI('\owns',       undef, "\x{220B}", role => 'RELOP', meaning => 'contains');

# \skew{}{}{} ????

# \symbol lookup symbol in font by index?
#**********************************************************************
# Other stuff
#**********************************************************************
# Some stuff that got missed in the appendices ?
RawTeX(<<'EoTeX');
\def\@namedef#1{\expandafter\def\csname #1\endcsname}
\def\@nameuse#1{\csname #1\endcsname}
\def\@cons#1#2{\begingroup\let\@elt\relax\xdef#1{#1\@elt #2}\endgroup}
\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}
\def\@carcube#1#2#3#4\@nil{#1#2#3}
\def\nfss@text#1{{\mbox{#1}}}
\def\@sect#1#2#3#4#5#6[#7]#8{}
EoTeX

Let('\@begindocumenthook', '\@empty');
DefMacroI('\@preamblecmds', undef, Tokens());
DefMacro('\@ifdefinable DefToken {}', sub {
    my ($gullet, $token, $if) = @_;
    if (isDefinable($token)) {
      return $if->unlist }
    else {
      my ($slash, @s) = ExplodeText($token->toString);
      DefMacroI('\reserved@a', undef, Tokens(@s));
      return (T_CS('\@notdefinable')); } });

Let('\@@ifdefinable', '\@ifdefinable');

DefMacro('\@rc@ifdefinable DefToken {}', sub {
    my ($gullet, $token, $if) = @_;
    Let('\@ifdefinable', '\@@ifdefinable');
    return $if->unlist; });

DefMacroI('\@notdefinable', undef, <<'EOL');
\@latex@error{%
  Command \@backslashchar\reserved@a\space
  already defined.
  Or name \@backslashchar\@qend... illegal,
  see p.192 of the manual}
EOL

DefMacroI('\@qend',   undef, Tokens(Explode('end')));
DefMacroI('\@qrelax', undef, Tokens(Explode('relax')));
DefMacroI('\@spaces', undef, '\space\space\space\space');
Let('\@sptoken', T_SPACE);

DefMacroI('\@uclclist', undef, '\oe\OE\o\O\ae\AE\dh\DH\dj\DJ\l\L\ng\NG\ss\SS\th\TH');

RawTeX(<<'EOL');
\DeclareRobustCommand{\MakeUppercase}[1]{{%
      \def\i{I}\def\j{J}%
      \def\reserved@a##1##2{\let##1##2\reserved@a}%
      \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
      \let\UTF@two@octets@noexpand\@empty
      \let\UTF@three@octets@noexpand\@empty
      \let\UTF@four@octets@noexpand\@empty
      \protected@edef\reserved@a{\uppercase{#1}}%
      \reserved@a
   }}
\DeclareRobustCommand{\MakeLowercase}[1]{{%
      \def\reserved@a##1##2{\let##2##1\reserved@a}%
      \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
      \let\UTF@two@octets@noexpand\@empty
      \let\UTF@three@octets@noexpand\@empty
      \let\UTF@four@octets@noexpand\@empty
      \protected@edef\reserved@a{\lowercase{#1}}%
      \reserved@a
   }}
\protected@edef\MakeUppercase#1{\MakeUppercase{#1}}
\protected@edef\MakeLowercase#1{\MakeLowercase{#1}}
EOL

#======================================================================

DefMacroI('\@ehc', undef, "I can't help");

DefMacro('\@gobble{}',           Tokens());
DefMacro('\@gobbletwo{}{}',      Tokens());
DefMacro('\@gobblefour{}{}{}{}', Tokens());
DefMacro('\@firstofone{}',       sub { $_[1]; });
Let('\@iden', '\@firstofone');
DefMacro('\@firstoftwo{}{}',     sub { $_[1]; });
DefMacro('\@secondoftwo{}{}',    sub { $_[2]; });
DefMacro('\@thirdofthree{}{}{}', sub { $_[3]; });
DefMacro('\@expandtwoargs{}{}{}', sub {
    ($_[1]->unlist, T_BEGIN, Expand($_[2])->unlist, T_END, T_BEGIN, Expand($_[3])->unlist, T_END); });
DefMacro('\@makeother{}', sub {
    my $letter = ToString($_[1]); $letter =~ s/^\\//;
    AssignCatcode($letter => CC_OTHER, 'local'); });

RawTeX(<<'EoTeX');
{\catcode`\^^M=13 \gdef\obeycr{\catcode`\^^M13 \def^^M{\\\relax}%
    \@gobblecr}%
{\catcode`\^^M=13 \gdef\@gobblecr{\@ifnextchar
\@gobble\ignorespaces}}%
\gdef\restorecr{\catcode`\^^M5 }}
EoTeX
RawTeX(<<'EoTeX');
\begingroup
  \catcode`P=12
  \catcode`T=12
  \lowercase{
    \def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
  \expandafter\endgroup\x
\def\strip@pt{\expandafter\rem@pt\the}
\def\strip@prefix#1>{}
\def\@sanitize{\@makeother\ \@makeother\\\@makeother\$\@makeother\&%
\@makeother\#\@makeother\^\@makeother\_\@makeother\%\@makeother\~}
\def \@onelevel@sanitize #1{%
  \edef #1{\expandafter\strip@prefix
           \meaning #1}%
}
\def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&%
  \do\#\do\^\do\_\do\%\do\~}
EoTeX

DefMacroI('\nfss@catcodes', undef, <<'EOMacro');
    \makeatletter
    \catcode`\ 9%
     \catcode`\^^I9%
     \catcode`\^^M9%
     \catcode`\\\z@
     \catcode`\{\@ne
     \catcode`\}\tw@
     \catcode`\#6%
     \catcode`\^7%
     \catcode`\%14%
   \@makeother\<%
   \@makeother\>%
   \@makeother\*%
   \@makeother\.%
   \@makeother\-%
   \@makeother\/%
   \@makeother\[%
   \@makeother\]%
   \@makeother\`%
   \@makeother\'%
   \@makeother\"%
EOMacro
DefMacroI('\ltx@hard@MessageBreak', undef, '^^J');

sub make_message {
  my ($cmd, @args) = @_;
  my $stomach  = $STATE->getStomach;
  my $lead_arg = ToString(shift(@args)) || '';
  $lead_arg =~ s/(?:\\\@?spaces?)+//g;
  my $type = $lead_arg || $cmd;
  $stomach->bgroup;
  Let('\protect', '\string');
  # Note that the arg really should be digested to get to the underlying text,
  # but why tempt fate, when we're already making an error message?
  #
  # We have to expand padded with two \@spaces, to avoid pointless errors.
  # e.g. a trailing "\csq@noline" from csquotes.sty, which was previously
  # \let\csq@noline\@gobble
  # will produce a "gobble has no argument" error on *every* message.
  my $message = join(" ",
    map { ToString(Expand($_, T_CS('\@spaces'), T_CS('\@spaces'))) } @args);
  $type    =~ s/(?:\\\@?spaces?)+/ /g;
  $message =~ s/(?:\\\@?spaces?)+/ /g;
  $message =~ s/\\MessageBreak/\n/g;
  $stomach->egroup;
  return ('latex', $type, $stomach, $message); }

# LaTeX 3 is loaded roughly here in the latex.ltx flow,
# early and always.
# ---
# Postponed until we can address the *performance penalty*
# instead, add these LoadPool calls to packages that expect them.
# ---
# LoadPool('expl3');

DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this.
DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[1], $_[2], $_[3], $_[4])); });
DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[1], $_[2])); });
DefPrimitive('\GenericInfo{}{}',    sub { Info(make_message('\GenericInfo',    $_[1], $_[2])); });

Let('\MessageBreak', '\relax');
RawTeX(<<'EoTeX');
\gdef\PackageError#1#2#3{%
  \GenericError{%
      (#1)\@spaces\@spaces\@spaces\@spaces
   }{%
      Package #1 Error: #2%
   }{%
      See the #1 package documentation for explanation.%
   }{#3}%
}
\def\PackageWarning#1#2{%
  \GenericWarning{%
      (#1)\@spaces\@spaces\@spaces\@spaces
   }{%
      Package #1 Warning: #2%
   }%
}
\def\PackageWarningNoLine#1#2{%
  \PackageWarning{#1}{#2\@gobble}}
\def\PackageInfo#1#2{%
  \GenericInfo{%
      (#1) \@spaces\@spaces\@spaces
   }{%
      Package #1 Info: #2%
   }%
}
\def\ClassError#1#2#3{%
  \GenericError{%
      (#1) \space\@spaces\@spaces\@spaces
   }{%
      Class #1 Error: #2%
   }{%
      See the #1 class documentation for explanation.%
   }{#3}%
}
\def\ClassWarning#1#2{%
  \GenericWarning{%
      (#1) \space\@spaces\@spaces\@spaces
   }{%
      Class #1 Warning: #2%
   }%
}
\def\ClassWarningNoLine#1#2{%
  \ClassWarning{#1}{#2\@gobble}}
\def\ClassInfo#1#2{%
  \GenericInfo{%
      (#1) \space\space\@spaces\@spaces
   }{%
      Class #1 Info: #2%
   }%
}
\def\@latex@error#1#2{%
  \GenericError{%
      \space\space\space\@spaces\@spaces\@spaces
   }{%
      LaTeX Error: #1%
   }{%
      See the LaTeX manual or LaTeX Companion for explanation.%
   }{#2}%
}
\def\@latex@warning#1{%
  \GenericWarning{%
      \space\space\space\@spaces\@spaces\@spaces
   }{%
      LaTeX Warning: #1%
   }%
}
\def\@latex@warning@no@line#1{%
  \@latex@warning{#1\@gobble}}
\def\@latex@info#1{%
  \GenericInfo{%
      \@spaces\@spaces\@spaces
   }{%
      LaTeX Info: #1%
   }%
}
\def\@latex@info@no@line#1{%
  \@latex@info{#1\@gobble}}
EoTeX

DefMacro('\@setsize{}{}{}{}', '');
DefMacro('\hexnumber@ {}', '\ifcase\number#1
 0\or 1\or 2\or 3\or 4\or 5\or 6\or 7\or 8\or
 9\or A\or B\or C\or D\or E\or F\fi');

DefMacro('\on@line', ' on input line \the\inputlineno');
Let('\@warning',  '\@latex@warning');
Let('\@@warning', '\@latex@warning@no@line');

DefMacro('\G@refundefinedtrue', '');

DefMacro('\@nomath{}',
  '\relax\ifmmode\@font@warning{Command \noexpand#1invalid in math mode}\fi');
DefMacro('\@font@warning{}',
  '\GenericWarning{(Font)\@spaces\@spaces\@spaces\space\space}{LaTeX Font Warning: #1}');

#======================================================================

RawTeX(<<'EOTeX');
  \chardef\@xxxii=32
  \mathchardef\@Mi=10001
  \mathchardef\@Mii=10002
  \mathchardef\@Miii=10003
  \mathchardef\@Miv=10004
  \def\@fontenc@load@list{\@elt{T1,OT1}}
EOTeX
DefMacroI('\@vpt',    undef, '5');
DefMacroI('\@vipt',   undef, '6');
DefMacroI('\@viipt',  undef, '7');
DefMacroI('\@viiipt', undef, '8');
DefMacroI('\@ixpt',   undef, '9');
DefMacroI('\@xpt',    undef, '10');
DefMacroI('\@xipt',   undef, '10.95');
DefMacroI('\@xiipt',  undef, '12');
DefMacroI('\@xivpt',  undef, '14.4');
DefMacroI('\@xviipt', undef, '17.28');
DefMacroI('\@xxpt',   undef, '20.74');
DefMacroI('\@xxvpt',  undef, '24.88');

DefMacroI('\@tempa',  undef, '');
DefMacroI('\@tempb',  undef, '');
DefMacroI('\@tempc',  undef, '');
DefMacroI('\@gtempa', undef, '');

RawTeX(<<'EOTeX');
\long\def \loop #1\repeat{%
  \def\iterate{#1\relax  % Extra \relax
               \expandafter\iterate\fi
               }%
  \iterate
  \let\iterate\relax
}
\newdimen\@ydim
\let\@@hyph=\-
\newbox\@arstrutbox
\newbox\@begindvibox
\newcount\@botnum
\newdimen\@botroom
\newcount\@chclass
\newcount\@chnum
\newdimen\@clnht
\newdimen\@clnwd
\newdimen\@colht
\newcount\@colnum
\newdimen\@colroom
\newbox\@curfield
\newbox\@curline
\newcount\@currtype
\newcount\@curtab
\newcount\@curtabmar
\newbox\@dashbox
\newcount\@dashcnt
\newdimen\@dashdim
\newcount\@dbltopnum
\newdimen\@dbltoproom
\let\@dischyph=\-
\newcount\@enumdepth
\newcount\@floatpenalty
\newdimen\@fpmin
\newcount \@fpstype
\newcount\@highpenalty
\newcount\@hightab
\newbox\@holdpg
\newinsert \@kludgeins
\newcount\@lastchclass
\newbox\@leftcolumn
\newbox\@linechar
\newdimen\@linelen
\newcount\@lowpenalty
\newdimen\@maxdepth
\newcount\@medpenalty
\newdimen\@mparbottom \@mparbottom\z@
\newinsert\@mpfootins
\newcount\@mplistdepth
\newcount\@multicnt
\newcount\@nxttabmar
\newbox\@outputbox
\newdimen\@pagedp
\newdimen\@pageht
\newbox\@picbox
\newdimen\@picht
\newdimen \@reqcolroom
\newskip\@rightskip \@rightskip \z@skip
\newcount\@savsf
\newdimen\@savsk
\newcount\@secpenalty
\def\@sqrt[#1]{\root #1\of}
\newbox\@tabfbox
\newcount\@tabpush
\newdimen \@textfloatsheight
\newdimen\@textmin
\newcount\@topnum
\newdimen\@toproom
\newcount\@xarg
\newdimen\@xdim
\newcount\@yarg
\newdimen\@ydim
\newcount\@yyarg
\newtoks\every@math@size
\newif \if@fcolmade
\newdimen\lower@bound
\newcount\par@deathcycles
\newdimen\upper@bound
\newif\if@insert
\newif\if@colmade
\newif\if@specialpage   \@specialpagefalse
\newif\if@firstcolumn   \@firstcolumntrue
\newif\if@twocolumn     \@twocolumnfalse
\newif\if@twoside       \@twosidefalse
\newif\if@reversemargin \@reversemarginfalse
\newif\if@mparswitch    \@mparswitchfalse
\newif\if@firstamp      \@firstampfalse
\newcount\col@number    \@ne
\newread\@inputcheck
\newwrite\@unused
\newwrite\@mainaux
\newwrite\@partaux
\let\@auxout=\@mainaux
\openout\@mainaux\jobname.aux
\newcount\@clubpenalty
\@clubpenalty \clubpenalty
\newif\if@filesw \@fileswtrue
\newif\if@partsw \@partswfalse
\def\@tempswafalse{\let\if@tempswa\iffalse}
\def\@tempswatrue{\let\if@tempswa\iftrue}
\let\if@tempswa\iffalse
\newcount\@tempcnta
\newcount\@tempcntb
\newif\if@tempswa
\newdimen\@tempdima
\newdimen\@tempdimb
\newdimen\@tempdimc
\newbox\@tempboxa
\newskip\@tempskipa
\newskip\@tempskipb
\newtoks\@temptokena
\newskip\@flushglue \@flushglue = 0pt plus 1fil
\newif\if@afterindent\@afterindenttrue
\newbox\rootbox

\newcount\@eqcnt
\newcount\@eqpen
\newif\if@eqnsw\@eqnswtrue
\newskip\@centering
\@centering = 0pt plus 1000pt
\let\@eqnsel=\relax

 \long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
      #2\relax}\fi}
 \long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum
          \else\expandafter\@gobble\fi{#1}}
 \long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi}
 \long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim
         \else\expandafter\@gobble\fi{#1}}
 \long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi}
 \long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw
          \else\@gobbletwo\fi{#1}\fi}
\def\@nnil{\@nil}
\def\@fornoop#1\@@#2#3{}
\long\def\@for#1:=#2\do#3{%
  \expandafter\def\expandafter\@fortmp\expandafter{#2}%
  \ifx\@fortmp\@empty \else
    \expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi}
\long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else
       #5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi}
\long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
       \expandafter\@fornoop \else
      #4\relax\expandafter\@iforloop\fi#2\@@#3{#4}}
\def\@tfor#1:={\@tf@r#1 }
\long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else
    \@tforloop#2\@nil\@nil\@@#1{#3}\fi}
\long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
       \expandafter\@fornoop \else
      #4\relax\expandafter\@tforloop\fi#2\@@#3{#4}}
\long\def\@break@tfor#1\@@#2#3{\fi\fi}
\def\@removeelement#1#2#3{%
  \def\reserved@a##1,#1,##2\reserved@a{##1,##2\reserved@b}%
  \def\reserved@b##1,\reserved@b##2\reserved@b{%
    \ifx,##1\@empty\else##1\fi}%
  \edef#3{%
    \expandafter\reserved@b\reserved@a,#2,\reserved@b,#1,\reserved@a}}
\def\remove@to@nnil#1\@nnil{}
\def\remove@angles#1>{\set@simple@size@args}
\def\remove@star#1*{#1}
\def\@defaultunits{\afterassignment\remove@to@nnil}

\newif\ifmath@fonts \math@fontstrue
\newbox\@labels
\newif\if@inlabel \@inlabelfalse
\newif\if@newlist   \@newlistfalse
\newif\if@noparitem \@noparitemfalse
\newif\if@noparlist \@noparlistfalse
\newif\if@noitemarg \@noitemargfalse
\newif\if@nmbrlist  \@nmbrlistfalse

\def\glb@settings{}%
EOTeX

DefMacroI('\@height',      undef, 'height');
DefMacroI('\@width',       undef, 'width');
DefMacroI('\@depth',       undef, 'depth');
DefMacroI('\@minus',       undef, 'minus');
DefMacroI('\@plus',        undef, 'plus');
DefMacroI('\hb@xt@',       undef, '\hbox to');
DefMacroI('\hmode@bgroup', undef, '\leavevmode\bgroup');

DefMacroI('\@backslashchar', undef, T_OTHER('\\'));
DefMacroI('\@percentchar',   undef, T_OTHER('%'));
DefMacroI('\@charlb',        undef, T_LETTER('{'));
DefMacroI('\@charrb',        undef, T_LETTER('}'));
#======================================================================

DefMacroI('\check@mathfonts', undef, Tokens());
DefMacro('\fontsize{}{}', Tokens());
# https://tex.stackexchange.com/questions/112492/setfontsize-vs-fontsize#112501
DefMacro('\@setfontsize{}{}{}', '\let\@currsize#1');

DefMacroI('\@vpt',    undef, T_OTHER('5'));
DefMacroI('\@vipt',   undef, T_OTHER('6'));
DefMacroI('\@viipt',  undef, T_OTHER('7'));
DefMacroI('\@viiipt', undef, T_OTHER('8'));
DefMacroI('\@ixpt',   undef, T_OTHER('9'));
DefMacro('\@xpt',    '10');
DefMacro('\@xipt',   '10.95');
DefMacro('\@xiipt',  '12');
DefMacro('\@xivpt',  '14.4');
DefMacro('\@xviipt', '17.28');
DefMacro('\@xxpt',   '20.74');
DefMacro('\@xxvpt',  '24.88');
DefMacro('\vpt',     '\edef\f@size{\@vpt}\rm');
DefMacro('\vipt',    '\edef\f@size{\@vipt}\rm');
DefMacro('\viipt',   '\edef\f@size{\@viipt}\rm');
DefMacro('\viiipt',  '\edef\f@size{\@viiipt}\rm');
DefMacro('\ixpt',    '\edef\f@size{\@ixpt}\rm');
DefMacro('\xpt',     '\edef\f@size{\@xpt}\rm');
DefMacro('\xipt',    '\edef\f@size{\@xipt}\rm');
DefMacro('\xiipt',   '\edef\f@size{\@xiipt}\rm');
DefMacro('\xivpt',   '\edef\f@size{\@xivpt}\rm');
DefMacro('\xviipt',  '\edef\f@size{\@xviipt}\rm');
DefMacro('\xxpt',    '\edef\f@size{\@xxpt}\rm');
DefMacro('\xxvpt',   '\edef\f@size{\@xxvpt}\rm');

DefMacroI('\defaultscriptratio',       undef, '.7');
DefMacroI('\defaultscriptscriptratio', undef, '.5');

#======================================================================
DefMacroI('\loggingoutput', undef, Tokens());
DefMacroI('\loggingall',    undef, Tokens());
DefMacroI('\tracingfonts',  undef, Tokens());
DefMacroI('\showoverfull',  undef, Tokens());
DefMacroI('\showoutput',    undef, Tokens());

#======================================================================
# Various symbols, accents, etc from Chapter 3 defined in TeX.pool

#**********************************************************************
# Semi-Undocumented stuff
#**********************************************************************
DefMacro('\@ifnextchar DefToken {}{}', sub {
    my ($gullet, $token, $if, $else) = @_;
    my $next = $gullet->readNonSpace;
    # NOTE: Not actually substituting, but collapsing ## pairs!!!!
    # use \egroup for $next, if we've fallen off end?
    ((XEquals($token, (defined $next ? $next : T_END)) ? $if : $else)->unlist,
      (defined $next ? ($next) : ())); });
Let('\kernel@ifnextchar', '\@ifnextchar');
Let('\@ifnext',           '\@ifnextchar');    # ????

# Hacky version matches multiple chars! but does NOT expand
DefMacro('\@ifnext@n {} {}{}', sub {
    my ($gullet, $tokens, $if, $else) = @_;
    my @toks = $tokens->unlist;
    my @read = ();
    while (my $t = $gullet->readToken) {
      push(@read, $t);
      if ($t->equals($toks[0])) { shift(@toks); }
      else                      { last; } }
    return Tokens((@toks ? $else->unlist : $if->unlist), @read); });

DefMacro('\@ifstar {}{}', sub {
    my ($gullet, $if, $else) = @_;
    my $next = $gullet->readNonSpace;
    if (T_OTHER('*')->equals($next)) {
      $if; }
    else {
      ($else, ($next ? $next : ())); } });

DefMacro('\@dblarg {}',    '\kernel@ifnextchar[{#1}{\@xdblarg{#1}}');
DefMacro('\@xdblarg {}{}', '#1[{#2}]{#2}');

DefMacro('\@testopt{}{}', sub {
    my ($gullet, $cmd, $option) = @_;
    ($gullet->ifNext(T_OTHER('[')) ? $cmd->unlist
      : ($cmd->unlist, T_OTHER('['), $option->unlist, T_OTHER(']'))); });
RawTeX(<<'EoTeX');
\def\@protected@testopt#1{%%
  \ifx\protect\@typeset@protect
    \expandafter\@testopt
  \else
    \@x@protect#1%
  \fi}
EoTeX

Let('\l@ngrel@x', '\relax');    # Never actually used anywhere, but...
DefMacro('\@star@or@long{}', '\@ifstar{\let\l@ngrel@x\relax#1}{\let\l@ngrel@x\long#1}');

# maybe this is easiest just to punt.
RawTeX(<<'EoTeX');
\def\in@#1#2{%
 \def\in@@##1#1##2##3\in@@{%
  \ifx\in@##2\in@false\else\in@true\fi}%
 \in@@#2#1\in@\in@@}
\newif\ifin@
EoTeX

DefMacro('\IfFileExists{}{}{}', sub {
    my ($gullet, $file, $if, $else) = @_;
    my $file_string = ToString(Expand($file));
    if (FindFile($file_string)) {
      DefMacro('\@filef@und', '"' . $file_string . '" ');
      return ($if->unlist); }
    else {
      return ($else->unlist); } });

DefMacro('\InputIfFileExists{}{}{}', sub {
    my ($gullet, $file, $if, $else) = @_;
    $file = Expand($file);
    my $file_string = ToString($file);
    if (FindFile($file_string)) {
      DefMacro('\@filef@und', '"' . $file_string . '" ');
      return Tokens($if, T_CS('\@addtofilelist'), T_BEGIN, $file, T_END,
        T_CS('\ltx@input'), T_BEGIN, $file, T_END); }
    else { return ($else->unlist); } });

#======================================================================
# Hair
DefPrimitiveI('\makeatletter', undef, sub { AssignCatcode('@' => CC_LETTER, 'local'); });
DefPrimitiveI('\makeatother',  undef, sub { AssignCatcode('@' => CC_OTHER,  'local'); });

#**********************************************************************
#**********************************************************************
# Sundry (is this ams ?)
DefPrimitiveI('\textprime', undef, UTF(0xB4));    # ACUTE ACCENT

Let('\endgraf', '\par');
Let('\endline', '\cr');
#**********************************************************************
# Should be defined in each (or many) package, but it's not going to
# get set correctly or maintained, so...
DefMacroI('\fileversion', undef, Tokens());
DefMacroI('\filedate',    undef, Tokens());

# Ultimately these may be overridden by babel, or otherwise,
# various of these are defined in various places by different classes.
DefMacroI('\chaptername', undef, 'Chapter');
DefMacroI('\partname',    undef, 'Part');
# The rest of these are defined in some classes, but not most.
#DefMacroI('\sectionname',       undef, 'Section');
#DefMacroI('\subsectionname',    undef, 'Subsection');
#DefMacroI('\subsubsectionname', undef, 'Subsubsection');
#DefMacroI('\paragraphname',     undef, 'Paragraph');
#DefMacroI('\subparagraphname',  undef, 'Subparagraph');

DefMacroI('\appendixname', undef, 'Appendix');
# These aren't defined in LaTeX,
# these definitions will give us more meaningful typerefnum's
DefMacroI('\sectiontyperefname',       undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\subsectiontyperefname',    undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\subsubsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\paragraphtyperefname',     undef, '\lx@paragraphsign\lx@ignorehardspaces');
DefMacroI('\subparagraphtyperefname',  undef, '\lx@paragraphsign\lx@ignorehardspaces');

#**********************************************************************
# Stuff that would appear in the aux file... maybe somebody uses it?
DefMacro('\bibdata{}',          Tokens());
DefMacro('\bibcite{}{}',        Tokens());
DefMacro('\citation{}',         Tokens());
DefMacro('\contentsline{}{}{}', Tokens());
DefMacro('\newlabel{}{}',       Tokens());

DefMacroI('\stop',                 undef, sub { $_[0]->closeMouth(1); return; });
DefMacroI('\ignorespacesafterend', undef, Tokens());
Let('\mathgroup', '\fam');
Let('\mathalpha', '\relax');

#\def\mathhexbox#1#2#3{\mbox{$\m@th \mathchar"#1#2#3$}}
DefPrimitive('\mathhexbox {}{}{}', sub {
    my ($stomach, $a, $b, $c) = @_;
    my $n = ToString($a) * 256 + ToString($b) * 16 + ToString($c);
    my ($role, $glyph) = decodeMathChar($n);
    return Box($glyph, LookupValue('font')->specialize($glyph)); });

DefMacroI('\nocorrlist', undef, ',.');
Let('\nocorr',    '\relax');
Let('\check@icl', '\@empty');
Let('\check@icr', '\@empty');
DefMacro('\text@command{}',                          '');    # ?
DefMacro('\check@nocorr@ Until:\nocorr Until:\@nil', '');
RawTeX('\newif\ifmaybe@ic');

DefMacroI('\maybe@ic',  undef, '');
DefMacroI('\maybe@ic@', undef, '');
# \t@st@ic
DefMacroI('\sw@slant',    undef, '');
DefMacroI('\fix@penalty', undef, '');

DefPrimitiveI('\@@end', undef, sub { $_[0]->getGullet->flush; return; });

#**********************************************************************
# Modern pdflatex seems to come with hyphenation tables predefined
# for many languages. We don't need or use hyphenation tables,
# but some (versions of some) software (babel), check for
# the presence of these \l@<language> macros
# But also see \iflanguage (re)defined in babel.def.ltxml
RawTeX(<<'EoTeX');
\newlanguage\l@english
\newlanguage\l@usenglishmax
\newlanguage\l@USenglish
\newlanguage\l@dumylang
\newlanguage\l@nohyphenation
\newlanguage\l@arabic
\newlanguage\l@basque
\newlanguage\l@bulgarian
\newlanguage\l@coptic
\newlanguage\l@welsh
\newlanguage\l@czech
\newlanguage\l@slovak
\newlanguage\l@german
\newlanguage\l@ngerman
\newlanguage\l@danish
\newlanguage\l@esperanto
\newlanguage\l@spanish
\newlanguage\l@catalan
\newlanguage\l@galician
\newlanguage\l@estonian
\newlanguage\l@farsi
\newlanguage\l@finnish
\newlanguage\l@french
\newlanguage\l@greek
\newlanguage\l@monogreek
\newlanguage\l@ancientgreek
\newlanguage\l@croatian
\newlanguage\l@hungarian
\newlanguage\l@interlingua
\newlanguage\l@ibycus
\newlanguage\l@indonesian
\newlanguage\l@icelandic
\newlanguage\l@italian
\newlanguage\l@latin
\newlanguage\l@mongolian
\newlanguage\l@dutch
\newlanguage\l@norsk
\newlanguage\l@polish
\newlanguage\l@portuguese
\newlanguage\l@pinyin
\newlanguage\l@romanian
\newlanguage\l@russian
\newlanguage\l@slovenian
\newlanguage\l@uppersorbian
\newlanguage\l@serbian
\newlanguage\l@swedish
\newlanguage\l@turkish
\newlanguage\l@ukenglish
\newlanguage\l@ukrainiane
EoTeX

#**********************************************************************
DefPrimitive('\protected@write Number {}{}', sub {
    my ($stomach, $port, $prelude, $tokens) = @_;
    $port = ToString($port);
    $stomach->bgroup;
    Let('\thepage', '\relax');
    my @stuff = Digest($prelude);
    Let('\protect', '\@unexpandable@protect');
    if (my $filename = LookupValue('output_file:' . $port)) {
      my $handle   = $filename . '_contents';
      my $contents = LookupValue($handle);
      AssignValue($handle => $contents . UnTeX($tokens) . "\n", 'global'); }
    else {
      Note(UnTeX($tokens)); }
    $stomach->egroup;
    return @stuff; });

#**********************************************************************
# LaTeX now includes fixltx2e by default.
# https://www.latex-project.org/news/latex2e-news/ltnews22.pdf

# This package allows you to define the font used for
# emphasis (\emph) within emphasis.
# For latexml, that styling should be left to the ultimate output,
# so we just define the command as a dummy.
DefMacro('\eminnershape', "");

# Undoubtedly not good enough
DefMacro('\TextOrMath{}{}', '\ifmmode#2\else#1\fi');

DefConstructor('\textsubscript{}', "<ltx:sub>#1</ltx:sub>",
  mode => 'text');

#**********************************************************************
# We need this bit from utf8.def for textcomp
DefPrimitive('\DeclareUnicodeCharacter Expanded {}', sub {
    my ($stomach, $hexcode, $expansion) = @_;
    my $char = $hexcode->toString();
    if ($char =~ /^[0-9a-fA-F]+$/) {
      if ((my $cp = hex($char)) <= 0x10FFFF) {
        $char = UTF($cp);
        AssignCatcode($char, CC_ACTIVE);
        DefMacroI(T_ACTIVE($char), undef, $expansion); }
      else {
        Error('unexpected', $char, $stomach,
          "$char too large for Unicode. Values between 0 and 10FFFF are permitted."); } }
    else {
      Error('unexpected', $char, $stomach,
        "'$char' is not a hexadecimal number."); } });

# LaTeX now includes textcomp by default.
RequirePackage('textcomp');

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Expl3 "Experimental LaTeX 3" is no longer Experimental!
# It is beginning to be built into latex.ltx
# We WILL need a new strategy to keep up; probably based in some form
# of pre-read/pre-processed latex.ltx !
#
# For now, a few macros required by other packages will be included:
DefMacroI(T_CS('\hook_gput_code:nnn'), '{}{}{}', '');
DefMacro('\NewHook{}',                    '');
DefMacro('\NewReversedHook{}',            '');
DefMacro('\NewMirroredHookPair{}{}',      '');
DefMacro('\ActivateGenericHook{}',        '');
DefMacro('\DisableGenericHook{}',         '');
DefMacro('\AddToHook{}[]{}',              '');
DefMacro('\AddToHookNext{}{}',            '');
DefMacro('\ClearHookNext{}',              '');
DefMacro('\RemoveFromHook{}[]',           '');
DefMacro('\SetDefaultHookLabel{}',        '');
DefMacro('\PushDefaultHookLabel{}',       '');
DefMacro('\PopDefaultHookLabel',          '');
DefMacro('\UseHook{}',                    '');
DefMacro('\UseOneTimeHook{}',             '');
DefMacro('\ShowHook{}',                   '');
DefMacro('\LogHook{}',                    '');
DefMacro('\DebugHooksOn',                 '');
DefMacro('\DebugHooksOff',                '');
DefMacro('\DeclareHookRule{}{}{}{}',      '');
DefMacro('\DeclareDefaultHookRule{}{}{}', '');
DefMacro('\ClearHookRule{}{}{}',          '');
DefMacro('\IfHookEmptyTF{}{}{}',          '#3');
DefMacro('\IfHookExistsTF{}{}{}',         '#3');
DefMacro('\MakeTextLowercase',            '\lowercase');
DefMacro('\MakeTextUppercase',            '\uppercase');

DefConditional('\if@includeinrelease');
Let('\@kernel@after@enddocument',               '\@empty');
Let('\@kernel@after@enddocument@afterlastpage', '\@empty');
Let('\@kernel@before@begindocument',            '\@empty');
Let('\@kernel@after@begindocument',             '\@empty');
Let('\conditionally@traceon',                   '\@empty');
Let('\conditionally@traceoff',                  '\@empty');
#**********************************************************************
1;
