# -*- mode: Perl -*-
# /=====================================================================\ #
# | amsmath | #
# | 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;
#**********************************************************************
# See amsldoc
# Currently only a random collection of things I need for DLMF chapters.
# Eventually, go through the doc and implement it all.
#**********************************************************************
Let('\@xp', '\expandafter');
Let('\@nx', '\noexpand');
# TODO:
# Interesting options for limits placement
# And TESTING!!!!!
# sub-packages:
RequirePackage('amsbsy');
RequirePackage('amstext');
RequirePackage('amsopn');
# Optional packages
# amscd
# amsxtra
DefMacroI('\AmSfont', undef, Tokens());
DefMacroI('\AmS', undef, "AmS");
# Unhandled options (for now)
foreach my $option (qw(
centertags tbtags
sumlimits nosumlimits
intlimits nointlimits
namelimits nonamelimits
alignedleftspaceyes alignedleftspaceno alignedleftspaceyesifneg)) {
DeclareOption($option, undef); }
DeclareOption('reqno', sub { AssignMapping('DOCUMENT_CLASSES', ltx_leqno => undef); });
DeclareOption('leqno', sub { AssignMapping('DOCUMENT_CLASSES', ltx_leqno => 1); });
DeclareOption('fleqn', sub { AssignMapping('DOCUMENT_CLASSES', ltx_fleqn => 1); });
ProcessOptions();
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 3: Displayed equations
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# General implementation comments:
# Some of these environments are intended for breaking up single equations
# into multiple lines, while others are for presenting several equations in a group.
# [some environments may be more ambiguous]
# In any case, there may be specific relative alignment expected between the lines.
#
# Our primary objective, in LaTeXML, is to get at the semantics of the document,
# and secondarily, to preserve enough of the author's intent to generate
# presentation MathML that has the originally desired appearance.
# Thus, our first concern is to recognize the portions of the input which represent
# individual equations. These sequences can then be passed to the math parser
# and hopefully recognized.
#
# We'll try to leverage the equationgroup/equation/MathFork arrangement
# to achieve the secondary objective.
# Where this doesn't work out, we'll insert additional hint or punctuation tokens
# that indicate the requested alignment points or linebreaks.
# Currently hints are discarded before parsing.
DefConditional('\ifmeasuring@'); # but we won't use it?
DefConditional('\iftagsleft@'); # but we won't use it?
DefConditional('\if@fleqn'); # but we won't use it?
Let('\notag', '\nonumber');
DefMacro('\tag OptionalMatch:* {}',
# expand \theequation, but in text mode! undo formatting if *
'\lx@equation@settag{\ifx#1*\let\fnum@equation\relax\fi'
. '\expandafter\def\expandafter\theequation\expandafter{#2}'
. '\lx@make@tags{equation}}', locked => 1);
# Note that \intertext may or may not be preceded by an explicit \\, with no apparent difference in TeX.
# latexml, however, can end up with 2, which end up incrementing coutners twice!
# Of course, that means we're doing something quite wrong, but we've at least got to recover the effect!!!
DefMacro('\@ams@intertext{}',
'\hidden@crcr\noalign{\@@ams@intertext{#1}}');
DefConstructor('\@@ams@intertext{}',
"<ltx:p class='ltx_intertext'>#1</ltx:p>", mode => 'text');
# Note that most of ams's alignment environments originate in AmSTeX.
# To make \begin{foo}...\end{foo} also work as \foo...\endfoo within AmSTeX.pool,
# we're wrapping an extra \@hidden@bgroup ... \@hidden@egroup around the
# code imlementing these.
# A utility for handling different alignment strategies centrally
sub amsAlignmentBindings {
my ($template, %properties) = @_;
my $cur_jot = LookupDimension('\jot');
if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
$properties{rowsep} = $cur_jot; }
alignmentBindings($template, 'math', attributes => {%properties});
Let("\\\\", '\@alignment@newline@noskip');
return; }
# This one is for alignment environments that can (and need to be) "rearranged"
# to recognize a set of equations contained within
sub amsRearrangeableBindings {
my ($template, %properties) = @_;
my $cur_jot = LookupDimension('\jot');
if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) {
$properties{attributes}{rowsep} = $cur_jot; }
AssignValue(Alignment => LaTeXML::Core::Alignment->new(
template => $template,
openContainer => sub { my %attr = RefStepID('@equationgroup');
$attr{'xml:id'} = $attr{id}; delete $attr{id};
$_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); },
closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); },
openRow => sub {
my ($doc, %props) = @_; # props are attributes, except for tag!
my $tags = $props{tags}; delete($props{tags});
$props{_tags} = $tags; # HACK!!! for dlmf
$doc->openElement('ltx:equation', %props);
$doc->absorb($tags) if $tags; },
closeRow => sub { $_[0]->closeElement('ltx:equation'); },
openColumn => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); },
closeColumn => sub { $_[0]->closeElement('ltx:_Capture_'); },
properties => {%properties}));
Let("\\\\", '\@alignment@newline@noskip');
Let('\@row@before', '\eqnarray@row@before');
Let('\@row@after', '\eqnarray@row@after');
Let('\intertext', '\@ams@intertext');
return; }
DefPrimitive('\lx@ams@cr@binding', sub {
Let("\\\\", '\@alignment@newline@noskip'); });
#======================================================================
# Section 3.1 introduction
#======================================================================
# Section 3.2 Single equations
# equation, equation*
# LaTeX's equation & equation* work, given that we're also defining
# \nonumber, \tag,...
#======================================================================
# A utiltity for extracting the contents of an XMArray
# $interrow, $intercolumn are optional markup to insert between rows & columns, respectively.
# can be array representation of xml.
sub extractXMArrayCells {
my ($document, $array, $interrow, $intercolumn) = @_;
my @contents = ();
my @rows = element_nodes($array);
while (@rows) {
my $row = shift(@rows);
my @cells = element_nodes($row);
while (@cells) {
my $cell = shift(@cells); # ltx:XMCell should contain a single ltx:XMArg
my ($arg) = element_nodes($cell);
if ($arg) {
my @nodes = element_nodes($arg);
# Strip leading & trailing XMHint's from cells;
# they're (presumably) only for positioning and interfere with interpretation of the whole.
if (@nodes && ($document->getNodeQName($nodes[0]) eq 'ltx:XMHint')) { shift(@nodes); }
if (@nodes && ($document->getNodeQName($nodes[-1]) eq 'ltx:XMHint')) { pop(@nodes); }
# Some cultures duplicate an operator at the end of one row and beginning of next
# when we merge the cells together, this confuses the parser
if (my $prev = $contents[-1]) {
if (my $next = $nodes[0]) {
my $role;
if (($document->getNodeQName($prev) eq 'ltx:XMTok')
&& ($document->getNodeQName($next) eq 'ltx:XMTok')
&& (($role = ($prev->getAttribute('role') || '<none>'))
eq ($next->getAttribute('role') || '<none>'))
&& (($prev->getAttribute('meaning') || '<none>') eq ($next->getAttribute('meaning') || '<none>'))
&& ($role =~ /^(?:ADDOP|MULOP|RELOP)$/)
) {
pop(@contents); } } }
push(@contents, @nodes); }
push(@contents, $intercolumn) if $intercolumn && @cells; }
push(@contents, $interrow) if $interrow && @rows; }
return @contents; }
#======================================================================
# Section 3.3 Split equations without alignment
# Multiline is for SINGLE equations,
# but split on multiple lines, using \\ to separate lines. (there are no &)
# Justifies the 1st line left, last line right, and middle ones centered.
# Useful when you are trying to fit a long equation into a known space,
# This preserves the splits and semantics by constructing an XMDual.
DefPrimitiveI('\@ams@multirow@bindings', 'RequiredKeyVals:multirow OptionalKeyVals', sub {
my ($stomach, $attributes, $options) = @_;
my %attr = $attributes->getPairs;
my %options = ($options ? $options->getPairs : ());
if (my $va = $attr{vattach}) {
$attr{vattach} = translateAttachment($va) || ToString($va); }
if ($attr{width} && $attr{width}->valueOf == 0) {
delete $attr{width}; }
my $before = $options{before_row};
my $after = $options{after_row};
my $col1 = {
before => Tokens(T_CS('\hfil'), T_CS('\displaystyle'),
($before ? (T_CS('\text'), T_BEGIN, $before, T_END) : ())),
after => Tokens(($after ? (T_CS('\text'), T_BEGIN, $after, T_END) : ())) };
my $template = LaTeXML::Core::Alignment::Template->new(repeated => [$col1]);
amsAlignmentBindings($template, %attr);
return; });
DefMacro('\multline',
'\ifmmode\@hidden@bgroup\@ams@multirow@bindings{name=multline}\@@AmS@multline\@start@alignment'
. '\else\@hidden@bgroup\@ams@multirow@bindings{name=multline}\@@multline\@start@alignment\fi');
DefMacro('\endmultline',
'\hidden@cr{}\@finish@alignment\@end@multline\@hidden@egroup');
DefMacro('\csname multline*\endcsname',
'\@hidden@bgroup\@ams@multirow@bindings{name=multline}\@@multlinestar\@start@alignment');
DefMacro('\csname endmultline*\endcsname',
'\hidden@cr{}\@finish@alignment\@end@multline\@hidden@egroup');
DefPrimitive('\@end@multline', sub { $_[0]->egroup; });
DefConstructor('\@@multline DigestedBody',
"<ltx:equation xml:id='#id'>"
. "#tags"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>#1</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
properties => sub { RefStepCounter('equation') },
beforeDigest => sub { $_[0]->bgroup; },
afterDigest => sub { # Nasty trick required to make "body" be the arg.
$_[1]->setProperty('MULTIROW_ALIGNMENT_RULE' => { '0' => 'left', '-1' => 'right' });
$_[1]->setBody($_[1]->getArg(1)->unlist, undef);
return; },
reversion => '\begin{multline}#1\end{multline}',
afterConstruct => sub {
rearrangeAMSMultirow($_[0], $_[1], $_[0]->getNode->lastChild->lastChild->lastChild->lastChild); });
DefConstructor('\@@multlinestar DigestedBody',
"<ltx:equation>"
. "<ltx:Math mode='display'>"
. "<ltx:XMath>#body</ltx:XMath>"
. "</ltx:Math>"
. "</ltx:equation>",
mode => 'display_math',
beforeDigest => sub { $_[0]->bgroup; },
afterDigest => sub { # Nasty trick required to make "body" be the arg.
$_[1]->setProperty('MULTIROW_ALIGNMENT_RULE' => { '0' => 'left', '-1' => 'right' });
$_[1]->setBody($_[1]->getArg(1)->unlist, undef); },
reversion => '\begin{multline*}#1\end{multline*}',
afterConstruct => sub {
rearrangeAMSMultirow($_[0], $_[1], $_[0]->getNode->lastChild->lastChild->lastChild->lastChild); });
# A version for AmSTeX when it appears within math mode.
DefConstructor('\@@AmS@multline DigestedBody',
"#body",
mode => 'display_math',
beforeDigest => sub { $_[0]->bgroup; },
afterDigest => sub { # Nasty trick required to make "body" be the arg.
$_[1]->setProperty('MULTIROW_ALIGNMENT_RULE' => { '0' => 'left', '-1' => 'right' });
$_[1]->setBody($_[1]->getArg(1)->unlist, undef); },
reversion => '\multline#1\endmultline',
afterConstruct => sub {
rearrangeAMSMultirow($_[0], $_[1], $_[0]->getNode->lastChild); });
# Adjust multirow environments to their special needs
# $array must be an ltx:XMArray
DefKeyVal('multirow', 'width', 'Dimension');
DefKeyVal('multirow', 'rowsep', 'Dimension');
sub rearrangeAMSMultirow {
my ($document, $whatsit, $array) = @_;
if ($array) {
my @rows = element_nodes($array);
# Adjust alignment of rows
if (defined(my $rowalignment = $whatsit->getProperty('MULTIROW_ALIGNMENT_RULE'))) {
my %align_spec = %{$rowalignment};
if (defined $align_spec{'default'}) {
for my $i (0 .. $#rows) {
map { $_->setAttribute(align => $align_spec{'default'}); } element_nodes($rows[$i]); }
delete $align_spec{'default'}; }
for my $i (sort (keys %align_spec)) {
map { $_->setAttribute(align => $align_spec{$i}); } element_nodes($rows[$i]); } }
my @stuff = extractXMArrayCells($document, $array);
$document->replaceTree(['ltx:XMDual', {},
['ltx:XMWrap', { rule => 'Anything,' },
createXMRefs($document, @stuff)],
$array],
$array); }
return; }
#======================================================================
# Section 3.4 Split equations with alignment
# split is for SINGLE equations,
# Interestingly, {split} (which creates multiple lines, with 2 columns)
# can be used not only within equation, but gather,
# AND (curiously) within an align environment!
# In the latter case, it fills the horizontal space of ONE of the align's column pairs,
# BUT
# (1) it is horizontally aligned AS IF it were made up of its two columns within the align
# (2) it only occupies (part of) a SINGLE row (and is vertically centered in it).
# And in particular, it's own multiple rows REMAIN aligned w/respect to each other,
# as if it's still a block.
# I've been unable to come up with a way of constructing this that is both (to some extent)
# logically constructed, and that preserves all the above alignment characteristics,
# that doesn't require at least some Magic width calculations to shift/reposition some element.
# Consequently, I'm currently focussed on getting the logical structure right,
# and occupying the "correct" rows/columns.
# That means that the split is NOT NECESSARILY HORIZONTALLY aligned correctly!
# Maybe we can come back & patch that some day?
# In the simpler cases (equation, gather),
# we'll use an XMDual to represent both the whole equation,
# and the alignment structure.
# (\intertext?)
DefConditional('\if@in@ams@align', sub {
grep { /^align/ } $STATE->lookupStackedValues('current_environment'); });
DefConstructor('\lx@ams@marksplitinalign', sub {
my $capture = $_[0]->getElement->parentNode->parentNode;
$capture->setAttribute(colspan => 2);
$capture->setAttribute(align => 'center'); },
# Skip a column (for left/right alignment)
afterDigest => sub { LookupValue('Alignment')->nextColumn; return; },
reversion => '', sizer => 0);
DefMacro('\split',
'\if@in@ams@align\lx@ams@marksplitinalign\fi'
. '\@hidden@bgroup\@ams@aligned@bindings\@@split\@start@alignment');
DefMacro('\endsplit', '\hidden@cr{}\@finish@alignment\@end@split\@hidden@egroup');
DefPrimitive('\@end@split', sub { $_[0]->egroup; });
DefConstructor('\@@split DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
reversion => '\begin{split}#1\end{split}',
afterConstruct => sub { rearrangeAMSSplit($_[0], $_[0]->getNode->lastChild); });
DefConstructor('\@@@split DigestedBody',
'#1',
reversion => '\begin{split}#1\end{split}',
afterConstruct => sub { rearrangeAMSSplit($_[0], $_[0]->getNode->lastChild); });
sub rearrangeAMSSplit {
my ($document, $array) = @_;
if ($array) {
my @stuff = extractXMArrayCells($document, $array);
$document->replaceTree(['ltx:XMDual', {},
['ltx:XMWrap', { rule => 'Anything,' },
createXMRefs($document, @stuff)],
$array],
$array); }
return; }
#======================================================================
# Section 3.5 Equation groups without alignment
# gather is for several equations, one per line, separated by \\ (& is not used)
# Why isn't this simply a direct equationgroup?
# but note that \intertext IS allowed....
# [or see dlmf code for equationgroup (but with optional implicit alignment)]
# NOTE: Does this need provision to deal with metadata?
DefConstructor('\@@amsgather SkipSpaces DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
afterConstruct => sub { rearrangeAMSGather($_[0], $_[0]->getNode->lastChild); });
DefPrimitive('\end@amsgather', sub { $_[0]->egroup; });
# Set up single centered column.
DefPrimitive('\@ams@gather@bindings', sub {
my $col = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH, T_CS('\hfil')) };
amsRearrangeableBindings(LaTeXML::Core::Alignment::Template->new(columns => [$col]),
attributes => { class => 'ltx_eqn_gather' }); });
# Each equation row (except intertext) consists of single equation.
# Since each equation is single column, w/ no alignment, we'll skip the MathFork stuff,
# and just pull the math content up past the _Capture_
#[maybe we could have avoided creating the capture in the first place?]
sub rearrangeAMSGather {
my ($document, $equationgroup) = @_;
foreach my $equation ($document->findnodes('ltx:equation', $equationgroup)) {
my @cells = $document->findnodes('ltx:_Capture_', $equation);
my @cell1cont = $document->getChildElements($cells[0]);
# Check if this equation is really an intertext
if ((scalar(@cells) == 1) && (scalar(@cell1cont) == 1)
&& (($cell1cont[0]->getAttribute('class') || '') =~ /\b(ltx_intertext)\b/)) {
$equation->replaceNode($cell1cont[0]); } # Replace equation with the block.
elsif ((scalar(@cells) == 1) && (scalar(@cell1cont) == 0)) { # Empty row? Remove it!
$equationgroup->removeChild($equation); }
else {
map { ($document->getNodeQName($_) eq 'ltx:_Capture_') && $document->unwrapNodes($_) }
$document->getChildElements($equation);
map { $_->setAttribute(mode => 'display') } $document->findnodes('ltx:Math', $equation); } }
return; }
# Note that some people use align or gather inside equation, which seems to "work"
# So, we'll treat align as aligned in such cases.
DefMacro('\gather',
'\ifmmode\let\endgather\endgathered\gathered\else'
. '\@hidden@bgroup\@ams@gather@bindings\@@amsgather'
. '\@equationgroup@numbering{numbered=1,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\endgather',
'\hidden@cr{}\@finish@alignment\end@amsgather\@hidden@egroup');
DefMacro('\csname gather*\endcsname',
'\ifmmode\expandafter\let\csname endgather*\endcsname\endgathered\gathered\else'
. '\@hidden@bgroup\@ams@gather@bindings\@@amsgather'
. '\@equationgroup@numbering{numbered=0,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\csname endgather*\endcsname',
'\hidden@cr{}\@finish@alignment\end@amsgather\@hidden@egroup');
#======================================================================
# Section 3.6 Equation groups with mutual alignment
# This environment can contain multiple columns, but apparently the intension is
# that each pair should constitute an equation:
# lhs & = rhs & lhs & = rhs ...
# where each lhs is right aligned, and rhs is left aligned.
# We'll use the equationgroup/equation/MathFork mechanism
# similar to eqnarray.
DefConstructor('\@@amsalign SkipSpaces DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
afterConstruct => sub { rearrangeAMSAlign($_[0], $_[0]->getNode->lastChild); });
DefPrimitive('\end@amsalign', sub { $_[0]->egroup; });
# Set up repeated pairs of columns.
DefPrimitive('\@ams@align@bindings', sub {
my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH) };
my $col2 = { before => Tokens(T_MATH, T_CS('\displaystyle')),
after => Tokens(T_MATH, T_CS('\hfil')) };
amsRearrangeableBindings(LaTeXML::Core::Alignment::Template->new(repeated => [$col1, $col2]),
attributes => { class => 'ltx_eqn_align', colsep => '0pt' });
});
# Each equation row (except intertext) consists of pairs (LHS, =RHS); group accordingly.
sub rearrangeAMSAlign {
my ($document, $equationgroup) = @_;
foreach my $equation ($document->findnodes('ltx:equation', $equationgroup)) {
if (my @cells = $document->findnodes('ltx:_Capture_', $equation)) {
my @cell1cont = $document->getChildElements($cells[0]);
# Check if this equation is really an intertext
if ((scalar(@cells) == 1) && (scalar(@cell1cont) == 1)
&& (($cell1cont[0]->getAttribute('class') || '') =~ /\b(ltx_intertext)\b/)) {
$equation->replaceNode($cell1cont[0]); } # Replace equation with the block.
elsif ((scalar(@cells) == 1) && (scalar(@cell1cont) == 0)) { # Empty row? Remove it!
$equationgroup->removeChild($equation); }
else {
equationgroupJoinCols($document, 2, $equation); } } }
return; }
#[Or should empty rows be removed? Numbered ones still show in print out!!!]
# Note that some people use align or gather inside equation, which seems to "work"
# So, we'll treat align as aligned in such cases.
DefMacro('\align',
'\ifmmode\let\endalign\endaligned\aligned\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=1,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi', locked => 1);
# Note the included \hidden@cr
DefMacro('\endalign',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup', locked => 1);
DefMacro('\csname align*\endcsname',
'\ifmmode\expandafter\let\csname endalign*\endcsname\endaligned\aligned\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=0,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi', locked => 1);
DefMacro('\csname endalign*\endcsname',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup', locked => 1);
# flalign typesets in the full column width (seems perverse to me).
# So, for the time being, it's treated exactly like align.
DefMacro('\flalign',
'\ifmmode\let\endfalign\endaligned\aligned\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=1,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\endflalign',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
DefMacro('\csname flalign*\endcsname',
'\ifmmode\expandafter\let\csname endfalign*\endcsname\endaligned\aligned\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=0,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\csname endflalign*\endcsname',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
# alignat doesn't stretch the columns out as much (?)
# and takes the number of column pairs (which we don't need?)
# We'll ignore these distinctions for now.
DefMacro('\alignat{}',
'\ifmmode\let\endalignat\endalignedat\alignedat{#1}\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=1,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\endalignat',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
DefMacro('\csname alignat*\endcsname{}',
'\ifmmode\expandafter\let\csname endalignat*\endcsname\endalignedat\alignedat{#1}\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=0,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\csname endalignat*\endcsname',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
DefMacro('\xalignat{}',
'\ifmmode\let\endalignat\endalignedat\alignedat{#1}\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=1,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\endxalignat',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
DefMacro('\csname xalignat*\endcsname{}',
'\ifmmode\expandafter\let\csname endalignat*\endcsname\endalignedat\alignedat{#1}\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=0,postset=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\csname endxalignat*\endcsname',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
DefMacro('\xxalignat{}',
'\ifmmode\let\endalignat\endalignedat\alignedat{#1}\else'
. '\@hidden@bgroup\@ams@align@bindings\@@amsalign'
. '\@equationgroup@numbering{numbered=1,post=1,grouped=1,aligned=1}'
. '\@start@alignment\fi');
DefMacro('\endxxalignat',
'\hidden@cr{}\@finish@alignment\end@amsalign\@hidden@egroup');
#======================================================================
# Section 3.7. Alignment building blocks
# gathered, aligned alignedat
# These are intended to be used within math environments, but do they have the same
# `semanitic' intent as far as separating equations?
# aligned/alignedat perhaps do, since the alignment doesn't make much sense otherwise
# [except for a single column, but then split should be used]
# gathered could make sense as arranging a single subexpression into multiple lines within
# a larger expression.
# On the other hand, we'll already be inside of a math environment, so delineating
# these potentially separate equations will be awkward anyway!
# We'll just create an XMDual to contain the whole results, and the aligned structure.
DefMacro('\gathered[]',
'\@hidden@bgroup\@ams@multirow@bindings{name=gathered,vattach=#1}\@@gathered\@start@alignment');
DefMacro('\endgathered',
'\hidden@cr{}\@finish@alignment\@end@gathered\@hidden@egroup');
DefPrimitive('\@end@gathered', sub { $_[0]->egroup; });
DefConstructor('\@@gathered DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
afterDigest => sub { $_[1]->setProperty('MULTIROW_ALIGNMENT_RULE' => { 'default' => 'center' }); },
reversion => '\begin{gathered}#1\end{gathered}',
afterConstruct => sub { rearrangeAMSMultirow($_[0], $_[1], $_[0]->getNode->lastChild); });
DefPrimitive('\@ams@aligned@bindings', sub {
my $col1 = { before => Tokens(T_CS('\hfil'),
T_CS('\displaystyle')) };
my $col2 = { before => Tokens(T_CS('\displaystyle')),
after => Tokens(T_CS('\hfil')) };
my $template = LaTeXML::Core::Alignment::Template->new(repeated => [$col1, $col2]);
amsAlignmentBindings($template, (name => 'aligned', colsep => '0pt'));
DefMacro('\@row@before', '');
DefMacro('\@row@after', '');
return; });
# Perverse, but See amsmath's \alignsafe@tesetopt
# Optional argument, but check for "[" WITHOUT triggering alignment machinery
DefParameterType('alignsafeOptional', sub {
my ($gullet, $default, $inner) = @_;
my ($tok, $value);
{ local $LaTeXML::ALIGN_STATE = 1000000;
$tok = $gullet->readNonSpace; }
if (!defined $tok) { }
elsif (($tok->equals(T_OTHER('[')))) {
$value = $gullet->readUntil(T_OTHER(']')); }
else {
$gullet->unread($tok); }
if (!$value && $default) {
$value = $default; }
elsif ($inner) {
($value) = $inner->reparseArgument($gullet, $value); }
$value; },
optional => 1,
reversion => sub {
my ($arg, $default, $inner) = @_;
if ($arg) {
(T_OTHER('['),
($inner ? $inner->revertArguments($arg) : Revert($arg)),
T_OTHER(']')); }
else { (); } });
DefMacro('\aligned alignsafeOptional',
'\@hidden@bgroup\@ams@aligned@bindings\@@amsaligned\@start@alignment', locked => 1);
DefMacro('\endaligned', '\hidden@cr{}\@finish@alignment\@end@amsaligned\@hidden@egroup', locked => 1);
DefMacro('\alignedat{} alignsafeOptional',
'\@hidden@bgroup\@ams@aligned@bindings\@@amsaligned\@start@alignment', locked => 1);
DefMacro('\endalignedat', '\hidden@cr{}\@finish@alignment\@end@amsaligned\@hidden@egroup', locked => 1);
DefPrimitive('\@end@amsaligned', sub { $_[0]->egroup; });
DefConstructor('\@@amsaligned DigestedBody',
'#1',
beforeDigest => sub { $_[0]->bgroup; },
# note that mathtools fails in image generation, w/o this space in reversion!
reversion => '\begin{aligned} #1\end{aligned}');
# If an aligned is the only child of an equation,
# it seems better to rewrite the thing into an equationgroup/equation/MathFork construct!
Tag('ltx:equation', afterClose => \&rearrangeLoneAMSAligned);
sub rearrangeLoneAMSAligned {
my ($document, $equation) = @_;
# Test whether this is a lone aligned within the equation.
my ($math, @more) = $document->findnodes('ltx:Math', $equation);
if ($math && !scalar(@more)) {
my ($array, @morenodes) = $document->getChildElements($document->getFirstChildElement($math));
if ($array && !scalar(@morenodes) && ($document->getNodeQName($array) eq 'ltx:XMArray')
&& (($array->getAttribute('name') || '') eq 'aligned')) {
# we unbind, even though we're going to add the inner XM nodes BACK into the DOM.
$math->unbindNode;
my $equationgroup = $document->renameNode($equation, 'ltx:equationgroup');
foreach my $mtr ($document->findnodes('ltx:XMRow', $array)) {
# new equation for each row (??? Or should it be each pair of columns?)
my $eqn = $document->openElementAt($equationgroup, 'ltx:equation');
if (my $id = $equation->getAttribute('xml:id')) {
$document->setAttribute($eqn, 'xml:id' => $document->modifyID($id . "X")); }
my @mtds = $document->findnodes('ltx:XMCell', $mtr);
while (@mtds) {
my ($main, $branch) = openMathFork($document, $eqn);
my @cells;
foreach (0, 1) { # Add the lhs & rhs, separately.
my $cell = shift(@mtds);
next unless $cell; # alignment not in pairs? Maybe we shouldn't even get this far?
my $td = $document->openElementAt($branch, 'ltx:td',
align => $cell->getAttribute('align'));
if (my $stuff = $cell->firstChild) {
push(@cells, $stuff);
my $imath = $document->openElementAt($td, 'ltx:Math',
_box => MathWhatsit(Digest(T_CS('\displaystyle')),
$document->getNodeBox($stuff)));
my $xmath = $document->openElementAt($imath, 'ltx:XMath');
# Clone the math nodes from the original equation but morphing the ID's
{ local $LaTeXML::Core::Document::ID_SUFFIX = '.mf';
$document->appendClone($xmath, $stuff->childNodes); }
$document->closeElementAt($xmath);
$document->closeElementAt($imath); }
$document->closeElementAt($td); }
# Finally, MOVE the contents of the cells into the MAIN branch!!!
# keeping the original ID's [This should be OK, due to moving the nodes, not recreating]
map { $main->firstChild->appendChild($_) } map { $_->childNodes } @cells;
# and synthesize a box from the lhs & rhs.
$document->setNodeBox($main, MathWhatsit(map { $document->getNodeBox($_) } @cells));
closeMathFork($document, $eqn, $main, $branch);
} } } }
return; }
#======================================================================
# set up a general macro to support variations on cases environments (see mathtools)
# \lx@ams@cases{keyvals}{left}{right} body
# keys are
# name : the name of the environment (for reversion)
# meaning: the (presumed) meaning of the construct
# style : \textstyle or \displaystyle
# conditionmode : mode of 2nd column, text or math
DefMacro('\lx@ams@cases{}',
'\lx@gen@cases@bindings{#1}\lx@ams@cr@binding\lx@ams@cases@{#1}\@start@alignment');
DefMacro('\lx@end@ams@cases',
'\hidden@cr{}\@finish@alignment\lx@end@gen@cases');
# The logical structure for cases extracts the columns of the alignment
# to give alternating value,condition (an empty condition is replaced by "otherwise" !?!?!)
DefConstructor('\lx@ams@cases@ RequiredKeyVals:lx@GEN DigestedBody',
'<ltx:XMWrap>#left#2#right</ltx:XMWrap>',
properties => sub { %{ $_[1]->getKeyVals }; },
afterConstruct => sub {
my ($document) = @_;
if (my $point = $document->getElement->lastChild) {
# Get the sequence of alternating (case, condition).
# Expecting ltx:XMArray/ltx:XMRow/ltx:XMCell [should have /ltx:XMArg, but could be empty!!!]
my @cells = $document->findnodes('ltx:XMArray/ltx:XMRow/ltx:XMCell', $point);
my @stuff = map { ($_->hasChildNodes ? createXMRefs($document, element_nodes($_))
: ['ltx:XMText', {}, 'otherwise']) } @cells;
$document->replaceTree(['ltx:XMDual', {},
['ltx:XMApp', {}, ['ltx:XMTok', { meaning => 'cases' }], @stuff],
$point],
$point); }
},
reversion => sub {
my ($whatsit, $kv, $body) = @_;
my $name = $kv->getValue('name');
(T_CS('\begin'), T_BEGIN, Revert($name), T_END,
Revert($body),
T_CS('\end'), T_BEGIN, Revert($name), T_END); });
# NOTE: Use \@left,\@right here, to avoid the hidden grouping (see TeX.pool, \@hidden@bgroup)
# NOTE: These defns have an column spec [] (omit that for mathtools)
DefMacro('\cases', '\lx@ams@cases{name=cases,meaning=cases,left=\@left\{}');
DefMacro('\endcases', '\lx@end@ams@cases');
#======================================================================
# Section 3.8 Adjusting tag placement
DefMacro('\raisetag{Dimension}', ''); # Ignorable
#======================================================================
# Section 3.9 Vertical spacing and page breaks in multiline display
DefMacro('\displaybreak[]', ''); # Ignorable
#======================================================================
# Section 3.10 Interrupting a display
# default when not used inside an appropriate alignment.
DefConstructor('\intertext{}', "<ltx:p class='ltx_intertext'>#1</ltx:p>", mode => 'text');
#======================================================================
# Section 3.11 Equation numbering
# Section 3.11.1 Numbering hierarchy
DefPrimitive('\numberwithin[]{}{}', sub {
my ($ignore, $format, $counter, $within) = @_;
$format = ($format ? ToString($format) : '\arabic');
$counter = ToString(Expand($counter)); $within = ToString(Expand($within));
NewCounter($counter, $within);
DefMacroI("\\the$counter", undef,
"\\csname the$within\\endcsname.$format\{$counter\}", scope => 'global');
});
# Section 3.11.2 Cross references to equation numbers
DefConstructor('\eqref Semiverbatim', "(<ltx:ref labelref='#label' _force_font='true'/>)",
properties => sub { (label => CleanLabel($_[1])); });
DefMacro('\thetag{}', '{\rm #1}');
# Section 3.11.3 Subordinate numbering sequences.
DefMacro('\subequations', '\lx@equationgroup@subnumbering@begin', locked => 1);
DefMacro('\endsubequations', '\lx@equationgroup@subnumbering@end', locked => 1);
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 4: Miscellaneous mathematical features
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Section 4.1 Matrices
NewCounter('MaxMatrixCols');
SetCounter('MaxMatrixCols' => Number(10));
# set up a general macro to support variations on matrix environments (see mathtools)
# \lx@ams@matrix{keyvals} body
# keys are
# name : the name of the environment (for reversion)
# datameaning: the (presumed) meaning of the array construct
# delimitermeaning : the operator meaning due to delimiters (eg. norm)(as applied to the array)
# style : (display|text|script|scriptscript)
# left : TeX code for left of matrix
# right : TeX code for right
# alignment: the alignment of the columnns (default "c")
DefMacro('\lx@ams@matrix {}',
'\lx@gen@matrix@bindings{#1}\lx@ams@cr@binding\lx@ams@matrix@{#1}\@start@alignment');
DefMacro('\lx@end@ams@matrix',
'\@finish@alignment\lx@end@gen@matrix');
# The delimiters around a matrix may simply be notational, or for readability,
# and don't affect the "meaning" of the array structure as a matrix.
# In that case, we'll use an XMDual to indidate the content is simply the matrix,
# but the presentation includes the delimiters.
# HOWEVER, the delimeters may also signify an OPERATION on the matrix
# in which case the application & meaning of that operator must be supplied.
# Essentially same as \lx#gen@plain@matrix@, but for the body & reversion!
DefConstructor('\lx@ams@matrix@ RequiredKeyVals:lx@GEN DigestedBody',
"?#needXMDual("
. "<ltx:XMDual>"
. "?#delimitermeaning(<ltx:XMApp><ltx:XMTok meaning='#delimitermeaning'/>)()"
. "?#datameaning(<ltx:XMApp><ltx:XMTok meaning='#datameaning'/>)()"
. "<ltx:XMRef _xmkey='#xmkey'/>"
. "?#delimitermeaning(</ltx:XMApp>)()"
. "?#datameaning(</ltx:XMApp>)()"
. "<ltx:XMWrap>#left<ltx:XMArg _xmkey='#xmkey'>#2</ltx:XMArg>#right</ltx:XMWrap>"
. "</ltx:XMDual>"
. ")("
. "#2"
. ")",
properties => sub { %{ $_[1]->getKeyVals }; },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $kv = $whatsit->getArg(1);
if ($kv->getValue('datameaning') || $kv->getValue('delimitermeaning')) {
$whatsit->setProperties(
needXMDual => 1,
xmkey => LaTeXML::Package::getXMArgID()); }
return; },
reversion => sub {
my ($whatsit, $kv, $body) = @_;
my $name = $kv->getValue('name');
my $align = $kv->getValue('alignment');
# Yuck!
my $alignment = $body->getProperty('alignment');
$alignment->normalizeAlignment if $alignment;
my $nc = ($alignment ? scalar(@{ $$alignment{columnwidths} }) : 0);
(($nc > 10 ? Invocation(T_CS('\setcounter'), T_OTHER('MaxMatrixCols'), T_OTHER($nc)) : ()),
($name ? (T_CS('\begin'), T_BEGIN, Revert($name), T_END) : ()),
(IsEmpty($align)
? ()
: ($kv->getValue('alignment-required')
? (T_BEGIN, Revert($align), T_END) : (T_OTHER('['), Revert($align), T_OTHER(']')))),
Revert($body),
($name ? (T_CS('\end'), T_BEGIN, Revert($name), T_END) : ())); }
);
# NOTE: Use \@left,\@right here, to avoid the hidden grouping (see TeX.pool, \@hidden@bgroup)
# NOTE: These defns have an column spec [] (omit that for mathtools)
DefMacro('\matrix', '\lx@ams@matrix{name=matrix,datameaning=matrix}');
DefMacro('\endmatrix', '\lx@end@ams@matrix');
DefMacro('\pmatrix', '\lx@ams@matrix{name=pmatrix,datameaning=matrix,left=\@left(,right=\@right)}');
DefMacro('\endpmatrix', '\lx@end@ams@matrix');
DefMacro('\bmatrix', '\lx@ams@matrix{name=bmatrix,datameaning=matrix,left=\@left[,right=\@right]}');
DefMacro('\endbmatrix', '\lx@end@ams@matrix');
DefMacro('\Bmatrix', '\lx@ams@matrix{name=Bmatrix,datameaning=matrix,left=\@left\{,right=\@right\}}');
DefMacro('\endBmatrix', '\lx@end@ams@matrix');
DefMacro('\vmatrix', '\lx@ams@matrix{name=vmatrix,delimitermeaning=determinant,datameaning=matrix,left=\@left|,right=\@right|}');
DefMacro('\endvmatrix', '\lx@end@ams@matrix');
DefMacro('\Vmatrix', '\lx@ams@matrix{name=Vmatrix,delimitermeaning=norm,datameaning=matrix,left=\@left\|,right=\@right\|}');
DefMacro('\endVmatrix', '\lx@end@ams@matrix');
#DefMacro('\smallmatrix', '\lx@ams@matrix{name=smallmatrix,atameaning=matrix,left=\scriptsize}');
DefMacro('\smallmatrix', '\lx@ams@matrix{name=smallmatrix,atameaning=matrix,style=\scriptsize}');
DefMacro('\endsmallmatrix', '\lx@end@ams@matrix');
# Some author code defines new matrix environments and uses this check guard.
# For now just gobble its argument, assuming the check passes (was handled in pdflatex)
DefMacro('\matrix@check{}', Tokens());
#======================================================================
# Section 4.2 Math spacing commands
# \, == \thinspace
# \: == \medspace
# \; == \thickspace
# \quad
# \qquad
# \! == \negthinspace
# \negmedspace
# \negthickspace
# these are now native to LaTeX (see section C.7.7 Spacing)
DefConstructor('\mspace{MuDimension}', "<ltx:XMHint name='mspace' width='#1'/>");
#======================================================================
# Section 4.3 Dots
# Nice idea, but not sure what I really should do about it.
# In principle, a processor has access to the context....
DefMathI('\dotsc', undef, "\x{2026}", role => 'ID', alias => '\dotsc');
DefMathI('\dotsb', undef, "\x{22EF}", role => 'ID', alias => '\dotsb');
DefMathI('\dotsm', undef, "\x{22EF}", role => 'ID', alias => '\dotsm');
DefMathI('\dotsi', undef, "\x{22EF}", role => 'ID', alias => '\dotsi');
DefMathI('\dotso', undef, "\x{2026}", role => 'ID', alias => '\dotso');
# Not really clear when these get set to something other than \relax, in amsfonts.sty
DefMacroI('\DOTSB', undef, Tokens());
DefMacroI('\DOTSI', undef, Tokens());
DefMacroI('\DOTSX', undef, Tokens());
Let('\hdots', '\lx@ldots');
DefMacro('\hdotsfor Number', sub {
(map { T_CS('\hdots') } 1 .. $_[1]->valueOf); });
# The basic idea is simple enough (in TeX world);
# Peek to see what follows (\futurelet) and if mathbin or mathrel, use cdots.
# That corresponds to the @role of the following token (once in XML),
# but we're always too early to check it!
# Using a Digested arg seems a little risky, especially "$", but usually can see @role
# This currently doesn't see deeply enough into $after, eg. \boldsymbol{+}
DefPrimitive('\lx@math@dots Digested', sub {
my ($stomach, $after) = @_;
my $role = $after && $after->getProperty('role');
my %binops = (ADDOP => 1, BINOP => 1, MULOP => 1, RELOP => 1);
return (Box(($role && $binops{$role} ? "\x{22EF}" : "\x{2026}"),
undef, undef, T_CS('\dots'), mode => 'math', name => 'dots', role => 'ID'),
$after); });
DefMacro('\dots', '\ifmmode\lx@math@dots\else\lx@ldots\fi', robust => 1);
#======================================================================
# Section 4.4 Nonbreaking dashes
# \nobreakdash
DefMacro('\nobreakdash', ''); # Ignorable
#======================================================================
# Section 4.5 Accents in math
DefMath('\dddot{}', "\x{02D9}\x{02D9}\x{02D9}", operator_role => 'OVERACCENT'); # DOT ABOVE
DefMath('\ddddot{}', "\x{02D9}\x{02D9}\x{02D9}\x{02D9}", operator_role => 'OVERACCENT'); # DOT ABOVE
# In amsxtra
# \sphat \sptilde
#======================================================================
# Section 4.6 Roots
# It would be nice to carry this info through to mathml, but ignore for now.
DefMacro('\leftroot{}', '');
DefMacro('\uproot{}', '');
#======================================================================
# Section 4.7 Boxed formulas
DefMacro('\boxed{}', '\ifmmode\boxed@math{#1}\else\boxed@text{#1}\fi', robust => 1);
DefConstructor('\boxed@math{}',
"<ltx:XMArg enclose='box'>#1</ltx:XMArg>",
alias => '\boxed');
DefConstructor('\boxed@text{}',
"<ltx:Math mode='display' framed='rectangle'>"
. "<ltx:XMath>"
. "#1"
. "</ltx:XMath>"
. "</ltx:Math>",
mode => 'math', bounded => 1,
beforeDigest => sub {
Let("\\\\", '\lx@newline'); },
alias => '\boxed');
DefMath('\implies', "\x{27F9}", role => 'ARROW', meaning => 'implies');
DefMath('\impliedby', "\x{27F8}", role => 'ARROW', meaning => 'implied-by');
DefMath('\And', '&', role => 'ADDOP', meaning => 'and');
#======================================================================
# Section 4.8 Over and under arrows
# Should be in LaTeX (& TeX): \overrightarrow, \overleftarrow
# Note that the arrow is treated as an accent over/under the argument!
DefMath('\underrightarrow{}', "\x{2192}", operator_role => 'UNDERACCENT');
DefMath('\underleftarrow{}', "\x{2190}", operator_role => 'UNDERACCENT');
DefMath('\overleftrightarrow{}', "\x{2194}", operator_role => 'OVERACCENT');
DefMath('\underleftrightarrow{}', "\x{2194}", operator_role => 'UNDERACCENT');
#======================================================================
# Section 4.9 Extensible arrows
# \xleftarrow, \xrightarrow
# set up a general macro to support variations on xarrows macros (see mathtools)
# \lx@long@arrow{token}{arrow}[under]{over}
DefConstructor('\lx@long@arrow DefToken {}[]{}',
"?#3("
. "<ltx:XMApp role='ARROW'>"
. "<ltx:XMWrap role='UNDERACCENT'>#3</ltx:XMWrap>"
. "<ltx:XMApp role='ARROW'>"
. "<ltx:XMWrap role='OVERACCENT'>#4</ltx:XMWrap>"
. "#2"
. "</ltx:XMApp>"
. "</ltx:XMApp>"
. ")("
. "<ltx:XMApp role='ARROW'>"
. "<ltx:XMWrap role='OVERACCENT'>#4</ltx:XMWrap>"
. "#2"
. "</ltx:XMApp>"
. ")",
reversion => sub {
my ($whatsit, $cs, $arrow, $under, $over) = @_;
($cs, ($under ? (T_OTHER('['), Revert($under), T_OTHER(']')) : ()), T_BEGIN, Revert($over), T_END); },
# specialize to ldots ???
properties => { font => sub { LookupValue('font')->specialize("\x{2026}"); } });
DefMacro('\xrightarrow', '\lx@long@arrow{\xrightarrow}{\rightarrow}');
DefMacro('\xleftarrow', '\lx@long@arrow{\xleftarrow}{\leftarrow}');
#======================================================================
# Section 4.10 Affixing symbols to other symbols
# Note that the 1st argument is treated set as an accent over (or under) the 2nd argument.
DefConstructor('\overset{}{}',
"<ltx:XMApp>"
. "<ltx:XMWrap role='OVERACCENT'>#1</ltx:XMWrap>"
. "<ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>");
DefConstructor('\underset{}{}',
"<ltx:XMApp>"
. "<ltx:XMWrap role='UNDERACCENT'>#1</ltx:XMWrap>"
. "<ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>");
#======================================================================
# Section 4.11 Fractions and related commands
# Section 4.11.1 The \frac, \dfrac, and \tfrac commands
DefConstructor('\tfrac ScriptStyle ScriptStyle',
"<ltx:XMApp>"
. "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='text'/>"
. "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>",
sizer => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); });
DefConstructor('\dfrac TextStyle TextStyle',
"<ltx:XMApp>"
. "<ltx:XMTok meaning='divide' role='FRACOP' mathstyle='display'/>"
. "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>",
sizer => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); });
# NOTE: This isn't actually in amsmath; where does it come from?
# NOTE: this should be simpler (maybe macro)... it should NOT use the regular sizer!!!
# This is messier to avoid MathML issues with the stretchability/symmetry/left|right spacing
DefConstructor('\ifrac{}{}',
"<ltx:XMApp>"
. "<ltx:XMTok stretchy='#stretchy' meaning='divide' role='MULOP' _font='#slashfont'"
. " lpadding='-0.222em' rpadding='-0.222em'>\x{2215}</ltx:XMTok>"
. "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>",
properties => sub {
my ($stomach, $num, $den) = @_;
my $font = LookupValue('font')->specialize('/');
my ($nw, $nh, $nd) = $num->getSize;
my ($dw, $dh, $dd) = $den->getSize;
my $stretchy = $nh->add($nd)->larger($dh->add($dd))->ptValue > 9;
return (stretchy => $stretchy && 'true', slashfont => $font); });
# Section 4.11.2 The \binom, \dbinom, and \tbinom commands
DefMath('\binom{}{}', '{\left({{#1}\atop{#2}}\right)}',
meaning => 'binomial');
DefMath('\tbinom{}{}', '{\textstyle\left({{#1}\atop{#2}}\right)}',
meaning => 'binomial');
DefMath('\dbinom{}{}', '{\displaystyle\left({{#1}\atop{#2}}\right)}',
meaning => 'binomial');
# Section 4.11.3 The \genfrac command
# \genfrac{open}{close}{thickness}{style}{numerator}{denominator}
# Annoying defaults: if thickness is empty, it is the normal line thickness
# if style is empty, it is the current mathstyle.
# (to disambiguate the optional's, shuffle the order)
DefMacro('\genfrac{}{}{}{}{}{}',
'\lx@genfrac{\if.#1.\else\@left#1\fi}{\if.#2.\else\@right#2\fi}{#3}{#4}{#5}{#6}');
DefMacro('\lx@genfrac{}{}{}{}{}{}',
'\if @#3@'
. '\if.#4.\lx@@genfrac{#1}{#2}{#5}{#6}\else\lx@@genfrac{#1}{#2}[#4]{#5}{#6}\fi'
. '\else'
. '\if.#4.\lx@@genfrac{#1}[#3]{#2}{#5}{#6}\else\lx@@genfrac{#1}[#3]{#2}[#4]{#5}{#6}\fi'
. '\fi');
DefConstructor('\lx@@genfrac{}[Dimension]{}[Number]',
"?#needXMDual(<ltx:XMDual>"
. "<ltx:XMApp>"
. "<ltx:XMRef _xmkey='#xmkey0'/>"
. "<ltx:XMRef _xmkey='#xmkey1'/>"
. "<ltx:XMRef _xmkey='#xmkey2'/>"
. "</ltx:XMApp>"
. "<ltx:XMWrap>"
. "#open)()"
. "<ltx:XMApp>"
. "<ltx:XMTok _xmkey='#xmkey0' role='#role' meaning='#meaning' mathstyle='#mathstyle' thickness='#thickness'/>"
. "<ltx:XMArg _xmkey='#xmkey1'>#top</ltx:XMArg>"
. "<ltx:XMArg _xmkey='#xmkey2'>#bottom</ltx:XMArg>"
. "</ltx:XMApp>"
. "?#needXMDual(#close"
. "</ltx:XMWrap>"
. "</ltx:XMDual>)()",
reversion => sub {
my ($whatsit) = @_;
my ($open, $thickness, $close, $stylecode) = $whatsit->getArgs;
$open = $open->getArg(1) if ref $open eq 'LaTeXML::Core::Whatsit';
$close = $close->getArg(1) if ref $close eq 'LaTeXML::Core::Whatsit';
(T_CS('\genfrac'),
T_BEGIN, Revert($open), T_END, # Assumes wrapped in \@left/\@right!
T_BEGIN, Revert($close), T_END,
T_BEGIN, ($thickness ? Revert($thickness) : ()), T_END,
T_BEGIN, ($stylecode ? Revert($stylecode) : ()), T_END,
T_BEGIN, Revert($whatsit->getProperty('top')), T_END,
T_BEGIN, Revert($whatsit->getProperty('bottom')), T_END); },
sizer => sub { fracSizer($_[0]->getProperty('top'), $_[0]->getProperty('bottom')); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my ($open, $thickness, $close, $stylecode) = $whatsit->getArgs;
$stylecode = $stylecode->valueOf if defined $stylecode;
my $mathstyle = (!defined $stylecode
? LookupValue('font')->getMathstyle
: ($stylecode == 0 ? 'display'
: ($stylecode == 1 ? 'text'
: ($stylecode == 2 ? 'script'
: 'scriptscript'))));
$stomach->bgroup;
MergeFont(mathstyle => $mathstyle);
MergeFont(fraction => 1);
my $numer = Digest($stomach->getGullet->readArg);
my $denom = Digest($stomach->getGullet->readArg);
$stomach->egroup;
my $meaning = ($thickness && ($thickness->valueOf == 0) ? undef : 'divide');
my $role = 'FRACOP';
$whatsit->setProperties(
open => $open,
close => $close,
role => $role,
meaning => $meaning,
thickness => $thickness,
mathstyle => $mathstyle,
top => $numer,
bottom => $denom);
if ($open || $close) {
$whatsit->setProperties(needXMDual => 1,
xmkey0 => LaTeXML::Package::getXMArgID(),
xmkey1 => LaTeXML::Package::getXMArgID(),
xmkey2 => LaTeXML::Package::getXMArgID()); }
});
#======================================================================
# Section 4.12 Continued fractions
# I think \cfracstyle my own invention? (I know I've redefined \cfrac in DLMFmath)
# XMDual doesn't seem quite appropriate, since the args (denominators) get
# divided up so oddly in the inline case.
# I've left it to a special case in conversion to pmml.
DefMacro('\cfrac', '\lx@savecfrac@mathstyle\let\cfrac=\lx@inner@cfrac\lx@inner@cfrac');
DefPrimitive('\lx@savecfrac@mathstyle', sub {
AssignValue(cfracmathstyle => LookupValue('font')->getMathstyle); });
DefConstructor('\lx@inner@cfrac InFractionStyle InFractionStyle',
"<ltx:XMApp>"
. "<ltx:XMTok name='#name' mathstyle='#mathstyle' meaning='continued-fraction'/>"
. "<ltx:XMArg>#1</ltx:XMArg>"
. "<ltx:XMArg>#2</ltx:XMArg>"
. "</ltx:XMApp>",
alias => '\cfrac',
beforeDigest => sub {
$_[0]->bgroup;
MergeFont(mathstyle => LookupValue('cfracmathstyle')); },
afterDigest => sub {
$_[0]->egroup;
$_[1]->setProperties(name => (LookupValue('CFRACSTYLE') eq 'inline' ? 'cfrac-inline' : 'cfrac'),
mathstyle => LookupValue('cfracmathstyle')); });
AssignValue(CFRACSTYLE => 'display');
# This should get incorporated into any \cfrac's that are constructed in scope.
DefConstructor('\cfracstyle{}', '',
afterDigest => sub {
my $style = ToString($_[1]->getArg(1));
$style = ($style eq 'd' ? 'display' : ($style eq 'i' ? 'inline' : $style));
AssignValue(CFRACSTYLE => $style); });
#======================================================================
# Section 4.13 Smash options
DefConstructor('\smash[]{}', "#2"); # well, what?
#======================================================================
# Section 4.14 Delimiters
# Section 4.14.1 Delimiter sizes
# Redefinitions(?) of \bigl, \bigr, \Bigl,\Bigr, \biggl, \biggr, \Biggl, \Biggr
# Section 4.14.2 Vertical bar notations
DefMath('\lvert', '|', role => 'OPEN', stretchy => 'false');
DefMath('\lVert', "\x{2225}", role => 'OPEN', stretchy => 'false'); # PARALLEL TO
DefMath('\rvert', '|', role => 'CLOSE', stretchy => 'false');
DefMath('\rVert', "\x{2225}", role => 'CLOSE', stretchy => 'false'); # PARALLEL TO
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 5 Operator names
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Section 5.1 Defining new operator names
# See package amsopn (included by default)
#======================================================================
# Section 5.2 \mod and it's relatives
# \bmod, \pmod which are already in LaTeX
DefMath('\mod', 'mod', role => 'MODIFIEROP', meaning => 'modulo');
DefMath('\pod{}', '(#1)', role => 'MODIFIER', meaning => 'modulo'); # Well, sorta postfix..
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 6 The \text command
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# See package amstext, included by default.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 7 Integrals and sums
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Section 7.1 Multiline subscripts and superscripts
# \substack, \begin{subarray}
# These make the combining operator be list, but often formulae would be better
DefMacro('\substack{}', '\begin{subarray}{c}#1\end{subarray}');
DefMacro('\subarray{}',
'\lx@ams@matrix{name=subarray,style=\scriptsize,datameaning=list,rowsep=0pt,alignment=#1,alignment-required=true}');
DefMacro('\endsubarray', '\lx@end@ams@matrix');
#======================================================================
# Section 7.2 the \sideset command
# This is intended to be a modifier for \sum or \prod
# NOTE that there can be at most one subscript in each of the pre & post, ditto for superscript.
# Thus, our representation is: sideset(presub,presup,postsub,postsup,object)
# Note, also, that this is quite ugly, but since it is a rather peculiar special case.... ?
DefConstructor('\sideset{}{}{}', sub {
my ($document, $pre, $post, $base, %props) = @_;
my @scripts = (undef, undef, undef, undef);
# Just to be "safe", scan for any NON scripts in the pre-scripts
# They work with \sideset, although it isn't clear what they should mean.
# we'll just insert them in front of the whole mess.
foreach my $script ($pre->unlist) {
if (!IsScript($script)) {
Warn('expected', '<sub/supserscript>', $document,
"Expected a sub/superscript in the prescripts of \\sideset",
"Got " . Stringify($script));
$document->insertElement('ltx:XMWrap', $script); } } # Stick any non-scripts FRONT!
my $node = $document->insertElement('ltx:XMArg', $base);
my $ch = $document->getFirstChildElement($node);
my ($opx, $ignore)
= ($ch && $ch->getAttribute('scriptpos') || 'post') =~ /^(pre|mid|post)?(\d+)?$/;
my $level0 = $props{scriptlevel} || 0;
my $level = $level0;
foreach my $script (reverse $pre->unlist) {
# If it's a script, handle it (non-scripts dealt with above & below)
if (my $scriptop = IsScript($script)) {
$node = sidesetWrap($document, $node, 'pre', $$scriptop[1], $level, $script);
$level++ if $$scriptop[0] eq 'FLOATING'; # After node is seen!
} }
my @after = ();
foreach my $script ($post->unlist) {
# If it's a script, handle it (non-scripts dealt with above & below)
if (my $scriptop = IsScript($script)) {
$level++ if $$scriptop[0] eq 'FLOATING'; # Before node is seen!
$node = sidesetWrap($document, $node, 'post', $$scriptop[1], $level, $script); }
else {
push(@after, $script); } } # Save non-scripts
$document->setAttribute($node, scriptpos => $opx . $level0) if $opx;
# Put any garbage in the post-scripts AFTER
foreach my $nonscript (@after) {
Warn('expected', '<sub/supserscript>', $document,
"Expected a sub/superscript in the postscripts of \\sideset",
"Got " . Stringify($nonscript));
$document->insertElement('ltx:XMWrap', $nonscript); } # Append, afterwards
});
sub sidesetWrap {
my ($document, $node, $x, $y, $level, $script) = @_;
my $new = $document->openElement('ltx:XMApp');
$document->insertElement('ltx:XMTok', undef, role => $y . 'OP', scriptpos => "$x$level");
$new->appendChild($node);
$document->insertElement('ltx:XMWrap', $script->getArg(1));
$document->closeElement('ltx:XMApp');
return $new; }
#======================================================================
# Section 7.3 Placement of subscripts and limits
# \limits and \nolimits; already in TeX
#======================================================================
# Section 7.4 Multiple integral signs
DefMath('\iint', "\x{222C}", meaning => 'double-integral', role => 'INTOP',
mathstyle => \&doVariablesizeOp);
DefMath('\iiint', "\x{222D}", meaning => 'triple-integral', role => 'INTOP',
mathstyle => \&doVariablesizeOp);
DefMath('\iiiint', "\x{2A0C}", meaning => 'quadruple-integral', role => 'INTOP',
mathstyle => \&doVariablesizeOp);
DefMath('\idotsint', "\x{222B}\x{22EF}\x{222B}", meaning => 'multiple-integral', role => 'INTOP',
mathstyle => \&doVariablesizeOp);
DefMacro('\MultiIntegral{}', sub {
my ($gullet, $n) = @_;
$n = ToString($n);
(($n == 0 ? T_CS('\idotsint') : ($n == 1 ? T_CS('\int') : ($n == 2 ? T_CS('\iint') : ($n == 3 ? T_CS('\iiint') : T_CS('\iiiint')))))); });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 8 Commutative diagrams
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Separately in amscd
# CD environment
# with commands @>.., @<<<, @VVV, @AAA to give right, left, down, up arrows...
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Section 9 Using math fonts
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Section 9.1 Introduction
# See amsfonts, euscript
#======================================================================
# Section 9.2 Recommended use of math font commands
#======================================================================
# Section 9.3 Bold math symbols
# See package amsbsy (included by default)
#======================================================================
# Section 9.4 Italic Greek letters
# I think this is sufficient:
DefMath('\varGamma', "\x{0393}", font => { shape => 'italic' });
DefMath('\varSigma', "\x{03A3}", font => { shape => 'italic' });
DefMath('\varDelta', "\x{0394}", font => { shape => 'italic' });
DefMath('\varUpsilon', "\x{03A5}", font => { shape => 'italic' });
DefMath('\varTheta', "\x{0398}", font => { shape => 'italic' });
DefMath('\varPhi', "\x{03A6}", font => { shape => 'italic' });
DefMath('\varLambda', "\x{039B}", font => { shape => 'italic' });
DefMath('\varPsi', "\x{03A8}", font => { shape => 'italic' });
DefMath('\varXi', "\x{039E}", font => { shape => 'italic' });
DefMath('\varOmega', "\x{03A9}", font => { shape => 'italic' });
DefMath('\varPi', "\x{03A0}", font => { shape => 'italic' });
#======================================================================
# And some random other weird naming
Let('\Hat', '\hat');
Let('\Check', '\check');
Let('\Tilde', '\tilde');
Let('\Acute', '\acute');
Let('\Grave', '\grave');
Let('\Dot', '\dot');
Let('\Ddot', '\ddot');
Let('\Breve', '\breve');
Let('\Bar', '\bar');
Let('\Vec', '\vec');
# And where does this go?
DefPrimitive('\allowdisplaybreaks[]', undef);
#======================================================================
DefMacroI('\mintagsep', undef, Tokens());
DefMacroI('\minalignsep', undef, "10pt");
DefRegister('\multlinegap' => Glue('10pt'));
DefRegister('\multlinetaggap' => Glue('10pt'));
DefMacro('\primfrac{}', Tokens());
# \shoveleft, \shoveright should do something about eqn number placement?
DefMacro('\shoveleft{}', '#1');
DefMacro('\shoveright{}', '#1');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1;