1# -*- mode: Perl -*-
2# /=====================================================================\ #
3# |  LaTeX                                                              | #
4# | Implementation for LaTeXML                                          | #
5# |=====================================================================| #
6# | Part of LaTeXML:                                                    | #
7# |  Public domain software, produced as part of work done by the       | #
8# |  United States Government & not subject to copyright in the US.     | #
9# |---------------------------------------------------------------------| #
10# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
11# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
12# \=========================================================ooo==U==ooo=/ #
13package LaTeXML::Package::Pool;
14use strict;
15use warnings;
16use LaTeXML::Package;
17use LaTeXML::Util::Pathname;
18
19#**********************************************************************
20# Organized following
21#  "LaTeX: A Document Preparation System"
22#   by Leslie Lamport
23#   2nd edition
24# Addison Wesley, 1994
25# Appendix C. Reference Manual
26#**********************************************************************
27# NOTE: This will be loaded after TeX.pool.ltxml, so it inherits.
28#**********************************************************************
29
30LoadPool('TeX');
31
32# Apparently LaTeX does NOT define \magnification,
33# and babel uses that to determine whether we're runing LaTeX!!!
34Let('\magnification', '\@undefined');
35#**********************************************************************
36# Basic \documentclass & \documentstyle
37
38#AssignValue('2.09_COMPATIBILITY'=>0);
39DefConditionalI('\if@compatibility', undef, sub { LookupValue('2.09_COMPATIBILITY'); });
40DefMacro('\@compatibilitytrue',  '');
41DefMacro('\@compatibilityfalse', '');
42
43Let('\@currentlabel', '\@empty');
44DefMacro('\@currdir', './');
45
46# Let's try just starting with this set (since we've loaded LaTeX)
47AssignValue(inPreamble => 1);    # \begin{document} will clear this.
48
49DefConstructor('\documentclass OptionalSemiverbatim SkipSpaces Semiverbatim []',
50  "<?latexml class='#2' ?#1(options='#1')?>",
51  beforeDigest => sub { onlyPreamble('\documentclass'); },
52  afterDigest  => sub {
53    my ($stomach, $whatsit) = @_;
54    my $options = $whatsit->getArg(1);
55    my $class   = ToString($whatsit->getArg(2));
56    $class =~ s/\s+//g;
57    LoadClass($class,
58      options => [($options ? split(/\s*,\s*/, ToString($options)) : ())],
59      after   => Tokens(T_CS('\AtBeginDocument'), T_CS('\warn@unusedclassoptions')));
60    return; });
61
62AssignValue('@unusedoptionlist', []);
63DefPrimitiveI('\warn@unusedclassoptions', undef, sub {
64    if (my @unused = @{ LookupValue('@unusedoptionlist') }) {
65      Info('unexpected', 'options', $_[0], "Unused global options: " . join(',', @unused));
66      AssignValue('@unusedoptionlist', []); }
67    return; });
68
69DefConstructor('\documentstyle OptionalSemiverbatim SkipSpaces Semiverbatim []',
70  "<?latexml class='#2' ?#1(options='#1') oldstyle='true'?>",
71  beforeDigest => sub {
72    Info('unexpected', '\documentstyle', $_[0], "Entering LaTeX 2.09 Compatibility mode");
73    AssignValue('2.09_COMPATIBILITY' => 1, 'global');
74    onlyPreamble('\documentstyle'); },
75  afterDigest => sub {
76    my ($stomach, $whatsit) = @_;
77    my $class = ToString($whatsit->getArg(2));
78    $class =~ s/\s+//g;
79    my $options = $whatsit->getArg(1);
80    $options = [($options ? split(/\s*,\s*/, ToString($options)) : ())];
81# Watch out; In principle, compatibility mode wants a .sty, not a .cls!!!
82# But, we'd prefer .cls, since we'll have better bindings.
83# And in fact, nobody's likely to write a binding for a .sty that wants to be a class anyway.
84# So, we'll just try for a .cls, punting to OmniBus if needed.
85# If we start wanting to read style files by default, we'll still need to handle this
86# specially, since class (or sty files pretending to be) cover so much more.
87#
88# EXCEPTION: of course there is an exception. Older arXiv articles use \documentstyle{aipproc}
89# to load a different aipproc.sty than the later evolved aipproc.cls. And of course we need to support that.
90    if (FindFile($class, type => 'sty', notex => !LookupValue('INCLUDE_CLASSES'))) {
91      InputDefinitions('article', type => 'cls', noerror => 1,
92        handleoptions => 1, options => $options);
93      RequirePackage($class, options => $options, as_class => 1,
94        after => Tokens(T_CS('\compat@loadpackages'))); }
95    elsif (FindFile($class, type => 'cls', notex => !LookupValue('INCLUDE_CLASSES'))) {
96      LoadClass($class, options => $options,
97        after => Tokens(T_CS('\compat@loadpackages'))); }
98    else {
99      InputDefinitions('OmniBus', type => 'cls', noerror => 1,
100        handleoptions => 1, options => $options,
101        after         => Tokens(T_CS('\compat@loadpackages')));
102      RequirePackage($class, options => $options, as_class => 1); }
103    return; });
104
105DefPrimitiveI('\compat@loadpackages', undef, sub {
106    my $name       = ToString(Expand(T_CS('\@currname')));
107    my $type       = ToString(Expand(T_CS('\@currext')));
108    my $hadmissing = 0;
109    foreach my $option (@{ LookupValue('@unusedoptionlist') }) {
110      if (FindFile($option, type => 'sty')) {
111        RequirePackage($option); }
112      else {
113        $hadmissing = 1;
114        Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } }
115    # Often, in compatibility mode, the options are used to load what are effectively
116    # document classes for specific journals, etc that introduce a bunch of new frontmatter!
117    # To try to recover from this, we'll go ahead & load the OmniBus class.
118    if ($hadmissing && !LookupValue('OmniBus.cls_loaded')) {
119      Info('note', 'OmniBus', $_[0], "Loading OmniBus class to attempt to cover missing options");
120      LoadClass('OmniBus'); }
121    AssignValue('@unusedoptionlist', []); });
122
123sub onlyPreamble {
124  my ($cs) = @_;
125  Error('unexpected', $cs, $STATE->getStomach,
126    "The current command '" . ToString($cs) . "' can only appear in the preamble")
127    unless LookupValue("inPreamble");
128  return; }
129#**********************************************************************
130# C.1.  Commands and Environments.
131#**********************************************************************
132
133#======================================================================
134# C.1.1 Command Names and Arguments
135#======================================================================
136# Nothing...
137
138#======================================================================
139# C.1.2 Environments
140#======================================================================
141
142# In LaTeX, \newenvironment{env} defines \env and \endenv.
143# \begin{env} & \end{env} open/close a group, and invoke these.
144# In fact, the \env & \endenv don't have to have been created by
145# \newenvironment; And in fact \endenv doesn't even have to be defined!
146# [it is created by \csname, and equiv to \relax if no previous defn]
147
148# We need to respect these usages here, but we also want to be able
149# to define environment constructors that `capture' the body so that
150# it can be processed specially, if needed.  These are the magic
151# '\begin{env}', '\end{env}' control sequences created by DefEnvironment.
152
153AssignValue(current_environment => '', 'global');
154DefMacro('\@currenvir', '');
155DefPrimitive('\lx@setcurrenvir{}', sub {
156    DefMacroI('\@currenvir', undef, $_[1]);
157    AssignValue(current_environment => ToString($_[1])); });
158Let('\@currenvline', '\@empty');
159
160DefMacro('\begin{}', sub {
161    my ($gullet, $env) = @_;
162    my $name   = $env && ToString(Expand($env));
163    my $before = LookupValue('@environment@' . $name . '@beforebegin');
164    my $after  = LookupValue('@environment@' . $name . '@atbegin');
165    if (IsDefined("\\begin{$name}")) {
166      (($before ? @$before : ()),
167        T_CS("\\begin{$name}")); }    # Magic cs!
168    else {
169      my $token = T_CS("\\$name");
170      if (!IsDefined($token)) {
171        my $undef = '{' . $name . '}';
172        $STATE->noteStatus(undefined => $undef);
173        Error('undefined', $undef, $gullet, "The environment " . $undef . " is not defined.");
174        $STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($token, undef,
175            sub { $_[0]->makeError('undefined', $undef); })); }
176      (($before ? @$before : ()),
177        T_CS('\begingroup'),
178        ($after ? @$after : ()),
179        Invocation(T_CS('\lx@setcurrenvir'), $env),
180        $token,); } });
181#  robust => 1); # Not yet working well enough
182
183DefMacro('\end{}', sub {
184    my ($gullet, $env) = @_;
185    my $name   = $env && ToString(Expand($env));
186    my $before = LookupValue('@environment@' . $name . '@atend');
187    my $after  = LookupValue('@environment@' . $name . '@afterend');
188    my $t;
189
190    if (IsDefined($t = T_CS("\\end{$name}"))) {
191      ($t,
192        ($after ? @$after : ())); }    # Magic CS!
193    else {
194      $t = T_CS("\\end$name");
195      (($before ? @$before : ()),
196        (IsDefined($t) ? $t : ()),
197        T_CS('\endgroup'),
198        ($after ? @$after : ())); } });
199##  robust => 1);    # Isn't really robust, but something similar(?)
200
201#======================================================================
202# C.1.3 Fragile Commands
203#======================================================================
204# Because of the way we `move information', revertable and pre-processed,
205# I don't think we actually need to do anything ...
206# [Course that means we're not _really_ TeX!]
207
208# \protect is already in TeX for obscure reasons...
209#Let('\@typeset@protect','\relax');
210RawTeX(<<'EOTeX');
211\def\@ignorefalse{\global\let\if@ignore\iffalse}
212\def\@ignoretrue {\global\let\if@ignore\iftrue}
213\def\zap@space#1 #2{%
214  #1%
215  \ifx#2\@empty\else\expandafter\zap@space\fi
216  #2}
217\def\@unexpandable@protect{\noexpand\protect\noexpand}
218\def\x@protect#1{%
219   \ifx\protect\@typeset@protect\else
220      \@x@protect#1%
221   \fi
222}
223\def\@x@protect#1\fi#2#3{%
224   \fi\protect#1%
225}
226\let\@typeset@protect\relax
227\def\set@display@protect{\let\protect\string}
228\def\set@typeset@protect{\let\protect\@typeset@protect}
229\def\protected@edef{%
230   \let\@@protect\protect
231   \let\protect\@unexpandable@protect
232   \afterassignment\restore@protect
233   \edef
234}
235\def\protected@xdef{%
236   \let\@@protect\protect
237   \let\protect\@unexpandable@protect
238   \afterassignment\restore@protect
239   \xdef
240}
241\def\unrestored@protected@xdef{%
242   \let\protect\@unexpandable@protect
243   \xdef
244}
245\def\restore@protect{\let\protect\@@protect}
246\set@typeset@protect
247\def\@nobreakfalse{\global\let\if@nobreak\iffalse}
248\def\@nobreaktrue {\global\let\if@nobreak\iftrue}
249\@nobreakfalse
250
251\newif\ifv@
252\newif\ifh@
253\newif\ifdt@p
254\newif\if@pboxsw
255\newif\if@rjfield
256\newif\if@firstamp
257\newif\if@negarg
258\newif\if@ovt
259\newif\if@ovb
260\newif\if@ovl
261\newif\if@ovr
262\newdimen\@ovxx
263\newdimen\@ovyy
264\newdimen\@ovdx
265\newdimen\@ovdy
266\newdimen\@ovro
267\newdimen\@ovri
268\newif\if@noskipsec \@noskipsectrue
269
270EOTeX
271
272#======================================================================
273# C.1.4 Declarations
274#======================================================================
275# actual implementation later.
276#======================================================================
277# C.1.5 Invisible Commands
278#======================================================================
279# actual implementation later.
280
281#======================================================================
282# C.1.6 The \\ Command
283#======================================================================
284# In math, \\ is just a formatting hint, unless within an array, cases, .. environment.
285DefConstructor("\\\\ OptionalMatch:* [Glue]",
286  "?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
287  reversion  => Tokens(T_CS("\\\\"), T_CR),
288  properties => { isBreak => 1 });
289
290DefConstructor('\newline',
291  "?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
292  reversion  => Tokens(T_CS('\newline'), T_CR),
293  properties => { isBreak => 1 });
294Let('\@normalcr',      "\\\\");
295Let('\@normalnewline', '\newline');
296##PushValue(TEXT_MODE_BINDINGS => [
297##    T_CS("\\\\"), T_CS('\@normalcr'), T_CS('\newline'), T_CS('\@normalnewline')]);
298
299DefMacro('\@nolnerr', '');
300DefMacro('\@centercr', '\ifhmode\unskip\else\@nolnerr\fi'
301    . '\par\@ifstar{\nobreak\@xcentercr}\@xcentercr');
302DefMacro('\@xcentercr',   '\addvspace{-\parskip}\@ifnextchar[\@icentercr\ignorespaces');
303DefMacro('\@icentercr[]', '\vskip #1\ignorespaces');
304
305#**********************************************************************
306# C.2. The Structure of the Document
307#**********************************************************************
308#   prepended files (using filecontents environment)
309#   preamble (starting with \documentclass)
310#   \begin{document}
311#    text
312#   \end{document}
313
314DefMacro('\AtBeginDocument{}', sub {
315    PushValue('@at@begin@document', $_[1]->unlist); });
316DefMacro('\AtEndDocument{}', sub {
317    PushValue('@at@end@document', $_[1]->unlist); });
318
319# Like  "<ltx:document xml:id='#id'>#body</ltx:document>",
320# But more complicated due to id, at begin/end document and so forth.
321# AND, lower-level so that we can cope with common errors at document end.
322DefConstructorI(T_CS('\begin{document}'), undef, sub {
323    my ($document, %props) = @_;
324    my $id = ToString($props{id});
325    if (my $docel = $document->findnode('/ltx:document')) {    # Already (auto) created?
326      $document->setAttribute($docel, 'xml:id' => $id) if $id; }
327    else {
328      $document->openElement('ltx:document', 'xml:id' => $id); } },
329  afterDigest => sub {
330    my ($stomach, $whatsit) = @_;
331    $stomach->beginMode('text');
332    DefMacroI('\@currenvir', undef, 'document');
333    AssignValue(current_environment => 'document');
334    $_[1]->setProperty(id => Expand(T_CS('\thedocument@ID')));
335    my @boxes = ();
336    if (my $ops = LookupValue('@document@preamble@atend')) {
337      push(@boxes, $stomach->digest(Tokens(@$ops))); }
338    if (my $ops = LookupValue('@at@begin@document')) {
339      push(@boxes, $stomach->digest(Tokens(@$ops))); }
340    AssignValue(inPreamble => 0);    # atbegin is still (sorta) preamble
341    if (my $ops = LookupValue('@document@preamble@afterend')) {
342      push(@boxes, $stomach->digest(Tokens(@$ops))); }
343    $_[1]->setFont(LookupValue('font'));    # Start w/ whatever font was last selected.
344    return @boxes; });
345
346# \document is used directly in e.g. expl3.sty
347Let(T_CS('\document'), T_CS('\begin{document}'), 'global');
348
349DefConstructorI(T_CS('\end{document}'), undef, sub {
350    my ($document) = @_;
351    $document->closeElement('ltx:document'); },
352  beforeDigest => sub {
353    my ($stomach) = @_;
354    my @boxes = ();
355    if (my $ops = LookupValue('@at@end@document')) {
356      push(@boxes, $stomach->digest(Tokens(@$ops))); }
357    # Should we try to indent the last paragraph? If so, it goes like this:
358    push(@boxes, $stomach->digest(T_CS('\normal@par')));
359    # Now we check whether we're down to the last stack frame.
360    # It is common for unclosed { or even environments
361    # and we want to at least compress & avoid unnecessary errors & warnings.
362    my $nframes = $STATE->getFrameDepth;
363    my $ifstack;
364    if ($STATE->isValueBound('current_environment', 0)
365      && ($STATE->valueInFrame('current_environment', 0) eq 'document')
366      && (!($ifstack = $STATE->lookupValue('if_stack')) || !$$ifstack[0])) { }    # OK!
367    else {
368      my @lines = ();
369      while ((!$STATE->isValueBound('current_environment', 0)
370          || ($STATE->valueInFrame('current_environment', 0) ne 'document'))
371        && ($STATE->getFrameDepth > 0)) {
372        # my $nonbox = $STATE->valueInFrame('groupNonBoxing',0) || 0;
373        my $tok = $STATE->valueInFrame('groupInitiator',        0) || '<unknown>';
374        my $loc = $STATE->valueInFrame('groupInitiatorLocator', 0);
375        $loc = defined $loc ? ToString($loc) : '<unknown>';
376        my $env = $STATE->isValueBound('current_environment', 0)
377          && $STATE->valueInFrame('current_environment', 0);
378        if ($env) {
379          push(@lines, "Environment $env opened by " . ToString($tok) . ' ' . $loc); }
380        else {    # but unclosed { is so common and latex itself doesn't Error!
381          push(@lines, "Group opened by " . ToString($tok) . ' ' . $loc); }
382        $STATE->popFrame; }
383      while (($ifstack = $STATE->lookupValue('if_stack')) && $$ifstack[0]) {
384        my $frame = $STATE->shiftValue('if_stack');
385        push(@lines, "Conditional " . ToString($$frame{token})
386            . "started " . ToString($$frame{start})); }
387      Warn('unexpected', '\end{document}', $stomach,
388        "Attempt to end document with open groups, environments or conditionals", @lines);
389    }
390    $stomach->endMode('text');
391    # ???? don't push? or what
392    #    if (my $ops = LookupValue('@after@end@document')) {
393    #      push(@boxes, Digest(Tokens(@$ops))); }
394    $stomach->getGullet->flush;
395    return @boxes; });
396
397# \enddocument is used directly in e.g. standalone.cls
398Let(T_CS('\enddocument'), T_CS('\end{document}'), 'global');
399
400#**********************************************************************
401# C.3. Sentences and Paragraphs
402#**********************************************************************
403
404#======================================================================
405# C.3.1 Making Sentences
406#======================================================================
407# quotes;  should these be handled in DOM/construction?
408# dashes:  We'll need some sort of Ligature analog, or something like
409# Omega's OTP, to combine sequences of "-" into endash, emdash,
410# Perhaps it also applies more semantically?
411# Such as interpreting certain sequences as section headings,
412# or math constructs.
413
414# Spacing; in TeX.pool.ltxml
415
416# Special Characters; in TeX.pool.ltxml
417
418# Logos
419# \TeX is in TeX.pool.ltxml
420##DefConstructorI('\LaTeX',  undef, 'LaTeX');
421##DefConstructorI('\LaTeXe', undef, 'LaTeX2e');
422DefMacroI('\LaTeX',      undef, 'LaTeX');
423DefMacroI('\LaTeXe',     undef, 'LaTeX2e');
424DefMacroI('\fmtname',    undef, 'LaTeX2e');
425DefMacroI('\fmtversion', undef, '2018/12/01');
426
427DefMacroI('\today', undef, sub { ExplodeText(today()); });
428
429# Use fonts (w/ special flag) to propogate emphasis as a font change,
430# but preserve it's "emph"-ness.
431DefConstructor('\emph{}',
432  "<ltx:emph _force_font='1'>#1",
433  mode           => 'text', bounded => 1, font => { emph => 1 }, alias => '\emph',
434  afterConstruct => sub { $_[0]->maybeCloseElement('ltx:emph'); },
435  beforeDigest   => sub {
436    DefMacroI('\f@shape', undef,
437      (ToString(Tokens(Expand(T_CS('\f@shape')))) eq 'it' ? 'n' : 'it')); });
438Tag('ltx:emph', autoClose => 1);
439
440#======================================================================
441# C.3.2 Making Paragraphs
442#======================================================================
443# \noindent, \indent, \par in TeX.pool.ltxml
444
445Let('\@@par', '\par');
446# Style parameters
447# \parindent, \baselineskip, \parskip alreadin in TeX.pool.ltxml
448
449DefPrimitive('\linespread{}', undef);
450
451# ?
452DefMacro('\@noligs', '');
453DefConditional('\if@endpe');
454DefMacro('\@doendpe', '');
455DefMacro('\@bsphack', '\relax');    # what else?
456DefMacro('\@esphack', '\relax');
457DefMacro('\@Esphack', '\relax');
458#======================================================================
459# C.3.3 Footnotes
460#======================================================================
461
462NewCounter('footnote');
463DefMacroI('\thefootnote', undef, '\arabic{footnote}');
464NewCounter('mpfootnote');
465DefMacroI('\thempfn',             undef, '\thefootnote');
466DefMacroI('\thempfootnote',       undef, '\arabic{mpfootnote}');
467DefMacroI('\footnotetyperefname', undef, 'footnote');
468
469sub makeNoteTags {
470  my ($counter, $mark, $tag) = @_;
471  if ($tag) {
472    return (
473      RefStepID($counter),
474      mark => $mark || $tag,
475      tags => Digest(T_BEGIN,
476        T_CS('\def'), T_CS('\the' . $counter), T_BEGIN, Revert($tag), T_END,
477        T_CS('\def'), T_CS('\typerefnum@' . $counter),
478        T_BEGIN,      T_CS('\\' . $counter . 'typerefname'), T_SPACE, Revert($tag), T_END,
479        Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
480        T_END)); }
481  else {
482    return (RefStepCounter($counter),
483      mark => $mark || DigestText(T_CS('\the' . $counter))); } }
484
485DefMacroI('\ext@footnote', undef, undef);
486DefConstructor('\lx@note[]{}[]{}',
487  "^<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
488    . "#tags"
489    . "#4"
490    . "</ltx:note>",
491  mode         => 'text', bounded => 1,
492  beforeDigest => sub { reenterTextMode(1); Digest('\normalfont'); },
493  properties   => sub {
494    my $type = ToString($_[2]);
495    (role => $type,
496      list => DigestText(T_CS('\ext@' . $type)),
497      makeNoteTags($type, $_[1], $_[3])); },
498  reversion => '');
499DefConstructor('\lx@notemark[]{}[]',
500  "<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
501    . "#tags"
502    . "</ltx:note>",
503  mode       => 'text',
504  properties => sub {
505    my $type = ToString($_[2]);
506    (role => $type . 'mark',
507      list => DigestText(T_CS('\ext@' . $type)),
508      makeNoteTags($type, $_[1], $_[3])); },
509  reversion => '');
510DefConstructor('\lx@notetext[]{}[]{}',
511  "<ltx:note role='#role' mark='#mark' xml:id='#id'>#4</ltx:note>",
512  mode       => 'text',
513  properties => sub {
514    my $type = ToString($_[2]);
515    (role => $type . 'text', makeNoteTags($type, $_[1], $_[3] || Digest(T_CS('\the' . $type)))); },
516  reversion => '');
517
518DefMacro('\footnote',     '\lx@note{footnote}');
519DefMacro('\footnotemark', '\lx@notemark{footnote}');
520DefMacro('\footnotetext', '\lx@notetext{footnote}');
521
522Tag('ltx:note', afterClose => \&relocateFootnote);
523
524# Find any pairs of footnotemark & footnotetext;
525# Move the contents of the text to the mark, removing the text node.
526sub relocateFootnote {
527  my ($document, $node) = @_;
528  if (($node->getAttribute('role') || '') =~ /^(\w+?)text$/) {
529    my $notetype = $1;    # Eg "footnote", "endnote",...
530    if (my $mark = $node->getAttribute('mark')) {
531      foreach my $marknote ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) {
532        relocateFootnote_aux($document, $notetype, $marknote, $node); } } }
533  elsif (($node->getAttribute('role') || '') =~ /^(\w+?)mark$/) {
534    my $notetype = $1;    # Eg "footnote", "endnote",...
535    if (my $mark = $node->getAttribute('mark')) {
536      foreach my $textnote ($document->findnodes(".//ltx:note[\@role='${notetype}text'][\@mark='$mark']")) {
537        relocateFootnote_aux($document, $notetype, $node, $textnote); } } }
538  return; }
539
540# Move the contents of the $textnote to the $marknote, remove $textnote.
541sub relocateFootnote_aux {
542  my ($document, $notetype, $marknote, $textnote) = @_;
543  $textnote->parentNode->removeChild($textnote);
544  $document->appendClone($marknote, $textnote->childNodes);
545  $document->setAttribute($marknote, role => $notetype);
546  if (my $labels = $textnote->getAttribute('labels')) {
547    GenerateID($document, $marknote);
548    $document->setAttribute($marknote, labels => $labels); }
549  return; }
550
551# Style parameters
552DefRegister('\footnotesep' => Dimension(0));
553DefPrimitiveI('\footnoterule', undef, undef);
554
555#======================================================================
556# C.3.4 Accents and Special Symbols
557#======================================================================
558# See TeX.pool.ltxml
559
560# See Section 3.3.2 Mathematical Symbols, below
561
562# Should this be here?
563DefMath('\mathring{}', "\x{030A}", operator_role => 'OVERACCENT');
564
565#**********************************************************************
566# C.4 Sectioning and Table of Contents
567#**********************************************************************
568
569#======================================================================
570# C.4.1 Sectioning Commands.
571#======================================================================
572# Note that LaTeX allows fairly arbitrary stuff in \the<ctr>, although
573# it can get you in trouble.  However, in almost all cases, the result
574# is plain text.  So, I'm putting refnum as an attribute, where I like it!
575# You want something else? Redefine!
576
577# Also, we're adding an id to each, that is parallel to the refnum, but
578# valid as an ID.  You can tune the representation by defining, eg. \thesection@ID
579
580# A little more messy than seems necessary:
581#  We don't know whether to step the counter and update \@currentlabel until we see the '*',
582# but we have to know it before we digest the title, since \label can be there!
583
584# These are defined in terms of \@startsection so that
585# casual user redefinitions work, too.
586DefMacroI('\chapter', undef, '\@startsection{chapter}{0}{}{}{}{}', locked => 1);
587DefMacroI('\part', undef, '\@startsection{part}{-1}{}{}{}{}'); # not locked since sometimes redefined as partition?
588DefMacroI('\section',       undef, '\@startsection{section}{1}{}{}{}{}',       locked => 1);
589DefMacroI('\subsection',    undef, '\@startsection{subsection}{2}{}{}{}{}',    locked => 1);
590DefMacroI('\subsubsection', undef, '\@startsection{subsubsection}{3}{}{}{}{}', locked => 1);
591DefMacroI('\paragraph',     undef, '\@startsection{paragraph}{4}{}{}{}{}',     locked => 1);
592DefMacroI('\subparagraph',  undef, '\@startsection{subparagraph}{5}{}{}{}{}',  locked => 1);
593map { Tag("ltx:$_", autoClose => 1) }
594  qw(part chapter section subsection subsubsection paragraph subparagraph);
595
596DefMacro('\secdef {}{} OptionalMatch:*', sub { ($_[3] ? ($_[2]) : ($_[1])); });
597
598DefMacroI('\@startsection@hook', undef, Tokens());
599NewCounter('secnumdepth');
600SetCounter('secnumdepth', Number(3));
601DefMacro('\@startsection{}{}{}{}{}{} OptionalMatch:*', sub {
602    my ($gullet, $type, $level, $ignore3, $ignore4, $ignore5, $ignore6, $flag) = @_;
603    my $ctr = LookupValue('counter_for_' . ToString($type)) || ToString($type);
604    $level = ToString($level);
605    if ($flag) {    # No number, not in TOC
606      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
607        T_BEGIN, $type->unlist, T_END,
608        T_BEGIN, T_END); }
609    elsif ((($level ne '') && ($level > CounterValue('secnumdepth')->valueOf))
610      || LookupValue('no_number_sections')) {
611      # No number, but in TOC
612      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
613        T_BEGIN, $type->unlist,  T_END,
614        T_BEGIN, T_OTHER('toc'), T_END); }
615    else {          # Number and in TOC
616      (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'),
617        T_BEGIN, $type->unlist,  T_END,
618        T_BEGIN, T_OTHER('toc'), T_END); } },
619  locked => 1);
620
621# Not sure if this is best, but if no explicit \section'ing...
622#### Tag('ltx:section',autoOpen=>1);
623
624DefConstructor('\@@numbered@section{} Undigested OptionalUndigested Undigested', sub {
625    my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
626    my $id = $props{id};
627    if (my $asif = $props{backmatterelement}) {
628      $document->setNode($document->find_insertion_point($asif)); }
629    $document->openElement("ltx:" . ToString($type),
630      'xml:id' => CleanID($id),
631      inlist   => ToString($inlist));
632    if (my $tags = $props{tags}) {
633      $document->absorb($tags); }
634    $document->insertElement('ltx:title',    $props{title});
635    $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
636  properties => sub {
637    my ($stomach, $type, $inlist, $toctitle, $title) = @_;
638    MaybePeekLabel();
639    $type = ToString($type);
640    my %props     = RefStepCounter($type);
641    my $xtitle    = Digest(Invocation(T_CS('\lx@format@title@@'),    $type, $title));
642    my $xtoctitle = Digest(Invocation(T_CS('\lx@format@toctitle@@'), $type, $toctitle || $title));
643    if ($type eq 'appendix') {
644      $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
645    $props{title}    = $xtitle;
646    $props{toctitle} = $xtoctitle
647      if $xtoctitle && $xtoctitle->unlist && (ToString($xtoctitle) ne ToString($xtitle));
648    return %props; });
649
650# No tags, at all? Consider...
651DefConstructor('\@@unnumbered@section{} Undigested OptionalUndigested Undigested', sub {
652    my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
653    my $id = $props{id};
654    if (my $asif = $props{backmatterelement}) {
655      $document->setNode($document->find_insertion_point($asif)); }
656    $document->openElement("ltx:" . ToString($type),
657      'xml:id' => CleanID($id),
658      inlist   => ToString($inlist));
659    $document->insertElement('ltx:title',    $props{title});
660    $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
661  properties => sub {
662    my ($stomach, $type, $inlist, $toctitle, $title) = @_;
663    MaybePeekLabel();
664    $type = ToString($type);
665    my %props = RefStepID($type);
666    if ($type eq 'appendix') {
667      $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
668    $props{title} = Digest(T_CS('\@hidden@bgroup'),
669      Invocation(T_CS('\lx@format@title@font@@'), $type), $title, T_CS('\@hidden@egroup'));
670    $props{toctitle} = $toctitle
671      && Digest(T_CS('\@hidden@bgroup'), Invocation(T_CS('\lx@format@toctitle@font@@'), $type),
672      $toctitle, T_CS('\@hidden@egroup'));
673    return %props; });
674
675#======================================================================
676# C.4.2 The Appendix
677#======================================================================
678# Handled in article,report or book.
679DefMacroI('\appendixname',   undef, 'Appendix');
680DefMacroI('\appendixesname', undef, 'Appendixes');
681
682# Class files should define \@appendix to call this as startAppendices('section') or chapter...
683# counter is also the element name!
684
685sub startAppendices { beginAppendices(@_); return; }
686
687sub beginAppendices {
688  my ($counter) = @_;
689  Let('\lx@save@theappendex',    '\the' . $counter,         'global');
690  Let('\lx@save@theappendex@ID', '\the' . $counter . '@ID', 'global');
691  Let('\lx@save@appendix',       T_CS('\\' . $counter),     'global');
692  Let('\lx@save@@appendix',      T_CS('\@appendix'),        'global');
693  AssignMapping('BACKMATTER_ELEMENT', 'ltx:appendix' => 'ltx:' . $counter);
694  if (LookupDefinition(T_CS('\c@chapter'))    # Has \chapter defined
695    && ($counter ne 'chapter')) {             # And appendices are below the chapter level.
696    NewCounter($counter, 'chapter', idprefix => 'A');
697    DefMacroI('\the' . $counter, undef, '\thechapter.\Alph{' . $counter . '}', scope => 'global'); }
698  else {
699    NewCounter($counter, 'document', idprefix => 'A');
700    DefMacroI('\the' . $counter, undef, '\Alph{' . $counter . '}', scope => 'global'); }
701  AssignMapping('counter_for_type', appendix => $counter);
702  Let(T_CS('\\' . $counter), T_CS('\@@appendix'), 'global');
703  Let(T_CS('\@appendix'),    T_CS('\relax'),      'global');
704  return; }
705
706sub endAppendices {
707  if (my $counter = LookupMapping('BACKMATTER_ELEMENT', 'ltx:appendix')) {
708    $counter =~ s/^ltx://;
709    Let('\the' . $counter,         '\lx@save@theappendex',    'global');
710    Let('\the' . $counter . '@ID', '\lx@save@theappendex@ID', 'global');
711    Let(T_CS('\\' . $counter),     '\lx@save@appendix',       'global');
712    Let(T_CS('\@appendix'),        '\lx@save@@appendix',      'global'); }
713  return; }
714
715DefMacroI('\\@@appendix', undef, '\@startsection{appendix}{0}{}{}{}{}');
716
717#======================================================================
718# C.4.3 Table of Contents
719#======================================================================
720# Insert stubs that will be filled in during post processing.
721DefMacroI('\contentsname', undef, 'Contents');
722DefConstructorI('\tableofcontents', undef,
723  "<ltx:TOC lists='toc' select='#select'><ltx:title>#name</ltx:title></ltx:TOC>",
724  properties => sub {
725    my $td = CounterValue('tocdepth')->valueOf + 1;
726    my @s  = (qw(ltx:part ltx:chapter ltx:section ltx:subsection ltx:subsubsection
727        ltx:paragraph ltx:subparagraph));
728    $td = $#s if $#s < $td;
729    @s  = map { $s[$_] } 0 .. $td;
730    push(@s, (qw(ltx:appendix ltx:index ltx:bibliography))) if @s;
731    (select => join(' | ', @s),
732      name => Digest(T_CS('\contentsname'))); });
733
734DefMacroI('\listfigurename', undef, 'List of Figures');
735DefConstructorI('\listoffigures', undef,
736  "<ltx:TOC lists='lof' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
737  properties => sub { (name => Digest(T_CS('\listfigurename'))); });
738
739DefMacroI('\listtablename', undef, 'List of Tables');
740DefConstructorI('\listoftables', undef,
741  "<ltx:TOC lists='lot' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
742  properties => sub { (name => Digest(T_CS('\listtablename'))); });
743
744DefPrimitive('\numberline{}{}',    undef);
745DefPrimitive('\addtocontents{}{}', undef);
746
747DefConstructor('\addcontentsline{}{}{}', sub {
748    my ($document, $inlist, $type, $title) = @_;
749    # Note that the node can be inlist $inlist.
750    # Could conceivably want to add $title as toctitle???
751    if (my $savenode = $document->floatToLabel) {
752      my $node  = $document->getNode;
753      my $lists = $node->getAttribute('inlist');
754      $inlist = ToString($inlist);
755      $document->setAttribute($node, inlist => ($lists ? $lists . ' ' . $inlist : $inlist));
756      $document->setNode($savenode); } });
757
758#======================================================================
759# C.4.4 Style registers
760#======================================================================
761NewCounter('tocdepth');
762
763#**********************************************************************
764# C.5 Classes, Packages and Page Styles
765#**********************************************************************
766
767#======================================================================
768# C.5.1 Document Class
769#======================================================================
770# Style Parameters
771DefRegister('\bibindent'     => Dimension(0));
772DefRegister('\columnsep'     => Dimension(0));
773DefRegister('\columnseprule' => Dimension(0));
774DefRegister('\mathindent'    => Dimension(0));
775
776#======================================================================
777# C.5.2 Packages
778#======================================================================
779# We'll prefer to load package.pm, but will try package.sty or
780# package.tex (the latter being unlikely to work, but....)
781# See Stomach.pm for details
782# Ignorable packages ??
783# pre-defined packages??
784
785DefMacroI('\@clsextension', undef, 'cls');
786DefMacroI('\@pkgextension', undef, 'sty');
787Let('\@currext',          '\@empty');
788Let('\@currname',         '\@empty');
789Let('\@classoptionslist', '\relax');
790# Note that there are variables used in Package.pmfor these,
791# but they are NOT tied to these macros. Do they need to be?
792DefMacroI('\@declaredoptions', undef, undef);
793DefMacroI('\@curroptions',     undef, undef);
794
795DefConstructor('\usepackage OptionalSemiverbatim Semiverbatim []',
796  "<?latexml package='#2' ?#1(options='#1')?>",
797  beforeDigest => sub { onlyPreamble('\usepackage'); },
798  afterDigest  => sub { my ($stomach, $whatsit) = @_;
799    my $options  = $whatsit->getArg(1);
800    my $packages = $whatsit->getArg(2);
801    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
802    for my $pkg (split(',', ToString($packages))) {
803      $pkg =~ s/\s+//g;
804      next if !$pkg || $pkg =~ /^%/;
805      RequirePackage($pkg, options => $options); }
806    return });
807
808DefConstructor('\RequirePackage OptionalSemiverbatim Semiverbatim []',
809  "<?latexml package='#2' ?#1(options='#1')?>",
810  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
811  afterDigest  => sub { my ($stomach, $whatsit) = @_;
812    my $options  = $whatsit->getArg(1);
813    my $packages = $whatsit->getArg(2);
814    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
815    for my $pkg (split(',', ToString($packages))) {
816      $pkg =~ s/\s+//g;
817      next if !$pkg || $pkg =~ /^%/;
818      RequirePackage($pkg, options => $options); }
819    return });
820
821DefConstructor('\LoadClass OptionalSemiverbatim Semiverbatim []',
822  "<?latexml class='#2' ?#1(options='#1')?>",
823  beforeDigest => sub { onlyPreamble('\LoadClass'); },
824  afterDigest  => sub { my ($stomach, $whatsit) = @_;
825    my $options = $whatsit->getArg(1);
826    my $class   = ToString($whatsit->getArg(2));
827    $class =~ s/\s+//g;
828    $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
829    LoadClass($class, options => $options);
830    return; });
831
832# Related internal macros for package definition
833# Internals used in Packages
834DefMacro('\NeedsTeXFormat{}[]', Tokens());
835
836DefPrimitive('\ProvidesClass{}[]', sub {
837    my ($stomach, $class, $version) = @_;
838    DefMacroI("\\ver@" . ToString($class) . ".cls", undef, $version || Tokens(), scope => 'global');
839    return; });
840
841# Note that these, like LaTeX, define macros like \var@mypkg.sty to give the version info.
842DefMacro('\ProvidesPackage{}[]', sub {
843    my ($stomach, $package, $version) = @_;
844    DefMacroI("\\ver@" . ToString($package) . ".sty", undef, $version || Tokens(), scope => 'global');
845    return; });
846
847DefMacro('\ProvidesFile{}[]', sub {
848    my ($stomach, $file, $version) = @_;
849    DefMacroI("\\ver@" . ToString($file), undef, $version || Tokens(), scope => 'global');
850    return; });
851
852DefPrimitive('\DeclareOption{}{}', sub {
853    my ($stomach, $option, $code) = @_;
854    ((ToString($option) eq '*') ?
855        DeclareOption(undef,             $code) :
856        DeclareOption(ToString($option), $code)); });
857
858DefPrimitive('\PassOptionsToPackage{}{}', sub {
859    my ($stomach, $name, $options) = @_;
860    $name = ToString($name);
861    $name =~ s/\s+//g;
862    PassOptions($name, 'sty', split(/\s*,\s*/, ToString(Expand($options)))); });
863
864DefPrimitive('\PassOptionsToClass{}{}', sub {
865    my ($stomach, $name, $options) = @_;
866    $name = ToString($name);
867    $name =~ s/\s+//g;
868    PassOptions($name, 'cls', split(/\s*,\s*/, ToString(Expand($options)))); });
869
870DefConstructor('\RequirePackageWithOptions Semiverbatim []',
871  "<?latexml package='#1'?>",
872  beforeDigest => sub { onlyPreamble('\RequirePackage'); },
873  afterDigest  => sub { my ($stomach, $whatsit) = @_;
874    my $package = ToString($whatsit->getArg(1));
875    $package =~ s/\s+//g;
876    RequirePackage($package, withoptions => 1);
877    return; });
878
879DefConstructor('\LoadClassWithOptions Semiverbatim []',
880  "<?latexml class='#1'?>",
881  beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); },
882  afterDigest  => sub { my ($stomach, $whatsit) = @_;
883    my $class = ToString($whatsit->getArg(1));
884    $class =~ s/\s+//g;
885    LoadClass($class, withoptions => 1);
886    return; });
887
888DefPrimitive('\@onefilewithoptions {} [][] {}', sub {
889    my ($stomach, $name, $option1, $option2, $ext) = @_;
890    InputDefinitions(ToString(Expand($name)), type => ToString(Expand($ext)), options => $option1);
891    return; });
892
893DefMacroI('\CurrentOption', undef, Tokens());
894
895DefPrimitiveI('\OptionNotUsed', undef, sub {
896    if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
897      my $type = ToString(Expand(T_CS('\@currext')));
898      if ($type eq 'cls') {
899        PushValue('@unusedoptionlist', $option); } }
900    return; });
901
902DefPrimitiveI('\@unknownoptionerror', undef, sub {
903    if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
904      my $name = ToString(Expand(T_CS('\@currname')));
905      my $type = ToString(Expand(T_CS('\@currext')));
906      Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); }
907    return; });
908
909DefPrimitive('\ExecuteOptions{}', sub {
910    my ($gullet, $options) = @_;
911    ExecuteOptions(split(/\s*,\s*/, ToString(Expand($options)))); });
912
913DefPrimitive('\ProcessOptions OptionalMatch:*', sub {
914    my ($stomach, $star) = @_;
915    ProcessOptions(($star ? (inorder => 1) : ())); });
916DefMacro('\@options', '\ProcessOptions*');
917
918DefMacro('\AtEndOfPackage{}', sub {
919    my ($gullet, $code) = @_;
920    my $name = ToString(Expand(T_CS('\@currname')));
921    my $type = ToString(Expand(T_CS('\@currext')));
922    AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); });
923
924DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension');
925DefMacro('\@ifclassloaded',   '\@ifl@aded\@clsextension');
926DefMacro('\@ifl@aded{}{}', sub {
927    my ($gullet, $ext, $name) = @_;
928    my $path = ToString(Expand($name)) . '.' . ToString(Expand($ext));
929    # If EITHER the raw TeX or ltxml version of this file was loaded.
930    if (LookupValue($path . '_loaded') || LookupValue($path . '.ltxml_loaded')) {
931      T_CS('\@firstoftwo'); }
932    else {
933      T_CS('\@secondoftwo'); } });
934
935DefMacro('\@ifpackagewith', '\@if@ptions\@pkgextension');
936DefMacro('\@ifclasswith',   '\@if@ptions\@clsextension');
937DefMacro('\@if@ptions{}{}{}', sub {
938    my ($gullet, $ext, $name, $option) = @_;
939    $option = ToString(Expand($option));
940    my $values = LookupValue('opt@' . ToString(Expand($name)) . '.' . ToString(Expand($ext)));
941    if (grep { $option eq $_ } @$values) {
942      T_CS('\@firstoftwo'); }
943    else {
944      T_CS('\@secondoftwo'); } });
945
946DefMacro('\@ptionlist {}', '\@ifundefined{opt@#1}\@empty{\csname opt@#1\endcsname}');
947
948DefMacro('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); });
949DefMacro('\addto@hook DefToken {}',    '#1\expandafter{\the#1#2}');
950
951# Alas, we're not tracking versions, so we'll assume it's "later" & cross fingers....
952DefMacro('\@ifpackagelater{}{}{}{}', '#3');
953DefMacro('\@ifclasslater{}{}{}{}',   '#3');
954Let('\AtEndOfClass', '\AtEndOfPackage');
955
956DefMacro('\AtBeginDvi {}', Tokens());
957
958RawTeX(<<'EoTeX');
959\def\@ifl@t@r#1#2{%
960  \ifnum\expandafter\@parse@version@#1//00\@nil<%
961        \expandafter\@parse@version@#2//00\@nil
962    \expandafter\@secondoftwo
963  \else
964    \expandafter\@firstoftwo
965  \fi}
966\def\@parse@version@#1{\@parse@version0#1}
967\def\@parse@version#1/#2/#3#4#5\@nil{%
968\@parse@version@dash#1-#2-#3#4\@nil
969}
970\def\@parse@version@dash#1-#2-#3#4#5\@nil{%
971  \if\relax#2\relax\else#1\fi#2#3#4 }
972EoTeX
973
974#======================================================================
975# Somewhat related I/O stuff
976DefMacro('\filename@parse{}', sub {
977    my ($gullet, $pathname) = @_;
978    my ($dir, $name, $ext) = pathname_split(ToString(Expand($pathname)));
979    $dir .= '/' if $dir;
980    DefMacroI('\filename@area', undef, Tokens(ExplodeText($dir)));
981    DefMacroI('\filename@base', undef, Tokens(ExplodeText($name)));
982    DefMacroI('\filename@ext',  undef, ($ext ? Tokens(ExplodeText($ext)) : T_CS('\relax'))); });
983
984DefMacroI('\@filelist', undef, Tokens());
985DefMacro('\@addtofilelist{}', sub {
986    DefMacroI('\@filelist', undef,
987      Expand(T_CS('\@filelist'), T_OTHER(','), $_[1]->unlist)); });
988
989#======================================================================
990# C.5.3 Page Styles
991#======================================================================
992# Ignored
993NewCounter('page');
994DefPrimitive('\pagestyle{}',     undef);
995DefPrimitive('\thispagestyle{}', undef);
996DefPrimitive('\markright{}',     undef);
997DefPrimitive('\markboth{}{}',    undef);
998DefPrimitiveI('\leftmark',  undef, undef);
999DefPrimitiveI('\rightmark', undef, undef);
1000DefPrimitive('\pagenumbering{}', undef);
1001DefMacro('\@mkboth', '\@gobbletwo');    # default, just in case
1002DefMacro('\ps@empty',
1003  '\let\@mkboth\@gobbletwo\let\@oddhead\@empty\let\@oddfoot\@empty' .
1004    '\let\@evenhead\@empty\let\@evenfoot\@empty');
1005DefMacro('\ps@plain', '\let\@mkboth\@gobbletwo' .
1006    '\let\@oddhead\@empty\def\@oddfoot{\reset@font\hfil\thepage' .
1007    '\hfil}\let\@evenhead\@empty\let\@evenfoot\@oddfoot');
1008Let(T_CS('\@leftmark'),  T_CS('\@firstoftwo'));
1009Let(T_CS('\@rightmark'), T_CS('\@secondoftwo'));
1010# In normal latex, these should \clearpage; at least we want new paragraph?
1011# Optional arg is sortof a heading, but w/o any particular styling(?)
1012DefMacro('\twocolumn[]',   '\ifx.#1.\else\par\noindent#1\fi\par');
1013DefMacro('\onecolumn',     '\par');
1014DefMacro('\@topnewpage{}', '#1');
1015# Style parameters from Fig. C.3, p.182
1016DefRegister('\paperheight'     => Dimension(0));
1017DefRegister('\paperwidth'      => Dimension(0));
1018DefRegister('\textheight'      => Dimension(0));
1019DefRegister('\textwidth'       => Dimension('6in'));
1020DefRegister('\topmargin'       => Dimension(0));
1021DefRegister('\headheight'      => Dimension(0));
1022DefRegister('\headsep'         => Dimension(0));
1023DefRegister('\footskip'        => Dimension(0));
1024DefRegister('\footheight'      => Dimension(0));
1025DefRegister('\evensidemargin'  => Dimension(0));
1026DefRegister('\oddsidemargin'   => Dimension(0));
1027DefRegister('\marginparwidth'  => Dimension(0));
1028DefRegister('\marginparsep'    => Dimension(0));
1029DefRegister('\columnwidth'     => Dimension('6in'));
1030DefRegister('\linewidth'       => Dimension('6in'));
1031DefRegister('\baselinestretch' => Dimension(0));
1032
1033#======================================================================
1034# C.5.4 The Title Page and Abstract
1035#======================================================================
1036
1037# See frontmatter support in TeX.ltxml
1038Let('\@title', '\@empty');
1039DefMacro('\title{}', '\def\@title{#1}\@add@frontmatter{ltx:title}{#1}', locked => 1);
1040
1041DefMacro('\@date', '\@empty');
1042DefMacro('\date{}',
1043  '\def\@date{#1}'
1044    . '\@add@frontmatter{ltx:date}[role=creation,'
1045    . 'name={\@ifundefined{datename}{}{\datename}}]{#1}');
1046
1047DefConstructor('\person@thanks{}', "^ <ltx:contact role='thanks'>#1</ltx:contact>",
1048  alias => '\thanks', mode => 'text');
1049DefConstructor('\@personname{}', "<ltx:personname>#1</ltx:personname>",
1050  beforeDigest => sub { Let('\thanks', '\person@thanks'); },
1051  bounded      => 1, mode => 'text');
1052
1053# Sanitize person names for (obvious) punctuation abuse at start+end
1054Tag('ltx:personname', afterClose => sub {
1055    my ($document, $node) = @_;
1056    if (my $first = $node->firstChild) {
1057      if ($first->nodeType == XML_TEXT_NODE) {
1058        my $first_text = $first->data;
1059        my $new_text   = $first_text;
1060        $new_text =~ s/^\W+//;
1061        if ($first_text ne $new_text) {
1062          $first->setData($new_text); } } }
1063    if (my $last = $node->lastChild) {
1064      if ($last->nodeType == XML_TEXT_NODE) {
1065        my $last_text = $last->data;
1066        my $new_text  = $last_text;
1067        $new_text =~ s/\W+$//;
1068        if ($last_text ne $new_text) {
1069          $last->setData($new_text); } } }
1070    return; });
1071
1072DefConstructorI('\and', undef, " and ");
1073
1074AssignValue(NUMBER_OF_AUTHORS => 0);
1075DefPrimitive('\lx@count@author', sub {
1076    AssignValue(NUMBER_OF_AUTHORS => LookupValue('NUMBER_OF_AUTHORS') + 1, 'global') });
1077DefMacro('\lx@author{}',
1078  '\lx@count@author'
1079    . '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}');
1080
1081DefConstructor('\lx@@@contact{}{}', "^ <ltx:contact role='#1'>#2</ltx:contact>");
1082DefMacro('\lx@contact{}{}',
1083  '\@add@to@frontmatter{ltx:creator}{\lx@@@contact{#1}{#2}}');
1084
1085DefMacro('\lx@author@sep',  '\qquad');
1086DefMacro('\lx@author@conj', '\qquad');
1087DefConstructor('\lx@author@prefix', sub {
1088    my ($document) = @_;
1089    my $node       = $document->getElement;
1090    my $nauthors   = LookupValue('NUMBER_OF_AUTHORS');
1091    my $i          = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') });
1092    if    ($i <= 1) { }
1093    elsif ($i == $nauthors) {
1094      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); }
1095    else {
1096      $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); }
1097});
1098
1099DefMacro('\@author',                 '\@empty');
1100DefMacro('\author[]{}',              '\def\@author{#2}\lx@make@authors@anded{#2}', locked => 1);
1101DefMacro('\lx@make@authors@anded{}', sub { andSplit(T_CS('\lx@author'), $_[1]); });
1102DefPrimitive('\ltx@authors@oneline', sub {
1103    AssignMapping('DOCUMENT_CLASSES', ltx_authors_1line => 1);
1104    return; });
1105DefPrimitive('\ltx@authors@multiline', sub {
1106    AssignMapping('DOCUMENT_CLASSES', ltx_authors_multiline => 1);
1107    return; });
1108
1109DefMacro('\@add@conversion@date', '\@add@frontmatter{ltx:date}[role=creation]{\today}');
1110
1111# Doesn't produce anything (we're already inserting frontmatter),
1112# But, it does make the various frontmatter macros into no-ops.
1113DefMacroI('\maketitle', undef,
1114  '\@startsection@hook'
1115    . '\global\let\thanks\relax'
1116    . '\global\let\maketitle\relax'
1117    . '\global\let\@maketitle\relax'
1118    . '\global\let\@thanks\@empty'
1119    . '\global\let\@author\@empty'
1120    . '\global\let\@date\@empty'
1121    . '\global\let\@title\@empty'
1122    . '\global\let\title\relax'
1123    . '\global\let\author\relax'
1124    . '\global\let\date\relax'
1125    . '\global\let\and\relax', locked => 1);
1126
1127DefMacro('\@thanks',  '\@empty');
1128DefMacro('\thanks{}', '\def\@thanks{#1}\lx@make@thanks{#1}');
1129DefConstructor('\lx@make@thanks{}', "<ltx:note role='thanks'>#1</ltx:note>");
1130
1131# Abstract SHOULD have been so simple, but seems to be a magnet for abuse.
1132# For one thing, we'd like to just write
1133#   DefEnvironment('{abstract}','<ltx:abstract>#body</ltx:abstract>');
1134# However, we don't want to place the <ltx:abstract> environment directly where
1135# we found it, but we want to add it to frontmatter. This requires capturing the
1136# recently digested list and storing it in the frontmatter structure.
1137
1138# The really messy stuff comes from the way authors -- and style designers -- misuse it.
1139# Basic LaTeX wants it to be an environment WITHIN the document environment,
1140# and AFTER the \maketitle.
1141# However, since all it really does is typeset "Abstract" in bold, it allows:
1142#   \abstract stuff...
1143# without even an \endabstract!  We MUST know when the abstract ends, so we've got
1144# to recognize when we've moved on to other stuff... \sections at the VERY LEAST.
1145
1146# Additional complications come from certain other classes and styles that
1147# redefine abstract to take the text as an argument. And some treat it
1148# like \title, \author, and such, that are expected to appear in the preamble!!
1149# The treatment below allows an abstract environment in the preamble,
1150# (even though straight latex doesn't) but does not cover the 1-arg case in preamble!
1151#
1152# Probably there are other places (eg in titlepage?) that should force the close??
1153
1154DefEnvironment('{abstract}', '',
1155  afterDigestBegin => sub {
1156    AssignValue(inPreamble => 0); },
1157  afterDigest => sub {
1158    my $frontmatter = LookupValue('frontmatter');
1159    push(@{ $$frontmatter{'ltx:abstract'} },
1160      ['ltx:abstract',
1161        { name => Digest(Tokens(T_CS('\format@title@abstract'),
1162              T_BEGIN, T_CS('\abstractname'), T_END)) },
1163        @LaTeXML::LIST]);
1164    DefMacroI('\maybe@end@abstract', undef, Tokens(), scope => 'global');
1165    return; },
1166  afterConstruct => sub {
1167    if (LookupValue('standalone_abstract_in_frontmatter')) {
1168      insertFrontMatter(@_); }
1169    return; },
1170  locked => 1, mode => 'text');
1171# If we get a plain \abstract, instead of an environment, look for \abstract{the abstract}
1172AssignValue('\abstract:locked' => 0);    # REDEFINE the above locked definition!
1173DefMacro('\abstract', sub {
1174    my ($gullet) = @_;
1175    ($gullet->ifNext(T_BEGIN)
1176      ? (T_CS('\abstract@onearg'))
1177      : (T_CS('\g@addto@macro'), T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract'),
1178        T_CS('\begin{abstract}'))); },
1179  locked => 1);
1180DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}\let\endabstract\relax');
1181
1182DefMacroI('\maybe@end@abstract', undef, '\endabstract');
1183
1184DefMacroI('\abstractname', undef, 'Abstract');
1185DefMacro('\format@title@abstract{}', '#1');
1186
1187# Hmm, titlepage is likely to be hairy, low-level markup,
1188# without even title, author, etc, specified as such!
1189# Hmm, should this even redefine author, title, etc so that they
1190# are simply output?
1191# This is horrible hackery; What we really need, I think, is the
1192# ability to bind some sort of "Do <this> when we create a text box"...
1193# ON Second Thought...
1194# For the time being, ignore titlepage!
1195# Maybe we could do some of this if there is no title/author
1196# otherwise defined? Ugh!
1197
1198#DefEnvironment('{titlepage}','');
1199# Or perhaps it's better just to ignore the markers?
1200#DefMacro('\titlepage','');
1201#DefMacro('\endtitlepage','');
1202
1203# Or perhaps not....
1204# There's a title and other stuff in here, but how could we guess?
1205# Well, there's likely to be a sequence of <p><text font="xx" fontsize="yy">...</text></p>
1206# Presumably the earlier, larger one is title, rest are authors/affiliations...
1207# Particularly, if they start with a pseudo superscript or other "marker", they're probably affil!
1208# For now, we just give an info message
1209DefEnvironment('{titlepage}', '<ltx:titlepage>#body',
1210  beforeDigest => sub { Let('\centering', '\relax');
1211    AddToMacro(T_CS('\maketitle'), T_CS('\unwind@titlepage'));
1212    DefEnvironmentI('abstract', undef,
1213      '<ltx:abstract>#body</ltx:abstract>');
1214    Info('unexpected', 'titlepage', $_[0],
1215      "When using titlepage, Frontmatter will not be well-structured");
1216    return; },
1217  beforeDigestEnd => sub { Digest(T_CS('\maybe@end@titlepage')); },
1218  locked          => 1, mode => 'text');
1219
1220Tag('ltx:titlepage', autoClose => 1);
1221DefConstructorI('\maybe@end@titlepage', undef, sub {
1222    my ($document) = @_;
1223    $document->maybeCloseElement('ltx:titlepage'); });
1224DefConstructorI('\unwind@titlepage', undef, sub {
1225    my ($document) = @_;
1226    if (my $titlepage = $document->maybeCloseElement('ltx:titlepage')) {
1227      $document->unwrapNodes($titlepage);
1228    }
1229});
1230
1231DefMacro('\sectionmark{}',       Tokens());
1232DefMacro('\subsectionmark{}',    Tokens());
1233DefMacro('\subsubsectionmark{}', Tokens());
1234DefMacro('\paragraphmark{}',     Tokens());
1235DefMacro('\subparagraphmark{}',  Tokens());
1236DefMacroI('\@oddfoot',  undef, Tokens());
1237DefMacroI('\@oddhed',   undef, Tokens());
1238DefMacroI('\@evenfoot', undef, Tokens());
1239DefMacroI('\@evenfoot', undef, Tokens());
1240#**********************************************************************
1241# C.6 Displayed Paragraphs
1242#**********************************************************************
1243
1244DefEnvironment('{center}', sub {
1245    $_[0]->maybeCloseElement('ltx:p');                        # this starts a new vertical block
1246    aligningEnvironment('center', 'ltx_centering', @_); },    # aligning will take care of \\\\ "rows"
1247  beforeDigest => sub {
1248    Let('\par', '\inner@par');
1249    Let('\\\\', '\inner@par'); });
1250# HOWEVER, define a plain \center to act like \centering (?)
1251DefMacroI('\center',    undef, '\centering');
1252DefMacroI('\endcenter', undef, '');
1253
1254DefEnvironment('{flushleft}', sub {
1255    $_[0]->maybeCloseElement('ltx:p');    # this starts a new vertical block
1256    aligningEnvironment('left', 'ltx_align_left', @_); },
1257  beforeDigest => sub {
1258    Let('\par', '\inner@par');
1259    Let('\\\\', '\inner@par'); });
1260DefEnvironment('{flushright}', sub {
1261    $_[0]->maybeCloseElement('ltx:p');    # this starts a new vertical block
1262    aligningEnvironment('right', 'ltx_align_right', @_); },
1263  beforeDigest => sub {
1264    Let('\par', '\inner@par');
1265    Let('\\\\', '\inner@par'); });
1266
1267# These add an operation to be carried out on the current node & following siblings, when the current group ends.
1268# These operators will add alignment (class) attributes to each "line" in the current block.
1269#DefPrimitiveI('\centering',   undef, sub { UnshiftValue(beforeAfterGroup=>T_CS('\@add@centering')); });
1270# NOTE: THere's a problem here.  The current method seems to work right for these operators
1271# appearing within the typical environments.  HOWEVER, it doesn't work for a simple \bgroup or \begingroup!!!
1272# (they don't create a node! or even a whatsit!)
1273DefConstructorI('\centering', undef,
1274  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
1275  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@centering')); });
1276DefConstructorI('\raggedright', undef,
1277  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
1278  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedright')); });
1279DefConstructorI('\raggedleft', undef,
1280  sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
1281  beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedleft')); });
1282
1283DefConstructorI('\@add@centering', undef,
1284  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
1285      map { setAlignOrClass($_[0], $_, 'center', 'ltx_centering') }
1286        $_[0]->getChildElements($node); } });
1287# Note that \raggedright is essentially align left
1288DefConstructorI('\@add@raggedright', undef,
1289  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
1290      map { setAlignOrClass($_[0], $_, undef, 'ltx_align_left') }
1291        $_[0]->getChildElements($node); } });
1292DefConstructorI('\@add@raggedleft', undef,
1293  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
1294      map { setAlignOrClass($_[0], $_, undef, 'ltx_align_right') }
1295        $_[0]->getChildElements($node); } });
1296
1297DefConstructorI('\@add@flushright', undef,
1298  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
1299      map { setAlignOrClass($_[0], $_, 'right', 'ltx_align_right') }
1300        $_[0]->getChildElements($node); } });
1301DefConstructorI('\@add@flushleft', undef,
1302  sub { if (my $node = LookupValue('ALIGNING_NODE')) {
1303      map { setAlignOrClass($_[0], $_, 'left', 'ltx_align_left') }
1304        $_[0]->getChildElements($node); } });
1305
1306#======================================================================-
1307# C.6.1 Quotations and Verse
1308#======================================================================-
1309DefConstructor('\@block@cr[Dimension]', "<ltx:break/>\n",
1310  reversion => Tokens(T_CS("\\\\"), T_CR));
1311DefEnvironment('{quote}',
1312  '<ltx:quote>#body</ltx:quote>',
1313  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
1314  mode         => 'text');
1315DefEnvironment('{quotation}',
1316  '<ltx:quote>#body</ltx:quote>',
1317  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
1318  mode         => 'text');
1319# NOTE: Handling of \\ within these environments?
1320DefEnvironment('{verse}',
1321  '<ltx:quote role="verse">#body</ltx:quote>',
1322  beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
1323  mode         => 'text');
1324
1325#======================================================================
1326# C.6.2 List-Making environments
1327#======================================================================
1328Tag('ltx:item',        autoClose => 1, autoOpen => 1);
1329Tag('ltx:inline-item', autoClose => 1, autoOpen => 1);
1330
1331# These are for the (not quite legit) case where \item appears outside
1332# of an itemize, enumerate, etc, environment.
1333# DefConstructor('\item[]',
1334#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
1335# DefConstructor('\subitem[]',
1336#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
1337# DefConstructor('\subsubitem[]',
1338#   "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
1339
1340# Or maybe best just to do \par ?
1341DefMacro('\item[]',       '\par');
1342DefMacro('\subitem[]',    '\par');
1343DefMacro('\subsubitem[]', '\par');
1344
1345AssignValue('@itemlevel' => 0, 'global');
1346AssignValue('enumlevel'  => 0, 'global');
1347AssignValue('@desclevel' => 0, 'global');
1348# protection against lower-level code...
1349DefConditional('\if@noitemarg');
1350DefMacro('\@item',      '\item');    # Hopefully no circles...
1351DefMacro('\@itemlabel', '');         # Maybe needs to be same as \item will be using?
1352
1353# Prepare for an list (itemize/enumerate/description/etc)
1354# by determining the right counter (level)
1355# and binding the right \item ( \$type@item, if $type is defined)
1356sub beginItemize {
1357  my ($type, $counter, %options) = @_;
1358  # The list-type and level of the *containing* list (if any!)
1359  my $outercounter = LookupValue('itemcounter');
1360  my $outerlevel   = $outercounter && (LookupValue($outercounter . 'level') || 0);
1361  $counter = '@item' unless $counter;
1362  my $listlevel = (LookupValue('itemization_level') || 0) + 1;    # level for this list overall
1363  my $level     = (LookupValue($counter . 'level')  || 0) + 1;    # level for lists of specific type
1364  AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep'));
1365  AssignValue('itemization_level' => $listlevel);
1366  AssignValue($counter . 'level'  => $level);
1367  AssignValue(itemization_items   => 0);
1368  my $listpostfix = ToString(Tokens(roman($listlevel)));
1369  my $postfix     = ToString(Tokens(roman($level)));
1370  my $usecounter  = ($options{nolevel} ? $counter : $counter . $postfix);
1371  Let('\item' => "\\" . $type . '@item') if defined $type;
1372  Let('\par', '\normal@par');                                     # In case within odd environment.
1373  DefMacroI('\@listctr', undef, Tokens(Explode($usecounter)));
1374  # Now arrange that this list's id's are relative to the current (outer) item (if any)
1375  # And that the items within this list's id's are relative to this (new) list.
1376##  if(! LookupDefinition(T_CS('\@listcontext'))){
1377##    Let(T_CS('\@listcontext'), T_CS('\@currentlabel')); }
1378  AssignValue(itemcounter => $usecounter);
1379  my $listcounter = '@itemize' . $listpostfix;
1380  if (!LookupValue('\c@' . $listcounter)) {    # Create new list counters as needed
1381    NewCounter($listcounter); }                #,  $outercounter.ToString(Tokens(roman($outerlevel))),
1382  if ($outercounter) {                         # Make this list's ID relative to outer list's ID
1383    my $outerusecounter = $outercounter . ToString(Tokens(roman($outerlevel)));
1384    DefMacroI('\the' . $listcounter . '@ID', undef,
1385      '\the' . $outerusecounter . '@ID.I' . '\arabic{' . $listcounter . '}');
1386    # AND reset this list's counter when the outer item is stepped
1387    my $x;
1388    AssignValue("\\cl\@$outerusecounter" =>
1389        Tokens(T_CS($listcounter), (($x = LookupValue('\cl@' . $outerusecounter)) ? $x->unlist : ())),
1390      'global');
1391  }
1392  # format the id of \item's relative to the id of this list
1393  DefMacroI('\the' . $usecounter . '@ID', undef, '\the' . $listcounter . '@ID.i' . '\@' . $usecounter . '@ID');
1394
1395  my $series;
1396  if ($series = $options{series}) {
1397    $series = ToString($series); }
1398  if (my $start = $options{start}) {
1399    SetCounter($usecounter, $start);
1400    AddToCounter($usecounter, Number(-1)); }
1401  elsif (my $s = $options{resume} || $options{'resume*'}) {
1402    if (($s = ToString($s)) ne 'noseries') {
1403      $series = ToString($s);
1404      SetCounter($usecounter,
1405        LookupValue('enumitem_series_' . $series . '_last') || Number(0)); } }
1406  else {
1407    ResetCounter($usecounter); }
1408
1409##  return RefStepCounter('@itemize' . $listpostfix); }
1410  return (RefStepCounter('@itemize' . $listpostfix),
1411    counter => $usecounter, series => $series); }    # So end can save counter value
1412
1413# These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements
1414# Only create the 1st level (so that binding style can start numbering 'within' appropriately)
1415# Additional ones created by need.
1416NewCounter('@itemizei', 'section', idprefix => 'I');
1417
1418# Create id, and tags for an itemize type \item
1419sub RefStepItemCounter {
1420  my ($tag)   = @_;
1421  my $counter = LookupValue('itemcounter');
1422  my $n       = LookupValue('itemization_items');
1423  AssignValue(itemization_items => $n + 1);
1424  my %attr = ();
1425  my $sep  = LookupDimension('\itemsep');
1426  if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) {
1427    $attr{itemsep} = $sep; }
1428  if (defined $tag) {
1429    my @props = RefStepID($counter);
1430    if ((ref $tag) && !scalar($tag->unlist)) {    # empty tag?
1431      return (@props); }
1432    else {
1433      my $ttag      = (ref $tag              ? $tag                      : T_OTHER($tag));
1434      my $formatter = ($counter =~ /^\@desc/ ? T_CS('\descriptionlabel') : T_CS('\makelabel'));
1435      my $typename  = (IsDefined(T_CS('\\' . $counter . 'name'))
1436        ? T_CS('\\' . $counter . 'name') : T_CS('\itemtyperefname'));
1437      my $tags = Digest(T_BEGIN,
1438        T_CS('\let'), T_CS('\the' . $counter),   T_CS('\@empty'),
1439        T_CS('\def'), T_CS('\fnum@' . $counter), T_BEGIN, $formatter, T_BEGIN, Revert($tag), T_END, T_END,
1440        T_CS('\def'), T_CS('\typerefnum@' . $counter),
1441        T_BEGIN,      $typename, T_SPACE, Revert($tag), T_END,
1442        Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
1443        T_END);
1444
1445      return (@props,
1446        ($tags ? (tags => $tags) : ()),
1447        %attr); } }
1448  else {
1449    return (RefStepCounter($counter), %attr); } }
1450
1451# The following two aren't used here; they're defined here so they
1452# can be used in paralist.sty, enumerate.sty (perhaps others?)
1453
1454# This isn't really satisfactory.
1455# We should record the marker used for the item,
1456# but it really should NOT be #refnum (which should be quasi unique)
1457# and is not \theenumi.. (which should be a counter value)
1458sub setItemizationStyle {
1459  my ($stuff, $level) = @_;
1460  if (defined $stuff) {
1461    $level = LookupValue('@itemlevel') || 0 unless defined $level;
1462    $level = ToString(Tokens(roman($level)));
1463    DefMacroI('\labelitem' . $level, undef, $stuff); }
1464  return; }
1465
1466sub setEnumerationStyle {
1467  my ($stuff, $level) = @_;
1468  if (defined $stuff) {
1469    $level = LookupValue('enumlevel') || 0 unless defined $level;
1470    $level = ToString(Tokens(roman($level)));
1471    my @in  = $stuff->unlist;
1472    my @out = ();
1473    my $ctr = T_OTHER('enum' . $level);
1474
1475    while (my $t = shift(@in)) {
1476      if (Equals($t, T_BEGIN)) {
1477        push(@out, $t);
1478        my $brlevel = 1;
1479        while ($brlevel && ($t = shift(@in))) {
1480          if    (Equals($t, T_BEGIN)) { $brlevel++; }
1481          elsif (Equals($t, T_END))   { $brlevel--; }
1482          push(@out, $t); } }
1483      elsif (Equals($t, T_LETTER('A'))) {
1484        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Alph'), $ctr));
1485        push(@out, T_CS('\theenum' . $level)); }
1486      elsif (Equals($t, T_LETTER('a'))) {
1487        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\alph'), $ctr));
1488        push(@out, T_CS('\theenum' . $level)); }
1489      elsif (Equals($t, T_LETTER('I'))) {
1490        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Roman'), $ctr));
1491        push(@out, T_CS('\theenum' . $level)); }
1492      elsif (Equals($t, T_LETTER('i'))) {
1493        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\roman'), $ctr));
1494        push(@out, T_CS('\theenum' . $level)); }
1495      elsif (Equals($t, T_OTHER('1'))) {
1496        DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\arabic'), $ctr));
1497        push(@out, T_CS('\theenum' . $level)); }
1498      else {
1499        push(@out, $t); } }
1500    DefMacroI('\labelenum' . $level, undef, Tokens(T_BEGIN, @out, T_END)); }
1501  return; }
1502
1503# id, but NO refnum (et.al) attributes on itemize \item ...
1504# unless the optional tag argument was given!
1505# We'll make the <ltx:tag> from either the optional arg, or from \labelitemi..
1506DefMacro('\itemize@item', '\par\itemize@item@');
1507DefConstructor('\itemize@item@ OptionalUndigested',
1508  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
1509  properties => sub { RefStepItemCounter($_[1]); });
1510DefConstructor('\inline@itemize@item OptionalUndigested',
1511  "<ltx:inline-item xml:id='#id'>#tags",
1512  properties => sub { RefStepItemCounter($_[1]); });
1513
1514DefMacro('\enumerate@item', '\par\enumerate@item@');
1515DefConstructor('\enumerate@item@ OptionalUndigested',
1516  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
1517  properties => sub { RefStepItemCounter($_[1]); });
1518DefConstructor('\inline@enumerate@item OptionalUndigested',
1519  "<ltx:inline-item xml:id='#id'>#tags",
1520  properties => sub { RefStepItemCounter($_[1]); });
1521
1522DefMacro('\description@item', '\par\description@item@');
1523DefConstructor('\description@item@ OptionalUndigested',
1524  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
1525  properties => sub { RefStepItemCounter($_[1]); });
1526DefConstructor('\inline@description@item OptionalUndigested',
1527  "<ltx:inline-item xml:id='#id'>#tags",
1528  properties => sub { RefStepItemCounter($_[1]); });
1529
1530DefEnvironment('{itemize}',
1531  "<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
1532  properties      => sub { beginItemize('itemize', '@item'); },
1533  beforeDigestEnd => sub { Digest('\par'); },
1534  locked          => 1, mode => 'text');
1535DefEnvironment('{enumerate}',
1536  "<ltx:enumerate xml:id='#id'>#body</ltx:enumerate>",
1537  properties      => sub { beginItemize('enumerate', 'enum'); },
1538  beforeDigestEnd => sub { Digest('\par'); },
1539  locked          => 1, mode => 'text');
1540DefEnvironment('{description}',
1541  "<ltx:description  xml:id='#id'>#body</ltx:description>",
1542  beforeDigest    => sub { Let('\makelabel', '\descriptionlabel'); },
1543  properties      => sub { beginItemize('description', '@desc'); },
1544  beforeDigestEnd => sub { Digest('\par'); },
1545  locked          => 1, mode => 'text');
1546
1547DefMacro('\makelabel{}', '#1');
1548
1549#----------------------------------------------------------------------
1550# Basic itemize bits
1551# Fake counter for itemize to give id's to ltx:item.
1552NewCounter('@itemi',   undef, idwithin => '@itemizei', idprefix => 'i');
1553NewCounter('@itemii',  undef, idwithin => '@itemi',    idprefix => 'i');
1554NewCounter('@itemiii', undef, idwithin => '@itemii',   idprefix => 'i');
1555NewCounter('@itemiv',  undef, idwithin => '@itemiii',  idprefix => 'i');
1556NewCounter('@itemv',   undef, idwithin => '@itemiv',   idprefix => 'i');
1557NewCounter('@itemvi',  undef, idwithin => '@itemv',    idprefix => 'i');
1558# These are empty to make the 'refnum' go away.
1559DefMacroI('\the@itemi',   undef, '');
1560DefMacroI('\the@itemii',  undef, '');
1561DefMacroI('\the@itemiii', undef, '');
1562DefMacroI('\the@itemiv',  undef, '');
1563DefMacroI('\the@itemv',   undef, '');
1564DefMacroI('\the@itemvi',  undef, '');
1565
1566# Formatted item tags.
1567# Really should be in the class file, but already was here.
1568DefMacroI('\labelitemi',   undef, '\textbullet');
1569DefMacroI('\labelitemii',  undef, '\normalfont\bfseries \textendash');
1570DefMacroI('\labelitemiii', undef, '\textasteriskcentered');
1571DefMacroI('\labelitemiv',  undef, '\textperiodcentered');
1572
1573# Make the fake counters point to the real labels
1574DefMacroI('\label@itemi',   undef, '\labelitemi');
1575DefMacroI('\label@itemii',  undef, '\labelitemii');
1576DefMacroI('\label@itemiii', undef, '\labelitemiii');
1577DefMacroI('\label@itemiv',  undef, '\labelitemiv');
1578
1579# These hookup latexml's tagging to normal latex's \labelitemi...
1580DefMacroI('\fnum@@itemi',   undef, '{\makelabel{\label@itemi}}');
1581DefMacroI('\fnum@@itemii',  undef, '{\makelabel{\label@itemii}}');
1582DefMacroI('\fnum@@itemiii', undef, '{\makelabel{\label@itemiii}}');
1583DefMacroI('\fnum@@itemiv',  undef, '{\makelabel{\label@itemiv}}');
1584
1585# These define the typerefnum form, for out-of-context \ref's
1586# Better would language sensitive!
1587my @pm_ordinal_suffices = ('th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th');
1588DefMacro('\lx@poormans@ordinal{}', sub {
1589    my ($gullet, $ctr) = @_;
1590    my $n      = CounterValue($ctr)->valueOf;
1591    my $string = "$n";
1592    if ($string =~ /.*?(\d)$/) {
1593      $string .= $pm_ordinal_suffices[$1]; }
1594    T_OTHER($string); });
1595DefMacroI('\itemtyperefname', undef, 'item');
1596DefMacroI('\itemcontext',     undef, '\space in \@listcontext');
1597DefMacroI('\itemcontext',     undef, '');
1598# Probably would help to give a bit more context for the ii & higher?
1599DefMacroI('\typerefnum@@itemi', undef, '\lx@poormans@ordinal{@itemi} \itemtyperefname \itemcontext');
1600DefMacroI('\typerefnum@@itemii', undef, '\lx@poormans@ordinal{@itemii} \itemtyperefname \itemcontext');
1601DefMacroI('\typerefnum@@itemiii', undef, '\lx@poormans@ordinal{@itemiii} \itemtyperefname \itemcontext');
1602DefMacroI('\typerefnum@@itemiv', undef, '\lx@poormans@ordinal{@itemiv} \itemtyperefname \itemcontext');
1603#----------------------------------------------------------------------
1604# Basic enumeration bits
1605
1606# Class file should have
1607#  NewCounter for enumi,...,
1608#  define \labelenumi,... and probably \p@enumii...
1609
1610# How the refnums look... (probably should be in class file, but already here)
1611DefMacroI('\p@enumii',  undef, '\theenumi');
1612DefMacroI('\p@enumiii', undef, '\theenumi(\theenumii)');
1613DefMacroI('\p@enumiv',  undef, '\p@enumii\theenumiii');
1614
1615# Formatting of item tags (probably should be in the class file, but already here)
1616DefMacroI('\labelenumi',   undef, '\theenumi.');
1617DefMacroI('\labelenumii',  undef, '(\theenumii)');
1618DefMacroI('\labelenumiii', undef, '\theenumiii.');
1619DefMacroI('\labelenumiv',  undef, '\theenumiv.');
1620
1621# These hookup latexml's tagging to normal latex's \labelenummi...
1622DefMacroI('\fnum@enumi',   undef, '{\makelabel{\labelenumi}}');
1623DefMacroI('\fnum@enumii',  undef, '{\makelabel{\labelenumii}}');
1624DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}');
1625DefMacroI('\fnum@enumiv',  undef, '{\makelabel{\labelenumiv}}');
1626
1627# These define the typerefnum form, for out-of-context \ref's
1628DefMacroI('\enumtyperefname',    undef, 'item');
1629DefMacroI('\typerefnum@enumi',   undef, '\enumtyperefname~\p@enumi\theenumi \itemcontext');
1630DefMacroI('\typerefnum@enumii',  undef, '\enumtyperefname~\p@enumii\theenumii \itemcontext');
1631DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\theenumiii \itemcontext');
1632DefMacroI('\typerefnum@enumiv',  undef, '\enumtyperefname~\p@enumiv\theenumiv \itemcontext');
1633#DefMacroI('\typerefnum@enumi',   undef, '\enumtyperefname~\p@enumi\labelenumi \itemcontext');
1634#DefMacroI('\typerefnum@enumii',  undef, '\enumtyperefname~\p@enumii\labelenumii \itemcontext');
1635#DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\labelenumiii \itemcontext');
1636#DefMacroI('\typerefnum@enumiv',  undef, '\enumtyperefname~\p@enumiv\labelenumiv \itemcontext');
1637
1638#----------------------------------------------------------------------
1639# Basic description list bits
1640# Fake counter for itemize to give id's to ltx:item.
1641NewCounter('@desci',   undef, idwithin => '@itemizei', idprefix => 'i');
1642NewCounter('@descii',  undef, idwithin => '@desci',    idprefix => 'i');
1643NewCounter('@desciii', undef, idwithin => '@descii',   idprefix => 'i');
1644NewCounter('@desciv',  undef, idwithin => '@desciii',  idprefix => 'i');
1645NewCounter('@descv',   undef, idwithin => '@desciv',   idprefix => 'i');
1646NewCounter('@descvi',  undef, idwithin => '@descv',    idprefix => 'i');
1647# No refnum's here, either
1648DefMacroI('\the@desci',   undef, '');
1649DefMacroI('\the@descii',  undef, '');
1650DefMacroI('\the@desciii', undef, '');
1651DefMacroI('\the@desciv',  undef, '');
1652DefMacroI('\the@descv',   undef, '');
1653DefMacroI('\the@descvi',  undef, '');
1654# These hookup latexml's numbering to normal latex's
1655# Umm.... but they're not normally used, since \item usually gets an argument!
1656DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1');
1657DefMacroI('\fnum@@desci',   undef, '{\descriptionlabel{}}');
1658DefMacroI('\fnum@@descii',  undef, '{\descriptionlabel{}}');
1659DefMacroI('\fnum@@desciii', undef, '{\descriptionlabel{}}');
1660DefMacroI('\fnum@@desciv',  undef, '{\descriptionlabel{}}');
1661
1662DefMacroI('\desctyperefname', undef, 'item');
1663
1664# Blech
1665map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\itemtyperefname'); }
1666  qw(@itemi @itemii @itemiii @itemiv @itemv @itemvi);
1667map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\enumtyperefname'); }
1668  qw(enumi enumii enumiii enumiv);
1669map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\desctyperefname'); }
1670  qw(@desci @descii @desciii @desciv @descv @descvi);
1671
1672#======================================================================
1673# C.6.3 The list and trivlist environments.
1674#======================================================================
1675# Generic lists are given a way to format the item label, and presumably
1676# a counter.
1677
1678DefConditional('\if@nmbrlist');
1679DefMacro('\@listctr', '');
1680DefPrimitive('\usecounter{}', sub {
1681    my ($stomach, $counter) = @_;
1682    $counter = ToString(Expand($counter));
1683    beginItemize('list', $counter, ($counter ? (nolevel => 1) : ()));
1684    return; });
1685
1686DefMacro('\list{}{}',
1687'\let\@listctr\@empty#2\ifx\@listctr\@empty\usecounter{}\fi\expandafter\def\csname fnum@\@listctr\endcsname{#1}\lx@list');
1688DefMacro('\endlist', '\endlx@list');
1689
1690DefConstructor('\lx@list DigestedBody',
1691  "<ltx:itemize>#1</ltx:itemize>",
1692  beforeDigest => sub { $_[0]->bgroup; });
1693DefPrimitive('\endlx@list', sub { $_[0]->egroup; });
1694
1695DefConstructor('\list@item OptionalUndigested',
1696  "<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
1697  properties => sub { RefStepItemCounter($_[1]); });
1698
1699# This isn't quite right, although it seems right for deep, internal uses with a single \item.
1700# Perhaps we need to check trivlist's afterwards and if they are just a single item,
1701# reduce it to an ltx:p ??
1702# DefMacro('\trivlist@item[]', '');
1703# DefEnvironment('{trivlist}',
1704#   '<ltx:p>#body</ltx:p>',
1705#   beforeDigest => sub { Let('\item', '\trivlist@item'); });
1706
1707DefEnvironment('{trivlist}',
1708  "<ltx:itemize>#body</ltx:itemize>",
1709  properties      => sub { beginItemize('trivlist'); },
1710  beforeDigestEnd => sub { Digest('\par'); });
1711
1712DefMacro('\trivlist@item', '\par\trivlist@item@');
1713DefConstructor('\trivlist@item@ OptionalUndigested',
1714  "<ltx:item xml:id='#id' itemsep='#itemsep'>"
1715    . "<ltx:tags><ltx:tag>#tag</ltx:tag></ltx:tags>",    # At least an empty tag! ?
1716  properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); });
1717
1718DefRegister('\topsep'             => Glue(0));
1719DefRegister('\partopsep'          => Glue(0));
1720DefRegister('\lx@default@itemsep' => Glue(0));
1721DefRegister('\itemsep'            => Glue(0));
1722DefRegister('\parsep'             => Glue(0));
1723DefRegister('\@topsep'            => Glue(0));
1724DefRegister('\@topsepadd'         => Glue(0));
1725DefRegister('\@outerparskip'      => Glue(0));
1726DefRegister('\leftmargin'         => Dimension(0));
1727DefRegister('\rightmargin'        => Dimension(0));
1728DefRegister('\listparindent'      => Dimension(0));
1729DefRegister('\itemindent'         => Dimension(0));
1730DefRegister('\labelwidth'         => Dimension(0));
1731DefRegister('\labelsep'           => Dimension(0));
1732DefRegister('\@totalleftmargin'   => Dimension(0));
1733DefRegister('\leftmargini'        => Dimension(0));
1734DefRegister('\leftmarginii'       => Dimension(0));
1735DefRegister('\leftmarginiii'      => Dimension(0));
1736DefRegister('\leftmarginiv'       => Dimension(0));
1737DefRegister('\leftmarginv'        => Dimension(0));
1738DefRegister('\leftmarginvi'       => Dimension(0));
1739DefRegister('\@listdepth'         => Number(0));
1740DefRegister('\@itempenalty'       => Number(0));
1741DefRegister('\@beginparpenalty'   => Number(0));
1742DefRegister('\@endparpenalty'     => Number(0));
1743DefRegister('\labelwidthi'        => Dimension(0));
1744DefRegister('\labelwidthii'       => Dimension(0));
1745DefRegister('\labelwidthiii'      => Dimension(0));
1746DefRegister('\labelwidthiv'       => Dimension(0));
1747DefRegister('\labelwidthv'        => Dimension(0));
1748DefRegister('\labelwidthvi'       => Dimension(0));
1749
1750DefRegister('\@itemdepth' => Number(0));
1751
1752#======================================================================
1753# C.6.4 Verbatim
1754#======================================================================
1755
1756# NOTE: how's the best way to get verbatim material through?
1757DefEnvironment('{verbatim}',  '<ltx:verbatim>#body</ltx:verbatim>');
1758DefEnvironment('{verbatim*}', '<ltx:verbatim>#body</ltx:verbatim>');
1759
1760DefMacroI('\@verbatim', undef,
1761  '\par\aftergroup\lx@end@verbatim\lx@@verbatim');    # Close enough?
1762DefConstructorI('\lx@@verbatim', undef,
1763  "<ltx:verbatim font='#font'>",
1764  beforeDigest => sub {
1765    my ($stomach) = @_;
1766    StartSemiverbatim('%', '\\', '{', '}');
1767    MergeFont(family => 'typewriter', series => 'medium', shape => 'upright');
1768    $STATE->assignCatcode(' ', CC_ACTIVE);    # Do NOT (necessarily) skip spaces after \verb!!!
1769    Let(T_ACTIVE(' '), T_SPACE); });
1770DefConstructorI('\lx@end@verbatim', undef,
1771  "</ltx:verbatim>",
1772  beforeDigest => sub {
1773    EndSemiverbatim(); });
1774
1775# verbatim is a bit of special case;
1776# It looks like an environment, but it only ends with an explicit "\end{verbatim}" on it's own line.
1777# So, we'll end up doing things more manually.
1778# We're going to sidestep the Gullet for inputting,
1779# and also the usual environment capture.
1780DefConstructorI(T_CS('\begin{verbatim}'), undef,
1781  "<ltx:verbatim font='#font'>#body</ltx:verbatim>",
1782  beforeDigest => [sub { $_[0]->bgroup;
1783      my @stuff = ();
1784      if (my $b = LookupValue('@environment@verbatim@atbegin')) {
1785        push(@stuff, Digest(@$b)); }
1786      AssignValue(current_environment => 'verbatim');
1787      DefMacroI('\@currenvir', undef, 'verbatim');
1788      MergeFont(family => 'typewriter');
1789      # Digest(T_CS('\par')); # NO! See beforeConstruct!
1790      @stuff; }],
1791  afterDigest => [sub {
1792      my ($stomach, $whatsit) = @_;
1793      #      $stomach->egroup;
1794      my $font   = $whatsit->getFont;
1795      my $loc    = $whatsit->getLocator;
1796      my $end    = "\\end{verbatim}";
1797      my @lines  = ();
1798      my $gullet = $stomach->getGullet;
1799      while (defined(my $line = $gullet->readRawLine)) {
1800        # The raw chars will still have to be decoded (but not space!!)
1801        $line = join('', map { ($_ eq ' ' ? ' ' : FontDecodeString($_, 'OT1_typewriter')) }
1802            split(//, $line));
1803        if ($line =~ /^(.*?)\\end\{verbatim\}(.*?)$/) {
1804          push(@lines, $1 . "\n"); $gullet->unread(Tokenize($2), T_CR);
1805          last; }
1806        push(@lines, $line . "\n"); }
1807      pop(@lines) if $lines[-1] eq "\n";
1808      # Note last line ends up as Whatsit's "trailer"
1809      if (my $b = LookupValue('@environment@verbatim@atend')) {
1810        push(@lines, ToString(Digest(@$b))); }
1811      $stomach->egroup;
1812      $whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end);
1813      return; }],
1814  beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });
1815
1816DefPrimitiveI('\@vobeyspaces', undef, sub {
1817    AssignCatcode(" " => 13);
1818    Let(T_ACTIVE(" "), '\nobreakspace');
1819    return });
1820
1821# WARNING: Need to be careful about what catcodes are active here
1822# And clearly separate expansion from digestion
1823DefMacroI('\verb', undef, sub {
1824    my ($gullet) = @_;
1825    StartSemiverbatim('%', '\\', '{', '}');
1826    $STATE->assignCatcode(' ', CC_ACTIVE);    # Do NOT (necessarily) skip spaces after \verb!!!
1827    my $init    = $gullet->readToken;
1828    my $starred = 0;
1829    if (T_OTHER('*')->equals($init)) {
1830      $starred = 1;
1831      $init    = $gullet->readToken; }
1832    if (!$init) {    # typically something read too far got \verb and the content is somewhere else..?
1833      Error('expected', 'delimiter', $gullet,
1834        "Verbatim argument lost", "Bindings for preceding code is probably broken");
1835      EndSemiverbatim();
1836      return (); }
1837    my $init_str = $init->getString();
1838    AssignCatcode($init_str => CC_ACTIVE);
1839    my $delim = T_ACTIVE($init_str);
1840    my $body  = $gullet->readUntil($delim);
1841    EndSemiverbatim();
1842    Tokens(
1843      T_CS('\@hidden@bgroup'),
1844      ($starred ? T_CS('\lx@use@visiblespace') : ()),
1845      Invocation(T_CS('\@internal@verb'),
1846        ($starred ? T_OTHER('*') : Tokens()),
1847        Tokens($init), $body),
1848      T_CS('\@hidden@egroup'))->unlist; });
1849
1850DefPrimitive('\lx@use@visiblespace', sub {
1851    my ($stomach, $star) = @_;
1852    $STATE->assignCatcode(' ', CC_ACTIVE);      # Do NOT (necessarily) skip spaces after \verb!!!
1853    Let(T_ACTIVE(' '), T_OTHER("\x{2423}"));    # Visible space
1854});
1855
1856DefConstructor('\@internal@verb{} Undigested {}',
1857  "?#isMath(<ltx:XMTok font='#font'>#text</ltx:XMTok>)"
1858    . "(<ltx:verbatim font='#font'>#text</ltx:verbatim>)",
1859  properties => sub { (text => ToString($_[3])); },
1860  font       => { family => 'typewriter', series => 'medium', shape => 'upright' },
1861  #  bounded         => 1,
1862  beforeConstruct => sub {
1863    my ($doc, $whatsit) = @_;
1864    if (!$whatsit->isMath) {
1865      $doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); } },
1866  reversion => '\verb#1#2#3#2');
1867
1868# This is defined by the alltt package.
1869# Environment('alltt', ?);
1870
1871# Actually, latex sets catcode to 13 ... is this close enough?
1872DefPrimitiveI('\obeycr',    undef, sub { AssignValue('PRESERVE_NEWLINES' => 1); });
1873DefPrimitiveI('\restorecr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 0); });
1874
1875DefMacroI('\normalsfcodes', undef, Tokens());
1876
1877#**********************************************************************
1878# C.7 Mathematical Formulas
1879#**********************************************************************
1880
1881#======================================================================
1882# C.7.1 Math Mode Environments
1883#======================================================================
1884DefMacroI('\@eqnnum', undef, '(\theequation)');
1885DefMacro('\fnum@equation', '\@eqnnum');
1886
1887# Redefined from TeX.pool, since with LaTeX we presumably have a more complete numbering system
1888DefConstructorI('\@@BEGINDISPLAYMATH', undef,
1889  "<ltx:equation xml:id='#id'>"
1890    . "<ltx:Math mode='display'>"
1891    . "<ltx:XMath>"
1892    . "#body"
1893    . "</ltx:XMath>"
1894    . "</ltx:Math>"
1895    . "</ltx:equation>",
1896  alias        => '$$',
1897  beforeDigest => sub {
1898    $_[0]->beginMode('display_math');
1899    if (my @everymath_toks = $STATE->lookupDefinition(T_CS('\everymath'))->valueOf->unlist()) {
1900      $_[0]->getGullet->unread(@everymath_toks); }
1901    if (my @everydisplay_toks = $STATE->lookupDefinition(T_CS('\everydisplay'))->valueOf->unlist()) {
1902      $_[0]->getGullet->unread(@everydisplay_toks); }
1903    return; },
1904  properties  => sub { RefStepID('equation') },
1905  captureBody => 1);
1906
1907DefEnvironment('{displaymath}',
1908  "<ltx:equation xml:id='#id'>"
1909    . "<ltx:Math mode='display'>"
1910    . "<ltx:XMath>"
1911    . "#body"
1912    . "</ltx:XMath>"
1913    . "</ltx:Math>"
1914    . "</ltx:equation>",
1915  mode       => 'display_math',
1916  properties => sub { RefStepID('equation') },
1917  locked     => 1);
1918DefEnvironment('{math}',
1919  "<ltx:Math mode='inline'>"
1920    . "<ltx:XMath>"
1921    . "#body"
1922    . "</ltx:XMath>"
1923    . "</ltx:Math>",
1924  mode => 'inline_math',
1925);
1926
1927Let('\curr@math@size', '\@empty');
1928
1929# Equation Numbering turns out to be rather convoluted!
1930# numbered equations & friends want to IMMEDIATELY increment the equation counter
1931# (you can see by \theequation)
1932# However, \nonumber, \tag, set a different number and RETRACT stepping the counter!
1933NewCounter('subequation', 'equation', idprefix => 'E', idwithin => 'equation');
1934DefMacro('\thesubequation',   '\theequation\alph{subequation}');
1935DefMacro('\fnum@subequation', '(\thesubequation)');
1936
1937# This provides {equation} with the capabilities for tags, nonumber, etc
1938# even though stock LaTeX provides no means to override them.
1939#   preset => boolean
1940#   postset => boolean
1941#   deferretract=>boolean
1942sub prepareEquationCounter {
1943  my (%options) = @_;
1944  AssignValue(EQUATION_NUMBERING => {%options}, 'global');
1945  return; }
1946
1947sub beforeEquation {
1948  my $numbering = LookupValue('EQUATION_NUMBERING');
1949  my $numbered  = $$numbering{numbered};
1950  my $ctr       = $$numbering{counter} || 'equation';
1951  MaybePeekLabel();
1952  $$numbering{in_equation} = 1;
1953  if ($$numbering{preset}) {
1954    AssignValue(EQUATIONROW_TAGS => {
1955        preset => 1,
1956        ($numbered ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
1957  else {
1958    AssignValue(EQUATIONROW_TAGS => {}, 'global'); }
1959  Let('\lx@saved@BEGINDISPLAYMATH', '\@@BEGINDISPLAYMATH');
1960  Let('\lx@saved@ENDDISPLAYMATH',   '\@@ENDDISPLAYMATH');
1961  Let('\@@ENDDISPLAYMATH',          '\lx@eDM@in@equation');
1962  Let('\@@BEGINDISPLAYMATH',        '\lx@bDM@in@equation');
1963  return; }
1964
1965# Note peculiar usages of \[,\],$$ WITHIN displayed math, like {equation}.
1966# Authors sometimes use \begin{equation} math \] some text \[ more math \end{equation}
1967# to create a cheap intertext. And, the refnum appears on the SECOND equation!
1968# But in redefining these, note that a "valid" \[ math \] might be used within some
1969# sort of text insertion (eg \footnote)!
1970# So, we've got to dance around with redefinitions!!
1971# SIDE NOTE: \[,\] are really just $$ w/ appropriate error checking!
1972DefMacro('\lx@bDM@in@equation', '\lx@saved@BEGINDISPLAYMATH\let\@@ENDDISPLAYMATH\lx@saved@ENDDISPLAYMATH');
1973DefMacro('\lx@eDM@in@equation',
1974  '\lx@retract@eqnno\lx@begin@fake@intertext'
1975    . '\let\lx@saved@BEGINDISPLAYMATH\@@BEGINDISPLAYMATH\let\lx@saved@bdm\['
1976    . '\let\@@BEGINDISPLAYMATH\lx@end@fake@intertext'
1977    . '\let\[\lx@end@fake@intertext');
1978
1979DefMacro('\lx@begin@fake@intertext', '\end{equation}');
1980DefMacro('\lx@end@fake@intertext',
1981  '\let\@@BEGINDISPLAYMATH\lx@saved@BEGINDISPLAYMATH\let\[\lx@saved@bdm' .
1982    '\begin{equation}');
1983DefPrimitive('\lx@retract@eqnno', sub { retractEquation(); });
1984
1985sub retractEquation {
1986  my $numbering = LookupValue('EQUATION_NUMBERING');
1987  # What about scopes? Is that handled automagically?
1988  # What about \@currentID....
1989  my $tags = LookupValue('EQUATIONROW_TAGS');
1990  my $ctr  = $$tags{counter} || $$numbering{counter} || 'equation';
1991  if ($$tags{preset}) {    # ONLY if the number was preset!
1992                           # counter (or ID counter) was stepped, so decrement it.
1993    AddToCounter(($$numbering{numbered} ? $ctr : 'UN' . $ctr), Number(-1)); }
1994  AssignValue(EQUATIONROW_TAGS => { RefStepID($ctr), reset => 1 }, 'global');
1995  return; }
1996
1997# Disable this equation's number
1998# Ahhhh, but in eqnarray, this isn't effected until \\ !!!!!
1999DefMacroI('\nonumber', undef, '\lx@equation@nonumber');
2000DefPrimitiveI('\lx@equation@nonumber', undef, sub {
2001    my $numbering = LookupValue('EQUATION_NUMBERING');
2002    if ($$numbering{in_equation}) {
2003      if ($$numbering{deferretract}) {
2004        my $tags = LookupValue('EQUATIONROW_TAGS');
2005        $$tags{retract} = 1; }
2006      else {
2007        retractEquation(); } }
2008    return; });
2009Let('\@LTX@nonumber', '\lx@equation@nonumber');
2010
2011# Set this equation's number explicitly (eg ams's \tag)
2012DefMacroI('\lx@equation@settag', undef, '\lx@equation@retract\lx@equation@settag@');
2013DefPrimitiveI('\lx@equation@retract', undef, sub { retractEquation(); });
2014DefPrimitive('\lx@equation@settag@ Digested', sub {
2015    my $tags = LookupValue('EQUATIONROW_TAGS');
2016    $$tags{tags} = $_[1];
2017    return; },
2018  mode => 'text');
2019
2020sub afterEquation {
2021  my ($whatsit) = @_;
2022  my $numbering = LookupValue('EQUATION_NUMBERING');
2023  my $tags      = LookupValue('EQUATIONROW_TAGS');
2024  my $ctr       = $$tags{counter} || $$numbering{counter} || 'equation';
2025  if (!$$tags{noretract}
2026    && ($$tags{retract} || ($$numbering{retract} && $$numbering{preset} && $$tags{preset}))) {
2027    retractEquation(); }
2028  elsif ($$numbering{postset} && !$$tags{reset}) {
2029    # my %props = ();
2030    # if ($$numbering{numbered}) {
2031    #   %props = RefStepCounter($ctr); }
2032    # else {
2033    #   %props = RefStepID($ctr); }
2034    # AssignValue(EQUATIONROW_TAGS => {%props}, 'global'); }
2035    AssignValue(EQUATIONROW_TAGS => {
2036        ($$numbering{numbered} ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
2037  elsif (!$$tags{reset} && $$numbering{numbered}) {
2038    $$tags{tags} = Digest(Invocation(T_CS('\lx@make@tags'), $ctr)); }
2039
2040  # Now install the tags in $whatsit or current Row, as appropriate.
2041  my $props = LookupValue('EQUATIONROW_TAGS');
2042  if ($$numbering{aligned}) {
2043    if (my $alignment = LookupValue('Alignment')) {
2044      my $row = $alignment->currentRow;
2045      $$row{id}   = $$props{id};
2046      $$row{tags} = $$props{tags}; } }
2047  elsif ($whatsit) {
2048    $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); }
2049  $$numbering{in_equation} = 0;
2050  return; }
2051
2052# My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...?
2053DefEnvironment('{equation}',
2054  "<ltx:equation xml:id='#id'>"
2055    . "#tags"
2056    . "<ltx:Math mode='display'>"
2057    . "<ltx:XMath>"
2058    . "#body"
2059    . "</ltx:XMath>"
2060    . "</ltx:Math>"
2061    . "</ltx:equation>",
2062  mode         => 'display_math',
2063  beforeDigest => sub {
2064    prepareEquationCounter(numbered => 1, preset => 1);
2065    beforeEquation(); },
2066  afterDigestBody => sub {
2067    afterEquation($_[1]); },
2068  locked => 1);
2069
2070# Note: In ams, this DOES get a number if \tag is used!
2071DefEnvironment('{equation*}',
2072  "<ltx:equation xml:id='#id'>"
2073    . "#tags"
2074    . "<ltx:Math mode='display'>"
2075    . "<ltx:XMath>"
2076    . "#body"
2077    . "</ltx:XMath>"
2078    . "</ltx:Math>"
2079    . "</ltx:equation>",
2080  mode         => 'display_math',
2081  beforeDigest => sub {
2082    prepareEquationCounter(numbered => undef, preset => 1);
2083    beforeEquation(); },
2084  afterDigestBody => sub {
2085    afterEquation($_[1]); },
2086  locked => 1);
2087
2088DefMacro('\[', '\@@BEGINDISPLAYMATH');
2089DefMacro('\]', '\@@ENDDISPLAYMATH');
2090DefMacro('\(', '\@@BEGININLINEMATH');
2091DefMacro('\)', '\@@ENDINLINEMATH');
2092
2093# Keep from expanding too early, if in alignments, or such.
2094DefMacroI('\ensuremath', undef,
2095  Tokens(T_CS('\protect'), T_CS('\@ensuremath')));
2096DefMacro('\@ensuremath{}', sub {
2097    my ($gullet, $stuff) = @_;
2098    if (LookupValue('IN_MATH')) { $stuff->unlist; }
2099    else { (T_MATH, $stuff->unlist, T_MATH); } });
2100
2101# Magic check that math-mode trigger follows
2102our $MATHENVS = 'displaymath|equation*?|eqnarray*?'
2103  . '|multline*?|align*?|falign*?|alignat*?|xalignat*?|xxalignat*?|gather*?';
2104DefMacro('\ensuremathfollows', sub {
2105    my ($gullet) = @_;
2106    $gullet->closeMouth unless ($gullet->getMouth->hasMoreInput);
2107    if (my $tok = $gullet->readToken()) {
2108      my $csname = $tok->getCSName;
2109      if ($csname eq '\begin') {
2110        my $arg = $gullet->readArg();
2111        $csname = $arg->toString;
2112        $gullet->unread(T_BEGIN, $arg->unlist, T_END);
2113      }
2114      $gullet->unread($tok);
2115      # We need to determine whether the TeX we're given needs to be wrapped in \(...\)
2116      # Does it have $'s around it? Does it have a display math environment?
2117      if ($csname !~ /^Math|\\\(|\\\[|(?:$MATHENVS)/o) {
2118        AssignValue('automath_triggered' => 1, 'global');
2119        return T_CS('\\('); } }
2120    return;
2121});
2122
2123DefMacro('\ensuremathpreceeds', sub {
2124    return LookupValue('automath_triggered') ? T_CS('\\)') : ();
2125});
2126
2127# Since the arXMLiv folks keep wanting ids on all math, let's try this!
2128Tag('ltx:Math', afterOpen => sub { GenerateID(@_, 'm'); });
2129
2130#======================================================================
2131# Sub-numbered equations
2132# Although LaTeX itself doesn't provide such an environment,
2133# many other packages & classes do, and they seem essentially equivalent.
2134# They provide an environment to wrap around equations,
2135# which hijack the equation counter to sub-number within the group.
2136DefConstructor('\lx@equationgroup@subnumbering@begin',
2137  "<ltx:equationgroup xml:id='#id'>#tags",
2138  afterDigest => sub {
2139    my ($stomach, $whatsit) = @_;
2140    my %eqn   = RefStepCounter('equation');
2141    my $eqnum = Expand(T_CS('\theequation'));
2142    AssignValue(SAVED_EQUATION_NUMBER => LookupValue('\c@equation'));
2143    $whatsit->setProperties(%eqn);
2144    ResetCounter('equation');
2145    DefMacroI('\theequation',    undef, UnTeX($eqnum) . '\alph{equation}');
2146    DefMacroI('\theequation@ID', undef, UnTeX($eqn{id}) . '.\@equation@ID'); });
2147Tag('ltx:equationgroup', autoClose => 1);
2148DefConstructor('\lx@equationgroup@subnumbering@end', sub {
2149    $_[0]->maybeCloseElement('ltx:equationgroup'); },
2150  afterDigest => sub {
2151    AssignValue('\c@equation', LookupValue('SAVED_EQUATION_NUMBER'), 'global'); });
2152
2153#======================================================================
2154# ========================================
2155# eqnarray, etal
2156# Tricky! There's a conflict between a math-level alignment (which
2157# intermingles non-math things like labels, refnums, intertext),
2158# and a text-level alignment (which fragments the math's logical structure).
2159# Our solution is to attempt to synthesize a logical structure of
2160# an equationgroup containing equations, but using MathFork structures
2161# to hide the intended aligmnents.
2162# Then, XSLT can be used in the end to either display in a logical
2163# (but non-aligned format), or display fully aligned (but hiding the semantics).
2164#======================================================================
2165# Equation Groups
2166#   <equationgroup> representing aligned collections of equations
2167#   in particular, eqnarray and amsmath's align, ...
2168# The intended usage is a sequence of patterns like
2169#     LHS & REL & RHS
2170# When the LHS is empty, it likely implies a continuation of the previous (or multirelation).
2171# When both the LHS & REL are empty, it likely implies a continuation of the previous RHS
2172#======================================================================
2173# The strategy here is to use the alignment mechanism to construct
2174# an initial form:
2175#  <equationgroup>   as the overall container
2176#    <equation>      for each row; this can receive id, refnum & label attributes
2177#      <_Capture_>   to capture each columns math.
2178# After the initial construction (but before any rewriting & parsing)
2179# we scan through the equations combining the ones that appear (heuristically)
2180# to be single equations on several rows (either as multi-relations, or
2181# rhs's of multiple rows)
2182#  The combinations are represented by a MathFork container which
2183# holds both the apparently meaningful complete equation, along with
2184# the rows & columns that show the desired alignment.
2185# Each of those columns contains a <Math>, thus these can also be parsed,
2186# and converted to MathML or images.
2187#
2188# Thus, both forms are present: A presentation-oriented stylesheet can
2189# thus represent the eqnarray by a table with aligned math chunks.
2190# A content-oriented stylesheet can select the composed meaningful pieces.
2191
2192# ========================================
2193# The following set deal with numbering the equations (rows) that make up an equationgroup.
2194# Note EQUATIONGROUP_NUMBER controls the numbering of the equations contained within the equationgroup,
2195# not the numbering of the equationgroup itself.
2196
2197DefPrimitive('\@equationgroup@numbering RequiredKeyVals', sub {
2198    my $kv = GetKeyVals($_[1]);
2199    prepareEquationCounter(map { ($_ => ToString($$kv{$_})) } keys %$kv); });
2200
2201# ========================================
2202# Some special kinds of rows...
2203DefConditionalI('\if@in@firstcolumn', undef, sub {
2204    my $x = LookupValue('Alignment');
2205    $x = ($x ? $x->currentColumnNumber : 9);
2206    $x < 2; });
2207# A bit more defensiveness, since people abuse eqnarray so badly.
2208# Eg. &&\lefteqn{...}  whatever?!?!
2209DefMacro('\lefteqn{}',
2210'\if@in@firstcolumn\multicolumn{3}{l}{\@ADDCLASS{ltx_eqn_lefteqn}\@@BEGININLINEMATH \displaystyle #1\@@ENDINLINEMATH\mbox{}}'
2211    . '\else\rlap{\@@BEGININLINEMATH\displaystyle #1\@@ENDINLINEMATH}\fi');
2212# \intertext (in amsmath)
2213
2214# ========================================
2215# eqnarray
2216DefMacroI('\eqnarray', undef,
2217  '\@eqnarray@bindings\@@eqnarray'
2218    . '\@equationgroup@numbering{numbered=1,preset=1,deferretract=1,grouped=1,aligned=1}'
2219    . '\@start@alignment',
2220  locked => 1);
2221DefMacroI('\endeqnarray', undef,
2222  '\cr\@finish@alignment\end@eqnarray',
2223  locked => 1);
2224DefMacro('\csname eqnarray*\endcsname',
2225  '\@eqnarray@bindings\@@eqnarray'
2226    . '\@equationgroup@numbering{numbered=1,preset=1,retract=1,grouped=1,aligned=1}'
2227    . '\@start@alignment',
2228  locked => 1);
2229DefMacro('\csname endeqnarray*\endcsname',
2230  '\@finish@alignment\end@eqnarray',
2231  locked => 1);
2232
2233DefPrimitive('\@eqnarray@bindings', sub {
2234    eqnarrayBindings(); });
2235
2236DefPrimitiveI('\eqnarray@row@before', undef, sub { beforeEquation(); });
2237DefPrimitiveI('\eqnarray@row@after',  undef, sub { afterEquation(); });
2238
2239DefPrimitiveI('\eqnarray@row@before@', undef, sub { beforeEquation(); });
2240DefPrimitiveI('\eqnarray@row@after@',  undef, sub { afterEquation(); });
2241DefMacroI('\eqnarray@row@before', undef, '\hidden@noalign{\eqnarray@row@before@}');
2242DefMacroI('\eqnarray@row@after',  undef, '\hidden@noalign{\eqnarray@row@after@}');
2243
2244sub eqnarrayBindings {
2245  my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
2246    after => Tokens(T_MATH) };
2247  my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
2248    after => Tokens(T_MATH, T_CS('\hfil')) };
2249  my $col3 = { before => Tokens(T_MATH, T_CS('\displaystyle')),
2250    after => Tokens(T_MATH, T_CS('\hfil')) };
2251
2252  my %attributes = (
2253    'class'  => 'ltx_eqn_eqnarray',
2254    'colsep' => LookupDimension('\arraycolsep')->multiply(2));
2255  my $cur_jot = LookupDimension('\jot');
2256  if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
2257    $attributes{rowsep} = $cur_jot; }
2258
2259  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
2260      template      => LaTeXML::Core::Alignment::Template->new(columns => [$col1, $col2, $col3]),
2261      openContainer => sub { my %attr = RefStepID('@equationgroup');
2262        $attr{'xml:id'} = $attr{id}; delete $attr{id};
2263        $attr{class}    = 'ltx_eqn_eqnarray';
2264        $_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); },
2265      closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); },
2266      openRow        => sub {
2267        my ($doc, %props) = @_;
2268        my $tags = $props{tags}; delete($props{tags});
2269        $doc->openElement('ltx:equation', %props);
2270        $doc->absorb($tags) if $tags; },
2271      closeRow    => sub { $_[0]->closeElement('ltx:equation'); },
2272      openColumn  => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); },
2273      closeColumn => sub { $_[0]->closeElement('ltx:_Capture_'); },
2274      properties  => { preserve_structure => 1, attributes => {%attributes} }));
2275
2276  Let("\\\\",                    '\@alignment@newline');
2277  Let('\@row@before',            '\eqnarray@row@before');
2278  Let('\@row@after',             '\eqnarray@row@after');
2279  Let('\lx@eqnarray@save@label', '\label');
2280  Let('\label',                  '\lx@eqnarray@label');
2281  return; }
2282
2283# A \label preceding \lefteqn throws of the implied \omit,e tc; so wrap in \hidden@align
2284DefMacro('\lx@eqnarray@label Semiverbatim',
2285'\ifnum1>\@alignment@column\hidden@noalign{\lx@eqnarray@save@label{#1}}\else\lx@eqnarray@save@label{#1}\fi');
2286
2287DefConstructor('\@@eqnarray SkipSpaces DigestedBody',
2288  '#1',
2289  beforeDigest   => sub { $_[0]->bgroup; },
2290  afterConstruct => sub { rearrangeEqnarray($_[0], $_[0]->getNode->lastChild); });
2291DefPrimitiveI('\end@eqnarray', undef, sub { $_[0]->egroup; });
2292
2293# ========================================
2294# Some tools for analyzing the equationgroup after we've constructed it.
2295# ========================================
2296
2297# ========================================
2298# For eqnarray, the "meaningful" unit will be at least one row,
2299# but often multiple rows.
2300# When the 1st column is empty, the row is assumed to continue the previous equation (if any!)
2301# When the 2nd column is also empty, it presumably continues the previous RHS.
2302# We'll combine these cases into a single equation, but remember the alignment structure.
2303# However, if more than 1 such row has refnums, we probably don't want to combine;
2304# But we really need to find a better way of representing the information!
2305# Note that there are common misuses of eqnarray;
2306# One type tries to get the equivalent of amsmath's gather environment by
2307# using a single column for the equations; the equations are right, centered or
2308# left aligned, depending on which column was used.
2309# Can we detect continuations, and can we distinguish continuations of equations vs. RHS?
2310# Probably a similar misuse where only 2 columns are used?
2311sub rearrangeEqnarray {
2312  my ($document, $equationgroup) = @_;
2313  # Scan the "equations" (rows) within the $equationgroup
2314  # to see what pattern of columns are present.
2315  my @rows = ();
2316  foreach my $rownode ($document->findnodes('ltx:equation', $equationgroup)) {
2317    my @cells = $document->findnodes('ltx:_Capture_', $rownode);    # representing each column.
2318    push(@rows, { node => $rownode, cols => [@cells],
2319        L        => ($cells[0] && $cells[0]->hasChildNodes),
2320        M        => ($cells[1] && $cells[1]->hasChildNodes),
2321        R        => ($cells[2] && $cells[2]->hasChildNodes),
2322        numbered => ($document->findnode('ltx:tags', $rownode) ? 1 : 0),
2323        labelled => $rownode->hasAttribute('label') }); }
2324  my $nL = scalar(grep { $$_{L} } @rows);
2325  my $nM = scalar(grep { $$_{M} } @rows);
2326  my $nR = scalar(grep { $$_{R} } @rows);
2327
2328  # Only a single column was used.  Remove the empty ones.
2329  # A heuristic: if any rows begin with a relation, there are probably continuations,
2330  # but maybe we don't want to try to distinguish the kinds?
2331  if (($nL && !$nM && !$nR)    # All left column
2332    || (!$nL && $nM  && !$nR)      # All center column
2333    || (!$nL && !$nM && $nR)) {    # All right column
2334                                   # We REALLY should remove the empty columns entirely!
2335    my $keepcol = ($nL ? 0 : ($nM ? 1 : 2));
2336    # REMOVE empty columns!
2337    foreach my $c (2, 1, 0) {
2338      next if $c == $keepcol;
2339      foreach my $row (@rows) {
2340        $document->removeNode($$row{cols}[$c]);
2341        splice(@{ $$row{cols} }, $c, 1); } }
2342    # If (some) columns begin with a relation, presumably a single equation?
2343    # Or maybe we should only connect those to previous row?
2344    my $t;
2345    if (grep { ($t = $$_{cols}[0]) && ($t = $document->getFirstChildElement($t))
2346          && (($t->getAttribute('role') || '') eq 'RELOP') } @rows) {
2347      equationgroupJoinRows($document, $equationgroup, map { $$_{node} } @rows); }
2348    else {
2349      # Really, these shouldn't even end up with MathFork!
2350      foreach my $row (@rows) {
2351        equationgroupJoinCols($document, 1, $$row{node}); } }
2352    return; }
2353  # What about case where only TWO columns get used? Worth analyzing?
2354  # Remaining case is when all 3 columns are used.
2355  my @eqs      = ();
2356  my $numbered = 0;
2357  my $oddness  = 0;
2358  foreach my $row (@rows) {
2359    my $class = 'unknown';
2360    if ($$row{L}) {    # 1st column non-empty; Presumably a new "equation"
2361      $class = 'new'; }
2362    elsif ($$row{M}) {    # 1st is empty & 2nd non-empty (Rel?); Probably continues
2363      if (!scalar(@eqs)) {    # But if no previous equation?
2364        $class = 'odd'; }
2365      elsif ($numbered && $$row{numbered}) {    # Separately numbered continuation?
2366        $class = 'new'; }                       # Keep it as separate equation, even though missing LHS
2367      else {
2368        $class = 'continue'; } }                # Continues as multiequation
2369    elsif ($$row{R}) {                          # 1st & 2nd is empty, non empty 3rd
2370      if (!scalar(@eqs)) {                      # But if no previous equation?
2371        $class = 'odd'; }
2372      elsif ($numbered && $$row{numbered}       # Separately numbered AND labeled?
2373        && $$row{labelled}) {
2374        $class = 'odd'; }                       # must keep separate, but weird!
2375      else {
2376        $class = 'continue'; } }                # Else, continues RHS.
2377    else {                                      # All columns empty
2378          # Probably the trailling //, but who knows...
2379          # Arguably: if it has a number &/or is labelled
2380          # we should either prserve (as odd) or move info to previous line (if any)
2381      $class = 'remove'; }
2382
2383    if ($class eq 'remove') {
2384      $document->removeNode($$row{node}); }
2385    elsif (($class eq 'new') || ($class eq 'odd')) {
2386      $numbered = $$row{numbered};
2387      push(@eqs, [$$row{node}]);
2388      $oddness++ if $class eq 'odd'; }
2389    else {
2390      $numbered |= $$row{numbered};
2391      push(@{ $eqs[-1] }, $$row{node}); }
2392  }
2393
2394  Warn('unexpected', 'eqnarray', $equationgroup,
2395    "Unrecognized equation patterns ($oddness) in eqnarray")
2396    if (scalar(@rows) > 1) && $oddness;
2397  # Now rearrange things appropriately.
2398  foreach my $eqset (@eqs) {
2399    equationgroupJoinRows($document, $equationgroup, @$eqset); }
2400  return; }
2401
2402# Style Parameters
2403#  \abovedisplayskip \abovedisplayshortskip, \jot are in TeX.pool
2404DefRegister('\mathindent' => Dimension(0));
2405
2406#======================================================================
2407# C.7.2 Common Structures
2408#======================================================================
2409# sub, superscript and prime are in TeX.pool
2410# Underlying support in TeX.pool.ltxml
2411DefConstructor('\frac InFractionStyle InFractionStyle',
2412  "<ltx:XMApp>"
2413    . "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='#mathstyle'/>"
2414    . "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
2415    . "</ltx:XMApp>",
2416  sizer      => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); },
2417  properties => { mathstyle => sub { LookupValue('font')->getMathstyle; } });
2418
2419# Ellipsis: See TeX.pool
2420
2421#======================================================================
2422# C.7.3 Mathematical Symbols
2423#======================================================================
2424# See Tables 3.3 through 3.8 (pp 41--44)
2425# Defined in TeX.pool
2426# [Possibly some are strictly LaTeX and should be moved here?]
2427
2428#======================================================================
2429# C.7.4 Arrays
2430#======================================================================
2431#  See Section C.10.2
2432
2433#======================================================================-
2434# C.7.5 Delimiters
2435#======================================================================-
2436# All this is already in TeX.pool
2437
2438DefMacro('\stackrel{}{}', '\lx@stackrel{{\scriptstyle #1}}{{#2}}');
2439DefConstructor('\lx@stackrel{}{}',
2440  "<ltx:XMApp role='RELOP'>"
2441    . "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
2442    . "<ltx:XMArg>#2</ltx:XMArg>"
2443    . "<ltx:XMArg>#1</ltx:XMArg>"
2444    . "</ltx:XMApp>",
2445  reversion  => '\stackrel{#1}{#2}',
2446  properties => { scriptpos => sub { "mid" . $_[0]->getScriptLevel; } }
2447);
2448#======================================================================-
2449# C.7.6 Putting One Thing Above Another
2450#======================================================================-
2451# All this is already in TeX.pool
2452
2453#======================================================================-
2454# C.7.7 Spacing
2455#======================================================================-
2456# All this is already in TeX.pool
2457
2458#======================================================================
2459# C.7.8 Changing Style
2460#======================================================================
2461# For Math style changes, we record the current font, which is then merged
2462# into the Whatsit's created for letters, etc.  The merging depends on
2463# the type of letter, greek, symbol, etc.
2464# Apparently, with the normal TeX setup, these fonts don't really merge,
2465# rather they override all of family, series and shape.
2466DefConstructor('\mathrm{}', '#1', bounded => 1, requireMath => 1,
2467  font => { family => 'serif', series => 'medium', shape => 'upright' });
2468DefConstructor('\mathit{}', '#1', bounded => 1, requireMath => 1,
2469  font => { shape => 'italic', family => 'serif', series => 'medium' });
2470DefConstructor('\mathbf{}', '#1', bounded => 1, requireMath => 1,
2471  font => { series => 'bold', family => 'serif', shape => 'upright' });
2472DefConstructor('\mathsf{}', '#1', bounded => 1, requireMath => 1,
2473  font => { family => 'sansserif', series => 'medium', shape => 'upright' });
2474DefConstructor('\mathtt{}', '#1', bounded => 1, requireMath => 1,
2475  font => { family => 'typewriter', series => 'medium', shape => 'upright' });
2476DefConstructor('\mathcal{}', '#1', bounded => 1, requireMath => 1,
2477  font => { family => 'caligraphic', series => 'medium', shape => 'upright' });
2478DefConstructor('\mathscr{}', '#1', bounded => 1, requireMath => 1,
2479  font => { family => 'script', series => 'medium', shape => 'upright' });
2480DefConstructor('\mathnormal{}', '#1', bounded => 1, requireMath => 1,
2481  font => { family => 'math', shape => 'italic', series => 'medium' });
2482
2483DefMacroI('\fontsubfuzz',  undef, '.4pt');
2484DefMacroI('\oldstylenums', undef, Tokens());
2485
2486DefPrimitiveI('\operator@font', undef, undef,
2487  font => { family => 'serif', series => 'medium', shape => 'upright' });
2488
2489#**********************************************************************
2490# C.8 Definitions, Numbering and Programming
2491#**********************************************************************
2492
2493#======================================================================
2494# C.8.1 Defining Commands
2495#======================================================================
2496
2497DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname');
2498
2499DefPrimitive('\newcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
2500    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
2501    if (!isDefinable($cs)) {
2502      Info('ignore', $cs, $stomach,
2503        "Ignoring redefinition (\\newcommand) of '" . ToString($cs) . "'")
2504        unless LookupValue(ToString($cs) . ':locked');
2505      return; }
2506    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
2507
2508DefPrimitive('\CheckCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', undef);
2509
2510DefPrimitive('\renewcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
2511    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
2512    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
2513
2514# low-level implementation of both \newcommand and \renewcommand depends on \@argdef
2515# and robustness upgrades are often realized via redefining \l@ngrel@x
2516DefPrimitive('\@argdef DefToken []{}', sub {
2517    DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });
2518DefPrimitive('\@xargdef DefToken [][]{}', sub {
2519    DefMacroI($_[1], convertLaTeXArgs($_[2], $_[3]), $_[4]); });
2520DefPrimitive('\@yargdef DefToken DefToken {}{}', sub {
2521    DefMacroI($_[1],
2522      (T_CS('\tw@')->equals($_[2])
2523        ? convertLaTeXArgs($_[3], Tokens())
2524        : convertLaTeXArgs($_[3])),
2525      $_[4]); });
2526DefPrimitive('\@reargdef DefToken []{}', sub {
2527    DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });
2528
2529DefPrimitive('\providecommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
2530    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
2531    return unless isDefinable($cs);
2532    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
2533
2534# Crazy; define \cs in terms of \cs[space] !!!
2535DefPrimitive('\DeclareRobustCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
2536    my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
2537    DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body, robust => 1); });
2538DefPrimitive('\MakeRobust DefToken', sub {
2539    my ($stomach, $cs) = @_;
2540    my $mungedcs = T_CS($cs->getString . ' ');
2541    if    (!LookupDefinition($cs))      { }    # Not defined
2542    elsif (LookupDefinition($mungedcs)) { }    # Already robust
2543    else {
2544      Let($mungedcs, $cs);
2545      DefMacroI($cs, undef, Tokens(T_CS('\protect'), $mungedcs)); } });
2546
2547# There are a bunch of ways in LaTeX to assign a command to
2548# a particular point within a font (which has one of many encodings)
2549# Since we have no practical way of knowing what that point is,
2550# and we really want to create unicode, we just ignore this stuff (but warn).
2551sub ignoredDefinition {
2552  my ($stomach, $command, $cs) = @_;
2553  #  Warn('ignore',$cs,$stomach,"ignoring ".ToString($command)." definition of ".ToString($cs));
2554  return; }
2555
2556#------------------------------------------------------------
2557# The following commands define encoding-specific expansions
2558# or glyphs.  The control-sequence is defined to use the expansion for
2559# the current encoding, if any, or the default expansion (for encoding "?").
2560# We don't want to redefine control-sequence if it already has a definition:
2561# It may be that we've already defined it to expand into the above conditional.
2562# But more importantly, we don't want to override a hand-written definition (if any).
2563#------------------------------------------------------------
2564DefPrimitive('\DeclareTextCommand DefToken {}[Number][]{}', sub {
2565    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
2566    my $css = ToString($cs);
2567    $encoding = ToString(Expand($encoding));
2568    if (!IsDefined($cs)) {    # If not already defined...
2569      DefMacroI($cs, undef,
2570'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
2571          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
2572    my $ecs = T_CS('\\' . $encoding . $css);
2573    DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion);
2574    return; });
2575
2576DefMacro('\DeclareTextCommandDefault DefToken', '\DeclareTextCommand{#1}{?}');
2577
2578DefPrimitive('\ProvideTextCommand DefToken {}[Number][]{}', sub {
2579    my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
2580    my $css = ToString($cs);
2581    $encoding = ToString(Expand($encoding));
2582    if (isDefinable($cs)) {    # If not already defined...
2583      DefMacroI($cs, undef,
2584'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
2585          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
2586    my $ecs = T_CS('\\' . $encoding . $css);
2587    if (!IsDefined($ecs)) {    # If not already defined...
2588      DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); }
2589    return; });
2590
2591DefMacro('\ProvideTextCommandDefault DefToken', '\ProvideTextCommand{#1}{?}');
2592
2593#------------------------------------------------------------
2594
2595DefPrimitive('\DeclareTextSymbol DefToken {}{Number}', sub {
2596    my ($gullet, $cs, $encoding, $code) = @_;
2597    $code = $code->valueOf;
2598    my $css = ToString($cs);
2599    $encoding = ToString(Expand($encoding));
2600    if (isDefinable($cs)) {    # If not already defined...
2601      DefMacroI($cs, undef,
2602'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
2603          . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
2604    my $ecs = T_CS('\\' . $encoding . $css);
2605    DefPrimitiveI($ecs, undef, FontDecode($code, $encoding));
2606    return; });
2607
2608# hmmm... what needs doing here; basically it means use this encoding as the default for the symbol
2609DefMacro('\DeclareTextSymbolDefault DefToken {}', '');    # '\DeclareTextSymbol{#1}{?}');
2610
2611#------------------------------------------------------------
2612DefPrimitive('\DeclareTextAccent DefToken {}{}', sub {
2613    ignoredDefinition('DeclareTextAccent', $_[1]); });
2614DefPrimitive('\DeclareTextAccentDefault{}{}',
2615  sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); });
2616
2617#------------------------------------------------------------
2618# TODO: Need to convert these DefPrimitive's into DefConstroctors
2619# that add a preable PI!!!!!!!!!
2620
2621DefPrimitive('\DeclareTextComposite{}{}{}{}',
2622  sub { ignoredDefinition('DeclareTextComposite', $_[1]); });
2623DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}',
2624  sub { ignoredDefinition('DeclareTextCompositeCommand', $_[1]); });
2625
2626DefPrimitive('\UndeclareTextCommand{}{}', undef);
2627DefMacro('\UseTextSymbol{}{}', '{\fontencoding{#1}#2}');
2628DefMacro('\UseTextAccent{}{}', '{\fontencoding{#1}#2{#3}}');
2629
2630DefPrimitive('\DeclareMathAccent DefToken {}{} {Number}', sub {
2631    my ($stomach, $cs, $kind, $class, $code) = @_;
2632    $class = ToString($class);
2633    my $info  = LookupValue('fontdeclaration@' . $class);
2634    my $glyph = FontDecode($code->valueOf, ($info ? $$info{encoding} : $class));
2635    DefMathI($cs, 'Digested', $glyph, operator_role => 'OVERACCENT');
2636    return AddToPreamble('\DeclareMathAccent', $cs, $kind, $class, $code); });
2637
2638DefPrimitive('\DeclareMathDelimiter{}{}{}{}',
2639  sub { ignoredDefinition('DeclareMathDelimiter', $_[1]); });
2640DefPrimitive('\DeclareMathRadical{}{}{}{}{}',
2641  sub { ignoredDefinition('DeclareMathRadical', $_[1]); });
2642DefPrimitive('\DeclareMathVersion{}',          undef);
2643DefPrimitive('\DeclarePreloadSizes{}{}{}{}{}', undef);
2644
2645# The next font declaration commands are based on
2646# http://tex.loria.fr/general/new/fntguide.html
2647# we ignore font encoding
2648DefPrimitive('\DeclareSymbolFont{}{}{}{}{}', sub {
2649    my ($stomach, $name, $enc, $family, $series, $shape) = @_;
2650    AssignValue('fontdeclaration@' . ToString($name),
2651      { family => ToString($family),
2652        series   => ToString($series),
2653        shape    => ToString($shape),
2654        encoding => ToString($enc) }); });
2655DefPrimitive('\DeclareSymbolFontAlphabet{}{}', sub {
2656    my ($stomach, $cs, $name) = @_;
2657    my $font = LookupValue('fontdeclarations@' . ToString($name)) || {};
2658    DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => $font); });
2659
2660DefPrimitive('\DeclareMathSizes{}{}{}{}', undef);
2661DefPrimitive('\DeclareMathAlphabet{}{}{}{}{}', sub {
2662    my ($stomach, $cs, $enc, $family, $series, $shape) = @_;
2663    my $csname = ToString($cs);
2664    # We won't override this, e.g. \mathrm by fouriernc.sty
2665    if (IsDefined($csname)) {
2666      Info('ignore', $csname, $stomach,
2667        "Ignoring redefinition (\\DeclareMathAlphabet) of '" . $csname . "'"); }
2668    else {
2669      my %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape);
2670      DefPrimitiveI(T_CS($csname), undef, undef, font => {%font}); }
2671    return; });
2672
2673DefMacro('\newmathalphabet{}{}{}', Tokens());    # or expand int DeclareMathAlphabet?
2674DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef);
2675DefPrimitive('\DeclareFontFamily{}{}{}',      undef);
2676DefPrimitive('\DeclareSizeFunction{}{}',      undef);
2677
2678my $symboltype_roles = {
2679  '\mathord'  => 'ID',   '\mathop'    => 'BIGOP', '\mathbin'   => 'BINOP', '\mathrel' => 'RELOP',
2680  '\mathopen' => 'OPEN', '\mathclose' => 'CLOSE', '\mathpunct' => 'PUNCT' };
2681DefPrimitive('\DeclareMathSymbol DefToken SkipSpaces DefToken {}{Number}', sub {
2682    my ($stomach, $cs, $type, $font, $code) = @_;
2683    my $encoding = ToString($font);    # Or maybe just a font name or class?
2684    if (my $decl = LookupValue('fontdeclaration@' . $encoding)) {
2685      $encoding = $$decl{encoding} if $$decl{encoding}; }
2686    my $glyph = FontDecode($code->valueOf, $encoding);
2687    my $role  = $$symboltype_roles{ ToString($type) };
2688    DefMathI($cs, undef, $glyph, role => $role);
2689    return; });
2690
2691DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', undef);
2692DefPrimitive('\DeclareErrorFont{}{}{}{}{}',   undef);
2693
2694DefMacroI('\cdp@list', undef, '\@empty');
2695Let('\cdp@elt', '\relax');
2696DefPrimitive('\DeclareFontEncoding{}{}{}', sub {
2697    my ($stomach, $encoding, $x, $y) = @_;
2698    AddToMacro(T_CS('\cdp@list'), T_CS('\cdp@elt'),
2699      T_BEGIN, $_[1]->unlist,           T_END,
2700      T_BEGIN, T_CS('\default@family'), T_END,
2701      T_BEGIN, T_CS('\default@series'), T_END,
2702      T_BEGIN, T_CS('\default@shape'),  T_END);
2703    my $encoding_expanded = Expand($encoding);
2704    my $encoding_str      = ToString($encoding_expanded);
2705    DefMacroI('\LastDeclaredEncoding', undef, $encoding_expanded);
2706    DefMacroI('\T@' . $encoding_str,   undef, $x);
2707    DefMacroI('\M@' . $encoding_str,   undef, Tokens(T_CS('\default@M'), $y->unlist));
2708    if (my $path = $encoding_str && FindFile(lc($encoding_str) . "enc", type => "dfu")) {
2709      InputDefinitions($path);
2710    }
2711    return;
2712});
2713DefMacroI('\LastDeclaredEncoding', undef, '');
2714DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef);
2715DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef);
2716DefMacroI('\LastDeclaredEncoding', undef, Tokens());
2717
2718DefPrimitive('\SetSymbolFont{}{}{}{}{}{}',   undef);
2719DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef);
2720DefPrimitive('\addtoversion{}{}',            undef);
2721DefPrimitive('\TextSymbolUnavailable{}',     undef);
2722
2723RawTeX(<<'EoTeX');
2724\DeclareSymbolFont{operators}   {OT1}{cmr} {m}{n}
2725\DeclareSymbolFont{letters}     {OML}{cmm} {m}{it}
2726\DeclareSymbolFont{symbols}     {OMS}{cmsy}{m}{n}
2727\DeclareSymbolFont{largesymbols}{OMX}{cmex}{m}{n}
2728EoTeX
2729
2730# At least all things on uclclist need to be macros
2731DefPrimitiveI('\lx@utf@OE', undef, "\x{0152}", alias => '\OE');    # LATIN CAPITAL LIGATURE OE
2732DefPrimitiveI('\lx@utf@oe', undef, "\x{0153}", alias => '\oe');    # LATIN SMALL LIGATURE OE
2733DefPrimitiveI('\lx@utf@AE', undef, UTF(0xC6),  alias => '\AE');    # LATIN CAPITAL LETTER AE
2734DefPrimitiveI('\lx@utf@ae', undef, UTF(0xE6),  alias => '\ae');    # LATIN SMALL LETTER AE
2735DefPrimitiveI('\lx@utf@AA', undef, UTF(0xC5), alias => '\AA'); # LATIN CAPITAL LETTER A WITH RING ABOVE
2736DefPrimitiveI('\lx@utf@aa', undef, UTF(0xE5), alias => '\aa'); # LATIN SMALL LETTER A WITH RING ABOVE
2737DefPrimitiveI('\lx@utf@O',  undef, UTF(0xD8),  alias => '\O');  # LATIN CAPITAL LETTER O WITH STROKE
2738DefPrimitiveI('\lx@utf@o',  undef, UTF(0xF8),  alias => '\o');  # LATIN SMALL LETTER O WITH STROKE
2739DefPrimitiveI('\lx@utf@L',  undef, "\x{0141}", alias => '\L');  # LATIN CAPITAL LETTER L WITH STROKE
2740DefPrimitiveI('\lx@utf@l',  undef, "\x{0142}", alias => '\l');  # LATIN SMALL LETTER L WITH STROKE
2741DefPrimitiveI('\lx@utf@ss', undef, UTF(0xDF),  alias => '\ss'); # LATIN SMALL LETTER SHARP S
2742DefPrimitiveI('\lx@utf@dh', undef, UTF(0xf0),  alias => '\dh'); # eth
2743DefPrimitiveI('\lx@utf@DH', undef, UTF(0xd0),  alias => '\DH'); # Eth (looks same as \DJ!)
2744DefPrimitiveI('\lx@utf@dj', undef, "\x{0111}", alias => '\dj'); # d with stroke
2745DefPrimitiveI('\lx@utf@DJ', undef, "\x{0110}", alias => '\DJ'); # D with stroke (looks sames as \DH!)
2746DefPrimitiveI('\lx@utf@ng', undef, "\x{014B}", alias => '\ng');
2747DefPrimitiveI('\lx@utf@NG', undef, "\x{014A}", alias => '\NG');
2748DefPrimitiveI('\lx@utf@th', undef, UTF(0xFE),  alias => '\th');
2749DefPrimitiveI('\lx@utf@TH', undef, UTF(0xDE),  alias => '\TH');
2750
2751DefMacroI('\OE', undef, '\lx@utf@OE');
2752DefMacroI('\oe', undef, '\lx@utf@oe');
2753DefMacroI('\AE', undef, '\lx@utf@AE');
2754DefMacroI('\ae', undef, '\lx@utf@ae');
2755DefMacroI('\ae', undef, '\lx@utf@ae');
2756DefMacroI('\AA', undef, '\lx@utf@AA');
2757DefMacroI('\aa', undef, '\lx@utf@aa');
2758DefMacroI('\O',  undef, '\lx@utf@O');
2759DefMacroI('\o',  undef, '\lx@utf@o');
2760DefMacroI('\L',  undef, '\lx@utf@L');
2761DefMacroI('\l',  undef, '\lx@utf@l');
2762DefMacroI('\ss', undef, '\lx@utf@ss');
2763DefMacroI('\dh', undef, '\lx@utf@dh');    # in latex?
2764DefMacroI('\DH', undef, '\lx@utf@DH');
2765DefMacroI('\dj', undef, '\lx@utf@dj');
2766DefMacroI('\DJ', undef, '\lx@utf@DJ');
2767DefMacroI('\ng', undef, '\lx@utf@ng');
2768DefMacroI('\NG', undef, '\lx@utf@NG');
2769DefMacroI('\th', undef, '\lx@utf@th');
2770DefMacroI('\TH', undef, '\lx@utf@TH');
2771
2772#======================================================================
2773# C.8.2 Defining Environments
2774#======================================================================
2775# Note that \env & \endenv defined by \newenvironment CAN be
2776# invoked directly.
2777
2778DefPrimitive('\newenvironment OptionalMatch:* {}[Number][]{}{}', sub {
2779    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
2780    $name = ToString(Expand($name));
2781    if (IsDefined(T_CS("\\$name"))) {
2782      Info('ignore', $name, $stomach,
2783        "Ignoring redefinition (\\newenvironment) of Environment '$name'")
2784        unless (LookupValue('\\' . $name . ':locked')
2785        || LookupValue('\\begin{' . $name . '}:locked'));
2786      return; }
2787    DefMacroI(T_CS("\\$name"),    convertLaTeXArgs($nargs, $opt), $begin);
2788    DefMacroI(T_CS("\\end$name"), undef,                          $end);
2789    return; });
2790
2791DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub {
2792    my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
2793    $name = ToString(Expand($name));
2794    if (!LookupValue("\\$name:locked")
2795      && !LookupValue("\\begin{$name}:locked")) {
2796      DefMacroI(T_CS("\\$name"),    convertLaTeXArgs($nargs, $opt), $begin);
2797      DefMacroI(T_CS("\\end$name"), undef,                          $end); }
2798    return; });
2799
2800#======================================================================
2801# C.8.3 Theorem-like Environments
2802#======================================================================
2803# The core non-customizable part is defined here.
2804# For customizable theorems, see amsthm.
2805AssignValue('thm@swap' => 0);
2806DefRegister('\thm@style'       => Tokenize('plain'));
2807DefRegister('\thm@headfont'    => Tokens(T_CS('\bfseries')));
2808DefRegister('\thm@notefont'    => Tokens(T_CS('\the'), T_CS('\thm@headfont')));
2809DefRegister('\thm@bodyfont'    => Tokens(T_CS('\itshape')));
2810DefRegister('\thm@headpunct'   => Tokens());
2811DefRegister('\thm@styling'     => Tokens());
2812DefRegister('\thm@headstyling' => Tokens());
2813DefRegister('\thm@prework'     => Tokens());
2814DefRegister('\thm@postwork'    => Tokens());
2815DefRegister('\thm@symbol'      => Tokens());
2816DefRegister('\thm@numbering'   => Tokens(T_CS('\arabic')));
2817
2818DefPrimitive('\th@plain', sub {
2819    AssignValue('\thm@bodyfont'    => T_CS('\itshape'));
2820    AssignValue('\thm@headstyling' => T_CS('\lx@makerunin'));
2821    return; });
2822
2823DefMacroI('\lx@makerunin',   undef, '\@ADDCLASS{ltx_runin}');
2824DefMacroI('\lx@makeoutdent', undef, '\@ADDCLASS{ltx_outdent}');
2825
2826DefMacroI('\@thmcountersep', undef, '.');
2827DefMacroI('\thm@doendmark',  undef, '');
2828
2829DefPrimitive('\newtheorem OptionalMatch:* {}[]{}[]', sub {
2830    my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
2831    defineNewTheorem($stomach, $flag, $thmset, $otherthmset, $type, $reset);
2832    # Reset these!
2833    DefRegister('\thm@prework'  => Tokens());
2834    DefRegister('\thm@postwork' => Tokens()); },
2835  locked => 1);
2836
2837# These record which style parameters CAN BE saved/restored for each style.
2838# (kinda weird, but different packages use different sets; particularly \thm@headfont)
2839#  THEOREM_<theoremname>_PARAMETERS will store the actual values for those parameters
2840AssignValue('SAVABLE_THEOREM_PARAMETERS' => {});
2841
2842sub setSavableTheoremParameters {
2843  my (@keys) = @_;    # Noe that these are saved for each theorem type.
2844  AssignValue('SAVABLE_THEOREM_PARAMETERS' => { map { $_ => 1; } @keys });
2845  return; }
2846
2847# Following amsthm's model, the various theorem customizations are stored in token registers
2848# Some packages may want to add '\thm@headfont' (or exclude it)
2849setSavableTheoremParameters(qw(
2850    \thm@bodyfont \thm@headpunct \thm@styling \thm@headstyling thm@swap));
2851
2852sub useTheoremStyle {
2853  my ($name) = @_;
2854  my $savable = LookupValue('SAVABLE_THEOREM_PARAMETERS');
2855  if (my $params = LookupValue('THEOREM_' . ToString($name) . '_PARAMETERS')) {
2856    for my $param_key (keys %$savable) {
2857      if (exists $$params{$param_key}) {
2858        AssignValue($param_key => $$params{$param_key}); }
2859  } }
2860  return; }
2861
2862sub saveTheoremStyle {
2863  my ($name, %parameters) = @_;
2864  $name = ToString($name);
2865  AssignValue('THEOREM_' . $name . '_PARAMETERS' => {%parameters}, 'global');
2866  return; }
2867
2868RawTeX('\th@plain');    # Activate the default style.
2869# [or just make it's values the defaults...]
2870
2871Tag('ltx:theorem', autoClose => 1);
2872Tag('ltx:proof',   autoClose => 1);
2873
2874sub defineNewTheorem {
2875  my ($stomach, $flag, $thmset, $otherthmset, $type, $within) = @_;
2876  $thmset = ToString($thmset);
2877  my $classname = CleanClassName($thmset);
2878  my $listname  = "theorem:$thmset"; $listname =~ s/\s//g;
2879  $otherthmset = $otherthmset       && ToString($otherthmset);
2880  $type        = undef unless $type && $type->unlist;
2881  $within      = $within ? ToString($within) : undef;
2882
2883  my $counter = $otherthmset || $thmset;
2884  $counter =~ s/\s+/./;    # convert spaces to period?
2885  if ($counter ne $thmset) {
2886    AssignMapping('counter_for_type', $thmset => $counter);
2887    DefMacroI(T_CS('\the' . $thmset), undef, T_CS('\the' . $counter), scope => 'global');
2888  }
2889  my $style     = ToString(LookupValue('\thm@style'));        # The name of the style
2890  my $numbering = ToString(LookupValue('\thm@numbering'));    # The number style macro
2891  $flag = 1 unless $numbering;
2892  if (!$otherthmset) {
2893    my $idprefix = "Thm$classname";
2894    $idprefix =~ s/\*/./g;
2895
2896    if (!LookupValue('\c@' . $counter)) {                     # if isn't already defined...
2897      NewCounter($counter, $within, idprefix => $idprefix); }
2898    DefMacroI(T_CS("\\the$counter"), undef,
2899      ($within
2900        ? "\\csname the" . $within . "\\endcsname\\\@thmcountersep" . $numbering . "{" . $counter . "}"
2901        : $numbering . "{" . $counter . "}"),
2902      scope => 'global') if $numbering; }
2903
2904  my %parameters = %{ LookupValue('SAVABLE_THEOREM_PARAMETERS') };
2905  saveTheoremStyle($thmset, map { ($_ => LookupValue($_)) } keys %parameters);
2906
2907  my $thmname = '\\lx@name@' . $thmset;
2908  DefMacroI($thmname, undef, $type, scope => 'global');
2909  my $swap = LookupValue('thm@swap');
2910
2911  DefMacroI('\fnum@' . $thmset, undef,
2912    Tokens($flag || !$counter
2913      ? (T_CS($thmname))
2914      : ($swap
2915        ? (T_CS('\the' . $counter), ($type ? (T_SPACE) : ()), T_CS($thmname))
2916        : (T_CS($thmname), ($type ? (T_SPACE) : ()), T_CS('\the' . $counter)))),
2917    scope => 'global');
2918  # amsthm allows you to define your own title formatter
2919  # if there was one defined, use it, else define a new one.
2920  my $headformatter = LookupValue('\thm@headformatter');
2921  DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0),
2922    ($headformatter
2923      ? Tokens(
2924        T_CS('\the'), T_CS('\thm@headfont'),
2925        $headformatter->unlist,
2926        T_BEGIN, ($type ? $type->unlist : ()), T_END,
2927        T_CS('\the' . $counter), T_BEGIN, Tokens(T_PARAM, T_OTHER('1')), T_END,
2928        T_CS('\the'),            T_CS('\thm@headpunct'))
2929      : '{\the\thm@headfont\lx@tag{\csname fnum@' . $thmset . '\endcsname}'
2930        . '{' . ($type ? '\ifx.#1.\else\space\the\thm@notefont(#1)\fi' : '#1') . '}'
2931        . '\the\thm@headpunct}'),
2932
2933    scope => 'global');
2934
2935  DefEnvironmentI($thmset, "OptionalUndigested",
2936    "<ltx:theorem xml:id='#id' inlist='thm $listname' class='ltx_theorem_$classname'>"
2937      . "#tags"
2938      . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
2939      . "#body",
2940    afterConstruct => sub { $_[0]->maybeCloseElement('ltx:theorem'); },
2941    beforeDigest   => sub {
2942      useTheoremStyle($thmset);
2943      Digest('\normalfont\the\thm@prework'); },
2944    afterDigestBegin => sub {
2945      my $name = $_[1]->getArg(1);
2946      Digest('\the\thm@bodyfont\the\thm@styling'
2947          . '\def\lx@thistheorem{' . ($name ? UnTeX($name) : '') . '}'); },
2948    beforeDigestEnd => sub { Digest('\thm@doendmark'); },
2949    afterDigest     => sub { Digest('\the\thm@postwork'); },
2950    properties      => sub {
2951      my %ctr = ();
2952      if ($counter) {
2953        if ($flag) {
2954          %ctr = RefStepID($counter);
2955          # Need(?) the refnum
2956          if ($type) {
2957            $ctr{tags} = Digest(T_BEGIN,
2958              T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'),
2959              Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)),
2960              T_END);
2961        } }
2962        else {
2963          %ctr = RefStepCounter($thmset); } }
2964      my $title = Digest(Tokens(T_BEGIN,
2965          T_CS('\the'), T_CS('\thm@headstyling'),
2966          T_CS('\format@title@' . $thmset),    #T_END,
2967          T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END,
2968          T_END));
2969      (%ctr,
2970        title     => $title,
2971        titlefont => $title->getFont,
2972      ); },
2973    scope => 'global');
2974  return; }
2975
2976#======================================================================
2977# C.8.4 Numbering
2978#======================================================================
2979# For LaTeX documents, We want id's on para, as well as sectional units.
2980# However, para get created implicitly on Document construction, rather than
2981# explicitly during digestion (via a whatsit), we can't use the usual LaTeX counter mechanism.
2982Tag('ltx:para', afterOpen => sub { GenerateID(@_, 'p'); });
2983
2984DefPrimitive('\newcounter{}[]', sub {
2985    NewCounter(ToString(Expand($_[1])), $_[2] && ToString(Expand($_[2])));
2986    return; });
2987Let(T_CS('\@definecounter'), T_CS('\newcounter'));
2988DefPrimitive('\setcounter{}{Number}',   sub { SetCounter(ToString(Expand($_[1])), $_[2]); });
2989DefPrimitive('\addtocounter{}{Number}', sub { AddToCounter(ToString(Expand($_[1])), $_[2]); });
2990DefPrimitive('\stepcounter{}',          sub { StepCounter(ToString(Expand($_[1])));    return; });
2991DefPrimitive('\refstepcounter{}',       sub { RefStepCounter(ToString(Expand($_[1]))); return; });
2992
2993sub addtoCounterReset {
2994  my ($ctr, $within) = @_;
2995  my $reg       = "\\cl\@$within";
2996  my $t         = T_CS($ctr);
2997  my $unt       = T_CS('UN' . $ctr);
2998  my $prevreset = LookupValue($reg);
2999  AssignValue($reg => Tokens($t, $unt, ($prevreset ? $prevreset->unlist : ())), 'global');
3000  return; }
3001
3002sub remfromCounterReset {
3003  my ($ctr, $within) = @_;
3004  my $reg       = "\\cl\@$within";
3005  my $t         = T_CS($ctr);
3006  my $unt       = T_CS('UN' . $ctr);
3007  my $prevreset = LookupValue($reg);
3008  AssignValue($reg => Tokens(grep { !$t->equals($_) && !$unt->equals($_) } $prevreset->unlist),
3009    'global') if $prevreset;
3010  return; }
3011
3012sub defCounterID {
3013  my ($ctr, $within) = @_;
3014  my $prefix = LookupValue('@ID@prefix@' . $ctr);
3015  if (defined $prefix) {
3016    if ($within) {
3017      DefMacroI(T_CS("\\the$ctr\@ID"), undef,
3018        "\\expandafter\\ifx\\csname the$within\@ID\\endcsname\\\@empty"
3019          . "\\else\\csname the$within\@ID\\endcsname.\\fi"
3020          . " $prefix\\csname \@$ctr\@ID\\endcsname",
3021        scope => 'global'); }
3022    else {
3023      DefMacroI(T_CS("\\the$ctr\@ID"), undef, "$prefix\\csname \@$ctr\@ID\\endcsname",
3024        scope => 'global'); } }
3025  return; }
3026
3027DefPrimitive('\@addtoreset{}{}', sub {
3028    my ($stomach, $ctr, $within) = @_;
3029    $ctr    = ToString(Expand($ctr));
3030    $within = ToString(Expand($within));
3031    addtoCounterReset($ctr, $within);
3032    defCounterID($ctr, $within) unless LookupDefinition(T_CS("\\the$ctr\@ID"));
3033    return; });
3034
3035DefMacro('\value{}', sub {
3036    T_CS("\\c@" . ToString(Expand($_[1]))) });
3037DefMacro('\@arabic{Number}', sub {
3038    ExplodeText(ToString($_[1]->valueOf)); });
3039DefMacro('\arabic{}', sub {
3040    ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
3041DefMacro('\@roman{Number}', sub {
3042    ExplodeText(radix_roman(ToString($_[1]->valueOf))); });
3043DefMacro('\roman{}', sub {
3044    ExplodeText(radix_roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
3045DefMacro('\@Roman{Number}', sub {
3046    ExplodeText(radix_Roman(ToString($_[1]->valueOf))); });
3047DefMacro('\Roman{}', sub {
3048    ExplodeText(radix_Roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
3049DefMacro('\@alph{Number}', sub {
3050    ExplodeText(radix_alpha($_[1]->valueOf)); });
3051DefMacro('\alph{}', sub {
3052    ExplodeText(radix_alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
3053DefMacro('\@Alph{Number}', sub {
3054    ExplodeText(radix_Alpha($_[1]->valueOf)); });
3055DefMacro('\Alph{}', sub {
3056    ExplodeText(radix_Alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
3057
3058our @fnsymbols = ("*", "\x{2020}", "\x{2021}", UTF(0xA7), UTF(0xB6),
3059  "\x{2225}", "**", "\x{2020}\x{2020}", "\x{2021}\x{2021}");
3060DefMacro('\@fnsymbol{Number}', sub {
3061    ExplodeText(radix_format($_[1]->valueOf, @fnsymbols)); });
3062DefMacro('\fnsymbol{}', sub {
3063    ExplodeText(radix_format(CounterValue(ToString(Expand($_[1])))->valueOf, @fnsymbols)); });
3064
3065# The following two macros were originally defined in chngcntr.sty,
3066# but were moved to LaTeX core in 2018.
3067DefPrimitive('\counterwithin OptionalMatch:* {}{}', sub {
3068    my ($stomach, $starred, $ctr, $within) = @_;
3069    $ctr    = ToString($ctr);
3070    $within = ToString($within);
3071    addtoCounterReset($ctr, $within);
3072    if (!$starred) {
3073      DefMacroI(T_CS("\\the$ctr"), undef, "\\the$within.\\arabic{$ctr}", scope => 'global');
3074      defCounterID($ctr, $within); }
3075    return; });
3076
3077DefPrimitive('\counterwithout OptionalMatch:* {}{}', sub {
3078    my ($stomach, $starred, $ctr, $within) = @_;
3079    $ctr    = ToString($ctr);
3080    $within = ToString($within);
3081    remfromCounterReset($ctr, $within);
3082    if (!$starred) {
3083      DefMacroI(T_CS("\\the$ctr"), undef, "\\arabic{$ctr}", scope => 'global');
3084      defCounterID($ctr); }
3085    return; });
3086
3087DefPrimitive('\@removefromreset{}{}', sub {
3088    my ($stomach, $ctr, $within) = @_;
3089    $ctr    = ToString($ctr);
3090    $within = ToString($within);
3091    remfromCounterReset($ctr, $within);
3092    # Should \the$ctr@ID be redefined?  It really should track changes to \the$ctr!
3093    # defCounterID($ctr);
3094    return; });
3095
3096DefMacroI('\cl@@ckpt', undef, '\@elt{page}');
3097#======================================================================
3098# C.8.5 The ifthen Package.
3099#======================================================================
3100# \ifthenelse
3101# and sundry conditionals...
3102#
3103# Yeah, maybe this'll get done someday....
3104
3105#**********************************************************************
3106# C.9 Figures and Other Floating Bodies
3107#**********************************************************************
3108
3109#======================================================================
3110# C.9.1 Figures and Tables
3111#======================================================================
3112
3113# Note that, the number is associated with the caption.
3114# (to allow multiple figures per figure environment?).
3115# Whatever reason, that causes complications: We can only increment
3116# counters with the caption, but then have to arrange for the counters,
3117# refnums, ids, get passed on to the figure, table when needed.
3118# AND, as soon as possible, since other items may base their id's on the id of the table!
3119
3120# Let the fonts for float be the default for all floats, figures, tables, etc.
3121DefMacro('\fnum@font@float',         '\@empty');
3122DefMacro('\format@title@font@float', '\@empty');
3123
3124DefMacro('\fnum@font@figure',         '\fnum@font@float');
3125DefMacro('\fnum@font@table',          '\fnum@font@float');
3126DefMacro('\format@title@font@figure', '\format@title@font@float');
3127DefMacro('\format@title@font@table',  '\format@title@font@float');
3128
3129# stubs for the latex float internals
3130for my $float (qw(@float @dblfloat)) {
3131  DefEnvironmentI($float, '[]{}',
3132    "<ltx:float xml:id='#id' inlist='#inlist' ?#1(placement='#1') class='ltx_float_#2'>"
3133      . "#body"
3134      . "</ltx:float>",
3135    afterDigestBegin => sub {
3136      DefMacroI('\@captype', undef, ToString($_[1]->getArg(2)));
3137      return; },
3138    afterDigest => sub {
3139      RescueCaptionCounters(ToString(Expand(T_CS('\@captype'))), $_[1]);
3140      return; }); }
3141# Could perhaps parameterize further with a separator?
3142DefMacro('\format@title@figure{}', '\lx@tag[][: ]{\lx@fnum@@{figure}}#1');
3143DefMacro('\format@title@table{}',  '\lx@tag[][: ]{\lx@fnum@@{table}}#1');
3144
3145DefMacro('\ext@figure', 'lof');
3146DefMacro('\ext@table',  'lot');
3147
3148DefConditional('\iflx@donecaption');
3149DefMacro('\caption',
3150'\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}');
3151# First, check for trailing \label, move it into the caption as a standard position
3152# NOTE: If one day we want to unlock \@caption, make sure to test against arXiv:cond-mat/0001395 for a passing build.
3153DefMacro('\@caption{}[]{}',
3154  '\@ifnext\label{\@caption@postlabel{#1}{#2}{#3}}{\@caption@{#1}{#2}{#3}}', locked => 1);
3155
3156DefMacro('\@caption@postlabel{}{}{} SkipMatch:\label Semiverbatim',
3157  '\@caption@{#1}{#2}{#3\label{#4}}');
3158
3159# Now, check for \label(s) inside to (potentially) record them.
3160DefMacro('\@caption@{}{}{}',
3161  '\@hack@caption@{#1}{#2}{}#3\label\endcaption');
3162DefMacro('\@hack@caption@{}{}{} Until:\label Until:\endcaption',
3163  '\ifx.#5.\@caption@@@{#1}{#2}{#3#4}'    # No more labels
3164    . '\else\@@@hack@caption@{#1}{#2}{#3#4}#5\endcaption\fi');
3165DefMacro('\@@@hack@caption@{}{}{} Semiverbatim Until:\label Until:\endcaption',
3166  '\lx@note@caption@label{#4}' .
3167    '\@hack@caption@{#1}{#2}{#3\label{#4}#5}\label#6\endcaption');
3168
3169DefPrimitive('\lx@note@caption@label{}', sub { MaybeNoteLabel($_[1]); });
3170
3171DefMacro('\@caption@@@{}{}{}',
3172  '\@@add@caption@counters'
3173    . '\@@toccaption{\lx@format@toctitle@@{#1}{\ifx.#2.#3\else#2\fi}}'
3174    . '\@@caption{\lx@format@title@@{#1}{#3}}');
3175
3176# Note that the counters only get incremented by \caption, NOT by \table, \figure, etc.
3177DefPrimitive('\@@add@caption@counters', sub {
3178    my $captype = ToString(Digest(T_CS('\@captype')));
3179    my %props   = RefStepCounter($captype);
3180    my $inlist  = ToString(Digest(T_CS('\ext@' . $captype)));
3181    AssignValue($captype . "_tags"   => $props{tags}, 'global');
3182    AssignValue($captype . "_id"     => $props{id},   'global');
3183    AssignValue($captype . "_inlist" => $inlist,      'global'); });
3184
3185sub RescueCaptionCounters {
3186  my ($captype, $whatsit) = @_;
3187  if (my $tags = LookupValue($captype . "_tags")) {
3188    AssignValue($captype . "_tags" => undef, 'global');
3189    $whatsit->setProperty(tags => $tags); }
3190  if (my $id = LookupValue($captype . "_id")) {
3191    AssignValue($captype . "_id" => undef, 'global');
3192    $whatsit->setProperty(id => $id); }
3193  if (my $inlist = LookupValue($captype . "_inlist")) {
3194    AssignValue($captype . "_inlist" => undef, 'global');
3195    $whatsit->setProperty(inlist => $inlist); }
3196  return; }
3197
3198DefConstructor('\@@generic@caption[]{}', "<ltx:text class='ltx_caption'>#2</ltx:text>",
3199  beforeDigest => sub {
3200    Error('unexpected', '\caption', $_[0],
3201      "Use of \\caption outside any known float"); });
3202
3203# Note that even without \caption, we'd probably like to have xml:id.
3204Tag('ltx:figure', afterClose => sub { GenerateID(@_, 'fig'); });
3205Tag('ltx:table',  afterClose => sub { GenerateID(@_, 'tab'); });
3206Tag('ltx:float',  afterClose => sub { GenerateID(@_, 'tab'); });
3207
3208# These may need to float up to where they're allowed,
3209# or they may need to close <p> or similar.
3210DefConstructor('\@@caption{}', "^^<ltx:caption>#1</ltx:caption>");
3211DefConstructor('\@@toccaption{}', "^^<ltx:toccaption>#1</ltx:toccaption>",
3212  sizer => '0');
3213
3214DefEnvironment('{figure}[]',
3215  "<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
3216    . "#tags"
3217    . "#body"
3218    . "</ltx:figure>",
3219  properties   => { layout => 'vertical' },
3220  beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
3221  afterDigest  => sub { RescueCaptionCounters('figure', $_[1]); },
3222  locked       => 1);
3223DefEnvironment('{figure*}[]',
3224  "<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
3225    . "#tags"
3226    . "#body"
3227    . "</ltx:figure>",
3228  properties   => { layout => 'vertical' },
3229  beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
3230  afterDigest  => sub { RescueCaptionCounters('figure', $_[1]); },
3231  locked       => 1);
3232DefEnvironment('{table}[]',
3233  "<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
3234    . "#tags"
3235    . "#body"
3236    . "</ltx:table>",
3237  properties   => { layout => 'vertical' },
3238  beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
3239  afterDigest  => sub { RescueCaptionCounters('table', $_[1]); },
3240  locked       => 1);
3241DefEnvironment('{table*}[]',
3242  "<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
3243    . "#tags"
3244    . "#body"
3245    . "</ltx:table>",
3246  properties   => { layout => 'vertical' },
3247  beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
3248  afterDigest  => sub { RescueCaptionCounters('table', $_[1]); },
3249  locked       => 1);
3250
3251# There are some floating figure/table packages that define environments
3252# to allow the figure to float left or right.  It APPEARS that they allow
3253# authors to either wrap with {figure} or NOT, as they like!
3254# The result apparently makes sense so long as they only use one \caption.
3255# If we get a figure containing a single figure, we want to collaps it.
3256sub collapseFloat {
3257  my ($document, $float) = @_;
3258  my $qname  = $document->getNodeQName($float);
3259  my @inners = $document->findnodes($qname, $float);
3260  if (scalar(@inners) == 1) {    # Only 1 inner float of same type
3261    my $inner    = $inners[0];
3262    my $ocaption = $document->findnodes('ltx:caption', $float);
3263    my $icaption = $document->findnodes('ltx:caption', $inner);
3264    if (!($ocaption && $icaption)) {    # If they don't both have captions.
3265      foreach my $attr (map { $_->nodeName } $inner->attributes) {
3266        next if $attr eq 'xml:id';
3267        $document->setAttribute($float, $attr => $inner->getAttribute($attr)); }
3268      if ($icaption) {
3269        if (my $id = $inner->getAttribute('xml:id')) {
3270          $document->unRecordID($id);
3271          $document->setAttribute($float, 'xml:id' => $id); } }
3272
3273      # Finally, replace $inner by it's children!
3274      # Note that since we can only append new stuff, we've got to remove the following first.
3275      my @save = ();
3276      my $following;
3277      while (($following = $float->lastChild) && ($$following != $$inner)) { # Remove & Save following siblings.
3278        unshift(@save, $float->removeChild($following)); }
3279      unshift(@save, $inner->childNodes);
3280      $float->removeChild($inner);
3281      map { $float->appendChild($_) } @save; }                               # Put these back.
3282  }
3283  return; }
3284Tag('ltx:figure', afterClose => \&collapseFloat);
3285Tag('ltx:table',  afterClose => \&collapseFloat);
3286Tag('ltx:float',  afterClose => \&collapseFloat);
3287
3288DefPrimitive('\flushbottom',      undef);
3289DefPrimitive('\suppressfloats[]', undef);
3290
3291NewCounter('topnumber');
3292DefMacroI('\topfraction', undef, "0.25");
3293NewCounter('bottomnumber');
3294DefMacroI('\bottomfraction', undef, "0.25");
3295NewCounter('totalnumber');
3296DefMacroI('\textfraction',      undef, "0.25");
3297DefMacroI('\floatpagefraction', undef, "0.25");
3298NewCounter('dbltopnumber');
3299DefMacroI('\dbltopfraction',       undef, "0.7");
3300DefMacroI('\dblfloatpagefraction', undef, "0.25");
3301DefRegister('\floatsep'         => Glue(0));
3302DefRegister('\textfloatsep'     => Glue(0));
3303DefRegister('\intextsep'        => Glue(0));
3304DefRegister('\dblfloatsep'      => Glue(0));
3305DefRegister('\dbltextfloatsep'  => Glue(0));
3306DefRegister('\@maxsep'          => Dimension(0));
3307DefRegister('\@dblmaxsep'       => Dimension(0));
3308DefRegister('\@fptop'           => Glue(0));
3309DefRegister('\@fpsep'           => Glue(0));
3310DefRegister('\@fpbot'           => Glue(0));
3311DefRegister('\@dblfptop'        => Glue(0));
3312DefRegister('\@dblfpsep'        => Glue(0));
3313DefRegister('\@dblfpbot'        => Glue(0));
3314DefRegister('\abovecaptionskip' => Glue(0));
3315DefRegister('\belowcaptionskip' => Glue(0));
3316Let('\topfigrule', '\relax');
3317Let('\botfigrule', '\relax');
3318Let('\dblfigrule', '\relax');
3319
3320DefMacroI('\figurename',  undef, 'Figure');
3321DefMacroI('\figuresname', undef, 'Figures');    # Never used?
3322DefMacroI('\tablename',   undef, 'Table');
3323DefMacroI('\tablesname',  undef, 'Tables');
3324
3325Let('\outer@nobreak', '\@empty');
3326DefMacro('\@dbflt{}',           '#1');
3327DefMacro('\@xdblfloat{}[]',     '\@xfloat{#1}[#2]');
3328DefMacro('\@floatplacement',    '');
3329DefMacro('\@dblfloatplacement', '');
3330
3331#======================================================================
3332# C.9.2 Marginal Notes
3333#======================================================================
3334
3335AssignValue(marginparclass => undef);
3336DefConstructor('\marginpar[]{}',
3337  "<ltx:note role='margin' class='#class'><ltx:inline-para>#2</ltx:inline-para></ltx:note>",
3338  properties => sub { (class => LookupValue('marginparclass')); });
3339DefPrimitiveI('\reversemarginpar', undef, sub { AssignValue(marginparclass => 'ltx_marginpar_reverse'); });
3340DefPrimitiveI('\normalmarginpar', undef, sub { AssignValue(marginparclass => undef); });
3341DefRegister('\marginparpush', Dimension(0));
3342
3343#**********************************************************************
3344# C.10 Lining It Up in Columns
3345#**********************************************************************
3346
3347#======================================================================
3348# C.10.1 The tabbing Environment
3349#======================================================================
3350DefRegister('\tabbingsep' => Dimension(0));
3351
3352DefMacroI('\tabbing', undef,
3353  '\@tabbing@bindings\@@tabbing\@start@alignment');
3354DefMacroI('\endtabbing', undef,
3355  '\@finish@alignment\@end@tabbing');
3356DefPrimitiveI('\@end@tabbing', undef, sub { $_[0]->egroup; });
3357DefConstructor('\@@tabbing SkipSpaces DigestedBody',
3358  '#1',
3359  reversion    => '\begin{tabbing}#1\end{tabbing}',
3360  beforeDigest => sub { $_[0]->bgroup; },
3361  mode         => 'text');
3362
3363DefMacroI('\@tabbing@tabset',  undef, '\@tabbing@tabset@marker&');
3364DefMacroI('\@tabbing@nexttab', undef, '\@tabbing@nexttab@marker&');
3365DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]', '\@tabbing@newline@marker\cr');
3366DefMacroI('\@tabbing@kill', undef, '\@tabbing@kill@marker\cr\@tabbing@start@tabs');
3367
3368DefConstructorI('\@tabbing@tabset@marker',  undef, '', reversion => '\=');
3369DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>');
3370DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR));
3371DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill',
3372  afterDigest => sub { LookupValue('Alignment')->removeRow; return; });
3373
3374AssignValue(tabbing_start_tabs => Tokens());
3375DefMacroI('\@tabbing@start@tabs', undef, sub { LookupValue('tabbing_start_tabs')->unlist; });
3376DefPrimitiveI('\@tabbing@increment', undef, sub {
3377    my @tabs = LookupValue('tabbing_start_tabs')->unlist;
3378    AssignValue(tabbing_start_tabs => Tokens(@tabs, T_CS('\>')), 'global'); });
3379DefPrimitiveI('\@tabbing@decrement', undef, sub {
3380    my ($ignore, @tabs) = LookupValue('tabbing_start_tabs')->unlist;
3381    AssignValue(tabbing_start_tabs => Tokens(@tabs), 'global'); });
3382
3383# NOTE: \< is NOT currently handled!!!
3384# Ugh!! The way we're setting the initial tabs, we can't really handle this!
3385DefPrimitiveI('\@tabbing@untab', undef, undef);
3386
3387# NOTE: \' and \` are NOT currently handled
3388DefPrimitiveI('\@tabbing@flushright', undef, undef);
3389DefPrimitiveI('\@tabbing@hfil',       undef, undef);
3390# NOTE: \pushtabs and \poptabs are NOT currently handled.
3391DefPrimitiveI('\@tabbing@pushtabs', undef, undef);
3392DefPrimitiveI('\@tabbing@poptabs',  undef, undef);
3393
3394DefMacro('\@tabbing@accent{}', sub { T_CS('\@tabbing@' . ToString($_[1])); });
3395
3396# Should there be some rowsep/colsep set here?
3397# Note that {tabbign} really shouldn't be handled by a tabular AT ALL....
3398# Should be recording accumulated widths and wrapping in ltx:text, with specified widths.
3399sub tabbingBindings {
3400  AssignValue(Alignment => LaTeXML::Core::Alignment->new(
3401      template => LaTeXML::Core::Alignment::Template->new(
3402        repeated => [{ after => Tokens(T_CS('\hfil')) }]),
3403      openContainer  => sub { $_[0]->openElement('ltx:tabular', @_[1 .. $#_]); },
3404      closeContainer => sub { $_[0]->closeElement('ltx:tabular'); },
3405      openRow        => sub { $_[0]->openElement('ltx:tr', @_[1 .. $#_]); },
3406      closeRow       => sub { $_[0]->closeElement('ltx:tr'); },
3407      openColumn     => sub { $_[0]->openElement('ltx:td', @_[1 .. $#_]); },
3408      closeColumn    => sub { $_[0]->closeElement('ltx:td'); }));
3409  Let("\\=",              '\@tabbing@tabset');
3410  Let("\\>",              '\@tabbing@nexttab');
3411  Let("\\\\",             '\@tabbing@newline');
3412  Let('\kill',            '\@tabbing@kill');
3413  Let("\\+",              '\@tabbing@increment');
3414  Let("\\-",              '\@tabbing@decrement');
3415  Let("\\<",              '\@tabbing@untab');
3416  Let('\@tabbing@' . "'", "\\'");
3417  Let('\@tabbing@' . "`", "\\`");
3418  Let('\a',               '\@tabbing@accent');
3419  Let("\\'",              '\@tabbing@flushright');
3420  Let("\\`",              '\@tabbing@hfil');
3421  Let('\pushtabs',        '\@tabbing@pushtabs');
3422  Let('\poptabs',         '\@tabbing@poptabs');
3423  return; }
3424
3425DefMacroI('\pushtabs', undef, Tokens());
3426DefMacroI('\poptabs',  undef, Tokens());
3427DefMacroI('\kill',     undef, Tokens());
3428
3429DefPrimitiveI('\@tabbing@bindings', undef, sub {
3430    tabbingBindings(); });
3431
3432# NOTE: Do it!!
3433
3434#======================================================================
3435# C.10.2 The array and tabular Environments
3436#======================================================================
3437# Tabular are a bit tricky in that we have to arrange for tr and td to
3438# be openned and closed at the right times; the only real markup is
3439# the & and \\. Also \multicolumn has to be cooperative.
3440# Along with this, we have to track which column specification applies
3441# to the current column.
3442# To simulate LaTeX's tabular borders & hlines, we simply add border
3443# attributes to all cells.  For HTML, CSS will be necessary to display them.
3444# [We'll ignore HTML's frame, rules and colgroup mechanisms.]
3445
3446DefRegister('\lx@arstrut',           Dimension('0pt'));
3447DefRegister('\lx@default@tabcolsep', Dimension('6pt'));
3448DefRegister('\tabcolsep',            Dimension('6pt'));
3449DefMacroI('\arraystretch', undef, "1");
3450Let('\@tabularcr', '\@alignment@newline');
3451AssignValue(GUESS_TABULAR_HEADERS => 1)    # Defaults to yes
3452  unless defined LookupValue('GUESS_TABULAR_HEADERS');
3453
3454sub tabularBindings {
3455  my ($template, %properties) = @_;
3456  $properties{guess_headers} = LookupValue('GUESS_TABULAR_HEADERS')
3457    unless defined $properties{guess_headers};
3458  if (!defined $properties{attributes}{colsep}) {
3459    my $sep = LookupDimension('\tabcolsep');
3460    if ($sep && ($sep->valueOf != LookupDimension('\lx@default@tabcolsep')->valueOf)) {
3461      $properties{attributes}{colsep} = $sep; } }
3462  if (!defined $properties{attributes}{rowsep}) {
3463    my $str = ToString(Expand(T_CS('\arraystretch')));
3464    if ($str != 1) {
3465      $properties{attributes}{rowsep} = Dimension(($str - 1) . 'em'); } }
3466  if (!defined $properties{strut}) {
3467    $properties{strut} = LookupRegister('\baselineskip')->multiply(1.5); }    # Account for html space
3468  alignmentBindings($template, 'text', %properties);
3469  Let("\\\\",            '\@tabularcr');
3470  Let('\tabularnewline', "\\\\");
3471  # NOTE: Fit this back in!!!!!!!
3472  # # Do like AddToMacro, but NOT global!
3473  foreach my $name ('@row@before', '@row@after', '@column@before', '@column@after') {
3474    my $cs = '\\' . $name;
3475    DefMacroI($cs, undef,
3476      Tokens(LookupDefinition(T_CS($cs))->getExpansion->unlist,
3477        T_CS('\@tabular' . $name))); }
3478  return; }
3479
3480# Keyvals are for attributes for the alignment.
3481# Typical keys are width, vattach,...
3482DefKeyVal('tabular', 'width', 'Dimension');
3483DefPrimitive('\@tabular@bindings AlignmentTemplate OptionalKeyVals:tabular', sub {
3484    my ($stomach, $template, $attributes) = @_;
3485    my %attr = ($attributes ? $attributes->getPairs : ());
3486    if (my $va = $attr{vattach}) {
3487      $attr{vattach} = translateAttachment($va) || ToString($va); }
3488    tabularBindings($template, attributes => {%attr});
3489    return; });
3490
3491DefMacroI('\@tabular@before',        undef, '');
3492DefMacroI('\@tabular@after',         undef, '');
3493DefMacroI('\@tabular@row@before',    undef, '');
3494DefMacroI('\@tabular@row@after',     undef, '');
3495DefMacroI('\@tabular@column@before', undef, '');
3496DefMacroI('\@tabular@column@after',  undef, '');
3497
3498# The Core alignment support is in LaTeXML::Core::Alignment and in TeX.ltxml
3499##DefMacro('\tabular[]{}', '\@tabular@bindings{#2}\@@tabular[#1]{#2}\@start@alignment');
3500DefMacro('\tabular[]{}',
3501  '\@tabular@bindings{#2}[vattach=#1]\@@tabular[#1]{#2}\@start@alignment\@tabular@before',
3502  locked => 1);
3503DefMacroI('\endtabular', undef,
3504  '\@tabular@after\@finish@alignment\@end@tabular',
3505  locked => 1);
3506DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; });
3507#DefMacroI('\@end@tabular', undef, undef);
3508# Note that the pattern will already have been interpreted by \@tabular@bindings,
3509# so make it Undigested here!
3510
3511DefConstructor('\@@tabular[] Undigested DigestedBody',
3512  '#3',
3513  reversion    => '\begin{tabular}[#1]{#2}#3\end{tabular}',
3514  beforeDigest => sub { $_[0]->bgroup; },
3515  sizer        => '#3',
3516  afterDigest  => sub {
3517    my ($stomach, $whatsit) = @_;
3518    if (my $alignment = LookupValue('Alignment')) {
3519      my $attr = $alignment->getProperty('attributes');
3520      $$attr{vattach} = translateAttachment($whatsit->getArg(1)); }
3521    return; },
3522  mode => 'text');
3523
3524DefMacro('\csname tabular*\endcsname{Dimension}[]{}',
3525###  '\@tabular@bindings{#3}\@@tabular@{#1}[#2]{#3}\@start@alignment');
3526  '\@tabular@bindings{#3}[width=#1,vattach=#2]\@@tabular@{#1}[#2]{#3}\@start@alignment');
3527DefMacro('\csname endtabular*\endcsname',
3528  '\@finish@alignment\@end@tabular@');
3529DefConstructor('\@@tabular@{Dimension}[] Undigested DigestedBody',
3530  '#4',
3531  beforeDigest => sub { $_[0]->bgroup; },
3532  reversion    => '\begin{tabular*}{#1}[#2]{#3}#4\end{tabular*}',
3533  mode         => 'text');
3534DefPrimitive('\@end@tabular@', sub { $_[0]->egroup; });
3535Let('\multicolumn', '\@multicolumn');
3536
3537DefPrimitiveI('\hline', undef, undef);    # Redefined inside tabular
3538# A weird bit that sometimes gets invoked by Cargo Cult programmers...
3539# to \noalign in the defn of \hline! Bizarre! (see latex.ltx)
3540# However, the really weird thing is the way this provides the } to close the argument
3541DefMacroI('\@xhline', undef, '\ifnum0=`{\fi}');
3542
3543#DefConstructor('\cline{}', '',
3544DefMacro('\cline{}', '\noalign{\@cline{#1}}');
3545DefConstructor('\@cline{}', '',
3546  afterDigest => sub {
3547    my ($stomach, $whatsit) = @_;
3548    my $cols = ToString($whatsit->getArg(1));
3549    my @cols = ();
3550    while ($cols =~ s/^,?(\d+)//) {
3551      my $n = $1;
3552      push(@cols, ($cols =~ s/^-(\d+)// ? ($n .. $1) : ($n))); }
3553    my $alignment = LookupValue('Alignment');
3554    $alignment->addLine('t', @cols) if $alignment;
3555    return; },
3556  sizer      => 0,
3557  properties => { isHorizontalRule => 1 });
3558
3559DefConstructorI('\vline', undef, "",    # ???
3560  properties => { isVerticalRule => 1 },
3561  sizer      => 0,
3562);
3563DefRegister('\lx@default@arraycolsep', Dimension('5pt'));
3564DefRegister('\arraycolsep',            Dimension('5pt'));
3565DefRegister('\arrayrulewidth',         Dimension('0.4pt'));
3566DefRegister('\doublerulesep',          Dimension('2pt'));
3567DefMacro('\extracolsep{}', Tokens());
3568#DefMacroI('\tabularnewline', undef, Tokens());
3569#Let('\tabularnewline', '\cr');
3570#DefMacroI('\tabularnewline', undef, '\cr'); # ???
3571#======================================================================
3572# Array and similar environments
3573
3574DefPrimitive('\@array@bindings [] AlignmentTemplate', sub {
3575    my ($stomach, $pos, $template) = @_;
3576    my $attr = { vattach => translateAttachment($pos),
3577      role => 'ARRAY' };
3578    # Determine column and row separations, if non default
3579    my $colsep = LookupDimension('\arraycolsep');
3580    if ($colsep && ($colsep->valueOf != LookupDimension('\lx@default@arraycolsep')->valueOf)) {
3581      $$attr{colsep} = $colsep; }
3582    my $str = ToString(Expand(T_CS('\arraystretch')));
3583    if ($str != 1) {
3584      $$attr{rowsep} = Dimension(($str - 1) . 'em'); }
3585    alignmentBindings($template, 'math', attributes => $attr);
3586    MergeFont(mathstyle => 'text');
3587    Let("\\\\", '\@alignment@newline');
3588    return; });
3589
3590DefMacro('\array[]{}',
3591  '\@array@bindings[#1]{#2}\@@array[#1]{#2}\@start@alignment');
3592DefMacroI('\endarray', undef,
3593  '\@finish@alignment\@end@array');
3594DefPrimitiveI('\@end@array', undef, sub { $_[0]->egroup; });
3595DefConstructor('\@@array[] Undigested DigestedBody',
3596  '#3',
3597  beforeDigest => sub { $_[0]->bgroup; },
3598  reversion    => '\begin{array}[#1]{#2}#3\end{array}');
3599
3600DefMacro('\@tabarray', '\m@th\@@array[c]');
3601#**********************************************************************
3602# C.11 Moving Information Around
3603#**********************************************************************
3604
3605#======================================================================
3606# C.11.1 Files
3607#======================================================================
3608DefPrimitive('\nofiles', undef);
3609
3610#======================================================================
3611# C.11.2 Cross-References
3612#======================================================================
3613
3614# \label attaches a label to the nearest parent that can accept a labels attribute
3615# but only those that have an xml:id (but should this require a refnum and/or title ???)
3616# Note that latex essentially allows redundant labels, but we can record only one!!!
3617DefConstructor('\label Semiverbatim', sub {
3618    my ($document, $olabel, %props) = @_;
3619    my $label = $props{label};
3620    if (my $savenode = $document->floatToLabel) {
3621      my $node   = $document->getNode;
3622      my %labels = map { ($_ => 1) } $label, split(/\s+/, $node->getAttribute('labels') || '');
3623      $document->setAttribute($node, labels => join(' ', sort keys %labels));
3624      $document->setNode($savenode); } },
3625  reversion   => '',
3626  properties  => { alignmentSkippable => 1, alignmentPreserve => 1 },
3627  afterDigest => sub {
3628    MaybeNoteLabel($_[1]->getArg(1));
3629    my $label = CleanLabel(ToString($_[1]->getArg(1)));
3630    $_[1]->setProperty(label => $label);
3631    my $scope = $label; $scope =~ s/^LABEL:/label:/;
3632    if (my $ctr = LookupValue('current_counter')) {
3633      unshift(@{ LookupValue('scopes_for_counter:' . $ctr) }, $scope);
3634      $STATE->activateScope($scope);
3635      $_[0]->beginMode('text');
3636      AssignValue('LABEL@' . $label, Digest(T_CS('\@currentlabel')), 'global');
3637      $_[0]->endMode('text'); }
3638    return; });
3639
3640# If a node has been labeled, but still  hasn't yet got an id by afterClose:late,
3641# we'd better generate an id for it.
3642Tag('ltx:*', 'afterClose:late' => sub {
3643    my ($document, $node) = @_;
3644    if ($node->hasAttribute('labels') && !($node->hasAttribute('xml:id'))) {
3645      GenerateID($document, $node); } });
3646
3647# These will get filled in during postprocessing.
3648# * is added to accommodate hyperref
3649DefConstructor('\ref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>",
3650  properties => sub { (label => CleanLabel($_[2])); });
3651DefConstructor('\pageref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>", # Same??
3652  properties => sub { (label => CleanLabel($_[2])); });
3653#======================================================================
3654# C.11.3 Bibliography and Citation
3655#======================================================================
3656
3657# Note that it's called \refname in LaTeX's article, but \bibname in report & book.
3658# And likewise, mixed up in various other classes!
3659
3660DefMacroI('\thebibliography@ID', undef, Tokens());
3661
3662# Do this before digesting the body of a bibliography
3663sub beforeDigestBibliography {
3664  AssignValue(inPreamble => 0); Digest('\@lx@inbibliographytrue');
3665  DefMacro('\bibliographystyle{}', '');
3666  DefMacro('\bibliography {}',     '');
3667  DefConstructorI('\endthebibliography', undef,
3668    "</ltx:biblist></ltx:bibliography>",
3669    locked => 1);
3670  ResetCounter('@bibitem');
3671  return; }
3672
3673# This sub does things that would commonly be needed when starting a bibliography
3674# setting the ID, etc...
3675sub beginBibliography {
3676  my ($whatsit) = @_;
3677  beginBibliography_clean($whatsit);
3678  # Fix for missing \bibitems!
3679  setupPseudoBibitem();
3680  return; }
3681
3682sub beginBibliography_clean {
3683  my ($whatsit) = @_;
3684  # Check if \bibsection is defined and try to decipher it.
3685  # Expecting something like \section*{sometext}
3686  my $bibtitle = undef;
3687  my $bs       = LookupDefinition(T_CS('\bibsection'));
3688  # FIXED: Sometimes $bs may be a Primitive.
3689  # Now guarding for that case or we get a perl:die for ->getExpansion
3690  if (my @t = $bs && ($bs->isExpandable ? $bs->getExpansion->unlist : $bs)) {
3691    our %bibunitmap = (
3692      '\\part'          => 'ltx:part',          '\\chapter'    => 'ltx:chapter',
3693      '\\section'       => 'ltx:section',       '\\subsection' => 'ltx:subsection',
3694      '\\subsubsection' => 'ltx:subsubsection', '\\paragraph'  => 'ltx:paragraph',
3695      '\\subparagraph'  => 'ltx:subparagraph');
3696    if (my $unit = $bibunitmap{ ToString(shift(@t)) }) {    # Found a sectional unit command.
3697      AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => $unit);
3698      if (ToString($t[0]) eq '*') { shift(@t); }
3699      # Check for balanced? or just take balanced begining?
3700      $bibtitle = Tokens(@t); } }
3701  noteBackmatterElement($whatsit, 'ltx:bibliography');
3702  # Try to compute a reasonable, but unique ID;
3703  # relative to the document's ID, if any.
3704  # But also, if there are multiple bibliographies,
3705  my $bibnumber = LookupValue('n_bibliographies') || 0;
3706  AssignValue(n_bibliographies => ++$bibnumber, 'global');
3707  my $docid = ToString(Expand(T_CS('\thedocument@ID')));
3708  my $bibid = ($docid ? $docid . '.' : '') . 'bib' . radix_alpha($bibnumber - 1);
3709  DefMacroI(T_CS('\thebibliography@ID'), undef, T_OTHER($bibid), scope => 'global');
3710  #  $whatsit->setProperty(id=>ToString(Expand(T_CS('\thebibliography@ID'))));
3711  $whatsit->setProperty(id => $bibid);
3712  my $title = ($bibtitle ? Digest($bibtitle)
3713    : DigestIf('\refname') || DigestIf('\bibname'));
3714  $whatsit->setProperty(title     => $title)          if $title;
3715  $whatsit->setProperty(titlefont => $title->getFont) if $title;
3716  $whatsit->setProperty(bibstyle  => LookupValue('BIBSTYLE'));
3717  $whatsit->setProperty(citestyle => LookupValue('CITE_STYLE'));
3718  #  $whatsit->setProperty(sort=> ???
3719  # And prepare for the likely nonsense that appears within bibliographies
3720  ResetCounter('enumiv');
3721  return; }
3722
3723DefMacro('\bibliography Semiverbatim',
3724  '\lx@ifusebbl{#1}{\input{\jobname.bbl}}{\lx@bibliography{#1}}');
3725
3726DefMacro('\lx@ifusebbl{}{}{}', sub {
3727    my ($gullet, $bib_files, $bbl_clause, $bib_clause) = @_;
3728    $bib_files = ToString(Expand($bib_files));
3729    return unless $bib_files;
3730    my $jobname = ToString(Expand(T_CS('\jobname')));
3731
3732    my $bbl_path     = FindFile($jobname, type => 'bbl');
3733    my $missing_bibs = '';
3734    if (LookupValue("NO_BIBTEX")) {
3735      if (not $bbl_path) {
3736        Info('expected', "bbl", $_[0], "Couldn't find bbl file, bibliography may be empty.");
3737        return Tokens(); }
3738      else {
3739        return $bbl_clause->unlist; } }
3740    else {
3741      for my $bf (split(',', $bib_files)) {
3742        my $bib_path = FindFile($bf, type => 'bib');
3743        if (not $bib_path) {
3744          $missing_bibs .= ',' unless length($missing_bibs) == 0;
3745          $missing_bibs .= $bf; } }
3746      if (length($missing_bibs) == 0 or not $bbl_path) {
3747        return $bib_clause->unlist; }
3748      else {
3749        Info('expected', $missing_bibs, $_[0], "Couldn't find all bib files, using " . $jobname . ".bbl instead");
3750        return $bbl_clause->unlist; } } });
3751
3752AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => 'ltx:section');
3753AssignMapping('BACKMATTER_ELEMENT', 'ltx:index'        => 'ltx:section');
3754
3755sub noteBackmatterElement {
3756  my ($whatsit, $backelement) = @_;
3757  $whatsit->setProperties(backmatterelement => LookupMapping('BACKMATTER_ELEMENT', $backelement));
3758  return; }
3759
3760sub adjustBackmatterElement {
3761  my ($document, $whatsit) = @_;
3762  if (my $asif = $whatsit->getProperty('backmatterelement')) {
3763    $document->setNode($document->find_insertion_point($asif)); }
3764  return; }
3765
3766DefConstructor('\lx@bibliography [] Semiverbatim',
3767  "<ltx:bibliography files='#2' xml:id='#id' "
3768    . "bibstyle='#bibstyle' citestyle='#citestyle' sort='#sort' lists='#1'>"
3769    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
3770    . "</ltx:bibliography>",
3771  afterDigest => sub { $_[0]->begingroup;    # wrapped so redefns don't take effect!
3772    beginBibliography($_[1]);
3773    $_[0]->endgroup; },
3774  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });
3775
3776# NOTE: This totally needs to be made extensible (parsing *.bst!?!? OMG!)
3777our $BIBSTYLES = {
3778  plain    => { citestyle => 'numbers', sort => 'true' },
3779  unsrt    => { citestyle => 'numbers', sort => 'false' },
3780  alpha    => { citestyle => 'AY',      sort => 'true' },
3781  abbrv    => { citestyle => 'numbers', sort => 'true' },
3782  plainnat => { citestyle => 'numbers', sort => 'true' },
3783  unsrtnat => { citestyle => 'numbers', sort => 'false' },
3784  alphanat => { citestyle => 'AY',      sort => 'true' },
3785  abbrvnat => { citestyle => 'numbers', sort => 'true' } };
3786
3787sub setBibstyle {
3788  my ($style) = @_;
3789  $style = ToString($style);
3790  AssignValue(BIBSTYLE => $style);
3791  if (my $parms = $$BIBSTYLES{$style}) {
3792    AssignValue(CITE_STYLE => $$parms{citestyle});
3793    AssignValue(CITE_SORT  => $$parms{sort}); }
3794  return; }
3795
3796DefConstructor('\bibstyle{}', sub {
3797    my ($document, $style) = @_;
3798    setBibstyle($style);
3799    # Really ?
3800    if (my $bib = $document->findnode('//ltx:bibliography')) {
3801      $document->setAttribute($bib, bibstyle  => LookupValue('BIBSTYLE'));
3802      $document->setAttribute($bib, citestyle => LookupValue('CITE_STYLE'));
3803      $document->setAttribute($bib, sort      => LookupValue('CITE_SORT')); }
3804  },
3805  afterDigest => sub {
3806    my $style = ToString($_[1]->getArg(1));
3807    AssignValue(BIBSTYLE => $style, 'global');
3808    if (my $parms = $$BIBSTYLES{$style}) {
3809      AssignValue(CITE_STYLE => $$parms{citestyle}); }
3810    else {
3811      Info('unexpected', $style, $_[0], "Unknown bibstyle '$style', it will be ignored"); }
3812    return; });
3813
3814DefMacro('\bibliographystyle Semiverbatim', '\bibstyle{#1}');
3815
3816DefConditional('\if@lx@inbibliography');
3817# Should be an environment, but people seem to want to misuse it.
3818DefConstructorI('\thebibliography', undef,
3819  "<ltx:bibliography xml:id='#id'>"
3820    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
3821    . "<ltx:biblist>",
3822  beforeDigest => sub {
3823    beforeDigestBibliography(); },
3824  afterDigest => sub {
3825    my ($stomach, $whatsit) = @_;
3826    # NOTE that in some perverse situations (revtex?)
3827    # it seems to be allowable to omit the argument
3828    # It's ignorable for latexml anyway, so we'll just read it if its there .
3829    my $gullet = $stomach->getGullet;
3830    $gullet->skipSpaces;
3831    $gullet->readArg if $gullet->ifNext(T_BEGIN);
3832    beginBibliography($_[1]);
3833  },
3834  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); },
3835  locked          => 1);
3836
3837# Close the bibliography
3838DefConstructorI('\endthebibliography', undef,
3839  "</ltx:biblist></ltx:bibliography>",
3840  locked => 1);
3841# auto close the bibliography and contained biblist.
3842Tag('ltx:biblist',      autoClose => 1);
3843Tag('ltx:bibliography', autoClose => 1);
3844
3845# Since SOME people seem to write bibliographies w/o \bibitem,
3846# just blank lines between apparent entries,
3847# Making \par do a \bibitem{} works, but screws up valid
3848# bibliographies with blank lines!
3849# So, let's do some redirection!
3850sub setupPseudoBibitem {
3851  Let('\save@bibitem', '\bibitem');
3852  Let('\save@par',     '\par');
3853  Let('\bibitem',      '\restoring@bibitem');
3854  Let('\par',          '\par@in@bibliography');
3855  Let('\vskip',        '\vskip@in@bibliography');
3856  # Moreover, some people use \item instead of \bibitem
3857  Let('\item', '\item@in@bibliography');
3858  # And protect from redefinitions.
3859  Let('\newblock', '\lx@bibnewblock');
3860  return; }
3861
3862DefMacroI('\par@in@bibliography', undef, sub {
3863    my ($gullet) = @_;
3864    $gullet->skipSpaces;
3865    my $tok = $gullet->readToken;
3866    # If next token is another \par, or a REAL \bibitem,
3867    if (Equals($tok, T_CS('\par')) || Equals($tok, T_CS('\bibitem'))) {
3868      ($tok); }    # then this \par expands into what followed
3869    else {         # Else, put it back, and start a bibitem.
3870      $gullet->unread($tok);
3871      (T_CS('\save@bibitem'), T_BEGIN, T_END); } });
3872
3873DefMacro('\vskip@in@bibliography Glue', undef);
3874
3875DefMacroI('\item@in@bibliography', undef, '\save@bibitem{}');
3876
3877# If we hit a real \bibitem, put \par & \bibitem back to correct defn, and then \bibitem.
3878# A bibitem with now key or label...
3879DefMacro('\restoring@bibitem',
3880  '\let\bibitem\save@bibitem\let\par\save@par\bibitem');
3881
3882NewCounter('@bibitem', 'bibliography', idprefix => 'bib');
3883DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}');
3884DefMacro('\@biblabel{}', '[#1]');
3885DefMacroI('\fnum@@bibitem', undef, '{\@biblabel{\the@bibitem}}');
3886# Hack for abused bibliographies; see below
3887DefMacro('\bibitem',
3888  '\if@lx@inbibliography\else\expandafter\lx@mung@bibliography\expandafter{\@currenvir}\fi'
3889    . '\lx@bibitem', locked => 1);
3890DefConstructor('\lx@bibitem[] Semiverbatim',
3891  "<ltx:bibitem key='#key' xml:id='#id'>"
3892    . "#tags"
3893    . "<ltx:bibblock>",
3894  afterDigest => sub {
3895    my $tag = $_[1]->getArg(1);
3896    my $key = CleanBibKey($_[1]->getArg(2));
3897    if ($tag) {
3898      $_[1]->setProperties(key => $key,
3899        RefStepID('@bibitem'),
3900        tags => Digest(T_BEGIN,
3901          T_CS('\def'), T_CS('\the@bibitem'), T_BEGIN, Revert($tag), T_END,
3902          Invocation(T_CS('\lx@make@tags'), T_OTHER('@bibitem')),
3903          T_END)); }
3904    else {
3905      $_[1]->setProperties(key => $key, RefStepCounter('@bibitem')); }
3906  });
3907
3908# This attempts to handle the case where folks put \bibitem's within an enumerate or such.
3909# We try to close the list and open the bibliography
3910DefMacro('\lx@mung@bibliography{}', sub {
3911    my ($gullet, $env) = @_;
3912    my $tag    = ToString($env);
3913    my @tokens = ();
3914    # If we're in some sort of list environment, maybe we can recover
3915    if (($tag eq 'enumerate') || ($tag eq 'itemize') || ($tag eq 'description')) {
3916      # nDamn! We're in a list {$tag}; try to close it!
3917      push(@tokens, Invocation('\end', $env),
3918        T_CS('\let'), T_CS('\end' . $tag),        T_CS('\endthebibliography'),
3919        T_CS('\let'), T_CS('\end{' . $tag . '}'), T_CS('\end{thebibliography}')); }
3920    # else ? it probably isn't going to work??
3921    # Now, try to open {thebibliography}
3922    push(@tokens, Invocation('\begin', Tokenize('thebibliography'), Tokens()));
3923    return Tokens(@tokens); });
3924
3925DefConstructorI('\lx@bibnewblock', undef, "<ltx:bibblock>");
3926Let('\newblock', '\lx@bibnewblock');
3927Tag('ltx:bibitem',  autoClose => 1);
3928Tag('ltx:bibblock', autoClose => 1);
3929
3930#----------------------------------------------------------------------
3931# We've got the same problem as LaTeX: Lather, Rinse, Repeat.
3932# It would be nice to know the bib info at digestion time
3933#  * whether author lists will collapse
3934#  * whether there are "a","b".. extensions on the year.
3935# We could process the bibliography first, (IF it is a separate *.bib!)
3936# but won't know which entries are included (and so can't resolve the a/b/c..)
3937# until we've finished looking at (all of) the source(s) that will refer to them!
3938#
3939# We can do this in 2 passes, however
3940#  (1) convert (latexml) both the source document(s) and the bibliography
3941#  (2) extract the required bibitems and integrate (latexmlpost) it into the documents.
3942# [Note that for mult-document sites, step (2) becomes 2 stages: scan and integrate]
3943#
3944# Here's the general layout.
3945#   <ltx:cite> contains everything that the citations produce,
3946#     including parens, pre-note, punctunation that precede the <ltx:bibcite>
3947#     and punctuation, post-note, parens, that follow it.
3948#   <ltx:bibcite show="string" bibrefs="keys" sep="" yysep="">phrases</ltx:bibcite>
3949#     encodes the actual citation.
3950#
3951#     bibrefs : lists the bibliographic keys that will be used
3952#     show    : gives the pattern for formatting using data from the bibliography
3953#       It can contain:
3954#         authors or fullauthors
3955#         year
3956#         number
3957#         phrase1,phrase2,... selects one of the phrases from the content of the <ltx:bibref>
3958#     This format is used as follows:
3959#       If author and year is present, and a subset of the citations share the same authors,
3960#         then the format is used, but the year is repeated for each citation in the subset,
3961#         as a link to the bib entry.
3962#       Otherwise, the format is applied to each entry.
3963#
3964# The design is intended to support natbib, as well as plain LaTeX.
3965
3966AssignValue(CITE_STYLE          => 'numbers');
3967AssignValue(CITE_OPEN           => T_OTHER('['));
3968AssignValue(CITE_CLOSE          => T_OTHER(']'));
3969AssignValue(CITE_SEPARATOR      => T_OTHER(','));
3970AssignValue(CITE_YY_SEPARATOR   => T_OTHER(','));
3971AssignValue(CITE_NOTE_SEPARATOR => T_OTHER(','));
3972AssignValue(CITE_UNIT           => undef);
3973
3974DefMacro('\@cite{}{}', '[{#1\if@tempswa , #2\fi}]');
3975DefConstructor('\@@cite []{}', "<ltx:cite ?#1(class='ltx_citemacro_#1')>#2</ltx:cite>",
3976  mode => 'text');
3977
3978# \@@bibref{what to show}{bibkeys}{phrase1}{phrase2}
3979DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
3980  "<ltx:bibref show='#1' bibrefs='#bibrefs' inlist='#bibunit'"
3981    . " separator='#separator' yyseparator='#yyseparator'>#3#4</ltx:bibref>",
3982  properties => sub { (bibrefs => CleanBibKey($_[2]),
3983      separator   => ToString(Digest(LookupValue('CITE_SEPARATOR'))),
3984      yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR'))),
3985      bibunit     => LookupValue('CITE_UNIT')); });
3986
3987# Simple container for any phrases used in the bibref
3988DefConstructor('\@@citephrase{}', "<ltx:bibrefphrase>#1</ltx:bibrefphrase>",
3989  mode => 'text');
3990
3991DefMacro('\cite[] Semiverbatim', sub {
3992    my ($gullet, $post, $keys) = @_;
3993    my ($style, $open, $close, $ns)
3994      = map { LookupValue($_) } qw(CITE_STYLE CITE_OPEN CITE_CLOSE CITE_NOTE_SEPARATOR);
3995    $post = undef unless $post && $post->unlist;
3996    Invocation(T_CS('\@@cite'),
3997      Tokens(Explode('cite')),
3998      Tokens($open,
3999        Invocation(T_CS('\@@bibref'), undef, $keys, undef, undef),
4000        ($post ? ($ns, T_SPACE, $post) : ()), $close)); });
4001
4002# NOTE: Eventually needs to be recognized by MakeBibliography
4003DefConstructor('\nocite Semiverbatim',
4004  "<ltx:cite><ltx:bibref show='nothing' bibrefs='#bibrefs' inlist='#bibunit'/></ltx:cite>",
4005  properties => sub {
4006    (bibrefs => CleanBibKey($_[1]),
4007      bibunit => LookupValue('CITE_UNIT')); });
4008#======================================================================
4009# C.11.4 Splitting the input
4010#======================================================================
4011Let('\@@input', '\input');    # Save TeX's version.
4012                              # LaTeX's \input is a bit different...
4013DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input');
4014DefPrimitive('\@iinput {}', sub { Input(Expand($_[1])); });
4015DefMacro('\@input{}',  '\IfFileExists{#1}{\@@input\@filef@und}{\typeout{No file #1.}}');
4016DefMacro('\@input@{}', '\InputIfFileExists{#1}{}{\typeout{No file #1.}}');
4017
4018DefMacro('\quote@name{}',          '"\quote@@name#1\@gobble""');
4019DefMacro('\quote@@name{} Match:"', '#1\quote@@name');
4020DefMacro('\unquote@name{}',        '\quote@@name#1\@gobble"');
4021
4022# Note that even excluded files SHOULD have the effects of their inclusion
4023# simulated by having read the corresponding aux file;
4024# But we're not bothering with that.
4025DefPrimitive('\include{}', sub {
4026    my ($stomach, $path) = @_;
4027    $path = ToString($path);
4028    my $table = LookupValue('including@only');
4029    if (!$table || $$table{$path}) {
4030      Input($path); }
4031    return; });
4032
4033# [note, this will load name.tex, if it exists, else name]
4034DefPrimitive('\includeonly{}', sub {
4035    my ($stomach, $paths) = @_;
4036    $paths = ToString($paths);
4037    my $table = LookupValue('including@only');
4038    AssignValue('including@only', $table = {}, 'global') unless $table;
4039    map { $$table{$_} = 1 } map { /^\s*(.*?)\s*$/ && $1; } split(/,/, $paths);
4040    return; });
4041
4042# NOTE: In the long run, we want to SAVE the contents and associate them with the given file name
4043#  AND, arrange so that when a file is read, we'll use the contents!
4044DefConstructorI(T_CS("\\begin{filecontents}"), "Semiverbatim",
4045  '',
4046  reversion   => '',
4047  afterDigest => [sub {
4048      my ($stomach, $whatsit) = @_;
4049      my $filename = ToString($whatsit->getArg(1));
4050      my @lines    = ();
4051      my $gullet   = $stomach->getGullet;
4052      my $line;
4053      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents}')) {
4054        push(@lines, $line); }
4055      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
4056      NoteLog("Cached filecontents for $filename (" . scalar(@lines) . " lines)"); }]);
4057DefConstructorI(T_CS("\\begin{filecontents*}"), "Semiverbatim",
4058  '',
4059  reversion   => '',
4060  afterDigest => [sub {
4061      my ($stomach, $whatsit) = @_;
4062      my $filename = ToString($whatsit->getArg(1));
4063      my @lines    = ();
4064      my $gullet   = $stomach->getGullet;
4065      my $line;
4066      while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents*}')) {
4067        push(@lines, $line); }
4068      AssignValue($filename . '_contents' => join("\n", @lines), 'global');
4069      NoteLog("Cached filecontents* for $filename (" . scalar(@lines) . " lines)"); }]);
4070DefMacro('\endfilecontents', '');
4071DefPrimitive('\listfiles', undef);
4072
4073#======================================================================
4074# C.11.5 Index and Glossary
4075#======================================================================
4076
4077# ---- The index commands
4078# Format of Index entries:
4079#   \index{entry!entry}  gives multilevel index
4080# Each entry:
4081#   foo@bar  sorts on "foo" but prints "bar"
4082# The entries can end with a |expression:
4083#   \index{...|(}    this page starts a range for foo
4084#   \index{...|)}    this page ends a range
4085#           The last two aren't handled in any particular way.
4086#           We _could_ mark start & end, and then the postprocessor would
4087#           need to fill in all likely links... ???
4088#   \index{...|see{key}}  cross reference.
4089#   \index{...|seealso{key}}  cross reference.
4090#   \index{...|textbf}  (etc) causes the number to be printed in bold!
4091#
4092# I guess the formula is that
4093#    \index{foo|whatever{pi}{pa}{po}}  => \whatever{pi}{pa}{po}{page}
4094# How should this get interpreted??
4095our %index_style = (textbf => 'bold', bf => 'bold', textrm => '', rm => '',
4096  textit => 'italic', it => 'italic', emph => 'italic');    # What else?
4097    # A bit screwy, but....
4098    # Expand \index{a!b!...} into \@index{\@indexphrase{a}\@indexphrase{b}...}
4099
4100sub process_index_phrases {
4101  my ($gullet, $phrases, $inlist) = @_;
4102  my @expansion = ();
4103  # Split the text into phrases, separated by "!"
4104  my @tokens = $phrases->unlist;
4105  return                      unless @tokens;
4106  push(@tokens, T_OTHER('!')) unless $tokens[-1]->getString eq '!';    # Add terminal !
4107  my @phrase = ();
4108  my @sortas = ();
4109  my $style;
4110  while (@tokens) {
4111    my $tok    = shift(@tokens);
4112    my $string = $tok->getString;
4113    if ($string eq '"') {
4114      push(@phrase, shift(@tokens)); }
4115    elsif ($string eq '@') {
4116      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
4117      @sortas = @phrase; @phrase = (); }
4118    elsif (($string eq '!') || ($string eq '|')) {
4119      while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); }    # Trim
4120      push(@expansion, T_CS('\@indexphrase'),
4121        (@sortas ? (T_OTHER('['), @sortas, T_OTHER(']')) : ()),
4122        T_BEGIN, @phrase, T_END)
4123        if @phrase;
4124      @phrase = (); @sortas = ();
4125      if ($string eq '|') {
4126        pop(@tokens);    # Remove the extra "!" stopbit.
4127        my $extra = ToString(Tokens(@tokens));
4128        if    ($extra =~ /^see\s*{/)      { push(@expansion, T_CS('\@indexsee'), @tokens[3 .. $#tokens]); }
4129        elsif ($extra =~ /^seealso\s*\{/) { push(@expansion, T_CS('\@indexseealso'), @tokens[7 .. $#tokens]); }
4130        elsif ($extra eq '(')             { $style = 'rangestart'; }                     # ?
4131        elsif ($extra eq ')')             { $style = 'rangeend'; }                       # ?
4132        else                              { $style = $index_style{$extra} || $extra; }
4133        @tokens = (); } }
4134    elsif (!@phrase && ($string =~ /\s/)) { }    # Skip leading whitespace
4135    else {
4136      push(@phrase, $tok); } }
4137  @expansion = (T_CS('\@index'),
4138    ($style || $inlist ? (T_OTHER('['), ($style ? T_OTHER($style) : ()), T_OTHER(']')) : ()),
4139    ($inlist ? (T_OTHER('['), T_OTHER($inlist), T_OTHER(']')) : ()),
4140    T_BEGIN, @expansion, T_END);
4141  return (T_BEGIN, T_CS('\normalfont'), @expansion, T_END); }
4142
4143DefMacro('\index{}', \&process_index_phrases);
4144
4145Tag('ltx:indexphrase',    afterClose => \&addIndexPhraseKey);
4146Tag('ltx:glossaryphrase', afterClose => \&addIndexPhraseKey);
4147### ltx:indexsee does NOT get a key (at this stage)!
4148
4149sub addIndexPhraseKey {
4150  my ($document, $node) = @_;
4151  if (!$node->getAttribute('key')) {
4152    $node->setAttribute(key => CleanIndexKey($node->textContent)); }
4153  return; }
4154
4155DefConstructor('\@index[][]{}', "^<ltx:indexmark style='#1' inlist='#2'>#3</ltx:indexmark>",
4156  mode => 'text', reversion => '', sizer => 0);
4157
4158DefConstructor('\@indexphrase[]{}',
4159  "<ltx:indexphrase key='#key' _standalone_font='true'>#2</ltx:indexphrase>",
4160  properties => { key => sub { CleanIndexKey($_[1]); } });
4161DefConstructor('\@indexsee{}',
4162  "<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
4163  properties => { name => sub { DigestIf('\seename') } });
4164
4165DefConstructor('\@indexseealso{}',
4166  "<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
4167  properties => { name => sub { DigestIf('\alsoname') } });
4168
4169DefConstructor('\glossary{}',
4170  "<ltx:glossaryphrase role='glossary' key='#key'>#1</ltx:glossaryphrase>",
4171  properties => { key => sub { CleanIndexKey($_[1]); } },
4172  sizer      => 0);
4173
4174#======================================================================
4175# This converts an indexphrase node into a sortable string.
4176# Seems the XML nodes are the best place to handle it (rather than Boxes),
4177# although some of the special cases (see, @, may end up tricky)
4178sub indexify {
4179  my ($node, $document) = @_;
4180  my $type = $node->nodeType;
4181  if ($type == XML_TEXT_NODE) {
4182    my $string = $node->textContent;
4183    $string =~ s/\W//g;    # to be safe (if perhaps non-unique?)
4184    $string =~ s/\s//g;    # Or remove entirely? Eventually worry about many=>1 mapping???
4185    return $string; }
4186  elsif ($type == XML_ELEMENT_NODE) {
4187    if ($document->getModel->getNodeQName($node) eq 'ltx:Math') {
4188      return indexify_tex($node->getAttribute('tex')); }
4189    else {
4190      return join('', map { indexify($_, $document) } $node->childNodes); } }
4191  elsif ($type == XML_DOCUMENT_FRAG_NODE) {
4192    return join('', map { indexify($_, $document) } content_nodes($node)); }
4193  else {
4194    return ""; } }
4195
4196# Try to clean up a TeX string into something
4197# Could walk the math tree and handle XMDual specially, but need to xref args.
4198# But also we'd have unicode showing up, which we'd like to latinize...
4199sub indexify_tex {
4200  my ($string) = @_;
4201  $string =~ s/(\\\@|\\,|\\:|\\;|\\!|\\ |\\\/|)//g;
4202  $string =~ s/(\\mathrm|\\mathit|\\mathbf|\\mathsf|\\mathtt|\\mathcal|\\mathscr|\\mbox|\\rm|\\it|\\bf|\\tt|\\small|\\tiny)//g;
4203  $string =~ s/\\left\b//g; $string =~ s/\\right\b//g;
4204  $string =~ s/(\\|\{|\})//g;
4205  $string =~ s/\W//g;           # to be safe (if perhaps non-unique?)
4206  $string =~ s/\s//g;           # Or remove entirely? Eventually worry about many=>1 mapping???
4207  return $string; }
4208
4209# ---- Creating the index itself
4210
4211AssignValue(INDEXLEVEL => 0);
4212
4213Tag('ltx:indexentry', autoClose => 1);
4214
4215sub closeIndexPhrase {
4216  my ($document) = @_;
4217  if ($document->isCloseable('ltx:indexphrase')) {
4218    $document->closeElement('ltx:indexphrase'); }
4219  return; }
4220
4221sub doIndexItem {
4222  my ($document, $level) = @_;
4223  $document->closeElement('ltx:indexrefs') if $document->isCloseable('ltx:indexrefs');
4224  closeIndexPhrase($document);
4225  my $l = LookupValue('INDEXLEVEL');
4226  while ($l < $level) {
4227    $document->openElement('ltx:indexlist'); $l++; }
4228  while ($l > $level) {
4229    $document->closeElement('ltx:indexlist'); $l--; }
4230  AssignValue(INDEXLEVEL => $l);
4231  if ($level) {
4232    $document->openElement('ltx:indexentry');
4233    $document->openElement('ltx:indexphrase'); }
4234  return; }
4235
4236DefConstructorI('\index@dotfill', undef, sub {
4237    my ($document) = @_;
4238    closeIndexPhrase($document);
4239    $document->openElement('ltx:indexrefs'); });
4240DefConstructorI('\index@item',       undef, sub { doIndexItem($_[0], 1); });
4241DefConstructorI('\index@subitem',    undef, sub { doIndexItem($_[0], 2); });
4242DefConstructorI('\index@subsubitem', undef, sub { doIndexItem($_[0], 3); });
4243DefConstructorI('\index@done',       undef, sub { doIndexItem($_[0], 0); });
4244
4245DefMacroI('\indexname', undef, 'Index');
4246DefEnvironment('{theindex}',
4247  "<ltx:index xml:id='#id'>"
4248    . "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
4249    . "#body"
4250    . "</ltx:index>",
4251  beforeDigest => sub {
4252    Let('\item',       '\index@item');
4253    Let('\subitem',    '\index@subitem');
4254    Let('\subsubitem', '\index@subsubitem');
4255    Let('\dotfill',    '\index@dotfill'); },
4256  beforeDigestEnd  => sub { Digest(T_CS('\index@done')); },
4257  afterDigestBegin => sub {
4258    my $docid = ToString(Expand(T_CS('\thedocument@ID')));
4259    my $title = DigestIf('\indexname');
4260    noteBackmatterElement($_[1], 'ltx:index');
4261    $_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'),
4262      title     => $title,
4263      titlefont => $title->getFont); },
4264  beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });
4265
4266DefPrimitiveI('\indexspace',   undef, undef);
4267DefPrimitiveI('\makeindex',    undef, undef);
4268DefPrimitiveI('\makeglossary', undef, undef);
4269
4270#======================================================================
4271# C.11.6 Terminal Input and Output
4272#======================================================================
4273
4274DefPrimitive('\typeout{}', sub {
4275    my ($stomach, $stuff) = @_;
4276    Note(ToString(Expand($stuff)));
4277    return; });
4278
4279DefPrimitive('\typein[]{}', undef);
4280
4281#**********************************************************************
4282# C.12 Line and Page Breaking
4283#**********************************************************************
4284
4285#======================================================================
4286# C.12.1 Line Breaking
4287#======================================================================
4288DefPrimitive('\linebreak[]',   undef);
4289DefPrimitive('\nolinebreak[]', undef);
4290DefPrimitiveI('\-', undef, undef);    # We don't do hyphenation.
4291                                      # \hyphenation in TeX.pool
4292
4293DefPrimitiveI('\sloppy', undef, undef);
4294DefPrimitiveI('\fussy',  undef, undef);
4295# sloppypar can be used as an environment, or by itself.
4296DefMacro('\sloppypar',    '\par\sloppy');
4297DefMacro('\endsloppypar', '\par');
4298DefMacroI('\nobreakdashes', undef, T_OTHER('-'));
4299
4300DefMacro('\showhyphens{}', '#1');     # ?
4301    #======================================================================
4302    # C.12.2 Page Breaking
4303    #======================================================================
4304DefMacro('\pagebreak[]', '\vadjust{\clearpage}');
4305DefPrimitive('\nopagebreak[]', undef);
4306DefPrimitiveI('\columnbreak', undef, undef);    # latex? or multicol?
4307DefPrimitive('\enlargethispage OptionalMatch:* {}', undef);
4308
4309DefMacroI('\clearpage',       undef, '\LTX@newpage');
4310DefMacroI('\cleardoublepage', undef, '\LTX@newpage');
4311DefPrimitiveI('\samepage', undef, undef);
4312
4313#**********************************************************************
4314# C.13 Lengths, Spaces and Boxes
4315#**********************************************************************
4316
4317#####
4318#####
4319#  Complete to here
4320#  [except for NOTE'd entries, of course]
4321#####
4322#####
4323
4324#======================================================================
4325# C.13.1 Length
4326#======================================================================
4327# \fill
4328DefMacro('\stretch{}', '0pt plus #1fill\relax');
4329
4330DefPrimitive('\newlength DefToken', sub {
4331    my ($stomach, $cs) = @_;
4332    DefRegisterI($cs, undef, Glue(0)); });
4333DefPrimitive('\setlength {Variable}{Dimension}', sub {
4334    my ($stomach, $variable, $length) = @_;
4335    my ($defn, @params) = @$variable;
4336    if (ref $defn) {
4337      $defn->setValue($length, @params); }
4338    return; });
4339DefPrimitive('\addtolength {Variable}{Dimension}', sub {
4340    my ($stomach, $variable, $length) = @_;
4341    my ($defn, @params) = @$variable;
4342    if (ref $defn) {
4343      my $oldlength = $defn->valueOf(@params);
4344      $defn->setValue($defn->valueOf(@params)->add($length), @params); }
4345    return; });
4346
4347DefMacro('\@settodim{}{}{}',
4348  '\setbox\@tempboxa\hbox{{#3}}#2#1\@tempboxa\setbox\@tempboxa\box\voidb@x');
4349DefMacroI('\settoheight', undef, '\@settodim\ht');
4350DefMacroI('\settodepth',  undef, '\@settodim\dp');
4351DefMacroI('\settowidth',  undef, '\@settodim\wd');
4352DefMacro('\@settopoint{}', '\divide#1\p@\multiply#1\p@');
4353
4354# Assuming noone tries to get clever with figuring out the allocation of
4355# numbers, these become simple DefRegister's
4356DefPrimitive('\newcount DefToken',  sub { DefRegisterI($_[1], undef, Number(0)); });
4357DefPrimitive('\newdimen DefToken',  sub { DefRegisterI($_[1], undef, Dimension(0)); });
4358DefPrimitive('\newskip DefToken',   sub { DefRegisterI($_[1], undef, Glue(0)); });
4359DefPrimitive('\newmuskip DefToken', sub { DefRegisterI($_[1], undef, MuGlue(0)); });
4360DefPrimitive('\newtoks DefToken',   sub { DefRegisterI($_[1], undef, Tokens()); });
4361
4362DefRegister('\fill', Glue(0, '1fill'));
4363
4364#======================================================================
4365# C.13.2 Space
4366#======================================================================
4367DefPrimitive('\hspace  OptionalMatch:* {Dimension}', sub {
4368    my ($stomach, $star, $length) = @_;
4369    my $s = DimensionToSpaces($length);
4370    return unless defined $s;
4371    Box($s, undef, undef, Invocation(T_CS('\hskip'), $length),
4372      width => $length, isSpace => 1); });
4373
4374DefPrimitive('\vspace OptionalMatch:* {}', undef);
4375DefPrimitive('\addvspace {}',              undef);
4376DefPrimitive('\addpenalty {}',             undef);
4377# \hfill, \vfill
4378
4379#======================================================================
4380# C.13.3 Boxes
4381#======================================================================
4382# Can't really get these?
4383DefMacroI('\height',      undef, '0pt');
4384DefMacroI('\totalheight', undef, '0pt');
4385DefMacroI('\depth',       undef, '0pt');
4386DefMacroI('\width',       undef, '0pt');
4387
4388DefConstructor('\mbox {}',
4389  "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text', bounded => 1,
4390  sizer        => '#1',
4391  robust       => 1,
4392  beforeDigest => sub { reenterTextMode(); });
4393
4394our %makebox_alignment = (l => 'left', r => 'right', s => 'justified');
4395DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox', robust => 1);
4396DefConstructor('\@makebox[Dimension][]{}',
4397  "<ltx:text ?#width(width='#width') ?#align(align='#align') _noautoclose='1'>#3</ltx:text>",
4398  mode         => 'text', bounded => 1, alias => '\makebox', sizer => '#3',
4399  beforeDigest => sub { reenterTextMode(); },
4400  properties   => sub {
4401    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
4402      ($_[1] ? (width => $_[1]) : ())) });
4403
4404DefRegister('\fboxrule', Dimension('.4pt'));
4405DefRegister('\fboxsep',  Dimension('3pt'));
4406
4407# Peculiar special case!
4408#  These are nominally text mode macros. However, there is a somewhat common idiom:
4409#     $ ... \framebox{$operator$} ... $
4410# in which case the operator gets boxed and really should be treated as a math object.
4411# (and ultimately converted to mml:menclose)
4412# So, we need to switch to text mode, as usual, but FIRST note whether we started in math mode!
4413# Afterwards, if we were in math mode, and the content is math, we'll convert the whole thing
4414# to a framed math object.
4415# Second special issue:
4416#   Although framebox doesn't allow flowed content inside, it is also somewhat common
4417# to put a vbox or some other block construct inside.
4418# Seemingly, the ultimate html gets somewhat tangled (browser bugs?)
4419# At any rate, since we're wrapping with an ltx:text, we'll try to unwrap it,
4420# if the contents are a single child that can handle the framing.
4421
4422DefMacro('\fbox{}',   '\@framebox{#1}',                       robust => 1);
4423DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox', robust => 1);
4424DefConstructor('\@framebox[Dimension][]{}',
4425  "?#mathframe(<ltx:XMArg enclose='box'>#inner</ltx:XMArg>)"
4426    . "(<ltx:text ?#width(width='#width') ?#align(align='#align')"
4427    . " framed='rectangle' framecolor='#framecolor'"
4428    . " _noautoclose='1'>#3</ltx:text>)",
4429  alias        => '\framebox', sizer => '#3',
4430  beforeDigest => sub {
4431    my ($stomach) = @_;
4432    my $wasmath = LookupValue('IN_MATH');
4433    $stomach->beginMode('text');
4434    AssignValue(FRAME_IN_MATH => $wasmath); },
4435  properties => sub {
4436    (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
4437      framecolor => LookupValue('font')->getColor,
4438      ($_[1] ? (width => $_[1]) : ())); },
4439  afterDigest => sub {
4440    my ($stomach, $whatsit) = @_;
4441    my $wasmath = LookupValue('FRAME_IN_MATH');
4442    my $arg     = $whatsit->getArg(3);
4443    $stomach->endMode('text');
4444    if ($wasmath && $arg->isMath) {
4445      $whatsit->setProperties(mathframe => 1, inner => $arg->getBody); }
4446    return; },
4447  afterConstruct => sub {
4448    my ($document, $whatsit) = @_;
4449    my $node = $document->getNode->lastChild;
4450    # If the generated node, has only a single (non space) child
4451    my @c = grep { ($_->nodeType != XML_TEXT_NODE) || ($_->textContent =~ /[^\s\n]/) }
4452      $node->childNodes;
4453    my $model = $document->getModel;
4454    # and that child can have the framed attribute
4455    if ((scalar(@c) == 1)
4456      && $document->canHaveAttribute($model->getNodeQName($c[0]), 'framed')) {
4457      # unwrap, copying the attributes
4458      $document->unwrapNodes($node);
4459      foreach my $k (qw(width align framed)) {
4460        if (my $v = $node->getAttribute($k)) {
4461          $document->setAttribute($c[0], $k => $v); } } } }
4462);
4463
4464DefPrimitive('\newsavebox DefToken', sub {
4465    my $n = LookupValue('allocated_boxes') + 1;
4466    AssignValue(allocated_boxes => $n, 'global');
4467    DefRegisterI($_[1], undef, Number($n));
4468    AssignValue('box' . $n, List()); });
4469
4470RawTeX(<<'EOL');
4471\def\newsavebox#1{\@ifdefinable{#1}{\newbox#1}}
4472\DeclareRobustCommand\savebox[1]{%
4473  \@ifnextchar(%)
4474    {\@savepicbox#1}{\@ifnextchar[{\@savebox#1}{\sbox#1}}}%
4475\DeclareRobustCommand\sbox[2]{\setbox#1\hbox{%
4476  \color@setgroup#2\color@endgroup}}
4477\def\@savebox#1[#2]{%
4478  \@ifnextchar [{\@isavebox#1[#2]}{\@isavebox#1[#2][c]}}
4479\long\def\@isavebox#1[#2][#3]#4{%
4480  \sbox#1{\@imakebox[#2][#3]{#4}}}
4481\def\@savepicbox#1(#2,#3){%
4482  \@ifnextchar[%]
4483    {\@isavepicbox#1(#2,#3)}{\@isavepicbox#1(#2,#3)[]}}
4484\long\def\@isavepicbox#1(#2,#3)[#4]#5{%
4485  \sbox#1{\@imakepicbox(#2,#3)[#4]{#5}}}
4486\def\lrbox#1{%
4487  \edef\reserved@a{%
4488    \endgroup
4489    \setbox#1\hbox{%
4490      \begingroup\aftergroup}%
4491        \def\noexpand\@currenvir{\@currenvir}%
4492        \def\noexpand\@currenvline{\on@line}}%
4493  \reserved@a
4494    \@endpefalse
4495    \color@setgroup
4496      \ignorespaces}
4497\def\endlrbox{\unskip\color@endgroup}
4498\DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax}
4499EOL
4500
4501# A soft sorta \par that only closes an ltx:p, but not ltx:para
4502DefConstructor('\lx@parboxnewline[]', sub {
4503    my ($document, $ignore, %props) = @_;
4504    $document->maybeCloseElement('ltx:p');
4505    return; },
4506  properties => { isBreak => 1 });
4507
4508# NOTE: There are 2 extra arguments (See LaTeX Companion, p.866)
4509# for height and inner-pos.  We're ignoring them, for now, though.
4510DefConstructor('\parbox[] OptionalUndigested OptionalUndigested {Dimension} VBoxContents', sub {
4511    my ($document, $attachment, $b, $c, $width, $body, %props) = @_;
4512    insertBlock($document, $body,
4513      width   => $width,
4514      vattach => $props{vattach},
4515      class   => 'ltx_parbox');
4516    return; },
4517  sizer      => '#5',
4518  properties => sub {
4519    (width => $_[4],
4520      vattach => translateAttachment($_[1]),
4521      height  => $_[2]); },
4522  mode         => 'text', bounded => 1,
4523  robust       => 1,
4524  beforeDigest => sub {
4525    AssignValue('\hsize' => $_[4]);
4526    Let('\\\\', '\lx@parboxnewline'); });
4527DefMacroI('\@parboxrestore', undef, Tokens());
4528
4529DefConditional('\if@minipage');
4530DefEnvironment('{minipage}[][][]{Dimension}', sub {
4531    my ($document, $attachment, $b, $c, $width, %props) = @_;
4532    my $vattach = translateAttachment($attachment);
4533    insertBlock($document, $props{body},
4534      para    => 1,
4535      width   => $width,
4536      vattach => $vattach,
4537      class   => 'ltx_minipage');
4538    return; },
4539  sizer      => '#body',
4540  mode       => 'text',
4541  properties => sub { (
4542      width   => $_[4],
4543      vattach => translateAttachment($_[1])); },
4544  beforeDigest => sub {
4545    Digest(T_CS('\@minipagetrue'));
4546    AssignValue('\hsize' => $_[4]);
4547    # this conflicts (& not needed?) with insertBlock
4548    Let('\\\\', '\lx@parboxnewline'); });
4549
4550DefConstructor('\rule[Dimension]{Dimension}{Dimension}',
4551  "<ltx:rule ?#offset(yoffset='#offset') width='#width' height='#height'/>",
4552  properties => sub { (offset => $_[1], width => $_[2], height => $_[3]) });
4553DefConstructor('\raisebox{Dimension}[Dimension][Dimension]{}',
4554  "<ltx:text yoffset='#1' _noautoclose='1'>#4</ltx:text>",
4555  mode         => 'text', bounded => 1,
4556  beforeDigest => sub { reenterTextMode(); },
4557  sizer        => sub { raisedSizer($_[0]->getArg(4), $_[0]->getArg(1)); });
4558
4559sub raisedSizer {
4560  my ($box, $y) = @_;
4561  my ($w, $h, $d) = $box->getSize;
4562  my $z = Dimension(0);
4563  $h = $h->add($y)->larger($z);
4564  $d = $d->subtract($y)->larger($z);
4565  return ($w, $h, $d); }
4566
4567#**********************************************************************
4568# C.14 Pictures and Color
4569#**********************************************************************
4570
4571# These are stubs for color or xcolor packages
4572Let('\set@color',        '\relax');
4573Let('\color@begingroup', '\relax');
4574Let('\color@endgroup',   '\relax');
4575Let('\color@setgroup',   '\relax');
4576Let('\color@hbox',       '\relax');
4577Let('\color@vbox',       '\relax');
4578Let('\color@endbox',     '\relax');
4579
4580#======================================================================
4581# C.14.1 The picture environment
4582#======================================================================
4583
4584#----------------------------------------------------------------------
4585
4586sub ResolveReader {
4587  my ($type) = @_;
4588  return $type if ref $type eq 'CODE';    # It's already a reader (hopefully).
4589  $type = ToString($type);
4590  if (my $descriptor = LookupMapping('PARAMETER_TYPES', $type)) {
4591    return $$descriptor{reader}; }
4592  # Otherwise, try to find a function named Read<Type>
4593  else {
4594    return LaTeXML::Core::Parameter::checkReaderFunction('Read' . $type); } }
4595
4596# This defines a Pair parameter type,
4597# that reads a parenthesized, comma separated pair of subtype.
4598# By default, the subtype is Float, but you can write Pair:Number, or Pair:Dimension
4599sub ReadPair {
4600  my ($gullet, $itemtype, $xarg, $yarg) = @_;
4601  my $itemreader;
4602  if   (!$itemtype) { $itemreader = \&ReadFloat; }
4603  else              { $itemreader = ResolveReader($itemtype); }
4604  if (!$itemreader) {
4605    Error('misdefined', $itemtype, $gullet, "Can't find reader for Pair items from '$itemtype'");
4606    return Pair(Dimension(0), Dimension(0)); }    # Assume something like this?
4607  $gullet->skipSpaces;
4608  if ($gullet->ifNext(T_OTHER('('))) {
4609    $gullet->readToken; $gullet->skipSpaces;
4610    if ($gullet->ifNext(T_OTHER('!'))) {          # maybe only for pstricks???
4611      Error('unexpected', 'postscript', $gullet,
4612        "Cannot process escape to postscript");
4613      $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
4614      return Pair(Dimension(0), Dimension(0)); }
4615    my $x = &$itemreader($gullet, $xarg);
4616    # This had also recognized ";" as a possible separator; if needed, will need to generalize
4617    # but we want to make readUntil accept only a single match
4618    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(',')); $gullet->skipSpaces;
4619    my $y = &$itemreader($gullet, $yarg);
4620    $gullet->skipSpaces; $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
4621    return Pair($x, $y); }
4622  else {
4623    return; } }
4624
4625sub ptValue {
4626  my ($value) = @_;
4627  return ((defined $value) && (ref $value) ? $value->ptValue : $value); }
4628
4629sub pxValue {
4630  my ($value) = @_;
4631  return ((defined $value) && (ref $value) ? $value->pxValue : $value); }
4632
4633#----------------------------------------------------------------------
4634# Picture parameters.
4635DefRegister('\unitlength', Dimension('1pt'));
4636DefPrimitiveI('\thinlines',  undef, sub { AssignValue('\@wholewidth', Dimension('0.4pt')); });
4637DefPrimitiveI('\thicklines', undef, sub { AssignValue('\@wholewidth', Dimension('0.8pt')); });
4638DefRegister('\@wholewidth' => Dimension('0.4pt'));
4639DefRegister('\@halfwidth'  => Dimension('0.2pt'));
4640DefMacro('\linethickness{}', '\@wholewidth #1\relax');
4641
4642DefPrimitive('\arrowlength{Dimension}', sub { AssignValue('\arrowlength', $_[1]); });
4643
4644#----------------------------------------------------------------------
4645# Picture transformation support
4646sub slopeToPicCoord {
4647  my ($slope, $xlength) = @_;
4648  my ($mx,    $my)      = ($slope->getX, $slope->getY);
4649  my $s = $mx->sign();
4650  $xlength = picScale($xlength);
4651  return Pair($xlength->multiply($s),
4652    $xlength->multiply(($s == 0) ? $my->sign() :
4653        $my->valueOf / $mx->absolute->valueOf)); }
4654
4655# Scale $value by \unitlength
4656sub picScale {
4657  my ($value) = @_;
4658  # Vain attempt at proper type coercion
4659  my $type = ref $value;
4660  my $unit = LookupValue('\unitlength');
4661  if    (!$value) { }
4662  elsif ($type eq 'LaTeXML::Common::Number') {
4663    return $unit->multiply($value); }
4664  elsif ($type eq 'LaTeXML::Common::Float') {
4665    return $unit->multiply($value); }
4666  elsif ($type eq 'LaTeXML::Common::Pair') {
4667    return Pair($unit->multiply($value->getX), $unit->multiply($value->getY)); }
4668  return $value->multiply($unit); }
4669
4670sub picProperties {
4671  my (%props) = @_;
4672  if (($props{stroke} || 'black') ne 'none') {
4673    $props{thick} = ptValue(LookupValue('\@wholewidth')); }
4674  if (my $arrowlength = LookupValue('\arrowlength')) {
4675    $props{arrowlength} = ptValue($arrowlength); }
4676  return %props; }
4677
4678#----------------------------------------------------------------------
4679# the code
4680DefMacroI('\qbeziermax', undef, '500');
4681
4682sub before_picture {
4683  Let('\raisebox', '\pic@raisebox');    # ? needs special treatment within picture
4684  return; }
4685
4686sub after_picture {
4687  return; }
4688
4689# Ugh... Is this safe?  Apparently, picture stuff is allowed w/o a {picture} environment???
4690Tag('ltx:picture', autoOpen => 0.5, autoClose => 1,
4691  afterOpen  => sub { GenerateID(@_, 'pic'); },
4692  afterClose => sub {
4693    my ($document, $node, $whatsit) = @_;
4694    # Make sure we get a size, in case autoOpen'd
4695    if ($whatsit) {
4696      my ($w, $h, $d) = $whatsit->getSize;
4697      $node->setAttribute(width  => $w->pxValue)          unless $node->hasAttribute('width');
4698      $node->setAttribute(height => $h->add($d)->pxValue) unless $node->hasAttribute('height'); } });
4699
4700# Note: Untex should prefix a setting of unitlength!!!
4701# First pair is (width,height)
4702# Second pair is the coordinate of the lower-left corner.
4703# [Note that for SVG the root viewport origin is at the TOP-left corner!
4704#  but that is currently handled in the SVG postprocessing module]
4705DefEnvironment('{picture} Pair OptionalPair',
4706  "<ltx:picture width='#width' height='#height' origin-x='#origin-x' origin-y='#origin-y'"
4707    . " fill='none' stroke='none' unitlength='#unitlength'>"
4708    . "?#transform(<ltx:g transform='#transform'>#body</ltx:g>)(#body)"
4709    . "</ltx:picture>",
4710  mode         => 'text',
4711  beforeDigest => \&before_picture,
4712  properties   => sub {
4713    my ($stomach, $size, $pos) = @_;
4714    picProperties(unitlength => LookupValue('\unitlength'),
4715      width      => $size && picScale($size->getX),
4716      height     => $size && picScale($size->getY),
4717      depth      => Dimension(0),
4718      'origin-x' => $pos && picScale($pos->getX),
4719      'origin-y' => $pos && picScale($pos->getY),
4720      ($pos ? (transform => 'translate(' . picScale($pos->negate)->pxValue . ')') : ())); },
4721  afterDigest => \&after_picture);
4722
4723DefMacroI(T_CS('\Gin@driver'), undef, Tokens());
4724
4725DefMacro('\put Until:){}', '\lx@pic@put#1){#2\relax}');
4726DefConstructor('\lx@pic@put Pair{}',
4727  "<ltx:g transform='#transform'"
4728    . " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'>#2</ltx:g>",
4729  properties => sub {
4730    my ($w, $h, $d) = $_[2]->getSize;
4731    $w = undef if $w && ($w->ptValue == 0);
4732    (transform => 'translate(' . picScale($_[1])->pxValue . ')',
4733      innerwidth => $w, innerheight => $h, innerdepth => $d); },
4734  alias => '\put',
4735  mode  => 'text');
4736
4737DefConstructor('\line Pair:Number {Float}',
4738  "<ltx:line points='#points' stroke='black' stroke-width='#thick'/>",
4739  alias      => '\line',
4740  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
4741DefConstructor('\vector Pair:Number {Float}',
4742  "<ltx:line points='#points' stroke='black' stroke-width='#thick' terminators='->'"
4743    . " arrowlength='#arrowlength'/>",
4744  alias      => '\vector',
4745  properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
4746DefConstructor('\circle OptionalMatch:* {Float}',
4747  "<ltx:circle x='0' y='0' r='#radius' fill='#fill' stroke='#stroke' stroke-width='#thick'/>",
4748  alias      => '\circle',
4749  properties => sub {
4750    my ($stomach, $filled, $dia) = @_;
4751    picProperties(radius => picScale($dia)->multiply(0.5)->pxValue,
4752      ($filled ? 'fill' : 'stroke') => 'black'); });
4753
4754DefConstructor('\oval [Float] Pair []',
4755  "<ltx:rect x='#x' y='#y' width='#width' height='#height' rx='#radius'"
4756    . "  stroke='black' fill='none' part='#3' stroke-width='#thick'/>",
4757  alias      => '\oval',
4758  properties => sub {
4759    my ($stomach, $r, $size, $part) = @_;
4760    $size = picScale($size);
4761    my $halfsize = $size->multiply(0.5);
4762    $r = ($r ? picScale($r) : Dimension('40pt'));
4763    $r = $r->smaller($halfsize->getX->absolute);
4764    $r = $r->smaller($halfsize->getY->absolute);
4765    picProperties(size => $size, radius => Dimension($r->valueOf),
4766      x      => $halfsize->getX->negate, y => $halfsize->getY->negate,
4767      width  => Dimension($size->getX->valueOf),
4768      height => Dimension($size->getY->valueOf),
4769    ); });
4770
4771DefConstructor('\qbezier [Number] Pair Pair Pair',
4772"<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke='black' stroke-width='#thick' />",
4773  alias      => '\qbezier',
4774  properties => sub {
4775    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
4776
4777DefMacro('\bezier Until:(', '\ifx.#1.\lx@pic@bezier{0}(\else\lx@pic@bezier{#1}(\fi');
4778DefConstructor('\lx@pic@bezier {Number} Pair Pair Pair',
4779  "<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
4780  alias      => '\bezier',
4781  properties => sub {
4782    picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
4783
4784# Generic boxing command (frames, dash, etc)
4785DefConstructor('\pic@makebox@ Undigested RequiredKeyVals Pair []{}',
4786  "?#framed(<ltx:rect x='0' y='0' width='#fwidth' height='#fheight'"
4787    . " stroke='black' stroke-width='#thick' fill='none' stroke-dasharray='#dash'/>)()"
4788    . "<ltx:g class='makebox' innerwidth='#width' innerheight='#height' innerdepth='#depth'"
4789    . " transform='translate(#xshift,#yshift)'>#box</ltx:g>",
4790  reversion    => '#1#3[#4]{#5}',
4791  beforeDigest => sub { reenterTextMode(); },
4792  properties   => sub {
4793    my ($stomach, $cs, $kv, $size, $pos, $box) = @_;
4794    my ($w, $h, $d)                            = $box->getSize;
4795    my ($ww, $hh)                              = ($w, $h);
4796    my ($x, $y)                                = (Dimension(0), Dimension(0));
4797    my $ht = ($h ? ($d ? $h->add($d) : $h) : ($d ? $d : Dimension(0)));
4798    if ($size) {    # && (($size->getX->valueOf != 0) || ($size->getY->valueOf != 0))) {
4799      $ww  = picScale($size->getX);
4800      $hh  = picScale($size->getY);
4801      $pos = ($pos ? lc(ToString($pos)) : '');
4802      if    ($pos =~ /l/) { $x = Number(0); }
4803      elsif ($pos =~ /r/) { $x = $ww->subtract($w); }
4804      else                { $x = $ww->subtract($w)->divide(2); }
4805      if ($pos =~ /b/)    { $y = Number(0); }
4806      elsif ($pos =~ /t/) { $y = $hh->subtract($ht); }
4807      else                { $y = $hh->subtract($ht)->divide(2); } }
4808    my $fw = ($ww && $ww->valueOf ? $ww : $w);
4809    my $fh = ($hh && $hh->valueOf ? $hh : $h->add($d));
4810    picProperties(
4811      box    => $box,
4812      width  => $w,                      height  => $h, depth => $d,
4813      fwidth => $fw,                     fheight => $fh,
4814      xshift => $x->pxValue,             yshift  => $y->pxValue,
4815      framed => $kv->getValue('framed'), dash    => $kv->getValue('dash')    # dashed
4816    ); });
4817
4818DefMacro('\pic@makebox',           '\pic@makebox@{\makebox}{}');
4819DefMacro('\pic@framebox',          '\pic@makebox@{\framebox}{framed=true}');
4820DefMacro('\lx@pic@dashbox{Float}', '\pic@makebox@{\dashbox(#1)}{framed=true,dash={#1}}');
4821DefMacro('\dashbox Until:(',       '\ifx.#1.\lx@pic@dashbox{0}(\else\lx@pic@dashbox{#1}(\fi');
4822DefMacro('\frame{}',               '\pic@makebox@{\framebox}{framed=true}(0,0)[bl]{#1}');
4823
4824DefMacro('\pic@savebox DefToken Pair []{}', '\pic@@savebox{#1}{\pic@makebox #2[#3]{#4}}');
4825DefPrimitive('\pic@@savebox DefToken {}', sub {
4826    AssignValue('box' . ToString($_[1]), Digest($_[2])); return; });
4827DefMacro('\@savepicbox', '\pic@savebox');
4828
4829DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}',
4830  "<ltx:g y='#1'>#4</ltx:g>",
4831  alias => '\raisebox');
4832
4833our %alignments = (l => 'left', c => 'center', r => 'right');
4834# Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop)
4835# This should be fairly compact vertically.
4836DefConstructor('\@shortstack@cr',
4837  "</ltx:p><ltx:p>",
4838  properties   => { isBreak => 1 },
4839  reversion    => Tokens(T_CS("\\\\"), T_CR),
4840  beforeDigest => sub { $_[0]->egroup; },
4841  afterDigest  => sub { $_[0]->bgroup; });
4842
4843DefConstructor('\shortstack[]{}  OptionalMatch:* [Dimension]',
4844  "<ltx:inline-block align='#align'><ltx:p>#2</ltx:p></ltx:inline-block>",
4845  bounded      => 1,
4846  sizer        => '#2',
4847  beforeDigest => sub { reenterTextMode();
4848    # then RE-RE-define this one!!!
4849    Let("\\\\", '\@shortstack@cr');
4850    $_[0]->bgroup; },
4851  afterDigest => sub { $_[0]->egroup; },
4852  # Note: does not get layout=vertical, since linebreaks are explicit
4853  properties => { align => sub { ($_[1] ? $alignments{ ToString($_[1]) } : undef); },
4854    vattach => 'top' },    # for size computation
4855  mode => 'text');
4856
4857DefMacro('\multiput Pair Pair {}{}', sub {
4858    my ($gullet, $pos, $d, $nt, $body) = @_;
4859    my ($x, $y, $dx, $dy, $n)
4860      = map { ToString($_) } ($pos->getX, $pos->getY, $d->getX, $d->getY, $nt);
4861    my @exp = ();
4862    for (my $i = 0 ; $i < $n ; $i++) {
4863      push(@exp, T_CS('\put'), T_OTHER('('), Explode($x), T_OTHER(','), Explode($y), T_OTHER(')'),
4864        T_BEGIN, $body->unlist, T_END);
4865      $x += $dx; $y += $dy; }
4866    @exp; });
4867
4868Tag('ltx:picture',
4869  afterOpen => sub {
4870    my ($document, $node, $thing) = @_;
4871    if ($thing) {
4872      $document->setAttribute($node, tex => UnTeX($thing)); } });
4873
4874Tag('ltx:g', afterClose => sub {
4875    my ($document, $node) = @_;
4876    $node->parentNode->removeChild($node) unless $node->hasChildNodes; });
4877
4878# \savebox -- already defined differntly in C.13 above ?
4879
4880#**********************************************************************
4881# C.15 Font Selection
4882#**********************************************************************
4883#======================================================================
4884# C.15.1 Changing the Type Style
4885#======================================================================
4886# Text styles.
4887
4888DefMacroI('\rmdefault',       undef, 'cmr');
4889DefMacroI('\sfdefault',       undef, 'cmss');
4890DefMacroI('\ttdefault',       undef, 'cmtt');
4891DefMacroI('\bfdefault',       undef, 'bx');
4892DefMacroI('\mddefault',       undef, 'm');
4893DefMacroI('\itdefault',       undef, 'it');
4894DefMacroI('\sldefault',       undef, 'sl');
4895DefMacroI('\scdefault',       undef, 'sc');
4896DefMacroI('\updefault',       undef, 'n');
4897DefMacroI('\encodingdefault', undef, 'OT1');
4898DefMacroI('\familydefault',   undef, '\rmdefault');
4899DefMacroI('\seriesdefault',   undef, '\mddefault');
4900DefMacroI('\shapedefault',    undef, '\updefault');
4901
4902Let('\mediumseries', '\mdseries');
4903Let('\normalshape',  '\upshape');
4904
4905# ? DefMacro('\f@encoding','cm');
4906DefMacro('\f@family', 'cmr');
4907DefMacro('\f@series', 'm');
4908DefMacro('\f@shape',  'n');
4909DefMacro('\f@size',   '10');
4910
4911# These do NOT immediately effect the font!
4912DefMacro('\fontfamily{}', '\edef\f@family{#1}');
4913DefMacro('\fontseries{}', '\edef\f@series{#1}');
4914DefMacro('\fontshape{}',  '\edef\f@shape{#1}');
4915
4916# For fonts not allowed in math!!!
4917DefPrimitive('\not@math@alphabet@@ {}', sub {
4918    if ($STATE->lookupValue('IN_MATH')) {
4919      my $c = ToString($_[1]);
4920      Warn('unexpected', $c, $_[0], "Command $c invalid in math mode"); }
4921    return; });
4922
4923# These DO immediately effect the font!
4924DefMacroI('\mdseries', undef, '\not@math@alphabet@@{\mddefault}\fontseries{\mddefault}\selectfont');
4925DefMacroI('\bfseries', undef, '\not@math@alphabet@@{\bfdefault}\fontseries{\bfdefault}\selectfont');
4926
4927DefMacroI('\rmfamily', undef, '\not@math@alphabet@@{\rmdefault}\fontfamily{\rmdefault}\selectfont');
4928DefMacroI('\sffamily', undef, '\not@math@alphabet@@{\sfdefault}\fontfamily{\sfdefault}\selectfont');
4929DefMacroI('\ttfamily', undef, '\not@math@alphabet@@{\ttdefault}\fontfamily{\ttdefault}\selectfont');
4930
4931DefMacroI('\upshape', undef, '\not@math@alphabet@@{\updefault}\fontshape{\updefault}\selectfont');
4932DefMacroI('\itshape', undef, '\not@math@alphabet@@{\itdefault}\fontshape{\itdefault}\selectfont');
4933DefMacroI('\slshape', undef, '\not@math@alphabet@@{\sldefault}\fontshape{\sldefault}\selectfont');
4934DefMacroI('\scshape', undef, '\not@math@alphabet@@{\scdefault}\fontshape{\scdefault}\selectfont');
4935
4936DefMacroI('\normalfont', undef,
4937  '\fontfamily{\rmdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
4938DefMacroI('\verbatim@font', undef,
4939  '\fontfamily{\ttdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
4940
4941Let('\reset@font', '\normalfont');
4942
4943DefPrimitive('\selectfont', sub {
4944    my $family = ToString(Expand(T_CS('\f@family')));
4945    my $series = ToString(Expand(T_CS('\f@series')));
4946    my $shape  = ToString(Expand(T_CS('\f@shape')));
4947    if    (my $sh = LaTeXML::Common::Font::lookupFontFamily($family)) { MergeFont(%$sh); }
4948    elsif (!LookupValue("reported_unrecognized_font_family_$family")) {
4949      AssignValue("reported_unrecognized_font_family_$family", 1, 'global');
4950      Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); }
4951    if    (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); }
4952    elsif (!LookupValue("reported_unrecognized_font_series_$series")) {
4953      AssignValue("reported_unrecognized_font_series_$series", 1, 'global');
4954      Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); }
4955    if    (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); }
4956    elsif (!LookupValue("reported_unrecognized_font_shape_$shape")) {
4957      AssignValue("reported_unrecognized_font_shape_$shape", 1, 'global');
4958      Info('unexpected', $shape, $_[0], "Unrecognized font shape '$shape'."); }
4959    return; });
4960
4961DefMacro('\usefont{}{}{}{}',
4962  '\fontencoding{#1}\fontfamily{#2}\fontseries{#3}\fontshape{#4}\selectfont');
4963
4964# If these series or shapes appear in math, they revert it to roman, medium, upright (?)
4965DefConstructor('\textmd@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4966  bounded      => 1, font => { series => 'medium' }, alias => '\textmd',
4967  beforeDigest => sub { DefMacro('\f@series', 'm'); });
4968DefConstructor('\textbf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4969  bounded      => 1, font => { series => 'bold' }, alias => '\textbf',
4970  beforeDigest => sub { DefMacro('\f@series', 'b'); });
4971DefConstructor('\textrm@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4972  bounded      => 1, font => { family => 'serif' }, alias => '\textrm',
4973  beforeDigest => sub { DefMacro('\f@family', 'cm'); });
4974DefConstructor('\textsf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4975  bounded      => 1, font => { family => 'sansserif' }, alias => '\textsf',
4976  beforeDigest => sub { DefMacro('\f@family', 'cmss'); });
4977DefConstructor('\texttt@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4978  bounded      => 1, font => { family => 'typewriter' }, alias => '\texttt',
4979  beforeDigest => sub { DefMacro('\f@family', 'cmtt'); });
4980
4981DefConstructor('\textup@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4982  bounded      => 1, font => { shape => 'upright' }, alias => '\textup',
4983  beforeDigest => sub { DefMacro('\f@shape', ''); });
4984DefConstructor('\textit@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4985  bounded      => 1, font => { shape => 'italic' }, alias => '\textit',
4986  beforeDigest => sub { DefMacro('\f@shape', 'i'); });
4987DefConstructor('\textsl@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4988  bounded      => 1, font => { shape => 'slanted' }, alias => '\textsl',
4989  beforeDigest => sub { DefMacro('\f@shape', 'sl'); });
4990DefConstructor('\textsc@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4991  bounded      => 1, font => { shape => 'smallcaps' }, alias => '\textsc',
4992  beforeDigest => sub { DefMacro('\f@shape', 'sc'); });
4993DefConstructor('\textnormal@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
4994  bounded => 1, font => { family => 'serif', series => 'medium', shape => 'upright' }, alias => '\textnormal',
4995  beforeDigest => sub { DefMacro('\f@family', 'cmtt');
4996    DefMacro('\f@series', 'm');
4997    DefMacro('\f@shape',  'n'); });
4998# These really should be robust! which is a source of expand timing issues!
4999DefMacro('\textmd{}',     '\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi',       protected => 1);
5000DefMacro('\textbf{}',     '\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi',       protected => 1);
5001DefMacro('\textrm{}',     '\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi',       protected => 1);
5002DefMacro('\textsf{}',     '\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi',       protected => 1);
5003DefMacro('\texttt{}',     '\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi',       protected => 1);
5004DefMacro('\textup{}',     '\ifmmode\textup@math{#1}\else{\upshape #1}\fi',        protected => 1);
5005DefMacro('\textit{}',     '\ifmmode\textit@math{#1}\else{\itshape #1}\fi',        protected => 1);
5006DefMacro('\textsl{}',     '\ifmmode\textsl@math{#1}\else{\slshape #1}\fi',        protected => 1);
5007DefMacro('\textsc{}',     '\ifmmode\textsc@math{#1}\else{\scshape #1}\fi',        protected => 1);
5008DefMacro('\textnormal{}', '\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi', protected => 1);
5009
5010DefPrimitive('\DeclareTextFontCommand{}{}', sub {
5011    my ($stomach, $cmd, $font) = @_;
5012    DefConstructorI($cmd, "{}",
5013      "?#isMath(<ltx:text _noautoclose='1'>#1</ltx:text>)(#1)",
5014      mode         => 'text', bounded => 1,
5015      beforeDigest => sub { Digest($font); (); });
5016    return; });
5017
5018DefPrimitive('\mathversion{}', sub {
5019    my ($stomach, $version) = @_;
5020    $version = ToString($version);
5021    if ($version eq 'bold') {
5022      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 1), 'local'); }
5023    elsif ($version eq 'normal') {
5024      AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 0), 'local'); }
5025    else { Error('unexpected', $version, $stomach, "Unknown math verison '$version'"); } });
5026
5027DefPrimitive('\DeclareOldFontCommand{}{}{}', sub {
5028    my ($stomach, $cmd, $font, $mathcmd) = @_;
5029    DefMacroI($cmd, undef, sub {
5030        return (LookupValue('IN_MATH') ? $mathcmd->unlist : $font->unlist); });
5031    #      Tokens(T_CS('\ifmmode'), $mathcmd->unlist, T_CS('\else'), $font->unlist, T_CS('\fi')));
5032    return; });
5033
5034DefMacro('\newfont{}{}', '\font#1=#2\relax');
5035
5036Let('\normalcolor', '\relax');
5037
5038#======================================================================
5039# C.15.2 Changing the Type Size
5040#======================================================================
5041# Handled in TeX.pool.ltxml
5042
5043#======================================================================
5044# C.15.3 Special Symbol
5045#======================================================================
5046DefMacro('\symbol{}', '\char#1\relax');
5047
5048# These in LaTeX, but not in the book...
5049DefPrimitiveI('\textdollar',           undef, "\$");
5050DefPrimitiveI('\textemdash',           undef, "\x{2014}");       # EM DASH
5051DefPrimitiveI('\textendash',           undef, "\x{2013}");       # EN DASH
5052DefPrimitiveI('\textexclamdown',       undef, UTF(0xA1));        # INVERTED EXCLAMATION MARK
5053DefPrimitiveI('\textquestiondown',     undef, UTF(0xBF));        # INVERTED QUESTION MARK
5054DefPrimitiveI('\textquotedblleft',     undef, "\x{201C}");       # LEFT DOUBLE QUOTATION MARK
5055DefPrimitiveI('\textquotedblright',    undef, "\x{201D}");       # RIGHT DOUBLE QUOTATION MARK
5056DefPrimitiveI('\textquotedbl',         undef, "\"");             # plain ascii DOUBLE QUOTATION
5057DefPrimitiveI('\textquoteleft',        undef, "\x{2018}");       # LEFT SINGLE QUOTATION MARK
5058DefPrimitiveI('\textquoteright',       undef, "\x{2019}");       # RIGHT SINGLE QUOTATION MARK
5059DefPrimitiveI('\textsterling',         undef, UTF(0xA3));        # POUND SIGN
5060DefPrimitiveI('\textasteriskcentered', undef, "*");
5061DefPrimitiveI('\textbackslash',        undef, UTF(0x5C));        # REVERSE SOLIDUS
5062DefPrimitiveI('\textbar',              undef, "|");
5063DefPrimitiveI('\textbraceleft',        undef, "{");
5064DefPrimitiveI('\textbraceright',       undef, "}");
5065DefPrimitiveI('\textbullet',           undef, "\x{2022}");       # BULLET
5066DefPrimitiveI('\textdaggerdbl',        undef, "\x{2021}");       # DOUBLE DAGGER
5067DefPrimitiveI('\textdagger',           undef, "\x{2020}");       # DAGGER
5068DefPrimitiveI('\textparagraph',        undef, UTF(0xB6));        # PILCROW SIGN
5069DefPrimitiveI('\textsection',          undef, UTF(0xA7));        # SECTION SIGN
5070DefAccent('\textcircled', UTF(0x20DD), UTF(0x25EF));             # Defined in TeX.pool
5071DefPrimitiveI('\textless',                 undef, "<");
5072DefPrimitiveI('\textgreater',              undef, ">");
5073DefPrimitiveI('\textcopyright',            undef, UTF(0xA9));    # COPYRIGHT SIGN
5074DefPrimitiveI('\textasciicircum',          undef, "^");
5075DefPrimitiveI('\textasciitilde',           undef, "~");
5076DefPrimitiveI('\textcompwordmark',         undef, "");           # ???
5077DefPrimitiveI('\textcapitalcompwordmark',  undef, "");           # ???
5078DefPrimitiveI('\textascendercompwordmark', undef, "");           # ???
5079DefPrimitiveI('\textunderscore',           undef, "_");
5080DefPrimitiveI('\textvisiblespace', undef, "\x{2423}"); # SYMBOL FOR SPACE;  Not really the right symbol!
5081DefPrimitiveI('\textellipsis',   undef, "\x{2026}");    # HORIZONTAL ELLIPSIS
5082DefPrimitiveI('\textregistered', undef, UTF(0xAE));     # REGISTERED SIGN
5083DefPrimitiveI('\texttrademark',  undef, "\x{2122}");    # TRADE MARK SIGN
5084DefConstructor('\textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
5085  mode => 'text');
5086# This is something coming from xetex/xelatex ? Why define this way?
5087#DefConstructor('\realsuperscript{}', "<ltx:text yoffset='0.5em' _noautoclose='1'>#1</ltx:text>");
5088DefConstructor('\realsuperscript{}', "<ltx:sup>#1</ltx:sup>",
5089  mode => 'text');
5090DefPrimitiveI('\textordfeminine',  undef, UTF(0xAA));    # FEMININE ORDINAL INDICATOR
5091DefPrimitiveI('\textordmasculine', undef, UTF(0xBA));    # MASCULINE ORDINAL INDICATOR
5092DefPrimitiveI('\SS', undef, 'SS',                        # ?
5093  locked => 1);                                          # cause it shows up in uclist!
5094DefMacroI('\dag',  undef, '\ifmmode{\dagger}\else\textdagger\fi');
5095DefMacroI('\ddag', undef, '\ifmmode{\ddagger}\else\textdaggerdbl\fi');
5096
5097DefConstructor('\sqrtsign Digested',
5098  "<ltx:XMApp><ltx:XMTok meaning='square-root'/><ltx:XMArg>#1</ltx:XMArg></ltx:XMApp>");
5099
5100DefPrimitiveI('\mathparagraph',  undef, UTF(0xB6));
5101DefPrimitiveI('\mathsection',    undef, UTF(0xA7));
5102DefPrimitiveI('\mathdollar',     undef, '$');
5103DefPrimitiveI('\mathsterling',   undef, UTF(0xA3));
5104DefPrimitiveI('\mathunderscore', undef, '_');
5105DefPrimitiveI('\mathellipsis',   undef, "\x{2026}");
5106
5107# Are these glyph "pieces" or use alone?
5108DefMathI('\arrowvert', undef, "|",        role => 'VERTBAR');
5109DefMathI('\Arrowvert', undef, "\x{2225}", role => 'VERTBAR');
5110
5111# The following are glyph "pieces"...
5112DefPrimitiveI('\braceld', undef, "\x{239D}");    #   left brace down part
5113DefPrimitiveI('\bracelu', undef, "\x{239B}");    #   left brace up part
5114DefPrimitiveI('\bracerd', undef, "\x{23A0}");    #   right brace down part
5115DefPrimitiveI('\braceru', undef, "\x{239E}");    #   right brace up part
5116
5117DefMathI('\cdotp', undef, "\x{22C5}", role => 'MULOP');
5118DefMathI('\ldotp', undef, ".",        role => 'MULOP');
5119DefMathI('\intop', undef, "\x{222B}", role => 'INTOP', meaning => 'integral',
5120  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
5121DefMathI('\ointop', undef, "\x{222E}", role => 'INTOP', meaning => 'contour-integral',
5122  scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
5123# WHat are these? They look like superscripted parentheses, or combining accents!
5124# \lhook
5125# \rhook
5126Let('\gets', '\leftarrow');
5127
5128DefPrimitiveI('\lmoustache', undef, "\x{23B0}");
5129DefPrimitiveI('\rmoustache', undef, "\x{23B1}");
5130DefMathI('\mapstochar', undef, "\x{21A6}", role => 'ARROW', meaning => 'maps-to');
5131DefMathI('\owns',       undef, "\x{220B}", role => 'RELOP', meaning => 'contains');
5132
5133# \skew{}{}{} ????
5134
5135# \symbol lookup symbol in font by index?
5136#**********************************************************************
5137# Other stuff
5138#**********************************************************************
5139# Some stuff that got missed in the appendices ?
5140RawTeX(<<'EoTeX');
5141\def\@namedef#1{\expandafter\def\csname #1\endcsname}
5142\def\@nameuse#1{\csname #1\endcsname}
5143\def\@cons#1#2{\begingroup\let\@elt\relax\xdef#1{#1\@elt #2}\endgroup}
5144\def\@car#1#2\@nil{#1}
5145\def\@cdr#1#2\@nil{#2}
5146\def\@carcube#1#2#3#4\@nil{#1#2#3}
5147\def\nfss@text#1{{\mbox{#1}}}
5148\def\@sect#1#2#3#4#5#6[#7]#8{}
5149EoTeX
5150
5151Let('\@begindocumenthook', '\@empty');
5152DefMacroI('\@preamblecmds', undef, Tokens());
5153DefMacro('\@ifdefinable DefToken {}', sub {
5154    my ($gullet, $token, $if) = @_;
5155    if (isDefinable($token)) {
5156      return $if->unlist }
5157    else {
5158      my ($slash, @s) = ExplodeText($token->toString);
5159      DefMacroI('\reserved@a', undef, Tokens(@s));
5160      return (T_CS('\@notdefinable')); } });
5161
5162Let('\@@ifdefinable', '\@ifdefinable');
5163
5164DefMacro('\@rc@ifdefinable DefToken {}', sub {
5165    my ($gullet, $token, $if) = @_;
5166    Let('\@ifdefinable', '\@@ifdefinable');
5167    return $if->unlist; });
5168
5169DefMacroI('\@notdefinable', undef, <<'EOL');
5170\@latex@error{%
5171  Command \@backslashchar\reserved@a\space
5172  already defined.
5173  Or name \@backslashchar\@qend... illegal,
5174  see p.192 of the manual}
5175EOL
5176
5177DefMacroI('\@qend',   undef, Tokens(Explode('end')));
5178DefMacroI('\@qrelax', undef, Tokens(Explode('relax')));
5179DefMacroI('\@spaces', undef, '\space\space\space\space');
5180Let('\@sptoken', T_SPACE);
5181
5182DefMacroI('\@uclclist', undef, '\oe\OE\o\O\ae\AE\dh\DH\dj\DJ\l\L\ng\NG\ss\SS\th\TH');
5183
5184RawTeX(<<'EOL');
5185\DeclareRobustCommand{\MakeUppercase}[1]{{%
5186      \def\i{I}\def\j{J}%
5187      \def\reserved@a##1##2{\let##1##2\reserved@a}%
5188      \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
5189      \let\UTF@two@octets@noexpand\@empty
5190      \let\UTF@three@octets@noexpand\@empty
5191      \let\UTF@four@octets@noexpand\@empty
5192      \protected@edef\reserved@a{\uppercase{#1}}%
5193      \reserved@a
5194   }}
5195\DeclareRobustCommand{\MakeLowercase}[1]{{%
5196      \def\reserved@a##1##2{\let##2##1\reserved@a}%
5197      \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
5198      \let\UTF@two@octets@noexpand\@empty
5199      \let\UTF@three@octets@noexpand\@empty
5200      \let\UTF@four@octets@noexpand\@empty
5201      \protected@edef\reserved@a{\lowercase{#1}}%
5202      \reserved@a
5203   }}
5204\protected@edef\MakeUppercase#1{\MakeUppercase{#1}}
5205\protected@edef\MakeLowercase#1{\MakeLowercase{#1}}
5206EOL
5207
5208#======================================================================
5209
5210DefMacroI('\@ehc', undef, "I can't help");
5211
5212DefMacro('\@gobble{}',           Tokens());
5213DefMacro('\@gobbletwo{}{}',      Tokens());
5214DefMacro('\@gobblefour{}{}{}{}', Tokens());
5215DefMacro('\@firstofone{}',       sub { $_[1]; });
5216Let('\@iden', '\@firstofone');
5217DefMacro('\@firstoftwo{}{}',     sub { $_[1]; });
5218DefMacro('\@secondoftwo{}{}',    sub { $_[2]; });
5219DefMacro('\@thirdofthree{}{}{}', sub { $_[3]; });
5220DefMacro('\@expandtwoargs{}{}{}', sub {
5221    ($_[1]->unlist, T_BEGIN, Expand($_[2])->unlist, T_END, T_BEGIN, Expand($_[3])->unlist, T_END); });
5222DefMacro('\@makeother{}', sub {
5223    my $letter = ToString($_[1]); $letter =~ s/^\\//;
5224    AssignCatcode($letter => CC_OTHER, 'local'); });
5225
5226RawTeX(<<'EoTeX');
5227{\catcode`\^^M=13 \gdef\obeycr{\catcode`\^^M13 \def^^M{\\\relax}%
5228    \@gobblecr}%
5229{\catcode`\^^M=13 \gdef\@gobblecr{\@ifnextchar
5230\@gobble\ignorespaces}}%
5231\gdef\restorecr{\catcode`\^^M5 }}
5232EoTeX
5233RawTeX(<<'EoTeX');
5234\begingroup
5235  \catcode`P=12
5236  \catcode`T=12
5237  \lowercase{
5238    \def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
5239  \expandafter\endgroup\x
5240\def\strip@pt{\expandafter\rem@pt\the}
5241\def\strip@prefix#1>{}
5242\def\@sanitize{\@makeother\ \@makeother\\\@makeother\$\@makeother\&%
5243\@makeother\#\@makeother\^\@makeother\_\@makeother\%\@makeother\~}
5244\def \@onelevel@sanitize #1{%
5245  \edef #1{\expandafter\strip@prefix
5246           \meaning #1}%
5247}
5248\def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&%
5249  \do\#\do\^\do\_\do\%\do\~}
5250EoTeX
5251
5252DefMacroI('\nfss@catcodes', undef, <<'EOMacro');
5253    \makeatletter
5254    \catcode`\ 9%
5255     \catcode`\^^I9%
5256     \catcode`\^^M9%
5257     \catcode`\\\z@
5258     \catcode`\{\@ne
5259     \catcode`\}\tw@
5260     \catcode`\#6%
5261     \catcode`\^7%
5262     \catcode`\%14%
5263   \@makeother\<%
5264   \@makeother\>%
5265   \@makeother\*%
5266   \@makeother\.%
5267   \@makeother\-%
5268   \@makeother\/%
5269   \@makeother\[%
5270   \@makeother\]%
5271   \@makeother\`%
5272   \@makeother\'%
5273   \@makeother\"%
5274EOMacro
5275DefMacroI('\ltx@hard@MessageBreak', undef, '^^J');
5276
5277sub make_message {
5278  my ($cmd, @args) = @_;
5279  my $stomach = $STATE->getStomach;
5280  my $lead_arg = ToString(shift(@args)) || '';
5281  $lead_arg =~ s/(?:\\\@?spaces?)+//g;
5282  my $type    = $lead_arg || $cmd;
5283  $stomach->bgroup;
5284  Let('\protect', '\string');
5285  # Note that the arg really should be digested to get to the underlying text,
5286  # but why tempt fate, when we're already making an error message?
5287  my $message = join(" ", map { ToString(Expand($_)) } @args);
5288  $type    =~ s/(?:\\\@?spaces?)+/ /g;
5289  $message =~ s/(?:\\\@?spaces?)+/ /g;
5290  $stomach->egroup;
5291  return ('latex', $type, $stomach, $message); }
5292
5293DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this.
5294DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[1], $_[2], $_[3], $_[4])); });
5295DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[1], $_[2])); });
5296DefPrimitive('\GenericInfo{}{}',    sub { Info(make_message('\GenericInfo',    $_[1], $_[2])); });
5297
5298Let('\MessageBreak', '\relax');
5299RawTeX(<<'EoTeX');
5300\gdef\PackageError#1#2#3{%
5301  \GenericError{%
5302      (#1)\@spaces\@spaces\@spaces\@spaces
5303   }{%
5304      Package #1 Error: #2%
5305   }{%
5306      See the #1 package documentation for explanation.%
5307   }{#3}%
5308}
5309\def\PackageWarning#1#2{%
5310  \GenericWarning{%
5311      (#1)\@spaces\@spaces\@spaces\@spaces
5312   }{%
5313      Package #1 Warning: #2%
5314   }%
5315}
5316\def\PackageWarningNoLine#1#2{%
5317  \PackageWarning{#1}{#2\@gobble}}
5318\def\PackageInfo#1#2{%
5319  \GenericInfo{%
5320      (#1) \@spaces\@spaces\@spaces
5321   }{%
5322      Package #1 Info: #2%
5323   }%
5324}
5325\def\ClassError#1#2#3{%
5326  \GenericError{%
5327      (#1) \space\@spaces\@spaces\@spaces
5328   }{%
5329      Class #1 Error: #2%
5330   }{%
5331      See the #1 class documentation for explanation.%
5332   }{#3}%
5333}
5334\def\ClassWarning#1#2{%
5335  \GenericWarning{%
5336      (#1) \space\@spaces\@spaces\@spaces
5337   }{%
5338      Class #1 Warning: #2%
5339   }%
5340}
5341\def\ClassWarningNoLine#1#2{%
5342  \ClassWarning{#1}{#2\@gobble}}
5343\def\ClassInfo#1#2{%
5344  \GenericInfo{%
5345      (#1) \space\space\@spaces\@spaces
5346   }{%
5347      Class #1 Info: #2%
5348   }%
5349}
5350\def\@latex@error#1#2{%
5351  \GenericError{%
5352      \space\space\space\@spaces\@spaces\@spaces
5353   }{%
5354      LaTeX Error: #1%
5355   }{%
5356      See the LaTeX manual or LaTeX Companion for explanation.%
5357   }{#2}%
5358}
5359\def\@latex@warning#1{%
5360  \GenericWarning{%
5361      \space\space\space\@spaces\@spaces\@spaces
5362   }{%
5363      LaTeX Warning: #1%
5364   }%
5365}
5366\def\@latex@warning@no@line#1{%
5367  \@latex@warning{#1\@gobble}}
5368\def\@latex@info#1{%
5369  \GenericInfo{%
5370      \@spaces\@spaces\@spaces
5371   }{%
5372      LaTeX Info: #1%
5373   }%
5374}
5375\def\@latex@info@no@line#1{%
5376  \@latex@info{#1\@gobble}}
5377EoTeX
5378
5379DefPrimitive('\@setsize{}{}{}{}', undef);
5380DefMacro('\on@line', ' on input line \the\inputlineno');
5381Let('\@warning',  '\@latex@warning');
5382Let('\@@warning', '\@latex@warning@no@line');
5383
5384DefMacro('\G@refundefinedtrue', '');
5385
5386DefMacro('\@nomath{}',
5387  '\relax\ifmmode\@font@warning{Command \noexpand#1invalid in math mode}\fi');
5388DefMacro('\@font@warning{}',
5389  '\GenericWarning{(Font)\@spaces\@spaces\@spaces\space\space}{LaTeX Font Warning: #1}');
5390
5391#======================================================================
5392
5393RawTeX(<<'EOTeX');
5394  \chardef\@xxxii=32
5395  \mathchardef\@Mi=10001
5396  \mathchardef\@Mii=10002
5397  \mathchardef\@Miii=10003
5398  \mathchardef\@Miv=10004
5399  \def\@fontenc@load@list{\@elt{T1,OT1}}
5400EOTeX
5401DefMacroI('\@vpt',    undef, '5');
5402DefMacroI('\@vipt',   undef, '6');
5403DefMacroI('\@viipt',  undef, '7');
5404DefMacroI('\@viiipt', undef, '8');
5405DefMacroI('\@ixpt',   undef, '9');
5406DefMacroI('\@xpt',    undef, '10');
5407DefMacroI('\@xipt',   undef, '10.95');
5408DefMacroI('\@xiipt',  undef, '12');
5409DefMacroI('\@xivpt',  undef, '14.4');
5410DefMacroI('\@xviipt', undef, '17.28');
5411DefMacroI('\@xxpt',   undef, '20.74');
5412DefMacroI('\@xxvpt',  undef, '24.88');
5413
5414DefMacroI('\@tempa',  undef, '');
5415DefMacroI('\@tempb',  undef, '');
5416DefMacroI('\@tempc',  undef, '');
5417DefMacroI('\@gtempa', undef, '');
5418
5419RawTeX(<<'EOTeX');
5420\long\def \loop #1\repeat{%
5421  \def\iterate{#1\relax  % Extra \relax
5422               \expandafter\iterate\fi
5423               }%
5424  \iterate
5425  \let\iterate\relax
5426}
5427\newdimen\@ydim
5428\let\@@hyph=\-
5429\newbox\@arstrutbox
5430\newbox\@begindvibox
5431\newcount\@botnum
5432\newdimen\@botroom
5433\newcount\@chclass
5434\newcount\@chnum
5435\newdimen\@clnht
5436\newdimen\@clnwd
5437\newdimen\@colht
5438\newcount\@colnum
5439\newdimen\@colroom
5440\newbox\@curfield
5441\newbox\@curline
5442\newcount\@currtype
5443\newcount\@curtab
5444\newcount\@curtabmar
5445\newbox\@dashbox
5446\newcount\@dashcnt
5447\newdimen\@dashdim
5448\newcount\@dbltopnum
5449\newdimen\@dbltoproom
5450\let\@dischyph=\-
5451\newcount\@enumdepth
5452\newcount\@floatpenalty
5453\newdimen\@fpmin
5454\newcount \@fpstype
5455\newcount\@highpenalty
5456\newcount\@hightab
5457\newbox\@holdpg
5458\newinsert \@kludgeins
5459\newcount\@lastchclass
5460\newbox\@leftcolumn
5461\newbox\@linechar
5462\newdimen\@linelen
5463\newcount\@lowpenalty
5464\newdimen\@maxdepth
5465\newcount\@medpenalty
5466\newdimen\@mparbottom \@mparbottom\z@
5467\newinsert\@mpfootins
5468\newcount\@mplistdepth
5469\newcount\@multicnt
5470\newcount\@nxttabmar
5471\newbox\@outputbox
5472\newdimen\@pagedp
5473\newdimen\@pageht
5474\newbox\@picbox
5475\newdimen\@picht
5476\newdimen \@reqcolroom
5477\newskip\@rightskip \@rightskip \z@skip
5478\newcount\@savsf
5479\newdimen\@savsk
5480\newcount\@secpenalty
5481\def\@sqrt[#1]{\root #1\of}
5482\newbox\@tabfbox
5483\newcount\@tabpush
5484\newdimen \@textfloatsheight
5485\newdimen\@textmin
5486\newcount\@topnum
5487\newdimen\@toproom
5488\newcount\@xarg
5489\newdimen\@xdim
5490\newcount\@yarg
5491\newdimen\@ydim
5492\newcount\@yyarg
5493\newtoks\every@math@size
5494\newif \if@fcolmade
5495\newdimen\lower@bound
5496\newcount\par@deathcycles
5497\newdimen\upper@bound
5498\newif\if@insert
5499\newif\if@colmade
5500\newif\if@specialpage   \@specialpagefalse
5501\newif\if@firstcolumn   \@firstcolumntrue
5502\newif\if@twocolumn     \@twocolumnfalse
5503\newif\if@twoside       \@twosidefalse
5504\newif\if@reversemargin \@reversemarginfalse
5505\newif\if@mparswitch    \@mparswitchfalse
5506\newif\if@firstamp      \@firstampfalse
5507\newcount\col@number    \@ne
5508\newread\@inputcheck
5509\newwrite\@unused
5510\newwrite\@mainaux
5511\newwrite\@partaux
5512\let\@auxout=\@mainaux
5513\openout\@mainaux\jobname.aux
5514\newcount\@clubpenalty
5515\@clubpenalty \clubpenalty
5516\newif\if@filesw \@fileswtrue
5517\newif\if@partsw \@partswfalse
5518\def\@tempswafalse{\let\if@tempswa\iffalse}
5519\def\@tempswatrue{\let\if@tempswa\iftrue}
5520\let\if@tempswa\iffalse
5521\newcount\@tempcnta
5522\newcount\@tempcntb
5523\newif\if@tempswa
5524\newdimen\@tempdima
5525\newdimen\@tempdimb
5526\newdimen\@tempdimc
5527\newbox\@tempboxa
5528\newskip\@tempskipa
5529\newskip\@tempskipb
5530\newtoks\@temptokena
5531\newskip\@flushglue \@flushglue = 0pt plus 1fil
5532\newif\if@afterindent\@afterindenttrue
5533\newbox\rootbox
5534
5535\newcount\@eqcnt
5536\newcount\@eqpen
5537\newif\if@eqnsw\@eqnswtrue
5538\newskip\@centering
5539\@centering = 0pt plus 1000pt
5540\let\@eqnsel=\relax
5541
5542 \long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
5543      #2\relax}\fi}
5544 \long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum
5545          \else\expandafter\@gobble\fi{#1}}
5546 \long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi}
5547 \long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim
5548         \else\expandafter\@gobble\fi{#1}}
5549 \long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi}
5550 \long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw
5551          \else\@gobbletwo\fi{#1}\fi}
5552\def\@nnil{\@nil}
5553\def\@fornoop#1\@@#2#3{}
5554\long\def\@for#1:=#2\do#3{%
5555  \expandafter\def\expandafter\@fortmp\expandafter{#2}%
5556  \ifx\@fortmp\@empty \else
5557    \expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi}
5558\long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else
5559       #5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi}
5560\long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
5561       \expandafter\@fornoop \else
5562      #4\relax\expandafter\@iforloop\fi#2\@@#3{#4}}
5563\def\@tfor#1:={\@tf@r#1 }
5564\long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else
5565    \@tforloop#2\@nil\@nil\@@#1{#3}\fi}
5566\long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
5567       \expandafter\@fornoop \else
5568      #4\relax\expandafter\@tforloop\fi#2\@@#3{#4}}
5569\long\def\@break@tfor#1\@@#2#3{\fi\fi}
5570\def\remove@to@nnil#1\@nnil{}
5571\def\remove@angles#1>{\set@simple@size@args}
5572\def\remove@star#1*{#1}
5573\def\@defaultunits{\afterassignment\remove@to@nnil}
5574
5575\newif\ifmath@fonts \math@fontstrue
5576\newbox\@labels
5577\newif\if@inlabel \@inlabelfalse
5578\newif\if@newlist   \@newlistfalse
5579\newif\if@noparitem \@noparitemfalse
5580\newif\if@noparlist \@noparlistfalse
5581\newif\if@noitemarg \@noitemargfalse
5582\newif\if@nmbrlist  \@nmbrlistfalse
5583
5584\def\glb@settings{}%
5585EOTeX
5586
5587DefMacroI('\@height',      undef, 'height');
5588DefMacroI('\@width',       undef, 'width');
5589DefMacroI('\@depth',       undef, 'depth');
5590DefMacroI('\@minus',       undef, 'minus');
5591DefMacroI('\@plus',        undef, 'plus');
5592DefMacroI('\hb@xt@',       undef, '\hbox to');
5593DefMacroI('\hmode@bgroup', undef, '\leavevmode\bgroup');
5594
5595DefMacroI('\@backslashchar', undef, T_OTHER('\\'));
5596DefMacroI('\@percentchar',   undef, T_OTHER('%'));
5597DefMacroI('\@charlb',        undef, T_LETTER('{'));
5598DefMacroI('\@charrb',        undef, T_LETTER('}'));
5599#======================================================================
5600
5601DefMacroI('\check@mathfonts', undef, Tokens());
5602DefMacro('\fontsize{}{}', Tokens());
5603# https://tex.stackexchange.com/questions/112492/setfontsize-vs-fontsize#112501
5604DefMacro('\@setfontsize{}{}{}', '\let\@currsize#1');
5605
5606DefMacroI('\@vpt',    undef, T_OTHER('5'));
5607DefMacroI('\@vipt',   undef, T_OTHER('6'));
5608DefMacroI('\@viipt',  undef, T_OTHER('7'));
5609DefMacroI('\@viiipt', undef, T_OTHER('8'));
5610DefMacroI('\@ixpt',   undef, T_OTHER('9'));
5611DefMacro('\@xpt',    '10');
5612DefMacro('\@xipt',   '10.95');
5613DefMacro('\@xiipt',  '12');
5614DefMacro('\@xivpt',  '14.4');
5615DefMacro('\@xviipt', '17.28');
5616DefMacro('\@xxpt',   '20.74');
5617DefMacro('\@xxvpt',  '24.88');
5618DefMacro('\vpt',     '\edef\f@size{\@vpt}\rm');
5619DefMacro('\vipt',    '\edef\f@size{\@vipt}\rm');
5620DefMacro('\viipt',   '\edef\f@size{\@viipt}\rm');
5621DefMacro('\viiipt',  '\edef\f@size{\@viiipt}\rm');
5622DefMacro('\ixpt',    '\edef\f@size{\@ixpt}\rm');
5623DefMacro('\xpt',     '\edef\f@size{\@xpt}\rm');
5624DefMacro('\xipt',    '\edef\f@size{\@xipt}\rm');
5625DefMacro('\xiipt',   '\edef\f@size{\@xiipt}\rm');
5626DefMacro('\xivpt',   '\edef\f@size{\@xivpt}\rm');
5627DefMacro('\xviipt',  '\edef\f@size{\@xviipt}\rm');
5628DefMacro('\xxpt',    '\edef\f@size{\@xxpt}\rm');
5629DefMacro('\xxvpt',   '\edef\f@size{\@xxvpt}\rm');
5630
5631DefMacroI('\defaultscriptratio',       undef, '.7');
5632DefMacroI('\defaultscriptscriptratio', undef, '.5');
5633
5634#======================================================================
5635DefMacroI('\loggingoutput', undef, Tokens());
5636DefMacroI('\loggingall',    undef, Tokens());
5637DefMacroI('\tracingfonts',  undef, Tokens());
5638DefMacroI('\showoverfull',  undef, Tokens());
5639DefMacroI('\showoutput',    undef, Tokens());
5640DefMacro('\wlog{}', Tokens());
5641
5642#======================================================================
5643# Various symbols, accents, etc from Chapter 3 defined in TeX.pool
5644
5645#**********************************************************************
5646# Semi-Undocumented stuff
5647#**********************************************************************
5648DefMacro('\@ifnextchar DefToken {}{}', sub {
5649    my ($gullet, $token, $if, $else) = @_;
5650    my $next = $gullet->readNonSpace;
5651    # NOTE: Not actually substituting, but collapsing ## pairs!!!!
5652    # use \egroup for $next, if we've fallen off end?
5653    ((XEquals($token, (defined $next ? $next : T_END)) ? $if : $else)->unlist,
5654      (defined $next ? ($next) : ())); });
5655Let('\kernel@ifnextchar', '\@ifnextchar');
5656Let('\@ifnext',           '\@ifnextchar');    # ????
5657
5658# Hacky version matches multiple chars! but does NOT expand
5659DefMacro('\@ifnext@n {} {}{}', sub {
5660    my ($gullet, $tokens, $if, $else) = @_;
5661    my @toks = $tokens->unlist;
5662    my @read = ();
5663    while (my $t = $gullet->readToken) {
5664      push(@read, $t);
5665      if ($t->equals($toks[0])) { shift(@toks); }
5666      else                      { last; } }
5667    return Tokens((@toks ? $else->unlist : $if->unlist), @read); });
5668
5669DefMacro('\@ifstar {}{}', sub {
5670    my ($gullet, $if, $else) = @_;
5671    my $next = $gullet->readNonSpace;
5672    if (T_OTHER('*')->equals($next)) {
5673      $if; }
5674    else {
5675      ($else, ($next ? $next : ())); } });
5676
5677DefMacro('\@dblarg {}',    '\kernel@ifnextchar[{#1}{\@xdblarg{#1}}');
5678DefMacro('\@xdblarg {}{}', '#1[{#2}]{#2}');
5679
5680DefMacro('\@testopt{}{}', sub {
5681    my ($gullet, $cmd, $option) = @_;
5682    ($gullet->ifNext(T_OTHER('[')) ? $cmd->unlist
5683      : ($cmd->unlist, T_OTHER('['), $option->unlist, T_OTHER(']'))); });
5684RawTeX(<<'EoTeX');
5685\def\@protected@testopt#1{%%
5686  \ifx\protect\@typeset@protect
5687    \expandafter\@testopt
5688  \else
5689    \@x@protect#1%
5690  \fi}
5691EoTeX
5692
5693Let('\l@ngrel@x', '\relax');    # Never actually used anywhere, but...
5694DefMacro('\@star@or@long{}', '\@ifstar{\let\l@ngrel@x\relax#1}{\let\l@ngrel@x\long#1}');
5695
5696# maybe this is easiest just to punt.
5697RawTeX(<<'EoTeX');
5698\def\in@#1#2{%
5699 \def\in@@##1#1##2##3\in@@{%
5700  \ifx\in@##2\in@false\else\in@true\fi}%
5701 \in@@#2#1\in@\in@@}
5702\newif\ifin@
5703EoTeX
5704
5705DefMacro('\IfFileExists{}{}{}', sub {
5706    my ($gullet, $file, $if, $else) = @_;
5707    my $file_string = ToString(Expand($file));
5708    if (FindFile($file_string)) {
5709      DefMacro('\@filef@und', '"' . $file_string . '" ');
5710      return ($if->unlist); }
5711    else {
5712      return ($else->unlist); } });
5713
5714DefMacro('\InputIfFileExists{}{}{}', sub {
5715    my ($gullet, $file, $if, $else) = @_;
5716    my $file_string = ToString(Expand($file));
5717    if (FindFile($file_string)) {
5718      DefMacro('\@filef@und', '"' . $file_string . '" ');
5719      Input($file_string);
5720      return ($if->unlist); }
5721    else { return ($else->unlist); } });
5722
5723#======================================================================
5724# Hair
5725DefPrimitiveI('\makeatletter', undef, sub { AssignCatcode('@' => CC_LETTER, 'local'); });
5726DefPrimitiveI('\makeatother',  undef, sub { AssignCatcode('@' => CC_OTHER,  'local'); });
5727
5728#**********************************************************************
5729#**********************************************************************
5730# Sundry (is this ams ?)
5731DefPrimitiveI('\textprime', undef, UTF(0xB4));    # ACUTE ACCENT
5732
5733Let('\endgraf', '\par');
5734Let('\endline', '\cr');
5735#**********************************************************************
5736# Should be defined in each (or many) package, but it's not going to
5737# get set correctly or maintained, so...
5738DefMacroI('\fileversion', undef, Tokens());
5739DefMacroI('\filedate',    undef, Tokens());
5740
5741# Ultimately these may be overridden by babel, or otherwise,
5742# various of these are defined in various places by different classes.
5743DefMacroI('\chaptername', undef, 'Chapter');
5744DefMacroI('\partname',    undef, 'Part');
5745# The rest of these are defined in some classes, but not most.
5746#DefMacroI('\sectionname',       undef, 'Section');
5747#DefMacroI('\subsectionname',    undef, 'Subsection');
5748#DefMacroI('\subsubsectionname', undef, 'Subsubsection');
5749#DefMacroI('\paragraphname',     undef, 'Paragraph');
5750#DefMacroI('\subparagraphname',  undef, 'Subparagraph');
5751
5752DefMacroI('\appendixname', undef, 'Appendix');
5753# These aren't defined in LaTeX,
5754# these definitions will give us more meaningful typerefnum's
5755DefMacroI('\sectiontyperefname',       undef, '\lx@sectionsign\lx@ignorehardspaces');
5756DefMacroI('\subsectiontyperefname',    undef, '\lx@sectionsign\lx@ignorehardspaces');
5757DefMacroI('\subsubsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces');
5758DefMacroI('\paragraphtyperefname',     undef, '\lx@paragraphsign\lx@ignorehardspaces');
5759DefMacroI('\subparagraphtyperefname',  undef, '\lx@paragraphsign\lx@ignorehardspaces');
5760
5761#**********************************************************************
5762# Stuff that would appear in the aux file... maybe somebody uses it?
5763DefMacro('\bibdata{}',          Tokens());
5764DefMacro('\bibcite{}{}',        Tokens());
5765DefMacro('\citation{}',         Tokens());
5766DefMacro('\contentsline{}{}{}', Tokens());
5767DefMacro('\newlabel{}{}',       Tokens());
5768
5769DefMacroI('\stop',                 undef, sub { $_[0]->closeMouth(1); return; });
5770DefMacroI('\ignorespacesafterend', undef, Tokens());
5771Let('\mathgroup', '\fam');
5772Let('\mathalpha', '\relax');
5773
5774#\def\mathhexbox#1#2#3{\mbox{$\m@th \mathchar"#1#2#3$}}
5775DefPrimitive('\mathhexbox {}{}{}', sub {
5776    my ($stomach, $a, $b, $c) = @_;
5777    my $n = ToString($a) * 256 + ToString($b) * 16 + ToString($c);
5778    my ($role, $glyph) = decodeMathChar($n);
5779    return Box($glyph, LookupValue('font')->specialize($glyph)); });
5780
5781DefMacroI('\nocorrlist', undef, ',.');
5782Let('\nocorr',    '\relax');
5783Let('\check@icl', '\@empty');
5784Let('\check@icr', '\@empty');
5785DefMacro('\text@command{}',                          '');    # ?
5786DefMacro('\check@nocorr@ Until:\nocorr Until:\@nil', '');
5787RawTeX('\newif\ifmaybe@ic');
5788
5789DefMacroI('\maybe@ic',  undef, '');
5790DefMacroI('\maybe@ic@', undef, '');
5791# \t@st@ic
5792DefMacroI('\sw@slant',    undef, '');
5793DefMacroI('\fix@penalty', undef, '');
5794
5795DefPrimitiveI('\@@end', undef, sub { $_[0]->getGullet->flush; return; });
5796
5797#**********************************************************************
5798# Modern pdflatex seems to come with hyphenation tables predefined
5799# for many languages. We don't need or use hyphenation tables,
5800# but some (versions of some) software (babel), check for
5801# the presence of these \l@<language> macros
5802# But also see \iflanguage (re)defined in babel.def.ltxml
5803RawTeX(<<'EoTeX');
5804\newlanguage\l@english
5805\newlanguage\l@usenglishmax
5806\newlanguage\l@USenglish
5807\newlanguage\l@dumylang
5808\newlanguage\l@nohyphenation
5809\newlanguage\l@arabic
5810\newlanguage\l@basque
5811\newlanguage\l@bulgarian
5812\newlanguage\l@coptic
5813\newlanguage\l@welsh
5814\newlanguage\l@czech
5815\newlanguage\l@slovak
5816\newlanguage\l@german
5817\newlanguage\l@ngerman
5818\newlanguage\l@danish
5819\newlanguage\l@esperanto
5820\newlanguage\l@spanish
5821\newlanguage\l@catalan
5822\newlanguage\l@galician
5823\newlanguage\l@estonian
5824\newlanguage\l@farsi
5825\newlanguage\l@finnish
5826\newlanguage\l@french
5827\newlanguage\l@greek
5828\newlanguage\l@monogreek
5829\newlanguage\l@ancientgreek
5830\newlanguage\l@croatian
5831\newlanguage\l@hungarian
5832\newlanguage\l@interlingua
5833\newlanguage\l@ibycus
5834\newlanguage\l@indonesian
5835\newlanguage\l@icelandic
5836\newlanguage\l@italian
5837\newlanguage\l@latin
5838\newlanguage\l@mongolian
5839\newlanguage\l@dutch
5840\newlanguage\l@norsk
5841\newlanguage\l@polish
5842\newlanguage\l@portuguese
5843\newlanguage\l@pinyin
5844\newlanguage\l@romanian
5845\newlanguage\l@russian
5846\newlanguage\l@slovenian
5847\newlanguage\l@uppersorbian
5848\newlanguage\l@serbian
5849\newlanguage\l@swedish
5850\newlanguage\l@turkish
5851\newlanguage\l@ukenglish
5852\newlanguage\l@ukrainiane
5853EoTeX
5854
5855#**********************************************************************
5856DefPrimitive('\protected@write Number {}{}', sub {
5857    my ($stomach, $port, $prelude, $tokens) = @_;
5858    $port = ToString($port);
5859    $stomach->bgroup;
5860    Let('\thepage', '\relax');
5861    my @stuff = Digest($prelude);
5862    Let('\protect', '\@unexpandable@protect');
5863    if (my $filename = LookupValue('output_file:' . $port)) {
5864      my $handle   = $filename . '_contents';
5865      my $contents = LookupValue($handle);
5866      AssignValue($handle => $contents . UnTeX($tokens) . "\n", 'global'); }
5867    else {
5868      Note(UnTeX($tokens)); }
5869    $stomach->egroup;
5870    return @stuff; });
5871
5872#**********************************************************************
5873# LaTeX now includes fixltx2e by default.
5874# https://www.latex-project.org/news/latex2e-news/ltnews22.pdf
5875
5876# This package allows you to define the font used for
5877# emphasis (\emph) within emphasis.
5878# For latexml, that styling should be left to the ultimate output,
5879# so we just define the command as a dummy.
5880DefMacro('\eminnershape', "");
5881
5882# Undoubtedly not good enough
5883DefMacro('\TextOrMath{}{}', '\ifmmode#2\else#1\fi');
5884
5885DefConstructor('\textsubscript{}', "<ltx:sub>#1</ltx:sub>",
5886  mode => 'text');
5887
5888#**********************************************************************
5889# We need this bit from utf8.def for textcomp
5890DefPrimitive('\DeclareUnicodeCharacter Expanded {}', sub {
5891    my ($stomach, $hexcode, $expansion) = @_;
5892    my $char = $hexcode->toString();
5893    if ($char =~ /^[0-9a-fA-F]+$/) {
5894      if ((my $cp = hex($char)) <= 0x10FFFF) {
5895        $char = UTF($cp);
5896        AssignCatcode($char, CC_ACTIVE);
5897        DefMacroI(T_ACTIVE($char), undef, $expansion); }
5898      else {
5899        Error('unexpected', $char, $stomach,
5900          "$char too large for Unicode. Values between 0 and 10FFFF are permitted."); } }
5901    else {
5902      Error('unexpected', $char, $stomach,
5903        "'$char' is not a hexadecimal number."); } });
5904
5905# LaTeX now includes textcomp by default.
5906RequirePackage('textcomp');
5907
5908#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5909# Expl3 "Experimental LaTeX 3" is no longer Experimental!
5910# It is beginning to be built into latex.ltx
5911# We WILL need a new strategy to keep up; probably based in some form
5912# of pre-read/pre-processed latex.ltx !
5913#
5914# For now, a few macros required by other packages will be included:
5915DefMacroI(T_CS('\hook_gput_code:nnn'), '{}{}{}', '');
5916
5917#**********************************************************************
59181;
5919