LaTeXML
view release on metacpan or search on metacpan
lib/LaTeXML/Package/LaTeX.pool.ltxml view on Meta::CPAN
# -*- mode: Perl -*-
# /=====================================================================\ #
# | LaTeX | #
# | Implementation for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use LaTeXML::Util::Pathname;
use List::Util qw(min max);
#**********************************************************************
# Organized following
# "LaTeX: A Document Preparation System"
# by Leslie Lamport
# 2nd edition
# Addison Wesley, 1994
# Appendix C. Reference Manual
#**********************************************************************
# NOTE: This will be loaded after TeX.pool.ltxml, so it inherits.
#**********************************************************************
LoadPool('TeX');
# Apparently LaTeX does NOT define \magnification,
# and babel uses that to determine whether we're runing LaTeX!!!
Let('\magnification', '\@undefined');
#**********************************************************************
# Basic \documentclass & \documentstyle
#AssignValue('2.09_COMPATIBILITY'=>0);
DefConditionalI('\if@compatibility', undef, sub { LookupValue('2.09_COMPATIBILITY'); });
DefMacro('\@compatibilitytrue', '');
DefMacro('\@compatibilityfalse', '');
Let('\@currentlabel', '\@empty');
DefMacro('\@currdir', './');
# Let's try just starting with this set (since we've loaded LaTeX)
AssignValue(inPreamble => 1); # \begin{document} will clear this.
DefConstructor('\documentclass OptionalSemiverbatim SkipSpaces Semiverbatim []',
"<?latexml class='#2' ?#1(options='#1')?>",
beforeDigest => sub { onlyPreamble('\documentclass'); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $options = $whatsit->getArg(1);
my $class = ToString($whatsit->getArg(2));
$class =~ s/\s+//g;
LoadClass($class,
options => [($options ? split(/\s*,\s*/, ToString($options)) : ())],
after => Tokens(T_CS('\AtBeginDocument'), T_CS('\warn@unusedclassoptions')));
return; });
AssignValue('@unusedoptionlist', []);
DefPrimitiveI('\warn@unusedclassoptions', undef, sub {
if (my @unused = @{ LookupValue('@unusedoptionlist') }) {
Info('unexpected', 'options', $_[0], "Unused global options: " . join(',', @unused));
AssignValue('@unusedoptionlist', []); }
return; });
DefConstructor('\documentstyle OptionalSemiverbatim SkipSpaces Semiverbatim []',
"<?latexml class='#2' ?#1(options='#1') oldstyle='true'?>",
beforeDigest => sub {
Info('unexpected', '\documentstyle', $_[0], "Entering LaTeX 2.09 Compatibility mode");
AssignValue('2.09_COMPATIBILITY' => 1, 'global');
onlyPreamble('\documentstyle'); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $class = ToString($whatsit->getArg(2));
$class =~ s/\s+//g;
my $options = $whatsit->getArg(1);
$options = [($options ? split(/\s*,\s*/, ToString($options)) : ())];
# Watch out; In principle, compatibility mode wants a .sty, not a .cls!!!
# But, we'd prefer .cls, since we'll have better bindings.
# And in fact, nobody's likely to write a binding for a .sty that wants to be a class anyway.
# So, we'll just try for a .cls, punting to OmniBus if needed.
# If we start wanting to read style files by default, we'll still need to handle this
# specially, since class (or sty files pretending to be) cover so much more.
#
# EXCEPTION: of course there is an exception. Older arXiv articles use \documentstyle{aipproc}
# to load a different aipproc.sty than the later evolved aipproc.cls. And of course we need to support that.
if (FindFile($class, type => 'sty', notex => !LookupValue('INCLUDE_CLASSES'))) {
InputDefinitions('article', type => 'cls', noerror => 1,
handleoptions => 1, options => $options);
RequirePackage($class, options => $options, as_class => 1,
after => Tokens(T_CS('\compat@loadpackages'))); }
elsif (FindFile($class, type => 'cls', notex => !LookupValue('INCLUDE_CLASSES'))) {
LoadClass($class, options => $options,
after => Tokens(T_CS('\compat@loadpackages'))); }
else {
InputDefinitions('OmniBus', type => 'cls', noerror => 1,
handleoptions => 1, options => $options,
after => Tokens(T_CS('\compat@loadpackages')));
RequirePackage($class, options => $options, as_class => 1); }
return; });
DefPrimitiveI('\compat@loadpackages', undef, sub {
my $name = ToString(Expand(T_CS('\@currname')));
my $type = ToString(Expand(T_CS('\@currext')));
my $hadmissing = 0;
foreach my $option (@{ LookupValue('@unusedoptionlist') }) {
if (FindFile($option, type => 'sty')) {
RequirePackage($option); }
else {
$hadmissing = 1;
Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } }
# Often, in compatibility mode, the options are used to load what are effectively
# document classes for specific journals, etc that introduce a bunch of new frontmatter!
# To try to recover from this, we'll go ahead & load the OmniBus class.
if ($hadmissing && !LookupValue('OmniBus.cls_loaded')) {
Info('note', 'OmniBus', $_[0], "Loading OmniBus class to attempt to cover missing options");
LoadClass('OmniBus'); }
AssignValue('@unusedoptionlist', []); });
sub onlyPreamble {
my ($cs) = @_;
Error('unexpected', $cs, $STATE->getStomach,
"The current command '" . ToString($cs) . "' can only appear in the preamble")
unless LookupValue("inPreamble");
return; }
#**********************************************************************
# C.1. Commands and Environments.
#**********************************************************************
#======================================================================
# C.1.1 Command Names and Arguments
#======================================================================
# Nothing...
#======================================================================
# C.1.2 Environments
#======================================================================
# In LaTeX, \newenvironment{env} defines \env and \endenv.
# \begin{env} & \end{env} open/close a group, and invoke these.
# In fact, the \env & \endenv don't have to have been created by
# \newenvironment; And in fact \endenv doesn't even have to be defined!
# [it is created by \csname, and equiv to \relax if no previous defn]
# We need to respect these usages here, but we also want to be able
# to define environment constructors that `capture' the body so that
# it can be processed specially, if needed. These are the magic
# '\begin{env}', '\end{env}' control sequences created by DefEnvironment.
AssignValue(current_environment => '', 'global');
DefMacro('\@currenvir', '');
DefPrimitive('\lx@setcurrenvir{}', sub {
DefMacroI('\@currenvir', undef, $_[1]);
AssignValue(current_environment => ToString($_[1])); });
DefMacro('\@checkend{}', '\def\reserved@a{#1}\ifx\reserved@a\@currenvir \else\@badend{#1}\fi}');
Let('\@currenvline', '\@empty');
DefMacro('\begin{}', sub {
my ($gullet, $env) = @_;
my $name = $env && ToString(Expand($env));
my $before = LookupValue('@environment@' . $name . '@beforebegin');
my $after = LookupValue('@environment@' . $name . '@atbegin');
if (IsDefined("\\begin{$name}")) {
(($before ? @$before : ()),
T_CS("\\begin{$name}")); } # Magic cs!
else {
my $token = T_CS("\\$name");
if (!IsDefined($token)) {
my $undef = '{' . $name . '}';
$STATE->noteStatus(undefined => $undef);
Error('undefined', $undef, $gullet, "The environment " . $undef . " is not defined.");
$STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($token, undef,
sub { $_[0]->makeError('undefined', $undef); })); }
(($before ? @$before : ()),
T_CS('\begingroup'),
($after ? @$after : ()),
Invocation(T_CS('\lx@setcurrenvir'), $env),
$token,); } });
# robust => 1); # Not yet working well enough
DefMacro('\end{}', sub {
my ($gullet, $env) = @_;
my $name = $env && ToString(Expand($env));
my $before = LookupValue('@environment@' . $name . '@atend');
my $after = LookupValue('@environment@' . $name . '@afterend');
my $t;
if (IsDefined($t = T_CS("\\end{$name}"))) {
($t,
($after ? @$after : ())); } # Magic CS!
else {
$t = T_CS("\\end$name");
(($before ? @$before : ()),
(IsDefined($t) ? $t : ()),
T_CS('\endgroup'),
($after ? @$after : ())); } });
## robust => 1); # Isn't really robust, but something similar(?)
#======================================================================
# C.1.3 Fragile Commands
#======================================================================
# Because of the way we `move information', revertable and pre-processed,
# I don't think we actually need to do anything ...
# [Course that means we're not _really_ TeX!]
# \protect is already in TeX for obscure reasons...
#Let('\@typeset@protect','\relax');
RawTeX(<<'EOTeX');
\def\@ignorefalse{\global\let\if@ignore\iffalse}
\def\@ignoretrue {\global\let\if@ignore\iftrue}
\def\zap@space#1 #2{%
#1%
\ifx#2\@empty\else\expandafter\zap@space\fi
#2}
\def\@unexpandable@protect{\noexpand\protect\noexpand}
\def\x@protect#1{%
\ifx\protect\@typeset@protect\else
\@x@protect#1%
\fi
}
\def\@x@protect#1\fi#2#3{%
\fi\protect#1%
}
\let\@typeset@protect\relax
\def\set@display@protect{\let\protect\string}
\def\set@typeset@protect{\let\protect\@typeset@protect}
\def\protected@edef{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\edef
}
\def\protected@xdef{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\xdef
}
\def\unrestored@protected@xdef{%
\let\protect\@unexpandable@protect
\xdef
lib/LaTeXML/Package/LaTeX.pool.ltxml view on Meta::CPAN
\newif\ifv@
\newif\ifh@
\newif\ifdt@p
\newif\if@pboxsw
\newif\if@rjfield
\newif\if@firstamp
\newif\if@negarg
\newif\if@ovt
\newif\if@ovb
\newif\if@ovl
\newif\if@ovr
\newdimen\@ovxx
\newdimen\@ovyy
\newdimen\@ovdx
\newdimen\@ovdy
\newdimen\@ovro
\newdimen\@ovri
\newif\if@noskipsec \@noskipsectrue
EOTeX
#======================================================================
# C.1.4 Declarations
#======================================================================
# actual implementation later.
#======================================================================
# C.1.5 Invisible Commands
#======================================================================
# actual implementation later.
#======================================================================
# C.1.6 The \\ Command
#======================================================================
# In math, \\ is just a formatting hint, unless within an array, cases, .. environment.
DefConstructor('\lx@newline OptionalMatch:* [Glue]', sub {
my ($document, $star, $skip, %props) = @_;
if ($STATE->lookupValue('IN_MATH')) {
$document->insertElement('ltx:XMHint', undef, name => 'newline'); }
else {
my $context = $document->getElement;
if (!$context || $context->getAttribute('_vertical_mode_')) { }
elsif (($document->getNodeQName($context) eq 'ltx:p')
&& $context->parentNode->getAttribute('_vertical_mode_')) {
$document->maybeCloseElement('ltx:p'); }
elsif ($document->canContain($context, 'ltx:break')) {
$document->insertElement('ltx:break'); } } },
reversion => Tokens(T_CS("\\\\"), T_CR),
properties => { isBreak => 1 });
Let('\\\\', '\lx@newline');
DefConstructor('\newline',
"?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
reversion => Tokens(T_CS('\newline'), T_CR),
properties => { isBreak => 1 });
Let('\@normalcr', "\\\\");
Let('\@normalnewline', '\newline');
##PushValue(TEXT_MODE_BINDINGS => [
## T_CS("\\\\"), T_CS('\@normalcr'), T_CS('\newline'), T_CS('\@normalnewline')]);
DefMacro('\@nolnerr', '');
DefMacro('\@centercr', '\ifhmode\unskip\else\@nolnerr\fi'
. '\par\@ifstar{\nobreak\@xcentercr}\@xcentercr');
DefMacro('\@xcentercr', '\addvspace{-\parskip}\@ifnextchar[\@icentercr\ignorespaces');
DefMacro('\@icentercr[]', '\vskip #1\ignorespaces');
#**********************************************************************
# C.2. The Structure of the Document
#**********************************************************************
# prepended files (using filecontents environment)
# preamble (starting with \documentclass)
# \begin{document}
# text
# \end{document}
DefMacro('\AtBeginDocument{}', sub {
PushValue('@at@begin@document', $_[1]->unlist); });
DefMacro('\AtEndDocument{}', sub {
PushValue('@at@end@document', $_[1]->unlist); });
# Like "<ltx:document xml:id='#id'>#body</ltx:document>",
# But more complicated due to id, at begin/end document and so forth.
# AND, lower-level so that we can cope with common errors at document end.
DefConstructorI(T_CS('\begin{document}'), undef, sub {
my ($document, %props) = @_;
my $id = ToString($props{id});
if (my $docel = $document->findnode('/ltx:document')) { # Already (auto) created?
$document->setAttribute($docel, 'xml:id' => $id) if $id; }
else {
$document->openElement('ltx:document', 'xml:id' => $id); } },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
$stomach->beginMode('text');
DefMacroI('\@currenvir', undef, 'document');
AssignValue(current_environment => 'document');
$_[1]->setProperty(id => Expand(T_CS('\thedocument@ID')));
my @boxes = ();
if (my $ops = LookupValue('@document@preamble@atend')) {
push(@boxes, $stomach->digest(Tokens(@$ops))); }
if (my $ops = LookupValue('@at@begin@document')) {
push(@boxes, $stomach->digest(Tokens(@$ops))); }
AssignValue(inPreamble => 0); # atbegin is still (sorta) preamble
if (my $ops = LookupValue('@document@preamble@afterend')) {
push(@boxes, $stomach->digest(Tokens(@$ops))); }
$_[1]->setFont(LookupValue('font')); # Start w/ whatever font was last selected.
return @boxes; });
# \document is used directly in e.g. expl3.sty
Let(T_CS('\document'), T_CS('\begin{document}'), 'global');
DefConstructorI(T_CS('\end{document}'), undef, sub {
my ($document) = @_;
$document->closeElement('ltx:document'); },
beforeDigest => sub {
my ($stomach) = @_;
my @boxes = ();
if (my $ops = LookupValue('@at@end@document')) {
push(@boxes, $stomach->digest(Tokens(@$ops))); }
# Should we try to indent the last paragraph? If so, it goes like this:
push(@boxes, $stomach->digest(T_CS('\normal@par')));
# Now we check whether we're down to the last stack frame.
# It is common for unclosed { or even environments
# and we want to at least compress & avoid unnecessary errors & warnings.
my $nframes = $STATE->getFrameDepth;
my $ifstack;
if ($STATE->isValueBound('current_environment', 0)
&& ($STATE->valueInFrame('current_environment', 0) eq 'document')
&& (!($ifstack = $STATE->lookupValue('if_stack')) || !$$ifstack[0])) { } # OK!
else {
my @lines = ();
while ((!$STATE->isValueBound('current_environment', 0)
|| ($STATE->valueInFrame('current_environment', 0) ne 'document'))
&& ($STATE->getFrameDepth > 0)) {
# my $nonbox = $STATE->valueInFrame('groupNonBoxing',0) || 0;
my $tok = $STATE->valueInFrame('groupInitiator', 0) || '<unknown>';
my $loc = $STATE->valueInFrame('groupInitiatorLocator', 0);
$loc = defined $loc ? ToString($loc) : '<unknown>';
my $env = $STATE->isValueBound('current_environment', 0)
&& $STATE->valueInFrame('current_environment', 0);
if ($env) {
push(@lines, "Environment $env opened by " . ToString($tok) . ' ' . $loc); }
else { # but unclosed { is so common and latex itself doesn't Error!
push(@lines, "Group opened by " . ToString($tok) . ' ' . $loc); }
$STATE->popFrame; }
while (($ifstack = $STATE->lookupValue('if_stack')) && $$ifstack[0]) {
my $frame = $STATE->shiftValue('if_stack');
push(@lines, "Conditional " . ToString($$frame{token})
. "started " . ToString($$frame{start})); }
Warn('unexpected', '\end{document}', $stomach,
"Attempt to end document with open groups, environments or conditionals", @lines);
}
$stomach->endMode('text');
# ???? don't push? or what
# if (my $ops = LookupValue('@after@end@document')) {
# push(@boxes, Digest(Tokens(@$ops))); }
$stomach->getGullet->flush;
return @boxes; });
# \enddocument is used directly in e.g. standalone.cls
Let(T_CS('\enddocument'), T_CS('\end{document}'), 'global');
#**********************************************************************
# C.3. Sentences and Paragraphs
#**********************************************************************
#======================================================================
# C.3.1 Making Sentences
#======================================================================
# quotes; should these be handled in DOM/construction?
# dashes: We'll need some sort of Ligature analog, or something like
# Omega's OTP, to combine sequences of "-" into endash, emdash,
# Perhaps it also applies more semantically?
# Such as interpreting certain sequences as section headings,
# or math constructs.
# Spacing; in TeX.pool.ltxml
# Special Characters; in TeX.pool.ltxml
# Logos
# \TeX is in TeX.pool.ltxml
DefConstructor('\LaTeX',
"<ltx:text class='ltx_LaTeX_logo' cssstyle='letter-spacing:-0.2em; margin-right:0.1em'>"
. "L"
. "<ltx:text cssstyle='font-variant:small-caps;' yoffset='0.4ex'>a</ltx:text>"
. "T"
. "<ltx:text cssstyle='font-variant:small-caps;font-size:120%' yoffset='-0.2ex'>e</ltx:text>"
. "X"
. "</ltx:text>",
sizer => sub { (Dimension('2.6em'), Dimension('1.6ex'), Dimension('0.5ex')); });
DefConstructor('\LaTeXe',
"<ltx:text class='ltx_LaTeX_logo' cssstyle='letter-spacing:-0.2em; margin-right:0.1em'>"
. "L"
. "<ltx:text cssstyle='font-variant:small-caps;' yoffset='0.4ex'>a</ltx:text>"
. "T"
. "<ltx:text cssstyle='font-variant:small-caps;font-size:120%' yoffset='-0.2ex'>e</ltx:text>"
. "X"
. "\x{2002}2<ltx:text cssstyle='font-style:italic' yoffset='-0.3ex'>\x{03B5}</ltx:text>"
. "</ltx:text>",
sizer => sub { (Dimension('3.7em'), Dimension('1.6ex'), Dimension('0.5ex')); });
DefMacroI('\fmtname', undef, 'LaTeX2e');
DefMacroI('\fmtversion', undef, '2018/12/01');
DefMacroI('\today', undef, sub { ExplodeText(today()); });
# Use fonts (w/ special flag) to propogate emphasis as a font change,
# but preserve it's "emph"-ness.
# Use ltx:emph in text, but use ltx:text within math, since it may collapse within XMText
# (and minimize html within math, which causes MathJax problems)
DefConstructor('\emph{}', sub {
my ($document, $body, %props) = @_;
my $tag = ($document->findnodes('ancestor::ltx:Math', $document->getNode) ? 'ltx:text' : 'ltx:emph');
$document->openElement($tag, _force_font => 1, %props);
$document->absorb($body);
$document->maybeCloseElement($tag); },
mode => 'text', bounded => 1, font => { emph => 1 }, alias => '\emph',
beforeDigest => sub {
DefMacroI('\f@shape', undef,
(ToString(Tokens(Expand(T_CS('\f@shape')))) eq 'it' ? 'n' : 'it')); });
Tag('ltx:emph', autoClose => 1);
#======================================================================
# C.3.2 Making Paragraphs
#======================================================================
# \noindent, \indent, \par in TeX.pool.ltxml
Let('\@@par', '\par');
DefMacro('\@par', '\let\par\@@par\par');
DefMacro('\@restorepar', '\def\par{\@par}');
# Style parameters
# \parindent, \baselineskip, \parskip alreadin in TeX.pool.ltxml
DefPrimitive('\linespread{}', undef);
# ?
DefMacro('\@noligs', '');
DefConditional('\if@endpe');
DefMacro('\@doendpe', '');
DefMacro('\@bsphack', '\relax'); # what else?
DefMacro('\@esphack', '\relax');
DefMacro('\@Esphack', '\relax');
#======================================================================
# C.3.3 Footnotes
#======================================================================
NewCounter('footnote');
DefMacroI('\thefootnote', undef, '\arabic{footnote}');
NewCounter('mpfootnote');
DefMacroI('\thempfn', undef, '\thefootnote');
DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}');
DefMacroI('\footnotetyperefname', undef, 'footnote');
sub makeNoteTags {
my ($counter, $mark, $tag) = @_;
if ($tag) {
return (
RefStepID($counter),
mark => $mark || $tag,
tags => Digest(T_BEGIN,
T_CS('\def'), T_CS('\the' . $counter), T_BEGIN, Revert($tag), T_END,
T_CS('\def'), T_CS('\typerefnum@' . $counter),
T_BEGIN, T_CS('\\' . $counter . 'typerefname'), T_SPACE, Revert($tag), T_END,
Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
T_END)); }
else {
return (RefStepCounter($counter),
mark => $mark || DigestText(T_CS('\the' . $counter))); } }
DefMacroI('\ext@footnote', undef, undef);
DefConstructor('\lx@note[]{}[]{}',
"^<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
. "#tags"
. "#4"
. "</ltx:note>",
mode => 'text', bounded => 1,
sizer => '#mark',
beforeDigest => sub { reenterTextMode(1); neutralizeFont(); },
properties => sub {
my $type = ToString($_[2]);
(role => $type,
list => DigestText(T_CS('\ext@' . $type)),
makeNoteTags($type, $_[1], $_[3])); },
reversion => '');
DefConstructor('\lx@notemark[]{}[]',
"^<ltx:note role='#role' mark='#mark' xml:id='#id' inlist='#list'>"
. "#tags"
. "</ltx:note>",
mode => 'text',
properties => sub {
my $type = ToString($_[2]);
(role => $type . 'mark',
list => DigestText(T_CS('\ext@' . $type)),
makeNoteTags($type, $_[1], $_[3])); },
reversion => '');
DefConstructor('\lx@notetext[]{}[]{}',
"^<ltx:note role='#role' mark='#mark' xml:id='#id'>#4</ltx:note>",
mode => 'text',
properties => sub {
my $type = ToString($_[2]);
(role => $type . 'text', makeNoteTags($type, $_[1], $_[3] || Digest(T_CS('\the' . $type)))); },
reversion => '');
DefMacro('\footnote', '\lx@note{footnote}', locked => 1);
DefMacro('\footnotemark', '\lx@notemark{footnote}', locked => 1);
DefMacro('\footnotetext', '\lx@notetext{footnote}', locked => 1);
DefMacro('\@footnotetext', '\lx@notetext{footnote}', locked => 1);
# we don't implement the internals directly, so lock them to the latexml variant
Let('\@thefnmark', '\lx@notemark{footnote}', locked => 1);
Tag('ltx:note', afterClose => \&relocateFootnote);
# Find any pairs of footnotemark & footnotetext;
# Move the contents of the text to the mark, removing the text node.
sub relocateFootnote {
my ($document, $node) = @_;
if (($node->getAttribute('role') || '') =~ /^(\w+?)text$/) {
my $notetype = $1; # Eg "footnote", "endnote",...
if (my $mark = $node->getAttribute('mark')) {
foreach my $marknote ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) {
relocateFootnote_aux($document, $notetype, $marknote, $node); } } }
elsif (($node->getAttribute('role') || '') =~ /^(\w+?)mark$/) {
my $notetype = $1; # Eg "footnote", "endnote",...
if (my $mark = $node->getAttribute('mark')) {
foreach my $textnote ($document->findnodes(".//ltx:note[\@role='${notetype}text'][\@mark='$mark']")) {
relocateFootnote_aux($document, $notetype, $node, $textnote); } } }
return; }
# Move the contents of the $textnote to the $marknote, remove $textnote.
sub relocateFootnote_aux {
my ($document, $notetype, $marknote, $textnote) = @_;
$textnote->parentNode->removeChild($textnote);
$document->appendClone($marknote, $textnote->childNodes);
$document->setAttribute($marknote, role => $notetype);
if (my $labels = $textnote->getAttribute('labels')) {
GenerateID($document, $marknote);
$document->setAttribute($marknote, labels => $labels); }
return; }
# Style parameters
DefRegister('\footnotesep' => Dimension(0));
DefPrimitiveI('\footnoterule', undef, undef);
#======================================================================
# C.3.4 Accents and Special Symbols
#======================================================================
# See TeX.pool.ltxml
# See Section 3.3.2 Mathematical Symbols, below
# Should this be here?
DefMath('\mathring{}', "\x{030A}", operator_role => 'OVERACCENT');
#**********************************************************************
# C.4 Sectioning and Table of Contents
#**********************************************************************
#======================================================================
# C.4.1 Sectioning Commands.
#======================================================================
# Note that LaTeX allows fairly arbitrary stuff in \the<ctr>, although
# it can get you in trouble. However, in almost all cases, the result
# is plain text. So, I'm putting refnum as an attribute, where I like it!
# You want something else? Redefine!
# Also, we're adding an id to each, that is parallel to the refnum, but
# valid as an ID. You can tune the representation by defining, eg. \thesection@ID
# A little more messy than seems necessary:
# We don't know whether to step the counter and update \@currentlabel until we see the '*',
# but we have to know it before we digest the title, since \label can be there!
# These are defined in terms of \@startsection so that
# casual user redefinitions work, too.
DefMacroI('\chapter', undef, '\@startsection{chapter}{0}{}{}{}{}', locked => 1);
DefMacroI('\part', undef, '\@startsection{part}{-1}{}{}{}{}'); # not locked since sometimes redefined as partition?
DefMacroI('\section', undef, '\@startsection{section}{1}{}{}{}{}', locked => 1);
DefMacroI('\subsection', undef, '\@startsection{subsection}{2}{}{}{}{}', locked => 1);
DefMacroI('\subsubsection', undef, '\@startsection{subsubsection}{3}{}{}{}{}', locked => 1);
DefMacroI('\paragraph', undef, '\@startsection{paragraph}{4}{}{}{}{}', locked => 1);
DefMacroI('\subparagraph', undef, '\@startsection{subparagraph}{5}{}{}{}{}', locked => 1);
map { Tag("ltx:$_", autoClose => 1) }
qw(part chapter section subsection subsubsection paragraph subparagraph);
DefMacro('\secdef {}{} OptionalMatch:*', sub { ($_[3] ? ($_[2]) : ($_[1])); });
DefMacroI('\@startsection@hook', undef, Tokens());
NewCounter('secnumdepth');
SetCounter('secnumdepth', Number(3));
DefMacro('\@startsection{}{}{}{}{}{} OptionalMatch:*', sub {
my ($gullet, $type, $level, $ignore3, $ignore4, $ignore5, $ignore6, $flag) = @_;
### Aside: Guard mode
# Never start sections in math mode -- this is a good recovery point for broken documents
if ($STATE->lookupValue('IN_MATH')) {
my $mode = $STATE->lookupValue('MODE');
if ($mode =~ /math/) { # double-check we're really in math
$STATE->getStomach->endMode($mode); }
else { # otherwise, just unset the flag?
$STATE->assignValue('IN_MATH' => 0, 'global'); } }
### Main logic
$level = ToString($level);
if ($flag) { # No number, not in TOC
(T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
T_BEGIN, $type->unlist, T_END,
T_BEGIN, T_END); }
elsif ((($level ne '') && ($level > CounterValue('secnumdepth')->valueOf))
|| LookupValue('no_number_sections')) {
# No number, but in TOC
(T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'),
T_BEGIN, $type->unlist, T_END,
T_BEGIN, T_OTHER('toc'), T_END); }
else { # Number and in TOC
(T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'),
T_BEGIN, $type->unlist, T_END,
T_BEGIN, T_OTHER('toc'), T_END); } },
locked => 1);
# Not sure if this is best, but if no explicit \section'ing...
#### Tag('ltx:section',autoOpen=>1);
DefConstructor('\@@numbered@section{} Undigested OptionalUndigested Undigested', sub {
my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
my $id = $props{id};
if (my $asif = $props{backmatterelement}) {
$document->setNode($document->find_insertion_point($asif)); }
# sanitize the type, we can't open arbitrary elements that are not in the schema
$type = ToString($type);
my $tagname = "ltx:" . $type;
if (!$document->getModel->isKnownTag($tagname)) {
# one special case from arXiv: app is short for appendix
if ($tagname eq 'ltx:app') {
$tagname = "ltx:appendix"; }
else {
$tagname = "ltx:section";
Warn("malformed", "ltx:$type", "Tried to open an unknown tag ltx:$type for numbered section"); } }
$document->openElement($tagname,
'xml:id' => CleanID($id),
inlist => ToString($inlist),
(LookupValue('INDENT_FIRST') ? (class => 'ltx_indent_first') : ()));
if (my $tags = $props{tags}) {
$document->absorb($tags); }
$document->insertElement('ltx:title', $props{title});
$document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
properties => sub {
my ($stomach, $type, $inlist, $toctitle, $title) = @_;
MaybePeekLabel();
$type = ToString($type);
my %props = RefStepCounter($type);
my $xtitle = Digest(Invocation(T_CS('\lx@format@title@@'), $type, $title));
my $xtoctitle = Digest(Invocation(T_CS('\lx@format@toctitle@@'), $type, $toctitle || $title));
if ($type eq 'appendix') {
$props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
$props{title} = $xtitle;
$props{toctitle} = $xtoctitle
if !IsEmpty($xtoctitle) && (ToString($xtoctitle) ne ToString($xtitle));
return %props; });
# No tags, at all? Consider...
DefConstructor('\@@unnumbered@section{} Undigested OptionalUndigested Undigested', sub {
my ($document, $type, $inlist, $toctitle, $title, %props) = @_;
my $id = $props{id};
if (my $asif = $props{backmatterelement}) {
$document->setNode($document->find_insertion_point($asif)); }
# sanitize the type, we can't open arbitrary elements that are not in the schema
$type = ToString($type);
my $tagname = "ltx:" . $type;
if (!$document->getModel->isKnownTag($tagname)) {
# one special case from arXiv: app is short for appendix
if ($tagname eq 'ltx:app') {
$tagname = "ltx:appendix"; }
else {
$tagname = "ltx:section";
Warn("malformed", "ltx:$type", "Tried to open an unknown tag ltx:$type for unnumbered section"); } }
$document->openElement($tagname,
'xml:id' => CleanID($id),
inlist => ToString($inlist),
(LookupValue('INDENT_FIRST') ? (class => 'ltx_indentfirst') : ()));
$document->insertElement('ltx:title', $props{title});
$document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; },
properties => sub {
my ($stomach, $type, $inlist, $toctitle, $title) = @_;
MaybePeekLabel();
$type = ToString($type);
my %props = RefStepID($type);
if ($type eq 'appendix') {
$props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); }
$props{title} = Digest(T_CS('\@hidden@bgroup'),
Invocation(T_CS('\lx@format@title@font@@'), $type), $title, T_CS('\@hidden@egroup'));
$props{toctitle} = $toctitle
&& Digest(T_CS('\@hidden@bgroup'), Invocation(T_CS('\lx@format@toctitle@font@@'), $type),
$toctitle, T_CS('\@hidden@egroup'));
return %props; });
#======================================================================
# C.4.2 The Appendix
#======================================================================
# Handled in article,report or book.
DefMacroI('\appendixname', undef, 'Appendix');
DefMacroI('\appendixesname', undef, 'Appendixes');
# Class files should define \@appendix to call this as startAppendices('section') or chapter...
# counter is also the element name!
sub startAppendices { beginAppendices(@_); return; }
sub beginAppendices {
my ($counter) = @_;
Let('\lx@save@theappendex', '\the' . $counter, 'global');
Let('\lx@save@theappendex@ID', '\the' . $counter . '@ID', 'global');
Let('\lx@save@appendix', T_CS('\\' . $counter), 'global');
Let('\lx@save@@appendix', T_CS('\@appendix'), 'global');
AssignMapping('BACKMATTER_ELEMENT', 'ltx:appendix' => 'ltx:' . $counter);
if (LookupDefinition(T_CS('\c@chapter')) # Has \chapter defined
&& ($counter ne 'chapter')) { # And appendices are below the chapter level.
NewCounter($counter, 'chapter', idprefix => 'A');
DefMacroI('\the' . $counter, undef, '\thechapter.\Alph{' . $counter . '}', scope => 'global'); }
else {
NewCounter($counter, 'document', idprefix => 'A');
DefMacroI('\the' . $counter, undef, '\Alph{' . $counter . '}', scope => 'global'); }
AssignMapping('counter_for_type', appendix => $counter);
Let(T_CS('\\' . $counter), T_CS('\@@appendix'), 'global');
Let(T_CS('\@appendix'), T_CS('\relax'), 'global');
return; }
sub endAppendices {
if (my $counter = LookupMapping('BACKMATTER_ELEMENT', 'ltx:appendix')) {
$counter =~ s/^ltx://;
Let('\the' . $counter, '\lx@save@theappendex', 'global');
Let('\the' . $counter . '@ID', '\lx@save@theappendex@ID', 'global');
Let(T_CS('\\' . $counter), '\lx@save@appendix', 'global');
Let(T_CS('\@appendix'), '\lx@save@@appendix', 'global'); }
return; }
DefMacroI('\\@@appendix', undef, '\@startsection{appendix}{0}{}{}{}{}');
#======================================================================
# C.4.3 Table of Contents
#======================================================================
# Insert stubs that will be filled in during post processing.
DefMacroI('\contentsname', undef, 'Contents');
DefConstructorI('\tableofcontents', undef,
"<ltx:TOC lists='toc' scope='global' select='#select'><ltx:title>#name</ltx:title></ltx:TOC>",
properties => sub {
my $td = CounterValue('tocdepth')->valueOf + 1;
my @s = (qw(ltx:part ltx:chapter ltx:section ltx:subsection ltx:subsubsection
ltx:paragraph ltx:subparagraph));
$td = $#s if $#s < $td;
@s = map { $s[$_] } 0 .. $td;
push(@s, (qw(ltx:appendix ltx:index ltx:bibliography))) if @s;
(select => join(' | ', @s),
name => Digest(T_CS('\contentsname'))); });
DefMacroI('\listfigurename', undef, 'List of Figures');
DefConstructorI('\listoffigures', undef,
"<ltx:TOC lists='lof' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
properties => sub { (name => Digest(T_CS('\listfigurename'))); });
DefMacroI('\listtablename', undef, 'List of Tables');
DefConstructorI('\listoftables', undef,
"<ltx:TOC lists='lot' scope='global'><ltx:title>#name</ltx:title></ltx:TOC>",
properties => sub { (name => Digest(T_CS('\listtablename'))); });
DefPrimitive('\numberline{}{}', undef);
DefPrimitive('\addtocontents{}{}', undef);
DefConstructor('\addcontentsline{}{}{}', sub {
my ($document, $inlist, $type, $title) = @_;
# Note that the node can be inlist $inlist.
# Could conceivably want to add $title as toctitle???
if (my $savenode = $document->floatToLabel) {
my $node = $document->getNode;
my $lists = $node->getAttribute('inlist');
$inlist = ToString($inlist);
$document->setAttribute($node, inlist => ($lists ? $lists . ' ' . $inlist : $inlist));
$document->setNode($savenode); } });
#======================================================================
# C.4.4 Style registers
#======================================================================
NewCounter('tocdepth');
#**********************************************************************
# C.5 Classes, Packages and Page Styles
#**********************************************************************
#======================================================================
# C.5.1 Document Class
#======================================================================
# Style Parameters
DefRegister('\bibindent' => Dimension(0));
DefRegister('\columnsep' => Dimension(0));
DefRegister('\columnseprule' => Dimension(0));
DefRegister('\mathindent' => Dimension(0));
#======================================================================
# C.5.2 Packages
#======================================================================
# We'll prefer to load package.pm, but will try package.sty or
# package.tex (the latter being unlikely to work, but....)
# See Stomach.pm for details
# Ignorable packages ??
# pre-defined packages??
DefMacroI('\@clsextension', undef, 'cls');
DefMacroI('\@pkgextension', undef, 'sty');
Let('\@currext', '\@empty');
Let('\@currname', '\@empty');
Let('\@classoptionslist', '\relax');
Let('\@raw@classoptionslist', '\relax');
# Note that there are variables used in Package.pm for these,
# but they are NOT tied to these macros. Do they need to be?
DefMacroI('\@declaredoptions', undef, Tokens());
DefMacroI('\@curroptions', undef, undef);
DefMacroI('\@unusedoptionlist', undef, Tokens());
DefConstructor('\usepackage OptionalSemiverbatim Semiverbatim []',
"<?latexml package='#2' ?#1(options='#1')?>",
beforeDigest => sub { onlyPreamble('\usepackage'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $options = $whatsit->getArg(1);
my $packages = $whatsit->getArg(2);
$options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
for my $pkg (split(',', ToString($packages))) {
$pkg =~ s/\s+//g;
next if !$pkg || $pkg =~ /^%/;
RequirePackage($pkg, options => $options); }
return });
DefConstructor('\RequirePackage OptionalSemiverbatim Semiverbatim []',
"<?latexml package='#2' ?#1(options='#1')?>",
beforeDigest => sub { onlyPreamble('\RequirePackage'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $options = $whatsit->getArg(1);
my $packages = $whatsit->getArg(2);
$options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
for my $pkg (split(',', ToString($packages))) {
$pkg =~ s/\s+//g;
next if !$pkg || $pkg =~ /^%/;
RequirePackage($pkg, options => $options); }
return });
DefConstructor('\LoadClass OptionalSemiverbatim Semiverbatim []',
"<?latexml class='#2' ?#1(options='#1')?>",
beforeDigest => sub { onlyPreamble('\LoadClass'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $options = $whatsit->getArg(1);
my $class = ToString($whatsit->getArg(2));
$class =~ s/\s+//g;
$options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())];
LoadClass($class, options => $options);
return; });
# Related internal macros for package definition
# Internals used in Packages
DefMacro('\NeedsTeXFormat{}[]', Tokens());
DefPrimitive('\ProvidesClass{}[]', sub {
my ($stomach, $class, $version) = @_;
DefMacroI("\\ver@" . ToString($class) . ".cls", undef, $version || Tokens(), scope => 'global');
return; });
# Note that these, like LaTeX, define macros like \var@mypkg.sty to give the version info.
DefMacro('\ProvidesPackage{}[]', sub {
my ($stomach, $package, $version) = @_;
DefMacroI("\\ver@" . ToString($package) . ".sty", undef, $version || Tokens(), scope => 'global');
return; });
DefMacro('\ProvidesFile{}[]', sub {
my ($stomach, $file, $version) = @_;
DefMacroI("\\ver@" . ToString($file), undef, $version || Tokens(), scope => 'global');
return; });
#\DeclareRelease{v4.46}{2020-03-19}{glossaries-2020-03-19.sty}
DefMacro('\DeclareRelease{}{}{}', sub {
my ($stomach, $version, $date, $name) = @_;
# anything useful?
return (); });
#\DeclareCurrentRelease{v4.49}{2021-11-01}
DefMacro('\DeclareCurrentRelease{}{}', sub {
my ($stomach, $version, $date) = @_;
# anything useful?
return (); });
DefMacro('\IncludeInRelease{}{}{} Until:\EndIncludeInRelease', sub {
# anything useful?
return (); });
DefMacro('\NewModuleRelease{}{}{} Until:\EndModuleRelease', sub {
# anything useful?
return (); });
DefPrimitive('\DeclareOption{}{}', sub {
my ($stomach, $option, $code) = @_;
((ToString($option) eq '*') ?
DeclareOption(undef, $code) :
DeclareOption(ToString($option), $code)); });
DefPrimitive('\PassOptionsToPackage{}{}', sub {
my ($stomach, $options, $name) = @_;
$name = ToString($name);
$name =~ s/\s+//g;
PassOptions($name, 'sty', split(/\s*,\s*/, ToString(Expand($options)))); });
DefPrimitive('\PassOptionsToClass{}{}', sub {
my ($stomach, $options, $name) = @_;
$name = ToString($name);
$name =~ s/\s+//g;
PassOptions($name, 'cls', split(/\s*,\s*/, ToString(Expand($options)))); });
DefConstructor('\RequirePackageWithOptions Semiverbatim []',
"<?latexml package='#1'?>",
beforeDigest => sub { onlyPreamble('\RequirePackage'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $package = ToString($whatsit->getArg(1));
$package =~ s/\s+//g;
RequirePackage($package, withoptions => 1);
return; });
DefConstructor('\LoadClassWithOptions Semiverbatim []',
"<?latexml class='#1'?>",
beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $class = ToString($whatsit->getArg(1));
$class =~ s/\s+//g;
LoadClass($class, withoptions => 1);
return; });
DefPrimitive('\@onefilewithoptions {} [][] {}', sub {
my ($stomach, $name, $option1, $option2, $ext) = @_;
InputDefinitions(ToString(Expand($name)), type => ToString(Expand($ext)), options => $option1);
return; });
DefMacroI('\CurrentOption', undef, Tokens());
DefPrimitiveI('\OptionNotUsed', undef, sub {
if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
my $type = ToString(Expand(T_CS('\@currext')));
if ($type eq 'cls') {
PushValue('@unusedoptionlist', $option); } }
return; });
DefPrimitiveI('\@unknownoptionerror', undef, sub {
if (my $option = ToString(Expand(T_CS('\CurrentOption')))) {
my $name = ToString(Expand(T_CS('\@currname')));
my $type = ToString(Expand(T_CS('\@currext')));
Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); }
return; });
DefPrimitive('\ExecuteOptions{}', sub {
my ($gullet, $options) = @_;
ExecuteOptions(split(/\s*,\s*/, ToString(Expand($options)))); });
DefPrimitive('\ProcessOptions OptionalMatch:*', sub {
my ($stomach, $star) = @_;
ProcessOptions(($star ? (inorder => 1) : ())); });
DefMacro('\@options', '\ProcessOptions*');
Let('\@enddocumenthook', '\@empty');
DefMacro('\AtEndOfPackage{}', sub {
my ($gullet, $code) = @_;
my $name = ToString(Expand(T_CS('\@currname')));
my $type = ToString(Expand(T_CS('\@currext')));
AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); });
DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension');
Let('\ltx@ifpackageloaded', '\@ifpackageloaded');
DefMacro('\@ifclassloaded', '\@ifl@aded\@clsextension');
Let('\ltx@ifclassloaded', '\@ifclassloaded');
DefMacro('\@ifl@aded{}{}', sub {
my ($gullet, $ext, $name) = @_;
my $path = ToString(Expand($name)) . '.' . ToString(Expand($ext));
# If EITHER the raw TeX or ltxml version of this file was loaded.
if (LookupValue($path . '_loaded') || LookupValue($path . '.ltxml_loaded')) {
T_CS('\@firstoftwo'); }
else {
T_CS('\@secondoftwo'); } });
DefMacro('\@ifpackagewith', '\@if@ptions\@pkgextension');
DefMacro('\@ifclasswith', '\@if@ptions\@clsextension');
DefMacro('\@if@ptions{}{}{}', sub {
my ($gullet, $ext, $name, $option) = @_;
$option = ToString(Expand($option));
my $values = LookupValue('opt@' . ToString(Expand($name)) . '.' . ToString(Expand($ext)));
if (grep { $option eq $_ } @$values) {
T_CS('\@firstoftwo'); }
else {
T_CS('\@secondoftwo'); } });
DefMacro('\@ptionlist {}', '\@ifundefined{opt@#1}\@empty{\csname opt@#1\endcsname}');
DefMacro('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); });
DefMacro('\addto@hook DefToken {}', '#1\expandafter{\the#1#2}');
# Alas, we're not tracking versions, so we'll assume it's "later" & cross fingers....
DefMacro('\@ifpackagelater{}{}{}{}', '#3');
DefMacro('\@ifclasslater{}{}{}{}', '#3');
Let('\AtEndOfClass', '\AtEndOfPackage');
DefMacro('\AtBeginDvi {}', Tokens());
RawTeX(<<'EoTeX');
\def\@ifl@t@r#1#2{%
\ifnum\expandafter\@parse@version@#1//00\@nil<%
\expandafter\@parse@version@#2//00\@nil
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi}
\def\@parse@version@#1{\@parse@version0#1}
\def\@parse@version#1/#2/#3#4#5\@nil{%
\@parse@version@dash#1-#2-#3#4\@nil
}
\def\@parse@version@dash#1-#2-#3#4#5\@nil{%
\if\relax#2\relax\else#1\fi#2#3#4 }
EoTeX
#======================================================================
# Somewhat related I/O stuff
DefMacro('\filename@parse{}', sub {
my ($gullet, $pathname) = @_;
my ($dir, $name, $ext) = pathname_split(ToString(Expand($pathname)));
$dir .= '/' if $dir;
DefMacroI('\filename@area', undef, Tokens(ExplodeText($dir)));
DefMacroI('\filename@base', undef, Tokens(ExplodeText($name)));
DefMacroI('\filename@ext', undef, ($ext ? Tokens(ExplodeText($ext)) : T_CS('\relax'))); });
DefMacroI('\@filelist', undef, Tokens());
DefMacro('\@addtofilelist{}', sub {
DefMacroI('\@filelist', undef,
Expand(T_CS('\@filelist'), T_OTHER(','), $_[1]->unlist)); });
#======================================================================
# C.5.3 Page Styles
#======================================================================
# Ignored
NewCounter('page');
DefPrimitive('\pagestyle{}', undef);
DefPrimitive('\thispagestyle{}', undef);
DefPrimitive('\markright{}', undef);
DefPrimitive('\markboth{}{}', undef);
DefPrimitiveI('\leftmark', undef, undef);
DefPrimitiveI('\rightmark', undef, undef);
DefPrimitive('\pagenumbering{}', undef);
DefMacro('\@mkboth', '\@gobbletwo'); # default, just in case
DefMacro('\ps@empty',
'\let\@mkboth\@gobbletwo\let\@oddhead\@empty\let\@oddfoot\@empty' .
'\let\@evenhead\@empty\let\@evenfoot\@empty');
DefMacro('\ps@plain', '\let\@mkboth\@gobbletwo' .
'\let\@oddhead\@empty\def\@oddfoot{\reset@font\hfil\thepage' .
'\hfil}\let\@evenhead\@empty\let\@evenfoot\@oddfoot');
Let(T_CS('\@leftmark'), T_CS('\@firstoftwo'));
Let(T_CS('\@rightmark'), T_CS('\@secondoftwo'));
# In normal latex, these should \clearpage; at least we want new paragraph?
# Optional arg is sortof a heading, but w/o any particular styling(?)
DefMacro('\twocolumn[]', '\ifx.#1.\else\par\noindent#1\fi\par');
DefMacro('\onecolumn', '\par');
DefMacro('\@onecolumna', '', locked => 1); # this is trouble in arXiv:cond-mat/0002147
DefMacro('\@twocolumna', '', locked => 1); # this is trouble in arXiv:cond-mat/0002147
# some bits mentioned as "from ltoutput.dtx" in latex.ltx
DefMacro('\@topnewpage{}', '#1');
DefMacro('\@next{}{}{}{}', '\ifx#2\@empty #4\else\expandafter\@xnext #2\@@#1#2#3\fi');
RawTeX('\def\@xnext \@elt #1#2\@@#3#4{\def#3{#1}\gdef#4{#2}}');
Let('\@elt', '\relax'); # ?
DefMacro('\@freelist', ''); # ?
DefMacro('\@currbox', '');
DefMacro('\@toplist', '');
DefMacro('\@botlist', '');
DefMacro('\@midlist', '');
DefMacro('\@currlist', '');
DefMacro('\@deferlist', '');
DefMacro('\@dbltoplist', '');
DefMacro('\@dbldeferlist', '');
DefMacro('\@startcolumn', '');
# Style parameters from Fig. C.3, p.182
DefRegister('\paperheight' => Dimension('11in'));
DefRegister('\paperwidth' => Dimension('8.5in'));
DefRegister('\textheight' => Dimension('7in'));
DefRegister('\textwidth' => Dimension('6in'));
DefRegister('\topmargin' => Dimension(0));
DefRegister('\headheight' => Dimension(0));
DefRegister('\headsep' => Dimension(0));
DefRegister('\footskip' => Dimension(0));
DefRegister('\footheight' => Dimension(0));
DefRegister('\evensidemargin' => Dimension(0));
DefRegister('\oddsidemargin' => Dimension(0));
DefRegister('\marginparwidth' => Dimension(0));
DefRegister('\marginparsep' => Dimension(0));
DefRegister('\columnwidth' => Dimension('6in'));
DefRegister('\linewidth' => Dimension('6in'));
DefRegister('\baselinestretch' => Dimension(0));
#======================================================================
# C.5.4 The Title Page and Abstract
#======================================================================
# See frontmatter support in TeX.ltxml
Let('\@title', '\@empty');
DefMacro('\title[]{}', '\if.#1.\else\def\shorttitle{#1}\@add@frontmatter{ltx:toctitle}{#1}\fi'
. '\def\@title{#2}\@add@frontmatter{ltx:title}{#2}', locked => 1);
DefMacro('\@date', '\@empty');
DefMacro('\date{}',
'\def\@date{#1}'
. '\@add@frontmatter{ltx:date}[role=creation,'
. 'name={\@ifundefined{datename}{}{\datename}}]{#1}');
DefConstructor('\person@thanks{}', "^ <ltx:contact role='thanks'>#1</ltx:contact>",
alias => '\thanks', mode => 'text');
DefConstructor('\@personname{}', "<ltx:personname>#1</ltx:personname>",
beforeDigest => sub { Let('\thanks', '\person@thanks'); },
bounded => 1, mode => 'text');
# Sanitize person names for (obvious) punctuation abuse at start+end
Tag('ltx:personname', afterClose => sub {
my ($document, $node) = @_;
if (my $first = $node->firstChild) {
if ($first->nodeType == XML_TEXT_NODE) {
my $first_text = $first->data;
my $new_text = $first_text;
$new_text =~ s/^[^\w)(}{\]\[]]+//;
if ($first_text ne $new_text) {
$first->setData($new_text); } } }
if (my $last = $node->lastChild) {
if ($last->nodeType == XML_TEXT_NODE) {
my $last_text = $last->data;
my $new_text = $last_text;
$new_text =~ s/[^\w)(}{\]\[]+$//;
if ($last_text ne $new_text) {
$last->setData($new_text); } } }
return; });
DefConstructorI('\and', undef, " and ");
AssignValue(NUMBER_OF_AUTHORS => 0);
DefPrimitive('\lx@count@author', sub {
AssignValue(NUMBER_OF_AUTHORS => LookupValue('NUMBER_OF_AUTHORS') + 1, 'global') });
DefMacro('\lx@author{}',
'\lx@count@author'
. '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}');
DefConstructor('\lx@@@contact{}{}', "^ <ltx:contact role='#1'>#2</ltx:contact>");
DefMacro('\lx@contact{}{}',
'\@add@to@frontmatter{ltx:creator}{\lx@@@contact{#1}{#2}}');
DefMacro('\lx@author@sep', '\qquad');
DefMacro('\lx@author@conj', '\qquad');
DefConstructor('\lx@author@prefix', sub {
my ($document) = @_;
my $node = $document->getElement;
my $nauthors = LookupValue('NUMBER_OF_AUTHORS');
my $i = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') });
if ($i <= 1) { }
elsif ($i == $nauthors) {
$document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); }
else {
$document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); }
});
DefMacro('\@author', '\@empty');
DefMacro('\author[]{}', '\def\@author{#2}\lx@make@authors@anded{#2}', locked => 1);
DefMacro('\lx@make@authors@anded{}', sub { andSplit(T_CS('\lx@author'), $_[1]); });
DefPrimitive('\ltx@authors@oneline', sub {
AssignMapping('DOCUMENT_CLASSES', ltx_authors_1line => 1);
return; });
DefPrimitive('\ltx@authors@multiline', sub {
AssignMapping('DOCUMENT_CLASSES', ltx_authors_multiline => 1);
return; });
DefMacro('\@add@conversion@date', '\@add@frontmatter{ltx:date}[role=creation]{\today}');
# Doesn't produce anything (we're already inserting frontmatter),
# But, it does make the various frontmatter macros into no-ops.
DefMacroI('\maketitle', undef,
'\lx@frontmatterhere'
. '\@startsection@hook'
. '\global\let\thanks\relax'
. '\global\let\maketitle\relax'
. '\global\let\@maketitle\relax'
. '\global\let\@thanks\@empty'
. '\global\let\@author\@empty'
. '\global\let\@date\@empty'
. '\global\let\@title\@empty'
. '\global\let\title\relax'
. '\global\let\author\relax'
. '\global\let\date\relax'
. '\global\let\and\relax', locked => 1);
DefMacro('\@thanks', '\@empty');
# make a throwaway optional argument available for OmniBus use
DefMacro('\thanks[]{}', '\def\@thanks{#2}\lx@make@thanks{#2}');
DefConstructor('\lx@make@thanks{}', "<ltx:note role='thanks'>#1</ltx:note>");
# Abstract SHOULD have been so simple, but seems to be a magnet for abuse.
# For one thing, we'd like to just write
# DefEnvironment('{abstract}','<ltx:abstract>#body</ltx:abstract>');
# However, we don't want to place the <ltx:abstract> environment directly where
# we found it, but we want to add it to frontmatter. This requires capturing the
# recently digested list and storing it in the frontmatter structure.
# The really messy stuff comes from the way authors -- and style designers -- misuse it.
# Basic LaTeX wants it to be an environment WITHIN the document environment,
# and AFTER the \maketitle.
# However, since all it really does is typeset "Abstract" in bold, it allows:
# \abstract stuff...
# without even an \endabstract! We MUST know when the abstract ends, so we've got
# to recognize when we've moved on to other stuff... \sections at the VERY LEAST.
# Additional complications come from certain other classes and styles that
# redefine abstract to take the text as an argument. And some treat it
# like \title, \author, and such, that are expected to appear in the preamble!!
# The treatment below allows an abstract environment in the preamble,
# (even though straight latex doesn't) but does not cover the 1-arg case in preamble!
#
# Probably there are other places (eg in titlepage?) that should force the close??
DefEnvironment('{abstract}', '',
afterDigestBegin => sub {
AssignValue(inPreamble => 0); },
afterDigest => sub {
my $frontmatter = LookupValue('frontmatter');
push(@{ $$frontmatter{'ltx:abstract'} },
['ltx:abstract',
{ name => Digest(Tokens(T_CS('\format@title@abstract'),
T_BEGIN, T_CS('\abstractname'), T_END)) },
@LaTeXML::LIST]);
DefMacroI('\maybe@end@abstract', undef, Tokens(), scope => 'global');
return; },
afterConstruct => sub { insertFrontMatter(@_); }, # HERE if not already done.
locked => 1, mode => 'text');
# If we get a plain \abstract, instead of an environment, look for \abstract{the abstract}
AssignValue('\abstract:locked' => 0); # REDEFINE the above locked definition!
DefMacro('\abstract', sub {
my ($gullet) = @_;
($gullet->ifNext(T_BEGIN)
? (T_CS('\abstract@onearg'))
: (T_CS('\g@addto@macro'), T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract'),
T_CS('\begin{abstract}'))); },
locked => 1);
DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}\let\endabstract\relax');
DefMacroI('\maybe@end@abstract', undef, '\endabstract');
DefMacroI('\abstractname', undef, 'Abstract');
DefMacro('\format@title@abstract{}', '#1');
# Hmm, titlepage is likely to be hairy, low-level markup,
# without even title, author, etc, specified as such!
# Hmm, should this even redefine author, title, etc so that they
# are simply output?
# This is horrible hackery; What we really need, I think, is the
# ability to bind some sort of "Do <this> when we create a text box"...
# ON Second Thought...
# For the time being, ignore titlepage!
# Maybe we could do some of this if there is no title/author
# otherwise defined? Ugh!
#DefEnvironment('{titlepage}','');
# Or perhaps it's better just to ignore the markers?
#DefMacro('\titlepage','');
#DefMacro('\endtitlepage','');
# Or perhaps not....
# There's a title and other stuff in here, but how could we guess?
# Well, there's likely to be a sequence of <p><text font="xx" fontsize="yy">...</text></p>
# Presumably the earlier, larger one is title, rest are authors/affiliations...
# Particularly, if they start with a pseudo superscript or other "marker", they're probably affil!
# For now, we just give an info message
DefEnvironment('{titlepage}', '<ltx:titlepage>#body',
beforeDigest => sub { Let('\centering', '\relax');
AssignValue(frontmatter_deferred => 1, 'global');
AddToMacro(T_CS('\maketitle'), T_CS('\unwind@titlepage'));
DefEnvironmentI('abstract', undef,
'<ltx:abstract>#body</ltx:abstract>');
Info('unexpected', 'titlepage', $_[0],
"When using titlepage, Frontmatter will not be well-structured");
return; },
beforeDigestEnd => sub { Digest(T_CS('\maybe@end@titlepage')); },
afterConstruct => sub { insertFrontMatter($_[0]); },
locked => 1, mode => 'text');
Tag('ltx:titlepage', autoClose => 1);
DefConstructorI('\maybe@end@titlepage', undef, sub {
my ($document) = @_;
$document->maybeCloseElement('ltx:titlepage'); });
DefConstructorI('\unwind@titlepage', undef, sub {
my ($document) = @_;
if (my $titlepage = $document->maybeCloseElement('ltx:titlepage')) {
$document->unwrapNodes($titlepage);
}
});
DefMacro('\sectionmark{}', Tokens());
DefMacro('\subsectionmark{}', Tokens());
DefMacro('\subsubsectionmark{}', Tokens());
DefMacro('\paragraphmark{}', Tokens());
DefMacro('\subparagraphmark{}', Tokens());
DefMacroI('\@oddfoot', undef, Tokens());
DefMacroI('\@oddhed', undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
DefMacroI('\@evenfoot', undef, Tokens());
#**********************************************************************
# C.6 Displayed Paragraphs
#**********************************************************************
DefEnvironment('{center}', sub {
$_[0]->maybeCloseElement('ltx:p'); # this starts a new vertical block
aligningEnvironment('center', 'ltx_centering', @_); }); # aligning will take care of \\\\ "rows"
# HOWEVER, define a plain \center to act like \centering (?)
DefMacroI('\center', undef, '\centering');
DefMacroI('\endcenter', undef, '');
DefEnvironment('{flushleft}', sub {
$_[0]->maybeCloseElement('ltx:p'); # this starts a new vertical block
aligningEnvironment('left', 'ltx_align_left', @_); });
DefEnvironment('{flushright}', sub {
$_[0]->maybeCloseElement('ltx:p'); # this starts a new vertical block
aligningEnvironment('right', 'ltx_align_right', @_); });
# These add an operation to be carried out on the current node & following siblings, when the current group ends.
# These operators will add alignment (class) attributes to each "line" in the current block.
sub setupAligningContext {
my ($document) = @_;
my $node = $document->getElement;
AssignValue(ALIGNING_NODE => [$node, $node->lastChild]) if $node;
return; }
sub applyAligningContext {
my ($document, $align, $class) = @_;
if (my $container = LookupValue('ALIGNING_NODE')) {
my ($node, $previous) = @$container;
my @children = $node->childNodes;
while (my $skip = shift(@children)) {
last if !$previous || $previous->isSameNode($skip); }
while (my $child = shift(@children)) {
setAlignOrClass($document, $child, $align, $class) if $child->nodeType == XML_ELEMENT_NODE; } }
return; }
DefConstructorI('\centering', undef, \&setupAligningContext,
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@centering')); });
DefConstructorI('\raggedright', undef, \&setupAligningContext,
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedright')); });
DefConstructorI('\raggedleft', undef, \&setupAligningContext,
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedleft')); });
DefConstructorI('\@add@centering', undef,
sub { applyAligningContext($_[0], 'center', 'ltx_centering'); });
# Note that \raggedright is essentially align left
DefConstructorI('\@add@raggedright', undef,
sub { applyAligningContext($_[0], undef, 'ltx_align_left'); });
DefConstructorI('\@add@raggedleft', undef,
sub { applyAligningContext($_[0], undef, 'ltx_align_right'); });
DefConstructorI('\@add@flushright', undef,
sub { applyAligningContext($_[0], 'right', 'ltx_align_right'); });
DefConstructorI('\@add@flushleft', undef,
sub { applyAligningContext($_[0], 'left', 'ltx_align_left'); });
#======================================================================-
# C.6.1 Quotations and Verse
#======================================================================-
Let('\@block@cr', '\lx@newline'); # Obsolete, but in case still used
DefEnvironment('{quote}',
'<ltx:quote>#body</ltx:quote>',
mode => 'text');
DefEnvironment('{quotation}',
'<ltx:quote>#body</ltx:quote>',
mode => 'text');
DefEnvironment('{verse}',
'<ltx:quote role="verse">#body</ltx:quote>',
mode => 'text');
#======================================================================
# C.6.2 List-Making environments
#======================================================================
Tag('ltx:item', autoClose => 1, autoOpen => 1);
Tag('ltx:inline-item', autoClose => 1, autoOpen => 1);
# These are for the (not quite legit) case where \item appears outside
# of an itemize, enumerate, etc, environment.
# DefConstructor('\item[]',
# "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
# DefConstructor('\subitem[]',
# "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
# DefConstructor('\subsubitem[]',
# "<ltx:item>?&defined(#1)(<ltx:tags><ltx:tag>#1</ltx:tag></ltx:tags>)");
# Or maybe best just to do \par ?
DefMacro('\item[]', '\par');
DefMacro('\subitem[]', '\par');
DefMacro('\subsubitem[]', '\par');
AssignValue('@itemlevel' => 0, 'global');
AssignValue('enumlevel' => 0, 'global');
AssignValue('@desclevel' => 0, 'global');
# protection against lower-level code...
DefConditional('\if@noitemarg');
DefMacro('\@item', '\item'); # Hopefully no circles...
DefMacro('\@itemlabel', ''); # Maybe needs to be same as \item will be using?
# Prepare for an list (itemize/enumerate/description/etc)
# by determining the right counter (level)
# and binding the right \item ( \$type@item, if $type is defined)
sub beginItemize {
my ($type, $counter, %options) = @_;
# The list-type and level of the *containing* list (if any!)
my $outercounter = LookupValue('itemcounter');
my $outerlevel = $outercounter && (LookupValue($outercounter . 'level') || 0);
$counter = '@item' unless $counter;
my $listlevel = (LookupValue('itemization_level') || 0) + 1; # level for this list overall
my $level = LookupValue($counter . 'level') || 0; # level for lists of specific type
$level++ unless $options{nolevel};
AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep'));
AssignValue('itemization_level' => $listlevel);
AssignValue($counter . 'level' => $level);
AssignValue(itemization_items => 0);
my $listpostfix = ToString(Tokens(roman($listlevel)));
my $postfix = ToString(Tokens(roman($level)));
my $usecounter = ($options{nolevel} ? $counter : $counter . $postfix);
Let('\item' => "\\" . $type . '@item') if defined $type;
Let('\par', '\normal@par'); # In case within odd environment.
DefMacroI('\@listctr', undef, Tokens(Explode($usecounter)));
# Now arrange that this list's id's are relative to the current (outer) item (if any)
# And that the items within this list's id's are relative to this (new) list.
## if(! LookupDefinition(T_CS('\@listcontext'))){
## Let(T_CS('\@listcontext'), T_CS('\@currentlabel')); }
AssignValue(itemcounter => $usecounter);
my $listcounter = '@itemize' . $listpostfix;
if (!LookupDefinition(T_CS('\c@' . $listcounter))) { # Create new list counters as needed
NewCounter($listcounter); } #, $outercounter.ToString(Tokens(roman($outerlevel))),
if ($outercounter) { # Make this list's ID relative to outer list's ID
my $outerusecounter = $outercounter . ToString(Tokens(roman($outerlevel)));
DefMacroI('\the' . $listcounter . '@ID', undef,
'\the' . $outerusecounter . '@ID.I' . '\arabic{' . $listcounter . '}');
# AND reset this list's counter when the outer item is stepped
my $x;
AssignValue("\\cl\@$outerusecounter" =>
Tokens(T_CS($listcounter), (($x = LookupValue('\cl@' . $outerusecounter)) ? $x->unlist : ())),
'global');
}
# format the id of \item's relative to the id of this list
DefMacroI(T_CS('\the' . $usecounter . '@ID'), undef,
Tokens(T_CS('\the' . $listcounter . '@ID'), T_OTHER('.i'), T_CS('\@' . $usecounter . '@ID')));
my $series;
if ($series = $options{series}) {
$series = ToString($series); }
if (my $start = $options{start}) {
SetCounter($usecounter, $start);
AddToCounter($usecounter, Number(-1)); }
elsif (my $s = $options{resume} || $options{'resume*'}) {
if (($s = ToString($s)) ne 'noseries') {
$series = ToString($s);
SetCounter($usecounter,
LookupValue('enumitem_series_' . $series . '_last') || Number(0)); } }
else {
ResetCounter($usecounter); }
## return RefStepCounter('@itemize' . $listpostfix); }
return (RefStepCounter('@itemize' . $listpostfix),
counter => $usecounter, series => $series); } # So end can save counter value
# These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements
# Only create the 1st level (so that binding style can start numbering 'within' appropriately)
# Additional ones created by need.
NewCounter('@itemizei', 'section', idprefix => 'I');
# Create id, and tags for an itemize type \item
sub RefStepItemCounter {
my ($tag) = @_;
my $counter = LookupValue('itemcounter');
my $n = LookupValue('itemization_items');
AssignValue(itemization_items => $n + 1);
my %attr = ();
my $sep = LookupDimension('\itemsep');
if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) {
$attr{itemsep} = $sep; }
if (defined $tag) {
my @props = RefStepID($counter);
if (IsEmpty($tag)) { # empty tag?
return (@props); }
else {
my $ttag = (ref $tag ? $tag : T_OTHER($tag));
my $formatter = ($counter =~ /^\@desc/ ? T_CS('\descriptionlabel') : T_CS('\makelabel'));
my $typename = (IsDefined(T_CS('\\' . $counter . 'name'))
? T_CS('\\' . $counter . 'name') : T_CS('\itemtyperefname'));
my $tags = Digest(T_BEGIN,
T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'),
T_CS('\def'), T_CS('\fnum@' . $counter), T_BEGIN, $formatter, T_BEGIN, Revert($tag), T_END, T_END,
T_CS('\def'), T_CS('\typerefnum@' . $counter),
T_BEGIN, $typename, T_SPACE, Revert($tag), T_END,
Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)),
T_END);
return (@props,
($tags ? (tags => $tags) : ()),
%attr); } }
else {
return (RefStepCounter($counter), %attr); } }
# The following two aren't used here; they're defined here so they
# can be used in paralist.sty, enumerate.sty (perhaps others?)
# This isn't really satisfactory.
# We should record the marker used for the item,
# but it really should NOT be #refnum (which should be quasi unique)
# and is not \theenumi.. (which should be a counter value)
sub setItemizationStyle {
my ($stuff, $level) = @_;
if (defined $stuff) {
$level = LookupValue('@itemlevel') || 0 unless defined $level;
$level = ToString(Tokens(roman($level)));
DefMacroI('\labelitem' . $level, undef, $stuff); }
return; }
sub setEnumerationStyle {
my ($stuff, $level) = @_;
if (defined $stuff) {
$level = LookupValue('enumlevel') || 0 unless defined $level;
$level = ToString(Tokens(roman($level)));
my @in = $stuff->unlist;
my @out = ();
my $ctr = T_OTHER('enum' . $level);
while (my $t = shift(@in)) {
if (Equals($t, T_BEGIN)) {
push(@out, $t);
my $brlevel = 1;
while ($brlevel && ($t = shift(@in))) {
if (Equals($t, T_BEGIN)) { $brlevel++; }
elsif (Equals($t, T_END)) { $brlevel--; }
push(@out, $t); } }
elsif (Equals($t, T_LETTER('A'))) {
DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Alph'), $ctr));
push(@out, T_CS('\theenum' . $level)); }
elsif (Equals($t, T_LETTER('a'))) {
DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\alph'), $ctr));
push(@out, T_CS('\theenum' . $level)); }
elsif (Equals($t, T_LETTER('I'))) {
DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Roman'), $ctr));
push(@out, T_CS('\theenum' . $level)); }
elsif (Equals($t, T_LETTER('i'))) {
DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\roman'), $ctr));
push(@out, T_CS('\theenum' . $level)); }
elsif (Equals($t, T_OTHER('1'))) {
DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\arabic'), $ctr));
push(@out, T_CS('\theenum' . $level)); }
else {
push(@out, $t); } }
DefMacroI('\labelenum' . $level, undef, Tokens(T_BEGIN, @out, T_END)); }
return; }
# End paragraphs, like \par, but only if within an item's text
DefConstructorI('\preitem@par', undef, sub {
my ($document, %props) = @_;
if (!$props{inPreamble} && !$document->getNodeQName($document->getElement(), 'ltx:itemize')) {
$document->maybeCloseElement('ltx:p');
$document->maybeCloseElement('ltx:para'); } },
alias => '\par');
# id, but NO refnum (et.al) attributes on itemize \item ...
# unless the optional tag argument was given!
# We'll make the <ltx:tag> from either the optional arg, or from \labelitemi..
DefMacro('\itemize@item', '\preitem@par\itemize@item@');
DefConstructor('\itemize@item@ OptionalUndigested',
"<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@itemize@item OptionalUndigested',
"<ltx:inline-item xml:id='#id'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefMacro('\enumerate@item', '\preitem@par\enumerate@item@');
DefConstructor('\enumerate@item@ OptionalUndigested',
"<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@enumerate@item OptionalUndigested',
"<ltx:inline-item xml:id='#id'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefMacro('\description@item', '\preitem@par\description@item@');
DefConstructor('\description@item@ OptionalUndigested',
"<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@description@item OptionalUndigested',
"<ltx:inline-item xml:id='#id'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
DefEnvironment('{itemize}',
"<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
properties => sub { beginItemize('itemize', '@item'); },
beforeDigestEnd => sub { Digest('\par'); },
locked => 1, mode => 'text');
DefEnvironment('{enumerate}',
"<ltx:enumerate xml:id='#id'>#body</ltx:enumerate>",
properties => sub { beginItemize('enumerate', 'enum'); },
beforeDigestEnd => sub { Digest('\par'); },
locked => 1, mode => 'text');
DefEnvironment('{description}',
"<ltx:description xml:id='#id'>#body</ltx:description>",
beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); },
properties => sub { beginItemize('description', '@desc'); },
beforeDigestEnd => sub { Digest('\par'); },
locked => 1, mode => 'text');
DefMacro('\makelabel{}', '#1');
DefMacro('\@mklab{}', '\hfil #1');
#----------------------------------------------------------------------
# Basic itemize bits
# Fake counter for itemize to give id's to ltx:item.
NewCounter('@itemi', undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('@itemii', undef, idwithin => '@itemi', idprefix => 'i');
NewCounter('@itemiii', undef, idwithin => '@itemii', idprefix => 'i');
NewCounter('@itemiv', undef, idwithin => '@itemiii', idprefix => 'i');
NewCounter('@itemv', undef, idwithin => '@itemiv', idprefix => 'i');
NewCounter('@itemvi', undef, idwithin => '@itemv', idprefix => 'i');
# These are empty to make the 'refnum' go away.
DefMacroI('\the@itemi', undef, '');
DefMacroI('\the@itemii', undef, '');
DefMacroI('\the@itemiii', undef, '');
DefMacroI('\the@itemiv', undef, '');
DefMacroI('\the@itemv', undef, '');
DefMacroI('\the@itemvi', undef, '');
# Formatted item tags.
# Really should be in the class file, but already was here.
DefMacroI('\labelitemi', undef, '\textbullet');
DefMacroI('\labelitemii', undef, '\normalfont\bfseries \textendash');
DefMacroI('\labelitemiii', undef, '\textasteriskcentered');
DefMacroI('\labelitemiv', undef, '\textperiodcentered');
# Make the fake counters point to the real labels
DefMacroI('\label@itemi', undef, '\labelitemi');
DefMacroI('\label@itemii', undef, '\labelitemii');
DefMacroI('\label@itemiii', undef, '\labelitemiii');
DefMacroI('\label@itemiv', undef, '\labelitemiv');
# These hookup latexml's tagging to normal latex's \labelitemi...
DefMacroI('\fnum@@itemi', undef, '{\makelabel{\label@itemi}}');
DefMacroI('\fnum@@itemii', undef, '{\makelabel{\label@itemii}}');
DefMacroI('\fnum@@itemiii', undef, '{\makelabel{\label@itemiii}}');
DefMacroI('\fnum@@itemiv', undef, '{\makelabel{\label@itemiv}}');
# These define the typerefnum form, for out-of-context \ref's
# Better would language sensitive!
my @pm_ordinal_suffices = ('th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th');
DefMacro('\lx@poormans@ordinal{}', sub {
my ($gullet, $ctr) = @_;
my $n = CounterValue($ctr)->valueOf;
my $string = "$n";
if ($string =~ /.*?(\d)$/) {
$string .= $pm_ordinal_suffices[$1]; }
T_OTHER($string); });
DefMacroI('\itemtyperefname', undef, 'item');
DefMacroI('\itemcontext', undef, '\space in \@listcontext');
DefMacroI('\itemcontext', undef, '');
# Probably would help to give a bit more context for the ii & higher?
DefMacroI('\typerefnum@@itemi', undef, '\lx@poormans@ordinal{@itemi} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemii', undef, '\lx@poormans@ordinal{@itemii} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemiii', undef, '\lx@poormans@ordinal{@itemiii} \itemtyperefname \itemcontext');
DefMacroI('\typerefnum@@itemiv', undef, '\lx@poormans@ordinal{@itemiv} \itemtyperefname \itemcontext');
#----------------------------------------------------------------------
# Basic enumeration bits
# Class file normally would have
# NewCounter for enumi,...,
# define \labelenumi,... and probably \p@enumii...
NewCounter('enumi', undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('enumii', undef, idwithin => 'enumi', idprefix => 'i');
NewCounter('enumiii', undef, idwithin => 'enumii', idprefix => 'i');
NewCounter('enumiv', undef, idwithin => 'enumiii', idprefix => 'i');
NewCounter('enumv', undef, idwithin => 'enumiv', idprefix => 'i'); # A couple of extra
NewCounter('enumvi', undef, idwithin => 'enumv', idprefix => 'i');
# How the refnums look... (probably should be in class file, but already here)
DefMacroI('\p@enumii', undef, '\theenumi');
DefMacroI('\p@enumiii', undef, '\theenumi(\theenumii)');
DefMacroI('\p@enumiv', undef, '\p@enumii\theenumiii');
# Formatting of item tags (probably should be in the class file, but already here)
DefMacroI('\labelenumi', undef, '\theenumi.');
DefMacroI('\labelenumii', undef, '(\theenumii)');
DefMacroI('\labelenumiii', undef, '\theenumiii.');
DefMacroI('\labelenumiv', undef, '\theenumiv.');
# These hookup latexml's tagging to normal latex's \labelenummi...
DefMacroI('\fnum@enumi', undef, '{\makelabel{\labelenumi}}');
DefMacroI('\fnum@enumii', undef, '{\makelabel{\labelenumii}}');
DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}');
DefMacroI('\fnum@enumiv', undef, '{\makelabel{\labelenumiv}}');
# These define the typerefnum form, for out-of-context \ref's
DefMacroI('\enumtyperefname', undef, 'item');
DefMacroI('\typerefnum@enumi', undef, '\enumtyperefname~\p@enumi\theenumi \itemcontext');
DefMacroI('\typerefnum@enumii', undef, '\enumtyperefname~\p@enumii\theenumii \itemcontext');
DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\theenumiii \itemcontext');
DefMacroI('\typerefnum@enumiv', undef, '\enumtyperefname~\p@enumiv\theenumiv \itemcontext');
#DefMacroI('\typerefnum@enumi', undef, '\enumtyperefname~\p@enumi\labelenumi \itemcontext');
#DefMacroI('\typerefnum@enumii', undef, '\enumtyperefname~\p@enumii\labelenumii \itemcontext');
#DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\labelenumiii \itemcontext');
#DefMacroI('\typerefnum@enumiv', undef, '\enumtyperefname~\p@enumiv\labelenumiv \itemcontext');
#----------------------------------------------------------------------
# Basic description list bits
# Fake counter for itemize to give id's to ltx:item.
NewCounter('@desci', undef, idwithin => '@itemizei', idprefix => 'i');
NewCounter('@descii', undef, idwithin => '@desci', idprefix => 'i');
NewCounter('@desciii', undef, idwithin => '@descii', idprefix => 'i');
NewCounter('@desciv', undef, idwithin => '@desciii', idprefix => 'i');
NewCounter('@descv', undef, idwithin => '@desciv', idprefix => 'i');
NewCounter('@descvi', undef, idwithin => '@descv', idprefix => 'i');
# No refnum's here, either
DefMacroI('\the@desci', undef, '');
DefMacroI('\the@descii', undef, '');
DefMacroI('\the@desciii', undef, '');
DefMacroI('\the@desciv', undef, '');
DefMacroI('\the@descv', undef, '');
DefMacroI('\the@descvi', undef, '');
# These hookup latexml's numbering to normal latex's
# Umm.... but they're not normally used, since \item usually gets an argument!
DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1');
DefMacroI('\fnum@@desci', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@descii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@desciii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@desciv', undef, '{\descriptionlabel{}}');
DefMacroI('\desctyperefname', undef, 'item');
# Blech
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\itemtyperefname'); }
qw(@itemi @itemii @itemiii @itemiv @itemv @itemvi);
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\enumtyperefname'); }
qw(enumi enumii enumiii enumiv);
map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\desctyperefname'); }
qw(@desci @descii @desciii @desciv @descv @descvi);
#======================================================================
# C.6.3 The list and trivlist environments.
#======================================================================
# Generic lists are given a way to format the item label, and presumably
# a counter.
DefConditional('\if@nmbrlist');
DefMacro('\@listctr', '');
DefPrimitive('\usecounter{}', sub {
my ($stomach, $counter) = @_;
$counter = ToString(Expand($counter));
beginItemize('list', $counter, ($counter ? (nolevel => 1) : ()));
return; });
DefMacro('\list{}{}',
'\let\@listctr\@empty#2\ifx\@listctr\@empty\usecounter{}\fi\expandafter\def\csname fnum@\@listctr\endcsname{#1}\lx@list');
DefMacro('\endlist', '\endlx@list');
# Start an anonymous list (often misused)
DefConstructor('\lx@list',
"<ltx:itemize>",
beforeDigest => sub { $_[0]->bgroup; });
# Close the anonymous list if we're still within one.
DefConstructor('\endlx@list', sub {
$_[0]->maybeCloseElement('ltx:itemize'); },
beforeDigest => sub { $_[0]->egroup; });
DefConstructor('\list@item OptionalUndigested',
"<ltx:item xml:id='#id' itemsep='#itemsep'>#tags",
properties => sub { RefStepItemCounter($_[1]); });
# This isn't quite right, although it seems right for deep, internal uses with a single \item.
# Perhaps we need to check trivlist's afterwards and if they are just a single item,
# reduce it to an ltx:p ??
DefConstructor('\trivlist',
"<ltx:itemize _autoclose='1'>",
properties => sub { beginItemize('trivlist'); });
DefConstructor('\endtrivlist', sub {
$_[0]->maybeCloseElement('ltx:itemize'); },
beforeDigest => sub { Digest('\par'); });
DefMacro('\trivlist@item', '\preitem@par\trivlist@item@');
DefConstructor('\trivlist@item@ OptionalUndigested',
"<ltx:item xml:id='#id' itemsep='#itemsep'>"
. "<ltx:tags><ltx:tag>#tag</ltx:tag></ltx:tags>", # At least an empty tag! ?
properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); });
DefMacro('\@trivlist', '\relax', locked => 1);
DefRegister('\topsep' => Glue(0));
DefRegister('\partopsep' => Glue(0));
DefRegister('\lx@default@itemsep' => Glue(0));
DefRegister('\itemsep' => Glue(0));
DefRegister('\parsep' => Glue(0));
DefRegister('\@topsep' => Glue(0));
DefRegister('\@topsepadd' => Glue(0));
DefRegister('\@outerparskip' => Glue(0));
DefRegister('\leftmargin' => Dimension(0));
DefRegister('\rightmargin' => Dimension(0));
DefRegister('\listparindent' => Dimension(0));
DefRegister('\itemindent' => Dimension(0));
DefRegister('\labelwidth' => Dimension(0));
DefRegister('\labelsep' => Dimension(0));
DefRegister('\@totalleftmargin' => Dimension(0));
DefRegister('\leftmargini' => Dimension(0));
DefRegister('\leftmarginii' => Dimension(0));
DefRegister('\leftmarginiii' => Dimension(0));
DefRegister('\leftmarginiv' => Dimension(0));
DefRegister('\leftmarginv' => Dimension(0));
DefRegister('\leftmarginvi' => Dimension(0));
DefRegister('\@listdepth' => Number(0));
DefRegister('\@itempenalty' => Number(0));
DefRegister('\@beginparpenalty' => Number(0));
DefRegister('\@endparpenalty' => Number(0));
DefRegister('\labelwidthi' => Dimension(0));
DefRegister('\labelwidthii' => Dimension(0));
DefRegister('\labelwidthiii' => Dimension(0));
DefRegister('\labelwidthiv' => Dimension(0));
DefRegister('\labelwidthv' => Dimension(0));
DefRegister('\labelwidthvi' => Dimension(0));
DefRegister('\@itemdepth' => Number(0));
#======================================================================
# C.6.4 Verbatim
#======================================================================
# NOTE: how's the best way to get verbatim material through?
#DefEnvironment('{verbatim}', '<ltx:verbatim>#body</ltx:verbatim>');
#DefEnvironment('{verbatim*}', '<ltx:verbatim>#body</ltx:verbatim>');
DefMacroI('\@verbatim', undef,
'\par\aftergroup\lx@end@verbatim\lx@@verbatim'); # Close enough?
DefConstructorI('\lx@@verbatim', undef,
"<ltx:verbatim font='#font'>",
beforeDigest => sub {
my ($stomach) = @_;
StartSemiverbatim('%', '\\', '{', '}');
MergeFont(family => 'typewriter', series => 'medium', shape => 'upright');
$STATE->assignCatcode(' ', CC_ACTIVE); # Do NOT (necessarily) skip spaces after \verb!!!
Let(T_ACTIVE(' '), T_SPACE); });
DefConstructorI('\lx@end@verbatim', undef,
"</ltx:verbatim>",
beforeDigest => sub {
EndSemiverbatim(); });
# verbatim is a bit of special case;
# It looks like an environment, but it only ends with an explicit "\end{verbatim}" on it's own line.
# So, we'll end up doing things more manually.
# We're going to sidestep the Gullet for inputting,
# and also the usual environment capture.
DefConstructorI(T_CS('\begin{verbatim}'), undef,
"<ltx:verbatim font='#font'>#body</ltx:verbatim>",
beforeDigest => [sub { beforeDigestVerbatim(0, @_); }],
afterDigest => [sub { afterDigestVerbatim(0, @_); }],
beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });
DefConstructorI(T_CS('\begin{verbatim*}'), undef,
"<ltx:verbatim font='#font'>#body</ltx:verbatim>",
beforeDigest => [sub { beforeDigestVerbatim(1, @_); }],
afterDigest => [sub { afterDigestVerbatim(1, @_); }],
beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });
sub beforeDigestVerbatim {
my ($starred, $stomach) = @_;
$stomach->bgroup;
my @stuff = ();
if (my $b = LookupValue('@environment@verbatim@atbegin')) {
push(@stuff, Digest(@$b)); }
AssignValue(current_environment => 'verbatim');
DefMacroI('\@currenvir', undef, 'verbatim');
MergeFont(family => 'typewriter');
# Digest(T_CS('\par')); # NO! See beforeConstruct!
return @stuff; }
sub afterDigestVerbatim {
my ($starred, $stomach, $whatsit) = @_;
# $stomach->egroup;
my $font = $whatsit->getFont;
my $loc = $whatsit->getLocator;
my $end = $starred ? '\end{verbatim*}' : '\end{verbatim}';
my $space = $starred ? "\x{2423}" : ' ';
my @lines = ();
my $gullet = $stomach->getGullet;
while (defined(my $line = $gullet->readRawLine)) {
my ($exiting, $remaining) = (0, undef);
if ($line =~ /^(.*?)\Q$end\E(.*?)$/) {
$exiting = 1;
$line = $1;
$remaining = $2; }
# The raw chars will still have to be decoded (but not space!!)
$line = join('', map { ($_ eq ' ' ? $space : FontDecodeString($_, 'OT1_typewriter')) }
split(//, $line));
push(@lines, $line . "\n");
if ($exiting) {
$gullet->unread(Tokenize($remaining), T_CR);
last; } }
pop(@lines) if $lines[-1] eq "\n";
# Note last line ends up as Whatsit's "trailer"
if (my $b = LookupValue('@environment@verbatim@atend')) {
push(@lines, ToString(Digest(@$b))); }
$stomach->egroup;
$whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end);
return; }
DefPrimitiveI('\@vobeyspaces', undef, sub {
AssignCatcode(" " => 13);
Let(T_ACTIVE(" "), '\nobreakspace');
return });
DefMacro('\@xobeysp', '\nobreakspace');
# WARNING: Need to be careful about what catcodes are active here
# And clearly separate expansion from digestion
DefMacroI('\verb', undef, sub {
my ($gullet) = @_;
StartSemiverbatim('%', '\\', '{', '}');
$STATE->assignCatcode(' ', CC_ACTIVE);
my $init;
my $skippedSpace = 0;
# As of texlive 2021, DO skip spaces before delimiter (even tho we've changed catcodes)
# but if we do skip spaces, * can be the delimiter
do { $init = $gullet->readToken();
$skippedSpace = 1 if (defined $init && $init->getString eq ' ');
} while (defined $init && $init->getString eq ' ');
my $starred = 0;
if (T_OTHER('*')->equals($init) && !$skippedSpace) {
$starred = 1;
do { $init = $gullet->readToken();
} while (defined $init && $init->getString eq ' '); }
if (!$init) { # typically something read too far got \verb and the content is somewhere else..?
Error('expected', 'delimiter', $gullet,
"Verbatim argument lost", "Bindings for preceding code is probably broken");
EndSemiverbatim();
return (); }
my $init_str = $init->getString();
AssignCatcode($init_str => CC_ACTIVE);
my $delim = T_ACTIVE($init_str);
my $body = $gullet->readUntil($delim);
EndSemiverbatim();
Tokens(
T_CS('\@hidden@bgroup'),
($starred ? T_CS('\lx@use@visiblespace') : ()),
Invocation(T_CS('\@internal@verb'),
($starred ? T_OTHER('*') : Tokens()),
Tokens($init), $body),
T_CS('\@hidden@egroup'))->unlist; });
DefPrimitive('\lx@use@visiblespace', sub {
my ($stomach, $star) = @_;
$STATE->assignCatcode(' ', CC_ACTIVE); # Do NOT (necessarily) skip spaces after \verb!!!
Let(T_ACTIVE(' '), T_OTHER("\x{2423}")); # Visible space
});
# Arrange to digest the body in text mode, to keep (eg) "_" from turning to "\_"
DefMacro('\@internal@verb{}{}{}', '\ifmmode\@internal@math@verb{#1}{#2}{#3}\else\@internal@text@verb{#1}{#2}{#3}\fi');
DefConstructor('\@internal@math@verb{} Undigested {}',
"<ltx:XMTok font='#font'>#3</ltx:XMTok>",
mode => 'text',
font => { family => 'typewriter', series => 'medium', shape => 'upright' },
reversion => '\verb#1#2#3#2');
DefConstructor('\@internal@text@verb{} Undigested {}',
"<ltx:verbatim font='#font'>#3</ltx:verbatim>",
font => { family => 'typewriter', series => 'medium', shape => 'upright' },
beforeConstruct => sub {
my ($doc, $whatsit) = @_;
$doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); },
reversion => '\verb#1#2#3#2');
# This is defined by the alltt package.
# Environment('alltt', ?);
# Actually, latex sets catcode to 13 ... is this close enough?
DefPrimitiveI('\obeycr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 1); });
DefPrimitiveI('\restorecr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 0); });
DefMacroI('\normalsfcodes', undef, Tokens());
#**********************************************************************
# C.7 Mathematical Formulas
#**********************************************************************
#======================================================================
# C.7.1 Math Mode Environments
#======================================================================
DefMacroI('\@eqnnum', undef, '(\theequation)', locked => 1);
DefMacro('\fnum@equation', '\@eqnnum');
# Redefined from TeX.pool, since with LaTeX we presumably have a more complete numbering system
DefConstructorI('\@@BEGINDISPLAYMATH', undef,
"<ltx:equation xml:id='#id'>"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
alias => '$$',
beforeDigest => sub {
$_[0]->beginMode('display_math');
if (my @everymath_toks = $STATE->lookupDefinition(T_CS('\everymath'))->valueOf->unlist()) {
$_[0]->getGullet->unread(@everymath_toks); }
if (my @everydisplay_toks = $STATE->lookupDefinition(T_CS('\everydisplay'))->valueOf->unlist()) {
$_[0]->getGullet->unread(@everydisplay_toks); }
return; },
properties => sub { RefStepID('equation') },
captureBody => 1);
DefEnvironment('{displaymath}',
"<ltx:equation xml:id='#id'>"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
properties => sub { RefStepID('equation') },
locked => 1);
DefEnvironment('{math}',
"<ltx:Math mode='inline'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>",
mode => 'inline_math',
);
Let('\curr@math@size', '\@empty');
# Equation Numbering turns out to be rather convoluted!
# numbered equations & friends want to IMMEDIATELY increment the equation counter
# (you can see by \theequation)
# However, \nonumber, \tag, set a different number and RETRACT stepping the counter!
NewCounter('subequation', 'equation', idprefix => 'E', idwithin => 'equation');
DefMacro('\thesubequation', '\theequation\alph{subequation}');
DefMacro('\fnum@subequation', '(\thesubequation)');
# This provides {equation} with the capabilities for tags, nonumber, etc
# even though stock LaTeX provides no means to override them.
# preset => boolean
# postset => boolean
# deferretract=>boolean
sub prepareEquationCounter {
my (%options) = @_;
AssignValue(EQUATION_NUMBERING => {%options}, 'global');
return; }
sub beforeEquation {
my $numbering = LookupValue('EQUATION_NUMBERING');
my $numbered = $$numbering{numbered};
my $ctr = $$numbering{counter} || 'equation';
MaybePeekLabel();
$$numbering{in_equation} = 1;
if ($$numbering{preset}) {
AssignValue(EQUATIONROW_TAGS => {
preset => 1,
($numbered ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
else {
AssignValue(EQUATIONROW_TAGS => {}, 'global'); }
Let('\@@ENDDISPLAYMATH', '\lx@eDM@in@equation');
Let('\@@BEGINDISPLAYMATH', '\lx@bDM@in@equation');
return; }
# Note peculiar usages of \[,\],$$ WITHIN displayed math, like {equation}.
# Authors sometimes use \begin{equation} math \] some text \[ more math \end{equation}
# to create a cheap intertext. And, the refnum appears on the SECOND equation!
# But in redefining these, note that a "valid" \[ math \] might be used within some
# sort of text insertion (eg \footnote)!
# So, we've got to dance around with redefinitions!!
# SIDE NOTE: \[,\] are really just $$ w/ appropriate error checking!
Let('\lx@saved@BEGINDISPLAYMATH', '\@@BEGINDISPLAYMATH');
Let('\lx@saved@ENDDISPLAYMATH', '\@@ENDDISPLAYMATH');
DefMacro('\lx@bDM@in@equation', '\lx@saved@BEGINDISPLAYMATH\let\@@ENDDISPLAYMATH\lx@saved@ENDDISPLAYMATH');
DefMacro('\lx@eDM@in@equation',
'\lx@retract@eqnno\lx@begin@fake@intertext'
. '\let\lx@saved@BEGINDISPLAYMATH\@@BEGINDISPLAYMATH\let\lx@saved@bdm\['
. '\let\@@BEGINDISPLAYMATH\lx@end@fake@intertext'
. '\let\[\lx@end@fake@intertext');
DefMacro('\lx@begin@fake@intertext', '\end{equation}');
DefMacro('\lx@end@fake@intertext',
'\let\@@BEGINDISPLAYMATH\lx@saved@BEGINDISPLAYMATH\let\[\lx@saved@bdm' .
'\begin{equation}');
DefPrimitive('\lx@retract@eqnno', sub { retractEquation(); });
sub retractEquation {
my $numbering = LookupValue('EQUATION_NUMBERING');
# What about scopes? Is that handled automagically?
# What about \@currentID....
my $tags = LookupValue('EQUATIONROW_TAGS');
my $ctr = $$tags{counter} || $$numbering{counter} || 'equation';
if ($$tags{preset}) { # ONLY if the number was preset!
# counter (or ID counter) was stepped, so decrement it.
AddToCounter(($$numbering{numbered} ? $ctr : 'UN' . $ctr), Number(-1)); }
AssignValue(EQUATIONROW_TAGS => { RefStepID($ctr), reset => 1 }, 'global');
return; }
# Disable this equation's number
# Ahhhh, but in eqnarray, this isn't effected until \\ !!!!!
DefMacroI('\nonumber', undef, '\lx@equation@nonumber');
DefPrimitiveI('\lx@equation@nonumber', undef, sub {
my $numbering = LookupValue('EQUATION_NUMBERING');
if ($$numbering{in_equation}) {
if ($$numbering{deferretract}) {
my $tags = LookupValue('EQUATIONROW_TAGS');
$$tags{retract} = 1; }
else {
retractEquation(); } }
return; });
Let('\@LTX@nonumber', '\lx@equation@nonumber');
# Set this equation's number explicitly (eg ams's \tag)
DefMacroI('\lx@equation@settag', undef, '\lx@equation@retract\lx@equation@settag@');
DefPrimitiveI('\lx@equation@retract', undef, sub { retractEquation(); });
DefPrimitive('\lx@equation@settag@ Digested', sub {
my $tags = LookupValue('EQUATIONROW_TAGS');
$$tags{tags} = $_[1];
return; },
mode => 'text');
sub afterEquation {
my ($whatsit) = @_;
my $numbering = LookupValue('EQUATION_NUMBERING');
my $tags = LookupValue('EQUATIONROW_TAGS');
my $ctr = $$tags{counter} || $$numbering{counter} || 'equation';
if (!$$tags{noretract}
&& ($$tags{retract} || ($$numbering{retract} && $$numbering{preset} && $$tags{preset}))) {
retractEquation(); }
elsif ($$numbering{postset} && !$$tags{reset}) {
# my %props = ();
# if ($$numbering{numbered}) {
# %props = RefStepCounter($ctr); }
# else {
# %props = RefStepID($ctr); }
# AssignValue(EQUATIONROW_TAGS => {%props}, 'global'); }
AssignValue(EQUATIONROW_TAGS => {
($$numbering{numbered} ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); }
elsif (!$$tags{reset} && $$numbering{numbered}) {
$$tags{tags} = Digest(Invocation(T_CS('\lx@make@tags'), $ctr)); }
# Now install the tags in $whatsit or current Row, as appropriate.
my $props = LookupValue('EQUATIONROW_TAGS');
if ($$numbering{aligned}) {
if (my $alignment = LookupValue('Alignment')) {
my $row = $alignment->currentRow;
$$row{id} = $$props{id};
$$row{tags} = $$props{tags}; } }
elsif ($whatsit) {
$whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); }
$$numbering{in_equation} = 0;
return; }
# My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...?
DefEnvironment('{equation}',
"<ltx:equation xml:id='#id'>"
. "#tags"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
beforeDigest => sub {
prepareEquationCounter(numbered => 1, preset => 1);
beforeEquation(); },
afterDigestBody => sub {
afterEquation($_[1]); },
locked => 1);
# Note: In ams, this DOES get a number if \tag is used!
DefEnvironment('{equation*}',
"<ltx:equation xml:id='#id'>"
. "#tags"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
beforeDigest => sub {
prepareEquationCounter(numbered => undef, preset => 1);
beforeEquation(); },
afterDigestBody => sub {
afterEquation($_[1]); },
locked => 1);
DefMacro('\[', '\@@BEGINDISPLAYMATH');
DefMacro('\]', '\@@ENDDISPLAYMATH');
DefMacro('\(', '\@@BEGININLINEMATH');
DefMacro('\)', '\@@ENDINLINEMATH');
# Keep from expanding too early, if in alignments, or such.
DefMacroI('\ensuremath', undef,
Tokens(T_CS('\protect'), T_CS('\@ensuremath')));
DefMacro('\@ensuremath{}', sub {
my ($gullet, $stuff) = @_;
if (LookupValue('IN_MATH')) { $stuff->unlist; }
else { (T_MATH, $stuff->unlist, T_MATH); } });
# Magic check that math-mode trigger follows
our $MATHENVS = 'displaymath|equation*?|eqnarray*?'
. '|multline*?|align*?|falign*?|alignat*?|xalignat*?|xxalignat*?|gather*?';
DefMacro('\ensuremathfollows', sub {
my ($gullet) = @_;
$gullet->closeMouth unless ($gullet->getMouth->hasMoreInput);
if (my $tok = $gullet->readToken()) {
my $csname = $tok->getCSName;
if ($csname eq '\begin') {
my $arg = $gullet->readArg();
$csname = $arg->toString;
$gullet->unread(T_BEGIN, $arg->unlist, T_END);
}
$gullet->unread($tok);
# We need to determine whether the TeX we're given needs to be wrapped in \(...\)
# Does it have $'s around it? Does it have a display math environment?
if ($csname !~ /^Math|\\\(|\\\[|(?:$MATHENVS)/o) {
AssignValue('automath_triggered' => 1, 'global');
return T_CS('\\('); } }
return;
});
DefMacro('\ensuremathpreceeds', sub {
return LookupValue('automath_triggered') ? T_CS('\\)') : ();
});
# Since the arXMLiv folks keep wanting ids on all math, let's try this!
Tag('ltx:Math', afterOpen => sub { GenerateID(@_, 'm'); });
#======================================================================
# Sub-numbered equations
# Although LaTeX itself doesn't provide such an environment,
# many other packages & classes do, and they seem essentially equivalent.
# They provide an environment to wrap around equations,
# which hijack the equation counter to sub-number within the group.
DefConstructor('\lx@equationgroup@subnumbering@begin',
"<ltx:equationgroup xml:id='#id'>"
. "#tags",
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my %eqn = RefStepCounter('equation');
my $eqnum = Expand(T_CS('\theequation'));
AssignValue(SAVED_EQUATION_NUMBER => LookupRegister('\c@equation'));
$whatsit->setProperties(%eqn);
ResetCounter('equation');
DefMacroI('\theequation', undef, UnTeX($eqnum) . '\alph{equation}');
DefMacroI('\theequation@ID', undef, UnTeX($eqn{id}) . '.\@equation@ID'); });
Tag('ltx:equationgroup', autoClose => 1);
DefConstructor('\lx@equationgroup@subnumbering@end', sub {
$_[0]->maybeCloseElement('ltx:equationgroup'); },
afterDigest => sub {
AssignRegister('\c@equation', LookupValue('SAVED_EQUATION_NUMBER'), 'global');
});
#======================================================================
# ========================================
# eqnarray, etal
# Tricky! There's a conflict between a math-level alignment (which
# intermingles non-math things like labels, refnums, intertext),
# and a text-level alignment (which fragments the math's logical structure).
# Our solution is to attempt to synthesize a logical structure of
# an equationgroup containing equations, but using MathFork structures
# to hide the intended aligmnents.
# Then, XSLT can be used in the end to either display in a logical
# (but non-aligned format), or display fully aligned (but hiding the semantics).
#======================================================================
# Equation Groups
# <equationgroup> representing aligned collections of equations
# in particular, eqnarray and amsmath's align, ...
# The intended usage is a sequence of patterns like
# LHS & REL & RHS
# When the LHS is empty, it likely implies a continuation of the previous (or multirelation).
# When both the LHS & REL are empty, it likely implies a continuation of the previous RHS
#======================================================================
# The strategy here is to use the alignment mechanism to construct
# an initial form:
# <equationgroup> as the overall container
# <equation> for each row; this can receive id, refnum & label attributes
# <_Capture_> to capture each columns math.
# After the initial construction (but before any rewriting & parsing)
# we scan through the equations combining the ones that appear (heuristically)
# to be single equations on several rows (either as multi-relations, or
# rhs's of multiple rows)
# The combinations are represented by a MathFork container which
# holds both the apparently meaningful complete equation, along with
# the rows & columns that show the desired alignment.
# Each of those columns contains a <Math>, thus these can also be parsed,
# and converted to MathML or images.
#
# Thus, both forms are present: A presentation-oriented stylesheet can
# thus represent the eqnarray by a table with aligned math chunks.
# A content-oriented stylesheet can select the composed meaningful pieces.
# ========================================
# The following set deal with numbering the equations (rows) that make up an equationgroup.
# Note EQUATIONGROUP_NUMBER controls the numbering of the equations contained within the equationgroup,
# not the numbering of the equationgroup itself.
DefPrimitive('\@equationgroup@numbering RequiredKeyVals', sub {
my $kv = GetKeyVals($_[1]);
prepareEquationCounter(map { ($_ => ToString($$kv{$_})) } keys %$kv); });
# ========================================
# Some special kinds of rows...
DefConditionalI('\if@in@firstcolumn', undef, sub {
my $alignment = LookupValue('Alignment');
my $n = ($alignment ? $alignment->currentColumnNumber : 9);
return $alignment && (!$$alignment{in_row} ||
(!$$alignment{in_column} && ($alignment->currentColumnNumber < 2))); });
# A bit more defensiveness, since people abuse eqnarray so badly.
# Eg. &&\lefteqn{...} whatever?!?!
DefMacro('\lefteqn{}',
'\if@in@firstcolumn\multicolumn{3}{l}{\@ADDCLASS{ltx_eqn_lefteqn}\@@BEGININLINEMATH \displaystyle #1\@@ENDINLINEMATH\mbox{}}'
. '\else\rlap{\@@BEGININLINEMATH\displaystyle #1\@@ENDINLINEMATH}\fi');
# \intertext (in amsmath)
Let('\displ@y', '\displaystyle'); # good enough?
DefMacro('\@lign', ''); # good enough?
# ========================================
# eqnarray
DefMacroI('\eqnarray', undef,
'\@eqnarray@bindings\@@eqnarray'
. '\@equationgroup@numbering{numbered=1,preset=1,deferretract=1,grouped=1,aligned=1}'
. '\@start@alignment',
locked => 1);
DefMacroI('\endeqnarray', undef,
'\cr\@finish@alignment\end@eqnarray',
locked => 1);
DefMacro('\csname eqnarray*\endcsname',
'\@eqnarray@bindings\@@eqnarray'
. '\@equationgroup@numbering{numbered=1,preset=1,retract=1,grouped=1,aligned=1}'
. '\@start@alignment',
locked => 1);
DefMacro('\csname endeqnarray*\endcsname',
'\@finish@alignment\end@eqnarray',
locked => 1);
DefPrimitive('\@eqnarray@bindings', sub {
eqnarrayBindings(); });
DefPrimitiveI('\eqnarray@row@before@', undef, sub { beforeEquation(); });
DefPrimitiveI('\eqnarray@row@after@', undef, sub { afterEquation(); });
DefMacroI('\eqnarray@row@before', undef, '\hidden@noalign{\eqnarray@row@before@}');
DefMacroI('\eqnarray@row@after', undef, '\hidden@noalign{\eqnarray@row@after@}');
sub eqnarrayBindings {
my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH) };
my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH, T_CS('\hfil')) };
my $col3 = { before => Tokens(T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH, T_CS('\hfil')) };
my %attributes = (
'class' => 'ltx_eqn_eqnarray',
'colsep' => LookupDimension('\arraycolsep')->multiply(2));
my $cur_jot = LookupDimension('\jot');
if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
$attributes{rowsep} = $cur_jot; }
AssignValue(Alignment => LaTeXML::Core::Alignment->new(
template => LaTeXML::Core::Alignment::Template->new(columns => [$col1, $col2, $col3]),
openContainer => sub { my %attr = RefStepID('@equationgroup');
$attr{'xml:id'} = $attr{id}; delete $attr{id};
$attr{class} = 'ltx_eqn_eqnarray';
$_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); },
closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); },
openRow => sub {
my ($doc, %props) = @_;
my $tags = $props{tags}; delete($props{tags});
$doc->openElement('ltx:equation', %props);
$doc->absorb($tags) if $tags; },
closeRow => sub { $_[0]->closeElement('ltx:equation'); },
openColumn => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); },
closeColumn => sub { $_[0]->closeElement('ltx:_Capture_'); },
properties => { preserve_structure => 1, attributes => {%attributes} }));
Let("\\\\", '\@alignment@newline');
Let('\lx@intercol', '\lx@math@intercol');
Let('\@row@before', '\eqnarray@row@before');
Let('\@row@after', '\eqnarray@row@after');
Let('\lx@eqnarray@save@label', '\label');
Let('\label', '\lx@eqnarray@label');
return; }
# A \label preceding \lefteqn throws of the implied \omit,e tc; so wrap in \hidden@align
DefMacro('\lx@eqnarray@label Semiverbatim',
'\hidden@noalign{\lx@eqnarray@save@label{#1}}');
DefConstructor('\@@eqnarray SkipSpaces DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
afterConstruct => sub { rearrangeEqnarray($_[0], $_[0]->getNode->lastChild); });
DefPrimitiveI('\end@eqnarray', undef, sub { $_[0]->egroup; });
# ========================================
# Some tools for analyzing the equationgroup after we've constructed it.
# ========================================
# ========================================
# For eqnarray, the "meaningful" unit will be at least one row,
# but often multiple rows.
# When the 1st column is empty, the row is assumed to continue the previous equation (if any!)
# When the 2nd column is also empty, it presumably continues the previous RHS.
# We'll combine these cases into a single equation, but remember the alignment structure.
# However, if more than 1 such row has refnums, we probably don't want to combine;
# But we really need to find a better way of representing the information!
# Note that there are common misuses of eqnarray;
# One type tries to get the equivalent of amsmath's gather environment by
# using a single column for the equations; the equations are right, centered or
# left aligned, depending on which column was used.
# Can we detect continuations, and can we distinguish continuations of equations vs. RHS?
# Probably a similar misuse where only 2 columns are used?
sub rearrangeEqnarray {
my ($document, $equationgroup) = @_;
# Scan the "equations" (rows) within the $equationgroup
# to see what pattern of columns are present.
my @rows = ();
foreach my $rownode ($document->findnodes('ltx:equation', $equationgroup)) {
my @cells = $document->findnodes('ltx:_Capture_', $rownode); # representing each column.
push(@rows, { node => $rownode, cols => [@cells],
L => ($cells[0] && $cells[0]->hasChildNodes),
M => ($cells[1] && $cells[1]->hasChildNodes),
R => ($cells[2] && $cells[2]->hasChildNodes),
numbered => ($document->findnode('ltx:tags', $rownode) ? 1 : 0),
labelled => $rownode->hasAttribute('label') }); }
my $nL = scalar(grep { $$_{L} } @rows);
my $nM = scalar(grep { $$_{M} } @rows);
my $nR = scalar(grep { $$_{R} } @rows);
# Only a single column was used. Remove the empty ones.
# A heuristic: if any rows begin with a relation, there are probably continuations,
# but maybe we don't want to try to distinguish the kinds?
if (($nL && !$nM && !$nR) # All left column
|| (!$nL && $nM && !$nR) # All center column
|| (!$nL && !$nM && $nR)) { # All right column
# We REALLY should remove the empty columns entirely!
my $keepcol = ($nL ? 0 : ($nM ? 1 : 2));
# REMOVE empty columns!
foreach my $c (2, 1, 0) {
next if $c == $keepcol;
foreach my $row (@rows) {
$document->removeNode($$row{cols}[$c]);
splice(@{ $$row{cols} }, $c, 1); } }
# If (some) columns begin with a relation, presumably a single equation?
# Or maybe we should only connect those to previous row?
my $t;
lib/LaTeXML/Package/LaTeX.pool.ltxml view on Meta::CPAN
# Probably the trailling //, but who knows...
# Arguably: if it has a number &/or is labelled
# we should either prserve (as odd) or move info to previous line (if any)
$class = 'remove'; }
if ($class eq 'remove') {
$document->removeNode($$row{node}); }
elsif (($class eq 'new') || ($class eq 'odd')) {
$numbered = $$row{numbered};
push(@eqs, [$$row{node}]);
$oddness++ if $class eq 'odd'; }
else {
$numbered |= $$row{numbered};
push(@{ $eqs[-1] }, $$row{node}); }
}
Warn('unexpected', 'eqnarray', $equationgroup,
"Unrecognized equation patterns ($oddness) in eqnarray")
if (scalar(@rows) > 1) && $oddness;
# Now rearrange things appropriately.
foreach my $eqset (@eqs) {
equationgroupJoinRows($document, $equationgroup, @$eqset); }
return; }
# Style Parameters
# \abovedisplayskip \abovedisplayshortskip, \jot are in TeX.pool
DefRegister('\mathindent' => Dimension(0));
#======================================================================
# C.7.2 Common Structures
#======================================================================
# sub, superscript and prime are in TeX.pool
# Underlying support in TeX.pool.ltxml
DefConstructor('\frac InFractionStyle InFractionStyle',
"<ltx:XMApp>"
. "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='#mathstyle'/>"
. "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>",
sizer => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); },
properties => { mathstyle => sub { LookupValue('font')->getMathstyle; } });
# Ellipsis: See TeX.pool
#======================================================================
# C.7.3 Mathematical Symbols
#======================================================================
# See Tables 3.3 through 3.8 (pp 41--44)
# Defined in TeX.pool
# [Possibly some are strictly LaTeX and should be moved here?]
#======================================================================
# C.7.4 Arrays
#======================================================================
# See Section C.10.2
#======================================================================-
# C.7.5 Delimiters
#======================================================================-
# All this is already in TeX.pool
DefMacro('\stackrel{}{}', '\lx@stackrel{{\scriptstyle #1}}{{#2}}');
DefConstructor('\lx@stackrel{}{}',
"<ltx:XMApp role='RELOP'>"
. "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
. "<ltx:XMArg>#2</ltx:XMArg>"
. "<ltx:XMArg>#1</ltx:XMArg>"
. "</ltx:XMApp>",
reversion => '\stackrel{#1}{#2}',
properties => { scriptpos => sub { "mid" . $_[0]->getScriptLevel; } }
);
#======================================================================-
# C.7.6 Putting One Thing Above Another
#======================================================================-
# All this is already in TeX.pool
#======================================================================-
# C.7.7 Spacing
#======================================================================-
# some of this is already in TeX.pool. the rest was in amsmath, but is now native to LaTeX
DefConstructorI('\thinspace', undef,
"?#isMath(<ltx:XMHint name='thinspace' width='#width'/>)(\x{2009})",
properties => { isSpace => 1, width => sub { LookupRegister('\thinmuskip'); } });
DefConstructorI('\negthinspace', undef,
"?#isMath(<ltx:XMHint name='negthinspace' width='#width'/>)()",
properties => { isSpace => 1, width => sub { LookupRegister('\thinmuskip')->negate; } });
DefConstructorI('\medspace', undef,
"?#isMath(<ltx:XMHint name='medspace' width='#width'/>)()",
properties => { isSpace => 1, width => sub { LookupRegister('\medmuskip'); } });
DefConstructorI('\negmedspace', undef,
"?#isMath(<ltx:XMHint name='negmedspace' width='#width'/>)()",
properties => { isSpace => 1, width => sub { LookupRegister('\medmuskip')->negate; } });
DefConstructorI('\thickspace', undef,
"?#isMath(<ltx:XMHint name='thickspace' width='#width'/>)(\x{2004})",
properties => { isSpace => 1, width => sub { LookupRegister('\thickmuskip'); } });
DefConstructorI('\negthickspace', undef,
"?#isMath(<ltx:XMHint name='negthickspace' width='#width'/>)(\x{2004})",
properties => { isSpace => 1, width => sub { LookupRegister('\thickmuskip')->negate; } });
#======================================================================
# C.7.8 Changing Style
#======================================================================
# For Math style changes, we record the current font, which is then merged
# into the Whatsit's created for letters, etc. The merging depends on
# the type of letter, greek, symbol, etc.
# Apparently, with the normal TeX setup, these fonts don't really merge,
# rather they override all of family, series and shape.
DefConstructor('\mathrm{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'serif', series => 'medium', shape => 'upright' });
DefConstructor('\mathit{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { shape => 'italic', family => 'serif', series => 'medium' });
DefConstructor('\mathbf{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { series => 'bold', family => 'serif', shape => 'upright' });
DefConstructor('\mathsf{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'sansserif', series => 'medium', shape => 'upright' });
DefConstructor('\mathtt{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'typewriter', series => 'medium', shape => 'upright' });
DefConstructor('\mathcal{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'caligraphic', series => 'medium', shape => 'upright' });
DefConstructor('\mathscr{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'script', series => 'medium', shape => 'upright' });
DefConstructor('\mathnormal{}', '#1', bounded => 1, requireMath => 1, locked => 1,
font => { family => 'math', shape => 'italic', series => 'medium' });
DefMacroI('\fontsubfuzz', undef, '.4pt');
DefMacroI('\oldstylenums', undef, Tokens());
DefPrimitiveI('\operator@font', undef, undef,
font => { family => 'serif', series => 'medium', shape => 'upright' });
#**********************************************************************
# C.8 Definitions, Numbering and Programming
#**********************************************************************
#======================================================================
# C.8.1 Defining Commands
#======================================================================
DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname');
DefPrimitive('\newcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
if (!isDefinable($cs)) {
Info('ignore', $cs, $stomach,
"Ignoring redefinition (\\newcommand) of '" . ToString($cs) . "'")
unless LookupValue(ToString($cs) . ':locked');
return; }
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
DefPrimitive('\CheckCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', undef);
DefPrimitive('\renewcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
# low-level implementation of both \newcommand and \renewcommand depends on \@argdef
# and robustness upgrades are often realized via redefining \l@ngrel@x
DefPrimitive('\@argdef DefToken []{}', sub {
DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });
DefPrimitive('\@xargdef DefToken [][]{}', sub {
DefMacroI($_[1], convertLaTeXArgs($_[2], $_[3]), $_[4]); });
DefPrimitive('\@yargdef DefToken DefToken {}{}', sub {
DefMacroI($_[1],
(T_CS('\tw@')->equals($_[2])
? convertLaTeXArgs($_[3], Tokens())
: convertLaTeXArgs($_[3])),
$_[4]); });
DefPrimitive('\@reargdef DefToken []{}', sub {
DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); });
DefPrimitive('\providecommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
return unless isDefinable($cs);
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
# Crazy; define \cs in terms of \cs[space] !!!
DefPrimitive('\DeclareRobustCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body, robust => 1); });
DefPrimitive('\MakeRobust DefToken', sub {
my ($stomach, $cs) = @_;
my $mungedcs = T_CS($cs->getString . ' ');
if (!LookupDefinition($cs)) { } # Not defined
elsif (LookupDefinition($mungedcs)) { } # Already robust
else {
Let($mungedcs, $cs);
DefMacroI($cs, undef, Tokens(T_CS('\protect'), $mungedcs)); } });
# There are a bunch of ways in LaTeX to assign a command to
# a particular point within a font (which has one of many encodings)
# Since we have no practical way of knowing what that point is,
# and we really want to create unicode, we just ignore this stuff (but warn).
sub ignoredDefinition {
my ($stomach, $command, $cs) = @_;
# Warn('ignore',$cs,$stomach,"ignoring ".ToString($command)." definition of ".ToString($cs));
return; }
#------------------------------------------------------------
# The following commands define encoding-specific expansions
# or glyphs. The control-sequence is defined to use the expansion for
# the current encoding, if any, or the default expansion (for encoding "?").
# We don't want to redefine control-sequence if it already has a definition:
# It may be that we've already defined it to expand into the above conditional.
# But more importantly, we don't want to override a hand-written definition (if any).
#------------------------------------------------------------
DefPrimitive('\DeclareTextCommand DefToken {}[Number][]{}', sub {
my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
my $css = ToString($cs);
$encoding = ToString(Expand($encoding));
if (!IsDefined($cs)) { # If not already defined...
DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
. '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
my $ecs = T_CS('\\' . $encoding . $css);
DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion);
return; });
DefMacro('\DeclareTextCommandDefault DefToken', '\DeclareTextCommand{#1}{?}');
DefPrimitive('\ProvideTextCommand DefToken {}[Number][]{}', sub {
my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_;
my $css = ToString($cs);
$encoding = ToString(Expand($encoding));
if (isDefinable($cs)) { # If not already defined...
DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
. '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
my $ecs = T_CS('\\' . $encoding . $css);
if (!IsDefined($ecs)) { # If not already defined...
DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); }
return; });
DefMacro('\ProvideTextCommandDefault DefToken', '\ProvideTextCommand{#1}{?}');
#------------------------------------------------------------
DefPrimitive('\DeclareTextSymbol DefToken {}{Number}', sub {
my ($gullet, $cs, $encoding, $code) = @_;
$code = $code->valueOf;
my $css = ToString($cs);
$encoding = ToString(Expand($encoding));
if (isDefinable($cs)) { # If not already defined...
DefMacroI($cs, undef,
'\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname'
. '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); }
my $ecs = T_CS('\\' . $encoding . $css);
DefPrimitiveI($ecs, undef, FontDecode($code, $encoding));
return; });
# hmmm... what needs doing here; basically it means use this encoding as the default for the symbol
DefMacro('\DeclareTextSymbolDefault DefToken {}', ''); # '\DeclareTextSymbol{#1}{?}');
#------------------------------------------------------------
DefPrimitive('\DeclareTextAccent DefToken {}{}', sub {
ignoredDefinition('DeclareTextAccent', $_[1]); });
DefPrimitive('\DeclareTextAccentDefault{}{}',
sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); });
#------------------------------------------------------------
# TODO: Need to convert these DefPrimitive's into DefConstroctors
# that add a preable PI!!!!!!!!!
DefPrimitive('\DeclareTextComposite{}{}{}{}',
sub { ignoredDefinition('DeclareTextComposite', $_[1]); });
DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}',
sub { ignoredDefinition('DeclareTextCompositeCommand', $_[1]); });
DefPrimitive('\UndeclareTextCommand{}{}', undef);
DefMacro('\UseTextSymbol{}{}', '{\fontencoding{#1}#2}');
DefMacro('\UseTextAccent{}{}', '{\fontencoding{#1}#2{#3}}');
DefPrimitive('\DeclareMathAccent DefToken {}{} {Number}', sub {
my ($stomach, $cs, $kind, $class, $code) = @_;
$class = ToString($class);
my $info = LookupValue('fontdeclaration@' . $class);
my $glyph = FontDecode($code->valueOf, ($info ? $$info{encoding} : $class));
DefMathI($cs, 'Digested', $glyph, operator_role => 'OVERACCENT');
return AddToPreamble('\DeclareMathAccent', $cs, $kind, $class, $code); });
DefPrimitive('\DeclareMathDelimiter{}{}{}{}',
sub { ignoredDefinition('DeclareMathDelimiter', $_[1]); });
DefPrimitive('\DeclareMathRadical{}{}{}{}{}',
sub { ignoredDefinition('DeclareMathRadical', $_[1]); });
DefPrimitive('\DeclareMathVersion{}', undef);
DefPrimitive('\DeclarePreloadSizes{}{}{}{}{}', undef);
# The next font declaration commands are based on
# http://tex.loria.fr/general/new/fntguide.html
# we ignore font encoding
DefPrimitive('\DeclareSymbolFont{}{}{}{}{}', sub {
my ($stomach, $name, $enc, $family, $series, $shape) = @_;
AssignValue('fontdeclaration@' . ToString($name),
{ family => ToString($family),
series => ToString($series),
shape => ToString($shape),
encoding => ToString($enc) }); });
DefPrimitive('\DeclareSymbolFontAlphabet{}{}', sub {
my ($stomach, $cs, $name) = @_;
my $font = LookupValue('fontdeclarations@' . ToString($name)) || {};
DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => $font); });
DefPrimitive('\DeclareMathSizes{}{}{}{}', undef);
DefPrimitive('\DeclareMathAlphabet{}{}{}{}{}', sub {
my ($stomach, $cs, $enc, $family, $series, $shape) = @_;
my $csname = ToString($cs);
# We won't override this, e.g. \mathrm by fouriernc.sty
if (IsDefined($csname)) {
Info('ignore', $csname, $stomach,
"Ignoring redefinition (\\DeclareMathAlphabet) of '" . $csname . "'"); }
else {
my %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape);
DefPrimitiveI(T_CS($csname), undef, undef, font => {%font}); }
return; });
DefMacro('\newmathalphabet{}{}{}', Tokens()); # or expand int DeclareMathAlphabet?
DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareFontFamily{}{}{}', undef);
DefPrimitive('\DeclareSizeFunction{}{}', undef);
my $symboltype_roles = {
'\mathord' => 'ID', '\mathop' => 'BIGOP', '\mathbin' => 'BINOP', '\mathrel' => 'RELOP',
'\mathopen' => 'OPEN', '\mathclose' => 'CLOSE', '\mathpunct' => 'PUNCT' };
DefPrimitive('\DeclareMathSymbol DefToken SkipSpaces DefToken {}{Number}', sub {
my ($stomach, $cs, $type, $font, $code) = @_;
my $encoding = ToString($font); # Or maybe just a font name or class?
if (my $decl = LookupValue('fontdeclaration@' . $encoding)) {
$encoding = $$decl{encoding} if $$decl{encoding}; }
my $glyph = FontDecode($code->valueOf, $encoding);
my $role = $$symboltype_roles{ ToString($type) };
DefMathI($cs, undef, $glyph, role => $role);
return; });
DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', sub { DefMacroI($_[1], undef, T_CS('\relax')); return; });
DefPrimitive('\DeclareErrorFont{}{}{}{}{}', sub { DefMacroI($_[1], undef, T_CS('\relax')); return; });
DefMacroI('\cdp@list', undef, '\@empty');
Let('\cdp@elt', '\relax');
DefPrimitive('\DeclareFontEncoding{}{}{}', sub {
my ($stomach, $encoding, $x, $y) = @_;
AddToMacro(T_CS('\cdp@list'), T_CS('\cdp@elt'),
T_BEGIN, $_[1]->unlist, T_END,
T_BEGIN, T_CS('\default@family'), T_END,
T_BEGIN, T_CS('\default@series'), T_END,
T_BEGIN, T_CS('\default@shape'), T_END);
my $encoding_expanded = Expand($encoding);
my $encoding_str = ToString($encoding_expanded);
DefMacroI('\LastDeclaredEncoding', undef, $encoding_expanded);
DefMacroI('\T@' . $encoding_str, undef, $x);
DefMacroI('\M@' . $encoding_str, undef, Tokens(T_CS('\default@M'), $y->unlist));
if (my $path = $encoding_str && FindFile(lc($encoding_str) . "enc", type => "dfu")) {
InputDefinitions($path);
}
return;
});
DefMacroI('\LastDeclaredEncoding', undef, '');
DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef);
DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef);
DefPrimitive('\DeclareEncodingSubset{}{}{}', undef);
DefMacroI('\LastDeclaredEncoding', undef, Tokens());
DefPrimitive('\SetSymbolFont{}{}{}{}{}{}', undef);
DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef);
DefPrimitive('\addtoversion{}{}', undef);
DefPrimitive('\TextSymbolUnavailable{}', undef);
RawTeX(<<'EoTeX');
\DeclareSymbolFont{operators} {OT1}{cmr} {m}{n}
\DeclareSymbolFont{letters} {OML}{cmm} {m}{it}
\DeclareSymbolFont{symbols} {OMS}{cmsy}{m}{n}
\DeclareSymbolFont{largesymbols}{OMX}{cmex}{m}{n}
EoTeX
# not sure what these should be, related to \nullfont...
DefPrimitiveI('\OMX', undef, undef, font => { family => 'cmex10' });
DefPrimitiveI('\tenln', undef, undef, font => { family => 'line10' });
DefPrimitiveI('\tenlnw', undef, undef, font => { family => 'linew10' });
DefPrimitiveI('\tencirc', undef, undef, font => { family => 'lcircle10' });
DefPrimitiveI('\tencircw', undef, undef, font => { family => 'lcirclew10' });
# At least all things on uclclist need to be macros
DefPrimitiveI('\lx@utf@OE', undef, "\x{0152}", alias => '\OE'); # LATIN CAPITAL LIGATURE OE
DefPrimitiveI('\lx@utf@oe', undef, "\x{0153}", alias => '\oe'); # LATIN SMALL LIGATURE OE
DefPrimitiveI('\lx@utf@AE', undef, UTF(0xC6), alias => '\AE'); # LATIN CAPITAL LETTER AE
DefPrimitiveI('\lx@utf@ae', undef, UTF(0xE6), alias => '\ae'); # LATIN SMALL LETTER AE
DefPrimitiveI('\lx@utf@AA', undef, UTF(0xC5), alias => '\AA'); # LATIN CAPITAL LETTER A WITH RING ABOVE
DefPrimitiveI('\lx@utf@aa', undef, UTF(0xE5), alias => '\aa'); # LATIN SMALL LETTER A WITH RING ABOVE
DefPrimitiveI('\lx@utf@O', undef, UTF(0xD8), alias => '\O'); # LATIN CAPITAL LETTER O WITH STROKE
DefPrimitiveI('\lx@utf@o', undef, UTF(0xF8), alias => '\o'); # LATIN SMALL LETTER O WITH STROKE
DefPrimitiveI('\lx@utf@L', undef, "\x{0141}", alias => '\L'); # LATIN CAPITAL LETTER L WITH STROKE
DefPrimitiveI('\lx@utf@l', undef, "\x{0142}", alias => '\l'); # LATIN SMALL LETTER L WITH STROKE
DefPrimitiveI('\lx@utf@ss', undef, UTF(0xDF), alias => '\ss'); # LATIN SMALL LETTER SHARP S
DefPrimitiveI('\lx@utf@dh', undef, UTF(0xf0), alias => '\dh'); # eth
DefPrimitiveI('\lx@utf@DH', undef, UTF(0xd0), alias => '\DH'); # Eth (looks same as \DJ!)
DefPrimitiveI('\lx@utf@dj', undef, "\x{0111}", alias => '\dj'); # d with stroke
DefPrimitiveI('\lx@utf@DJ', undef, "\x{0110}", alias => '\DJ'); # D with stroke (looks sames as \DH!)
DefPrimitiveI('\lx@utf@ng', undef, "\x{014B}", alias => '\ng');
DefPrimitiveI('\lx@utf@NG', undef, "\x{014A}", alias => '\NG');
DefPrimitiveI('\lx@utf@th', undef, UTF(0xFE), alias => '\th');
DefPrimitiveI('\lx@utf@TH', undef, UTF(0xDE), alias => '\TH');
DefMacroI('\OE', undef, '\lx@utf@OE');
DefMacroI('\oe', undef, '\lx@utf@oe');
DefMacroI('\AE', undef, '\lx@utf@AE');
DefMacroI('\ae', undef, '\lx@utf@ae');
DefMacroI('\ae', undef, '\lx@utf@ae');
DefMacroI('\AA', undef, '\lx@utf@AA');
DefMacroI('\aa', undef, '\lx@utf@aa');
DefMacroI('\O', undef, '\lx@utf@O');
DefMacroI('\o', undef, '\lx@utf@o');
DefMacroI('\L', undef, '\lx@utf@L');
DefMacroI('\l', undef, '\lx@utf@l');
DefMacroI('\ss', undef, '\lx@utf@ss');
DefMacroI('\dh', undef, '\lx@utf@dh'); # in latex?
DefMacroI('\DH', undef, '\lx@utf@DH');
DefMacroI('\dj', undef, '\lx@utf@dj');
DefMacroI('\DJ', undef, '\lx@utf@DJ');
DefMacroI('\ng', undef, '\lx@utf@ng');
DefMacroI('\NG', undef, '\lx@utf@NG');
DefMacroI('\th', undef, '\lx@utf@th');
DefMacroI('\TH', undef, '\lx@utf@TH');
#======================================================================
# C.8.2 Defining Environments
#======================================================================
# Note that \env & \endenv defined by \newenvironment CAN be
# invoked directly.
DefPrimitive('\newenvironment OptionalMatch:* {}[Number][]{}{}', sub {
my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
$name = ToString(Expand($name));
if (IsDefined(T_CS("\\$name"))) {
Info('ignore', $name, $stomach,
"Ignoring redefinition (\\newenvironment) of Environment '$name'")
unless (LookupValue('\\' . $name . ':locked')
|| LookupValue('\\begin{' . $name . '}:locked'));
return; }
DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin);
DefMacroI(T_CS("\\end$name"), undef, $end);
return; });
DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub {
my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
$name = ToString(Expand($name));
if (!LookupValue("\\$name:locked")
&& !LookupValue("\\begin{$name}:locked")) {
DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin);
DefMacroI(T_CS("\\end$name"), undef, $end); }
return; });
#======================================================================
# C.8.3 Theorem-like Environments
#======================================================================
# The core non-customizable part is defined here.
# For customizable theorems, see amsthm.
AssignValue('thm@swap' => 0);
DefRegister('\thm@style' => Tokenize('plain'));
DefRegister('\thm@headfont' => Tokens(T_CS('\bfseries')));
DefRegister('\thm@notefont' => Tokens(T_CS('\the'), T_CS('\thm@headfont')));
DefRegister('\thm@bodyfont' => Tokens(T_CS('\itshape')));
DefRegister('\thm@headformatter' => Tokens());
DefRegister('\thm@headpunct' => Tokens());
DefRegister('\thm@styling' => Tokens());
DefRegister('\thm@headstyling' => Tokens());
DefRegister('\thm@prework' => Tokens());
DefRegister('\thm@postwork' => Tokens());
DefRegister('\thm@symbol' => Tokens());
DefRegister('\thm@numbering' => Tokens(T_CS('\arabic')));
DefPrimitive('\th@plain', sub {
AssignRegister('\thm@bodyfont' => T_CS('\itshape'));
AssignRegister('\thm@headstyling' => T_CS('\lx@makerunin'));
return; });
DefMacroI('\lx@makerunin', undef, '\@ADDCLASS{ltx_runin}');
DefMacroI('\lx@makeoutdent', undef, '\@ADDCLASS{ltx_outdent}');
DefMacroI('\@thmcountersep', undef, '.');
DefMacroI('\thm@doendmark', undef, '');
DefPrimitive('\newtheorem OptionalMatch:* {}[]{}[]', sub {
my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
defineNewTheorem($stomach, $flag, $thmset, $otherthmset, $type, $reset);
# Reset these!
DefRegister('\thm@prework' => Tokens());
DefRegister('\thm@postwork' => Tokens()); },
locked => 1);
# These record which style parameters CAN BE saved/restored for each style.
# (kinda weird, but different packages use different sets; particularly \thm@headfont)
# THEOREM_<theoremname>_PARAMETERS will store the actual values for those parameters
AssignValue('SAVABLE_THEOREM_PARAMETERS' => {});
sub setSavableTheoremParameters {
my (@keys) = @_; # Noe that these are saved for each theorem type.
AssignValue('SAVABLE_THEOREM_PARAMETERS' => { map { $_ => 1; } @keys });
return; }
# Following amsthm's model, the various theorem customizations are stored in token registers
# Some packages may want to add '\thm@headfont' (or exclude it)
setSavableTheoremParameters(qw(
\thm@bodyfont \thm@headpunct \thm@styling \thm@headstyling thm@swap));
sub useTheoremStyle {
my ($name) = @_;
my $savable = LookupValue('SAVABLE_THEOREM_PARAMETERS');
if (my $params = LookupValue('THEOREM_' . ToString($name) . '_PARAMETERS')) {
for my $param_key (keys %$savable) {
if (exists $$params{$param_key}) {
if ($param_key =~ /^\\/) {
AssignRegister($param_key => $$params{$param_key}); }
else {
AssignValue($param_key => $$params{$param_key}); } } } }
return; }
sub saveTheoremStyle {
my ($name, %parameters) = @_;
$name = ToString($name);
AssignValue('THEOREM_' . $name . '_PARAMETERS' => {%parameters}, 'global');
return; }
RawTeX('\th@plain'); # Activate the default style.
# [or just make it's values the defaults...]
Tag('ltx:theorem', autoClose => 1);
Tag('ltx:proof', autoClose => 1);
sub defineNewTheorem {
my ($stomach, $flag, $thmset, $otherthmset, $type, $within) = @_;
$thmset = ToString($thmset);
my $classname = CleanClassName($thmset);
my $listname = "theorem:$thmset";
# Do we need a Cleaner for constructor bits?
$listname =~ s/\s+//g;
$listname =~ s/'/prime/g;
$listname =~ s/\?/question/g;
$listname =~ s/\#/hash/g;
$otherthmset = $otherthmset && ToString($otherthmset);
$type = undef if IsEmpty($type);
$within = $within ? ToString(Digest($within)) : undef;
my $counter = $otherthmset || $thmset;
$counter =~ s/\s+/./; # convert spaces to period?
if ($counter ne $thmset) {
AssignMapping('counter_for_type', $thmset => $counter);
DefMacroI(T_CS('\the' . $thmset), undef, T_CS('\the' . $counter), scope => 'global');
}
my $style = ToString(LookupRegister('\thm@style')); # The name of the style
my $numbering = ToString(LookupRegister('\thm@numbering')); # The number style macro
$flag = 1 unless $numbering;
if (!$otherthmset) {
my $idprefix = "Thm$classname";
$idprefix =~ s/\*/./g;
if (!LookupDefinition(T_CS('\c@' . $counter))) { # if isn't already defined...
NewCounter($counter, $within, idprefix => $idprefix); }
DefMacroI(T_CS("\\the$counter"), undef,
($within
? "\\csname the" . $within . "\\endcsname\\\@thmcountersep" . $numbering . "{" . $counter . "}"
: $numbering . "{" . $counter . "}"),
scope => 'global') if $numbering; }
my %parameters = %{ LookupValue('SAVABLE_THEOREM_PARAMETERS') };
my %saved = ();
foreach my $key (keys %parameters) {
$saved{$key} = ($key =~ /^\\/ ? LookupRegister($key) : LookupValue($key)); }
saveTheoremStyle($thmset, %saved);
my $thmname = '\\lx@name@' . $thmset;
DefMacroI($thmname, undef, $type, scope => 'global');
my $swap = LookupValue('thm@swap');
DefMacroI('\fnum@' . $thmset, undef,
Tokens($flag || !$counter
? (T_CS($thmname))
: ($swap
? (T_CS('\the' . $counter), ($type ? (T_SPACE) : ()), T_CS($thmname))
: (T_CS($thmname), ($type ? (T_SPACE) : ()), T_CS('\the' . $counter)))),
scope => 'global');
# amsthm allows you to define your own title formatter
# if there was one defined, use it, else define a new one.
my $headformatter = LookupRegister('\thm@headformatter');
DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0),
(!IsEmpty($headformatter)
? Tokens(
T_CS('\the'), T_CS('\thm@headfont'),
$headformatter->unlist,
T_BEGIN, ($type ? $type->unlist : ()), T_END,
T_CS('\the' . $counter), T_BEGIN, Tokens(T_PARAM, T_OTHER('1')), T_END,
T_CS('\the'), T_CS('\thm@headpunct'))
: '{\the\thm@headfont\lx@tag{\csname fnum@' . $thmset . '\endcsname}'
. '{' . ($type ? '\ifx.#1.\else\space\the\thm@notefont(#1)\fi' : '#1') . '}'
. '\the\thm@headpunct}'),
scope => 'global');
DefEnvironmentI($thmset, "OptionalUndigested",
"<ltx:theorem xml:id='#id' inlist='thm $listname' class='ltx_theorem_$classname'>"
. "#tags"
. "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
. "#body",
afterConstruct => sub { $_[0]->maybeCloseElement('ltx:theorem'); },
beforeDigest => sub {
useTheoremStyle($thmset);
Digest('\normalfont\the\thm@prework'); },
afterDigestBegin => sub {
my $name = $_[1]->getArg(1);
Digest('\the\thm@bodyfont\the\thm@styling'
. '\def\lx@thistheorem{' . ($name ? UnTeX($name) : '') . '}'); },
beforeDigestEnd => sub { Digest('\thm@doendmark\the\thm@postwork'); },
properties => sub {
my %ctr = ();
if ($counter) {
if ($flag) {
%ctr = RefStepID($counter);
# Need(?) the refnum
if ($type) {
$ctr{tags} = Digest(T_BEGIN,
T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'),
Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)),
T_END);
} }
else {
%ctr = RefStepCounter($thmset); } }
my $title = Digest(Tokens(T_BEGIN,
T_CS('\the'), T_CS('\thm@headstyling'),
T_CS('\format@title@' . $thmset), #T_END,
T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END,
T_END));
(%ctr,
title => $title,
titlefont => $title->getFont,
); },
scope => 'global');
return; }
#======================================================================
# C.8.4 Numbering
#======================================================================
# For LaTeX documents, We want id's on para, as well as sectional units.
# However, para get created implicitly on Document construction, rather than
# explicitly during digestion (via a whatsit), we can't use the usual LaTeX counter mechanism.
Tag('ltx:para', afterOpen => sub { GenerateID(@_, 'p'); });
DefPrimitive('\newcounter{}[]', sub {
NewCounter(ToString(Expand($_[1])), $_[2] && ToString(Expand($_[2])));
return; });
Let(T_CS('\@definecounter'), T_CS('\newcounter'));
DefPrimitive('\setcounter{}{Number}', sub { SetCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\addtocounter{}{Number}', sub { AddToCounter(ToString(Expand($_[1])), $_[2]); });
DefPrimitive('\stepcounter{}', sub { StepCounter(ToString(Expand($_[1]))); return; });
DefPrimitive('\refstepcounter{}', sub { RefStepCounter(ToString(Expand($_[1]))); return; });
sub addtoCounterReset {
my ($ctr, $within) = @_;
my $reg = "\\cl\@$within";
my $t = T_CS($ctr);
my $unt = T_CS('UN' . $ctr);
my $prevreset = LookupValue($reg);
AssignValue($reg => Tokens($t, $unt, ($prevreset ? $prevreset->unlist : ())), 'global');
return; }
sub remfromCounterReset {
my ($ctr, $within) = @_;
my $reg = "\\cl\@$within";
my $t = T_CS($ctr);
my $unt = T_CS('UN' . $ctr);
my $prevreset = LookupValue($reg);
AssignValue($reg => Tokens(grep { !$t->equals($_) && !$unt->equals($_) } $prevreset->unlist),
'global') if $prevreset;
return; }
sub defCounterID {
my ($ctr, $within) = @_;
my $prefix = LookupValue('@ID@prefix@' . $ctr);
if (defined $prefix) {
if ($within) {
DefMacroI(T_CS("\\the$ctr\@ID"), undef,
"\\expandafter\\ifx\\csname the$within\@ID\\endcsname\\\@empty"
. "\\else\\csname the$within\@ID\\endcsname.\\fi"
. " $prefix\\csname \@$ctr\@ID\\endcsname",
scope => 'global'); }
else {
DefMacroI(T_CS("\\the$ctr\@ID"), undef, "$prefix\\csname \@$ctr\@ID\\endcsname",
scope => 'global'); } }
return; }
DefPrimitive('\@addtoreset{}{}', sub {
my ($stomach, $ctr, $within) = @_;
$ctr = ToString(Expand($ctr));
$within = ToString(Expand($within));
addtoCounterReset($ctr, $within);
defCounterID($ctr, $within) unless LookupDefinition(T_CS("\\the$ctr\@ID"));
return; });
DefMacro('\value{}', sub {
T_CS("\\c@" . ToString(Expand($_[1]))) });
DefMacro('\@arabic{Number}', sub {
ExplodeText(ToString($_[1]->valueOf)); });
DefMacro('\arabic{}', sub {
ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
DefMacro('\@roman{Number}', sub {
ExplodeText(radix_roman(ToString($_[1]->valueOf))); });
DefMacro('\roman{}', sub {
ExplodeText(radix_roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Roman{Number}', sub {
ExplodeText(radix_Roman(ToString($_[1]->valueOf))); });
DefMacro('\Roman{}', sub {
ExplodeText(radix_Roman(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@alph{Number}', sub {
ExplodeText(radix_alpha($_[1]->valueOf)); });
DefMacro('\alph{}', sub {
ExplodeText(radix_alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
DefMacro('\@Alph{Number}', sub {
ExplodeText(radix_Alpha($_[1]->valueOf)); });
DefMacro('\Alph{}', sub {
ExplodeText(radix_Alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); });
our @fnsymbols = ("*", "\x{2020}", "\x{2021}", UTF(0xA7), UTF(0xB6),
"\x{2225}", "**", "\x{2020}\x{2020}", "\x{2021}\x{2021}");
DefMacro('\@fnsymbol{Number}', sub {
ExplodeText(radix_format($_[1]->valueOf, @fnsymbols)); });
DefMacro('\fnsymbol{}', sub {
ExplodeText(radix_format(CounterValue(ToString(Expand($_[1])))->valueOf, @fnsymbols)); });
# The following two macros were originally defined in chngcntr.sty,
# but were moved to LaTeX core in 2018.
DefPrimitive('\counterwithin OptionalMatch:* {}{}', sub {
my ($stomach, $starred, $ctr, $within) = @_;
$ctr = ToString($ctr);
$within = ToString($within);
addtoCounterReset($ctr, $within);
if (!$starred) {
DefMacroI(T_CS("\\the$ctr"), undef, "\\the$within.\\arabic{$ctr}", scope => 'global');
defCounterID($ctr, $within); }
return; });
DefPrimitive('\counterwithout OptionalMatch:* {}{}', sub {
my ($stomach, $starred, $ctr, $within) = @_;
$ctr = ToString($ctr);
$within = ToString($within);
remfromCounterReset($ctr, $within);
if (!$starred) {
DefMacroI(T_CS("\\the$ctr"), undef, "\\arabic{$ctr}", scope => 'global');
defCounterID($ctr); }
return; });
DefPrimitive('\@removefromreset{}{}', sub {
my ($stomach, $ctr, $within) = @_;
$ctr = ToString($ctr);
$within = ToString($within);
remfromCounterReset($ctr, $within);
# Should \the$ctr@ID be redefined? It really should track changes to \the$ctr!
# defCounterID($ctr);
return; });
DefMacroI('\cl@@ckpt', undef, '\@elt{page}');
#======================================================================
# C.8.5 The ifthen Package.
#======================================================================
# \ifthenelse
# and sundry conditionals...
#
# Yeah, maybe this'll get done someday....
#**********************************************************************
# C.9 Figures and Other Floating Bodies
#**********************************************************************
#======================================================================
# C.9.1 Figures and Tables
#======================================================================
# Note that, the number is associated with the caption.
# (to allow multiple figures per figure environment?).
# Whatever reason, that causes complications: We can only increment
# counters with the caption, but then have to arrange for the counters,
# refnums, ids, get passed on to the figure, table when needed.
# AND, as soon as possible, since other items may base their id's on the id of the table!
# Let the fonts for float be the default for all floats, figures, tables, etc.
DefMacro('\fnum@font@float', '\@empty');
DefMacro('\format@title@font@float', '\@empty');
DefMacro('\fnum@font@figure', '\fnum@font@float');
DefMacro('\fnum@font@table', '\fnum@font@float');
DefMacro('\format@title@font@figure', '\format@title@font@float');
DefMacro('\format@title@font@table', '\format@title@font@float');
# stubs for the latex float internals
DefEnvironmentI('@float', '[]{}',
"<ltx:float xml:id='#id' inlist='#inlist' ?#1(placement='#1') class='ltx_float_#2'>"
. "#body"
. "</ltx:float>",
properties => { layout => 'vertical' },
beforeDigestBegin => sub { beforeFloat(ToString($_[1]->getArg(2))); },
afterDigest => sub { afterFloat($_[1]); });
DefEnvironmentI('@dblfloat', '[]{}',
"<ltx:float xml:id='#id' inlist='#inlist' ?#1(placement='#1') class='ltx_float_#2'>"
. "#body"
. "</ltx:float>",
properties => { layout => 'vertical' },
beforeDigestBegin => sub { beforeFloat(ToString($_[1]->getArg(2)), double => 1); },
afterDigest => sub { afterFloat($_[1]); });
# Could perhaps parameterize further with a separator?
DefMacro('\format@title@figure{}', '\lx@tag[][: ]{\lx@fnum@@{figure}}#1');
DefMacro('\format@title@table{}', '\lx@tag[][: ]{\lx@fnum@@{table}}#1');
DefMacro('\ext@figure', 'lof');
DefMacro('\ext@table', 'lot');
DefConditional('\iflx@donecaption');
DefMacro('\caption',
'\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}');
# First, check for trailing \label, move it into the caption as a standard position
# NOTE: If one day we want to unlock \@caption, make sure to test against arXiv:cond-mat/0001395 for a passing build.
DefMacro('\@caption{}[]{}',
'\@ifnext\label{\@caption@postlabel{#1}{#2}{#3}}{\@caption@{#1}{#2}{#3}}', locked => 1);
DefMacro('\@caption@postlabel{}{}{} SkipMatch:\label Semiverbatim',
'\@caption@{#1}{#2}{#3\label{#4}}');
# Now, check for \label(s) inside to (potentially) record them.
DefMacro('\@caption@{}{}{}',
'\@hack@caption@{#1}{#2}{}#3\label\endcaption');
DefMacro('\@hack@caption@{}{}{} Until:\label Until:\endcaption',
'\ifx.#5.\@caption@@@{#1}{#2}{#3#4}' # No more labels
. '\else\@@@hack@caption@{#1}{#2}{#3#4}#5\endcaption\fi');
DefMacro('\@@@hack@caption@{}{}{} Semiverbatim Until:\label Until:\endcaption',
'\lx@note@caption@label{#4}' .
'\@hack@caption@{#1}{#2}{#3\label{#4}#5}\label#6\endcaption');
DefPrimitive('\lx@note@caption@label{}', sub { MaybeNoteLabel($_[1]); });
DefMacro('\@caption@@@{}{}{}',
'\@@add@caption@counters'
. '\@@toccaption{\lx@format@toctitle@@{#1}{\ifx.#2.#3\else#2\fi}}'
. '\@@caption{\lx@format@title@@{#1}{#3}}');
# Note that the counters only get incremented by \caption, NOT by \table, \figure, etc.
DefPrimitive('\@@add@caption@counters', sub {
my $captype = ToString(Digest(T_CS('\@captype')));
my $pre = LookupValue('PREINCREMENTED_' . $captype); # If pre-incremented by beforeFloat
my %props = ($pre ? %$pre : RefStepCounter($captype));
my $inlist = ToString(Digest(T_CS('\ext@' . $captype)));
AssignValue('PREINCREMENTED_' . $captype => undef, 'global');
AssignValue($captype . "_tags" => $props{tags}, 'global');
AssignValue($captype . "_id" => $props{id}, 'global');
AssignValue($captype . "_inlist" => $inlist, 'global'); });
sub RescueCaptionCounters {
my ($captype, $whatsit) = @_;
if (my $tags = LookupValue($captype . "_tags")) {
AssignValue($captype . "_tags" => undef, 'global');
$whatsit->setProperty(tags => $tags); }
if (my $id = LookupValue($captype . "_id")) {
AssignValue($captype . "_id" => undef, 'global');
$whatsit->setProperty(id => $id); }
if (my $inlist = LookupValue($captype . "_inlist")) {
AssignValue($captype . "_inlist" => undef, 'global');
$whatsit->setProperty(inlist => $inlist); }
return; }
DefConstructor('\@@generic@caption[]{}', "<ltx:text class='ltx_caption'>#2</ltx:text>",
beforeDigest => sub {
Error('unexpected', '\caption', $_[0],
"Use of \\caption outside any known float"); });
our $FIGURE_PANEL_CLASS = 'ltx_figure_panel';
# most block-level elements are expected horizontally standalone in a {figure}.
# (especially ones that directly match LaTeX {envs})
our %standalone_panel_names = map { $_ => 1 }
qw(ltx:p ltx:listing ltx:math ltx:itemize ltx:enumerate ltx:quote ltx:theorem ltx:proof
ltx:description ltx:equation ltx:equationgroup ltx:verbatim);
sub arrange_panels_and_breaks {
my ($document, $node, $whatsit) = @_;
my $float_width = ($whatsit->getProperty('floatwidth') || Dimension('345pt'))->valueOf();
# Note that 0.0625x minimal panel width will naturally enforce a limit of max 16 panels per row.
# except that we are currently under-sizing(?) some individual subfigures, so enforce max 32.
my $min_panel_width = 0.03125 * $float_width;
my $current_width = 0;
my @all_panels = ();
my @all_contents = ();
# row contents will bookkeep triples of [node, name(optional), width(optional)]
my @row_contents = ();
my $panels_found = 0;
my $panel_break_names = LookupValue('figure_panel_break_names');
if (!$panel_break_names) {
# initialize schema-based tag names on first call for a given document;
my $model = $STATE->getModel;
$panel_break_names = { map { $_ => 1 } ('ltx:break',
$model->getSchemaClassNames('Meta'),
$model->getSchemaClassNames('SectionalFrontMatter'),
lib/LaTeXML/Package/LaTeX.pool.ltxml view on Meta::CPAN
$child->unbindNode;
$prev_node->appendChild($child);
push(@row_contents, [$prev_node, $prev_name, $prev_width + $child_width]);
} elsif ($child_name eq 'ltx:block') {
$prev_node->unbindNode;
$child->appendChild($prev_node);
push(@row_contents, [$child, $child_name, $prev_width + $child_width]);
push(@all_panels, $child);
} else { # create a new block to hold the two siblings.
my $block = $document->wrapNodes('ltx:block', $prev_node, $child);
push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]);
pop(@all_panels); push(@all_panels, $block);
} }
else {
# otherwise keep the last panel as-is, and append a sibling
push(@row_contents, $prev_panel);
push(@row_contents, [$child, $child_name, $child_width]);
push(@all_panels, $child); } }
else { # no last panel? just add the child
push(@row_contents, [$child, $child_name, $child_width]);
push(@all_panels, $child);
# yet if standalone, finalize current row content, and add a break
if ($is_standalone && @row_contents) {
push(@all_contents, map { $$_[0] } @row_contents);
if (my $trailer = $child->nextSibling) {
if (!$$panel_break_names{ $document->getNodeQName($trailer) }) {
my $break = $document->insertElementBefore($trailer, 'break', 'class' => 'ltx_break');
push(@all_contents, $break); } }
@row_contents = ();
$current_width = 0; } }
$current_width += $child_width; } } }
push(@all_contents, map { $$_[0] } @row_contents);
# simplify: if we only ever added 1 panel, remove its class (so that we only mark complex figures)
if (scalar(@all_panels) > 1) {
for my $panel (@all_panels) {
$document->addClass($panel, $FIGURE_PANEL_CLASS); } }
return @all_contents; }
sub BuildPanelsAndID {
my ($document, $node, $whatsit, $id_kind) = @_;
# 1. Each figure/table/float is comprised of 1 or more "panels", possibly separated by line-breaks,
# where multiple panels tend to correspond to subfigures/subtables/subfloats.
my @contents = arrange_panels_and_breaks($document, $node, $whatsit);
# 2. IDs
# Note that even without \caption, we'd probably like to have xml:id.
return GenerateID($document, $node, $whatsit, $id_kind); }
Tag('ltx:figure', afterClose => sub { BuildPanelsAndID(@_, 'fig'); });
Tag('ltx:float', afterClose => sub { BuildPanelsAndID(@_, 'tab'); });
Tag('ltx:table', afterClose => sub { BuildPanelsAndID(@_, 'tab'); });
# These may need to float up to where they're allowed,
# or they may need to close <p> or similar.
DefConstructor('\@@caption{}', "^^<ltx:caption>#1</ltx:caption>", sizer => 0);
DefConstructor('\@@toccaption{}', "^^<ltx:toccaption>#1</ltx:toccaption>",
sizer => 0);
sub beforeFloat {
my ($type, %options) = @_;
#Debug("FLOAT begin $type". join(',',map { $_."=".ToString($options{$_}); } sort keys %options));
DefMacroI('\@captype', undef, $type);
AssignRegister('\hsize' => LookupDimension($options{double} ? '\textwidth' : '\columnwidth'));
if (my $main = $options{preincrement}) {
if (($type ne (LookupValue('LAST_FLOATTYPE') || '')) # first of subtype?
&& !IfCondition('\iflx@donecaption')) { # If main caption NOT already appeared?
AssignValue('PREINCREMENTED_' . $main => { RefStepCounter($main) }, 'global'); } }
return; }
sub afterFloat {
my ($whatsit) = @_;
my $type = ToString(Expand(T_CS('\@captype')));
AssignValue('PREINCREMENTED_' . $type => undef, 'global');
#Debug("FLOAT end $type");
$whatsit->setProperty(floatwidth => LookupRegister('\hsize'));
RescueCaptionCounters($type, $whatsit);
AssignValue(LAST_FLOATTYPE => $type, 'global');
return; }
DefEnvironment('{figure}[]',
"<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
. "#tags"
. "#body"
. "</ltx:figure>",
properties => { layout => 'vertical' },
beforeDigest => sub { beforeFloat('figure'); },
afterDigest => sub { afterFloat($_[1]); },
locked => 1);
DefEnvironment('{figure*}[]',
"<ltx:figure xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
. "#tags"
. "#body"
. "</ltx:figure>",
properties => { layout => 'vertical' },
beforeDigest => sub { beforeFloat('figure', double => 1); },
afterDigest => sub { afterFloat($_[1]); },
locked => 1);
DefEnvironment('{table}[]',
"<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
. "#tags"
. "#body"
. "</ltx:table>",
properties => { layout => 'vertical' },
beforeDigest => sub { beforeFloat('table'); },
afterDigest => sub { afterFloat($_[1]); },
locked => 1);
DefEnvironment('{table*}[]',
"<ltx:table xml:id='#id' inlist='#inlist' ?#1(placement='#1')>"
. "#tags"
. "#body"
. "</ltx:table>",
properties => { layout => 'vertical' },
beforeDigest => sub { beforeFloat('table', double => 1); },
afterDigest => sub { afterFloat($_[1]); },
locked => 1);
# There are some floating figure/table packages that define environments
# to allow the figure to float left or right. It APPEARS that they allow
# authors to either wrap with {figure} or NOT, as they like!
# The result apparently makes sense so long as they only use one \caption.
# If we get a figure containing a single figure, we want to collaps it.
sub collapseFloat {
my ($document, $float) = @_;
my $qname = $document->getNodeQName($float);
my @inners = $document->findnodes($qname, $float);
if (scalar(@inners) == 1) { # Only 1 inner float of same type
my $inner = $inners[0];
my $ocaption = $document->findnodes('ltx:caption', $float);
my $icaption = $document->findnodes('ltx:caption', $inner);
if (!($ocaption && $icaption)) { # If they don't both have captions.
foreach my $attr (map { $_->nodeName } $inner->attributes) {
next if $attr eq 'xml:id';
$document->setAttribute($float, $attr => $inner->getAttribute($attr)); }
if ($icaption) {
if (my $id = $inner->getAttribute('xml:id')) {
$document->unRecordID($id);
$document->setAttribute($float, 'xml:id' => $id); } }
# Finally, replace $inner by it's children!
# Note that since we can only append new stuff, we've got to remove the following first.
my @save = ();
my $following;
while (($following = $float->lastChild) && ($$following != $$inner)) { # Remove & Save following siblings.
unshift(@save, $float->removeChild($following)); }
unshift(@save, $inner->childNodes);
$float->removeChild($inner);
map { $float->appendChild($_) } @save; } # Put these back.
}
return; }
Tag('ltx:figure', afterClose => \&collapseFloat);
Tag('ltx:table', afterClose => \&collapseFloat);
Tag('ltx:float', afterClose => \&collapseFloat);
DefPrimitive('\flushbottom', undef);
DefPrimitive('\suppressfloats[]', undef);
NewCounter('topnumber');
DefMacroI('\topfraction', undef, "0.25");
NewCounter('bottomnumber');
DefMacroI('\bottomfraction', undef, "0.25");
NewCounter('totalnumber');
DefMacroI('\textfraction', undef, "0.25");
DefMacroI('\floatpagefraction', undef, "0.25");
NewCounter('dbltopnumber');
DefMacroI('\dbltopfraction', undef, "0.7");
DefMacroI('\dblfloatpagefraction', undef, "0.25");
DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt'));
DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt'));
DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt'));
DefRegister('\@maxsep' => Dimension(0));
DefRegister('\@dblmaxsep' => Dimension(0));
DefRegister('\@fptop' => Glue(0));
DefRegister('\@fpsep' => Glue(0));
DefRegister('\@fpbot' => Glue(0));
DefRegister('\@dblfptop' => Glue(0));
DefRegister('\@dblfpsep' => Glue(0));
DefRegister('\@dblfpbot' => Glue(0));
DefRegister('\abovecaptionskip' => Glue(0));
DefRegister('\belowcaptionskip' => Glue(0));
Let('\topfigrule', '\relax');
Let('\botfigrule', '\relax');
Let('\dblfigrule', '\relax');
DefMacroI('\figurename', undef, 'Figure');
DefMacroI('\figuresname', undef, 'Figures'); # Never used?
DefMacroI('\tablename', undef, 'Table');
DefMacroI('\tablesname', undef, 'Tables');
Let('\outer@nobreak', '\@empty');
DefMacro('\@dbflt{}', '#1');
DefMacro('\@xdblfloat{}[]', '\@xfloat{#1}[#2]');
DefMacro('\@floatplacement', '');
DefMacro('\@dblfloatplacement', '');
#======================================================================
# C.9.2 Marginal Notes
#======================================================================
DefConditional('\if@reversemargin');
Let('\reversemarginpar', '\@reversemargintrue');
Let('\normalmarginpar', '\@reversemarginfalse');
DefConstructor('\marginpar[]{}',
"?#1(<ltx:note role='margin' class='ltx_marginpar_left'><ltx:inline-logical-block>#1</ltx:inline-logical-block></ltx:note>"
. "?#2(<ltx:note role='margin' class='ltx_marginpar_right'><ltx:inline-logical-block>#2</ltx:inline-logical-block></ltx:note>))"
. "(<ltx:note role='margin' class='ltx_marginpar'><ltx:inline-logical-block>#2</ltx:inline-logical-block></ltx:note>)");
DefRegister('\marginparpush', Dimension(0));
#**********************************************************************
# C.10 Lining It Up in Columns
#**********************************************************************
#======================================================================
# C.10.1 The tabbing Environment
#======================================================================
DefRegister('\tabbingsep' => Dimension(0));
DefMacroI('\tabbing', undef,
'\par\@tabbing@bindings\@@tabbing\@start@alignment');
DefMacroI('\endtabbing', undef,
'\@finish@alignment\@end@tabbing\par');
DefPrimitiveI('\@end@tabbing', undef, sub { $_[0]->egroup; });
DefConstructor('\@@tabbing SkipSpaces DigestedBody',
'#1',
reversion => '\begin{tabbing}#1\end{tabbing}',
beforeDigest => sub { $_[0]->bgroup; },
mode => 'text');
DefMacroI('\@tabbing@tabset', undef, '\@tabbing@tabset@marker&');
DefMacroI('\@tabbing@nexttab', undef, '\@tabbing@nexttab@marker&');
DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]', '\@tabbing@newline@marker\cr');
DefMacroI('\@tabbing@kill', undef, '\@tabbing@kill@marker\cr\@tabbing@start@tabs');
DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\=',
properties => { alignmentSkippable => 1 });
DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>',
properties => { alignmentSkippable => 1 });
DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR));
DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill',
afterDigest => sub { LookupValue('Alignment')->removeRow; return; },
properties => { alignmentSkippable => 1 });
AssignValue(tabbing_start_tabs => Tokens());
DefMacroI('\@tabbing@start@tabs', undef, sub { LookupValue('tabbing_start_tabs')->unlist; });
DefPrimitiveI('\@tabbing@increment', undef, sub {
my @tabs = LookupValue('tabbing_start_tabs')->unlist;
AssignValue(tabbing_start_tabs => Tokens(@tabs, T_CS('\>')), 'global'); });
DefPrimitiveI('\@tabbing@decrement', undef, sub {
my ($ignore, @tabs) = LookupValue('tabbing_start_tabs')->unlist;
AssignValue(tabbing_start_tabs => Tokens(@tabs), 'global'); });
# NOTE: \< is NOT currently handled!!!
# Ugh!! The way we're setting the initial tabs, we can't really handle this!
DefPrimitiveI('\@tabbing@untab', undef, undef);
# NOTE: \' and \` are NOT currently handled
DefPrimitiveI('\@tabbing@flushright', undef, undef);
DefPrimitiveI('\@tabbing@hfil', undef, undef);
# NOTE: \pushtabs and \poptabs are NOT currently handled.
DefPrimitiveI('\@tabbing@pushtabs', undef, undef);
DefPrimitiveI('\@tabbing@poptabs', undef, undef);
DefMacro('\@tabbing@accent{}', sub { T_CS('\@tabbing@' . ToString($_[1])); });
# Should there be some rowsep/colsep set here?
# Note that {tabbign} really shouldn't be handled by a tabular AT ALL....
# Should be recording accumulated widths and wrapping in ltx:text, with specified widths.
sub tabbingBindings {
AssignValue(Alignment => LaTeXML::Core::Alignment->new(
template => LaTeXML::Core::Alignment::Template->new(
repeated => [{ before => Tokens(T_CS('\lx@text@intercol')),
after => Tokens(T_CS('\hfil'), T_CS('\lx@text@intercol')) }]),
openContainer => sub { $_[0]->openElement('ltx:tabular', @_[1 .. $#_]); },
closeContainer => sub { $_[0]->closeElement('ltx:tabular'); },
openRow => sub { $_[0]->openElement('ltx:tr', @_[1 .. $#_]); },
closeRow => sub { $_[0]->closeElement('ltx:tr'); },
openColumn => sub { $_[0]->openElement('ltx:td', @_[1 .. $#_]); },
closeColumn => sub { $_[0]->closeElement('ltx:td'); },
properties => { attributes => { 'class' => 'ltx_tabbing' } }));
Let("\\=", '\@tabbing@tabset');
Let("\\>", '\@tabbing@nexttab');
Let("\\\\", '\@tabbing@newline');
Let('\kill', '\@tabbing@kill');
Let("\\+", '\@tabbing@increment');
Let("\\-", '\@tabbing@decrement');
Let("\\<", '\@tabbing@untab');
Let('\@tabbing@' . "'", "\\'");
Let('\@tabbing@' . "`", "\\`");
Let('\a', '\@tabbing@accent');
Let("\\'", '\@tabbing@flushright');
Let("\\`", '\@tabbing@hfil');
Let('\pushtabs', '\@tabbing@pushtabs');
Let('\poptabs', '\@tabbing@poptabs');
return; }
DefMacroI('\pushtabs', undef, Tokens());
DefMacroI('\poptabs', undef, Tokens());
DefMacroI('\kill', undef, Tokens());
DefPrimitiveI('\@tabbing@bindings', undef, sub {
tabbingBindings(); });
# NOTE: Do it!!
# Internals of tabbing, as an experiment (e.g. files using program.sty raw as in cs/0003026)
DefMacro('\@startfield', '\global\setbox\@curfield\hbox\bgroup\color@begingroup');
DefMacro('\@stopfield', '\color@endgroup\egroup');
DefMacro('\@contfield', '\global\setbox\@curfield\hbox\bgroup\color@begingroup\unhbox\@curfield');
DefMacro('\@addfield', '\global\setbox\@curline\hbox{\unhbox\@curline\unhbox\@curfield}');
#======================================================================
# C.10.2 The array and tabular Environments
#======================================================================
# Tabular are a bit tricky in that we have to arrange for tr and td to
# be openned and closed at the right times; the only real markup is
# the & and \\. Also \multicolumn has to be cooperative.
# Along with this, we have to track which column specification applies
# to the current column.
# To simulate LaTeX's tabular borders & hlines, we simply add border
# attributes to all cells. For HTML, CSS will be necessary to display them.
# [We'll ignore HTML's frame, rules and colgroup mechanisms.]
DefRegister('\lx@arstrut', Dimension('0pt'));
DefRegister('\lx@default@tabcolsep', Dimension('6pt'));
DefRegister('\tabcolsep', Dimension('6pt'));
DefMacroI('\arraystretch', undef, "1");
Let('\@tabularcr', '\@alignment@newline');
AssignValue(GUESS_TABULAR_HEADERS => 1) # Defaults to yes
unless defined LookupValue('GUESS_TABULAR_HEADERS');
sub tabularBindings {
my ($template, %properties) = @_;
$properties{guess_headers} = LookupValue('GUESS_TABULAR_HEADERS')
unless defined $properties{guess_headers};
if (!defined $properties{attributes}{colsep}) {
my $sep = LookupDimension('\tabcolsep');
if ($sep && ($sep->valueOf != LookupDimension('\lx@default@tabcolsep')->valueOf)) {
$properties{attributes}{colsep} = $sep; } }
if (!defined $properties{attributes}{rowsep}) {
my $str = ToString(Expand(T_CS('\arraystretch')));
if ($str != 1) {
$properties{attributes}{rowsep} = Dimension(($str - 1) . 'em'); } }
if (!defined $properties{strut}) {
$properties{strut} = LookupRegister('\baselineskip')->multiply(1.5); } # Account for html space
alignmentBindings($template, 'text', %properties);
Let("\\\\", '\@tabularcr');
Let('\lx@intercol', '\lx@text@intercol');
Let('\tabularnewline', "\\\\");
# NOTE: Fit this back in!!!!!!!
# # Do like AddToMacro, but NOT global!
foreach my $name ('@row@before', '@row@after', '@column@before', '@column@after') {
my $cs = '\\' . $name;
DefMacroI($cs, undef,
Tokens(LookupDefinition(T_CS($cs))->getExpansion->unlist,
T_CS('\@tabular' . $name))); }
return; }
# Keyvals are for attributes for the alignment.
# Typical keys are width, vattach,...
DefKeyVal('tabular', 'width', 'Dimension');
DefPrimitive('\@tabular@bindings AlignmentTemplate OptionalKeyVals:tabular', sub {
my ($stomach, $template, $attributes) = @_;
my %attr = ($attributes ? $attributes->getPairs : ());
if (my $va = $attr{vattach}) {
$attr{vattach} = translateAttachment($va) || ToString($va); }
tabularBindings($template, attributes => {%attr});
return; });
DefMacroI('\@tabular@before', undef, '');
DefMacroI('\@tabular@after', undef, '');
DefMacroI('\@tabular@row@before', undef, '');
DefMacroI('\@tabular@row@after', undef, '');
DefMacroI('\@tabular@column@before', undef, '');
DefMacroI('\@tabular@column@after', undef, '');
# The Core alignment support is in LaTeXML::Core::Alignment and in TeX.ltxml
##DefMacro('\tabular[]{}', '\@tabular@bindings{#2}\@@tabular[#1]{#2}\@start@alignment');
DefMacro('\tabular[]{}',
'\@tabular@bindings{#2}[vattach=#1]\@@tabular[#1]{#2}\@start@alignment\@tabular@before',
locked => 1);
DefMacroI('\endtabular', undef,
'\@tabular@after\@finish@alignment\@end@tabular',
locked => 1);
DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; });
#DefMacroI('\@end@tabular', undef, undef);
# Note that the pattern will already have been interpreted by \@tabular@bindings,
# so make it Undigested here!
DefConstructor('\@@tabular[] Undigested DigestedBody',
'#3',
reversion => '\begin{tabular}[#1]{#2}#3\end{tabular}',
beforeDigest => sub { $_[0]->bgroup; },
sizer => '#3',
afterDigest => sub {
my ($stomach, $whatsit) = @_;
if (my $alignment = LookupValue('Alignment')) {
my $attr = $alignment->getProperty('attributes');
$$attr{vattach} = translateAttachment($whatsit->getArg(1)); }
return; },
locked => 1,
mode => 'text');
DefMacro('\csname tabular*\endcsname{Dimension}[]{}',
### '\@tabular@bindings{#3}\@@tabular@{#1}[#2]{#3}\@start@alignment');
'\@tabular@bindings{#3}[width=#1,vattach=#2]\@@tabular@{#1}[#2]{#3}\@start@alignment');
DefMacro('\csname endtabular*\endcsname',
'\@finish@alignment\@end@tabular@');
DefConstructor('\@@tabular@{Dimension}[] Undigested DigestedBody',
'#4',
beforeDigest => sub { $_[0]->bgroup; },
reversion => '\begin{tabular*}{#1}[#2]{#3}#4\end{tabular*}',
mode => 'text');
DefPrimitive('\@end@tabular@', sub { $_[0]->egroup; });
Let('\multicolumn', '\@multicolumn');
# A weird bit that sometimes gets invoked by Cargo Cult programmers...
# to \noalign in the defn of \hline! Bizarre! (see latex.ltx)
# However, the really weird thing is the way this provides the } to close the argument
DefMacroI('\@xhline', undef, '\ifnum0=`{\fi}');
DefMacro('\cline{}', '\noalign{\@cline{#1}}');
DefConstructor('\@cline{}', '',
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $cols = ToString($whatsit->getArg(1));
my @cols = ();
while ($cols =~ s/^,?(\d+)//) {
my $n = $1;
push(@cols, ($cols =~ s/^-(\d+)// ? ($n .. $1) : ($n))); }
my $alignment = LookupValue('Alignment');
$alignment->addLine('t', @cols) if $alignment;
return; },
sizer => 0, alias => '\cline',
properties => { isHorizontalRule => 1 });
DefConstructorI('\vline', undef, "", # ???
properties => { isVerticalRule => 1 },
sizer => 0,
);
DefRegister('\lx@default@arraycolsep', Dimension('5pt'));
DefRegister('\arraycolsep', Dimension('5pt'));
DefRegister('\arrayrulewidth', Dimension('0.4pt'));
DefRegister('\doublerulesep', Dimension('2pt'));
DefMacro('\extracolsep{}', Tokens());
#DefMacroI('\tabularnewline', undef, Tokens());
#Let('\tabularnewline', '\cr');
#DefMacroI('\tabularnewline', undef, '\cr'); # ???
#======================================================================
# Array and similar environments
DefPrimitive('\@array@bindings [] AlignmentTemplate', sub {
my ($stomach, $pos, $template) = @_;
my $attr = { vattach => translateAttachment($pos),
role => 'ARRAY' };
# Determine column and row separations, if non default
my $colsep = LookupDimension('\arraycolsep');
if ($colsep && ($colsep->valueOf != LookupDimension('\lx@default@arraycolsep')->valueOf)) {
$$attr{colsep} = $colsep; }
my $str = ToString(Expand(T_CS('\arraystretch')));
if ($str != 1) {
$$attr{rowsep} = Dimension(($str - 1) . 'em'); }
alignmentBindings($template, 'math', attributes => $attr);
if (my $font = LookupValue('font')) {
if (($font->getMathstyle || 'text') eq 'display') {
MergeFont(mathstyle => 'text'); } }
Let("\\\\", '\@alignment@newline');
Let('\lx@intercol', '\lx@math@intercol');
return; });
DefMacro('\array[]{}',
'\@array@bindings[#1]{#2}\@@array[#1]{#2}\@start@alignment');
DefMacroI('\endarray', undef,
'\@finish@alignment\@end@array');
DefPrimitiveI('\@end@array', undef, sub { $_[0]->egroup; });
DefConstructor('\@@array[] Undigested DigestedBody',
'#3',
beforeDigest => sub { $_[0]->bgroup; },
reversion => '\begin{array}[#1]{#2}#3\end{array}');
DefMacro('\@tabarray', '\m@th\@@array[c]');
#**********************************************************************
# C.11 Moving Information Around
#**********************************************************************
#======================================================================
# C.11.1 Files
#======================================================================
DefPrimitive('\nofiles', undef);
#======================================================================
# C.11.2 Cross-References
#======================================================================
# \label attaches a label to the nearest parent that can accept a labels attribute
# but only those that have an xml:id (but should this require a refnum and/or title ???)
# Note that latex essentially allows redundant labels, but we can record only one!!!
DefConstructor('\label Semiverbatim', sub {
my ($document, $olabel, %props) = @_;
my $label = $props{label};
if (my $savenode = $document->floatToLabel) {
my $node = $document->getNode;
my %labels = map { ($_ => 1) } $label, split(/\s+/, $node->getAttribute('labels') || '');
$document->setAttribute($node, labels => join(' ', sort keys %labels));
$document->setNode($savenode); } },
reversion => sub { # disappear from tex/content-tex strings, but NOT in general reversion!
(defined $LaTeXML::DUAL_BRANCH ? () : Tokens(T_CS('\label'), T_BEGIN, Revert($_[1]), T_END)); },
properties => { alignmentSkippable => 1, alignmentPreserve => 1 },
afterDigest => sub {
MaybeNoteLabel($_[1]->getArg(1));
my $label = CleanLabel(ToString($_[1]->getArg(1)));
$_[1]->setProperty(label => $label);
my $scope = $label; $scope =~ s/^LABEL:/label:/;
if (my $ctr = LookupValue('current_counter')) {
unshift(@{ LookupValue('scopes_for_counter:' . $ctr) }, $scope);
$STATE->activateScope($scope);
$_[0]->beginMode('text');
AssignValue('LABEL@' . $label, Digest(T_CS('\@currentlabel')), 'global');
$_[0]->endMode('text'); }
return; });
# If a node has been labeled, but still hasn't yet got an id by afterClose:late,
# we'd better generate an id for it.
Tag('ltx:*', 'afterClose:late' => sub {
my ($document, $node) = @_;
if ($node->hasAttribute('labels') && !($node->hasAttribute('xml:id'))) {
GenerateID($document, $node); } });
# These will get filled in during postprocessing.
# * comes from hyperref
DefConstructor('\ref OptionalMatch:* Semiverbatim', "<ltx:ref ?#1(class='ltx_nolink')() labelref='#label' _force_font='true'/>",
sizer => '()', # Don't actually know how big this will be!
properties => sub { (label => CleanLabel($_[2])); });
# "page" does not make sense in xml. If the user really wants, they will need:
# \usepackage{latexml} ... \iflatexml alternate\else page \pageref{label}\fi
Let('\pageref', '\ref');
#======================================================================
# C.11.3 Bibliography and Citation
#======================================================================
# Note that it's called \refname in LaTeX's article, but \bibname in report & book.
# And likewise, mixed up in various other classes!
DefMacroI('\thebibliography@ID', undef, Tokens());
# Do this before digesting the body of a bibliography
sub beforeDigestBibliography {
AssignValue(inPreamble => 0); Digest('\@lx@inbibliographytrue');
DefMacro('\bibliographystyle{}', '');
DefMacro('\bibliography {}', '');
# avoid \let-based redefinitions of the ending.
Let('\endthebibliography', '\saved@endthebibliography');
ResetCounter('@bibitem');
return; }
# This sub does things that would commonly be needed when starting a bibliography
# setting the ID, etc...
sub beginBibliography {
my ($whatsit) = @_;
beginBibliography_clean($whatsit);
# Fix for missing \bibitems!
setupPseudoBibitem();
return; }
sub beginBibliography_clean {
my ($whatsit) = @_;
# Check if \bibsection is defined and try to decipher it.
# Expecting something like \section*{sometext}
my $bibtitle = undef;
my $bs = LookupDefinition(T_CS('\bibsection'));
# FIXED: Sometimes $bs may be a Primitive.
# Now guarding for that case or we get a perl:die for ->getExpansion
if (my @t = $bs && ($bs->isExpandable ? $bs->getExpansion->unlist : $bs)) {
our %bibunitmap = (
'\\part' => 'ltx:part', '\\chapter' => 'ltx:chapter',
'\\section' => 'ltx:section', '\\subsection' => 'ltx:subsection',
'\\subsubsection' => 'ltx:subsubsection', '\\paragraph' => 'ltx:paragraph',
'\\subparagraph' => 'ltx:subparagraph');
if (my $unit = $bibunitmap{ ToString(shift(@t)) }) { # Found a sectional unit command.
AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => $unit);
if (ToString($t[0]) eq '*') { shift(@t); }
# Check for balanced? or just take balanced begining?
$bibtitle = Tokens(@t); } }
noteBackmatterElement($whatsit, 'ltx:bibliography');
# Try to compute a reasonable, but unique ID;
# relative to the document's ID, if any.
# But also, if there are multiple bibliographies,
my $bibnumber = LookupValue('n_bibliographies') || 0;
AssignValue(n_bibliographies => ++$bibnumber, 'global');
my $docid = ToString(Expand(T_CS('\thedocument@ID')));
my $bibid = ($docid ? $docid . '.' : '') . 'bib' . radix_alpha($bibnumber - 1);
DefMacroI(T_CS('\thebibliography@ID'), undef, T_OTHER($bibid), scope => 'global');
# $whatsit->setProperty(id=>ToString(Expand(T_CS('\thebibliography@ID'))));
$whatsit->setProperty(id => $bibid);
my $title = ($bibtitle ? Digest($bibtitle)
: DigestIf('\refname') || DigestIf('\bibname'));
$whatsit->setProperty(title => $title) if $title;
$whatsit->setProperty(titlefont => $title->getFont) if $title;
$whatsit->setProperty(bibstyle => LookupValue('BIBSTYLE'));
$whatsit->setProperty(citestyle => LookupValue('CITE_STYLE'));
# $whatsit->setProperty(sort=> ???
# And prepare for the likely nonsense that appears within bibliographies
ResetCounter('enumiv');
return; }
DefMacro('\bibliography Semiverbatim',
'\lx@ifusebbl{#1}{\input{\jobname.bbl}}{\lx@bibliography{#1}}');
DefMacro('\lx@ifusebbl{}{}{}', sub {
my ($gullet, $bib_files, $bbl_clause, $bib_clause) = @_;
$bib_files = ToString(Expand($bib_files));
return unless $bib_files;
my $jobname = ToString(Expand(T_CS('\jobname')));
my $bbl_path = FindFile($jobname, type => 'bbl');
my $missing_bibs = '';
if (LookupValue("NO_BIBTEX")) {
if (not $bbl_path) {
Info('expected', "bbl", $_[0], "Couldn't find bbl file, bibliography may be empty.");
return Tokens(); }
else {
return $bbl_clause->unlist; } }
else {
for my $bf (split(',', $bib_files)) {
my $bib_path = FindFile($bf, type => 'bib');
if (not $bib_path) {
$missing_bibs .= ',' unless length($missing_bibs) == 0;
$missing_bibs .= $bf; } }
if (length($missing_bibs) == 0 or not $bbl_path) {
return $bib_clause->unlist; }
else {
Info('expected', $missing_bibs, $_[0], "Couldn't find all bib files, using " . $jobname . ".bbl instead");
return $bbl_clause->unlist; } } });
AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => 'ltx:section');
AssignMapping('BACKMATTER_ELEMENT', 'ltx:index' => 'ltx:section');
sub noteBackmatterElement {
my ($whatsit, $backelement) = @_;
$whatsit->setProperties(backmatterelement => LookupMapping('BACKMATTER_ELEMENT', $backelement));
return; }
sub adjustBackmatterElement {
my ($document, $whatsit) = @_;
if (my $asif = $whatsit->getProperty('backmatterelement')) {
$document->setNode($document->find_insertion_point($asif)); }
return; }
DefConstructor('\lx@bibliography [] Semiverbatim',
"<ltx:bibliography files='#2' xml:id='#id' "
. "bibstyle='#bibstyle' citestyle='#citestyle' sort='#sort' lists='#1'>"
. "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
. "</ltx:bibliography>",
afterDigest => sub { $_[0]->begingroup; # wrapped so redefns don't take effect!
beginBibliography($_[1]);
$_[0]->endgroup; },
beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });
# NOTE: This totally needs to be made extensible (parsing *.bst!?!? OMG!)
our $BIBSTYLES = {
plain => { citestyle => 'numbers', sort => 'true' },
unsrt => { citestyle => 'numbers', sort => 'false' },
alpha => { citestyle => 'AY', sort => 'true' },
abbrv => { citestyle => 'numbers', sort => 'true' },
plainnat => { citestyle => 'numbers', sort => 'true' },
unsrtnat => { citestyle => 'numbers', sort => 'false' },
alphanat => { citestyle => 'AY', sort => 'true' },
abbrvnat => { citestyle => 'numbers', sort => 'true' } };
sub setBibstyle {
my ($style) = @_;
$style = ToString($style);
AssignValue(BIBSTYLE => $style);
if (my $parms = $$BIBSTYLES{$style}) {
AssignValue(CITE_STYLE => $$parms{citestyle});
AssignValue(CITE_SORT => $$parms{sort}); }
return; }
DefConstructor('\bibstyle{}', sub {
my ($document, $style) = @_;
setBibstyle($style);
# Really ?
if (my $bib = $document->findnode('//ltx:bibliography')) {
$document->setAttribute($bib, bibstyle => LookupValue('BIBSTYLE'));
$document->setAttribute($bib, citestyle => LookupValue('CITE_STYLE'));
$document->setAttribute($bib, sort => LookupValue('CITE_SORT')); }
},
afterDigest => sub {
my $style = ToString($_[1]->getArg(1));
AssignValue(BIBSTYLE => $style, 'global');
if (my $parms = $$BIBSTYLES{$style}) {
AssignValue(CITE_STYLE => $$parms{citestyle}); }
else {
Info('unexpected', $style, $_[0], "Unknown bibstyle '$style', it will be ignored"); }
return; });
DefMacro('\bibliographystyle Semiverbatim', '\bibstyle{#1}');
DefConditional('\if@lx@inbibliography');
# Should be an environment, but people seem to want to misuse it.
DefConstructorI('\thebibliography', undef,
"<ltx:bibliography xml:id='#id'>"
. "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
. "<ltx:biblist>",
beforeDigest => sub {
beforeDigestBibliography(); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
# NOTE that in some perverse situations (revtex?)
# it seems to be allowable to omit the argument
# It's ignorable for latexml anyway, so we'll just read it if its there .
my $gullet = $stomach->getGullet;
$gullet->skipSpaces;
$gullet->readArg if $gullet->ifNext(T_BEGIN);
beginBibliography($_[1]);
},
beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); },
locked => 1);
# Close the bibliography
DefConstructorI('\endthebibliography', undef, sub {
$_[0]->maybeCloseElement('ltx:biblist');
$_[0]->maybeCloseElement('ltx:bibliography'); },
locked => 1);
Let('\saved@endthebibliography', '\endthebibliography');
# auto close the bibliography and contained biblist.
Tag('ltx:biblist', autoClose => 1);
Tag('ltx:bibliography', autoClose => 1);
# Since SOME people seem to write bibliographies w/o \bibitem,
# just blank lines between apparent entries,
# Making \par do a \bibitem{} works, but screws up valid
# bibliographies with blank lines!
# So, let's do some redirection!
sub setupPseudoBibitem {
Let('\save@bibitem', '\bibitem');
Let('\save@par', '\par');
# see arXiv:hep-th/0002043 for why the backslash itself needs binding.
Let('\save@backbackslash', '\\\\');
Let('\bibitem', '\restoring@bibitem');
Let('\par', '\par@in@bibliography');
Let('\\\\', '\par@in@bibliography');
Let('\vskip', '\vskip@in@bibliography');
# Moreover, some people use \item instead of \bibitem
Let('\item', '\item@in@bibliography');
# And protect from redefinitions.
Let('\newblock', '\lx@bibnewblock');
my $gullet = $STATE->getStomach->getGullet;
# Risky, but when bibliography immediatesly starts with text (no implied \par)
if (my $token = $gullet->readNonSpace) {
$gullet->unread($token);
if (!$token->isExecutable) {
$gullet->unread(T_CS('\par')); } }
return; }
DefMacroI('\par@in@bibliography', undef, sub {
my ($gullet) = @_;
$gullet->skipSpaces;
my $tok = $gullet->readToken;
# If next token is another \par, or a REAL \bibitem,
if (Equals($tok, T_CS('\par')) || Equals($tok, T_CS('\bibitem'))) {
($tok); } # then this \par expands into what followed
else { # Else, put it back, and start a bibitem.
$gullet->unread($tok);
(T_CS('\save@bibitem'), T_BEGIN, T_END); } });
DefMacro('\vskip@in@bibliography Glue', undef);
DefMacroI('\item@in@bibliography', undef, '\save@bibitem{}');
# If we hit a real \bibitem, put \par & \bibitem back to correct defn, and then \bibitem.
# A bibitem with now key or label...
DefMacro('\restoring@bibitem',
'\let\bibitem\save@bibitem\let\par\save@par\let\\\\\save@backbackslash\bibitem');
NewCounter('@bibitem', 'bibliography', idprefix => 'bib');
DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}');
DefMacro('\@biblabel{}', '[#1]');
DefMacroI('\fnum@@bibitem', undef, '{\@biblabel{\the@bibitem}}');
# Hack for abused bibliographies; see below
DefMacro('\bibitem',
'\if@lx@inbibliography\else\expandafter\lx@mung@bibliography\expandafter{\@currenvir}\fi'
. '\lx@bibitem', locked => 1);
DefConstructor('\lx@bibitem[] Semiverbatim',
"<ltx:bibitem key='#key' xml:id='#id'>"
. "#tags"
. "<ltx:bibblock>",
afterDigest => sub {
my $tag = $_[1]->getArg(1);
my $key = CleanBibKey($_[1]->getArg(2));
if ($tag) {
$_[1]->setProperties(key => $key,
RefStepID('@bibitem'),
tags => Digest(T_BEGIN,
T_CS('\def'), T_CS('\the@bibitem'), T_BEGIN, Revert($tag), T_END,
Invocation(T_CS('\lx@make@tags'), T_OTHER('@bibitem')),
T_END)); }
else {
$_[1]->setProperties(key => $key, RefStepCounter('@bibitem')); }
});
# This attempts to handle the case where folks put \bibitem's within an enumerate or such.
# We try to close the list and open the bibliography
DefMacro('\lx@mung@bibliography{}', sub {
my ($gullet, $env) = @_;
my $tag = ToString($env);
my @tokens = ();
# If we're in some sort of list environment, maybe we can recover
if (($tag eq 'enumerate') || ($tag eq 'itemize') || ($tag eq 'description')) {
# nDamn! We're in a list {$tag}; try to close it!
push(@tokens, Invocation('\end', $env),
T_CS('\let'), T_CS('\end' . $tag), T_CS('\endthebibliography'),
T_CS('\let'), T_CS('\end{' . $tag . '}'), T_CS('\end{thebibliography}')); }
# else ? it probably isn't going to work??
# Now, try to open {thebibliography}
push(@tokens, T_CS('\lx@mung@bibliography@pre'), Invocation('\thebibliography'), Tokens());
return Tokens(@tokens); });
DefConstructor('\lx@mung@bibliography@pre', sub {
my ($document) = @_;
my $parent = $document->getNode;
my $tag = $document->getNodeQName($parent);
if ($tag =~ /^ltx:(?:itemize|enumerate|description)$/) {
$document->maybeCloseElement($tag); } # Or even remove (if empty)?
return; });
DefConstructorI('\lx@bibnewblock', undef, sub {
$_[0]->openElement('ltx:bibblock') if $_[0]->isOpenable('ltx:bibblock'); });
Let('\newblock', '\lx@bibnewblock');
Tag('ltx:bibitem', autoOpen => 1, autoClose => 1);
Tag('ltx:bibblock', autoOpen => 1, autoClose => 1);
#----------------------------------------------------------------------
# We've got the same problem as LaTeX: Lather, Rinse, Repeat.
# It would be nice to know the bib info at digestion time
# * whether author lists will collapse
# * whether there are "a","b".. extensions on the year.
# We could process the bibliography first, (IF it is a separate *.bib!)
# but won't know which entries are included (and so can't resolve the a/b/c..)
# until we've finished looking at (all of) the source(s) that will refer to them!
#
# We can do this in 2 passes, however
# (1) convert (latexml) both the source document(s) and the bibliography
# (2) extract the required bibitems and integrate (latexmlpost) it into the documents.
# [Note that for mult-document sites, step (2) becomes 2 stages: scan and integrate]
#
# Here's the general layout.
# <ltx:cite> contains everything that the citations produce,
# including parens, pre-note, punctunation that precede the <ltx:bibcite>
# and punctuation, post-note, parens, that follow it.
# <ltx:bibcite show="string" bibrefs="keys" sep="" yysep="">phrases</ltx:bibcite>
# encodes the actual citation.
#
# bibrefs : lists the bibliographic keys that will be used
# show : gives the pattern for formatting using data from the bibliography
# It can contain:
# authors or fullauthors
# year
# number
# phrase1,phrase2,... selects one of the phrases from the content of the <ltx:bibref>
# This format is used as follows:
# If author and year is present, and a subset of the citations share the same authors,
# then the format is used, but the year is repeated for each citation in the subset,
# as a link to the bib entry.
# Otherwise, the format is applied to each entry.
#
# The design is intended to support natbib, as well as plain LaTeX.
AssignValue(CITE_STYLE => 'numbers');
AssignValue(CITE_OPEN => T_OTHER('['));
AssignValue(CITE_CLOSE => T_OTHER(']'));
AssignValue(CITE_SEPARATOR => T_OTHER(','));
AssignValue(CITE_YY_SEPARATOR => T_OTHER(','));
AssignValue(CITE_NOTE_SEPARATOR => T_OTHER(','));
AssignValue(CITE_UNIT => undef);
DefMacro('\@cite{}{}', '[{#1\if@tempswa , #2\fi}]');
DefConstructor('\@@cite []{}', "<ltx:cite ?#1(class='ltx_citemacro_#1')>#2</ltx:cite>",
alias => '\cite',
mode => 'text');
# \@@bibref{what to show}{bibkeys}{phrase1}{phrase2}
DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
"<ltx:bibref show='#1' bibrefs='#bibrefs' inlist='#bibunit'"
. " separator='#separator' yyseparator='#yyseparator'>#3#4</ltx:bibref>",
properties => sub { (bibrefs => CleanBibKey($_[2]),
separator => ToString(Digest(LookupValue('CITE_SEPARATOR'))),
yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR'))),
bibunit => LookupValue('CITE_UNIT')); });
# Simple container for any phrases used in the bibref
DefConstructor('\@@citephrase{}', "<ltx:bibrefphrase>#1</ltx:bibrefphrase>",
mode => 'text');
DefMacro('\cite[] Semiverbatim', sub {
my ($gullet, $post, $keys) = @_;
my ($style, $open, $close, $ns)
= map { LookupValue($_) } qw(CITE_STYLE CITE_OPEN CITE_CLOSE CITE_NOTE_SEPARATOR);
$post = undef if IsEmpty($post);
Invocation(T_CS('\@@cite'),
Tokens(Explode('cite')),
Tokens($open,
Invocation(T_CS('\@@bibref'), undef, $keys, undef, undef),
($post ? ($ns, T_SPACE, $post) : ()), $close)); });
# NOTE: Eventually needs to be recognized by MakeBibliography
# For now, defer until document end.
DefMacro('\nocite{}', sub {
PushValue('@at@end@document', (T_CS('\lx@mark@nocite'), T_BEGIN, $_[1], T_END));
return (); });
DefConstructor('\lx@mark@nocite Semiverbatim',
"<ltx:cite><ltx:bibref show='nothing' bibrefs='#bibrefs' inlist='#bibunit'/></ltx:cite>",
properties => sub {
(bibrefs => CleanBibKey($_[1]),
bibunit => LookupValue('CITE_UNIT')); });
#======================================================================
# C.11.4 Splitting the input
#======================================================================
Let('\@@input', '\input'); # Save TeX's version.
# LaTeX's \input is a bit different...
# Input, now
DefPrimitive('\ltx@input {}', sub { Input(Expand($_[1])); });
DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input');
Let('\@iinput', '\ltx@input');
DefMacro('\@input{}', '\IfFileExists{#1}{\@@input\@filef@und}{\typeout{No file #1.}}');
DefMacro('\@input@{}', '\InputIfFileExists{#1}{}{\typeout{No file #1.}}');
DefMacro('\quote@name{}', '"\quote@@name#1\@gobble""');
DefMacro('\quote@@name{} Match:"', '#1\quote@@name');
DefMacro('\unquote@name{}', '\quote@@name#1\@gobble"');
# Note that even excluded files SHOULD have the effects of their inclusion
# simulated by having read the corresponding aux file;
# But we're not bothering with that.
DefPrimitive('\include{}', sub {
my ($stomach, $path) = @_;
$path = ToString($path);
my $table = LookupValue('including@only');
if (!$table || $$table{$path}) {
Input($path); }
return; });
Let('\@include', '\include');
# [note, this will load name.tex, if it exists, else name]
DefPrimitive('\includeonly{}', sub {
my ($stomach, $paths) = @_;
$paths = ToString($paths);
my $table = LookupValue('including@only');
AssignValue('including@only', $table = {}, 'global') unless $table;
map { $$table{$_} = 1 } map { /^\s*(.*?)\s*$/ && $1; } split(/,/, $paths);
return; });
# NOTE: In the long run, we want to SAVE the contents and associate them with the given file name
# AND, arrange so that when a file is read, we'll use the contents!
DefConstructorI(T_CS("\\begin{filecontents}"), "Semiverbatim",
'',
reversion => '',
afterDigest => [sub {
my ($stomach, $whatsit) = @_;
my $filename = ToString($whatsit->getArg(1));
my @lines = ();
my $gullet = $stomach->getGullet;
my $line;
while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents}')) {
push(@lines, $line); }
AssignValue($filename . '_contents' => join("\n", @lines), 'global');
NoteLog("Cached filecontents for $filename (" . scalar(@lines) . " lines)"); }]);
DefConstructorI(T_CS("\\begin{filecontents*}"), "Semiverbatim",
'',
reversion => '',
afterDigest => [sub {
my ($stomach, $whatsit) = @_;
my $filename = ToString($whatsit->getArg(1));
my @lines = ();
my $gullet = $stomach->getGullet;
my $line;
while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents*}')) {
push(@lines, $line); }
AssignValue($filename . '_contents' => join("\n", @lines), 'global');
NoteLog("Cached filecontents* for $filename (" . scalar(@lines) . " lines)"); }]);
DefMacro('\endfilecontents', '');
DefPrimitive('\listfiles', undef);
#======================================================================
# C.11.5 Index and Glossary
#======================================================================
# ---- The index commands
# Format of Index entries:
# \index{entry!entry} gives multilevel index
# Each entry:
# foo@bar sorts on "foo" but prints "bar"
# The entries can end with a |expression:
# \index{...|(} this page starts a range for foo
# \index{...|)} this page ends a range
# The last two aren't handled in any particular way.
# We _could_ mark start & end, and then the postprocessor would
# need to fill in all likely links... ???
# \index{...|see{key}} cross reference.
# \index{...|seealso{key}} cross reference.
# \index{...|textbf} (etc) causes the number to be printed in bold!
#
# I guess the formula is that
# \index{foo|whatever{pi}{pa}{po}} => \whatever{pi}{pa}{po}{page}
# How should this get interpreted??
our %index_style = (textbf => 'bold', bf => 'bold', textrm => '', rm => '',
textit => 'italic', it => 'italic', emph => 'italic'); # What else?
# A bit screwy, but....
# Expand \index{a!b!...} into \@index{\@indexphrase{a}\@indexphrase{b}...}
sub process_index_phrases {
my ($gullet, $phrases, $inlist) = @_;
my @expansion = ();
my @tokens = $phrases->unlist;
# check we have a well-formed argument
return unless @tokens;
if (!$phrases->isBalanced) { # if ill-formed, discard;
Warn("malformed", "indexentry", $gullet,
'index entry has unbalanced groups, discarding: "' . ToString($phrases) . '"');
return; }
# Split the text into phrases, separated by "!"
push(@tokens, T_OTHER('!')) unless $tokens[-1]->getString eq '!'; # Add terminal !
my @phrase = ();
my @sortas = ();
my $style;
while (@tokens) {
my $tok = shift(@tokens);
my $string = $tok->getString;
if ($string eq '"') {
push(@phrase, shift(@tokens)); }
elsif ($string eq '@') {
while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); } # Trim
@sortas = @phrase; @phrase = (); }
elsif (($string eq '!') || ($string eq '|')) {
while (@phrase && ($phrase[-1]->getString =~ /\s/)) { pop(@phrase); } # Trim
push(@expansion, T_CS('\@indexphrase'),
(@sortas ? (T_OTHER('['), @sortas, T_OTHER(']')) : ()),
T_BEGIN, @phrase, T_END)
if @phrase;
@phrase = (); @sortas = ();
if ($string eq '|') {
pop(@tokens); # Remove the extra "!" stopbit.
my $extra = ToString(Tokens(@tokens));
if ($extra =~ /^see\s*{/) { push(@expansion, T_CS('\@indexsee'), @tokens[3 .. $#tokens]); }
elsif ($extra =~ /^seealso\s*\{/) { push(@expansion, T_CS('\@indexseealso'), @tokens[7 .. $#tokens]); }
elsif ($extra eq '(') { $style = 'rangestart'; } # ?
elsif ($extra eq ')') { $style = 'rangeend'; } # ?
else { $style = $index_style{$extra} || $extra; }
@tokens = (); } }
elsif (!@phrase && ($string =~ /\s/)) { } # Skip leading whitespace
else {
push(@phrase, $tok); } }
@expansion = (T_CS('\@index'),
($style || $inlist ? (T_OTHER('['), ($style ? T_OTHER($style) : ()), T_OTHER(']')) : ()),
($inlist ? (T_OTHER('['), T_OTHER($inlist), T_OTHER(']')) : ()),
T_BEGIN, @expansion, T_END);
return @expansion; }
# read verbatim, as if with LaTeX's \@sanitize;
# useful for \index (maybe others?)
DefParameterType('SanitizedVerbatim', sub {
my ($gullet) = @_;
$gullet->readUntil(T_BEGIN);
# crucial: deactivate the backslash to avoid activating command sequences
# chars switched to CC_OTHER by \@sanitize: ' ', '\\', '$', '&', '#', '^', '_', '%', '~'
# some of those are already in state's "SPECIALS", so only adding the rest:
StartSemiverbatim(' ', '\\', '%');
my $arg = $gullet->readBalanced();
EndSemiverbatim();
# now that we have the semiverbatim tokens, retokenize.
# this may seem like wasted work, but it avoids very unfortunate error propagation in cases
# where the \index argument was malformed for one reason or another.
#
# the strangeness comes from the original TeX workflow requiring multiple conversion calls,
# alongside a call to the `makeidx` binary, which we don't do in latexml. This parameter type
# emulates one important aspect implied by those steps.
$arg = TokenizeInternal(UnTeX($arg));
return $arg; },
reversion => sub { (T_BEGIN, Revert($_[0]), T_END); });
# real-world LaTeX \index
DefMacro('\index SanitizedVerbatim', \&process_index_phrases);
Tag('ltx:indexphrase', afterClose => \&addIndexPhraseKey);
Tag('ltx:glossaryphrase', afterClose => \&addIndexPhraseKey);
### ltx:indexsee does NOT get a key (at this stage)!
sub addIndexPhraseKey {
my ($document, $node) = @_;
if (!$node->getAttribute('key')) {
$node->setAttribute(key => CleanIndexKey($node->textContent)); }
return; }
DefConstructor('\@index[][]{}', "^<ltx:indexmark style='#1' inlist='#2'>#3</ltx:indexmark>",
bounded => 1, beforeDigest => sub { reenterTextMode(); neutralizeFont(); },
mode => 'text', reversion => '', sizer => 0);
DefConstructor('\@indexphrase[]{}',
"<ltx:indexphrase key='#key' _standalone_font='true'>#2</ltx:indexphrase>",
properties => { key => sub { CleanIndexKey($_[1]); } });
DefConstructor('\@indexsee{}',
"<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
properties => { name => sub { DigestIf('\seename') } });
DefConstructor('\@indexseealso{}',
"<ltx:indexsee key='#key' name='#name' _standalone_font='true'>#1</ltx:indexsee>",
properties => { name => sub { DigestIf('\alsoname') } });
DefConstructor('\glossary{}', sub {
my ($document, $arg, %props) = @_;
my $current_node = $document->getNode;
my $current_node_name = $document->getNodeQName($current_node);
if ($current_node_name eq '#PCDATA') {
$current_node_name = $document->getNodeQName($current_node->parentNode); }
if ($current_node_name =~ /^ltx:(?:p|text)/) {
Warn("unexpected", "glossary", $document, "glossary support is not yet ready for use in the main text flow.");
} else {
$document->insertElement('ltx:glossaryphrase', $arg, role => 'glossary', key => $props{'key'}); }
return; },
properties => { key => sub { CleanIndexKey($_[1]); } },
sizer => 0);
#======================================================================
# This converts an indexphrase node into a sortable string.
# Seems the XML nodes are the best place to handle it (rather than Boxes),
# although some of the special cases (see, @, may end up tricky)
sub indexify {
my ($node, $document) = @_;
my $type = $node->nodeType;
if ($type == XML_TEXT_NODE) {
my $string = $node->textContent;
$string =~ s/\W//g; # to be safe (if perhaps non-unique?)
$string =~ s/\s//g; # Or remove entirely? Eventually worry about many=>1 mapping???
return $string; }
elsif ($type == XML_ELEMENT_NODE) {
if ($document->getModel->getNodeQName($node) eq 'ltx:Math') {
return indexify_tex($node->getAttribute('tex')); }
else {
return join('', map { indexify($_, $document) } $node->childNodes); } }
elsif ($type == XML_DOCUMENT_FRAG_NODE) {
return join('', map { indexify($_, $document) } content_nodes($node)); }
else {
return ""; } }
# Try to clean up a TeX string into something
# Could walk the math tree and handle XMDual specially, but need to xref args.
# But also we'd have unicode showing up, which we'd like to latinize...
sub indexify_tex {
my ($string) = @_;
$string =~ s/(\\\@|\\,|\\:|\\;|\\!|\\ |\\\/|)//g;
$string =~ s/(\\mathrm|\\mathit|\\mathbf|\\mathsf|\\mathtt|\\mathcal|\\mathscr|\\mbox|\\rm|\\it|\\bf|\\tt|\\small|\\tiny)//g;
$string =~ s/\\left\b//g; $string =~ s/\\right\b//g;
$string =~ s/(\\|\{|\})//g;
$string =~ s/\W//g; # to be safe (if perhaps non-unique?)
$string =~ s/\s//g; # Or remove entirely? Eventually worry about many=>1 mapping???
return $string; }
# ---- Creating the index itself
AssignValue(INDEXLEVEL => 0);
Tag('ltx:indexentry', autoClose => 1);
sub closeIndexPhrase {
my ($document) = @_;
if ($document->isCloseable('ltx:indexphrase')) {
$document->closeElement('ltx:indexphrase'); }
return; }
sub doIndexItem {
my ($document, $level) = @_;
$document->closeElement('ltx:indexrefs') if $document->isCloseable('ltx:indexrefs');
closeIndexPhrase($document);
my $l = LookupValue('INDEXLEVEL');
while ($l < $level) {
$document->openElement('ltx:indexlist'); $l++; }
while ($l > $level) {
$document->closeElement('ltx:indexlist'); $l--; }
AssignValue(INDEXLEVEL => $l);
if ($level) {
$document->openElement('ltx:indexentry');
$document->openElement('ltx:indexphrase'); }
return; }
DefConstructorI('\index@dotfill', undef, sub {
my ($document) = @_;
closeIndexPhrase($document);
$document->openElement('ltx:indexrefs'); });
DefConstructorI('\index@item', undef, sub { doIndexItem($_[0], 1); });
DefConstructorI('\index@subitem', undef, sub { doIndexItem($_[0], 2); });
DefConstructorI('\index@subsubitem', undef, sub { doIndexItem($_[0], 3); });
DefConstructorI('\index@done', undef, sub { doIndexItem($_[0], 0); });
DefMacroI('\indexname', undef, 'Index');
DefEnvironment('{theindex}',
"<ltx:index xml:id='#id'>"
. "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
. "#body"
. "</ltx:index>",
beforeDigest => sub {
Let('\item', '\index@item');
Let('\subitem', '\index@subitem');
Let('\subsubitem', '\index@subsubitem');
Let('\dotfill', '\index@dotfill'); },
beforeDigestEnd => sub { Digest(T_CS('\index@done')); },
afterDigestBegin => sub {
my $docid = ToString(Expand(T_CS('\thedocument@ID')));
my $title = DigestIf('\indexname');
noteBackmatterElement($_[1], 'ltx:index');
$_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'),
title => $title,
titlefont => $title->getFont); },
beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); });
DefPrimitiveI('\indexspace', undef, undef);
DefPrimitiveI('\makeindex', undef, undef);
DefPrimitiveI('\makeglossary', undef, undef);
#======================================================================
# C.11.6 Terminal Input and Output
#======================================================================
DefPrimitive('\typeout{}', sub {
my ($stomach, $stuff) = @_;
Note(ToString(Expand($stuff)));
return; });
DefPrimitive('\typein[]{}', undef);
#**********************************************************************
# C.12 Line and Page Breaking
#**********************************************************************
#======================================================================
# C.12.1 Line Breaking
#======================================================================
DefPrimitive('\linebreak[]', undef);
DefPrimitive('\nolinebreak[]', undef);
DefPrimitiveI('\-', undef, undef); # We don't do hyphenation.
# \hyphenation in TeX.pool
DefPrimitiveI('\sloppy', undef, undef);
DefPrimitiveI('\fussy', undef, undef);
# sloppypar can be used as an environment, or by itself.
DefMacro('\sloppypar', '\par\sloppy');
DefMacro('\endsloppypar', '\par');
DefMacroI('\nobreakdashes', undef, T_OTHER('-'));
DefMacro('\showhyphens{}', '#1'); # ?
#======================================================================
# C.12.2 Page Breaking
#======================================================================
DefMacro('\pagebreak[Default:4]', sub {
my ($gullet, $arg) = @_;
$arg = $arg->toString;
if ($arg <= 2) {
return; }
return Invocation(T_CS('\vadjust'), T_CS('\clearpage')); });
DefPrimitive('\nopagebreak[]', undef);
DefPrimitiveI('\columnbreak', undef, undef); # latex? or multicol?
DefPrimitive('\enlargethispage OptionalMatch:* {}', undef);
DefMacroI('\clearpage', undef, '\LTX@newpage');
DefMacroI('\cleardoublepage', undef, '\LTX@newpage');
DefPrimitiveI('\samepage', undef, undef);
#**********************************************************************
# C.13 Lengths, Spaces and Boxes
#**********************************************************************
#####
#####
# Complete to here
# [except for NOTE'd entries, of course]
#####
#####
#======================================================================
# C.13.1 Length
#======================================================================
# \fill
DefMacro('\stretch{}', '0pt plus #1fill\relax');
DefPrimitive('\newlength DefToken', sub {
my ($stomach, $cs) = @_;
DefRegisterI($cs, undef, Glue(0), allocate => '\skip'); });
DefPrimitive('\setlength {Variable}{Dimension}', sub {
my ($stomach, $variable, $length) = @_;
my ($defn, @params) = @$variable;
if (ref $defn) {
$defn->setValue($length, undef, @params); }
return; });
DefPrimitive('\addtolength {Variable}{Dimension}', sub {
my ($stomach, $variable, $length) = @_;
my ($defn, @params) = @$variable;
if (ref $defn) {
my $oldlength = $defn->valueOf(@params);
$defn->setValue($defn->valueOf(@params)->add($length), undef, @params); }
return; });
DefMacro('\@settodim{}{}{}',
'\setbox\@tempboxa\hbox{{#3}}#2#1\@tempboxa\setbox\@tempboxa\box\voidb@x');
DefMacroI('\settoheight', undef, '\@settodim\ht');
DefMacroI('\settodepth', undef, '\@settodim\dp');
DefMacroI('\settowidth', undef, '\@settodim\wd');
DefMacro('\@settopoint{}', '\divide#1\p@\multiply#1\p@');
DefRegister('\fill', Glue('0pt plus 1fill'));
#======================================================================
# C.13.2 Space
#======================================================================
DefPrimitive('\hspace OptionalMatch:* {Dimension}', sub {
my ($stomach, $star, $length) = @_;
my $s = DimensionToSpaces($length);
return unless defined $s;
Box($s, undef, undef, Invocation(T_CS('\hskip'), $length),
width => $length, isSpace => 1); });
DefPrimitive('\vspace OptionalMatch:* {}', undef);
DefPrimitive('\addvspace {}', undef);
DefPrimitive('\addpenalty {}', undef);
DefPrimitiveI('\@endparenv');
# \hfill, \vfill
#======================================================================
# C.13.3 Boxes
#======================================================================
# Can't really get these?
DefMacroI('\height', undef, '0pt');
DefMacroI('\totalheight', undef, '0pt');
DefMacroI('\depth', undef, '0pt');
DefMacroI('\width', undef, '0pt');
DefConstructor('\mbox {}',
"<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text', bounded => 1,
sizer => '#1',
robust => 1,
beforeDigest => sub { reenterTextMode(); });
our %makebox_alignment = (l => 'left', r => 'right', s => 'justified');
DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox', robust => 1);
DefConstructor('\@makebox[Dimension][]{}',
"<ltx:text ?#width(width='#width') ?#align(align='#align') _noautoclose='1'>#3</ltx:text>",
mode => 'text', bounded => 1, alias => '\makebox', sizer => '#3',
beforeDigest => sub { reenterTextMode(); },
properties => sub {
(($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
($_[1] ? (width => $_[1]) : ())) });
DefRegister('\fboxrule', Dimension('.4pt'));
DefRegister('\fboxsep', Dimension('3pt'));
# Peculiar special case!
# These are nominally text mode macros. However, there is a somewhat common idiom:
# $ ... \framebox{$operator$} ... $
# in which case the operator gets boxed and really should be treated as a math object.
# (and ultimately converted to mml:menclose)
# So, we need to switch to text mode, as usual, but FIRST note whether we started in math mode!
# Afterwards, if we were in math mode, and the content is math, we'll convert the whole thing
# to a framed math object.
# Second special issue:
# Although framebox doesn't allow flowed content inside, it is also somewhat common
# to put a vbox or some other block construct inside.
# Seemingly, the ultimate html gets somewhat tangled (browser bugs?)
# At any rate, since we're wrapping with an ltx:text, we'll try to unwrap it,
# if the contents are a single child that can handle the framing.
DefMacro('\fbox{}', '\@framebox{#1}', robust => 1);
DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox', robust => 1);
DefConstructor('\@framebox[Dimension][]{}',
"?#mathframe(<ltx:XMArg enclose='box'>#inner</ltx:XMArg>)"
. "(<ltx:text ?#width(width='#width') ?#align(align='#align')"
. " framed='rectangle' framecolor='#framecolor' cssstyle='#cssstyle'"
. " _noautoclose='1'>#3</ltx:text>)",
alias => '\framebox', sizer => '#3',
beforeDigest => sub {
my ($stomach) = @_;
my $wasmath = LookupValue('IN_MATH');
$stomach->beginMode('text');
AssignValue(FRAME_IN_MATH => $wasmath); },
properties => sub {
my $sep = LookupRegister('\fboxsep')->toAttribute;
(($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
framecolor => LookupValue('font')->getColor,
($sep ne '3.0pt' ? (cssstyle => 'padding:' . $sep) : ()),
($_[1] ? (width => $_[1]) : ())); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $wasmath = LookupValue('FRAME_IN_MATH');
my $arg = $whatsit->getArg(3);
$stomach->endMode('text');
if ($wasmath && $arg->isMath) {
$whatsit->setProperties(mathframe => 1, inner => $arg->getBody); }
return; },
afterConstruct => sub {
my ($document, $whatsit) = @_;
my $node = $document->getNode->lastChild;
# If the generated node, has only a single (non space) child
my @c = grep { ($_->nodeType != XML_TEXT_NODE) || ($_->textContent =~ /[^\s\n]/) }
$node->childNodes;
my $model = $document->getModel;
# and that child can have the framed attribute
if ((scalar(@c) == 1)
&& $document->canHaveAttribute($model->getNodeQName($c[0]), 'framed')
&& !$c[0]->hasAttribute('framed')) {
# unwrap, copying the attributes
$document->unwrapNodes($node);
foreach my $k (qw(width align framed)) {
if (my $v = $node->getAttribute($k)) {
$document->setAttribute($c[0], $k => $v); } } } }
);
DefPrimitive('\newsavebox DefToken', sub {
my $n = LookupValue('allocated_boxes') + 1;
AssignValue(allocated_boxes => $n, 'global');
DefRegisterI($_[1], undef, Number($n));
AssignValue('box' . $n, List()); });
RawTeX(<<'EOL');
\def\newsavebox#1{\@ifdefinable{#1}{\newbox#1}}
\DeclareRobustCommand\savebox[1]{%
\@ifnextchar(%)
{\@savepicbox#1}{\@ifnextchar[{\@savebox#1}{\sbox#1}}}%
\DeclareRobustCommand\sbox[2]{\setbox#1\hbox{%
\color@setgroup#2\color@endgroup}}
\def\@savebox#1[#2]{%
\@ifnextchar [{\@isavebox#1[#2]}{\@isavebox#1[#2][c]}}
\long\def\@isavebox#1[#2][#3]#4{%
\sbox#1{\@imakebox[#2][#3]{#4}}}
\def\@savepicbox#1(#2,#3){%
\@ifnextchar[%]
{\@isavepicbox#1(#2,#3)}{\@isavepicbox#1(#2,#3)[]}}
\long\def\@isavepicbox#1(#2,#3)[#4]#5{%
\sbox#1{\@imakepicbox(#2,#3)[#4]{#5}}}
\def\lrbox#1{%
\edef\reserved@a{%
\endgroup
\setbox#1\hbox{%
\begingroup\aftergroup}%
\def\noexpand\@currenvir{\@currenvir}%
\def\noexpand\@currenvline{\on@line}}%
\reserved@a
\@endpefalse
\color@setgroup
\ignorespaces}
\def\endlrbox{\unskip\color@endgroup}
\DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax}
EOL
Let('\lx@parboxnewline', '\lx@newline'); # Obsolete, but in case still used
# NOTE: There are 2 extra arguments (See LaTeX Companion, p.866)
# for height and inner-pos. We're ignoring inner-pos, for now, though.
DefConstructor('\parbox[] [Dimension] OptionalUndigested {Dimension} VBoxContents', sub {
my ($document, $attachment, $b, $c, $width, $body, %props) = @_;
insertBlock($document, $body,
width => $width,
vattach => $props{vattach},
class => 'ltx_parbox');
return; },
sizer => '#5',
properties => sub {
(width => $_[4],
vattach => translateAttachment($_[1]),
height => $_[2]); },
mode => 'text', bounded => 1,
robust => 1,
beforeDigest => sub {
reenterTextMode(1); # actually vertical mode!
AssignRegister('\hsize' => $_[4]);
Let('\\\\', '\lx@newline'); });
DefMacroI('\@parboxrestore', undef, Tokens());
DefConditional('\if@minipage');
DefMacro('\@setminipage', '');
DefEnvironment('{minipage}[][][]{Dimension}', sub {
my ($document, $attachment, $b, $c, $width, %props) = @_;
my $vattach = translateAttachment($attachment);
insertBlock($document, $props{body},
width => $width,
vattach => $vattach,
class => 'ltx_minipage');
return; },
sizer => '#body',
mode => 'text',
properties => sub { (
width => $_[4],
vattach => translateAttachment($_[1])); },
beforeDigest => sub {
reenterTextMode(1); # actually vertical mode!
Digest(T_CS('\@minipagetrue'));
AssignRegister('\hsize' => $_[4]);
Let('\\\\', '\lx@newline');
});
DefConstructor('\rule[Dimension]{Dimension}{Dimension}',
"<ltx:rule ?#offset(yoffset='#offset') width='#width' height='#height'/>",
properties => sub { (offset => $_[1], width => $_[2], height => $_[3]) });
DefConstructor('\raisebox{Dimension}[Dimension][Dimension]{}',
"<ltx:text yoffset='#1' _noautoclose='1'>#4</ltx:text>",
mode => 'text', bounded => 1,
beforeDigest => sub { reenterTextMode(); },
sizer => sub { raisedSizer($_[0]->getArg(4), $_[0]->getArg(1)); });
DefMacro('\@finalstrut{}', '\unskip\ifhmode\nobreak\fi\vrule\@width\z@\@height\z@\@depth\dp#1');
#**********************************************************************
# C.14 Pictures and Color
#**********************************************************************
# These are stubs for color or xcolor packages
Let('\set@color', '\relax');
Let('\color@begingroup', '\relax');
Let('\color@endgroup', '\relax');
Let('\color@setgroup', '\relax');
Let('\color@hbox', '\relax');
Let('\color@vbox', '\relax');
Let('\color@endbox', '\relax');
#======================================================================
# C.14.1 The picture environment
#======================================================================
#----------------------------------------------------------------------
sub ResolveReader {
my ($type) = @_;
return $type if ref $type eq 'CODE'; # It's already a reader (hopefully).
$type = ToString($type);
if (my $descriptor = LookupMapping('PARAMETER_TYPES', $type)) {
return $$descriptor{reader}; }
# Otherwise, try to find a function named Read<Type>
else {
return LaTeXML::Core::Parameter::checkReaderFunction('Read' . $type); } }
# This defines a Pair parameter type,
# that reads a parenthesized, comma separated pair of subtype.
# By default, the subtype is Float, but you can write Pair:Number, or Pair:Dimension
sub ReadPair {
my ($gullet, $itemtype, $xarg, $yarg) = @_;
my $itemreader;
if (!$itemtype) { $itemreader = \&ReadFloat; }
else { $itemreader = ResolveReader($itemtype); }
if (!$itemreader) {
Error('misdefined', $itemtype, $gullet, "Can't find reader for Pair items from '$itemtype'");
return Pair(Dimension(0), Dimension(0)); } # Assume something like this?
$gullet->skipSpaces;
if ($gullet->ifNext(T_OTHER('('))) {
$gullet->readToken; $gullet->skipSpaces;
if ($gullet->ifNext(T_OTHER('!'))) { # maybe only for pstricks???
Error('unexpected', 'postscript', $gullet,
"Cannot process escape to postscript");
$gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
return Pair(Dimension(0), Dimension(0)); }
my $x = &$itemreader($gullet, $xarg);
# This had also recognized ";" as a possible separator; if needed, will need to generalize
# but we want to make readUntil accept only a single match
$gullet->skipSpaces; $gullet->readUntil(T_OTHER(',')); $gullet->skipSpaces;
my $y = &$itemreader($gullet, $yarg);
$gullet->skipSpaces; $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces;
return Pair($x, $y); }
else {
return; } }
sub ptValue {
my ($value) = @_;
return ((defined $value) && (ref $value) ? $value->ptValue : $value); }
sub pxValue {
my ($value) = @_;
return ((defined $value) && (ref $value) ? $value->pxValue : $value); }
#----------------------------------------------------------------------
# Picture parameters.
DefRegister('\unitlength', Dimension('1pt'));
DefPrimitiveI('\thinlines', undef, sub { AssignRegister('\@wholewidth', Dimension('0.4pt')); });
DefPrimitiveI('\thicklines', undef, sub { AssignRegister('\@wholewidth', Dimension('0.8pt')); });
DefRegister('\@wholewidth' => Dimension('0.4pt'));
DefRegister('\@halfwidth' => Dimension('0.2pt'));
DefMacro('\linethickness{}', '\@wholewidth #1\relax');
DefPrimitive('\arrowlength{Dimension}', sub { AssignValue('arrowlength', $_[1]); });
#----------------------------------------------------------------------
# Picture transformation support
sub slopeToPicCoord {
my ($slope, $xlength) = @_;
my ($mx, $my) = ($slope->getX, $slope->getY);
my $s = $mx->sign();
$xlength = picScale($xlength);
return Pair($xlength->multiply($s),
$xlength->multiply(($s == 0) ? $my->sign() :
$my->valueOf / $mx->absolute->valueOf)); }
# Scale $value by \unitlength
sub picScale {
my ($value) = @_;
# Vain attempt at proper type coercion
my $type = ref $value;
my $unit = LookupRegister('\unitlength');
if (!$value) { }
elsif ($type eq 'LaTeXML::Common::Number') {
return $unit->multiply($value); }
elsif ($type eq 'LaTeXML::Common::Float') {
return $unit->multiply($value); }
elsif ($type eq 'LaTeXML::Common::Pair') {
return Pair($unit->multiply($value->getX), $unit->multiply($value->getY)); }
return $value->multiply($unit); }
sub picProperties {
my (%props) = @_;
if (($props{stroke} || '') ne 'none') {
$props{thick} = ptValue(LookupRegister('\@wholewidth')); }
if (my $arrowlength = LookupValue('arrowlength')) {
$props{arrowlength} = ptValue($arrowlength); }
$props{color} = Black unless defined $props{color};
return %props; }
#----------------------------------------------------------------------
# the code
DefMacroI('\qbeziermax', undef, '500');
sub before_picture {
Let('\raisebox', '\pic@raisebox'); # ? needs special treatment within picture
return; }
sub after_picture {
return; }
# Ugh... Is this safe? Apparently, picture stuff is allowed w/o a {picture} environment???
Tag('ltx:picture', autoOpen => 0.5, autoClose => 1,
afterOpen => sub { GenerateID(@_, 'pic'); },
afterClose => sub {
my ($document, $node, $whatsit) = @_;
# Make sure we get a size, in case autoOpen'd
if ($whatsit) {
my ($w, $h, $d) = $whatsit->getSize;
$node->setAttribute(width => $w->pxValue) unless $node->hasAttribute('width');
$node->setAttribute(height => $h->add($d)->pxValue) unless $node->hasAttribute('height'); } });
# Note: Untex should prefix a setting of unitlength!!!
# First pair is (width,height)
# Second pair is the coordinate of the lower-left corner.
# [Note that for SVG the root viewport origin is at the TOP-left corner!
# but that is currently handled in the SVG postprocessing module]
DefEnvironment('{picture} Pair OptionalPair',
"<ltx:picture width='#width' height='#height' origin-x='#origin-x' origin-y='#origin-y'"
. " fill='none' stroke='none' unitlength='#unitlength'>"
. "?#transform(<ltx:g transform='#transform'>#body</ltx:g>)(#body)"
. "</ltx:picture>",
mode => 'text',
beforeDigest => \&before_picture,
properties => sub {
my ($stomach, $size, $pos) = @_;
picProperties(unitlength => LookupRegister('\unitlength'),
width => $size && picScale($size->getX),
height => $size && picScale($size->getY),
depth => Dimension(0),
'origin-x' => $pos && picScale($pos->getX),
'origin-y' => $pos && picScale($pos->getY),
($pos ? (transform => 'translate(' . picScale($pos->negate)->pxValue . ')') : ())); },
afterDigest => \&after_picture);
DefMacroI(T_CS('\Gin@driver'), undef, Tokens());
DefMacro('\@killglue', '\unskip\@whiledim \lastskip >\z@\do{\unskip}');
DefMacro('\put SkipSpaces Match:( Until:, Until:){}', '\lx@pic@put(#2,#3){#4\relax}');
DefConstructor('\lx@pic@put Pair{}',
"<ltx:g transform='#transform'"
. " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'>#2</ltx:g>",
properties => sub {
my ($w, $h, $d) = $_[2]->getSize;
$w = undef if $w && ($w->ptValue == 0);
(transform => 'translate(' . picScale($_[1])->pxValue . ')',
innerwidth => $w, innerheight => $h, innerdepth => $d); },
alias => '\put',
mode => 'text');
DefConstructor('\line Pair:Number {Float}',
"<ltx:line points='#points' stroke='#color' stroke-width='#thick'/>",
alias => '\line',
properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
DefConstructor('\vector Pair:Number {Float}',
"<ltx:line points='#points' stroke='#color' stroke-width='#thick' terminators='->'"
. " arrowlength='#arrowlength'/>",
alias => '\vector',
properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); });
DefConstructor('\circle OptionalMatch:* {Float}',
"<ltx:circle x='0' y='0' r='#radius' fill='#fill' stroke='#stroke' stroke-width='#thick'/>",
alias => '\circle',
properties => sub {
my ($stomach, $filled, $dia) = @_;
picProperties(radius => picScale($dia)->multiply(0.5)->pxValue,
($filled ? 'fill' : 'stroke') => Black); });
DefConstructor('\oval [Float] Pair []',
"<ltx:rect x='#x' y='#y' width='#width' height='#height' rx='#radius'"
. " stroke='#color' fill='none' part='#3' stroke-width='#thick'/>",
alias => '\oval',
properties => sub {
my ($stomach, $r, $size, $part) = @_;
$size = picScale($size);
my $halfsize = $size->multiply(0.5);
$r = ($r ? picScale($r) : Dimension('40pt'));
$r = $r->smaller($halfsize->getX->absolute);
$r = $r->smaller($halfsize->getY->absolute);
picProperties(size => $size, radius => Dimension($r->valueOf),
x => $halfsize->getX->negate, y => $halfsize->getY->negate,
width => Dimension($size->getX->valueOf),
height => Dimension($size->getY->valueOf),
); });
DefConstructor('\qbezier [Number] Pair Pair Pair',
"<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke='#color' stroke-width='#thick' />",
alias => '\qbezier',
properties => sub {
picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
DefMacro('\bezier Until:(', '\ifx.#1.\lx@pic@bezier{0}(\else\lx@pic@bezier{#1}(\fi');
DefConstructor('\lx@pic@bezier {Number} Pair Pair Pair',
"<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
alias => '\bezier',
properties => sub {
picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
# Generic boxing command (frames, dash, etc)
DefConstructor('\pic@makebox@ Undigested RequiredKeyVals Pair []{}',
"?#framed(<ltx:rect x='0' y='0' width='#fwidth' height='#fheight'"
. " stroke='#color' stroke-width='#thick' fill='none' stroke-dasharray='#dash'/>)()"
. "<ltx:g class='makebox' innerwidth='#width' innerheight='#height' innerdepth='#depth'"
. " transform='translate(#xshift,#yshift)'>#box</ltx:g>",
reversion => '#1#3[#4]{#5}',
beforeDigest => sub { reenterTextMode(); },
properties => sub {
my ($stomach, $cs, $kv, $size, $pos, $box) = @_;
my ($w, $h, $d) = $box->getSize;
my ($ww, $hh) = ($w, $h);
my ($x, $y) = (Dimension(0), Dimension(0));
my $ht = ($h ? ($d ? $h->add($d) : $h) : ($d ? $d : Dimension(0)));
if ($size) { # && (($size->getX->valueOf != 0) || ($size->getY->valueOf != 0))) {
$ww = picScale($size->getX);
$hh = picScale($size->getY);
$pos = ($pos ? lc(ToString($pos)) : '');
if ($pos =~ /l/) { $x = Dimension(0); }
elsif ($pos =~ /r/) { $x = $ww->subtract($w); }
else { $x = $ww->subtract($w)->divide(2); }
if ($pos =~ /b/) { $y = Dimension(0); }
elsif ($pos =~ /t/) { $y = $hh->subtract($ht); }
else { $y = $hh->subtract($ht)->divide(2); } }
my $fw = ($ww && $ww->valueOf ? $ww : $w);
my $fh = ($hh && $hh->valueOf ? $hh : $h->add($d));
picProperties(
box => $box,
width => $w, height => $h, depth => $d,
fwidth => $fw, fheight => $fh,
xshift => $x->pxValue, yshift => $y->pxValue,
framed => $kv->getValue('framed'), dash => $kv->getValue('dash') # dashed
); });
DefMacro('\pic@makebox', '\pic@makebox@{\makebox}{}');
DefMacro('\pic@framebox', '\pic@makebox@{\framebox}{framed=true}');
DefMacro('\lx@pic@dashbox{Float}', '\pic@makebox@{\dashbox(#1)}{framed=true,dash={#1}}');
DefMacro('\dashbox Until:(', '\ifx.#1.\lx@pic@dashbox{0}(\else\lx@pic@dashbox{#1}(\fi');
DefMacro('\frame{}', '\pic@makebox@{\framebox}{framed=true}(0,0)[bl]{#1}');
DefMacro('\pic@savebox DefToken Pair []{}', '\pic@@savebox{#1}{\pic@makebox #2[#3]{#4}}');
DefPrimitive('\pic@@savebox DefToken {}', sub {
AssignValue('box' . ToString($_[1]), Digest($_[2])); return; });
DefMacro('\@savepicbox', '\pic@savebox');
DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}',
"<ltx:g y='#1'>#4</ltx:g>",
alias => '\raisebox');
our %alignments = (l => 'left', c => 'center', r => 'right');
# Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop)
# This should be fairly compact vertically.
DefConstructor('\@shortstack@cr',
"</ltx:p><ltx:p>",
properties => { isBreak => 1 },
reversion => Tokens(T_CS("\\\\"), T_CR),
beforeDigest => sub { $_[0]->egroup; },
afterDigest => sub { $_[0]->bgroup; });
DefConstructor('\shortstack[]{} OptionalMatch:* [Dimension]',
"<ltx:inline-block align='#align'><ltx:p>#2</ltx:p></ltx:inline-block>",
bounded => 1,
sizer => '#2',
beforeDigest => sub { reenterTextMode();
# then RE-RE-define this one!!!
Let("\\\\", '\@shortstack@cr');
AssignRegister('\baselineskip' => Glue('-1pt'));
AssignRegister('\lineskip' => Glue('3pt'));
$_[0]->bgroup; },
afterDigest => sub { $_[1]->getSize; # precompute while binding in effect
$_[0]->egroup; },
# Note: does not get layout=vertical, since linebreaks are explicit
properties => { align => sub { ($_[1] ? $alignments{ ToString($_[1]) } : undef); },
vattach => 'bottom' }, # for size computation
mode => 'text');
DefMacro('\multiput Pair Pair {}{}', sub {
my ($gullet, $pos, $d, $nt, $body) = @_;
my ($x, $y, $dx, $dy, $n)
= map { ToString($_) } ($pos->getX, $pos->getY, $d->getX, $d->getY, $nt);
my @exp = ();
for (my $i = 0 ; $i < $n ; $i++) {
push(@exp, T_CS('\put'), T_OTHER('('), Explode($x), T_OTHER(','), Explode($y), T_OTHER(')'),
T_BEGIN, $body->unlist, T_END);
$x += $dx; $y += $dy; }
@exp; });
Tag('ltx:picture',
afterOpen => sub {
my ($document, $node, $thing) = @_;
if ($thing && !$node->getAttribute('tex')) {
$document->setAttribute($node, tex => UnTeX($thing)); } });
Tag('ltx:g', afterClose => sub {
my ($document, $node) = @_;
$node->parentNode->removeChild($node) unless $node->hasChildNodes; });
# \savebox -- already defined differntly in C.13 above ?
#**********************************************************************
# C.15 Font Selection
#**********************************************************************
#======================================================================
# C.15.1 Changing the Type Style
#======================================================================
# Text styles.
DefMacroI('\rmdefault', undef, 'cmr', locked => 1);
DefMacroI('\sfdefault', undef, 'cmss', locked => 1);
DefMacroI('\ttdefault', undef, 'cmtt', locked => 1);
DefMacroI('\bfdefault', undef, 'bx', locked => 1);
DefMacroI('\mddefault', undef, 'm', locked => 1);
DefMacroI('\itdefault', undef, 'it', locked => 1);
DefMacroI('\sldefault', undef, 'sl', locked => 1);
DefMacroI('\scdefault', undef, 'sc', locked => 1);
DefMacroI('\updefault', undef, 'n', locked => 1);
DefMacroI('\encodingdefault', undef, 'OT1');
DefMacroI('\familydefault', undef, '\rmdefault');
DefMacroI('\seriesdefault', undef, '\mddefault');
DefMacroI('\shapedefault', undef, '\updefault');
Let('\mediumseries', '\mdseries');
Let('\normalshape', '\upshape');
# ? DefMacro('\f@encoding','cm');
DefMacro('\f@family', 'cmr');
DefMacro('\f@series', 'm');
DefMacro('\f@shape', 'n');
DefMacro('\f@size', '10');
# These do NOT immediately effect the font!
DefMacro('\fontfamily{}', '\edef\f@family{#1}');
DefMacro('\fontseries{}', '\edef\f@series{#1}');
DefMacro('\fontshape{}', '\edef\f@shape{#1}');
# For fonts not allowed in math!!!
DefPrimitive('\not@math@alphabet@@ {}', sub {
if ($STATE->lookupValue('IN_MATH')) {
my $c = ToString($_[1]);
Warn('unexpected', $c, $_[0], "Command $c invalid in math mode"); }
return; });
# These DO immediately effect the font!
DefMacroI('\mdseries', undef, '\not@math@alphabet@@{\mddefault}\fontseries{\mddefault}\selectfont');
DefMacroI('\bfseries', undef, '\not@math@alphabet@@{\bfdefault}\fontseries{\bfdefault}\selectfont');
DefMacroI('\rmfamily', undef, '\not@math@alphabet@@{\rmdefault}\fontfamily{\rmdefault}\selectfont');
DefMacroI('\sffamily', undef, '\not@math@alphabet@@{\sfdefault}\fontfamily{\sfdefault}\selectfont');
DefMacroI('\ttfamily', undef, '\not@math@alphabet@@{\ttdefault}\fontfamily{\ttdefault}\selectfont');
DefMacroI('\upshape', undef, '\not@math@alphabet@@{\updefault}\fontshape{\updefault}\selectfont');
DefMacroI('\itshape', undef, '\not@math@alphabet@@{\itdefault}\fontshape{\itdefault}\selectfont');
DefMacroI('\slshape', undef, '\not@math@alphabet@@{\sldefault}\fontshape{\sldefault}\selectfont');
DefMacroI('\scshape', undef, '\not@math@alphabet@@{\scdefault}\fontshape{\scdefault}\selectfont');
DefMacroI('\normalfont', undef,
'\fontfamily{\rmdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
DefMacroI('\verbatim@font', undef,
'\fontfamily{\ttdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont');
Let('\reset@font', '\normalfont');
DefMacro('\@fontswitch{}{}', '\ifmmode #2\relax\else #1 \fi');
DefPrimitive('\selectfont', sub {
my $family = ToString(Expand(T_CS('\f@family')));
my $series = ToString(Expand(T_CS('\f@series')));
my $shape = ToString(Expand(T_CS('\f@shape')));
if (my $sh = LaTeXML::Common::Font::lookupFontFamily($family)) { MergeFont(%$sh); }
elsif (LoadFontMap($family)) {
# Special case hack: Tentatively treat family as the encoding! (typically "U" encoding)
MergeFont(encoding => $family); }
elsif (!LookupValue("reported_unrecognized_font_family_$family")) {
AssignValue("reported_unrecognized_font_family_$family", 1, 'global');
Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); }
if (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); }
elsif (!LookupValue("reported_unrecognized_font_series_$series")) {
AssignValue("reported_unrecognized_font_series_$series", 1, 'global');
Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); }
if (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); }
elsif (!LookupValue("reported_unrecognized_font_shape_$shape")) {
AssignValue("reported_unrecognized_font_shape_$shape", 1, 'global');
Info('unexpected', $shape, $_[0], "Unrecognized font shape '$shape'."); }
return; });
DefMacro('\usefont{}{}{}{}',
'\fontencoding{#1}\fontfamily{#2}\fontseries{#3}\fontshape{#4}\selectfont');
# If these series or shapes appear in math, they revert it to roman, medium, upright (?)
DefConstructor('\textmd@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { series => 'medium' }, alias => '\textmd',
beforeDigest => sub { DefMacro('\f@series', 'm'); });
DefConstructor('\textbf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { series => 'bold' }, alias => '\textbf',
beforeDigest => sub { DefMacro('\f@series', 'b'); });
DefConstructor('\textrm@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { family => 'serif' }, alias => '\textrm',
beforeDigest => sub { DefMacro('\f@family', 'cm'); });
DefConstructor('\textsf@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { family => 'sansserif' }, alias => '\textsf',
beforeDigest => sub { DefMacro('\f@family', 'cmss'); });
DefConstructor('\texttt@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { family => 'typewriter' }, alias => '\texttt',
beforeDigest => sub { DefMacro('\f@family', 'cmtt'); });
DefConstructor('\textup@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { shape => 'upright' }, alias => '\textup',
beforeDigest => sub { DefMacro('\f@shape', ''); });
DefConstructor('\textit@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { shape => 'italic' }, alias => '\textit',
beforeDigest => sub { DefMacro('\f@shape', 'it'); });
DefConstructor('\textsl@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { shape => 'slanted' }, alias => '\textsl',
beforeDigest => sub { DefMacro('\f@shape', 'sl'); });
DefConstructor('\textsc@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { shape => 'smallcaps' }, alias => '\textsc',
beforeDigest => sub { DefMacro('\f@shape', 'sc'); });
DefConstructor('\textnormal@math{}', "<ltx:text _noautoclose='1'>#1</ltx:text>", mode => 'text',
bounded => 1, font => { family => 'serif', series => 'medium', shape => 'upright' }, alias => '\textnormal',
beforeDigest => sub { DefMacro('\f@family', 'cmtt');
DefMacro('\f@series', 'm');
DefMacro('\f@shape', 'n'); });
# These really should be robust! which is a source of expand timing issues!
DefMacro('\textmd{}', '\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi', protected => 1);
DefMacro('\textbf{}', '\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi', protected => 1);
DefMacro('\textrm{}', '\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi', protected => 1);
DefMacro('\textsf{}', '\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi', protected => 1);
DefMacro('\texttt{}', '\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi', protected => 1);
DefMacro('\textup{}', '\ifmmode\textup@math{#1}\else{\upshape #1}\fi', protected => 1);
DefMacro('\textit{}', '\ifmmode\textit@math{#1}\else{\itshape #1}\fi', protected => 1);
DefMacro('\textsl{}', '\ifmmode\textsl@math{#1}\else{\slshape #1}\fi', protected => 1);
DefMacro('\textsc{}', '\ifmmode\textsc@math{#1}\else{\scshape #1}\fi', protected => 1);
DefMacro('\textnormal{}', '\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi', protected => 1);
DefPrimitive('\DeclareTextFontCommand{}{}', sub {
my ($stomach, $cmd, $font) = @_;
DefConstructorI($cmd, "{}",
"?#isMath(<ltx:text _noautoclose='1'>#1</ltx:text>)(#1)",
mode => 'text', bounded => 1,
beforeDigest => sub { Digest($font); (); });
return; });
DefPrimitive('\mathversion{}', sub {
my ($stomach, $version) = @_;
$version = ToString($version);
if ($version eq 'bold') {
AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 1), 'local'); }
elsif ($version eq 'normal') {
AssignValue(mathfont => LookupValue('mathfont')->merge(forcebold => 0), 'local'); }
else { Error('unexpected', $version, $stomach, "Unknown math verison '$version'"); } });
DefMacro('\not@math@alphabet{}{}', '\relax
\ifmmode
\@latex@error{Command \noexpand#1 invalid in math mode}%
{%
Please
\ifx#2\relax
define a new math alphabet
if you want to use a special font in math mode%
\else
use the math alphabet \noexpand#2 instead of
the #1 command%
\fi.
}%
\fi');
DefMacro('\math@version', 'normal');
DefPrimitive('\DeclareOldFontCommand{}{}{}', sub {
my ($stomach, $cmd, $font, $mathcmd) = @_;
DefMacroI($cmd, undef, sub {
return (LookupValue('IN_MATH') ? $mathcmd->unlist : $font->unlist); });
# Tokens(T_CS('\ifmmode'), $mathcmd->unlist, T_CS('\else'), $font->unlist, T_CS('\fi')));
return; });
DefMacro('\newfont{}{}', '\font#1=#2\relax');
Let('\normalcolor', '\relax');
#======================================================================
# C.15.2 Changing the Type Size
#======================================================================
# Handled in TeX.pool.ltxml
#======================================================================
# C.15.3 Special Symbol
#======================================================================
DefMacro('\symbol{}', '\char#1\relax');
# These in LaTeX, but not in the book...
DefPrimitiveI('\textdollar', undef, "\$");
DefPrimitiveI('\textemdash', undef, "\x{2014}"); # EM DASH
DefPrimitiveI('\textendash', undef, "\x{2013}"); # EN DASH
DefPrimitiveI('\textexclamdown', undef, UTF(0xA1)); # INVERTED EXCLAMATION MARK
DefPrimitiveI('\textquestiondown', undef, UTF(0xBF)); # INVERTED QUESTION MARK
DefPrimitiveI('\textquotedblleft', undef, "\x{201C}"); # LEFT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedblright', undef, "\x{201D}"); # RIGHT DOUBLE QUOTATION MARK
DefPrimitiveI('\textquotedbl', undef, "\""); # plain ascii DOUBLE QUOTATION
DefPrimitiveI('\textquoteleft', undef, "\x{2018}"); # LEFT SINGLE QUOTATION MARK
DefPrimitiveI('\textquoteright', undef, "\x{2019}"); # RIGHT SINGLE QUOTATION MARK
DefPrimitiveI('\textsterling', undef, UTF(0xA3)); # POUND SIGN
DefPrimitiveI('\textasteriskcentered', undef, "*");
DefPrimitiveI('\textbackslash', undef, UTF(0x5C)); # REVERSE SOLIDUS
DefPrimitiveI('\textbar', undef, "|");
DefPrimitiveI('\textbraceleft', undef, "{");
DefPrimitiveI('\textbraceright', undef, "}");
DefPrimitiveI('\textbullet', undef, "\x{2022}"); # BULLET
DefPrimitiveI('\textdaggerdbl', undef, "\x{2021}"); # DOUBLE DAGGER
DefPrimitiveI('\textdagger', undef, "\x{2020}"); # DAGGER
DefPrimitiveI('\textparagraph', undef, UTF(0xB6)); # PILCROW SIGN
DefPrimitiveI('\textsection', undef, UTF(0xA7)); # SECTION SIGN
DefPrimitiveI('\textless', undef, "<");
DefPrimitiveI('\textgreater', undef, ">");
DefPrimitiveI('\textcopyright', undef, UTF(0xA9)); # COPYRIGHT SIGN
DefPrimitiveI('\textasciicircum', undef, "^");
DefPrimitiveI('\textasciitilde', undef, "~");
DefPrimitiveI('\textcompwordmark', undef, ""); # ???
DefPrimitiveI('\textcapitalcompwordmark', undef, ""); # ???
DefPrimitiveI('\textascendercompwordmark', undef, ""); # ???
DefPrimitiveI('\textunderscore', undef, "_");
DefPrimitiveI('\textvisiblespace', undef, "\x{2423}"); # SYMBOL FOR SPACE; Not really the right symbol!
DefPrimitiveI('\textellipsis', undef, "\x{2026}"); # HORIZONTAL ELLIPSIS
DefPrimitiveI('\textregistered', undef, UTF(0xAE)); # REGISTERED SIGN
DefPrimitiveI('\texttrademark', undef, "\x{2122}"); # TRADE MARK SIGN
DefConstructor('\textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
mode => 'text');
DefConstructor('\@textsuperscript{}', "<ltx:sup>#1</ltx:sup>",
mode => 'text', locked => 1);
# This is something coming from xetex/xelatex ? Why define this way?
#DefConstructor('\realsuperscript{}', "<ltx:text yoffset='0.5em' _noautoclose='1'>#1</ltx:text>");
DefConstructor('\realsuperscript{}', "<ltx:sup>#1</ltx:sup>",
mode => 'text');
DefPrimitiveI('\textordfeminine', undef, UTF(0xAA)); # FEMININE ORDINAL INDICATOR
DefPrimitiveI('\textordmasculine', undef, UTF(0xBA)); # MASCULINE ORDINAL INDICATOR
our %unicode_enclosed_alphanumerics = (
0 => "\x{24EA}", 1 => "\x{2460}", 2 => "\x{2461}", 3 => "\x{2462}", 4 => "\x{2463}",
5 => "\x{2464}", 6 => "\x{2465}", 7 => "\x{2466}", 8 => "\x{2467}", 9 => "\x{2468}",
10 => "\x{2469}", 11 => "\x{246A}", 12 => "\x{246B}", 13 => "\x{246C}", 14 => "\x{246D}",
15 => "\x{246E}", 16 => "\x{246F}", 17 => "\x{2470}", 18 => "\x{2471}", 19 => "\x{2472}",
20 => "\x{2473}",
a => "\x{24D0}", b => "\x{24D1}", c => "\x{24D2}", d => "\x{24D3}", e => "\x{24D4}",
f => "\x{24D5}", g => "\x{24D6}", h => "\x{24D7}", i => "\x{24D8}", j => "\x{24D9}",
k => "\x{24DA}", l => "\x{24DB}", 'm' => "\x{24DC}", n => "\x{24DD}", o => "\x{24DE}",
p => "\x{24DF}", q => "\x{24E0}", r => "\x{24E1}", 's' => "\x{24E2}", t => "\x{24E3}",
u => "\x{24E4}", v => "\x{24E5}", w => "\x{24E6}", x => "\x{24E7}", y => "\x{24E8}",
z => "\x{24E9}", A => "\x{24B6}", B => "\x{24B7}", C => "\x{24B8}", D => "\x{24B9}",
E => "\x{24BA}", F => "\x{24BB}", G => "\x{24BC}", H => "\x{24BD}", I => "\x{24BE}",
J => "\x{24BF}", K => "\x{24C0}", L => "\x{24C1}", 'M' => "\x{24C2}", N => "\x{24C3}",
O => "\x{24C4}", P => "\x{24C5}", Q => "\x{24C6}", R => "\x{24C7}", 'S' => "\x{24C8}",
T => "\x{24C9}", U => "\x{24CA}", V => "\x{24CB}", W => "\x{24CC}", X => "\x{24CD}",
Y => "\x{24CE}", Z => "\x{24CF}");
DefPrimitive('\textcircled {}', sub {
my ($stomach, $arg) = @_;
my $text = ToString($arg);
# if we know of a circled unicode character - use it,
# if not - use the combining accent.
# For now supports the range 0 to 20 as well as the latin alphabet.
my $is_number = $text =~ /^\d+$/;
my $content = $unicode_enclosed_alphanumerics{$text} || "$text\x{20DD}";
my %props = $STATE->lookupValue('IN_MATH') ?
(role => ($is_number ? 'NUMBER' : 'UNKNOWN'), meaning => "circled-$text") : ();
return Box($content, undef, undef, undef, %props); });
DefPrimitiveI('\SS', undef, 'SS', # ?
locked => 1); # cause it shows up in uclist!
DefMacroI('\dag', undef, '\ifmmode{\dagger}\else\textdagger\fi');
DefMacroI('\ddag', undef, '\ifmmode{\ddagger}\else\textdaggerdbl\fi');
DefConstructor('\sqrtsign Digested',
"<ltx:XMApp><ltx:XMTok meaning='square-root'/><ltx:XMArg>#1</ltx:XMArg></ltx:XMApp>");
DefPrimitiveI('\mathparagraph', undef, UTF(0xB6));
DefPrimitiveI('\mathsection', undef, UTF(0xA7));
DefPrimitiveI('\mathdollar', undef, '$');
DefPrimitiveI('\mathsterling', undef, UTF(0xA3));
DefPrimitiveI('\mathunderscore', undef, '_');
DefPrimitiveI('\mathellipsis', undef, "\x{2026}");
# Are these glyph "pieces" or use alone?
DefMathI('\arrowvert', undef, "|", role => 'VERTBAR');
DefMathI('\Arrowvert', undef, "\x{2225}", role => 'VERTBAR');
# The following are glyph "pieces"...
DefPrimitiveI('\braceld', undef, "\x{239D}"); # left brace down part
DefPrimitiveI('\bracelu', undef, "\x{239B}"); # left brace up part
DefPrimitiveI('\bracerd', undef, "\x{23A0}"); # right brace down part
DefPrimitiveI('\braceru', undef, "\x{239E}"); # right brace up part
DefMathI('\cdotp', undef, "\x{22C5}", role => 'MULOP');
DefMathI('\ldotp', undef, ".", role => 'MULOP');
DefMathI('\intop', undef, "\x{222B}", role => 'INTOP', meaning => 'integral',
scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
DefMathI('\ointop', undef, "\x{222E}", role => 'INTOP', meaning => 'contour-integral',
scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp);
# WHat are these? They look like superscripted parentheses, or combining accents!
# \lhook
# \rhook
Let('\gets', '\leftarrow');
DefPrimitiveI('\lmoustache', undef, "\x{23B0}");
DefPrimitiveI('\rmoustache', undef, "\x{23B1}");
DefMathI('\mapstochar', undef, "\x{21A6}", role => 'ARROW', meaning => 'maps-to');
DefMathI('\owns', undef, "\x{220B}", role => 'RELOP', meaning => 'contains');
# \skew{}{}{} ????
# \symbol lookup symbol in font by index?
#**********************************************************************
# Other stuff
#**********************************************************************
# Some stuff that got missed in the appendices ?
RawTeX(<<'EoTeX');
\def\@namedef#1{\expandafter\def\csname #1\endcsname}
\def\@nameuse#1{\csname #1\endcsname}
\def\@cons#1#2{\begingroup\let\@elt\relax\xdef#1{#1\@elt #2}\endgroup}
\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}
\def\@carcube#1#2#3#4\@nil{#1#2#3}
\def\nfss@text#1{{\mbox{#1}}}
\def\@sect#1#2#3#4#5#6[#7]#8{}
EoTeX
Let('\@begindocumenthook', '\@empty');
DefMacroI('\@preamblecmds', undef, Tokens());
DefMacro('\@ifdefinable DefToken {}', sub {
my ($gullet, $token, $if) = @_;
if (isDefinable($token)) {
return $if->unlist }
else {
my ($slash, @s) = ExplodeText($token->toString);
DefMacroI('\reserved@a', undef, Tokens(@s));
return (T_CS('\@notdefinable')); } });
Let('\@@ifdefinable', '\@ifdefinable');
DefMacro('\@rc@ifdefinable DefToken {}', sub {
my ($gullet, $token, $if) = @_;
Let('\@ifdefinable', '\@@ifdefinable');
return $if->unlist; });
DefMacroI('\@notdefinable', undef, <<'EOL');
\@latex@error{%
Command \@backslashchar\reserved@a\space
already defined.
Or name \@backslashchar\@qend... illegal,
see p.192 of the manual}
EOL
DefMacroI('\@qend', undef, Tokens(Explode('end')));
DefMacroI('\@qrelax', undef, Tokens(Explode('relax')));
DefMacroI('\@spaces', undef, '\space\space\space\space');
Let('\@sptoken', T_SPACE);
DefMacroI('\@uclclist', undef, '\oe\OE\o\O\ae\AE\dh\DH\dj\DJ\l\L\ng\NG\ss\SS\th\TH');
RawTeX(<<'EOL');
\DeclareRobustCommand{\MakeUppercase}[1]{{%
\def\i{I}\def\j{J}%
\def\reserved@a##1##2{\let##1##2\reserved@a}%
\expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
\let\UTF@two@octets@noexpand\@empty
\let\UTF@three@octets@noexpand\@empty
\let\UTF@four@octets@noexpand\@empty
\protected@edef\reserved@a{\uppercase{#1}}%
\reserved@a
}}
\DeclareRobustCommand{\MakeLowercase}[1]{{%
\def\reserved@a##1##2{\let##2##1\reserved@a}%
\expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}%
\let\UTF@two@octets@noexpand\@empty
\let\UTF@three@octets@noexpand\@empty
\let\UTF@four@octets@noexpand\@empty
\protected@edef\reserved@a{\lowercase{#1}}%
\reserved@a
}}
\protected@edef\MakeUppercase#1{\MakeUppercase{#1}}
\protected@edef\MakeLowercase#1{\MakeLowercase{#1}}
EOL
#======================================================================
DefMacroI('\@ehc', undef, "I can't help");
DefMacro('\@gobble{}', Tokens());
DefMacro('\@gobbletwo{}{}', Tokens());
DefMacro('\@gobblefour{}{}{}{}', Tokens());
DefMacro('\@firstofone{}', sub { $_[1]; });
Let('\@iden', '\@firstofone');
DefMacro('\@firstoftwo{}{}', sub { $_[1]; });
DefMacro('\@secondoftwo{}{}', sub { $_[2]; });
DefMacro('\@thirdofthree{}{}{}', sub { $_[3]; });
DefMacro('\@expandtwoargs{}{}{}', sub {
($_[1]->unlist, T_BEGIN, Expand($_[2])->unlist, T_END, T_BEGIN, Expand($_[3])->unlist, T_END); });
DefMacro('\@makeother{}', sub {
my $letter = ToString($_[1]); $letter =~ s/^\\//;
AssignCatcode($letter => CC_OTHER, 'local'); });
RawTeX(<<'EoTeX');
{\catcode`\^^M=13 \gdef\obeycr{\catcode`\^^M13 \def^^M{\\\relax}%
\@gobblecr}%
{\catcode`\^^M=13 \gdef\@gobblecr{\@ifnextchar
\@gobble\ignorespaces}}%
\gdef\restorecr{\catcode`\^^M5 }}
EoTeX
RawTeX(<<'EoTeX');
\begingroup
\catcode`P=12
\catcode`T=12
\lowercase{
\def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
\expandafter\endgroup\x
\def\strip@pt{\expandafter\rem@pt\the}
\def\strip@prefix#1>{}
\def\@sanitize{\@makeother\ \@makeother\\\@makeother\$\@makeother\&%
\@makeother\#\@makeother\^\@makeother\_\@makeother\%\@makeother\~}
\def \@onelevel@sanitize #1{%
\edef #1{\expandafter\strip@prefix
\meaning #1}%
}
\def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&%
\do\#\do\^\do\_\do\%\do\~}
EoTeX
DefMacroI('\nfss@catcodes', undef, <<'EOMacro');
\makeatletter
\catcode`\ 9%
\catcode`\^^I9%
\catcode`\^^M9%
\catcode`\\\z@
\catcode`\{\@ne
\catcode`\}\tw@
\catcode`\#6%
\catcode`\^7%
\catcode`\%14%
\@makeother\<%
\@makeother\>%
\@makeother\*%
\@makeother\.%
\@makeother\-%
\@makeother\/%
\@makeother\[%
\@makeother\]%
\@makeother\`%
\@makeother\'%
\@makeother\"%
EOMacro
DefMacroI('\ltx@hard@MessageBreak', undef, '^^J');
sub make_message {
my ($cmd, @args) = @_;
my $stomach = $STATE->getStomach;
my $lead_arg = ToString(shift(@args)) || '';
$lead_arg =~ s/(?:\\\@?spaces?)+//g;
my $type = $lead_arg || $cmd;
$stomach->bgroup;
Let('\protect', '\string');
# Note that the arg really should be digested to get to the underlying text,
# but why tempt fate, when we're already making an error message?
#
# We have to expand padded with two \@spaces, to avoid pointless errors.
# e.g. a trailing "\csq@noline" from csquotes.sty, which was previously
# \let\csq@noline\@gobble
# will produce a "gobble has no argument" error on *every* message.
my $message = join(" ",
map { ToString(Expand($_, T_CS('\@spaces'), T_CS('\@spaces'))) } @args);
$type =~ s/(?:\\\@?spaces?)+/ /g;
$message =~ s/(?:\\\@?spaces?)+/ /g;
$message =~ s/\\MessageBreak/\n/g;
$stomach->egroup;
return ('latex', $type, $stomach, $message); }
# LaTeX 3 is loaded roughly here in the latex.ltx flow,
# early and always.
# ---
# Postponed until we can address the *performance penalty*
# instead, add these LoadPool calls to packages that expect them.
# ---
# LoadPool('expl3');
DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this.
DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[1], $_[2], $_[3], $_[4])); });
DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[1], $_[2])); });
DefPrimitive('\GenericInfo{}{}', sub { Info(make_message('\GenericInfo', $_[1], $_[2])); });
Let('\MessageBreak', '\relax');
RawTeX(<<'EoTeX');
\gdef\PackageError#1#2#3{%
\GenericError{%
(#1)\@spaces\@spaces\@spaces\@spaces
}{%
Package #1 Error: #2%
}{%
See the #1 package documentation for explanation.%
}{#3}%
}
\def\PackageWarning#1#2{%
\GenericWarning{%
(#1)\@spaces\@spaces\@spaces\@spaces
}{%
Package #1 Warning: #2%
}%
}
\def\PackageWarningNoLine#1#2{%
\PackageWarning{#1}{#2\@gobble}}
\def\PackageInfo#1#2{%
\GenericInfo{%
(#1) \@spaces\@spaces\@spaces
}{%
Package #1 Info: #2%
}%
}
\def\ClassError#1#2#3{%
\GenericError{%
(#1) \space\@spaces\@spaces\@spaces
}{%
Class #1 Error: #2%
}{%
See the #1 class documentation for explanation.%
}{#3}%
}
\def\ClassWarning#1#2{%
\GenericWarning{%
(#1) \space\@spaces\@spaces\@spaces
}{%
Class #1 Warning: #2%
}%
}
\def\ClassWarningNoLine#1#2{%
\ClassWarning{#1}{#2\@gobble}}
\def\ClassInfo#1#2{%
\GenericInfo{%
(#1) \space\space\@spaces\@spaces
}{%
Class #1 Info: #2%
}%
}
\def\@latex@error#1#2{%
\GenericError{%
\space\space\space\@spaces\@spaces\@spaces
}{%
LaTeX Error: #1%
}{%
See the LaTeX manual or LaTeX Companion for explanation.%
}{#2}%
}
\def\@latex@warning#1{%
\GenericWarning{%
\space\space\space\@spaces\@spaces\@spaces
}{%
LaTeX Warning: #1%
}%
}
\def\@latex@warning@no@line#1{%
\@latex@warning{#1\@gobble}}
\def\@latex@info#1{%
\GenericInfo{%
\@spaces\@spaces\@spaces
}{%
LaTeX Info: #1%
}%
}
\def\@latex@info@no@line#1{%
\@latex@info{#1\@gobble}}
EoTeX
DefMacro('\@setsize{}{}{}{}', '');
DefMacro('\hexnumber@ {}', '\ifcase\number#1
0\or 1\or 2\or 3\or 4\or 5\or 6\or 7\or 8\or
9\or A\or B\or C\or D\or E\or F\fi');
DefMacro('\on@line', ' on input line \the\inputlineno');
Let('\@warning', '\@latex@warning');
Let('\@@warning', '\@latex@warning@no@line');
DefMacro('\G@refundefinedtrue', '');
DefMacro('\@nomath{}',
'\relax\ifmmode\@font@warning{Command \noexpand#1invalid in math mode}\fi');
DefMacro('\@font@warning{}',
'\GenericWarning{(Font)\@spaces\@spaces\@spaces\space\space}{LaTeX Font Warning: #1}');
#======================================================================
RawTeX(<<'EOTeX');
\chardef\@xxxii=32
\mathchardef\@Mi=10001
\mathchardef\@Mii=10002
\mathchardef\@Miii=10003
\mathchardef\@Miv=10004
\def\@fontenc@load@list{\@elt{T1,OT1}}
EOTeX
DefMacroI('\@vpt', undef, '5');
DefMacroI('\@vipt', undef, '6');
DefMacroI('\@viipt', undef, '7');
DefMacroI('\@viiipt', undef, '8');
DefMacroI('\@ixpt', undef, '9');
DefMacroI('\@xpt', undef, '10');
DefMacroI('\@xipt', undef, '10.95');
DefMacroI('\@xiipt', undef, '12');
DefMacroI('\@xivpt', undef, '14.4');
DefMacroI('\@xviipt', undef, '17.28');
DefMacroI('\@xxpt', undef, '20.74');
DefMacroI('\@xxvpt', undef, '24.88');
DefMacroI('\@tempa', undef, '');
DefMacroI('\@tempb', undef, '');
DefMacroI('\@tempc', undef, '');
DefMacroI('\@gtempa', undef, '');
RawTeX(<<'EOTeX');
\long\def \loop #1\repeat{%
\def\iterate{#1\relax % Extra \relax
\expandafter\iterate\fi
}%
\iterate
\let\iterate\relax
}
\newdimen\@ydim
\let\@@hyph=\-
\newbox\@arstrutbox
\newbox\@begindvibox
\newcount\@botnum
\newdimen\@botroom
\newcount\@chclass
\newcount\@chnum
\newdimen\@clnht
\newdimen\@clnwd
\newdimen\@colht
\newcount\@colnum
\newdimen\@colroom
\newbox\@curfield
\newbox\@curline
\newcount\@currtype
\newcount\@curtab
\newcount\@curtabmar
\newbox\@dashbox
\newcount\@dashcnt
\newdimen\@dashdim
\newcount\@dbltopnum
\newdimen\@dbltoproom
\let\@dischyph=\-
\newcount\@enumdepth
\newcount\@floatpenalty
\newdimen\@fpmin
\newcount \@fpstype
\newcount\@highpenalty
\newcount\@hightab
\newbox\@holdpg
\newinsert \@kludgeins
\newcount\@lastchclass
\newbox\@leftcolumn
\newbox\@linechar
\newdimen\@linelen
\newcount\@lowpenalty
\newdimen\@maxdepth
\newcount\@medpenalty
\newdimen\@mparbottom \@mparbottom\z@
\newinsert\@mpfootins
\newcount\@mplistdepth
\newcount\@multicnt
\newcount\@nxttabmar
\newbox\@outputbox
\newdimen\@pagedp
\newdimen\@pageht
\newbox\@picbox
\newdimen\@picht
\newdimen \@reqcolroom
\newskip\@rightskip \@rightskip \z@skip
lib/LaTeXML/Package/LaTeX.pool.ltxml view on Meta::CPAN
\newbox\rootbox
\newcount\@eqcnt
\newcount\@eqpen
\newif\if@eqnsw\@eqnswtrue
\newskip\@centering
\@centering = 0pt plus 1000pt
\let\@eqnsel=\relax
\long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
#2\relax}\fi}
\long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum
\else\expandafter\@gobble\fi{#1}}
\long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi}
\long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim
\else\expandafter\@gobble\fi{#1}}
\long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi}
\long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw
\else\@gobbletwo\fi{#1}\fi}
\def\@nnil{\@nil}
\def\@fornoop#1\@@#2#3{}
\long\def\@for#1:=#2\do#3{%
\expandafter\def\expandafter\@fortmp\expandafter{#2}%
\ifx\@fortmp\@empty \else
\expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi}
\long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else
#5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi}
\long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
\expandafter\@fornoop \else
#4\relax\expandafter\@iforloop\fi#2\@@#3{#4}}
\def\@tfor#1:={\@tf@r#1 }
\long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else
\@tforloop#2\@nil\@nil\@@#1{#3}\fi}
\long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
\expandafter\@fornoop \else
#4\relax\expandafter\@tforloop\fi#2\@@#3{#4}}
\long\def\@break@tfor#1\@@#2#3{\fi\fi}
\def\@removeelement#1#2#3{%
\def\reserved@a##1,#1,##2\reserved@a{##1,##2\reserved@b}%
\def\reserved@b##1,\reserved@b##2\reserved@b{%
\ifx,##1\@empty\else##1\fi}%
\edef#3{%
\expandafter\reserved@b\reserved@a,#2,\reserved@b,#1,\reserved@a}}
\def\remove@to@nnil#1\@nnil{}
\def\remove@angles#1>{\set@simple@size@args}
\def\remove@star#1*{#1}
\def\@defaultunits{\afterassignment\remove@to@nnil}
\newif\ifmath@fonts \math@fontstrue
\newbox\@labels
\newif\if@inlabel \@inlabelfalse
\newif\if@newlist \@newlistfalse
\newif\if@noparitem \@noparitemfalse
\newif\if@noparlist \@noparlistfalse
\newif\if@noitemarg \@noitemargfalse
\newif\if@nmbrlist \@nmbrlistfalse
\def\glb@settings{}%
EOTeX
DefMacroI('\@height', undef, 'height');
DefMacroI('\@width', undef, 'width');
DefMacroI('\@depth', undef, 'depth');
DefMacroI('\@minus', undef, 'minus');
DefMacroI('\@plus', undef, 'plus');
DefMacroI('\hb@xt@', undef, '\hbox to');
DefMacroI('\hmode@bgroup', undef, '\leavevmode\bgroup');
DefMacroI('\@backslashchar', undef, T_OTHER('\\'));
DefMacroI('\@percentchar', undef, T_OTHER('%'));
DefMacroI('\@charlb', undef, T_LETTER('{'));
DefMacroI('\@charrb', undef, T_LETTER('}'));
#======================================================================
DefMacroI('\check@mathfonts', undef, Tokens());
DefMacro('\fontsize{}{}', Tokens());
# https://tex.stackexchange.com/questions/112492/setfontsize-vs-fontsize#112501
DefMacro('\@setfontsize{}{}{}', '\let\@currsize#1');
DefMacroI('\@vpt', undef, T_OTHER('5'));
DefMacroI('\@vipt', undef, T_OTHER('6'));
DefMacroI('\@viipt', undef, T_OTHER('7'));
DefMacroI('\@viiipt', undef, T_OTHER('8'));
DefMacroI('\@ixpt', undef, T_OTHER('9'));
DefMacro('\@xpt', '10');
DefMacro('\@xipt', '10.95');
DefMacro('\@xiipt', '12');
DefMacro('\@xivpt', '14.4');
DefMacro('\@xviipt', '17.28');
DefMacro('\@xxpt', '20.74');
DefMacro('\@xxvpt', '24.88');
DefMacro('\vpt', '\edef\f@size{\@vpt}\rm');
DefMacro('\vipt', '\edef\f@size{\@vipt}\rm');
DefMacro('\viipt', '\edef\f@size{\@viipt}\rm');
DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm');
DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm');
DefMacro('\xpt', '\edef\f@size{\@xpt}\rm');
DefMacro('\xipt', '\edef\f@size{\@xipt}\rm');
DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm');
DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm');
DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm');
DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm');
DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm');
DefMacroI('\defaultscriptratio', undef, '.7');
DefMacroI('\defaultscriptscriptratio', undef, '.5');
#======================================================================
DefMacroI('\loggingoutput', undef, Tokens());
DefMacroI('\loggingall', undef, Tokens());
DefMacroI('\tracingfonts', undef, Tokens());
DefMacroI('\showoverfull', undef, Tokens());
DefMacroI('\showoutput', undef, Tokens());
#======================================================================
# Various symbols, accents, etc from Chapter 3 defined in TeX.pool
#**********************************************************************
# Semi-Undocumented stuff
#**********************************************************************
DefMacro('\@ifnextchar DefToken {}{}', sub {
my ($gullet, $token, $if, $else) = @_;
my $next = $gullet->readNonSpace;
# NOTE: Not actually substituting, but collapsing ## pairs!!!!
# use \egroup for $next, if we've fallen off end?
((XEquals($token, (defined $next ? $next : T_END)) ? $if : $else)->unlist,
(defined $next ? ($next) : ())); });
Let('\kernel@ifnextchar', '\@ifnextchar');
Let('\@ifnext', '\@ifnextchar'); # ????
# Hacky version matches multiple chars! but does NOT expand
DefMacro('\@ifnext@n {} {}{}', sub {
my ($gullet, $tokens, $if, $else) = @_;
my @toks = $tokens->unlist;
my @read = ();
while (my $t = $gullet->readToken) {
push(@read, $t);
if ($t->equals($toks[0])) { shift(@toks); }
else { last; } }
return Tokens((@toks ? $else->unlist : $if->unlist), @read); });
DefMacro('\@ifstar {}{}', sub {
my ($gullet, $if, $else) = @_;
my $next = $gullet->readNonSpace;
if (T_OTHER('*')->equals($next)) {
$if; }
else {
($else, ($next ? $next : ())); } });
DefMacro('\@dblarg {}', '\kernel@ifnextchar[{#1}{\@xdblarg{#1}}');
DefMacro('\@xdblarg {}{}', '#1[{#2}]{#2}');
DefMacro('\@testopt{}{}', sub {
my ($gullet, $cmd, $option) = @_;
($gullet->ifNext(T_OTHER('[')) ? $cmd->unlist
: ($cmd->unlist, T_OTHER('['), $option->unlist, T_OTHER(']'))); });
RawTeX(<<'EoTeX');
\def\@protected@testopt#1{%%
\ifx\protect\@typeset@protect
\expandafter\@testopt
\else
\@x@protect#1%
\fi}
EoTeX
Let('\l@ngrel@x', '\relax'); # Never actually used anywhere, but...
DefMacro('\@star@or@long{}', '\@ifstar{\let\l@ngrel@x\relax#1}{\let\l@ngrel@x\long#1}');
# maybe this is easiest just to punt.
RawTeX(<<'EoTeX');
\def\in@#1#2{%
\def\in@@##1#1##2##3\in@@{%
\ifx\in@##2\in@false\else\in@true\fi}%
\in@@#2#1\in@\in@@}
\newif\ifin@
EoTeX
DefMacro('\IfFileExists{}{}{}', sub {
my ($gullet, $file, $if, $else) = @_;
my $file_string = ToString(Expand($file));
if (FindFile($file_string)) {
DefMacro('\@filef@und', '"' . $file_string . '" ');
return ($if->unlist); }
else {
return ($else->unlist); } });
DefMacro('\InputIfFileExists{}{}{}', sub {
my ($gullet, $file, $if, $else) = @_;
$file = Expand($file);
my $file_string = ToString($file);
if (FindFile($file_string)) {
DefMacro('\@filef@und', '"' . $file_string . '" ');
return Tokens($if, T_CS('\@addtofilelist'), T_BEGIN, $file, T_END,
T_CS('\ltx@input'), T_BEGIN, $file, T_END); }
else { return ($else->unlist); } });
#======================================================================
# Hair
DefPrimitiveI('\makeatletter', undef, sub { AssignCatcode('@' => CC_LETTER, 'local'); });
DefPrimitiveI('\makeatother', undef, sub { AssignCatcode('@' => CC_OTHER, 'local'); });
#**********************************************************************
#**********************************************************************
# Sundry (is this ams ?)
DefPrimitiveI('\textprime', undef, UTF(0xB4)); # ACUTE ACCENT
Let('\endgraf', '\par');
Let('\endline', '\cr');
#**********************************************************************
# Should be defined in each (or many) package, but it's not going to
# get set correctly or maintained, so...
DefMacroI('\fileversion', undef, Tokens());
DefMacroI('\filedate', undef, Tokens());
# Ultimately these may be overridden by babel, or otherwise,
# various of these are defined in various places by different classes.
DefMacroI('\chaptername', undef, 'Chapter');
DefMacroI('\partname', undef, 'Part');
# The rest of these are defined in some classes, but not most.
#DefMacroI('\sectionname', undef, 'Section');
#DefMacroI('\subsectionname', undef, 'Subsection');
#DefMacroI('\subsubsectionname', undef, 'Subsubsection');
#DefMacroI('\paragraphname', undef, 'Paragraph');
#DefMacroI('\subparagraphname', undef, 'Subparagraph');
DefMacroI('\appendixname', undef, 'Appendix');
# These aren't defined in LaTeX,
# these definitions will give us more meaningful typerefnum's
DefMacroI('\sectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\subsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\subsubsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces');
DefMacroI('\paragraphtyperefname', undef, '\lx@paragraphsign\lx@ignorehardspaces');
DefMacroI('\subparagraphtyperefname', undef, '\lx@paragraphsign\lx@ignorehardspaces');
#**********************************************************************
# Stuff that would appear in the aux file... maybe somebody uses it?
DefMacro('\bibdata{}', Tokens());
DefMacro('\bibcite{}{}', Tokens());
DefMacro('\citation{}', Tokens());
DefMacro('\contentsline{}{}{}', Tokens());
DefMacro('\newlabel{}{}', Tokens());
DefMacroI('\stop', undef, sub { $_[0]->closeMouth(1); return; });
DefMacroI('\ignorespacesafterend', undef, Tokens());
Let('\mathgroup', '\fam');
Let('\mathalpha', '\relax');
#\def\mathhexbox#1#2#3{\mbox{$\m@th \mathchar"#1#2#3$}}
DefPrimitive('\mathhexbox {}{}{}', sub {
my ($stomach, $a, $b, $c) = @_;
my $n = ToString($a) * 256 + ToString($b) * 16 + ToString($c);
my ($role, $glyph) = decodeMathChar($n);
return Box($glyph, LookupValue('font')->specialize($glyph)); });
DefMacroI('\nocorrlist', undef, ',.');
Let('\nocorr', '\relax');
Let('\check@icl', '\@empty');
Let('\check@icr', '\@empty');
DefMacro('\text@command{}', ''); # ?
DefMacro('\check@nocorr@ Until:\nocorr Until:\@nil', '');
RawTeX('\newif\ifmaybe@ic');
DefMacroI('\maybe@ic', undef, '');
DefMacroI('\maybe@ic@', undef, '');
# \t@st@ic
DefMacroI('\sw@slant', undef, '');
DefMacroI('\fix@penalty', undef, '');
DefPrimitiveI('\@@end', undef, sub { $_[0]->getGullet->flush; return; });
#**********************************************************************
# Modern pdflatex seems to come with hyphenation tables predefined
# for many languages. We don't need or use hyphenation tables,
# but some (versions of some) software (babel), check for
# the presence of these \l@<language> macros
# But also see \iflanguage (re)defined in babel.def.ltxml
RawTeX(<<'EoTeX');
\newlanguage\l@english
\newlanguage\l@usenglishmax
\newlanguage\l@USenglish
\newlanguage\l@dumylang
\newlanguage\l@nohyphenation
\newlanguage\l@arabic
\newlanguage\l@basque
\newlanguage\l@bulgarian
\newlanguage\l@coptic
\newlanguage\l@welsh
\newlanguage\l@czech
\newlanguage\l@slovak
\newlanguage\l@german
\newlanguage\l@ngerman
\newlanguage\l@danish
\newlanguage\l@esperanto
\newlanguage\l@spanish
\newlanguage\l@catalan
\newlanguage\l@galician
\newlanguage\l@estonian
\newlanguage\l@farsi
\newlanguage\l@finnish
\newlanguage\l@french
\newlanguage\l@greek
\newlanguage\l@monogreek
\newlanguage\l@ancientgreek
\newlanguage\l@croatian
\newlanguage\l@hungarian
\newlanguage\l@interlingua
\newlanguage\l@ibycus
\newlanguage\l@indonesian
\newlanguage\l@icelandic
\newlanguage\l@italian
\newlanguage\l@latin
\newlanguage\l@mongolian
\newlanguage\l@dutch
\newlanguage\l@norsk
\newlanguage\l@polish
\newlanguage\l@portuguese
\newlanguage\l@pinyin
\newlanguage\l@romanian
\newlanguage\l@russian
\newlanguage\l@slovenian
\newlanguage\l@uppersorbian
\newlanguage\l@serbian
\newlanguage\l@swedish
\newlanguage\l@turkish
\newlanguage\l@ukenglish
\newlanguage\l@ukrainiane
EoTeX
#**********************************************************************
DefPrimitive('\protected@write Number {}{}', sub {
my ($stomach, $port, $prelude, $tokens) = @_;
$port = ToString($port);
$stomach->bgroup;
Let('\thepage', '\relax');
my @stuff = Digest($prelude);
Let('\protect', '\@unexpandable@protect');
if (my $filename = LookupValue('output_file:' . $port)) {
my $handle = $filename . '_contents';
my $contents = LookupValue($handle);
AssignValue($handle => $contents . UnTeX($tokens) . "\n", 'global'); }
else {
Note(UnTeX($tokens)); }
$stomach->egroup;
return @stuff; });
#**********************************************************************
# LaTeX now includes fixltx2e by default.
# https://www.latex-project.org/news/latex2e-news/ltnews22.pdf
# This package allows you to define the font used for
# emphasis (\emph) within emphasis.
# For latexml, that styling should be left to the ultimate output,
# so we just define the command as a dummy.
DefMacro('\eminnershape', "");
# Undoubtedly not good enough
DefMacro('\TextOrMath{}{}', '\ifmmode#2\else#1\fi');
DefConstructor('\textsubscript{}', "<ltx:sub>#1</ltx:sub>",
mode => 'text');
#**********************************************************************
# We need this bit from utf8.def for textcomp
DefPrimitive('\DeclareUnicodeCharacter Expanded {}', sub {
my ($stomach, $hexcode, $expansion) = @_;
my $char = $hexcode->toString();
if ($char =~ /^[0-9a-fA-F]+$/) {
if ((my $cp = hex($char)) <= 0x10FFFF) {
$char = UTF($cp);
AssignCatcode($char, CC_ACTIVE);
DefMacroI(T_ACTIVE($char), undef, $expansion); }
else {
Error('unexpected', $char, $stomach,
"$char too large for Unicode. Values between 0 and 10FFFF are permitted."); } }
else {
Error('unexpected', $char, $stomach,
"'$char' is not a hexadecimal number."); } });
# LaTeX now includes textcomp by default.
RequirePackage('textcomp');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Expl3 "Experimental LaTeX 3" is no longer Experimental!
# It is beginning to be built into latex.ltx
# We WILL need a new strategy to keep up; probably based in some form
# of pre-read/pre-processed latex.ltx !
#
# For now, a few macros required by other packages will be included:
DefMacroI(T_CS('\hook_gput_code:nnn'), '{}{}{}', '');
DefMacro('\NewHook{}', '');
DefMacro('\NewReversedHook{}', '');
DefMacro('\NewMirroredHookPair{}{}', '');
DefMacro('\ActivateGenericHook{}', '');
DefMacro('\DisableGenericHook{}', '');
DefMacro('\AddToHook{}[]{}', '');
DefMacro('\AddToHookNext{}{}', '');
DefMacro('\ClearHookNext{}', '');
DefMacro('\RemoveFromHook{}[]', '');
DefMacro('\SetDefaultHookLabel{}', '');
DefMacro('\PushDefaultHookLabel{}', '');
DefMacro('\PopDefaultHookLabel', '');
DefMacro('\UseHook{}', '');
DefMacro('\UseOneTimeHook{}', '');
DefMacro('\ShowHook{}', '');
DefMacro('\LogHook{}', '');
DefMacro('\DebugHooksOn', '');
DefMacro('\DebugHooksOff', '');
DefMacro('\DeclareHookRule{}{}{}{}', '');
DefMacro('\DeclareDefaultHookRule{}{}{}', '');
DefMacro('\ClearHookRule{}{}{}', '');
DefMacro('\IfHookEmptyTF{}{}{}', '#3');
DefMacro('\IfHookExistsTF{}{}{}', '#3');
DefMacro('\MakeTextLowercase', '\lowercase');
DefMacro('\MakeTextUppercase', '\uppercase');
DefConditional('\if@includeinrelease');
Let('\@kernel@after@enddocument', '\@empty');
Let('\@kernel@after@enddocument@afterlastpage', '\@empty');
Let('\@kernel@before@begindocument', '\@empty');
Let('\@kernel@after@begindocument', '\@empty');
Let('\conditionally@traceon', '\@empty');
Let('\conditionally@traceoff', '\@empty');
#**********************************************************************
1;
( run in 0.858 second using v1.01-cache-2.11-cpan-d8267643d1d )