# -*- 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;
#**********************************************************************
# 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');
# 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')?>",
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $options = $whatsit->getArg(1);
LoadClass(ToString($whatsit->getArg(2)),
options => [($options ? split(/,\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'); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $class = ToString($whatsit->getArg(2));
my $options = $whatsit->getArg(1);
$options = [($options ? split(/,\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.
LoadClass($class, options => $options, after => Tokens(T_CS('\compat@loadpackages')));
return; });
DefPrimitiveI('\compat@loadpackages', undef, sub {
my $name = ToString(Digest(T_CS('\@currname')));
my $type = ToString(Digest(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 {
DefMacro('\@currenvir', $_[1]);
AssignValue(current_environment => ToString($_[1])); });
Let('\@currenvline', '\@empty');
DefMacro('\begin{}', sub {
my ($gullet, $env) = @_;
my $name = $env && ToString($env);
if (IsDefined("\\begin{$name}")) {
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 { LaTeXML::Core::Stomach::makeError($_[0], 'undefined', $undef); })); }
(T_CS('\begingroup'), Invocation(T_CS('\lx@setcurrenvir'), $env), $token); } });
DefMacro('\end{}', sub {
my ($gullet, $env) = @_;
my $name = $env && ToString($env);
my $t;
if (IsDefined($t = T_CS("\\end{$name}"))) { $t; } # Magic CS!
elsif (IsDefined($t = T_CS("\\end$name"))) { ($t, T_CS('\endgroup')); }
else { (T_CS('\endgroup')); } });
#======================================================================
# C.1.3 Fragile Commands
#======================================================================
# Because of the way we `move information', revertable and pre-processed,
# I don't think we actually need to do anything ...
# [Course that means we're not _really_ TeX!]
# \protect is already in TeX for obscure reasons...
#Let('\@typeset@protect','\relax');
RawTeX(<<'EOTeX');
\def\@ignorefalse{\global\let\if@ignore\iffalse}
\def\@ignoretrue {\global\let\if@ignore\iftrue}
\def\zap@space#1 #2{%
#1%
\ifx#2\@empty\else\expandafter\zap@space\fi
#2}
\def\@unexpandable@protect{\noexpand\protect\noexpand}
\def\x@protect#1{%
\ifx\protect\@typeset@protect\else
\@x@protect#1%
\fi
}
\def\@x@protect#1\fi#2#3{%
\fi\protect#1%
}
\let\@typeset@protect\relax
\def\set@display@protect{\let\protect\string}
\def\set@typeset@protect{\let\protect\@typeset@protect}
\def\protected@edef{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\edef
}
\def\protected@xdef{%
\let\@@protect\protect
\let\protect\@unexpandable@protect
\afterassignment\restore@protect
\xdef
}
\def\unrestored@protected@xdef{%
\let\protect\@unexpandable@protect
\xdef
}
\def\restore@protect{\let\protect\@@protect}
\set@typeset@protect
\def\@nobreakfalse{\global\let\if@nobreak\iffalse}
\def\@nobreaktrue {\global\let\if@nobreak\iftrue}
\@nobreakfalse
\newif\ifv@
\newif\ifh@
\newif\ifdt@p
\newif\if@pboxsw
\newif\if@rjfield
\newif\if@firstamp
\newif\if@negarg
\newif\if@ovt
\newif\if@ovb
\newif\if@ovl
\newif\if@ovr
\newdimen\@ovxx
\newdimen\@ovyy
\newdimen\@ovdx
\newdimen\@ovdy
\newdimen\@ovro
\newdimen\@ovri
\newif\if@noskipsec \@noskipsectrue
EOTeX
#======================================================================
# C.1.4 Declarations
#======================================================================
# actual implementation later.
#======================================================================
# C.1.5 Invisible Commands
#======================================================================
# actual implementation later.
#======================================================================
# C.1.6 The \\ Command
#======================================================================
# In math, \\ is just a formatting hint, unless within an array, cases, .. environment.
DefConstructor("\\\\ OptionalMatch:* [Glue]",
"?#isMath(<ltx:XMHint name='newline'/>)(<ltx:break/>)",
reversion => Tokens(T_CS("\\\\"), T_CR));
Let('\@normalcr', "\\\\");
PushValue(TEXT_MODE_BINDINGS => [T_CS("\\\\"), T_CS('\@normalcr')]);
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 {
AssignValue('@at@begin@document', []) unless LookupValue('@at@begin@document');
PushValue('@at@begin@document', $_[1]->unlist); });
DefMacro('\AtEndDocument{}', sub {
AssignValue('@at@end@document', []) unless LookupValue('@at@end@document');
PushValue('@at@end@document', $_[1]->unlist); });
DefEnvironment('{document}', sub {
# "<ltx:document xml:id='#id'>#body</ltx:document>",
my ($document, %props) = @_;
my $id = ToString($props{id});
my $body = $props{body};
if (my $docel = $document->findnode('/ltx:document')) { # Already (auto) created?
$document->setAttribute($docel, 'xml:id' => $id) if $id;
$document->absorb($body); }
else {
$document->insertElement('ltx:document', $body, 'xml:id' => $id); } },
beforeDigest => sub { AssignValue(inPreamble => 0); },
afterDigestBegin => sub {
$_[1]->setProperty(id => Expand(T_CS('\thedocument@ID')));
if (my $ops = LookupValue('@at@begin@document')) {
my @boxes = Digest(Tokens(@$ops));
$_[1]->setFont(LookupValue('font')); # Start w/ whatever font was selected.
return @boxes; }
else {
return; } },
beforeDigestEnd => sub {
$_[0]->getGullet->flush;
if (my $ops = LookupValue('@at@end@document')) {
Digest(Tokens(@$ops)); }
else {
return; } },
mode => 'text');
#**********************************************************************
# 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
DefConstructorI('\LaTeX', undef, 'LaTeX');
DefConstructorI('\LaTeXe', undef, 'LaTeX2e');
DefMacroI('\fmtname', undef, 'LaTeX2e');
DefMacroI('\fmtversion', undef, 'XXXX/XX/XX');
DefMacroI('\today', undef, sub { ExplodeText(today()); });
DefConstructor('\emph{}', "<ltx:emph>#1</ltx:emph>", mode => 'text');
#======================================================================
# C.3.2 Making Paragraphs
#======================================================================
# \noindent, \indent, \par in TeX.pool.ltxml
Let('\@@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}');
DefConstructor('\footnote[]{}',
"<ltx:note role='footnote' mark='#refnum'>#2</ltx:note>",
mode => 'text', bounded => 1,
beforeDigest => sub { reenterTextMode(1); Digest('\normalfont'); },
properties => sub {
($_[1] ? (refnum => $_[1]) : RefStepCounter('footnote')) });
DefConstructor('\footnotemark[]',
"<ltx:note role='footnotemark' mark='#refnum'></ltx:note>",
mode => 'text',
properties => sub {
($_[1] ? (refnum => $_[1]) : RefStepCounter('footnote')) });
DefConstructor('\footnotetext[]{}',
"<ltx:note role='footnotetext' mark='#refnum'>#2</ltx:note>",
mode => 'text',
properties => sub {
($_[1] ? (refnum => $_[1]) : (refnum => Digest(T_CS("\\thefootnote")))); });
Tag('ltx:note', afterClose => \&relocateFootnote);
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 $note ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) {
$node->parentNode->removeChild($node);
$document->appendClone($note, $node->childNodes);
$document->setAttribute($note, role => $notetype);
if (my $labels = $node->getAttribute('labels')) {
GenerateID($document, $note);
$document->setAttribute($note, 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) = @_;
my $ctr = LookupValue('counter_for_' . ToString($type)) || ToString($type);
$level = ToString($level);
if ($flag || (($level ne '') && ($level > CounterValue('secnumdepth')->valueOf))) {
RefStepID($ctr);
(T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'), T_BEGIN, $type->unlist, T_END); }
else {
RefStepCounter($ctr);
(T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'), T_BEGIN, $type->unlist, T_END); } });
# Redefine these if you want to assemble the name (eg. \chaptername), refnum and titles differently
# \@@numbered@section{type}[toctitle]{title}
DefMacro('\@@numbered@section{}[]{}',
'\@@section{#1}{\@currentID}{\@currentlabel}'
. '{\lx@fnum@@{#1}}'
. '{\format@toctitle@{#1}{\ifx.#2.#3\else#2\fi}}'
. '{\format@title@{#1}{#3}}');
# NOTE: Unclear here, whether the "formatted refnum" should be empty, or just the type abbreviation?
DefMacro('\@@unnumbered@section{}[]{}',
'\@@section{#1}{\@currentID}{}{}{#2}{#3}');
#----------------------------------------------------------------------
# The following macros provide a few layers of customization
# in particular for supporting localization for different languages.
#----------------------------------------------------------------------
# \format@title@{type}{title}
# Format a title (or caption) appropriately for type.
# This is usually somewhat verbose, but establishes the context that this is a Chapter, or Figure, or whatever
# invokes \format@title@type{title} if that macro is defined, else composes \lx@fnum@@{type} title.
# Define \format@title@type{title} if the default is not appropriate.
DefMacro('\format@title@{}{}',
'{\@ifundefined{format@title@#1}{\@@compose@title{\lx@fnum@@{#1}}{#2}}{\csname format@title@#1\endcsname{#2}}}');
# \format@toctitle@{type}{toctitle}
# Format a toctitle (or toccaption) appropriately for type.
# This is usually somewhat concise, and the context implies that this is a Chapter, Figure or whatever
# invokes \format@toctitle@type{title} if that macro is defined, else composes \lx@fnum@toc@@{type} title
# Define \format@toctitle@type{title} if the default is not appropriate.
DefMacro('\format@toctitle@{}{}',
'{\@ifundefined{format@toctitle@#1}{\@@compose@title{\lx@fnum@toc@@{#1}}{#2}}{\csname format@toctitle@#1\endcsname{#2}}}');
DefMacro('\@@compose@title{}{}', '\@tag[][ ]{#1}#2');
DefConstructor('\@tag[][]{}', "?#3(<ltx:tag open='#1' close='#2'>#3</ltx:tag>)()");
## NOTE that a 3rd form seems desirable: an concise form that cannot rely on context for the type.
## This would be useful for the titles in links; thus can be plain (unicode) text.
## However, I hate setting up even more machinery & options and dragging yet another form around....
# \@@section{type}{id}{refnum}{formattedrefnum}{toctitle}{title}
DefConstructor('\\@@section{}{}{}{}{}{}', sub {
my ($document, $type, $id, $refnum, $frefnum, $toctitle, $title, %props) = @_;
$frefnum = undef if $frefnum && $refnum && (ToString($frefnum) eq ToString($refnum));
$document->openElement("ltx:" . ToString($type), 'xml:id' => CleanID($id), refnum => $refnum, frefnum => $frefnum);
$document->insertElement('ltx:title', $title);
$document->insertElement('ltx:toctitle', $toctitle)
if $toctitle && $toctitle->unlist && (ToString($toctitle) ne ToString($title)); },
bounded => 1);
# Not sure if this is best, but if no explicit \section'ing...
#### Tag('ltx:section',autoOpen=>1);
#======================================================================
# C.4.2 The Appendix
#======================================================================
# Handled in article,report or book.
DefMacroI('\appendixname', undef, 'Appendix');
DefMacroI('\appendixesname', undef, 'Appendixes');
#======================================================================
# 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 role='contents' select='#select' name='#name'/>",
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 role='figures' select='ltx:figure' name='#name' scope='global'/>",
properties => sub { (name => Digest(T_CS('\listfigurename'))); });
DefMacroI('\listtablename', undef, 'List of Tables');
DefConstructorI('\listoftables', undef,
"<ltx:TOC role='tables' select='ltx:table' name='#name' scope='global'/>",
properties => sub { (name => Digest(T_CS('\listtablename'))); });
DefPrimitive('\addcontentsline{}{}{}', undef);
DefPrimitive('\numberline{}{}', undef);
DefPrimitive('\addtocontents{}{}', undef);
#======================================================================
# 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');
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);
my @pkgs = grep { $_ } grep { !/^\s*%/ } split(/,\s*/, ToString($packages));
$options = [($options ? split(/,\s*/, (ToString($options))) : ())];
map { RequirePackage($_, options => $options) } @pkgs;
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);
my @pkgs = grep { $_ } grep { !/^\s*%/ } split(/,\s*/, ToString($packages));
$options = [($options ? split(/,\s*/, (ToString($options))) : ())];
map { RequirePackage($_, options => $options) } @pkgs;
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 = $whatsit->getArg(2);
$options = [($options ? split(/,\s*/, (ToString($options))) : ())];
LoadClass(ToString($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; });
DefPrimitive('\DeclareOption{}{}', sub {
my ($stomach, $option, $code) = @_;
((ToString($option) eq '*') ?
DeclareOption(undef, $code) :
DeclareOption(ToString($option), $code)); });
DefPrimitive('\PassOptionsToPackage{}{}', sub {
my ($stomach, $name, $options) = @_;
PassOptions($name, 'pkg', split(/,\s*/, ToString(Digest($options)))); });
DefPrimitive('\PassOptionsToClass{}{}', sub {
my ($stomach, $name, $options) = @_;
PassOptions($name, 'cls', split(/,\s*/, ToString(Digest($options)))); });
DefConstructor('\RequirePackageWithOptions Semiverbatim []',
"<?latexml package='#1'?>",
beforeDigest => sub { onlyPreamble('\RequirePackage'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $package = $whatsit->getArg(1);
RequirePackage(ToString($package), withoptions => 1);
return; });
DefConstructor('\LoadClassWithOptions Semiverbatim []',
"<?latexml class='#1'?>",
beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my $class = $whatsit->getArg(1);
LoadClass(ToString($class), withoptions => 1);
return; });
DefMacroI('\CurrentOption', undef, Tokens());
DefPrimitiveI('\OptionNotUsed', undef, sub {
if (my $option = ToString(Digest(T_CS('\CurrentOption')))) {
my $type = ToString(Digest(T_CS('\@currext')));
if ($type eq 'cls') {
PushValue('@unusedoptionlist', $option); } }
return; });
DefPrimitiveI('\@unknownoptionerror', undef, sub {
if (my $option = ToString(Digest(T_CS('\CurrentOption')))) {
my $name = ToString(Digest(T_CS('\@currname')));
my $type = ToString(Digest(T_CS('\@currext')));
Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); }
return; });
DefPrimitive('\ExecuteOptions{}', sub {
my ($gullet, $options) = @_;
ExecuteOptions(split(/,\s*/, ToString(Digest($options)))); });
DefPrimitive('\ProcessOptions OptionalMatch:*', sub {
my ($stomach, $star) = @_;
ProcessOptions(($star ? (inorder => 1) : ())); });
DefMacro('\AtEndOfPackage{}', sub {
my ($gullet, $code) = @_;
my $name = ToString(Digest(T_CS('\@currname')));
my $type = ToString(Digest(T_CS('\@currext')));
AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); });
DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension');
DefMacro('\@ifclassloaded', '\@ifl@aded\@clsextension');
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('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); });
DefMacro('\addto@hook DefToken {}', '#1\expandafter{\the#1#2}');
Let('\AtEndOfClass', '\AtEndOfPackage');
DefMacro('\AtBeginDvi {}', Tokens());
#======================================================================
# 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);
DefPrimitive('\twocolumn[]', undef);
DefPrimitiveI('\onecolumn', undef, undef);
# Style parameters from Fig. C.3, p.182
DefRegister('\paperheight' => Dimension(0));
DefRegister('\paperwidth' => Dimension(0));
DefRegister('\textheight' => Dimension(0));
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
DefMacro('\title{}', '\@add@frontmatter{ltx:title}{#1}');
DefMacro('\date{}',
'\@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');
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('\@author{}',
'\lx@count@author'
. '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}');
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{}', sub { andSplit(T_CS('\@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,
'\@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');
DefConstructor('\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);
AddToMacro(T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract')); },
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; },
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('\begin{abstract}')); },
locked => 1);
DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}');
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');
DefEnvironment('{abstract}',
'<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@title')); },
locked => 1, mode => 'text');
DefConstructorI('\maybe@end@title', undef, sub {
my ($document) = @_;
if ($document->isCloseable('ltx:titlepage')) {
$document->closeElement('ltx: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"
beforeDigest => sub {
Let('\par', '\inner@par');
Let('\\\\', '\inner@par'); });
# 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', @_); },
beforeDigest => sub {
Let('\par', '\inner@par');
Let('\\\\', '\inner@par'); });
DefEnvironment('{flushright}', sub {
$_[0]->maybeCloseElement('ltx:p'); # this starts a new vertical block
aligningEnvironment('right', 'ltx_align_right', @_); },
beforeDigest => sub {
Let('\par', '\inner@par');
Let('\\\\', '\inner@par'); });
# 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.
#DefPrimitiveI('\centering', undef, sub { UnshiftValue(beforeAfterGroup=>T_CS('\@add@centering')); });
# NOTE: THere's a problem here. The current method seems to work right for these operators
# appearing within the typical environments. HOWEVER, it doesn't work for a simple \bgroup or \begingroup!!!
# (they don't create a node! or even a whatsit!)
DefConstructorI('\centering', undef,
sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@centering')); });
DefConstructorI('\raggedright', undef,
sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedright')); });
DefConstructorI('\raggedleft', undef,
sub { AssignValue(ALIGNING_NODE => $_[0]->getElement); return; },
beforeDigest => sub { UnshiftValue(beforeAfterGroup => T_CS('\@add@raggedleft')); });
DefConstructorI('\@add@centering', undef,
sub { if (my $node = LookupValue('ALIGNING_NODE')) {
map { setAlignOrClass($_[0], $_, 'center', 'ltx_centering') }
$_[0]->getChildElements($node); } });
# Note that \raggedright is essentially align left
DefConstructorI('\@add@raggedright', undef,
sub { if (my $node = LookupValue('ALIGNING_NODE')) {
map { setAlignOrClass($_[0], $_, undef, 'ltx_align_left') }
$_[0]->getChildElements($node); } });
DefConstructorI('\@add@raggedleft', undef,
sub { if (my $node = LookupValue('ALIGNING_NODE')) {
map { setAlignOrClass($_[0], $_, undef, 'ltx_align_right') }
$_[0]->getChildElements($node); } });
DefConstructorI('\@add@flushright', undef,
sub { if (my $node = LookupValue('ALIGNING_NODE')) {
map { setAlignOrClass($_[0], $_, 'right', 'ltx_align_right') }
$_[0]->getChildElements($node); } });
DefConstructorI('\@add@flushleft', undef,
sub { if (my $node = LookupValue('ALIGNING_NODE')) {
map { setAlignOrClass($_[0], $_, 'left', 'ltx_align_left') }
$_[0]->getChildElements($node); } });
#======================================================================-
# C.6.1 Quotations and Verse
#======================================================================-
DefConstructor('\@block@cr[Dimension]', "<ltx:break/>\n",
reversion => Tokens(T_CS("\\\\"), T_CR));
DefEnvironment('{quote}',
'<ltx:quote>#body</ltx:quote>',
beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
mode => 'text');
DefEnvironment('{quotation}',
'<ltx:quote>#body</ltx:quote>',
beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
mode => 'text');
# NOTE: Handling of \\ within these environments?
DefEnvironment('{verse}',
'<ltx:quote role="verse">#body</ltx:quote>',
beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') },
mode => 'text');
#======================================================================
# C.6.2 List-Making environments
#======================================================================
Tag('ltx:item', autoClose => 1);
Tag('ltx:inline-item', autoClose => 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:tag>#1</ltx:tag>)");
DefConstructor('\subitem[]',
"<ltx:item>?&defined(#1)(<ltx:tag>#1</ltx:tag>)");
DefConstructor('\subsubitem[]',
"<ltx:item>?&defined(#1)(<ltx:tag>#1</ltx:tag>)");
AssignValue(itemlevel => 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, $nolevel) = @_;
$counter = '@@item' unless $counter;
my $level = LookupValue('itemlevel') + 1;
AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep'));
AssignValue(itemlevel => $level);
AssignValue(itemization_items => 0);
my $postfix = ToString(Tokens(roman($level)));
my $usecounter = ($nolevel ? $counter : $counter . $postfix);
Let('\item' => "\\" . $type . '@item') if defined $type;
DefMacroI('\@listctr', undef, Tokens(Explode($usecounter)));
AssignValue(itemcounter => $usecounter);
ResetCounter($usecounter);
return RefStepCounter('@itemize' . $postfix); }
# Create id, and refnum attributes 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 $ttag = (ref $tag ? $tag : T_OTHER($tag));
my @props = RefStepID($counter);
my $refnum = Digest(Tokens(T_BEGIN, $ttag->unlist, T_END));
my $frefnum = Digest(Tokens(T_BEGIN, Invocation(T_CS('\makelabel'), $ttag), T_END));
my $s_refnum = ToString($refnum);
my $s_frefnum = ToString($frefnum);
return (@props, refnum => $refnum,
($frefnum && (!$refnum || ($s_frefnum ne $s_refnum)) ? (frefnum => $frefnum) : ()), %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') unless defined $level;
$level = ToString(Tokens(roman($level)));
DefMacroI('\labelitem' . $level, undef, $stuff); }
return; }
sub setEnumerationStyle {
my ($stuff, $level) = @_;
if (defined $stuff) {
$level = LookupValue('itemlevel') 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_LETTER('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; }
# 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..
DefConstructor('\itemize@item OptionalUndigested',
"<ltx:item xml:id='#id' ?#1(refnum='#refnum') itemsep='#itemsep'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@itemize@item OptionalUndigested',
"<ltx:inline-item xml:id='#id' ?#1(refnum='#refnum')>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\enumerate@item OptionalUndigested',
"<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@enumerate@item OptionalUndigested',
"<ltx:inline-item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\description@item OptionalUndigested',
"<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefConstructor('\inline@description@item OptionalUndigested',
"<ltx:inline-item xml:id='#id'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
properties => sub { RefStepItemCounter($_[1]); });
DefEnvironment('{itemize}',
"<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
properties => sub { beginItemize('itemize', '@@item'); },
locked => 1, mode => 'text');
DefEnvironment('{enumerate}',
"<ltx:enumerate xml:id='#id'>#body</ltx:enumerate>",
properties => sub { beginItemize('enumerate', 'enum'); },
locked => 1, mode => 'text');
DefEnvironment('{description}',
"<ltx:description xml:id='#id'>#body</ltx:description>",
beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); },
properties => sub { beginItemize('description', '@@desc'); },
locked => 1, mode => 'text');
DefMacro('\makelabel{}', '#1');
# Basic enumeration bits
DefMacroI('\labelenumi', undef, '\theenumi.');
DefMacroI('\labelenumii', undef, '(\theenumii)');
DefMacroI('\labelenumiii', undef, '\theenumiii.');
DefMacroI('\labelenumiv', undef, '\theenumiv.');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@enumi', undef, '{\makelabel{\labelenumi}}');
DefMacroI('\fnum@enumii', undef, '{\makelabel{\labelenumii}}');
DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}');
DefMacroI('\fnum@enumiv', undef, '{\makelabel{\labelenumiv}}');
# You _could_ define \refnum@enumi, etc, to specify how to refer to the item!
# Basic itemize bits
DefMacroI('\labelitemi', undef, '\textbullet');
DefMacroI('\labelitemii', undef, '\normalfont\bfseries \textendash');
DefMacroI('\labelitemiii', undef, '\textasteriskcentered');
DefMacroI('\labelitemiv', undef, '\textperiodcentered');
# These really aren't normal latex counters, but we use them for ids
NewCounter('@@itemi', '@itemizei', idprefix => 'i');
NewCounter('@@itemii', '@itemizeii', idprefix => 'i');
NewCounter('@@itemiii', '@itemizeiii', idprefix => 'i');
NewCounter('@@itemiv', '@itemizeiv', idprefix => 'i');
NewCounter('@@itemv', '@itemizev', idprefix => 'i');
NewCounter('@@itemvi', '@itemizevi', idprefix => 'i');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@@@itemi', undef, '{\makelabel{\labelitemi}}');
DefMacroI('\fnum@@@itemii', undef, '{\makelabel{\labelitemii}}');
DefMacroI('\fnum@@@itemiii', undef, '{\makelabel{\labelitemiii}}');
DefMacroI('\fnum@@@itemiv', undef, '{\makelabel{\labelitemiv}}');
# Basic description list bits
DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1');
# These really aren't normal latex counters, but we use them for ids
NewCounter('@@desci', '@itemizei', idprefix => 'i');
NewCounter('@@descii', '@itemizeii', idprefix => 'i');
NewCounter('@@desciii', '@itemizeiii', idprefix => 'i');
NewCounter('@@desciv', '@itemizeiv', idprefix => 'i');
NewCounter('@@descv', '@itemizev', idprefix => 'i');
NewCounter('@@descvi', '@itemizevi', idprefix => 'i');
# These hookup latexml's numbering to normal latex's
DefMacroI('\fnum@@@desci', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@descii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@desciii', undef, '{\descriptionlabel{}}');
DefMacroI('\fnum@@@desciv', undef, '{\descriptionlabel{}}');
#======================================================================
# 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 ? 1 : 0));
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');
DefConstructor('\lx@list DigestedBody',
"<ltx:itemize>#1</ltx:itemize>",
beforeDigest => sub { $_[0]->bgroup; });
DefPrimitive('\endlx@list', sub { $_[0]->egroup; });
DefConstructor('\list@item OptionalUndigested',
"<ltx:item xml:id='#id' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' itemsep='#itemsep'>"
. "?&defined(#frefnum)(<ltx:tag>#frefnum</ltx:tag>)(?&defined(#refnum)(<ltx:tag>#refnum</ltx:tag>))",
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 ??
# DefMacro('\trivlist@item[]', '');
# DefEnvironment('{trivlist}',
# '<ltx:p>#body</ltx:p>',
# beforeDigest => sub { Let('\item', '\trivlist@item'); });
DefEnvironment('{trivlist}',
"<ltx:itemize>#body</ltx:itemize>",
properties => sub { beginItemize('trivlist'); });
DefConstructor('\trivlist@item OptionalUndigested',
"<ltx:item xml:id='#id' refnum='#refnum' itemsep='#itemsep'>"
. "<ltx:tag>#refnum</ltx:tag>", # At least an empty tag!
properties => sub { ($_[1] ? (refnum => Digest(Expand($_[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>');
Let('\@verbatim', '\verbatim'); # Close enough?
# 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 { $_[0]->bgroup;
AssignValue(current_environment => 'verbatim');
DefMacroI('\@currenv', undef, 'verbatim');
MergeFont(family => 'typewriter');
# Digest(T_CS('\par')); # NO! See beforeConstruct!
}],
afterDigest => [sub {
my ($stomach, $whatsit) = @_;
$stomach->egroup;
my $font = $whatsit->getFont;
my $loc = $whatsit->getLocator;
my $end = "\\end{verbatim}";
my @lines = ();
my $gullet = $stomach->getGullet;
while (defined(my $line = $gullet->readRawLine)) {
if ($line =~ /^(.*?)\\end\{verbatim\}(.*?)$/) {
push(@lines, $1 . "\n"); $gullet->unread(Tokenize($2), T_CR);
last; }
push(@lines, $line . "\n"); }
pop(@lines) if $lines[-1] eq "\n";
# Note last line ends up as Whatsit's "trailer"
$whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end);
return; }],
beforeConstruct => sub { $_[0]->maybeCloseElement('ltx:p'); });
DefPrimitiveI('\@vobeyspaces', undef, sub {
AssignCatcode(" " => 13);
Let(T_ACTIVE(" "), '\nobreakspace');
return });
# WARNING: Need to be careful about what catcodes are active here
DefMacroI('\verb', undef, sub {
my ($gullet) = @_;
my $mouth = $gullet->getMouth;
my ($init, $body);
StartSemiverbatim('%', '\\', '{', '}');
$init = $mouth->readToken;
$init = $mouth->readToken if ToString($init) eq '*'; # Should I bother handling \verb* ?
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 (); }
$body = $mouth->readTokens($init);
EndSemiverbatim();
Invocation((LookupValue('IN_MATH') ? T_CS('\@math@verb') : T_CS('\@text@verb')),
Tokens($init), $body)->unlist; });
DefConstructor('\@text@verb{}{}', "<ltx:verbatim font='#font'>#2</ltx:verbatim>",
beforeDigest => [sub { $_[0]->bgroup; MergeFont(family => 'typewriter'); }],
afterDigest => sub { $_[0]->egroup; },
# Since ltx:verbatim is both inline & block, we have to fudge inline mode
beforeConstruct => sub {
$_[0]->canContain($_[0]->getElement, '#PCDATA')
|| $_[0]->openElement('ltx:p'); },
reversion => '\verb#1#2#1');
DefConstructor('\@math@verb{}{}', "#2", # Will already end up wrapped as XMTok!
beforeDigest => [sub { $_[0]->bgroup; MergeFont(family => 'typewriter'); }],
afterDigest => sub { $_[0]->egroup; },
reversion => '\verb#1#2#1');
# 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)');
DefMacro('\fnum@equation', '\@eqnnum');
DefMacro('\refnum@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'); },
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',
);
# 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' refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
properties => sub { RefStepCounter('equation') },
locked => 1);
DefEnvironment('{equation*}',
"<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);
# Define \( ..\) and \[ ... \] to act like environments.
# I would have thought these should be locked, but it seems relatively common to
# redefine them as \left[ \right] and \left( \right) !
DefConstructorI('\[', undef,
"<ltx:equation xml:id='#id'>"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
beforeDigest => sub { $_[0]->beginMode('display_math'); },
captureBody => 1,
properties => sub { RefStepID('equation') });
DefConstructorI('\]', undef, "", beforeDigest => sub { $_[0]->endMode('display_math'); });
DefConstructorI('\(', undef,
"<ltx:Math mode='inline'>"
. "<ltx:XMath>"
. "#body"
. "</ltx:XMath>"
. "</ltx:Math>",
beforeDigest => sub { $_[0]->beginMode('inline_math'); },
captureBody => 1);
DefConstructorI('\)', undef, "", beforeDigest => sub { $_[0]->endMode('inline_math'); });
# 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 = 'math|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'); });
# ========================================
# 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.
DefPrimitiveI('\@equationgroup@number', undef, sub { AssignValue(EQUATIONGROUP_NUMBER => '_AUTO_'); });
DefPrimitiveI('\@equationgroup@nonumber', undef, sub { AssignValue(EQUATIONGROUP_NUMBER => 0); });
DefPrimitiveI('\nonumber', undef, sub { AssignValue(EQUATIONROW_NUMBER => 0, 'global'); });
# Alternate versions of alignment open/close@row that deal with numbering.
# Depending on how an eqnarray ends, we might end up with an empty equation, which will be deleted.
# If non-empty, we'll set an id, and perhaps a refnum.
DefPrimitiveI('\@equationgroup@close@row', undef, sub { $_[0]->egroup; });
DefConstructor('\@equationgroup@open@row SkipSpaces DigestedBody',
"",
reversion => '#1',
beforeDigest => sub { $_[0]->bgroup;
LookupValue('Alignment')->newRow;
AssignValue(EQUATIONROW_NUMBER
=> LookupValue('EQUATIONGROUP_NUMBER'), 'global');
return; },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
# if($whatsit->getArg(1)->unlist){ # If non-empty row, possibly we'll number
# The idea is to avoid numbering an empty rows.... or should we be avoiding the empty rows!?!?!
# Some confusion here; some cases latex DOES give you empty, numbered rows (end of ams' align),
# Others, it ignores the empty line (\\ optional/ignorable before ams' \intertext)
# So, I suspect we should unconditionally do this branch, but work harder at avoiding
# empty rows in the first place.
if (1) {
my $num = LookupValue('EQUATIONROW_NUMBER');
my %props = ();
if (!$num) { %props = RefStepID('equation'); }
elsif ($num eq '_AUTO_') { %props = RefStepCounter('equation'); }
else { %props = RefStepID('equation');
$props{refnum} = $num; }
# GACK: Store the refnum & id in the alignment's row structure!
# [wouldn't it be nicer to be using Whatsits inside the alignment?]
my $row = LookupValue('Alignment')->currentRow;
$$row{id} = $props{id};
$$row{refnum} = $props{refnum};
$$row{frefnum} = $props{frefnum};
$$row{rrefnum} = $props{rrefnum}; }
return; });
# ========================================
# Some special kinds of rows...
DefConditionalI('\if@in@firstcolumn', undef, sub {
my $x = LookupValue('Alignment');
$x = ($x ? $x->currentColumnNumber : 9);
$x < 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)
# ========================================
# eqnarray
DefMacroI('\eqnarray', undef,
'\@eqnarray@bindings\@@eqnarray'
. '\@equationgroup@number\@start@alignment',
locked => 1);
DefMacroI('\endeqnarray', undef,
'\@finish@alignment\end@eqnarray',
locked => 1);
DefMacro('\csname eqnarray*\endcsname',
'\@eqnarray@bindings\@@eqnarray\@equationgroup@nonumber\@start@alignment',
locked => 1);
DefMacro('\csname endeqnarray*\endcsname',
'\@finish@alignment\end@eqnarray',
locked => 1);
DefPrimitive('\@eqnarray@bindings', sub {
eqnarrayBindings(); });
sub eqnarrayBindings {
my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
after => Tokens(T_CS('\@hidden@egroup'), T_MATH) };
my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
after => Tokens(T_CS('\@hidden@egroup'), T_MATH, T_CS('\hfil')) };
my $col3 = { before => Tokens(T_MATH, T_CS('\@hidden@bgroup'), T_CS('\displaystyle')),
after => Tokens(T_CS('\@hidden@egroup'), 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 { $_[0]->openElement('ltx:equation', @_[1 .. $#_]); },
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(T_ALIGN, '\@alignment@align');
Let("\\\\", '\@alignment@newline');
Let('\cr', '\@alignment@cr');
Let('\@open@row', '\@equationgroup@open@row');
Let('\@close@row', '\@equationgroup@close@row');
return; }
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 => $rownode->hasAttribute('refnum'),
labelled => $rownode->hasAttribute('label') }); }
my $nL = scalar(grep { $$_{L} } @rows);
my $nM = scalar(grep { $$_{M} } @rows);
my $nR = scalar(grep { $$_{R} } @rows);
# Only a single column was used. Remove the empty ones.
# A heuristic: if any rows begin with a relation, there are probably continuations,
# but maybe we don't want to try to distinguish the kinds?
if (($nL && !$nM && !$nR) # All left column
|| (!$nL && $nM && !$nR) # All center column
|| (!$nL && !$nM && $nR)) { # All right column
# We REALLY should remove the empty columns entirely!
my $keepcol = ($nL ? 0 : ($nM ? 1 : 2));
# REMOVE empty columns!
foreach my $c (2, 1, 0) {
next if $c == $keepcol;
foreach my $row (@rows) {
$document->removeNode($$row{cols}[$c]);
splice(@{ $$row{cols} }, $c, 1); } }
# If (some) columns begin with a relation, presumably a single equation?
# Or maybe we should only connect those to previous row?
my $t;
if (grep { ($t = $$_{cols}[0]) && ($t = $document->getFirstChildElement($t))
&& (($t->getAttribute('role') || '') eq 'RELOP') } @rows) {
equationgroupJoinRows($document, $equationgroup, map { $$_{node} } @rows); }
else {
# Really, these shouldn't even end up with MathFork!
foreach my $row (@rows) {
equationgroupJoinCols($document, 1, $$row{node}); } }
return; }
# What about case where only TWO columns get used? Worth analyzing?
# Remaining case is when all 3 columns are used.
my @eqs = ();
my $numbered = 0;
my $oddness = 0;
foreach my $row (@rows) {
my $class = 'unknown';
if ($$row{L}) { # 1st column non-empty; Presumably a new "equation"
$class = 'new'; }
elsif ($$row{M}) { # 1st is empty & 2nd non-empty (Rel?); Probably continues
if (!scalar(@eqs)) { # But if no previous equation?
$class = 'odd'; }
elsif ($numbered && $$row{numbered}) { # Separately numbered continuation?
$class = 'new'; } # Keep it as separate equation, even though missing LHS
else {
$class = 'continue'; } } # Continues as multiequation
else { # 1st & 2nd is empty
if (!scalar(@eqs)) { # But if no previous equation?
$class = 'odd'; }
elsif ($numbered && $$row{numbered} # Separately numbered AND labeled?
&& $$row{labelled}) {
$class = 'odd'; } # must keep separate, but weird!
else {
$class = 'continue'; } } # Else, continues RHS.
if (($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
DefConstructor('\stackrel{}{}',
"<ltx:XMApp role='RELOP'>"
. "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
. "<ltx:XMArg>#2</ltx:XMArg>"
. "<ltx:XMArg>#1</ltx:XMArg>"
. "</ltx:XMApp>",
properties => { scriptpos => sub { "mid" . $_[0]->getBoxingLevel; } }
);
#======================================================================-
# C.7.6 Putting One Thing Above Another
#======================================================================-
# All this is already in TeX.pool
DefMacro('\skew{}{}{}', ''); # ? just some subtle spacing, right????
#======================================================================-
# C.7.7 Spacing
#======================================================================-
# All this is already in TeX.pool
#======================================================================
# 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,
font => { family => 'serif', series => 'medium', shape => 'upright' });
DefConstructor('\mathit{}', '#1', bounded => 1, requireMath => 1,
font => { shape => 'italic', family => 'serif', series => 'medium' });
DefConstructor('\mathbf{}', '#1', bounded => 1, requireMath => 1,
font => { series => 'bold', family => 'serif', shape => 'upright' });
DefConstructor('\mathsf{}', '#1', bounded => 1, requireMath => 1,
font => { family => 'sansserif', series => 'medium', shape => 'upright' });
DefConstructor('\mathtt{}', '#1', bounded => 1, requireMath => 1,
font => { family => 'typewriter', series => 'medium', shape => 'upright' });
DefConstructor('\mathcal{}', '#1', bounded => 1, requireMath => 1,
font => { family => 'caligraphic', series => 'medium', shape => 'upright' });
DefConstructor('\mathscr{}', '#1', bounded => 1, requireMath => 1,
font => { family => 'script', series => 'medium', shape => 'upright' });
DefConstructor('\mathnormal{}', '#1', bounded => 1, requireMath => 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:* DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
if (!isDefinable($cs)) {
Info('ignore', $cs, $stomach,
"Ignoring redefinition (\\newcommand) of '" . Stringify($cs) . "'")
unless LookupValue(ToString($cs) . ':locked');
return; }
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
DefPrimitive('\CheckCommand OptionalMatch:* DefToken [Number][]{}', undef);
DefPrimitive('\renewcommand OptionalMatch:* DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
DefPrimitive('\providecommand OptionalMatch:* DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
return unless isDefinable($cs);
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
# Need to figure out exactly what `robust' means to LaTeXML...
DefPrimitive('\DeclareRobustCommand OptionalMatch:* DefToken [Number][]{}', sub {
my ($stomach, $star, $cs, $nargs, $opt, $body) = @_;
DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); });
# 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]); });
#------------------------------------------------------------
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{}{}{}{}',
sub { ignoredDefinition('DeclareMathAccent', $_[1]); });
DefPrimitive('\DeclareMathDelimiter{}{}{}{}',
sub { ignoredDefinition('DeclareMathAccent', $_[1]); });
DefPrimitive('\DeclareMathRadical{}{}{}{}{}',
sub { ignoredDefinition('DeclareMathAccent', $_[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) }); });
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 %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape);
DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => {%font}); });
DefMacro('\newmathalphabet{}{}{}', Tokens()); # or expand int DeclareMathAlphabet?
DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareFontFamily{}{}{}', undef);
DefPrimitive('\DeclareSizeFunction{}{}', undef);
DefPrimitive('\DeclareMathSymbol{}{}{}{}',
sub { ignoredDefinition('DeclareMathSymbol', $_[1]); });
DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', undef);
DefPrimitive('\DeclareErrorFont{}{}{}{}{}', undef);
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 $e = Expand($encoding);
DefMacroI('\LastDeclaredEncoding', undef, $e);
DefMacroI('\T@' . ToString($e), undef, $x);
DefMacroI('\M@' . ToString($e), undef, Tokens(T_CS('\default@M'), $y->unlist));
});
DefMacroI('\LastDeclaredEncoding', undef, '');
DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef);
DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef);
DefMacroI('\LastDeclaredEncoding', undef, Tokens());
DefPrimitive('\SetSymbolFont{}{}{}{}{}{}', undef);
DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef);
DefPrimitive('\addtoversion{}{}', undef);
DefPrimitive('\TextSymbolUnavailable{}', undef);
#======================================================================
# 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(Digest($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); });
DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub {
my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_;
$name = ToString(Digest($name));
DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin);
DefMacroI(T_CS("\\end$name"), undef, $end); });
#======================================================================
# C.8.3 Theorem-like Environments
#======================================================================
# The core non-customizable part is defined here.
# For customizable theorems, see amsthm.
AssignValue('thm@swap' => 'N');
DefRegister('\thm@style' => Tokenize('plain'));
DefRegister('\thm@headfont' => Tokens(T_CS('\bfseries')));
DefRegister('\thm@bodyfont' => Tokens(T_CS('\itshape')));
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_OTHER('arabic')));
DefRegister('\thm@headformater' => undef);
# AssignValue('\thm@headfont' => T_CS('\bfseries'));
# AssignValue('\thm@bodyfont' => T_CS('\itshape'));
DefPrimitive('\th@plain', sub {
# AssignValue('\thm@headfont' => T_CS('\bfseries'));
AssignValue('\thm@bodyfont' => T_CS('\itshape'));
# AssignValue('\thm@headpunct'=> T_OTHER('.'));
AssignValue('\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, '');
DefMacro('\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);
# Some packages may want to add '\thm@headfont'
AssignValue(THEOREM_PARAMETERS => ['\thm@bodyfont', '\thm@headpunct',
'\thm@styling', '\thm@headstyling',
'thm@swap']);
###RawTeX('\AtBeginDocument{\th@plain}'); # Activate the default style.
RawTeX('\th@plain'); # Activate the default style.
# [or just make it's values the defaults...]
sub defineNewTheorem {
my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_;
$thmset = ToString($thmset);
$otherthmset = $otherthmset && ToString($otherthmset);
$type = undef unless $type->unlist;
$reset = $reset ? ToString($reset) : undef;
my $counter = $otherthmset || $thmset;
$counter =~ s/\*$//; # Strip trailing * off theoremclass for ntheorem.sty
my $style = ToString(LookupValue('\thm@style')); # The name of the style
my $numbering = ToString(LookupValue('\thm@numbering')); # The number style macro
$flag = 1 unless $numbering;
if (!$otherthmset) {
my $idprefix = "Thm$counter";
$idprefix =~ s/\*/./g;
if (!LookupValue('\c@' . $counter)) { # if isn't already defined...
NewCounter($counter, $reset, idprefix => $idprefix); }
DefMacroI(T_CS("\\the$counter"), undef,
($reset
? "\\csname the" . $reset . "\\endcsname\\\@thmcountersep\\" . $numbering . "{" . $counter . "}"
: "\\" . $numbering . "{" . $counter . "}"),
scope => 'global') if $numbering; }
my @parameters = @{ LookupValue('THEOREM_PARAMETERS') };
AssignValue('THEOREM_' . $thmset . '_PARAMETERS'
=> { map { ($_ => LookupValue($_)) } @parameters }, 'global');
my $thmname = '\\' . $thmset . 'name';
DefMacroI($thmname, undef, $type, scope => 'global');
my $swap = LookupValue('thm@swap') eq 'S';
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 = LookupValue('\thm@headformatter');
DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0),
($headformatter
? Tokens($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'))
: '\@tag{\csname fnum@' . $thmset . '\endcsname}'
. ($type ? '\ifx.#1.\else\space(#1)\fi' : '#1') . '\the\thm@headpunct'),
scope => 'global');
DefEnvironment("{$thmset} OptionalUndigested",
"<ltx:theorem xml:id='#id' class='#class'"
. " refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum'>"
. "<ltx:title font='#titlefont' _force_font='true'>#title</ltx:title>"
. "#body"
. "</ltx:theorem>",
beforeDigest => sub {
my $params = LookupValue('THEOREM_' . $thmset . '_PARAMETERS');
foreach my $v (keys %$params) {
AssignValue($v => $$params{$v}); }
Digest('\normalfont\the\thm@prework'); },
afterDigestBegin => sub { Digest('\the\thm@bodyfont\the\thm@styling'); },
beforeDigestEnd => sub { Digest('\thm@doendmark'); },
afterDigest => sub { Digest('\the\thm@postwork'); },
properties => sub {
my %ctr = ($counter ? ($flag ? RefStepID($counter) : RefStepCounter($counter)) : ());
my $title = Digest(Tokens(T_BEGIN,
T_CS('\the'), T_CS('\thm@headfont'),
T_CS('\the'), T_CS('\thm@headstyling'),
T_CS('\format@title@' . $thmset),
T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END,
T_END));
(%ctr,
frefnum => Digest(Tokens(T_CS('\fnum@' . $thmset))),
title => $title,
titlefont => $title->getFont,
class => 'ltx_theorem_' . CleanClassName($thmset),
); },
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; });
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; });
DefPrimitive('\@addtoreset{}{}', sub {
my ($stomach, $ctr, $within) = @_;
$ctr = ToString(Expand($ctr));
$within = ToString(Expand($within));
my $unctr = "UN$ctr"; # UNctr is counter for generating ID's for UN-numbered items.
AssignValue("\\cl\@$within" =>
Tokens(T_CS($ctr), T_CS($unctr),
(LookupValue("\\cl\@$within") ? LookupValue("\\cl\@$within")->unlist : ())),
'global');
# This counter might be doing double duty generating ID's as well, so we may need to patch up.
my $prefix = LookupValue('@ID@prefix@' . $ctr);
if (defined $prefix) {
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');
DefMacroI(T_CS("\\\@$ctr\@ID"), undef, "0", scope => 'global'); }
return; });
DefMacro('\value{}', sub {
ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); });
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)); });
#======================================================================
# 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!
## I'd rather not define these, since the \fnum@@ defn is better....
DefMacroI('\fnum@figure', undef, '\figurename\nobreakspace\thefigure');
DefMacroI('\fnum@table', undef, '\tablename\nobreakspace\thetable');
DefMacro('\format@title@figure{}', '\@tag[][: ]{\fnum@figure}#1');
DefMacro('\format@title@table{}', '\@tag[][: ]{\fnum@table}#1');
DefConditional('\iflx@donecaption');
DefMacro('\caption',
'\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}');
DefMacro('\@caption{}[]{}',
'\@@add@caption@counters'
. '\@@toccaption{\format@toctitle@{#1}{\ifx.#2.#3\else#2\fi}}'
. '\@@caption{\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 %props = RefStepCounter($captype);
AssignValue($captype . "_refnum" => $props{refnum}, 'global'); # global??
AssignValue($captype . "_frefnum" => $props{frefnum}, 'global');
AssignValue($captype . "_rrefnum" => $props{rrefnum}, 'global');
AssignValue($captype . "_id" => $props{id}, 'global'); });
sub RescueCaptionCounters {
my ($captype, $whatsit) = @_;
if (my $refnum = LookupValue($captype . "_refnum")) {
AssignValue($captype . "_refnum" => undef, 'global');
$whatsit->setProperty(refnum => $refnum); }
if (my $frefnum = LookupValue($captype . "_frefnum")) {
AssignValue($captype . "_frefnum" => undef, 'global');
$whatsit->setProperty(frefnum => $frefnum); }
if (my $rrefnum = LookupValue($captype . "_rrefnum")) {
AssignValue($captype . "_rrefnum" => undef, 'global');
$whatsit->setProperty(rrefnum => $rrefnum); }
if (my $id = LookupValue($captype . "_id")) {
AssignValue($captype . "_id" => undef, 'global');
$whatsit->setProperty(id => $id); }
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"); });
# Note that even without \caption, we'd probably like to have xml:id.
Tag('ltx:figure', afterClose => sub { GenerateID(@_, 'fig'); });
Tag('ltx:table', afterClose => sub { GenerateID(@_, 'tab'); });
Tag('ltx:float', afterClose => sub { GenerateID(@_, '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>");
DefConstructor('\@@toccaption{}', "^^<ltx:toccaption>#1</ltx:toccaption>",
sizer => '0');
DefEnvironment('{figure}[]',
"<ltx:figure refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
. "#body"
. "</ltx:figure>",
properties => { layout => 'vertical' },
beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
afterDigest => sub { RescueCaptionCounters('figure', $_[1]); });
DefEnvironment('{figure*}[]',
"<ltx:figure refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
. "#body"
. "</ltx:figure>",
properties => { layout => 'vertical' },
beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); },
afterDigest => sub { RescueCaptionCounters('figure', $_[1]); });
DefEnvironment('{table}[]',
"<ltx:table refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
. "#body"
. "</ltx:table>",
properties => { layout => 'vertical' },
beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
afterDigest => sub { RescueCaptionCounters('table', $_[1]); });
DefEnvironment('{table*}[]',
"<ltx:table refnum='#refnum' frefnum='#frefnum' rrefnum='#rrefnum' xml:id='#id' ?#1(placement='#1')>"
. "#body"
. "</ltx:table>",
properties => { layout => 'vertical' },
beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); },
afterDigest => sub { RescueCaptionCounters('table', $_[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(0));
DefRegister('\textfloatsep' => Glue(0));
DefRegister('\intextsep' => Glue(0));
DefRegister('\dblfloatsep' => Glue(0));
DefRegister('\dbltextfloatsep' => Glue(0));
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));
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');
#======================================================================
# C.9.2 Marginal Notes
#======================================================================
DefConstructor('\marginpar[]{}', "<ltx:note role='margin'>#2</ltx:note>");
DefPrimitiveI('\reversemarginpar', undef, undef);
DefPrimitiveI('\normalmarginpar', undef, undef);
DefRegister('\marginparpush', Dimension(0));
#**********************************************************************
# C.10 Lining It Up in Columns
#**********************************************************************
#======================================================================
# C.10.1 The tabbing Environment
#======================================================================
DefRegister('\tabbingsep' => Dimension(0));
DefMacroI('\tabbing', undef,
'\@tabbing@bindings\@@tabbing\@start@alignment');
DefMacroI('\endtabbing', undef,
'\@finish@alignment\@end@tabbing');
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,
'\@close@inner@column\@close@column'
. '\@tabbing@tabset@marker'
. '\@open@column\@open@inner@column');
DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\=');
DefMacroI('\@tabbing@nexttab', undef,
'\@close@inner@column.\@close@column'
. '\@tabbing@nexttab@marker'
. '\@open@column\@open@inner@column');
DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>');
DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]',
'\@close@inner@column\@close@column\@close@row'
. '\@tabbing@newline@marker'
. '\@open@row\@open@column\@open@inner@column\@tabbing@start@tabs');
DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR));
DefMacroI('\@tabbing@kill', undef,
'\@close@inner@column\@close@column\@close@row'
. '\@tabbing@kill@marker'
. '\@open@row\@open@column\@open@inner@column\@tabbing@start@tabs');
DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill',
afterDigest => sub { LookupValue('Alignment')->removeRow; return; });
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);
# Should there be some rowsep/colsep set here?
sub tabbingBindings {
AssignValue(Alignment => LaTeXML::Core::Alignment->new(
template => LaTeXML::Core::Alignment::Template->new(
repeated => [{ after => Tokens(T_CS('\hfil')) }]),
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'); }));
Let("\\=", '\@tabbing@tabset');
Let("\\>", '\@tabbing@nexttab');
Let("\\\\", '\@tabbing@newline');
Let('\kill', '\@tabbing@kill');
Let("\\+", '\@tabbing@increment');
Let("\\-", '\@tabbing@decrement');
Let("\\<", '\@tabbing@untab');
Let("\\'", '\@tabbing@flushright');
Let("\\`", '\@tabbing@hfil');
Let('\pushtabs', '\@tabbing@pushtabs');
Let('\poptabs', '\@tabbing@poptabs');
# Let('\a', '\@tabbing@accent');
Let('\@open@row', '\default@open@row');
Let('\@close@row', '\default@close@row');
return; }
DefMacroI('\pushtabs', undef, Tokens());
DefMacroI('\poptabs', undef, Tokens());
DefMacroI('\kill', undef, Tokens());
DefPrimitiveI('\@tabbing@bindings', undef, sub {
tabbingBindings(); });
# NOTE: Do it!!
#======================================================================
# 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@default@tabcolsep', Dimension('6pt'));
DefRegister('\tabcolsep', Dimension('6pt'));
DefMacroI('\arraystretch', undef, "1");
sub tabularBindings {
my ($template, %properties) = @_;
$properties{guess_headers} = 1 unless defined $properties{guess_headers}; # Defaults to yes
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'); } }
alignmentBindings($template, 'text', %properties);
# 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@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');
DefMacroI('\endtabular', undef, '\@finish@alignment\@end@tabular');
DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; });
# 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) = @_;
my $attr = LookupValue('Alignment')->getProperty('attributes');
$$attr{vattach} = translateAttachment($whatsit->getArg(1));
return; },
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');
DefPrimitiveI('\hline', undef, undef); # Redefined inside tabular
# 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}');
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; },
properties => { isHorizontalRule => 1 });
DefConstructorI('\vline', undef, "", # ???
properties => { isVerticalRule => 1 },);
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());
#======================================================================
# 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);
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}');
#**********************************************************************
# 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 => '',
properties => { alignmentSkippable => 1, alignmentPreserve => 1 },
afterDigest => sub {
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.
# * is added to accommodate hyperref
DefConstructor('\ref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>",
properties => sub { (label => CleanLabel($_[2])); });
DefConstructor('\pageref OptionalMatch:* Semiverbatim', "<ltx:ref labelref='#label' _force_font='true'/>", # Same??
properties => sub { (label => CleanLabel($_[2])); });
#======================================================================
# 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());
# 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) = @_;
# 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 = 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}}{\@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 = '';
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; } });
DefConstructor('\@bibliography Semiverbatim',
"<ltx:bibliography files='#1' xml:id='#id' "
. "bibstyle='#bibstyle' citestyle='#citestyle' sort='#sort'>"
. "<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; });
# 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' } };
DefConstructor('\bibstyle{}', sub {
my ($document, $style) = @_;
$style = ToString($style);
if (my $bib = $document->findnode('//ltx:bibliography')) {
$document->setAttribute($bib, bibstyle => $style);
if (my $parms = $$BIBSTYLES{$style}) {
$document->setAttribute($bib, citestyle => $$parms{citestyle});
$document->setAttribute($bib, sort => $$parms{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{}', '\bibstyle{#1}');
# 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 { AssignValue(inPreamble => 0); },
afterDigest => sub {
my ($stomach) = @_;
# 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]); },
locked => 1);
# Close the bibliography
DefConstructorI('\endthebibliography', undef,
"</ltx:biblist></ltx:bibliography>",
afterDigest => sub { my $t = T_CS('\@appendix');
Digest($t) if IsDefined($t);
return; },
locked => 1);
# 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');
Let('\bibitem', '\restoring@bibitem');
Let('\par', '\par@in@bibliography');
# Moreover, some people use \item instead of \bibitem
Let('\item', '\item@in@bibliography');
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); } });
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\bibitem');
NewCounter('@bibitem', 'bibliography', idprefix => 'bib');
DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}');
DefConstructor('\bibitem[] Semiverbatim',
"<ltx:bibitem key='#key' xml:id='#id'>"
. "<ltx:bibtag role='refnum'>#refnum</ltx:bibtag>"
. "<ltx:bibblock>",
afterDigest => sub {
my $tag = $_[1]->getArg(1);
my $key = CleanBibKey($_[1]->getArg(2));
$_[1]->setProperties(key => $key,
($tag ? (RefStepID('@bibitem'), refnum => $tag) : RefStepCounter('@bibitem'))); });
DefConstructorI('\newblock', undef, "<ltx:bibblock>");
Tag('ltx:bibitem', autoClose => 1);
Tag('ltx:bibblock', 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(','));
DefConstructor('\@@cite []{}', "<ltx:cite ?#1(class='ltx_citemacro_#1')>#2</ltx:cite>",
mode => 'text');
# \@@bibref{what to show}{bibkeys}{phrase1}{phrase2}
DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
"<ltx:bibref show='#1' bibrefs='#bibrefs'"
. " 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')))); });
# 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 unless $post && $post->unlist;
Invocation(T_CS('\@@cite'),
Tokens(Explode('cite')),
Tokens($open,
Invocation(T_CS('\@@bibref'), Tokens(Explode("Refnum")), $keys, undef, undef),
($post ? ($ns, T_SPACE, $post) : ()), $close)); });
# NOTE: Eventually needs to be recognized by MakeBibliography
DefConstructor('\nocite Semiverbatim',
"<ltx:cite><ltx:bibref show='nothing' bibrefs='#bibrefs'/></ltx:cite>",
properties => sub { (bibrefs => CleanBibKey($_[1])) });
#======================================================================
# C.11.4 Splitting the input
#======================================================================
Let('\@@input', '\input'); # Save TeX's version.
# LaTeX's \input is a bit different...
DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input');
DefPrimitive('\@iinput {}', sub { Input(Expand($_[1])); });
# 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; });
# [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 } split(/,\s*/, $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');
NoteProgress("[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');
NoteProgress("[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) = @_;
my @expansion = ();
# Split the text into phrases, separated by "!"
my @tokens = $phrases->unlist;
return unless @tokens;
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 ? (T_OTHER('['), T_OTHER($style), T_OTHER(']')) : ()),
T_BEGIN, @expansion, T_END);
return @expansion; }
DefMacro('\index{}', \&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'>#2</ltx:indexmark>",
mode => 'text', reversion => '', sizer => 0);
DefConstructor('\@indexphrase[]{}',
"<ltx:indexphrase key='#key'>#2</ltx:indexphrase>",
properties => { key => sub { CleanIndexKey($_[1]); } });
DefConstructor('\@indexsee{}',
"<ltx:indexsee key='#key' name='#name'>#1</ltx:indexsee>",
properties => { name => sub { DigestIf('\seename') } });
DefConstructor('\@indexseealso{}',
"<ltx:indexsee key='#key' name='#name'>#1</ltx:indexsee>",
properties => { name => sub { DigestIf('\alsoname') } });
DefConstructor('\glossary{}',
"<ltx:glossaryphrase role='glossary' key='#key'>#1</ltx:glossaryphrase>",
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');
$_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'),
title => $title,
titlefont => $title->getFont); });
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) = @_;
print STDERR ToString(Expand($stuff)) . "\n" if LookupValue('VERBOSITY') > -1;
return; });
DefPrimitive('\typein[]{}', undef);
#**********************************************************************
# C.12 Line and Page Breaking
#**********************************************************************
#======================================================================
# C.12.1 Line Breaking
#======================================================================
DefPrimitive('\linebreak[]', undef);
DefPrimitive('\nolinebreak[]', undef);
# \\ already defined
DefMacroI('\newline', undef, "\\\\\\relax");
DefPrimitiveI('\-', undef, undef); # We don't do hyphenation.
# \hyphenation in TeX.pool
DefPrimitiveI('\sloppy', undef, undef);
DefPrimitiveI('\fussy', undef, undef);
DefEnvironment('{sloppypar}', '#body');
DefMacroI('\nobreakdashes', undef, T_OTHER('-'));
DefMacro('\showhyphens{}', '#1'); # ?
#======================================================================
# C.12.2 Page Breaking
#======================================================================
DefMacro('\pagebreak[]', '\vadjust{\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('\@check@length DefToken', sub {
my ($stomach, $cs) = @_;
my $defn = LookupDefinition($cs);
if (!$defn || !$defn->isRegister) {
Warn('undefined', $cs, $stomach, "'" . ToString($cs) . "' is not a length; defining it now");
DefRegisterI($cs, undef, Dimension(0)); }
return; });
DefPrimitive('\newlength DefToken', sub {
my ($stomach, $cs) = @_;
DefRegisterI($cs, undef, Dimension(0)); });
DefMacro('\setlength{}{}', '\@check@length{#1}#1#2\relax');
DefMacro('\addtolength{}{}', '\@check@length{#1}\advance#1 #2\relax');
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');
# Assuming noone tries to get clever with figuring out the allocation of
# numbers, these become simple DefRegister's
DefPrimitive('\newcount DefToken', sub { DefRegisterI($_[1], undef, Number(0)); });
DefPrimitive('\newdimen DefToken', sub { DefRegisterI($_[1], undef, Dimension(0)); });
DefPrimitive('\newskip DefToken', sub { DefRegisterI($_[1], undef, Glue(0)); });
DefPrimitive('\newmuskip DefToken', sub { DefRegisterI($_[1], undef, MuGlue(0)); });
DefPrimitive('\newtoks DefToken', sub { DefRegisterI($_[1], undef, Tokens()); });
DefRegister('\fill', Glue(0, '1fill'));
#======================================================================
# C.13.2 Space
#======================================================================
DefMacro('\hspace OptionalMatch:* {Dimension}',
'\ifmmode\@math@hskip #2\relax\else\@text@hskip #2\relax\fi');
DefPrimitive('\vspace OptionalMatch:* {}', undef);
DefPrimitive('\addvspace {}', undef);
DefPrimitive('\addpenalty {}', undef);
# \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',
beforeDigest => sub { reenterTextMode(); });
our %makebox_alignment = (l => 'left', r => 'right', s => 'justified');
DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox');
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}');
DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox');
DefConstructor('\@framebox[][]{}',
"?#mathframe(<ltx:XMArg enclose='box'>#inner</ltx:XMArg>)"
. "(<ltx:text ?#width(width='#width') ?#align(align='#align')"
. " framed='rectangle' framecolor='#framecolor'"
. " _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 {
(($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()),
framecolor => LookupValue('font')->getColor,
($_[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')) {
# 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); } } } }
);
AssignValue(SAVEBOX => 100);
DefPrimitive('\newsavebox DefToken', sub {
my $n = LookupValue('SAVEBOX') + 1;
AssignValue(SAVEBOX => $n, 'global');
DefRegisterI($_[1], undef, Number($n));
AssignValue('box' . $n, List()); });
DefPrimitive('\sbox {Register} {}', sub {
my ($defn) = @{ $_[1] };
my $value = $defn->valueOf()->valueOf;
my $contents = Digest($_[2]);
AssignValue('box' . $value, $contents); return; });
DefMacro('\savebox{}', '\@ifnextchar({\pic@savebox#1}{\@savebox#1}');
DefPrimitive('\@savebox DefToken[][]{}', sub {
my ($defn, @args) = @{ LookupDefinition($_[1]) };
my $value = $defn->valueOf(@args);
AssignValue('box' . $value, Digest($_[4])); return; });
DefPrimitive('\@savebox Register [][]{}', sub {
my ($defn) = @{ $_[1] };
my $value = $defn->valueOf()->valueOf;
my $contents = Digest($_[4]);
# AssignValue('box' . $value, Digest($_[4])); return; });
AssignValue('box' . $value, $contents); return; });
DefMacroI('\begin{lrbox}', '{Token}',
'\@begin@lrbox #1');
DefPrimitiveI('\end{lrbox}', undef, sub { $_[0]->egroup; });
DefPrimitive('\@begin@lrbox Token', sub {
my ($stomach, $token) = @_;
$stomach->bgroup;
# my $font = $STATE->lookupValue('font');
# my $loc = $stomach->getGullet->getLocator;
# my $box = LaTeXML::Core::Box->new($stomach->digestNextBody(),$font,$loc);
my $box = List($stomach->digestNextBody());
AssignValue('box' . ToString($token), $box); });
DefPrimitive('\usebox {Register}', sub {
my ($defn) = @{ $_[1] };
my $value = $defn->valueOf()->valueOf;
LookupValue('box' . $value) || Box(); });
# NOTE: There are 2 extra arguments (See LaTeX Companion, p.866)
# for height and inner-pos. We're ignoring them, for now, though.
DefConstructor('\parbox[][Dimension][]{Dimension}{}', 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,
beforeDigest => sub {
AssignValue('\hsize' => $_[4]);
Let('\\\\', '\par'); });
DefConditional('\if@minipage');
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; },
mode => 'text',
properties => sub { (
width => $_[4],
vattach => translateAttachment($_[1])); },
beforeDigest => sub {
Digest(T_CS('\@minipagetrue'));
AssignValue('\hsize' => $_[4]);
# this conflicts (& not needed?) with insertBlock
Let('\\\\', '\par'); });
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)); });
sub raisedSizer {
my ($box, $y) = @_;
my ($w, $h, $d) = $box->getSize;
my $z = Dimension(0);
$h = $h->add($y)->larger($z);
$d = $d->subtract($y)->larger($z);
return ($w, $h, $d); }
#**********************************************************************
# 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);
$gullet->skipSpaces; $gullet->readUntil(T_OTHER(','), 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 $value ? $value->ptValue : undef; }
# This eases conversion of a Pair to 2 attributes.
sub PairAttr {
my ($pair, $xattr, $yattr) = @_;
### Why does perlcritic think that this is comma separating statements?
### return ($pair ? { $xattr => ptValue($pair->getX), $yattr => ptValue($pair->getY) } : {}); }
return ($pair ? { $xattr => $pair->getX->ptValue, $yattr => $pair->getY->ptValue } : {}); }
sub SimplePairAttr {
my ($pair, $xattr, $yattr) = @_;
return ($pair ? { $xattr => $pair->getX, $yattr => $pair->getY } : {}); }
#----------------------------------------------------------------------
# Picture parameters.
DefRegister('\unitlength', Dimension('1pt'));
DefPrimitiveI('\thinlines', undef, sub { AssignValue('\@wholewidth', Dimension('0.4pt')); });
DefPrimitiveI('\thicklines', undef, sub { AssignValue('\@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)); }
sub picScale {
my ($value) = @_;
return ($value ? $value->multiply(LookupValue('\unitlength')) : undef); }
sub picProperties {
my (%props) = @_;
if (($props{stroke} || 'black') ne 'none') {
$props{thick} = ptValue(LookupValue('\@wholewidth')); }
if (my $arrowlength = LookupValue('\arrowlength')) {
$props{arrowlength} = ptValue($arrowlength); }
return %props; }
#----------------------------------------------------------------------
# the code
DefMacroI('\qbeziermax', undef, '500');
sub before_picture {
# Let('\line', '\pic@line'); # Only pic?
# Let('\vector', '\pic@vector'); # Only pic
# Let('\circle', '\pic@circle'); # ???
# Let('\oval', '\pic@oval'); # Only pic
# Let('\qbezier', '\pic@qbezier'); # Only pic
# Let('\makebox', '\pic@makebox'); # CHECK for (
# Let('\savebox', '\pic@savebox'); # CHECK for (
# Let('\framebox', '\pic@framebox'); # CHECK for (
Let('\raisebox', '\pic@raisebox'); # ? needs special treatment within picture
# Let('\dashbox', '\pic@dashbox'); # Only pic
# Let('\frame', '\pic@frame'); # ?
return; }
sub after_picture {
return; }
# Since these ultimately generate external resources, it can be useful to have a handle on them.
Tag('ltx:graphics', afterOpen => sub { GenerateID(@_, 'g'); });
Tag('ltx:picture', afterOpen => sub { GenerateID(@_, 'pic'); });
# Ugh... Is this safe? Apparently, picture stuff is allowed w/o a {picture} environment???
### Tag('ltx:picture', autoOpen=>1, autoClose=>1);
### Actually, NO! It triggers a deep recursion
### NOTE: This needs to be researched!!!
# 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 %&SimplePairAttr(#size,width,height)"
. " fill='black' stroke='black' unitlength='#unitlength'>"
. "?#pos(<ltx:g transform='#pos'>#body</ltx:g>)(#body)"
. "</ltx:picture>",
mode => 'text',
beforeDigest => \&before_picture,
properties => sub { (unitlength => LookupValue('\unitlength'),
($_[2] ? (pos => 'translate(' . ptValue(picScale($_[2]->negate)) . ')') : ()),
picProperties(size => picScale($_[1]))); },
afterDigest => \&after_picture);
DefConstructor('\put Pair {}',
"<ltx:g transform='#pos'>#2</ltx:g>",
properties => sub { pos => 'translate(' . ptValue(picScale($_[1])) . ')'; },
mode => 'text');
#DefConstructor('\pic@line Pair:Number {Float}',
DefConstructor('\line Pair:Number {Float}',
"<ltx:line points='#points' stroke-width='#thick'/>",
alias => '\line',
properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->ptValue()); });
#DefConstructor('\pic@vector Pair:Number {Float}',
DefConstructor('\vector Pair:Number {Float}',
"<ltx:line points='#points' stroke-width='#thick' terminators='->'"
. " arrowlength='#arrowlength'/>",
alias => '\vector',
properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->ptValue()); });
#DefConstructor('\pic@circle OptionalMatch:* {Float}',
DefConstructor('\circle OptionalMatch:* {Float}',
"<ltx:circle x='0' y='0' r='&ptValue(#radius)' fill='#fill' stroke='#stroke'"
. " stroke-width='#thick'/>",
alias => '\circle',
properties => sub {
my ($stomach, $filled, $dia) = @_;
$dia = picScale($dia);
$dia = $dia->add(LookupValue('\@wholewidth')) unless $filled;
picProperties(radius => $dia->multiply(0.5),
($filled ? 'stroke' : 'fill') => 'none'); });
##DefConstructor('\pic@oval [Float] Pair []',
DefConstructor('\oval [Float] Pair []',
"<ltx:rect %&PairAttr(#pos,x,y) %&PairAttr(#size,width,height) rx='&ptValue(#radius)'"
. " 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);
my $pos = Pair($halfsize->getX->negate, $halfsize->getY->negate);
$r = ($r ? picScale($r) : Dimension('40pt'));
$r = $r->smaller($halfsize->getX->absolute);
$r = $r->smaller($halfsize->getY->absolute);
picProperties(size => $size, pos => $pos, radius => $r); });
##DefConstructor('\pic@qbezier [Number] Pair Pair Pair',
DefConstructor('\qbezier [Number] Pair Pair Pair',
"<ltx:bezier ?#1(displayedpoints='#1') points='&ptValue(#pt)' stroke-width='#thick' />",
alias => '\qbezier',
properties => sub {
picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); });
DefConstructor('\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]))); });
DefConstructor('\pic@makebox Pair []{}',
"<ltx:g %&PairAttr(#size,width,height) pos='#2'>#3</ltx:g>",
alias => '\makebox',
beforeDigest => sub { reenterTextMode(); },
properties => sub { picProperties(size => picScale($_[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; });
DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}',
"<ltx:g y='#1'>#4</ltx:g>",
alias => '\raisebox');
DefConstructor('\pic@framebox Pair []{}',
"<ltx:rect x='0' y='0' %&PairAttr(#size,width,height) stroke-width='#thick' fill='none'/>"
. "<ltx:g %&PairAttr(#size,width,height) pos='#2'>#3</ltx:g>",
alias => '\framebox',
beforeDigest => sub { reenterTextMode(); },
properties => sub { picProperties(size => picScale($_[1])); });
#DefConstructor('\pic@dashbox {Float} Pair [] {}',
DefConstructor('\dashbox {Float} Pair [] {}',
"<ltx:rect x='0' y='0' %&PairAttr(#size,width,height)"
. " stroke-width='#thick' stroke-dasharray='&ptValue(#dash)' fill='none'/>" .
"<ltx:g %&PairAttr(#size,width,height) pos='#3'>#4</ltx:g>",
alias => '\dashbox',
properties => sub { picProperties(dash => picScale($_[1]), size => picScale($_[2])); });
#DefConstructor('\pic@frame{}',
DefConstructor('\frame{}',
"<ltx:g framed='true' stroke-width='#thick'>#1</ltx:g>",
alias => '\frame',
properties => sub { picProperties(); });
our %alignments = (l => 'left', c => 'center', r => 'right');
# Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop)
DefConstructor('\@shortstack@cr',
"</ltx:p><ltx:p>",
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,
beforeDigest => sub { reenterTextMode();
# then RE-RE-define this one!!!
Let("\\\\", '\@shortstack@cr');
$_[0]->bgroup; },
afterDigest => sub { $_[0]->egroup; },
properties => { align => sub { ($_[1] ? $alignments{ ToString($_[0]) } : undef); } },
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 && (ref $thing eq 'LaTeXML::Core::Whatsit') && !$thing->getProperty('_added_tex')) {
my $tex = UnTeX($thing);
$document->setAttribute($node, tex => $tex);
$thing->setProperty('_added_tex', 1); } });
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');
DefMacroI('\sfdefault', undef, 'cmss');
DefMacroI('\ttdefault', undef, 'cmtt');
DefMacroI('\bfdefault', undef, 'bx');
DefMacroI('\mddefault', undef, 'm');
DefMacroI('\itdefault', undef, 'it');
DefMacroI('\sldefault', undef, 'sl');
DefMacroI('\scdefault', undef, 'sc');
DefMacroI('\updefault', undef, 'n');
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', 'cm');
DefMacro('\f@series', '');
DefMacro('\f@shape', '');
DefMacro('\f@size', '');
# 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');
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); }
else { Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); }
if (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); }
else { Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); }
if (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); }
else { 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', 'i'); });
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{}', '\protect\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi');
DefMacro('\textbf{}', '\protect\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi');
DefMacro('\textrm{}', '\protect\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi');
DefMacro('\textsf{}', '\protect\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi');
DefMacro('\texttt{}', '\protect\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi');
DefMacro('\textup{}', '\protect\ifmmode\textup@math{#1}\else{\upshape #1}\fi');
DefMacro('\textit{}', '\protect\ifmmode\textit@math{#1}\else{\itshape #1}\fi');
DefMacro('\textsl{}', '\protect\ifmmode\textsl@math{#1}\else{\slshape #1}\fi');
DefMacro('\textsc{}', '\protect\ifmmode\textsc@math{#1}\else{\scshape #1}\fi');
DefMacro('\textnormal{}', '\protect\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi');
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('\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('\textperiodcentered', undef, "\x{22C5}"); # DOT OPERATOR
DefPrimitiveI('\textsection', undef, UTF(0xA7)); # SECTION SIGN
DefAccent('\textcircled', UTF(0x20DD), UTF(0x25EF)); # Defined in TeX.pool
DefPrimitiveI('\textless', undef, "<");
DefPrimitiveI('\textgreater', undef, ">");
DefPrimitiveI('\textcopyright', undef, UTF(0xA9)); # COPYRIGHT SIGN
DefPrimitiveI('\textasciicircum', undef, "^");
DefPrimitiveI('\textasciitilde', undef, "~");
DefPrimitiveI('\textcompwordmark', 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');
# 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
DefPrimitiveI('\SS', undef, 'SS'); # ?
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}
EoTeX
DefMacro('\@ifdefinable Token {}', 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')); } });
DefMacroI('\@notdefinable', undef,
'\@latex@error{%
Command \@backslashchar\reserved@a\space
already defined.
Or name \@backslashchar\@qend... illegal,
see p.192 of the manual}');
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');
DefMacro('\MakeUppercase{}', sub {
my @t = LookupDefinition(T_CS('\@uclclist'))->getExpansion->unlist;
my @x = (T_CS('\def'), T_CS('\i'), T_BEGIN, T_LETTER('I'), T_END,
T_CS('\def'), T_CS('\j'), T_BEGIN, T_LETTER('J'), T_END);
while (@t) { push(@x, T_CS('\let'), shift(@t), shift(@t)); }
my $arg = Expand(Tokens(T_BEGIN, @x, $_[1]->unlist, T_END));
(T_CS('\uppercase'), T_BEGIN, $arg->unlist, T_END); });
DefMacro('\MakeLowercase{}', sub {
my @t = LookupDefinition(T_CS('\@uclclist'))->getExpansion->unlist;
my @x = ();
while (@t) { my $y = shift(@t); push(@x, T_CS('\let'), shift(@t), $y); }
my $arg = Expand(Tokens(T_BEGIN, @x, $_[1]->unlist, T_END));
(T_CS('\lowercase'), T_BEGIN, $arg->unlist, T_END); });
#======================================================================
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); });
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\@makeother#1{\catcode`#1=12\relax}
\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
sub make_message {
my ($cmd, @args) = @_;
my $stomach = $STATE->getStomach;
$stomach->bgroup;
Let('\protect', '\string');
my $message = join("\n", map { ToString(Expand($_)) } @args);
$stomach->egroup;
return ('latex', $cmd, $stomach, $message); }
DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this.
DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[2], $_[3], $_[4])); });
DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[2], $_[3])); });
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
DefPrimitive('\@setsize{}{}{}{}', undef);
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
EOTeX
DefMacroI('\@vpt', undef, '5');
DefMacroI('\@vipt', undef, '6');
DefMacroI('\@viipt', undef, '7');
DefMacroI('\@viiipt', undef, '8');
DefMacroI('\@ixpt', undef, '9');
DefMacroI('\@xpt', undef, '10');
DefMacroI('\@xipt', undef, '10.95');
DefMacroI('\@xiipt', undef, '12');
DefMacroI('\@xivpt', undef, '14.4');
DefMacroI('\@xviipt', undef, '17.28');
DefMacroI('\@xxpt', undef, '20.74');
DefMacroI('\@xxvpt', undef, '24.88');
DefMacroI('\@tempa', undef, '');
DefMacroI('\@tempb', undef, '');
DefMacroI('\@tempc', undef, '');
DefMacroI('\@gtempa', undef, '');
RawTeX(<<'EOTeX');
\long\def \loop #1\repeat{%
\def\iterate{#1\relax % Extra \relax
\expandafter\iterate\fi
}%
\iterate
\let\iterate\relax
}
\newdimen\@ydim
\let\@@hyph=\-
\newbox\@arstrutbox
\newbox\@begindvibox
\newcount\@botnum
\newdimen\@botroom
\newcount\@chclass
\newcount\@chnum
\newdimen\@clnht
\newdimen\@clnwd
\newdimen\@colht
\newcount\@colnum
\newdimen\@colroom
\newbox\@curfield
\newbox\@curline
\newcount\@currtype
\newcount\@curtab
\newcount\@curtabmar
\newbox\@dashbox
\newcount\@dashcnt
\newdimen\@dashdim
\newcount\@dbltopnum
\newdimen\@dbltoproom
\let\@dischyph=\-
\newcount\@enumdepth
\newcount\@floatpenalty
\newdimen\@fpmin
\newcount \@fpstype
\newcount\@highpenalty
\newcount\@hightab
\newbox\@holdpg
\newinsert \@kludgeins
\newcount\@lastchclass
\newbox\@leftcolumn
\newbox\@linechar
\newdimen\@linelen
\newcount\@lowpenalty
\newdimen\@maxdepth
\newcount\@medpenalty
\newdimen\@mparbottom \@mparbottom\z@
\newinsert\@mpfootins
\newcount\@mplistdepth
\newcount\@multicnt
\newcount\@nxttabmar
\newbox\@outputbox
\newdimen\@pagedp
\newdimen\@pageht
\newbox\@picbox
\newdimen\@picht
\newdimen \@reqcolroom
\newskip\@rightskip \@rightskip \z@skip
\newcount\@savsf
\newdimen\@savsk
\newcount\@secpenalty
\def\@sqrt[#1]{\root #1\of}
\newbox\@tabfbox
\newcount\@tabpush
\newdimen \@textfloatsheight
\newdimen\@textmin
\newcount\@topnum
\newdimen\@toproom
\newcount\@xarg
\newdimen\@xdim
\newcount\@yarg
\newdimen\@ydim
\newcount\@yyarg
\newtoks\every@math@size
\newif \if@fcolmade
\newdimen\lower@bound
\newcount\par@deathcycles
\newdimen\upper@bound
\newif\if@insert
\newif\if@colmade
\newif\if@specialpage \@specialpagefalse
\newif\if@firstcolumn \@firstcolumntrue
\newif\if@twocolumn \@twocolumnfalse
\newif\if@twoside \@twosidefalse
\newif\if@reversemargin \@reversemarginfalse
\newif\if@mparswitch \@mparswitchfalse
\newcount\col@number \@ne
\newread\@inputcheck
\newwrite\@unused
\newwrite\@mainaux
\newwrite\@partaux
\let\@auxout=\@mainaux
\openout\@mainaux\jobname.aux
\newcount\@clubpenalty
\@clubpenalty \clubpenalty
\newif\if@filesw \@fileswtrue
\newif\if@partsw \@partswfalse
\def\@tempswafalse{\let\if@tempswa\iffalse}
\def\@tempswatrue{\let\if@tempswa\iftrue}
\let\if@tempswa\iffalse
\newcount\@tempcnta
\newcount\@tempcntb
\newif\if@tempswa
\newdimen\@tempdima
\newdimen\@tempdimb
\newdimen\@tempdimc
\newbox\@tempboxa
\newskip\@tempskipa
\newskip\@tempskipb
\newtoks\@temptokena
\newskip\@flushglue \@flushglue = 0pt plus 1fil
\newif\if@afterindent\@afterindenttrue
\newbox\rootbox
\newcount\@eqcnt
\newcount\@eqpen
\newif\if@eqnsw\@eqnswtrue
\newskip\@centering
\@centering = 0pt plus 1000pt
\let\@eqnsel=\relax
\long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
#2\relax}\fi}
\long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum
\else\expandafter\@gobble\fi{#1}}
\long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi}
\long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim
\else\expandafter\@gobble\fi{#1}}
\long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi}
\long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw
\else\@gobbletwo\fi{#1}\fi}
\def\@nnil{\@nil}
\def\@fornoop#1\@@#2#3{}
\long\def\@for#1:=#2\do#3{%
\expandafter\def\expandafter\@fortmp\expandafter{#2}%
\ifx\@fortmp\@empty \else
\expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi}
\long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else
#5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi}
\long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
\expandafter\@fornoop \else
#4\relax\expandafter\@iforloop\fi#2\@@#3{#4}}
\def\@tfor#1:={\@tf@r#1 }
\long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else
\@tforloop#2\@nil\@nil\@@#1{#3}\fi}
\long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil
\expandafter\@fornoop \else
#4\relax\expandafter\@tforloop\fi#2\@@#3{#4}}
\long\def\@break@tfor#1\@@#2#3{\fi\fi}
\def\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
EOTeX
DefMacroI('\@height', undef, 'height');
DefMacroI('\@width', undef, 'width');
DefMacroI('\@depth', undef, 'depth');
DefMacroI('\@minus', undef, 'minus');
DefMacroI('\@plus', undef, 'plus');
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());
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());
DefMacro('\wlog{}', 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?
(LaTeXML::Core::Definition::Expandable::substituteTokens(
XEquals($token, (defined $next ? $next : T_END)) ? $if : $else),
(defined $next ? ($next) : ())); });
Let('\kernel@ifnextchar', '\@ifnextchar');
Let('\@ifnext', '\@ifnextchar'); # ????
DefMacro('\@ifstar {}{}', sub {
my ($gullet, $if, $else) = @_;
my $next = $gullet->readNonSpace;
# NOTE: Not actually substituting, but collapsing ## pairs!!!!
if (T_OTHER('*')->equals($next)) {
LaTeXML::Core::Definition::Expandable::substituteTokens($if); }
else {
(LaTeXML::Core::Definition::Expandable::substituteTokens($else), $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(']'))); });
Let('\@protected@testopt', '\@testopt'); # ?
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) = @_;
$file = ToString(Expand($file));
(FindFile($file) ? $if->unlist : $else->unlist); });
DefMacro('\InputIfFileExists{}{}{}', sub {
my ($gullet, $file, $if, $else) = @_;
$file = ToString(Expand($file));
if (FindFile($file)) {
Input($file);
$if->unlist; }
else { $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,
# various of these are defined in various places by different classes.
# And, at any rate, they're currently unused.
DefMacroI('\chaptername', undef, 'Chapter');
DefMacroI('\partname', undef, 'Part');
# The rest of these are defined in some classes, but not most.
# DefMacroI('\sectionname',undef,'');
# DefMacroI('\subsectionname',undef,'');
# DefMacroI('\subsubsectionname',undef,Tokens());
# DefMacroI('\paragraphname',undef,Tokens());
# DefMacroI('\subparagraphname',undef,Tokens());
#**********************************************************************
# 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());
DefMacroI('\mathgroup', undef, Tokens());
DefMacroI('\mathalpha', undef, Tokens());
#\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@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 {
print STDERR UnTeX($tokens) . "\n"; }
$stomach->egroup;
return @stuff; });
#**********************************************************************
1;