# -*- 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 #_# | # # | 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'); DefMacro('\@currdir', './'); # Let's try just starting with this set (since we've loaded LaTeX) AssignValue(inPreamble => 1); # \begin{document} will clear this. DefConstructor('\documentclass OptionalSemiverbatim SkipSpaces Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\documentclass'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $options = $whatsit->getArg(1); my $class = ToString($whatsit->getArg(2)); $class =~ s/\s+//g; LoadClass($class, options => [($options ? split(/\s*,\s*/, ToString($options)) : ())], after => Tokens(T_CS('\AtBeginDocument'), T_CS('\warn@unusedclassoptions'))); return; }); AssignValue('@unusedoptionlist', []); DefPrimitiveI('\warn@unusedclassoptions', undef, sub { if (my @unused = @{ LookupValue('@unusedoptionlist') }) { Info('unexpected', 'options', $_[0], "Unused global options: " . join(',', @unused)); AssignValue('@unusedoptionlist', []); } return; }); DefConstructor('\documentstyle OptionalSemiverbatim SkipSpaces Semiverbatim []', "", beforeDigest => sub { Info('unexpected', '\documentstyle', $_[0], "Entering LaTeX 2.09 Compatibility mode"); AssignValue('2.09_COMPATIBILITY' => 1, 'global'); onlyPreamble('\documentstyle'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $class = ToString($whatsit->getArg(2)); $class =~ s/\s+//g; my $options = $whatsit->getArg(1); $options = [($options ? split(/\s*,\s*/, ToString($options)) : ())]; # Watch out; In principle, compatibility mode wants a .sty, not a .cls!!! # But, we'd prefer .cls, since we'll have better bindings. # And in fact, nobody's likely to write a binding for a .sty that wants to be a class anyway. # So, we'll just try for a .cls, punting to OmniBus if needed. # If we start wanting to read style files by default, we'll still need to handle this # specially, since class (or sty files pretending to be) cover so much more. # # EXCEPTION: of course there is an exception. Older arXiv articles use \documentstyle{aipproc} # to load a different aipproc.sty than the later evolved aipproc.cls. And of course we need to support that. if (FindFile($class, type => 'sty', notex => !LookupValue('INCLUDE_CLASSES'))) { InputDefinitions('article', type => 'cls', noerror => 1, handleoptions => 1, options => $options); RequirePackage($class, options => $options, as_class => 1, after => Tokens(T_CS('\compat@loadpackages'))); } elsif (FindFile($class, type => 'cls', notex => !LookupValue('INCLUDE_CLASSES'))) { LoadClass($class, options => $options, after => Tokens(T_CS('\compat@loadpackages'))); } else { InputDefinitions('OmniBus', type => 'cls', noerror => 1, handleoptions => 1, options => $options, after => Tokens(T_CS('\compat@loadpackages'))); RequirePackage($class, options => $options, as_class => 1); } return; }); DefPrimitiveI('\compat@loadpackages', undef, sub { my $name = ToString(Expand(T_CS('\@currname'))); my $type = ToString(Expand(T_CS('\@currext'))); my $hadmissing = 0; foreach my $option (@{ LookupValue('@unusedoptionlist') }) { if (FindFile($option, type => 'sty')) { RequirePackage($option); } else { $hadmissing = 1; Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } } # Often, in compatibility mode, the options are used to load what are effectively # document classes for specific journals, etc that introduce a bunch of new frontmatter! # To try to recover from this, we'll go ahead & load the OmniBus class. if ($hadmissing && !LookupValue('OmniBus.cls_loaded')) { Info('note', 'OmniBus', $_[0], "Loading OmniBus class to attempt to cover missing options"); LoadClass('OmniBus'); } AssignValue('@unusedoptionlist', []); }); sub onlyPreamble { my ($cs) = @_; Error('unexpected', $cs, $STATE->getStomach, "The current command '" . ToString($cs) . "' can only appear in the preamble") unless LookupValue("inPreamble"); return; } #********************************************************************** # C.1. Commands and Environments. #********************************************************************** #====================================================================== # C.1.1 Command Names and Arguments #====================================================================== # Nothing... #====================================================================== # C.1.2 Environments #====================================================================== # In LaTeX, \newenvironment{env} defines \env and \endenv. # \begin{env} & \end{env} open/close a group, and invoke these. # In fact, the \env & \endenv don't have to have been created by # \newenvironment; And in fact \endenv doesn't even have to be defined! # [it is created by \csname, and equiv to \relax if no previous defn] # We need to respect these usages here, but we also want to be able # to define environment constructors that `capture' the body so that # it can be processed specially, if needed. These are the magic # '\begin{env}', '\end{env}' control sequences created by DefEnvironment. AssignValue(current_environment => '', 'global'); DefMacro('\@currenvir', ''); DefPrimitive('\lx@setcurrenvir{}', sub { DefMacroI('\@currenvir', undef, $_[1]); AssignValue(current_environment => ToString($_[1])); }); Let('\@currenvline', '\@empty'); DefMacro('\begin{}', sub { my ($gullet, $env) = @_; my $name = $env && ToString(Expand($env)); my $before = LookupValue('@environment@' . $name . '@beforebegin'); my $after = LookupValue('@environment@' . $name . '@atbegin'); if (IsDefined("\\begin{$name}")) { (($before ? @$before : ()), T_CS("\\begin{$name}")); } # Magic cs! else { my $token = T_CS("\\$name"); if (!IsDefined($token)) { my $undef = '{' . $name . '}'; $STATE->noteStatus(undefined => $undef); Error('undefined', $undef, $gullet, "The environment " . $undef . " is not defined."); $STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($token, undef, sub { $_[0]->makeError('undefined', $undef); })); } (($before ? @$before : ()), T_CS('\begingroup'), ($after ? @$after : ()), Invocation(T_CS('\lx@setcurrenvir'), $env), $token,); } }); # robust => 1); # Not yet working well enough DefMacro('\end{}', sub { my ($gullet, $env) = @_; my $name = $env && ToString(Expand($env)); my $before = LookupValue('@environment@' . $name . '@atend'); my $after = LookupValue('@environment@' . $name . '@afterend'); my $t; if (IsDefined($t = T_CS("\\end{$name}"))) { ($t, ($after ? @$after : ())); } # Magic CS! else { $t = T_CS("\\end$name"); (($before ? @$before : ()), (IsDefined($t) ? $t : ()), T_CS('\endgroup'), ($after ? @$after : ())); } }); ## robust => 1); # Isn't really robust, but something similar(?) #====================================================================== # C.1.3 Fragile Commands #====================================================================== # Because of the way we `move information', revertable and pre-processed, # I don't think we actually need to do anything ... # [Course that means we're not _really_ TeX!] # \protect is already in TeX for obscure reasons... #Let('\@typeset@protect','\relax'); RawTeX(<<'EOTeX'); \def\@ignorefalse{\global\let\if@ignore\iffalse} \def\@ignoretrue {\global\let\if@ignore\iftrue} \def\zap@space#1 #2{% #1% \ifx#2\@empty\else\expandafter\zap@space\fi #2} \def\@unexpandable@protect{\noexpand\protect\noexpand} \def\x@protect#1{% \ifx\protect\@typeset@protect\else \@x@protect#1% \fi } \def\@x@protect#1\fi#2#3{% \fi\protect#1% } \let\@typeset@protect\relax \def\set@display@protect{\let\protect\string} \def\set@typeset@protect{\let\protect\@typeset@protect} \def\protected@edef{% \let\@@protect\protect \let\protect\@unexpandable@protect \afterassignment\restore@protect \edef } \def\protected@xdef{% \let\@@protect\protect \let\protect\@unexpandable@protect \afterassignment\restore@protect \xdef } \def\unrestored@protected@xdef{% \let\protect\@unexpandable@protect \xdef } \def\restore@protect{\let\protect\@@protect} \set@typeset@protect \def\@nobreakfalse{\global\let\if@nobreak\iffalse} \def\@nobreaktrue {\global\let\if@nobreak\iftrue} \@nobreakfalse \newif\ifv@ \newif\ifh@ \newif\ifdt@p \newif\if@pboxsw \newif\if@rjfield \newif\if@firstamp \newif\if@negarg \newif\if@ovt \newif\if@ovb \newif\if@ovl \newif\if@ovr \newdimen\@ovxx \newdimen\@ovyy \newdimen\@ovdx \newdimen\@ovdy \newdimen\@ovro \newdimen\@ovri \newif\if@noskipsec \@noskipsectrue EOTeX #====================================================================== # C.1.4 Declarations #====================================================================== # actual implementation later. #====================================================================== # C.1.5 Invisible Commands #====================================================================== # actual implementation later. #====================================================================== # C.1.6 The \\ Command #====================================================================== # In math, \\ is just a formatting hint, unless within an array, cases, .. environment. DefConstructor("\\\\ OptionalMatch:* [Glue]", "?#isMath()()", reversion => Tokens(T_CS("\\\\"), T_CR), properties => { isBreak => 1 }); DefConstructor('\newline', "?#isMath()()", reversion => Tokens(T_CS('\newline'), T_CR), properties => { isBreak => 1 }); Let('\@normalcr', "\\\\"); Let('\@normalnewline', '\newline'); ##PushValue(TEXT_MODE_BINDINGS => [ ## T_CS("\\\\"), T_CS('\@normalcr'), T_CS('\newline'), T_CS('\@normalnewline')]); DefMacro('\@nolnerr', ''); DefMacro('\@centercr', '\ifhmode\unskip\else\@nolnerr\fi' . '\par\@ifstar{\nobreak\@xcentercr}\@xcentercr'); DefMacro('\@xcentercr', '\addvspace{-\parskip}\@ifnextchar[\@icentercr\ignorespaces'); DefMacro('\@icentercr[]', '\vskip #1\ignorespaces'); #********************************************************************** # C.2. The Structure of the Document #********************************************************************** # prepended files (using filecontents environment) # preamble (starting with \documentclass) # \begin{document} # text # \end{document} DefMacro('\AtBeginDocument{}', sub { PushValue('@at@begin@document', $_[1]->unlist); }); DefMacro('\AtEndDocument{}', sub { PushValue('@at@end@document', $_[1]->unlist); }); # Like "#body", # But more complicated due to id, at begin/end document and so forth. # AND, lower-level so that we can cope with common errors at document end. DefConstructorI(T_CS('\begin{document}'), undef, sub { my ($document, %props) = @_; my $id = ToString($props{id}); if (my $docel = $document->findnode('/ltx:document')) { # Already (auto) created? $document->setAttribute($docel, 'xml:id' => $id) if $id; } else { $document->openElement('ltx:document', 'xml:id' => $id); } }, afterDigest => sub { my ($stomach, $whatsit) = @_; $stomach->beginMode('text'); DefMacroI('\@currenvir', undef, 'document'); AssignValue(current_environment => 'document'); $_[1]->setProperty(id => Expand(T_CS('\thedocument@ID'))); my @boxes = (); if (my $ops = LookupValue('@document@preamble@atend')) { push(@boxes, $stomach->digest(Tokens(@$ops))); } if (my $ops = LookupValue('@at@begin@document')) { push(@boxes, $stomach->digest(Tokens(@$ops))); } AssignValue(inPreamble => 0); # atbegin is still (sorta) preamble if (my $ops = LookupValue('@document@preamble@afterend')) { push(@boxes, $stomach->digest(Tokens(@$ops))); } $_[1]->setFont(LookupValue('font')); # Start w/ whatever font was last selected. return @boxes; }); # \document is used directly in e.g. expl3.sty Let(T_CS('\document'), T_CS('\begin{document}'), 'global'); DefConstructorI(T_CS('\end{document}'), undef, sub { my ($document) = @_; $document->closeElement('ltx:document'); }, beforeDigest => sub { my ($stomach) = @_; my @boxes = (); if (my $ops = LookupValue('@at@end@document')) { push(@boxes, $stomach->digest(Tokens(@$ops))); } # Should we try to indent the last paragraph? If so, it goes like this: push(@boxes, $stomach->digest(T_CS('\normal@par'))); # Now we check whether we're down to the last stack frame. # It is common for unclosed { or even environments # and we want to at least compress & avoid unnecessary errors & warnings. my $nframes = $STATE->getFrameDepth; my $ifstack; if ($STATE->isValueBound('current_environment', 0) && ($STATE->valueInFrame('current_environment', 0) eq 'document') && (!($ifstack = $STATE->lookupValue('if_stack')) || !$$ifstack[0])) { } # OK! else { my @lines = (); while ((!$STATE->isValueBound('current_environment', 0) || ($STATE->valueInFrame('current_environment', 0) ne 'document')) && ($STATE->getFrameDepth > 0)) { # my $nonbox = $STATE->valueInFrame('groupNonBoxing',0) || 0; my $tok = $STATE->valueInFrame('groupInitiator', 0) || ''; my $loc = $STATE->valueInFrame('groupInitiatorLocator', 0); $loc = defined $loc ? ToString($loc) : ''; my $env = $STATE->isValueBound('current_environment', 0) && $STATE->valueInFrame('current_environment', 0); if ($env) { push(@lines, "Environment $env opened by " . ToString($tok) . ' ' . $loc); } else { # but unclosed { is so common and latex itself doesn't Error! push(@lines, "Group opened by " . ToString($tok) . ' ' . $loc); } $STATE->popFrame; } while (($ifstack = $STATE->lookupValue('if_stack')) && $$ifstack[0]) { my $frame = $STATE->shiftValue('if_stack'); push(@lines, "Conditional " . ToString($$frame{token}) . "started " . ToString($$frame{start})); } Warn('unexpected', '\end{document}', $stomach, "Attempt to end document with open groups, environments or conditionals", @lines); } $stomach->endMode('text'); # ???? don't push? or what # if (my $ops = LookupValue('@after@end@document')) { # push(@boxes, Digest(Tokens(@$ops))); } $stomach->getGullet->flush; return @boxes; }); # \enddocument is used directly in e.g. standalone.cls Let(T_CS('\enddocument'), T_CS('\end{document}'), 'global'); #********************************************************************** # C.3. Sentences and Paragraphs #********************************************************************** #====================================================================== # C.3.1 Making Sentences #====================================================================== # quotes; should these be handled in DOM/construction? # dashes: We'll need some sort of Ligature analog, or something like # Omega's OTP, to combine sequences of "-" into endash, emdash, # Perhaps it also applies more semantically? # Such as interpreting certain sequences as section headings, # or math constructs. # Spacing; in TeX.pool.ltxml # Special Characters; in TeX.pool.ltxml # Logos # \TeX is in TeX.pool.ltxml ##DefConstructorI('\LaTeX', undef, 'LaTeX'); ##DefConstructorI('\LaTeXe', undef, 'LaTeX2e'); DefMacroI('\LaTeX', undef, 'LaTeX'); DefMacroI('\LaTeXe', undef, 'LaTeX2e'); DefMacroI('\fmtname', undef, 'LaTeX2e'); DefMacroI('\fmtversion', undef, '2018/12/01'); DefMacroI('\today', undef, sub { ExplodeText(today()); }); # Use fonts (w/ special flag) to propogate emphasis as a font change, # but preserve it's "emph"-ness. DefConstructor('\emph{}', "#1", mode => 'text', bounded => 1, font => { emph => 1 }, alias => '\emph', afterConstruct => sub { $_[0]->maybeCloseElement('ltx:emph'); }, beforeDigest => sub { DefMacroI('\f@shape', undef, (ToString(Tokens(Expand(T_CS('\f@shape')))) eq 'it' ? 'n' : 'it')); }); Tag('ltx:emph', autoClose => 1); #====================================================================== # C.3.2 Making Paragraphs #====================================================================== # \noindent, \indent, \par in TeX.pool.ltxml Let('\@@par', '\par'); # Style parameters # \parindent, \baselineskip, \parskip alreadin in TeX.pool.ltxml DefPrimitive('\linespread{}', undef); # ? DefMacro('\@noligs', ''); DefConditional('\if@endpe'); DefMacro('\@doendpe', ''); DefMacro('\@bsphack', '\relax'); # what else? DefMacro('\@esphack', '\relax'); DefMacro('\@Esphack', '\relax'); #====================================================================== # C.3.3 Footnotes #====================================================================== NewCounter('footnote'); DefMacroI('\thefootnote', undef, '\arabic{footnote}'); NewCounter('mpfootnote'); DefMacroI('\thempfn', undef, '\thefootnote'); DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); DefMacroI('\footnotetyperefname', undef, 'footnote'); sub makeNoteTags { my ($counter, $mark, $tag) = @_; if ($tag) { return ( RefStepID($counter), mark => $mark || $tag, tags => Digest(T_BEGIN, T_CS('\def'), T_CS('\the' . $counter), T_BEGIN, Revert($tag), T_END, T_CS('\def'), T_CS('\typerefnum@' . $counter), T_BEGIN, T_CS('\\' . $counter . 'typerefname'), T_SPACE, Revert($tag), T_END, Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)), T_END)); } else { return (RefStepCounter($counter), mark => $mark || DigestText(T_CS('\the' . $counter))); } } DefMacroI('\ext@footnote', undef, undef); DefConstructor('\lx@note[]{}[]{}', "^" . "#tags" . "#4" . "", mode => 'text', bounded => 1, beforeDigest => sub { reenterTextMode(1); Digest('\normalfont'); }, properties => sub { my $type = ToString($_[2]); (role => $type, list => DigestText(T_CS('\ext@' . $type)), makeNoteTags($type, $_[1], $_[3])); }, reversion => ''); DefConstructor('\lx@notemark[]{}[]', "" . "#tags" . "", mode => 'text', properties => sub { my $type = ToString($_[2]); (role => $type . 'mark', list => DigestText(T_CS('\ext@' . $type)), makeNoteTags($type, $_[1], $_[3])); }, reversion => ''); DefConstructor('\lx@notetext[]{}[]{}', "#4", mode => 'text', properties => sub { my $type = ToString($_[2]); (role => $type . 'text', makeNoteTags($type, $_[1], $_[3] || Digest(T_CS('\the' . $type)))); }, reversion => ''); DefMacro('\footnote', '\lx@note{footnote}'); DefMacro('\footnotemark', '\lx@notemark{footnote}'); DefMacro('\footnotetext', '\lx@notetext{footnote}'); Tag('ltx:note', afterClose => \&relocateFootnote); # Find any pairs of footnotemark & footnotetext; # Move the contents of the text to the mark, removing the text node. sub relocateFootnote { my ($document, $node) = @_; if (($node->getAttribute('role') || '') =~ /^(\w+?)text$/) { my $notetype = $1; # Eg "footnote", "endnote",... if (my $mark = $node->getAttribute('mark')) { foreach my $marknote ($document->findnodes(".//ltx:note[\@role='${notetype}mark'][\@mark='$mark']")) { relocateFootnote_aux($document, $notetype, $marknote, $node); } } } elsif (($node->getAttribute('role') || '') =~ /^(\w+?)mark$/) { my $notetype = $1; # Eg "footnote", "endnote",... if (my $mark = $node->getAttribute('mark')) { foreach my $textnote ($document->findnodes(".//ltx:note[\@role='${notetype}text'][\@mark='$mark']")) { relocateFootnote_aux($document, $notetype, $node, $textnote); } } } return; } # Move the contents of the $textnote to the $marknote, remove $textnote. sub relocateFootnote_aux { my ($document, $notetype, $marknote, $textnote) = @_; $textnote->parentNode->removeChild($textnote); $document->appendClone($marknote, $textnote->childNodes); $document->setAttribute($marknote, role => $notetype); if (my $labels = $textnote->getAttribute('labels')) { GenerateID($document, $marknote); $document->setAttribute($marknote, labels => $labels); } return; } # Style parameters DefRegister('\footnotesep' => Dimension(0)); DefPrimitiveI('\footnoterule', undef, undef); #====================================================================== # C.3.4 Accents and Special Symbols #====================================================================== # See TeX.pool.ltxml # See Section 3.3.2 Mathematical Symbols, below # Should this be here? DefMath('\mathring{}', "\x{030A}", operator_role => 'OVERACCENT'); #********************************************************************** # C.4 Sectioning and Table of Contents #********************************************************************** #====================================================================== # C.4.1 Sectioning Commands. #====================================================================== # Note that LaTeX allows fairly arbitrary stuff in \the, 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) { # No number, not in TOC (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'), T_BEGIN, $type->unlist, T_END, T_BEGIN, T_END); } elsif ((($level ne '') && ($level > CounterValue('secnumdepth')->valueOf)) || LookupValue('no_number_sections')) { # No number, but in TOC (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@unnumbered@section'), T_BEGIN, $type->unlist, T_END, T_BEGIN, T_OTHER('toc'), T_END); } else { # Number and in TOC (T_CS('\par'), T_CS('\@startsection@hook'), T_CS('\\@@numbered@section'), T_BEGIN, $type->unlist, T_END, T_BEGIN, T_OTHER('toc'), T_END); } }, locked => 1); # Not sure if this is best, but if no explicit \section'ing... #### Tag('ltx:section',autoOpen=>1); DefConstructor('\@@numbered@section{} Undigested OptionalUndigested Undigested', sub { my ($document, $type, $inlist, $toctitle, $title, %props) = @_; my $id = $props{id}; if (my $asif = $props{backmatterelement}) { $document->setNode($document->find_insertion_point($asif)); } $document->openElement("ltx:" . ToString($type), 'xml:id' => CleanID($id), inlist => ToString($inlist)); if (my $tags = $props{tags}) { $document->absorb($tags); } $document->insertElement('ltx:title', $props{title}); $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; }, properties => sub { my ($stomach, $type, $inlist, $toctitle, $title) = @_; MaybePeekLabel(); $type = ToString($type); my %props = RefStepCounter($type); my $xtitle = Digest(Invocation(T_CS('\lx@format@title@@'), $type, $title)); my $xtoctitle = Digest(Invocation(T_CS('\lx@format@toctitle@@'), $type, $toctitle || $title)); if ($type eq 'appendix') { $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); } $props{title} = $xtitle; $props{toctitle} = $xtoctitle if $xtoctitle && $xtoctitle->unlist && (ToString($xtoctitle) ne ToString($xtitle)); return %props; }); # No tags, at all? Consider... DefConstructor('\@@unnumbered@section{} Undigested OptionalUndigested Undigested', sub { my ($document, $type, $inlist, $toctitle, $title, %props) = @_; my $id = $props{id}; if (my $asif = $props{backmatterelement}) { $document->setNode($document->find_insertion_point($asif)); } $document->openElement("ltx:" . ToString($type), 'xml:id' => CleanID($id), inlist => ToString($inlist)); $document->insertElement('ltx:title', $props{title}); $document->insertElement('ltx:toctitle', $props{toctitle}) if $props{toctitle}; }, properties => sub { my ($stomach, $type, $inlist, $toctitle, $title) = @_; MaybePeekLabel(); $type = ToString($type); my %props = RefStepID($type); if ($type eq 'appendix') { $props{backmatterelement} = LookupMapping('BACKMATTER_ELEMENT', 'ltx:' . $type); } $props{title} = Digest(T_CS('\@hidden@bgroup'), Invocation(T_CS('\lx@format@title@font@@'), $type), $title, T_CS('\@hidden@egroup')); $props{toctitle} = $toctitle && Digest(T_CS('\@hidden@bgroup'), Invocation(T_CS('\lx@format@toctitle@font@@'), $type), $toctitle, T_CS('\@hidden@egroup')); return %props; }); #====================================================================== # C.4.2 The Appendix #====================================================================== # Handled in article,report or book. DefMacroI('\appendixname', undef, 'Appendix'); DefMacroI('\appendixesname', undef, 'Appendixes'); # Class files should define \@appendix to call this as startAppendices('section') or chapter... # counter is also the element name! sub startAppendices { beginAppendices(@_); return; } sub beginAppendices { my ($counter) = @_; Let('\lx@save@theappendex', '\the' . $counter, 'global'); Let('\lx@save@theappendex@ID', '\the' . $counter . '@ID', 'global'); Let('\lx@save@appendix', T_CS('\\' . $counter), 'global'); Let('\lx@save@@appendix', T_CS('\@appendix'), 'global'); AssignMapping('BACKMATTER_ELEMENT', 'ltx:appendix' => 'ltx:' . $counter); if (LookupDefinition(T_CS('\c@chapter')) # Has \chapter defined && ($counter ne 'chapter')) { # And appendices are below the chapter level. NewCounter($counter, 'chapter', idprefix => 'A'); DefMacroI('\the' . $counter, undef, '\thechapter.\Alph{' . $counter . '}', scope => 'global'); } else { NewCounter($counter, 'document', idprefix => 'A'); DefMacroI('\the' . $counter, undef, '\Alph{' . $counter . '}', scope => 'global'); } AssignMapping('counter_for_type', appendix => $counter); Let(T_CS('\\' . $counter), T_CS('\@@appendix'), 'global'); Let(T_CS('\@appendix'), T_CS('\relax'), 'global'); return; } sub endAppendices { if (my $counter = LookupMapping('BACKMATTER_ELEMENT', 'ltx:appendix')) { $counter =~ s/^ltx://; Let('\the' . $counter, '\lx@save@theappendex', 'global'); Let('\the' . $counter . '@ID', '\lx@save@theappendex@ID', 'global'); Let(T_CS('\\' . $counter), '\lx@save@appendix', 'global'); Let(T_CS('\@appendix'), '\lx@save@@appendix', 'global'); } return; } DefMacroI('\\@@appendix', undef, '\@startsection{appendix}{0}{}{}{}{}'); #====================================================================== # C.4.3 Table of Contents #====================================================================== # Insert stubs that will be filled in during post processing. DefMacroI('\contentsname', undef, 'Contents'); DefConstructorI('\tableofcontents', undef, "#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, "#name", properties => sub { (name => Digest(T_CS('\listfigurename'))); }); DefMacroI('\listtablename', undef, 'List of Tables'); DefConstructorI('\listoftables', undef, "#name", properties => sub { (name => Digest(T_CS('\listtablename'))); }); DefPrimitive('\numberline{}{}', undef); DefPrimitive('\addtocontents{}{}', undef); DefConstructor('\addcontentsline{}{}{}', sub { my ($document, $inlist, $type, $title) = @_; # Note that the node can be inlist $inlist. # Could conceivably want to add $title as toctitle??? if (my $savenode = $document->floatToLabel) { my $node = $document->getNode; my $lists = $node->getAttribute('inlist'); $inlist = ToString($inlist); $document->setAttribute($node, inlist => ($lists ? $lists . ' ' . $inlist : $inlist)); $document->setNode($savenode); } }); #====================================================================== # C.4.4 Style registers #====================================================================== NewCounter('tocdepth'); #********************************************************************** # C.5 Classes, Packages and Page Styles #********************************************************************** #====================================================================== # C.5.1 Document Class #====================================================================== # Style Parameters DefRegister('\bibindent' => Dimension(0)); DefRegister('\columnsep' => Dimension(0)); DefRegister('\columnseprule' => Dimension(0)); DefRegister('\mathindent' => Dimension(0)); #====================================================================== # C.5.2 Packages #====================================================================== # We'll prefer to load package.pm, but will try package.sty or # package.tex (the latter being unlikely to work, but....) # See Stomach.pm for details # Ignorable packages ?? # pre-defined packages?? DefMacroI('\@clsextension', undef, 'cls'); DefMacroI('\@pkgextension', undef, 'sty'); Let('\@currext', '\@empty'); Let('\@currname', '\@empty'); Let('\@classoptionslist', '\relax'); # Note that there are variables used in Package.pmfor these, # but they are NOT tied to these macros. Do they need to be? DefMacroI('\@declaredoptions', undef, undef); DefMacroI('\@curroptions', undef, undef); DefConstructor('\usepackage OptionalSemiverbatim Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\usepackage'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $options = $whatsit->getArg(1); my $packages = $whatsit->getArg(2); $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())]; for my $pkg (split(',', ToString($packages))) { $pkg =~ s/\s+//g; next if !$pkg || $pkg =~ /^%/; RequirePackage($pkg, options => $options); } return }); DefConstructor('\RequirePackage OptionalSemiverbatim Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\RequirePackage'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $options = $whatsit->getArg(1); my $packages = $whatsit->getArg(2); $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())]; for my $pkg (split(',', ToString($packages))) { $pkg =~ s/\s+//g; next if !$pkg || $pkg =~ /^%/; RequirePackage($pkg, options => $options); } return }); DefConstructor('\LoadClass OptionalSemiverbatim Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\LoadClass'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $options = $whatsit->getArg(1); my $class = ToString($whatsit->getArg(2)); $class =~ s/\s+//g; $options = [($options ? split(/\s*,\s*/, (ToString($options))) : ())]; LoadClass($class, options => $options); return; }); # Related internal macros for package definition # Internals used in Packages DefMacro('\NeedsTeXFormat{}[]', Tokens()); DefPrimitive('\ProvidesClass{}[]', sub { my ($stomach, $class, $version) = @_; DefMacroI("\\ver@" . ToString($class) . ".cls", undef, $version || Tokens(), scope => 'global'); return; }); # Note that these, like LaTeX, define macros like \var@mypkg.sty to give the version info. DefMacro('\ProvidesPackage{}[]', sub { my ($stomach, $package, $version) = @_; DefMacroI("\\ver@" . ToString($package) . ".sty", undef, $version || Tokens(), scope => 'global'); return; }); DefMacro('\ProvidesFile{}[]', sub { my ($stomach, $file, $version) = @_; DefMacroI("\\ver@" . ToString($file), undef, $version || Tokens(), scope => 'global'); return; }); DefPrimitive('\DeclareOption{}{}', sub { my ($stomach, $option, $code) = @_; ((ToString($option) eq '*') ? DeclareOption(undef, $code) : DeclareOption(ToString($option), $code)); }); DefPrimitive('\PassOptionsToPackage{}{}', sub { my ($stomach, $name, $options) = @_; $name = ToString($name); $name =~ s/\s+//g; PassOptions($name, 'sty', split(/\s*,\s*/, ToString(Expand($options)))); }); DefPrimitive('\PassOptionsToClass{}{}', sub { my ($stomach, $name, $options) = @_; $name = ToString($name); $name =~ s/\s+//g; PassOptions($name, 'cls', split(/\s*,\s*/, ToString(Expand($options)))); }); DefConstructor('\RequirePackageWithOptions Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\RequirePackage'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $package = ToString($whatsit->getArg(1)); $package =~ s/\s+//g; RequirePackage($package, withoptions => 1); return; }); DefConstructor('\LoadClassWithOptions Semiverbatim []', "", beforeDigest => sub { onlyPreamble('\LoadClassWithOptions'); }, afterDigest => sub { my ($stomach, $whatsit) = @_; my $class = ToString($whatsit->getArg(1)); $class =~ s/\s+//g; LoadClass($class, withoptions => 1); return; }); DefPrimitive('\@onefilewithoptions {} [][] {}', sub { my ($stomach, $name, $option1, $option2, $ext) = @_; InputDefinitions(ToString(Expand($name)), type => ToString(Expand($ext)), options => $option1); return; }); DefMacroI('\CurrentOption', undef, Tokens()); DefPrimitiveI('\OptionNotUsed', undef, sub { if (my $option = ToString(Expand(T_CS('\CurrentOption')))) { my $type = ToString(Expand(T_CS('\@currext'))); if ($type eq 'cls') { PushValue('@unusedoptionlist', $option); } } return; }); DefPrimitiveI('\@unknownoptionerror', undef, sub { if (my $option = ToString(Expand(T_CS('\CurrentOption')))) { my $name = ToString(Expand(T_CS('\@currname'))); my $type = ToString(Expand(T_CS('\@currext'))); Info('unexpected', $option, $_[0], "Unexpected option '$option' passed to $name.$type"); } return; }); DefPrimitive('\ExecuteOptions{}', sub { my ($gullet, $options) = @_; ExecuteOptions(split(/\s*,\s*/, ToString(Expand($options)))); }); DefPrimitive('\ProcessOptions OptionalMatch:*', sub { my ($stomach, $star) = @_; ProcessOptions(($star ? (inorder => 1) : ())); }); DefMacro('\@options', '\ProcessOptions*'); DefMacro('\AtEndOfPackage{}', sub { my ($gullet, $code) = @_; my $name = ToString(Expand(T_CS('\@currname'))); my $type = ToString(Expand(T_CS('\@currext'))); AddToMacro(T_CS('\\' . $name . '.' . $type . '-h@@k'), $code); }); DefMacro('\@ifpackageloaded', '\@ifl@aded\@pkgextension'); 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('\@ptionlist {}', '\@ifundefined{opt@#1}\@empty{\csname opt@#1\endcsname}'); DefMacro('\g@addto@macro DefToken {}', sub { AddToMacro($_[1], $_[2]); }); DefMacro('\addto@hook DefToken {}', '#1\expandafter{\the#1#2}'); # Alas, we're not tracking versions, so we'll assume it's "later" & cross fingers.... DefMacro('\@ifpackagelater{}{}{}{}', '#3'); DefMacro('\@ifclasslater{}{}{}{}', '#3'); Let('\AtEndOfClass', '\AtEndOfPackage'); DefMacro('\AtBeginDvi {}', Tokens()); RawTeX(<<'EoTeX'); \def\@ifl@t@r#1#2{% \ifnum\expandafter\@parse@version@#1//00\@nil<% \expandafter\@parse@version@#2//00\@nil \expandafter\@secondoftwo \else \expandafter\@firstoftwo \fi} \def\@parse@version@#1{\@parse@version0#1} \def\@parse@version#1/#2/#3#4#5\@nil{% \@parse@version@dash#1-#2-#3#4\@nil } \def\@parse@version@dash#1-#2-#3#4#5\@nil{% \if\relax#2\relax\else#1\fi#2#3#4 } EoTeX #====================================================================== # Somewhat related I/O stuff DefMacro('\filename@parse{}', sub { my ($gullet, $pathname) = @_; my ($dir, $name, $ext) = pathname_split(ToString(Expand($pathname))); $dir .= '/' if $dir; DefMacroI('\filename@area', undef, Tokens(ExplodeText($dir))); DefMacroI('\filename@base', undef, Tokens(ExplodeText($name))); DefMacroI('\filename@ext', undef, ($ext ? Tokens(ExplodeText($ext)) : T_CS('\relax'))); }); DefMacroI('\@filelist', undef, Tokens()); DefMacro('\@addtofilelist{}', sub { DefMacroI('\@filelist', undef, Expand(T_CS('\@filelist'), T_OTHER(','), $_[1]->unlist)); }); #====================================================================== # C.5.3 Page Styles #====================================================================== # Ignored NewCounter('page'); DefPrimitive('\pagestyle{}', undef); DefPrimitive('\thispagestyle{}', undef); DefPrimitive('\markright{}', undef); DefPrimitive('\markboth{}{}', undef); DefPrimitiveI('\leftmark', undef, undef); DefPrimitiveI('\rightmark', undef, undef); DefPrimitive('\pagenumbering{}', undef); DefMacro('\@mkboth', '\@gobbletwo'); # default, just in case DefMacro('\ps@empty', '\let\@mkboth\@gobbletwo\let\@oddhead\@empty\let\@oddfoot\@empty' . '\let\@evenhead\@empty\let\@evenfoot\@empty'); DefMacro('\ps@plain', '\let\@mkboth\@gobbletwo' . '\let\@oddhead\@empty\def\@oddfoot{\reset@font\hfil\thepage' . '\hfil}\let\@evenhead\@empty\let\@evenfoot\@oddfoot'); Let(T_CS('\@leftmark'), T_CS('\@firstoftwo')); Let(T_CS('\@rightmark'), T_CS('\@secondoftwo')); # In normal latex, these should \clearpage; at least we want new paragraph? # Optional arg is sortof a heading, but w/o any particular styling(?) DefMacro('\twocolumn[]', '\ifx.#1.\else\par\noindent#1\fi\par'); DefMacro('\onecolumn', '\par'); DefMacro('\@topnewpage{}', '#1'); # 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 Let('\@title', '\@empty'); DefMacro('\title{}', '\def\@title{#1}\@add@frontmatter{ltx:title}{#1}', locked => 1); DefMacro('\@date', '\@empty'); DefMacro('\date{}', '\def\@date{#1}' . '\@add@frontmatter{ltx:date}[role=creation,' . 'name={\@ifundefined{datename}{}{\datename}}]{#1}'); DefConstructor('\person@thanks{}', "^ #1", alias => '\thanks', mode => 'text'); DefConstructor('\@personname{}', "#1", beforeDigest => sub { Let('\thanks', '\person@thanks'); }, bounded => 1, mode => 'text'); # Sanitize person names for (obvious) punctuation abuse at start+end Tag('ltx:personname', afterClose => sub { my ($document, $node) = @_; if (my $first = $node->firstChild) { if ($first->nodeType == XML_TEXT_NODE) { my $first_text = $first->data; my $new_text = $first_text; $new_text =~ s/^\W+//; if ($first_text ne $new_text) { $first->setData($new_text); } } } if (my $last = $node->lastChild) { if ($last->nodeType == XML_TEXT_NODE) { my $last_text = $last->data; my $new_text = $last_text; $new_text =~ s/\W+$//; if ($last_text ne $new_text) { $last->setData($new_text); } } } return; }); DefConstructorI('\and', undef, " and "); AssignValue(NUMBER_OF_AUTHORS => 0); DefPrimitive('\lx@count@author', sub { AssignValue(NUMBER_OF_AUTHORS => LookupValue('NUMBER_OF_AUTHORS') + 1, 'global') }); DefMacro('\lx@author{}', '\lx@count@author' . '\@add@frontmatter{ltx:creator}[role=author]{\lx@author@prefix\@personname{#1}}'); DefConstructor('\lx@@@contact{}{}', "^ #2"); DefMacro('\lx@contact{}{}', '\@add@to@frontmatter{ltx:creator}{\lx@@@contact{#1}{#2}}'); DefMacro('\lx@author@sep', '\qquad'); DefMacro('\lx@author@conj', '\qquad'); DefConstructor('\lx@author@prefix', sub { my ($document) = @_; my $node = $document->getElement; my $nauthors = LookupValue('NUMBER_OF_AUTHORS'); my $i = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') }); if ($i <= 1) { } elsif ($i == $nauthors) { $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); } else { $document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); } }); DefMacro('\@author', '\@empty'); DefMacro('\author[]{}', '\def\@author{#2}\lx@make@authors@anded{#2}', locked => 1); DefMacro('\lx@make@authors@anded{}', sub { andSplit(T_CS('\lx@author'), $_[1]); }); DefPrimitive('\ltx@authors@oneline', sub { AssignMapping('DOCUMENT_CLASSES', ltx_authors_1line => 1); return; }); DefPrimitive('\ltx@authors@multiline', sub { AssignMapping('DOCUMENT_CLASSES', ltx_authors_multiline => 1); return; }); DefMacro('\@add@conversion@date', '\@add@frontmatter{ltx:date}[role=creation]{\today}'); # Doesn't produce anything (we're already inserting frontmatter), # But, it does make the various frontmatter macros into no-ops. DefMacroI('\maketitle', undef, '\@startsection@hook' . '\global\let\thanks\relax' . '\global\let\maketitle\relax' . '\global\let\@maketitle\relax' . '\global\let\@thanks\@empty' . '\global\let\@author\@empty' . '\global\let\@date\@empty' . '\global\let\@title\@empty' . '\global\let\title\relax' . '\global\let\author\relax' . '\global\let\date\relax' . '\global\let\and\relax', locked => 1); DefMacro('\@thanks', '\@empty'); DefMacro('\thanks{}', '\def\@thanks{#1}\lx@make@thanks{#1}'); DefConstructor('\lx@make@thanks{}', "#1"); # 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}','#body'); # However, we don't want to place the environment directly where # we found it, but we want to add it to frontmatter. This requires capturing the # recently digested list and storing it in the frontmatter structure. # The really messy stuff comes from the way authors -- and style designers -- misuse it. # Basic LaTeX wants it to be an environment WITHIN the document environment, # and AFTER the \maketitle. # However, since all it really does is typeset "Abstract" in bold, it allows: # \abstract stuff... # without even an \endabstract! We MUST know when the abstract ends, so we've got # to recognize when we've moved on to other stuff... \sections at the VERY LEAST. # Additional complications come from certain other classes and styles that # redefine abstract to take the text as an argument. And some treat it # like \title, \author, and such, that are expected to appear in the preamble!! # The treatment below allows an abstract environment in the preamble, # (even though straight latex doesn't) but does not cover the 1-arg case in preamble! # # Probably there are other places (eg in titlepage?) that should force the close?? DefEnvironment('{abstract}', '', afterDigestBegin => sub { AssignValue(inPreamble => 0); }, afterDigest => sub { my $frontmatter = LookupValue('frontmatter'); push(@{ $$frontmatter{'ltx:abstract'} }, ['ltx:abstract', { name => Digest(Tokens(T_CS('\format@title@abstract'), T_BEGIN, T_CS('\abstractname'), T_END)) }, @LaTeXML::LIST]); DefMacroI('\maybe@end@abstract', undef, Tokens(), scope => 'global'); return; }, afterConstruct => sub { if (LookupValue('standalone_abstract_in_frontmatter')) { insertFrontMatter(@_); } 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('\g@addto@macro'), T_CS('\@startsection@hook'), T_CS('\maybe@end@abstract'), T_CS('\begin{abstract}'))); }, locked => 1); DefMacro('\abstract@onearg{}', '\begin{abstract}#1\end{abstract}\let\endabstract\relax'); DefMacroI('\maybe@end@abstract', undef, '\endabstract'); DefMacroI('\abstractname', undef, 'Abstract'); DefMacro('\format@title@abstract{}', '#1'); # Hmm, titlepage is likely to be hairy, low-level markup, # without even title, author, etc, specified as such! # Hmm, should this even redefine author, title, etc so that they # are simply output? # This is horrible hackery; What we really need, I think, is the # ability to bind some sort of "Do 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

...

# 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}', '#body', beforeDigest => sub { Let('\centering', '\relax'); AddToMacro(T_CS('\maketitle'), T_CS('\unwind@titlepage')); DefEnvironmentI('abstract', undef, '#body'); Info('unexpected', 'titlepage', $_[0], "When using titlepage, Frontmatter will not be well-structured"); return; }, beforeDigestEnd => sub { Digest(T_CS('\maybe@end@titlepage')); }, locked => 1, mode => 'text'); Tag('ltx:titlepage', autoClose => 1); DefConstructorI('\maybe@end@titlepage', undef, sub { my ($document) = @_; $document->maybeCloseElement('ltx:titlepage'); }); DefConstructorI('\unwind@titlepage', undef, sub { my ($document) = @_; if (my $titlepage = $document->maybeCloseElement('ltx:titlepage')) { $document->unwrapNodes($titlepage); } }); DefMacro('\sectionmark{}', Tokens()); DefMacro('\subsectionmark{}', Tokens()); DefMacro('\subsubsectionmark{}', Tokens()); DefMacro('\paragraphmark{}', Tokens()); DefMacro('\subparagraphmark{}', Tokens()); DefMacroI('\@oddfoot', undef, Tokens()); DefMacroI('\@oddhed', undef, Tokens()); DefMacroI('\@evenfoot', undef, Tokens()); DefMacroI('\@evenfoot', undef, Tokens()); #********************************************************************** # C.6 Displayed Paragraphs #********************************************************************** DefEnvironment('{center}', sub { $_[0]->maybeCloseElement('ltx:p'); # this starts a new vertical block aligningEnvironment('center', 'ltx_centering', @_); }, # aligning will take care of \\\\ "rows" 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]', "\n", reversion => Tokens(T_CS("\\\\"), T_CR)); DefEnvironment('{quote}', '#body', beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') }, mode => 'text'); DefEnvironment('{quotation}', '#body', beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') }, mode => 'text'); # NOTE: Handling of \\ within these environments? DefEnvironment('{verse}', '#body', beforeDigest => sub { Let('\\\\', '\@block@cr'); Let('\par', '\@block@cr') }, mode => 'text'); #====================================================================== # C.6.2 List-Making environments #====================================================================== Tag('ltx:item', autoClose => 1, autoOpen => 1); Tag('ltx:inline-item', autoClose => 1, autoOpen => 1); # These are for the (not quite legit) case where \item appears outside # of an itemize, enumerate, etc, environment. # DefConstructor('\item[]', # "?&defined(#1)(#1)"); # DefConstructor('\subitem[]', # "?&defined(#1)(#1)"); # DefConstructor('\subsubitem[]', # "?&defined(#1)(#1)"); # Or maybe best just to do \par ? DefMacro('\item[]', '\par'); DefMacro('\subitem[]', '\par'); DefMacro('\subsubitem[]', '\par'); AssignValue('@itemlevel' => 0, 'global'); AssignValue('enumlevel' => 0, 'global'); AssignValue('@desclevel' => 0, 'global'); # protection against lower-level code... DefConditional('\if@noitemarg'); DefMacro('\@item', '\item'); # Hopefully no circles... DefMacro('\@itemlabel', ''); # Maybe needs to be same as \item will be using? # Prepare for an list (itemize/enumerate/description/etc) # by determining the right counter (level) # and binding the right \item ( \$type@item, if $type is defined) sub beginItemize { my ($type, $counter, %options) = @_; # The list-type and level of the *containing* list (if any!) my $outercounter = LookupValue('itemcounter'); my $outerlevel = $outercounter && (LookupValue($outercounter . 'level') || 0); $counter = '@item' unless $counter; my $listlevel = (LookupValue('itemization_level') || 0) + 1; # level for this list overall my $level = (LookupValue($counter . 'level') || 0) + 1; # level for lists of specific type AssignRegister('\itemsep' => LookupDimension('\lx@default@itemsep')); AssignValue('itemization_level' => $listlevel); AssignValue($counter . 'level' => $level); AssignValue(itemization_items => 0); my $listpostfix = ToString(Tokens(roman($listlevel))); my $postfix = ToString(Tokens(roman($level))); my $usecounter = ($options{nolevel} ? $counter : $counter . $postfix); Let('\item' => "\\" . $type . '@item') if defined $type; Let('\par', '\normal@par'); # In case within odd environment. DefMacroI('\@listctr', undef, Tokens(Explode($usecounter))); # Now arrange that this list's id's are relative to the current (outer) item (if any) # And that the items within this list's id's are relative to this (new) list. ## if(! LookupDefinition(T_CS('\@listcontext'))){ ## Let(T_CS('\@listcontext'), T_CS('\@currentlabel')); } AssignValue(itemcounter => $usecounter); my $listcounter = '@itemize' . $listpostfix; if (!LookupValue('\c@' . $listcounter)) { # Create new list counters as needed NewCounter($listcounter); } #, $outercounter.ToString(Tokens(roman($outerlevel))), if ($outercounter) { # Make this list's ID relative to outer list's ID my $outerusecounter = $outercounter . ToString(Tokens(roman($outerlevel))); DefMacroI('\the' . $listcounter . '@ID', undef, '\the' . $outerusecounter . '@ID.I' . '\arabic{' . $listcounter . '}'); # AND reset this list's counter when the outer item is stepped my $x; AssignValue("\\cl\@$outerusecounter" => Tokens(T_CS($listcounter), (($x = LookupValue('\cl@' . $outerusecounter)) ? $x->unlist : ())), 'global'); } # format the id of \item's relative to the id of this list DefMacroI('\the' . $usecounter . '@ID', undef, '\the' . $listcounter . '@ID.i' . '\@' . $usecounter . '@ID'); my $series; if ($series = $options{series}) { $series = ToString($series); } if (my $start = $options{start}) { SetCounter($usecounter, $start); AddToCounter($usecounter, Number(-1)); } elsif (my $s = $options{resume} || $options{'resume*'}) { if (($s = ToString($s)) ne 'noseries') { $series = ToString($s); SetCounter($usecounter, LookupValue('enumitem_series_' . $series . '_last') || Number(0)); } } else { ResetCounter($usecounter); } ## return RefStepCounter('@itemize' . $listpostfix); } return (RefStepCounter('@itemize' . $listpostfix), counter => $usecounter, series => $series); } # So end can save counter value # These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements # Only create the 1st level (so that binding style can start numbering 'within' appropriately) # Additional ones created by need. NewCounter('@itemizei', 'section', idprefix => 'I'); # Create id, and tags for an itemize type \item sub RefStepItemCounter { my ($tag) = @_; my $counter = LookupValue('itemcounter'); my $n = LookupValue('itemization_items'); AssignValue(itemization_items => $n + 1); my %attr = (); my $sep = LookupDimension('\itemsep'); if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { $attr{itemsep} = $sep; } if (defined $tag) { my @props = RefStepID($counter); if ((ref $tag) && !scalar($tag->unlist)) { # empty tag? return (@props); } else { my $ttag = (ref $tag ? $tag : T_OTHER($tag)); my $formatter = ($counter =~ /^\@desc/ ? T_CS('\descriptionlabel') : T_CS('\makelabel')); my $typename = (IsDefined(T_CS('\\' . $counter . 'name')) ? T_CS('\\' . $counter . 'name') : T_CS('\itemtyperefname')); my $tags = Digest(T_BEGIN, T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), T_CS('\def'), T_CS('\fnum@' . $counter), T_BEGIN, $formatter, T_BEGIN, Revert($tag), T_END, T_END, T_CS('\def'), T_CS('\typerefnum@' . $counter), T_BEGIN, $typename, T_SPACE, Revert($tag), T_END, Invocation(T_CS('\lx@make@tags'), T_OTHER($counter)), T_END); return (@props, ($tags ? (tags => $tags) : ()), %attr); } } else { return (RefStepCounter($counter), %attr); } } # The following two aren't used here; they're defined here so they # can be used in paralist.sty, enumerate.sty (perhaps others?) # This isn't really satisfactory. # We should record the marker used for the item, # but it really should NOT be #refnum (which should be quasi unique) # and is not \theenumi.. (which should be a counter value) sub setItemizationStyle { my ($stuff, $level) = @_; if (defined $stuff) { $level = LookupValue('@itemlevel') || 0 unless defined $level; $level = ToString(Tokens(roman($level))); DefMacroI('\labelitem' . $level, undef, $stuff); } return; } sub setEnumerationStyle { my ($stuff, $level) = @_; if (defined $stuff) { $level = LookupValue('enumlevel') || 0 unless defined $level; $level = ToString(Tokens(roman($level))); my @in = $stuff->unlist; my @out = (); my $ctr = T_OTHER('enum' . $level); while (my $t = shift(@in)) { if (Equals($t, T_BEGIN)) { push(@out, $t); my $brlevel = 1; while ($brlevel && ($t = shift(@in))) { if (Equals($t, T_BEGIN)) { $brlevel++; } elsif (Equals($t, T_END)) { $brlevel--; } push(@out, $t); } } elsif (Equals($t, T_LETTER('A'))) { DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Alph'), $ctr)); push(@out, T_CS('\theenum' . $level)); } elsif (Equals($t, T_LETTER('a'))) { DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\alph'), $ctr)); push(@out, T_CS('\theenum' . $level)); } elsif (Equals($t, T_LETTER('I'))) { DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\Roman'), $ctr)); push(@out, T_CS('\theenum' . $level)); } elsif (Equals($t, T_LETTER('i'))) { DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\roman'), $ctr)); push(@out, T_CS('\theenum' . $level)); } elsif (Equals($t, T_OTHER('1'))) { DefMacroI('\theenum' . $level, undef, Invocation(T_CS('\arabic'), $ctr)); push(@out, T_CS('\theenum' . $level)); } else { push(@out, $t); } } DefMacroI('\labelenum' . $level, undef, Tokens(T_BEGIN, @out, T_END)); } return; } # id, but NO refnum (et.al) attributes on itemize \item ... # unless the optional tag argument was given! # We'll make the from either the optional arg, or from \labelitemi.. DefMacro('\itemize@item', '\par\itemize@item@'); DefConstructor('\itemize@item@ OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefConstructor('\inline@itemize@item OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefMacro('\enumerate@item', '\par\enumerate@item@'); DefConstructor('\enumerate@item@ OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefConstructor('\inline@enumerate@item OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefMacro('\description@item', '\par\description@item@'); DefConstructor('\description@item@ OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefConstructor('\inline@description@item OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); DefEnvironment('{itemize}', "#body", properties => sub { beginItemize('itemize', '@item'); }, beforeDigestEnd => sub { Digest('\par'); }, locked => 1, mode => 'text'); DefEnvironment('{enumerate}', "#body", properties => sub { beginItemize('enumerate', 'enum'); }, beforeDigestEnd => sub { Digest('\par'); }, locked => 1, mode => 'text'); DefEnvironment('{description}', "#body", beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, properties => sub { beginItemize('description', '@desc'); }, beforeDigestEnd => sub { Digest('\par'); }, locked => 1, mode => 'text'); DefMacro('\makelabel{}', '#1'); #---------------------------------------------------------------------- # Basic itemize bits # Fake counter for itemize to give id's to ltx:item. NewCounter('@itemi', undef, idwithin => '@itemizei', idprefix => 'i'); NewCounter('@itemii', undef, idwithin => '@itemi', idprefix => 'i'); NewCounter('@itemiii', undef, idwithin => '@itemii', idprefix => 'i'); NewCounter('@itemiv', undef, idwithin => '@itemiii', idprefix => 'i'); NewCounter('@itemv', undef, idwithin => '@itemiv', idprefix => 'i'); NewCounter('@itemvi', undef, idwithin => '@itemv', idprefix => 'i'); # These are empty to make the 'refnum' go away. DefMacroI('\the@itemi', undef, ''); DefMacroI('\the@itemii', undef, ''); DefMacroI('\the@itemiii', undef, ''); DefMacroI('\the@itemiv', undef, ''); DefMacroI('\the@itemv', undef, ''); DefMacroI('\the@itemvi', undef, ''); # Formatted item tags. # Really should be in the class file, but already was here. DefMacroI('\labelitemi', undef, '\textbullet'); DefMacroI('\labelitemii', undef, '\normalfont\bfseries \textendash'); DefMacroI('\labelitemiii', undef, '\textasteriskcentered'); DefMacroI('\labelitemiv', undef, '\textperiodcentered'); # Make the fake counters point to the real labels DefMacroI('\label@itemi', undef, '\labelitemi'); DefMacroI('\label@itemii', undef, '\labelitemii'); DefMacroI('\label@itemiii', undef, '\labelitemiii'); DefMacroI('\label@itemiv', undef, '\labelitemiv'); # These hookup latexml's tagging to normal latex's \labelitemi... DefMacroI('\fnum@@itemi', undef, '{\makelabel{\label@itemi}}'); DefMacroI('\fnum@@itemii', undef, '{\makelabel{\label@itemii}}'); DefMacroI('\fnum@@itemiii', undef, '{\makelabel{\label@itemiii}}'); DefMacroI('\fnum@@itemiv', undef, '{\makelabel{\label@itemiv}}'); # These define the typerefnum form, for out-of-context \ref's # Better would language sensitive! my @pm_ordinal_suffices = ('th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'); DefMacro('\lx@poormans@ordinal{}', sub { my ($gullet, $ctr) = @_; my $n = CounterValue($ctr)->valueOf; my $string = "$n"; if ($string =~ /.*?(\d)$/) { $string .= $pm_ordinal_suffices[$1]; } T_OTHER($string); }); DefMacroI('\itemtyperefname', undef, 'item'); DefMacroI('\itemcontext', undef, '\space in \@listcontext'); DefMacroI('\itemcontext', undef, ''); # Probably would help to give a bit more context for the ii & higher? DefMacroI('\typerefnum@@itemi', undef, '\lx@poormans@ordinal{@itemi} \itemtyperefname \itemcontext'); DefMacroI('\typerefnum@@itemii', undef, '\lx@poormans@ordinal{@itemii} \itemtyperefname \itemcontext'); DefMacroI('\typerefnum@@itemiii', undef, '\lx@poormans@ordinal{@itemiii} \itemtyperefname \itemcontext'); DefMacroI('\typerefnum@@itemiv', undef, '\lx@poormans@ordinal{@itemiv} \itemtyperefname \itemcontext'); #---------------------------------------------------------------------- # Basic enumeration bits # Class file should have # NewCounter for enumi,..., # define \labelenumi,... and probably \p@enumii... # How the refnums look... (probably should be in class file, but already here) DefMacroI('\p@enumii', undef, '\theenumi'); DefMacroI('\p@enumiii', undef, '\theenumi(\theenumii)'); DefMacroI('\p@enumiv', undef, '\p@enumii\theenumiii'); # Formatting of item tags (probably should be in the class file, but already here) DefMacroI('\labelenumi', undef, '\theenumi.'); DefMacroI('\labelenumii', undef, '(\theenumii)'); DefMacroI('\labelenumiii', undef, '\theenumiii.'); DefMacroI('\labelenumiv', undef, '\theenumiv.'); # These hookup latexml's tagging to normal latex's \labelenummi... DefMacroI('\fnum@enumi', undef, '{\makelabel{\labelenumi}}'); DefMacroI('\fnum@enumii', undef, '{\makelabel{\labelenumii}}'); DefMacroI('\fnum@enumiii', undef, '{\makelabel{\labelenumiii}}'); DefMacroI('\fnum@enumiv', undef, '{\makelabel{\labelenumiv}}'); # These define the typerefnum form, for out-of-context \ref's DefMacroI('\enumtyperefname', undef, 'item'); DefMacroI('\typerefnum@enumi', undef, '\enumtyperefname~\p@enumi\theenumi \itemcontext'); DefMacroI('\typerefnum@enumii', undef, '\enumtyperefname~\p@enumii\theenumii \itemcontext'); DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\theenumiii \itemcontext'); DefMacroI('\typerefnum@enumiv', undef, '\enumtyperefname~\p@enumiv\theenumiv \itemcontext'); #DefMacroI('\typerefnum@enumi', undef, '\enumtyperefname~\p@enumi\labelenumi \itemcontext'); #DefMacroI('\typerefnum@enumii', undef, '\enumtyperefname~\p@enumii\labelenumii \itemcontext'); #DefMacroI('\typerefnum@enumiii', undef, '\enumtyperefname~\p@enumiii\labelenumiii \itemcontext'); #DefMacroI('\typerefnum@enumiv', undef, '\enumtyperefname~\p@enumiv\labelenumiv \itemcontext'); #---------------------------------------------------------------------- # Basic description list bits # Fake counter for itemize to give id's to ltx:item. NewCounter('@desci', undef, idwithin => '@itemizei', idprefix => 'i'); NewCounter('@descii', undef, idwithin => '@desci', idprefix => 'i'); NewCounter('@desciii', undef, idwithin => '@descii', idprefix => 'i'); NewCounter('@desciv', undef, idwithin => '@desciii', idprefix => 'i'); NewCounter('@descv', undef, idwithin => '@desciv', idprefix => 'i'); NewCounter('@descvi', undef, idwithin => '@descv', idprefix => 'i'); # No refnum's here, either DefMacroI('\the@desci', undef, ''); DefMacroI('\the@descii', undef, ''); DefMacroI('\the@desciii', undef, ''); DefMacroI('\the@desciv', undef, ''); DefMacroI('\the@descv', undef, ''); DefMacroI('\the@descvi', undef, ''); # These hookup latexml's numbering to normal latex's # Umm.... but they're not normally used, since \item usually gets an argument! DefMacro('\descriptionlabel{}', '\normalfont\bfseries #1'); DefMacroI('\fnum@@desci', undef, '{\descriptionlabel{}}'); DefMacroI('\fnum@@descii', undef, '{\descriptionlabel{}}'); DefMacroI('\fnum@@desciii', undef, '{\descriptionlabel{}}'); DefMacroI('\fnum@@desciv', undef, '{\descriptionlabel{}}'); DefMacroI('\desctyperefname', undef, 'item'); # Blech map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\itemtyperefname'); } qw(@itemi @itemii @itemiii @itemiv @itemv @itemvi); map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\enumtyperefname'); } qw(enumi enumii enumiii enumiv); map { DefMacroI(T_CS('\\' . $_ . 'name'), undef, '\desctyperefname'); } qw(@desci @descii @desciii @desciv @descv @descvi); #====================================================================== # C.6.3 The list and trivlist environments. #====================================================================== # Generic lists are given a way to format the item label, and presumably # a counter. DefConditional('\if@nmbrlist'); DefMacro('\@listctr', ''); DefPrimitive('\usecounter{}', sub { my ($stomach, $counter) = @_; $counter = ToString(Expand($counter)); beginItemize('list', $counter, ($counter ? (nolevel => 1) : ())); return; }); DefMacro('\list{}{}', '\let\@listctr\@empty#2\ifx\@listctr\@empty\usecounter{}\fi\expandafter\def\csname fnum@\@listctr\endcsname{#1}\lx@list'); DefMacro('\endlist', '\endlx@list'); DefConstructor('\lx@list DigestedBody', "#1", beforeDigest => sub { $_[0]->bgroup; }); DefPrimitive('\endlx@list', sub { $_[0]->egroup; }); DefConstructor('\list@item OptionalUndigested', "#tags", properties => sub { RefStepItemCounter($_[1]); }); # This isn't quite right, although it seems right for deep, internal uses with a single \item. # Perhaps we need to check trivlist's afterwards and if they are just a single item, # reduce it to an ltx:p ?? # DefMacro('\trivlist@item[]', ''); # DefEnvironment('{trivlist}', # '#body', # beforeDigest => sub { Let('\item', '\trivlist@item'); }); DefEnvironment('{trivlist}', "#body", properties => sub { beginItemize('trivlist'); }, beforeDigestEnd => sub { Digest('\par'); }); DefMacro('\trivlist@item', '\par\trivlist@item@'); DefConstructor('\trivlist@item@ OptionalUndigested', "" . "#tag", # At least an empty tag! ? properties => sub { ($_[1] ? (tag => 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}', '#body'); DefEnvironment('{verbatim*}', '#body'); DefMacroI('\@verbatim', undef, '\par\aftergroup\lx@end@verbatim\lx@@verbatim'); # Close enough? DefConstructorI('\lx@@verbatim', undef, "", beforeDigest => sub { my ($stomach) = @_; StartSemiverbatim('%', '\\', '{', '}'); MergeFont(family => 'typewriter', series => 'medium', shape => 'upright'); $STATE->assignCatcode(' ', CC_ACTIVE); # Do NOT (necessarily) skip spaces after \verb!!! Let(T_ACTIVE(' '), T_SPACE); }); DefConstructorI('\lx@end@verbatim', undef, "", beforeDigest => sub { EndSemiverbatim(); }); # verbatim is a bit of special case; # It looks like an environment, but it only ends with an explicit "\end{verbatim}" on it's own line. # So, we'll end up doing things more manually. # We're going to sidestep the Gullet for inputting, # and also the usual environment capture. DefConstructorI(T_CS('\begin{verbatim}'), undef, "#body", beforeDigest => [sub { $_[0]->bgroup; my @stuff = (); if (my $b = LookupValue('@environment@verbatim@atbegin')) { push(@stuff, Digest(@$b)); } AssignValue(current_environment => 'verbatim'); DefMacroI('\@currenvir', undef, 'verbatim'); MergeFont(family => 'typewriter'); # Digest(T_CS('\par')); # NO! See beforeConstruct! @stuff; }], 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)) { # The raw chars will still have to be decoded (but not space!!) $line = join('', map { ($_ eq ' ' ? ' ' : FontDecodeString($_, 'OT1_typewriter')) } split(//, $line)); 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" if (my $b = LookupValue('@environment@verbatim@atend')) { push(@lines, ToString(Digest(@$b))); } $stomach->egroup; $whatsit->setBody(map { Box($_, $font, $loc, T_OTHER($_)) } @lines, $end); return; }], 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 # And clearly separate expansion from digestion DefMacroI('\verb', undef, sub { my ($gullet) = @_; StartSemiverbatim('%', '\\', '{', '}'); $STATE->assignCatcode(' ', CC_ACTIVE); # Do NOT (necessarily) skip spaces after \verb!!! my $init = $gullet->readToken; my $starred = 0; if (T_OTHER('*')->equals($init)) { $starred = 1; $init = $gullet->readToken; } if (!$init) { # typically something read too far got \verb and the content is somewhere else..? Error('expected', 'delimiter', $gullet, "Verbatim argument lost", "Bindings for preceding code is probably broken"); EndSemiverbatim(); return (); } my $init_str = $init->getString(); AssignCatcode($init_str => CC_ACTIVE); my $delim = T_ACTIVE($init_str); my $body = $gullet->readUntil($delim); EndSemiverbatim(); Tokens( T_CS('\@hidden@bgroup'), ($starred ? T_CS('\lx@use@visiblespace') : ()), Invocation(T_CS('\@internal@verb'), ($starred ? T_OTHER('*') : Tokens()), Tokens($init), $body), T_CS('\@hidden@egroup'))->unlist; }); DefPrimitive('\lx@use@visiblespace', sub { my ($stomach, $star) = @_; $STATE->assignCatcode(' ', CC_ACTIVE); # Do NOT (necessarily) skip spaces after \verb!!! Let(T_ACTIVE(' '), T_OTHER("\x{2423}")); # Visible space }); DefConstructor('\@internal@verb{} Undigested {}', "?#isMath(#text)" . "(#text)", properties => sub { (text => ToString($_[3])); }, font => { family => 'typewriter', series => 'medium', shape => 'upright' }, # bounded => 1, beforeConstruct => sub { my ($doc, $whatsit) = @_; if (!$whatsit->isMath) { $doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); } }, reversion => '\verb#1#2#3#2'); # This is defined by the alltt package. # Environment('alltt', ?); # Actually, latex sets catcode to 13 ... is this close enough? DefPrimitiveI('\obeycr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 1); }); DefPrimitiveI('\restorecr', undef, sub { AssignValue('PRESERVE_NEWLINES' => 0); }); DefMacroI('\normalsfcodes', undef, Tokens()); #********************************************************************** # C.7 Mathematical Formulas #********************************************************************** #====================================================================== # C.7.1 Math Mode Environments #====================================================================== DefMacroI('\@eqnnum', undef, '(\theequation)'); DefMacro('\fnum@equation', '\@eqnnum'); # Redefined from TeX.pool, since with LaTeX we presumably have a more complete numbering system DefConstructorI('\@@BEGINDISPLAYMATH', undef, "" . "" . "" . "#body" . "" . "" . "", alias => '$$', beforeDigest => sub { $_[0]->beginMode('display_math'); if (my @everymath_toks = $STATE->lookupDefinition(T_CS('\everymath'))->valueOf->unlist()) { $_[0]->getGullet->unread(@everymath_toks); } if (my @everydisplay_toks = $STATE->lookupDefinition(T_CS('\everydisplay'))->valueOf->unlist()) { $_[0]->getGullet->unread(@everydisplay_toks); } return; }, properties => sub { RefStepID('equation') }, captureBody => 1); DefEnvironment('{displaymath}', "" . "" . "" . "#body" . "" . "" . "", mode => 'display_math', properties => sub { RefStepID('equation') }, locked => 1); DefEnvironment('{math}', "" . "" . "#body" . "" . "", mode => 'inline_math', ); Let('\curr@math@size', '\@empty'); # Equation Numbering turns out to be rather convoluted! # numbered equations & friends want to IMMEDIATELY increment the equation counter # (you can see by \theequation) # However, \nonumber, \tag, set a different number and RETRACT stepping the counter! NewCounter('subequation', 'equation', idprefix => 'E', idwithin => 'equation'); DefMacro('\thesubequation', '\theequation\alph{subequation}'); DefMacro('\fnum@subequation', '(\thesubequation)'); # This provides {equation} with the capabilities for tags, nonumber, etc # even though stock LaTeX provides no means to override them. # preset => boolean # postset => boolean # deferretract=>boolean sub prepareEquationCounter { my (%options) = @_; AssignValue(EQUATION_NUMBERING => {%options}, 'global'); return; } sub beforeEquation { my $numbering = LookupValue('EQUATION_NUMBERING'); my $numbered = $$numbering{numbered}; my $ctr = $$numbering{counter} || 'equation'; MaybePeekLabel(); $$numbering{in_equation} = 1; if ($$numbering{preset}) { AssignValue(EQUATIONROW_TAGS => { preset => 1, ($numbered ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); } else { AssignValue(EQUATIONROW_TAGS => {}, 'global'); } Let('\lx@saved@BEGINDISPLAYMATH', '\@@BEGINDISPLAYMATH'); Let('\lx@saved@ENDDISPLAYMATH', '\@@ENDDISPLAYMATH'); Let('\@@ENDDISPLAYMATH', '\lx@eDM@in@equation'); Let('\@@BEGINDISPLAYMATH', '\lx@bDM@in@equation'); return; } # Note peculiar usages of \[,\],$$ WITHIN displayed math, like {equation}. # Authors sometimes use \begin{equation} math \] some text \[ more math \end{equation} # to create a cheap intertext. And, the refnum appears on the SECOND equation! # But in redefining these, note that a "valid" \[ math \] might be used within some # sort of text insertion (eg \footnote)! # So, we've got to dance around with redefinitions!! # SIDE NOTE: \[,\] are really just $$ w/ appropriate error checking! DefMacro('\lx@bDM@in@equation', '\lx@saved@BEGINDISPLAYMATH\let\@@ENDDISPLAYMATH\lx@saved@ENDDISPLAYMATH'); DefMacro('\lx@eDM@in@equation', '\lx@retract@eqnno\lx@begin@fake@intertext' . '\let\lx@saved@BEGINDISPLAYMATH\@@BEGINDISPLAYMATH\let\lx@saved@bdm\[' . '\let\@@BEGINDISPLAYMATH\lx@end@fake@intertext' . '\let\[\lx@end@fake@intertext'); DefMacro('\lx@begin@fake@intertext', '\end{equation}'); DefMacro('\lx@end@fake@intertext', '\let\@@BEGINDISPLAYMATH\lx@saved@BEGINDISPLAYMATH\let\[\lx@saved@bdm' . '\begin{equation}'); DefPrimitive('\lx@retract@eqnno', sub { retractEquation(); }); sub retractEquation { my $numbering = LookupValue('EQUATION_NUMBERING'); # What about scopes? Is that handled automagically? # What about \@currentID.... my $tags = LookupValue('EQUATIONROW_TAGS'); my $ctr = $$tags{counter} || $$numbering{counter} || 'equation'; if ($$tags{preset}) { # ONLY if the number was preset! # counter (or ID counter) was stepped, so decrement it. AddToCounter(($$numbering{numbered} ? $ctr : 'UN' . $ctr), Number(-1)); } AssignValue(EQUATIONROW_TAGS => { RefStepID($ctr), reset => 1 }, 'global'); return; } # Disable this equation's number # Ahhhh, but in eqnarray, this isn't effected until \\ !!!!! DefMacroI('\nonumber', undef, '\lx@equation@nonumber'); DefPrimitiveI('\lx@equation@nonumber', undef, sub { my $numbering = LookupValue('EQUATION_NUMBERING'); if ($$numbering{in_equation}) { if ($$numbering{deferretract}) { my $tags = LookupValue('EQUATIONROW_TAGS'); $$tags{retract} = 1; } else { retractEquation(); } } return; }); Let('\@LTX@nonumber', '\lx@equation@nonumber'); # Set this equation's number explicitly (eg ams's \tag) DefMacroI('\lx@equation@settag', undef, '\lx@equation@retract\lx@equation@settag@'); DefPrimitiveI('\lx@equation@retract', undef, sub { retractEquation(); }); DefPrimitive('\lx@equation@settag@ Digested', sub { my $tags = LookupValue('EQUATIONROW_TAGS'); $$tags{tags} = $_[1]; return; }, mode => 'text'); sub afterEquation { my ($whatsit) = @_; my $numbering = LookupValue('EQUATION_NUMBERING'); my $tags = LookupValue('EQUATIONROW_TAGS'); my $ctr = $$tags{counter} || $$numbering{counter} || 'equation'; if (!$$tags{noretract} && ($$tags{retract} || ($$numbering{retract} && $$numbering{preset} && $$tags{preset}))) { retractEquation(); } elsif ($$numbering{postset} && !$$tags{reset}) { # my %props = (); # if ($$numbering{numbered}) { # %props = RefStepCounter($ctr); } # else { # %props = RefStepID($ctr); } # AssignValue(EQUATIONROW_TAGS => {%props}, 'global'); } AssignValue(EQUATIONROW_TAGS => { ($$numbering{numbered} ? RefStepCounter($ctr) : RefStepID($ctr)) }, 'global'); } elsif (!$$tags{reset} && $$numbering{numbered}) { $$tags{tags} = Digest(Invocation(T_CS('\lx@make@tags'), $ctr)); } # Now install the tags in $whatsit or current Row, as appropriate. my $props = LookupValue('EQUATIONROW_TAGS'); if ($$numbering{aligned}) { if (my $alignment = LookupValue('Alignment')) { my $row = $alignment->currentRow; $$row{id} = $$props{id}; $$row{tags} = $$props{tags}; } } elsif ($whatsit) { $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); } $$numbering{in_equation} = 0; return; } # My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...? DefEnvironment('{equation}', "" . "#tags" . "" . "" . "#body" . "" . "" . "", mode => 'display_math', beforeDigest => sub { prepareEquationCounter(numbered => 1, preset => 1); beforeEquation(); }, afterDigestBody => sub { afterEquation($_[1]); }, locked => 1); # Note: In ams, this DOES get a number if \tag is used! DefEnvironment('{equation*}', "" . "#tags" . "" . "" . "#body" . "" . "" . "", mode => 'display_math', beforeDigest => sub { prepareEquationCounter(numbered => undef, preset => 1); beforeEquation(); }, afterDigestBody => sub { afterEquation($_[1]); }, locked => 1); DefMacro('\[', '\@@BEGINDISPLAYMATH'); DefMacro('\]', '\@@ENDDISPLAYMATH'); DefMacro('\(', '\@@BEGININLINEMATH'); DefMacro('\)', '\@@ENDINLINEMATH'); # Keep from expanding too early, if in alignments, or such. DefMacroI('\ensuremath', undef, Tokens(T_CS('\protect'), T_CS('\@ensuremath'))); DefMacro('\@ensuremath{}', sub { my ($gullet, $stuff) = @_; if (LookupValue('IN_MATH')) { $stuff->unlist; } else { (T_MATH, $stuff->unlist, T_MATH); } }); # Magic check that math-mode trigger follows our $MATHENVS = 'displaymath|equation*?|eqnarray*?' . '|multline*?|align*?|falign*?|alignat*?|xalignat*?|xxalignat*?|gather*?'; DefMacro('\ensuremathfollows', sub { my ($gullet) = @_; $gullet->closeMouth unless ($gullet->getMouth->hasMoreInput); if (my $tok = $gullet->readToken()) { my $csname = $tok->getCSName; if ($csname eq '\begin') { my $arg = $gullet->readArg(); $csname = $arg->toString; $gullet->unread(T_BEGIN, $arg->unlist, T_END); } $gullet->unread($tok); # We need to determine whether the TeX we're given needs to be wrapped in \(...\) # Does it have $'s around it? Does it have a display math environment? if ($csname !~ /^Math|\\\(|\\\[|(?:$MATHENVS)/o) { AssignValue('automath_triggered' => 1, 'global'); return T_CS('\\('); } } return; }); DefMacro('\ensuremathpreceeds', sub { return LookupValue('automath_triggered') ? T_CS('\\)') : (); }); # Since the arXMLiv folks keep wanting ids on all math, let's try this! Tag('ltx:Math', afterOpen => sub { GenerateID(@_, 'm'); }); #====================================================================== # Sub-numbered equations # Although LaTeX itself doesn't provide such an environment, # many other packages & classes do, and they seem essentially equivalent. # They provide an environment to wrap around equations, # which hijack the equation counter to sub-number within the group. DefConstructor('\lx@equationgroup@subnumbering@begin', "#tags", afterDigest => sub { my ($stomach, $whatsit) = @_; my %eqn = RefStepCounter('equation'); my $eqnum = Expand(T_CS('\theequation')); AssignValue(SAVED_EQUATION_NUMBER => LookupValue('\c@equation')); $whatsit->setProperties(%eqn); ResetCounter('equation'); DefMacroI('\theequation', undef, UnTeX($eqnum) . '\alph{equation}'); DefMacroI('\theequation@ID', undef, UnTeX($eqn{id}) . '.\@equation@ID'); }); Tag('ltx:equationgroup', autoClose => 1); DefConstructor('\lx@equationgroup@subnumbering@end', sub { $_[0]->maybeCloseElement('ltx:equationgroup'); }, afterDigest => sub { AssignValue('\c@equation', LookupValue('SAVED_EQUATION_NUMBER'), 'global'); }); #====================================================================== # ======================================== # eqnarray, etal # Tricky! There's a conflict between a math-level alignment (which # intermingles non-math things like labels, refnums, intertext), # and a text-level alignment (which fragments the math's logical structure). # Our solution is to attempt to synthesize a logical structure of # an equationgroup containing equations, but using MathFork structures # to hide the intended aligmnents. # Then, XSLT can be used in the end to either display in a logical # (but non-aligned format), or display fully aligned (but hiding the semantics). #====================================================================== # Equation Groups # 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: # as the overall container # 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 , thus these can also be parsed, # and converted to MathML or images. # # Thus, both forms are present: A presentation-oriented stylesheet can # thus represent the eqnarray by a table with aligned math chunks. # A content-oriented stylesheet can select the composed meaningful pieces. # ======================================== # The following set deal with numbering the equations (rows) that make up an equationgroup. # Note EQUATIONGROUP_NUMBER controls the numbering of the equations contained within the equationgroup, # not the numbering of the equationgroup itself. DefPrimitive('\@equationgroup@numbering RequiredKeyVals', sub { my $kv = GetKeyVals($_[1]); prepareEquationCounter(map { ($_ => ToString($$kv{$_})) } keys %$kv); }); # ======================================== # Some special kinds of rows... DefConditionalI('\if@in@firstcolumn', undef, sub { my $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@numbering{numbered=1,preset=1,deferretract=1,grouped=1,aligned=1}' . '\@start@alignment', locked => 1); DefMacroI('\endeqnarray', undef, '\cr\@finish@alignment\end@eqnarray', locked => 1); DefMacro('\csname eqnarray*\endcsname', '\@eqnarray@bindings\@@eqnarray' . '\@equationgroup@numbering{numbered=1,preset=1,retract=1,grouped=1,aligned=1}' . '\@start@alignment', locked => 1); DefMacro('\csname endeqnarray*\endcsname', '\@finish@alignment\end@eqnarray', locked => 1); DefPrimitive('\@eqnarray@bindings', sub { eqnarrayBindings(); }); DefPrimitiveI('\eqnarray@row@before', undef, sub { beforeEquation(); }); DefPrimitiveI('\eqnarray@row@after', undef, sub { afterEquation(); }); DefPrimitiveI('\eqnarray@row@before@', undef, sub { beforeEquation(); }); DefPrimitiveI('\eqnarray@row@after@', undef, sub { afterEquation(); }); DefMacroI('\eqnarray@row@before', undef, '\hidden@noalign{\eqnarray@row@before@}'); DefMacroI('\eqnarray@row@after', undef, '\hidden@noalign{\eqnarray@row@after@}'); sub eqnarrayBindings { my $col1 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')), after => Tokens(T_MATH) }; my $col2 = { before => Tokens(T_CS('\hfil'), T_MATH, T_CS('\displaystyle')), after => Tokens(T_MATH, T_CS('\hfil')) }; my $col3 = { before => Tokens(T_MATH, T_CS('\displaystyle')), after => Tokens(T_MATH, T_CS('\hfil')) }; my %attributes = ( 'class' => 'ltx_eqn_eqnarray', 'colsep' => LookupDimension('\arraycolsep')->multiply(2)); my $cur_jot = LookupDimension('\jot'); if ($cur_jot && ($cur_jot->valueOf != LookupDimension('\lx@default@jot')->valueOf)) { $attributes{rowsep} = $cur_jot; } AssignValue(Alignment => LaTeXML::Core::Alignment->new( template => LaTeXML::Core::Alignment::Template->new(columns => [$col1, $col2, $col3]), openContainer => sub { my %attr = RefStepID('@equationgroup'); $attr{'xml:id'} = $attr{id}; delete $attr{id}; $attr{class} = 'ltx_eqn_eqnarray'; $_[0]->openElement('ltx:equationgroup', %attr, @_[1 .. $#_]); }, closeContainer => sub { $_[0]->closeElement('ltx:equationgroup'); }, openRow => sub { my ($doc, %props) = @_; my $tags = $props{tags}; delete($props{tags}); $doc->openElement('ltx:equation', %props); $doc->absorb($tags) if $tags; }, closeRow => sub { $_[0]->closeElement('ltx:equation'); }, openColumn => sub { $_[0]->openElement('ltx:_Capture_', @_[1 .. $#_]); }, closeColumn => sub { $_[0]->closeElement('ltx:_Capture_'); }, properties => { preserve_structure => 1, attributes => {%attributes} })); Let("\\\\", '\@alignment@newline'); Let('\@row@before', '\eqnarray@row@before'); Let('\@row@after', '\eqnarray@row@after'); Let('\lx@eqnarray@save@label', '\label'); Let('\label', '\lx@eqnarray@label'); return; } # A \label preceding \lefteqn throws of the implied \omit,e tc; so wrap in \hidden@align DefMacro('\lx@eqnarray@label Semiverbatim', '\ifnum1>\@alignment@column\hidden@noalign{\lx@eqnarray@save@label{#1}}\else\lx@eqnarray@save@label{#1}\fi'); DefConstructor('\@@eqnarray SkipSpaces DigestedBody', '#1', beforeDigest => sub { $_[0]->bgroup; }, afterConstruct => sub { rearrangeEqnarray($_[0], $_[0]->getNode->lastChild); }); DefPrimitiveI('\end@eqnarray', undef, sub { $_[0]->egroup; }); # ======================================== # Some tools for analyzing the equationgroup after we've constructed it. # ======================================== # ======================================== # For eqnarray, the "meaningful" unit will be at least one row, # but often multiple rows. # When the 1st column is empty, the row is assumed to continue the previous equation (if any!) # When the 2nd column is also empty, it presumably continues the previous RHS. # We'll combine these cases into a single equation, but remember the alignment structure. # However, if more than 1 such row has refnums, we probably don't want to combine; # But we really need to find a better way of representing the information! # Note that there are common misuses of eqnarray; # One type tries to get the equivalent of amsmath's gather environment by # using a single column for the equations; the equations are right, centered or # left aligned, depending on which column was used. # Can we detect continuations, and can we distinguish continuations of equations vs. RHS? # Probably a similar misuse where only 2 columns are used? sub rearrangeEqnarray { my ($document, $equationgroup) = @_; # Scan the "equations" (rows) within the $equationgroup # to see what pattern of columns are present. my @rows = (); foreach my $rownode ($document->findnodes('ltx:equation', $equationgroup)) { my @cells = $document->findnodes('ltx:_Capture_', $rownode); # representing each column. push(@rows, { node => $rownode, cols => [@cells], L => ($cells[0] && $cells[0]->hasChildNodes), M => ($cells[1] && $cells[1]->hasChildNodes), R => ($cells[2] && $cells[2]->hasChildNodes), numbered => ($document->findnode('ltx:tags', $rownode) ? 1 : 0), labelled => $rownode->hasAttribute('label') }); } my $nL = scalar(grep { $$_{L} } @rows); my $nM = scalar(grep { $$_{M} } @rows); my $nR = scalar(grep { $$_{R} } @rows); # Only a single column was used. Remove the empty ones. # A heuristic: if any rows begin with a relation, there are probably continuations, # but maybe we don't want to try to distinguish the kinds? if (($nL && !$nM && !$nR) # All left column || (!$nL && $nM && !$nR) # All center column || (!$nL && !$nM && $nR)) { # All right column # We REALLY should remove the empty columns entirely! my $keepcol = ($nL ? 0 : ($nM ? 1 : 2)); # REMOVE empty columns! foreach my $c (2, 1, 0) { next if $c == $keepcol; foreach my $row (@rows) { $document->removeNode($$row{cols}[$c]); splice(@{ $$row{cols} }, $c, 1); } } # If (some) columns begin with a relation, presumably a single equation? # Or maybe we should only connect those to previous row? my $t; if (grep { ($t = $$_{cols}[0]) && ($t = $document->getFirstChildElement($t)) && (($t->getAttribute('role') || '') eq 'RELOP') } @rows) { equationgroupJoinRows($document, $equationgroup, map { $$_{node} } @rows); } else { # Really, these shouldn't even end up with MathFork! foreach my $row (@rows) { equationgroupJoinCols($document, 1, $$row{node}); } } return; } # What about case where only TWO columns get used? Worth analyzing? # Remaining case is when all 3 columns are used. my @eqs = (); my $numbered = 0; my $oddness = 0; foreach my $row (@rows) { my $class = 'unknown'; if ($$row{L}) { # 1st column non-empty; Presumably a new "equation" $class = 'new'; } elsif ($$row{M}) { # 1st is empty & 2nd non-empty (Rel?); Probably continues if (!scalar(@eqs)) { # But if no previous equation? $class = 'odd'; } elsif ($numbered && $$row{numbered}) { # Separately numbered continuation? $class = 'new'; } # Keep it as separate equation, even though missing LHS else { $class = 'continue'; } } # Continues as multiequation elsif ($$row{R}) { # 1st & 2nd is empty, non empty 3rd if (!scalar(@eqs)) { # But if no previous equation? $class = 'odd'; } elsif ($numbered && $$row{numbered} # Separately numbered AND labeled? && $$row{labelled}) { $class = 'odd'; } # must keep separate, but weird! else { $class = 'continue'; } } # Else, continues RHS. else { # All columns empty # Probably the trailling //, but who knows... # Arguably: if it has a number &/or is labelled # we should either prserve (as odd) or move info to previous line (if any) $class = 'remove'; } if ($class eq 'remove') { $document->removeNode($$row{node}); } elsif (($class eq 'new') || ($class eq 'odd')) { $numbered = $$row{numbered}; push(@eqs, [$$row{node}]); $oddness++ if $class eq 'odd'; } else { $numbered |= $$row{numbered}; push(@{ $eqs[-1] }, $$row{node}); } } Warn('unexpected', 'eqnarray', $equationgroup, "Unrecognized equation patterns ($oddness) in eqnarray") if (scalar(@rows) > 1) && $oddness; # Now rearrange things appropriately. foreach my $eqset (@eqs) { equationgroupJoinRows($document, $equationgroup, @$eqset); } return; } # Style Parameters # \abovedisplayskip \abovedisplayshortskip, \jot are in TeX.pool DefRegister('\mathindent' => Dimension(0)); #====================================================================== # C.7.2 Common Structures #====================================================================== # sub, superscript and prime are in TeX.pool # Underlying support in TeX.pool.ltxml DefConstructor('\frac InFractionStyle InFractionStyle', "" . "" . "#1#2" . "", sizer => sub { fracSizer($_[0]->getArg(1), $_[0]->getArg(2)); }, properties => { mathstyle => sub { LookupValue('font')->getMathstyle; } }); # Ellipsis: See TeX.pool #====================================================================== # C.7.3 Mathematical Symbols #====================================================================== # See Tables 3.3 through 3.8 (pp 41--44) # Defined in TeX.pool # [Possibly some are strictly LaTeX and should be moved here?] #====================================================================== # C.7.4 Arrays #====================================================================== # See Section C.10.2 #======================================================================- # C.7.5 Delimiters #======================================================================- # All this is already in TeX.pool DefMacro('\stackrel{}{}', '\lx@stackrel{{\scriptstyle #1}}{{#2}}'); DefConstructor('\lx@stackrel{}{}', "" . "" . "#2" . "#1" . "", reversion => '\stackrel{#1}{#2}', properties => { scriptpos => sub { "mid" . $_[0]->getScriptLevel; } } ); #======================================================================- # C.7.6 Putting One Thing Above Another #======================================================================- # All this is already in TeX.pool #======================================================================- # C.7.7 Spacing #======================================================================- # 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:* SkipSpaces DefToken [Number][]{}', sub { my ($stomach, $star, $cs, $nargs, $opt, $body) = @_; if (!isDefinable($cs)) { Info('ignore', $cs, $stomach, "Ignoring redefinition (\\newcommand) of '" . ToString($cs) . "'") unless LookupValue(ToString($cs) . ':locked'); return; } DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); }); DefPrimitive('\CheckCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', undef); DefPrimitive('\renewcommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub { my ($stomach, $star, $cs, $nargs, $opt, $body) = @_; DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); }); # low-level implementation of both \newcommand and \renewcommand depends on \@argdef # and robustness upgrades are often realized via redefining \l@ngrel@x DefPrimitive('\@argdef DefToken []{}', sub { DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); }); DefPrimitive('\@xargdef DefToken [][]{}', sub { DefMacroI($_[1], convertLaTeXArgs($_[2], $_[3]), $_[4]); }); DefPrimitive('\@yargdef DefToken DefToken {}{}', sub { DefMacroI($_[1], (T_CS('\tw@')->equals($_[2]) ? convertLaTeXArgs($_[3], Tokens()) : convertLaTeXArgs($_[3])), $_[4]); }); DefPrimitive('\@reargdef DefToken []{}', sub { DefMacroI($_[1], convertLaTeXArgs($_[2]), $_[3]); }); DefPrimitive('\providecommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub { my ($stomach, $star, $cs, $nargs, $opt, $body) = @_; return unless isDefinable($cs); DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body); }); # Crazy; define \cs in terms of \cs[space] !!! DefPrimitive('\DeclareRobustCommand OptionalMatch:* SkipSpaces DefToken [Number][]{}', sub { my ($stomach, $star, $cs, $nargs, $opt, $body) = @_; DefMacroI($cs, convertLaTeXArgs($nargs, $opt), $body, robust => 1); }); DefPrimitive('\MakeRobust DefToken', sub { my ($stomach, $cs) = @_; my $mungedcs = T_CS($cs->getString . ' '); if (!LookupDefinition($cs)) { } # Not defined elsif (LookupDefinition($mungedcs)) { } # Already robust else { Let($mungedcs, $cs); DefMacroI($cs, undef, Tokens(T_CS('\protect'), $mungedcs)); } }); # There are a bunch of ways in LaTeX to assign a command to # a particular point within a font (which has one of many encodings) # Since we have no practical way of knowing what that point is, # and we really want to create unicode, we just ignore this stuff (but warn). sub ignoredDefinition { my ($stomach, $command, $cs) = @_; # Warn('ignore',$cs,$stomach,"ignoring ".ToString($command)." definition of ".ToString($cs)); return; } #------------------------------------------------------------ # The following commands define encoding-specific expansions # or glyphs. The control-sequence is defined to use the expansion for # the current encoding, if any, or the default expansion (for encoding "?"). # We don't want to redefine control-sequence if it already has a definition: # It may be that we've already defined it to expand into the above conditional. # But more importantly, we don't want to override a hand-written definition (if any). #------------------------------------------------------------ DefPrimitive('\DeclareTextCommand DefToken {}[Number][]{}', sub { my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_; my $css = ToString($cs); $encoding = ToString(Expand($encoding)); if (!IsDefined($cs)) { # If not already defined... DefMacroI($cs, undef, '\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname' . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); } my $ecs = T_CS('\\' . $encoding . $css); DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); return; }); DefMacro('\DeclareTextCommandDefault DefToken', '\DeclareTextCommand{#1}{?}'); DefPrimitive('\ProvideTextCommand DefToken {}[Number][]{}', sub { my ($gullet, $cs, $encoding, $nargs, $opt, $expansion) = @_; my $css = ToString($cs); $encoding = ToString(Expand($encoding)); if (isDefinable($cs)) { # If not already defined... DefMacroI($cs, undef, '\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname' . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); } my $ecs = T_CS('\\' . $encoding . $css); if (!IsDefined($ecs)) { # If not already defined... DefMacroI($ecs, convertLaTeXArgs($nargs, $opt), $expansion); } return; }); DefMacro('\ProvideTextCommandDefault DefToken', '\ProvideTextCommand{#1}{?}'); #------------------------------------------------------------ DefPrimitive('\DeclareTextSymbol DefToken {}{Number}', sub { my ($gullet, $cs, $encoding, $code) = @_; $code = $code->valueOf; my $css = ToString($cs); $encoding = ToString(Expand($encoding)); if (isDefinable($cs)) { # If not already defined... DefMacroI($cs, undef, '\expandafter\ifx\csname\cf@encoding\string' . $css . '\endcsname\relax\csname?\string' . $css . '\endcsname' . '\else\csname\cf@encoding\string' . $css . '\endcsname\fi'); } my $ecs = T_CS('\\' . $encoding . $css); DefPrimitiveI($ecs, undef, FontDecode($code, $encoding)); return; }); # hmmm... what needs doing here; basically it means use this encoding as the default for the symbol DefMacro('\DeclareTextSymbolDefault DefToken {}', ''); # '\DeclareTextSymbol{#1}{?}'); #------------------------------------------------------------ DefPrimitive('\DeclareTextAccent DefToken {}{}', sub { ignoredDefinition('DeclareTextAccent', $_[1]); }); DefPrimitive('\DeclareTextAccentDefault{}{}', sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); }); #------------------------------------------------------------ # TODO: Need to convert these DefPrimitive's into DefConstroctors # that add a preable PI!!!!!!!!! DefPrimitive('\DeclareTextComposite{}{}{}{}', sub { ignoredDefinition('DeclareTextComposite', $_[1]); }); DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}', sub { ignoredDefinition('DeclareTextCompositeCommand', $_[1]); }); DefPrimitive('\UndeclareTextCommand{}{}', undef); DefMacro('\UseTextSymbol{}{}', '{\fontencoding{#1}#2}'); DefMacro('\UseTextAccent{}{}', '{\fontencoding{#1}#2{#3}}'); DefPrimitive('\DeclareMathAccent DefToken {}{} {Number}', sub { my ($stomach, $cs, $kind, $class, $code) = @_; $class = ToString($class); my $info = LookupValue('fontdeclaration@' . $class); my $glyph = FontDecode($code->valueOf, ($info ? $$info{encoding} : $class)); DefMathI($cs, 'Digested', $glyph, operator_role => 'OVERACCENT'); return AddToPreamble('\DeclareMathAccent', $cs, $kind, $class, $code); }); DefPrimitive('\DeclareMathDelimiter{}{}{}{}', sub { ignoredDefinition('DeclareMathDelimiter', $_[1]); }); DefPrimitive('\DeclareMathRadical{}{}{}{}{}', sub { ignoredDefinition('DeclareMathRadical', $_[1]); }); DefPrimitive('\DeclareMathVersion{}', undef); DefPrimitive('\DeclarePreloadSizes{}{}{}{}{}', undef); # The next font declaration commands are based on # http://tex.loria.fr/general/new/fntguide.html # we ignore font encoding DefPrimitive('\DeclareSymbolFont{}{}{}{}{}', sub { my ($stomach, $name, $enc, $family, $series, $shape) = @_; AssignValue('fontdeclaration@' . ToString($name), { family => ToString($family), series => ToString($series), shape => ToString($shape), encoding => ToString($enc) }); }); DefPrimitive('\DeclareSymbolFontAlphabet{}{}', sub { my ($stomach, $cs, $name) = @_; my $font = LookupValue('fontdeclarations@' . ToString($name)) || {}; DefPrimitiveI(T_CS(ToString($cs)), undef, undef, font => $font); }); DefPrimitive('\DeclareMathSizes{}{}{}{}', undef); DefPrimitive('\DeclareMathAlphabet{}{}{}{}{}', sub { my ($stomach, $cs, $enc, $family, $series, $shape) = @_; my $csname = ToString($cs); # We won't override this, e.g. \mathrm by fouriernc.sty if (IsDefined($csname)) { Info('ignore', $csname, $stomach, "Ignoring redefinition (\\DeclareMathAlphabet) of '" . $csname . "'"); } else { my %font = LaTeXML::Common::Font::lookupTeXFont($family, $series, $shape); DefPrimitiveI(T_CS($csname), undef, undef, font => {%font}); } return; }); DefMacro('\newmathalphabet{}{}{}', Tokens()); # or expand int DeclareMathAlphabet? DefPrimitive('\DeclareFontShape{}{}{}{}{}{}', undef); DefPrimitive('\DeclareFontFamily{}{}{}', undef); DefPrimitive('\DeclareSizeFunction{}{}', undef); my $symboltype_roles = { '\mathord' => 'ID', '\mathop' => 'BIGOP', '\mathbin' => 'BINOP', '\mathrel' => 'RELOP', '\mathopen' => 'OPEN', '\mathclose' => 'CLOSE', '\mathpunct' => 'PUNCT' }; DefPrimitive('\DeclareMathSymbol DefToken SkipSpaces DefToken {}{Number}', sub { my ($stomach, $cs, $type, $font, $code) = @_; my $encoding = ToString($font); # Or maybe just a font name or class? if (my $decl = LookupValue('fontdeclaration@' . $encoding)) { $encoding = $$decl{encoding} if $$decl{encoding}; } my $glyph = FontDecode($code->valueOf, $encoding); my $role = $$symboltype_roles{ ToString($type) }; DefMathI($cs, undef, $glyph, role => $role); return; }); DefPrimitive('\DeclareFixedFont{}{}{}{}{}{}', 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 $encoding_expanded = Expand($encoding); my $encoding_str = ToString($encoding_expanded); DefMacroI('\LastDeclaredEncoding', undef, $encoding_expanded); DefMacroI('\T@' . $encoding_str, undef, $x); DefMacroI('\M@' . $encoding_str, undef, Tokens(T_CS('\default@M'), $y->unlist)); if (my $path = $encoding_str && FindFile(lc($encoding_str) . "enc", type => "dfu")) { InputDefinitions($path); } return; }); DefMacroI('\LastDeclaredEncoding', undef, ''); DefPrimitive('\DeclareFontSubstitution{}{}{}{}', undef); DefPrimitive('\DeclareFontEncodingDefaults{}{}', undef); DefMacroI('\LastDeclaredEncoding', undef, Tokens()); DefPrimitive('\SetSymbolFont{}{}{}{}{}{}', undef); DefPrimitive('\SetMathAlphabet{}{}{}{}{}{}', undef); DefPrimitive('\addtoversion{}{}', undef); DefPrimitive('\TextSymbolUnavailable{}', undef); RawTeX(<<'EoTeX'); \DeclareSymbolFont{operators} {OT1}{cmr} {m}{n} \DeclareSymbolFont{letters} {OML}{cmm} {m}{it} \DeclareSymbolFont{symbols} {OMS}{cmsy}{m}{n} \DeclareSymbolFont{largesymbols}{OMX}{cmex}{m}{n} EoTeX # At least all things on uclclist need to be macros DefPrimitiveI('\lx@utf@OE', undef, "\x{0152}", alias => '\OE'); # LATIN CAPITAL LIGATURE OE DefPrimitiveI('\lx@utf@oe', undef, "\x{0153}", alias => '\oe'); # LATIN SMALL LIGATURE OE DefPrimitiveI('\lx@utf@AE', undef, UTF(0xC6), alias => '\AE'); # LATIN CAPITAL LETTER AE DefPrimitiveI('\lx@utf@ae', undef, UTF(0xE6), alias => '\ae'); # LATIN SMALL LETTER AE DefPrimitiveI('\lx@utf@AA', undef, UTF(0xC5), alias => '\AA'); # LATIN CAPITAL LETTER A WITH RING ABOVE DefPrimitiveI('\lx@utf@aa', undef, UTF(0xE5), alias => '\aa'); # LATIN SMALL LETTER A WITH RING ABOVE DefPrimitiveI('\lx@utf@O', undef, UTF(0xD8), alias => '\O'); # LATIN CAPITAL LETTER O WITH STROKE DefPrimitiveI('\lx@utf@o', undef, UTF(0xF8), alias => '\o'); # LATIN SMALL LETTER O WITH STROKE DefPrimitiveI('\lx@utf@L', undef, "\x{0141}", alias => '\L'); # LATIN CAPITAL LETTER L WITH STROKE DefPrimitiveI('\lx@utf@l', undef, "\x{0142}", alias => '\l'); # LATIN SMALL LETTER L WITH STROKE DefPrimitiveI('\lx@utf@ss', undef, UTF(0xDF), alias => '\ss'); # LATIN SMALL LETTER SHARP S DefPrimitiveI('\lx@utf@dh', undef, UTF(0xf0), alias => '\dh'); # eth DefPrimitiveI('\lx@utf@DH', undef, UTF(0xd0), alias => '\DH'); # Eth (looks same as \DJ!) DefPrimitiveI('\lx@utf@dj', undef, "\x{0111}", alias => '\dj'); # d with stroke DefPrimitiveI('\lx@utf@DJ', undef, "\x{0110}", alias => '\DJ'); # D with stroke (looks sames as \DH!) DefPrimitiveI('\lx@utf@ng', undef, "\x{014B}", alias => '\ng'); DefPrimitiveI('\lx@utf@NG', undef, "\x{014A}", alias => '\NG'); DefPrimitiveI('\lx@utf@th', undef, UTF(0xFE), alias => '\th'); DefPrimitiveI('\lx@utf@TH', undef, UTF(0xDE), alias => '\TH'); DefMacroI('\OE', undef, '\lx@utf@OE'); DefMacroI('\oe', undef, '\lx@utf@oe'); DefMacroI('\AE', undef, '\lx@utf@AE'); DefMacroI('\ae', undef, '\lx@utf@ae'); DefMacroI('\ae', undef, '\lx@utf@ae'); DefMacroI('\AA', undef, '\lx@utf@AA'); DefMacroI('\aa', undef, '\lx@utf@aa'); DefMacroI('\O', undef, '\lx@utf@O'); DefMacroI('\o', undef, '\lx@utf@o'); DefMacroI('\L', undef, '\lx@utf@L'); DefMacroI('\l', undef, '\lx@utf@l'); DefMacroI('\ss', undef, '\lx@utf@ss'); DefMacroI('\dh', undef, '\lx@utf@dh'); # in latex? DefMacroI('\DH', undef, '\lx@utf@DH'); DefMacroI('\dj', undef, '\lx@utf@dj'); DefMacroI('\DJ', undef, '\lx@utf@DJ'); DefMacroI('\ng', undef, '\lx@utf@ng'); DefMacroI('\NG', undef, '\lx@utf@NG'); DefMacroI('\th', undef, '\lx@utf@th'); DefMacroI('\TH', undef, '\lx@utf@TH'); #====================================================================== # C.8.2 Defining Environments #====================================================================== # Note that \env & \endenv defined by \newenvironment CAN be # invoked directly. DefPrimitive('\newenvironment OptionalMatch:* {}[Number][]{}{}', sub { my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_; $name = ToString(Expand($name)); if (IsDefined(T_CS("\\$name"))) { Info('ignore', $name, $stomach, "Ignoring redefinition (\\newenvironment) of Environment '$name'") unless (LookupValue('\\' . $name . ':locked') || LookupValue('\\begin{' . $name . '}:locked')); return; } DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin); DefMacroI(T_CS("\\end$name"), undef, $end); return; }); DefPrimitive('\renewenvironment OptionalMatch:* {}[Number][]{}{}', sub { my ($stomach, $star, $name, $nargs, $opt, $begin, $end) = @_; $name = ToString(Expand($name)); if (!LookupValue("\\$name:locked") && !LookupValue("\\begin{$name}:locked")) { DefMacroI(T_CS("\\$name"), convertLaTeXArgs($nargs, $opt), $begin); DefMacroI(T_CS("\\end$name"), undef, $end); } return; }); #====================================================================== # C.8.3 Theorem-like Environments #====================================================================== # The core non-customizable part is defined here. # For customizable theorems, see amsthm. AssignValue('thm@swap' => 0); DefRegister('\thm@style' => Tokenize('plain')); DefRegister('\thm@headfont' => Tokens(T_CS('\bfseries'))); DefRegister('\thm@notefont' => Tokens(T_CS('\the'), T_CS('\thm@headfont'))); DefRegister('\thm@bodyfont' => Tokens(T_CS('\itshape'))); DefRegister('\thm@headpunct' => Tokens()); DefRegister('\thm@styling' => Tokens()); DefRegister('\thm@headstyling' => Tokens()); DefRegister('\thm@prework' => Tokens()); DefRegister('\thm@postwork' => Tokens()); DefRegister('\thm@symbol' => Tokens()); DefRegister('\thm@numbering' => Tokens(T_CS('\arabic'))); DefPrimitive('\th@plain', sub { AssignValue('\thm@bodyfont' => T_CS('\itshape')); 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, ''); DefPrimitive('\newtheorem OptionalMatch:* {}[]{}[]', sub { my ($stomach, $flag, $thmset, $otherthmset, $type, $reset) = @_; defineNewTheorem($stomach, $flag, $thmset, $otherthmset, $type, $reset); # Reset these! DefRegister('\thm@prework' => Tokens()); DefRegister('\thm@postwork' => Tokens()); }, locked => 1); # These record which style parameters CAN BE saved/restored for each style. # (kinda weird, but different packages use different sets; particularly \thm@headfont) # THEOREM__PARAMETERS will store the actual values for those parameters AssignValue('SAVABLE_THEOREM_PARAMETERS' => {}); sub setSavableTheoremParameters { my (@keys) = @_; # Noe that these are saved for each theorem type. AssignValue('SAVABLE_THEOREM_PARAMETERS' => { map { $_ => 1; } @keys }); return; } # Following amsthm's model, the various theorem customizations are stored in token registers # Some packages may want to add '\thm@headfont' (or exclude it) setSavableTheoremParameters(qw( \thm@bodyfont \thm@headpunct \thm@styling \thm@headstyling thm@swap)); sub useTheoremStyle { my ($name) = @_; my $savable = LookupValue('SAVABLE_THEOREM_PARAMETERS'); if (my $params = LookupValue('THEOREM_' . ToString($name) . '_PARAMETERS')) { for my $param_key (keys %$savable) { if (exists $$params{$param_key}) { AssignValue($param_key => $$params{$param_key}); } } } return; } sub saveTheoremStyle { my ($name, %parameters) = @_; $name = ToString($name); AssignValue('THEOREM_' . $name . '_PARAMETERS' => {%parameters}, 'global'); return; } RawTeX('\th@plain'); # Activate the default style. # [or just make it's values the defaults...] Tag('ltx:theorem', autoClose => 1); Tag('ltx:proof', autoClose => 1); sub defineNewTheorem { my ($stomach, $flag, $thmset, $otherthmset, $type, $within) = @_; $thmset = ToString($thmset); my $classname = CleanClassName($thmset); my $listname = "theorem:$thmset"; $listname =~ s/\s//g; $otherthmset = $otherthmset && ToString($otherthmset); $type = undef unless $type && $type->unlist; $within = $within ? ToString($within) : undef; my $counter = $otherthmset || $thmset; $counter =~ s/\s+/./; # convert spaces to period? if ($counter ne $thmset) { AssignMapping('counter_for_type', $thmset => $counter); DefMacroI(T_CS('\the' . $thmset), undef, T_CS('\the' . $counter), scope => 'global'); } my $style = ToString(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$classname"; $idprefix =~ s/\*/./g; if (!LookupValue('\c@' . $counter)) { # if isn't already defined... NewCounter($counter, $within, idprefix => $idprefix); } DefMacroI(T_CS("\\the$counter"), undef, ($within ? "\\csname the" . $within . "\\endcsname\\\@thmcountersep" . $numbering . "{" . $counter . "}" : $numbering . "{" . $counter . "}"), scope => 'global') if $numbering; } my %parameters = %{ LookupValue('SAVABLE_THEOREM_PARAMETERS') }; saveTheoremStyle($thmset, map { ($_ => LookupValue($_)) } keys %parameters); my $thmname = '\\lx@name@' . $thmset; DefMacroI($thmname, undef, $type, scope => 'global'); my $swap = LookupValue('thm@swap'); DefMacroI('\fnum@' . $thmset, undef, Tokens($flag || !$counter ? (T_CS($thmname)) : ($swap ? (T_CS('\the' . $counter), ($type ? (T_SPACE) : ()), T_CS($thmname)) : (T_CS($thmname), ($type ? (T_SPACE) : ()), T_CS('\the' . $counter)))), scope => 'global'); # amsthm allows you to define your own title formatter # if there was one defined, use it, else define a new one. my $headformatter = LookupValue('\thm@headformatter'); DefMacroI('\format@title@' . $thmset, convertLaTeXArgs(1, 0), ($headformatter ? Tokens( T_CS('\the'), T_CS('\thm@headfont'), $headformatter->unlist, T_BEGIN, ($type ? $type->unlist : ()), T_END, T_CS('\the' . $counter), T_BEGIN, Tokens(T_PARAM, T_OTHER('1')), T_END, T_CS('\the'), T_CS('\thm@headpunct')) : '{\the\thm@headfont\lx@tag{\csname fnum@' . $thmset . '\endcsname}' . '{' . ($type ? '\ifx.#1.\else\space\the\thm@notefont(#1)\fi' : '#1') . '}' . '\the\thm@headpunct}'), scope => 'global'); DefEnvironmentI($thmset, "OptionalUndigested", "" . "#tags" . "#title" . "#body", afterConstruct => sub { $_[0]->maybeCloseElement('ltx:theorem'); }, beforeDigest => sub { useTheoremStyle($thmset); Digest('\normalfont\the\thm@prework'); }, afterDigestBegin => sub { my $name = $_[1]->getArg(1); Digest('\the\thm@bodyfont\the\thm@styling' . '\def\lx@thistheorem{' . ($name ? UnTeX($name) : '') . '}'); }, beforeDigestEnd => sub { Digest('\thm@doendmark'); }, afterDigest => sub { Digest('\the\thm@postwork'); }, properties => sub { my %ctr = (); if ($counter) { if ($flag) { %ctr = RefStepID($counter); # Need(?) the refnum if ($type) { $ctr{tags} = Digest(T_BEGIN, T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); } } else { %ctr = RefStepCounter($thmset); } } my $title = Digest(Tokens(T_BEGIN, T_CS('\the'), T_CS('\thm@headstyling'), T_CS('\format@title@' . $thmset), #T_END, T_BEGIN, ($_[1] ? $_[1]->unlist : ()), T_END, T_END)); (%ctr, title => $title, titlefont => $title->getFont, ); }, scope => 'global'); return; } #====================================================================== # C.8.4 Numbering #====================================================================== # For LaTeX documents, We want id's on para, as well as sectional units. # However, para get created implicitly on Document construction, rather than # explicitly during digestion (via a whatsit), we can't use the usual LaTeX counter mechanism. Tag('ltx:para', afterOpen => sub { GenerateID(@_, 'p'); }); DefPrimitive('\newcounter{}[]', sub { NewCounter(ToString(Expand($_[1])), $_[2] && ToString(Expand($_[2]))); return; }); Let(T_CS('\@definecounter'), T_CS('\newcounter')); DefPrimitive('\setcounter{}{Number}', sub { SetCounter(ToString(Expand($_[1])), $_[2]); }); DefPrimitive('\addtocounter{}{Number}', sub { AddToCounter(ToString(Expand($_[1])), $_[2]); }); DefPrimitive('\stepcounter{}', sub { StepCounter(ToString(Expand($_[1]))); return; }); DefPrimitive('\refstepcounter{}', sub { RefStepCounter(ToString(Expand($_[1]))); return; }); sub addtoCounterReset { my ($ctr, $within) = @_; my $reg = "\\cl\@$within"; my $t = T_CS($ctr); my $unt = T_CS('UN' . $ctr); my $prevreset = LookupValue($reg); AssignValue($reg => Tokens($t, $unt, ($prevreset ? $prevreset->unlist : ())), 'global'); return; } sub remfromCounterReset { my ($ctr, $within) = @_; my $reg = "\\cl\@$within"; my $t = T_CS($ctr); my $unt = T_CS('UN' . $ctr); my $prevreset = LookupValue($reg); AssignValue($reg => Tokens(grep { !$t->equals($_) && !$unt->equals($_) } $prevreset->unlist), 'global') if $prevreset; return; } sub defCounterID { my ($ctr, $within) = @_; my $prefix = LookupValue('@ID@prefix@' . $ctr); if (defined $prefix) { if ($within) { DefMacroI(T_CS("\\the$ctr\@ID"), undef, "\\expandafter\\ifx\\csname the$within\@ID\\endcsname\\\@empty" . "\\else\\csname the$within\@ID\\endcsname.\\fi" . " $prefix\\csname \@$ctr\@ID\\endcsname", scope => 'global'); } else { DefMacroI(T_CS("\\the$ctr\@ID"), undef, "$prefix\\csname \@$ctr\@ID\\endcsname", scope => 'global'); } } return; } DefPrimitive('\@addtoreset{}{}', sub { my ($stomach, $ctr, $within) = @_; $ctr = ToString(Expand($ctr)); $within = ToString(Expand($within)); addtoCounterReset($ctr, $within); defCounterID($ctr, $within) unless LookupDefinition(T_CS("\\the$ctr\@ID")); return; }); DefMacro('\value{}', sub { T_CS("\\c@" . ToString(Expand($_[1]))) }); DefMacro('\@arabic{Number}', sub { ExplodeText(ToString($_[1]->valueOf)); }); DefMacro('\arabic{}', sub { ExplodeText(CounterValue(ToString(Expand($_[1])))->valueOf); }); DefMacro('\@roman{Number}', sub { ExplodeText(radix_roman(ToString($_[1]->valueOf))); }); DefMacro('\roman{}', sub { ExplodeText(radix_roman(CounterValue(ToString(Expand($_[1])))->valueOf)); }); DefMacro('\@Roman{Number}', sub { ExplodeText(radix_Roman(ToString($_[1]->valueOf))); }); DefMacro('\Roman{}', sub { ExplodeText(radix_Roman(CounterValue(ToString(Expand($_[1])))->valueOf)); }); DefMacro('\@alph{Number}', sub { ExplodeText(radix_alpha($_[1]->valueOf)); }); DefMacro('\alph{}', sub { ExplodeText(radix_alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); }); DefMacro('\@Alph{Number}', sub { ExplodeText(radix_Alpha($_[1]->valueOf)); }); DefMacro('\Alph{}', sub { ExplodeText(radix_Alpha(CounterValue(ToString(Expand($_[1])))->valueOf)); }); our @fnsymbols = ("*", "\x{2020}", "\x{2021}", UTF(0xA7), UTF(0xB6), "\x{2225}", "**", "\x{2020}\x{2020}", "\x{2021}\x{2021}"); DefMacro('\@fnsymbol{Number}', sub { ExplodeText(radix_format($_[1]->valueOf, @fnsymbols)); }); DefMacro('\fnsymbol{}', sub { ExplodeText(radix_format(CounterValue(ToString(Expand($_[1])))->valueOf, @fnsymbols)); }); # The following two macros were originally defined in chngcntr.sty, # but were moved to LaTeX core in 2018. DefPrimitive('\counterwithin OptionalMatch:* {}{}', sub { my ($stomach, $starred, $ctr, $within) = @_; $ctr = ToString($ctr); $within = ToString($within); addtoCounterReset($ctr, $within); if (!$starred) { DefMacroI(T_CS("\\the$ctr"), undef, "\\the$within.\\arabic{$ctr}", scope => 'global'); defCounterID($ctr, $within); } return; }); DefPrimitive('\counterwithout OptionalMatch:* {}{}', sub { my ($stomach, $starred, $ctr, $within) = @_; $ctr = ToString($ctr); $within = ToString($within); remfromCounterReset($ctr, $within); if (!$starred) { DefMacroI(T_CS("\\the$ctr"), undef, "\\arabic{$ctr}", scope => 'global'); defCounterID($ctr); } return; }); DefPrimitive('\@removefromreset{}{}', sub { my ($stomach, $ctr, $within) = @_; $ctr = ToString($ctr); $within = ToString($within); remfromCounterReset($ctr, $within); # Should \the$ctr@ID be redefined? It really should track changes to \the$ctr! # defCounterID($ctr); return; }); DefMacroI('\cl@@ckpt', undef, '\@elt{page}'); #====================================================================== # C.8.5 The ifthen Package. #====================================================================== # \ifthenelse # and sundry conditionals... # # Yeah, maybe this'll get done someday.... #********************************************************************** # C.9 Figures and Other Floating Bodies #********************************************************************** #====================================================================== # C.9.1 Figures and Tables #====================================================================== # Note that, the number is associated with the caption. # (to allow multiple figures per figure environment?). # Whatever reason, that causes complications: We can only increment # counters with the caption, but then have to arrange for the counters, # refnums, ids, get passed on to the figure, table when needed. # AND, as soon as possible, since other items may base their id's on the id of the table! # Let the fonts for float be the default for all floats, figures, tables, etc. DefMacro('\fnum@font@float', '\@empty'); DefMacro('\format@title@font@float', '\@empty'); DefMacro('\fnum@font@figure', '\fnum@font@float'); DefMacro('\fnum@font@table', '\fnum@font@float'); DefMacro('\format@title@font@figure', '\format@title@font@float'); DefMacro('\format@title@font@table', '\format@title@font@float'); # stubs for the latex float internals for my $float (qw(@float @dblfloat)) { DefEnvironmentI($float, '[]{}', "" . "#body" . "", afterDigestBegin => sub { DefMacroI('\@captype', undef, ToString($_[1]->getArg(2))); return; }, afterDigest => sub { RescueCaptionCounters(ToString(Expand(T_CS('\@captype'))), $_[1]); return; }); } # Could perhaps parameterize further with a separator? DefMacro('\format@title@figure{}', '\lx@tag[][: ]{\lx@fnum@@{figure}}#1'); DefMacro('\format@title@table{}', '\lx@tag[][: ]{\lx@fnum@@{table}}#1'); DefMacro('\ext@figure', 'lof'); DefMacro('\ext@table', 'lot'); DefConditional('\iflx@donecaption'); DefMacro('\caption', '\lx@donecaptiontrue\@ifundefined{@captype}{\@@generic@caption}{\expandafter\@caption\expandafter{\@captype}}'); # First, check for trailing \label, move it into the caption as a standard position # NOTE: If one day we want to unlock \@caption, make sure to test against arXiv:cond-mat/0001395 for a passing build. DefMacro('\@caption{}[]{}', '\@ifnext\label{\@caption@postlabel{#1}{#2}{#3}}{\@caption@{#1}{#2}{#3}}', locked => 1); DefMacro('\@caption@postlabel{}{}{} SkipMatch:\label Semiverbatim', '\@caption@{#1}{#2}{#3\label{#4}}'); # Now, check for \label(s) inside to (potentially) record them. DefMacro('\@caption@{}{}{}', '\@hack@caption@{#1}{#2}{}#3\label\endcaption'); DefMacro('\@hack@caption@{}{}{} Until:\label Until:\endcaption', '\ifx.#5.\@caption@@@{#1}{#2}{#3#4}' # No more labels . '\else\@@@hack@caption@{#1}{#2}{#3#4}#5\endcaption\fi'); DefMacro('\@@@hack@caption@{}{}{} Semiverbatim Until:\label Until:\endcaption', '\lx@note@caption@label{#4}' . '\@hack@caption@{#1}{#2}{#3\label{#4}#5}\label#6\endcaption'); DefPrimitive('\lx@note@caption@label{}', sub { MaybeNoteLabel($_[1]); }); DefMacro('\@caption@@@{}{}{}', '\@@add@caption@counters' . '\@@toccaption{\lx@format@toctitle@@{#1}{\ifx.#2.#3\else#2\fi}}' . '\@@caption{\lx@format@title@@{#1}{#3}}'); # Note that the counters only get incremented by \caption, NOT by \table, \figure, etc. DefPrimitive('\@@add@caption@counters', sub { my $captype = ToString(Digest(T_CS('\@captype'))); my %props = RefStepCounter($captype); my $inlist = ToString(Digest(T_CS('\ext@' . $captype))); AssignValue($captype . "_tags" => $props{tags}, 'global'); AssignValue($captype . "_id" => $props{id}, 'global'); AssignValue($captype . "_inlist" => $inlist, 'global'); }); sub RescueCaptionCounters { my ($captype, $whatsit) = @_; if (my $tags = LookupValue($captype . "_tags")) { AssignValue($captype . "_tags" => undef, 'global'); $whatsit->setProperty(tags => $tags); } if (my $id = LookupValue($captype . "_id")) { AssignValue($captype . "_id" => undef, 'global'); $whatsit->setProperty(id => $id); } if (my $inlist = LookupValue($captype . "_inlist")) { AssignValue($captype . "_inlist" => undef, 'global'); $whatsit->setProperty(inlist => $inlist); } return; } DefConstructor('\@@generic@caption[]{}', "#2", 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

or similar. DefConstructor('\@@caption{}', "^^#1"); DefConstructor('\@@toccaption{}', "^^#1", sizer => '0'); DefEnvironment('{figure}[]', "" . "#tags" . "#body" . "", properties => { layout => 'vertical' }, beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); }, afterDigest => sub { RescueCaptionCounters('figure', $_[1]); }, locked => 1); DefEnvironment('{figure*}[]', "" . "#tags" . "#body" . "", properties => { layout => 'vertical' }, beforeDigest => sub { DefMacroI('\@captype', undef, 'figure'); }, afterDigest => sub { RescueCaptionCounters('figure', $_[1]); }, locked => 1); DefEnvironment('{table}[]', "" . "#tags" . "#body" . "", properties => { layout => 'vertical' }, beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); }, afterDigest => sub { RescueCaptionCounters('table', $_[1]); }, locked => 1); DefEnvironment('{table*}[]', "" . "#tags" . "#body" . "", properties => { layout => 'vertical' }, beforeDigest => sub { DefMacroI('\@captype', undef, 'table'); }, afterDigest => sub { RescueCaptionCounters('table', $_[1]); }, locked => 1); # There are some floating figure/table packages that define environments # to allow the figure to float left or right. It APPEARS that they allow # authors to either wrap with {figure} or NOT, as they like! # The result apparently makes sense so long as they only use one \caption. # If we get a figure containing a single figure, we want to collaps it. sub collapseFloat { my ($document, $float) = @_; my $qname = $document->getNodeQName($float); my @inners = $document->findnodes($qname, $float); if (scalar(@inners) == 1) { # Only 1 inner float of same type my $inner = $inners[0]; my $ocaption = $document->findnodes('ltx:caption', $float); my $icaption = $document->findnodes('ltx:caption', $inner); if (!($ocaption && $icaption)) { # If they don't both have captions. foreach my $attr (map { $_->nodeName } $inner->attributes) { next if $attr eq 'xml:id'; $document->setAttribute($float, $attr => $inner->getAttribute($attr)); } if ($icaption) { if (my $id = $inner->getAttribute('xml:id')) { $document->unRecordID($id); $document->setAttribute($float, 'xml:id' => $id); } } # Finally, replace $inner by it's children! # Note that since we can only append new stuff, we've got to remove the following first. my @save = (); my $following; while (($following = $float->lastChild) && ($$following != $$inner)) { # Remove & Save following siblings. unshift(@save, $float->removeChild($following)); } unshift(@save, $inner->childNodes); $float->removeChild($inner); map { $float->appendChild($_) } @save; } # Put these back. } return; } Tag('ltx:figure', afterClose => \&collapseFloat); Tag('ltx:table', afterClose => \&collapseFloat); Tag('ltx:float', afterClose => \&collapseFloat); DefPrimitive('\flushbottom', undef); DefPrimitive('\suppressfloats[]', undef); NewCounter('topnumber'); DefMacroI('\topfraction', undef, "0.25"); NewCounter('bottomnumber'); DefMacroI('\bottomfraction', undef, "0.25"); NewCounter('totalnumber'); DefMacroI('\textfraction', undef, "0.25"); DefMacroI('\floatpagefraction', undef, "0.25"); NewCounter('dbltopnumber'); DefMacroI('\dbltopfraction', undef, "0.7"); DefMacroI('\dblfloatpagefraction', undef, "0.25"); DefRegister('\floatsep' => Glue(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)); DefRegister('\abovecaptionskip' => Glue(0)); DefRegister('\belowcaptionskip' => Glue(0)); Let('\topfigrule', '\relax'); Let('\botfigrule', '\relax'); Let('\dblfigrule', '\relax'); DefMacroI('\figurename', undef, 'Figure'); DefMacroI('\figuresname', undef, 'Figures'); # Never used? DefMacroI('\tablename', undef, 'Table'); DefMacroI('\tablesname', undef, 'Tables'); Let('\outer@nobreak', '\@empty'); DefMacro('\@dbflt{}', '#1'); DefMacro('\@xdblfloat{}[]', '\@xfloat{#1}[#2]'); DefMacro('\@floatplacement', ''); DefMacro('\@dblfloatplacement', ''); #====================================================================== # C.9.2 Marginal Notes #====================================================================== AssignValue(marginparclass => undef); DefConstructor('\marginpar[]{}', "#2", properties => sub { (class => LookupValue('marginparclass')); }); DefPrimitiveI('\reversemarginpar', undef, sub { AssignValue(marginparclass => 'ltx_marginpar_reverse'); }); DefPrimitiveI('\normalmarginpar', undef, sub { AssignValue(marginparclass => 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, '\@tabbing@tabset@marker&'); DefMacroI('\@tabbing@nexttab', undef, '\@tabbing@nexttab@marker&'); DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]', '\@tabbing@newline@marker\cr'); DefMacroI('\@tabbing@kill', undef, '\@tabbing@kill@marker\cr\@tabbing@start@tabs'); DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\='); DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>'); DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR)); 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); DefMacro('\@tabbing@accent{}', sub { T_CS('\@tabbing@' . ToString($_[1])); }); # Should there be some rowsep/colsep set here? # Note that {tabbign} really shouldn't be handled by a tabular AT ALL.... # Should be recording accumulated widths and wrapping in ltx:text, with specified widths. sub tabbingBindings { AssignValue(Alignment => LaTeXML::Core::Alignment->new( template => LaTeXML::Core::Alignment::Template->new( repeated => [{ 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@' . "'", "\\'"); Let('\@tabbing@' . "`", "\\`"); Let('\a', '\@tabbing@accent'); Let("\\'", '\@tabbing@flushright'); Let("\\`", '\@tabbing@hfil'); Let('\pushtabs', '\@tabbing@pushtabs'); Let('\poptabs', '\@tabbing@poptabs'); return; } DefMacroI('\pushtabs', undef, Tokens()); DefMacroI('\poptabs', undef, Tokens()); DefMacroI('\kill', undef, Tokens()); DefPrimitiveI('\@tabbing@bindings', undef, sub { tabbingBindings(); }); # NOTE: Do it!! #====================================================================== # C.10.2 The array and tabular Environments #====================================================================== # Tabular are a bit tricky in that we have to arrange for tr and td to # be openned and closed at the right times; the only real markup is # the & and \\. Also \multicolumn has to be cooperative. # Along with this, we have to track which column specification applies # to the current column. # To simulate LaTeX's tabular borders & hlines, we simply add border # attributes to all cells. For HTML, CSS will be necessary to display them. # [We'll ignore HTML's frame, rules and colgroup mechanisms.] DefRegister('\lx@arstrut', Dimension('0pt')); DefRegister('\lx@default@tabcolsep', Dimension('6pt')); DefRegister('\tabcolsep', Dimension('6pt')); DefMacroI('\arraystretch', undef, "1"); Let('\@tabularcr', '\@alignment@newline'); AssignValue(GUESS_TABULAR_HEADERS => 1) # Defaults to yes unless defined LookupValue('GUESS_TABULAR_HEADERS'); sub tabularBindings { my ($template, %properties) = @_; $properties{guess_headers} = LookupValue('GUESS_TABULAR_HEADERS') unless defined $properties{guess_headers}; if (!defined $properties{attributes}{colsep}) { my $sep = LookupDimension('\tabcolsep'); if ($sep && ($sep->valueOf != LookupDimension('\lx@default@tabcolsep')->valueOf)) { $properties{attributes}{colsep} = $sep; } } if (!defined $properties{attributes}{rowsep}) { my $str = ToString(Expand(T_CS('\arraystretch'))); if ($str != 1) { $properties{attributes}{rowsep} = Dimension(($str - 1) . 'em'); } } if (!defined $properties{strut}) { $properties{strut} = LookupRegister('\baselineskip')->multiply(1.5); } # Account for html space alignmentBindings($template, 'text', %properties); Let("\\\\", '\@tabularcr'); Let('\tabularnewline', "\\\\"); # NOTE: Fit this back in!!!!!!! # # Do like AddToMacro, but NOT global! foreach my $name ('@row@before', '@row@after', '@column@before', '@column@after') { my $cs = '\\' . $name; DefMacroI($cs, undef, Tokens(LookupDefinition(T_CS($cs))->getExpansion->unlist, T_CS('\@tabular' . $name))); } return; } # Keyvals are for attributes for the alignment. # Typical keys are width, vattach,... DefKeyVal('tabular', 'width', 'Dimension'); DefPrimitive('\@tabular@bindings AlignmentTemplate OptionalKeyVals:tabular', sub { my ($stomach, $template, $attributes) = @_; my %attr = ($attributes ? $attributes->getPairs : ()); if (my $va = $attr{vattach}) { $attr{vattach} = translateAttachment($va) || ToString($va); } tabularBindings($template, attributes => {%attr}); return; }); DefMacroI('\@tabular@before', undef, ''); DefMacroI('\@tabular@after', undef, ''); DefMacroI('\@tabular@row@before', undef, ''); DefMacroI('\@tabular@row@after', undef, ''); DefMacroI('\@tabular@column@before', undef, ''); DefMacroI('\@tabular@column@after', undef, ''); # The Core alignment support is in LaTeXML::Core::Alignment and in TeX.ltxml ##DefMacro('\tabular[]{}', '\@tabular@bindings{#2}\@@tabular[#1]{#2}\@start@alignment'); DefMacro('\tabular[]{}', '\@tabular@bindings{#2}[vattach=#1]\@@tabular[#1]{#2}\@start@alignment\@tabular@before', locked => 1); DefMacroI('\endtabular', undef, '\@tabular@after\@finish@alignment\@end@tabular', locked => 1); DefPrimitiveI('\@end@tabular', undef, sub { $_[0]->egroup; }); #DefMacroI('\@end@tabular', undef, undef); # Note that the pattern will already have been interpreted by \@tabular@bindings, # so make it Undigested here! DefConstructor('\@@tabular[] Undigested DigestedBody', '#3', reversion => '\begin{tabular}[#1]{#2}#3\end{tabular}', beforeDigest => sub { $_[0]->bgroup; }, sizer => '#3', afterDigest => sub { my ($stomach, $whatsit) = @_; if (my $alignment = LookupValue('Alignment')) { my $attr = $alignment->getProperty('attributes'); $$attr{vattach} = translateAttachment($whatsit->getArg(1)); } return; }, 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{}', '', DefMacro('\cline{}', '\noalign{\@cline{#1}}'); DefConstructor('\@cline{}', '', afterDigest => sub { my ($stomach, $whatsit) = @_; my $cols = ToString($whatsit->getArg(1)); my @cols = (); while ($cols =~ s/^,?(\d+)//) { my $n = $1; push(@cols, ($cols =~ s/^-(\d+)// ? ($n .. $1) : ($n))); } my $alignment = LookupValue('Alignment'); $alignment->addLine('t', @cols) if $alignment; return; }, sizer => 0, properties => { isHorizontalRule => 1 }); DefConstructorI('\vline', undef, "", # ??? properties => { isVerticalRule => 1 }, sizer => 0, ); DefRegister('\lx@default@arraycolsep', Dimension('5pt')); DefRegister('\arraycolsep', Dimension('5pt')); DefRegister('\arrayrulewidth', Dimension('0.4pt')); DefRegister('\doublerulesep', Dimension('2pt')); DefMacro('\extracolsep{}', Tokens()); #DefMacroI('\tabularnewline', undef, Tokens()); #Let('\tabularnewline', '\cr'); #DefMacroI('\tabularnewline', undef, '\cr'); # ??? #====================================================================== # Array and similar environments DefPrimitive('\@array@bindings [] AlignmentTemplate', sub { my ($stomach, $pos, $template) = @_; my $attr = { vattach => translateAttachment($pos), role => 'ARRAY' }; # Determine column and row separations, if non default my $colsep = LookupDimension('\arraycolsep'); if ($colsep && ($colsep->valueOf != LookupDimension('\lx@default@arraycolsep')->valueOf)) { $$attr{colsep} = $colsep; } my $str = ToString(Expand(T_CS('\arraystretch'))); if ($str != 1) { $$attr{rowsep} = Dimension(($str - 1) . 'em'); } alignmentBindings($template, 'math', attributes => $attr); MergeFont(mathstyle => 'text'); Let("\\\\", '\@alignment@newline'); return; }); DefMacro('\array[]{}', '\@array@bindings[#1]{#2}\@@array[#1]{#2}\@start@alignment'); DefMacroI('\endarray', undef, '\@finish@alignment\@end@array'); DefPrimitiveI('\@end@array', undef, sub { $_[0]->egroup; }); DefConstructor('\@@array[] Undigested DigestedBody', '#3', beforeDigest => sub { $_[0]->bgroup; }, reversion => '\begin{array}[#1]{#2}#3\end{array}'); DefMacro('\@tabarray', '\m@th\@@array[c]'); #********************************************************************** # C.11 Moving Information Around #********************************************************************** #====================================================================== # C.11.1 Files #====================================================================== DefPrimitive('\nofiles', undef); #====================================================================== # C.11.2 Cross-References #====================================================================== # \label attaches a label to the nearest parent that can accept a labels attribute # but only those that have an xml:id (but should this require a refnum and/or title ???) # Note that latex essentially allows redundant labels, but we can record only one!!! DefConstructor('\label Semiverbatim', sub { my ($document, $olabel, %props) = @_; my $label = $props{label}; if (my $savenode = $document->floatToLabel) { my $node = $document->getNode; my %labels = map { ($_ => 1) } $label, split(/\s+/, $node->getAttribute('labels') || ''); $document->setAttribute($node, labels => join(' ', sort keys %labels)); $document->setNode($savenode); } }, reversion => '', properties => { alignmentSkippable => 1, alignmentPreserve => 1 }, afterDigest => sub { MaybeNoteLabel($_[1]->getArg(1)); my $label = CleanLabel(ToString($_[1]->getArg(1))); $_[1]->setProperty(label => $label); my $scope = $label; $scope =~ s/^LABEL:/label:/; if (my $ctr = LookupValue('current_counter')) { unshift(@{ LookupValue('scopes_for_counter:' . $ctr) }, $scope); $STATE->activateScope($scope); $_[0]->beginMode('text'); AssignValue('LABEL@' . $label, Digest(T_CS('\@currentlabel')), 'global'); $_[0]->endMode('text'); } return; }); # If a node has been labeled, but still hasn't yet got an id by afterClose:late, # we'd better generate an id for it. Tag('ltx:*', 'afterClose:late' => sub { my ($document, $node) = @_; if ($node->hasAttribute('labels') && !($node->hasAttribute('xml:id'))) { GenerateID($document, $node); } }); # These will get filled in during postprocessing. # * is added to accommodate hyperref DefConstructor('\ref OptionalMatch:* Semiverbatim', "", properties => sub { (label => CleanLabel($_[2])); }); DefConstructor('\pageref OptionalMatch:* Semiverbatim', "", # 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()); # Do this before digesting the body of a bibliography sub beforeDigestBibliography { AssignValue(inPreamble => 0); Digest('\@lx@inbibliographytrue'); DefMacro('\bibliographystyle{}', ''); DefMacro('\bibliography {}', ''); DefConstructorI('\endthebibliography', undef, "", locked => 1); ResetCounter('@bibitem'); return; } # This sub does things that would commonly be needed when starting a bibliography # setting the ID, etc... sub beginBibliography { my ($whatsit) = @_; beginBibliography_clean($whatsit); # Fix for missing \bibitems! setupPseudoBibitem(); return; } sub beginBibliography_clean { my ($whatsit) = @_; # Check if \bibsection is defined and try to decipher it. # Expecting something like \section*{sometext} my $bibtitle = undef; my $bs = LookupDefinition(T_CS('\bibsection')); # FIXED: Sometimes $bs may be a Primitive. # Now guarding for that case or we get a perl:die for ->getExpansion if (my @t = $bs && ($bs->isExpandable ? $bs->getExpansion->unlist : $bs)) { our %bibunitmap = ( '\\part' => 'ltx:part', '\\chapter' => 'ltx:chapter', '\\section' => 'ltx:section', '\\subsection' => 'ltx:subsection', '\\subsubsection' => 'ltx:subsubsection', '\\paragraph' => 'ltx:paragraph', '\\subparagraph' => 'ltx:subparagraph'); if (my $unit = $bibunitmap{ ToString(shift(@t)) }) { # Found a sectional unit command. AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => $unit); if (ToString($t[0]) eq '*') { shift(@t); } # Check for balanced? or just take balanced begining? $bibtitle = Tokens(@t); } } noteBackmatterElement($whatsit, 'ltx:bibliography'); # Try to compute a reasonable, but unique ID; # relative to the document's ID, if any. # But also, if there are multiple bibliographies, my $bibnumber = LookupValue('n_bibliographies') || 0; AssignValue(n_bibliographies => ++$bibnumber, 'global'); my $docid = ToString(Expand(T_CS('\thedocument@ID'))); my $bibid = ($docid ? $docid . '.' : '') . 'bib' . radix_alpha($bibnumber - 1); DefMacroI(T_CS('\thebibliography@ID'), undef, T_OTHER($bibid), scope => 'global'); # $whatsit->setProperty(id=>ToString(Expand(T_CS('\thebibliography@ID')))); $whatsit->setProperty(id => $bibid); my $title = ($bibtitle ? Digest($bibtitle) : DigestIf('\refname') || DigestIf('\bibname')); $whatsit->setProperty(title => $title) if $title; $whatsit->setProperty(titlefont => $title->getFont) if $title; $whatsit->setProperty(bibstyle => LookupValue('BIBSTYLE')); $whatsit->setProperty(citestyle => LookupValue('CITE_STYLE')); # $whatsit->setProperty(sort=> ??? # And prepare for the likely nonsense that appears within bibliographies ResetCounter('enumiv'); return; } DefMacro('\bibliography Semiverbatim', '\lx@ifusebbl{#1}{\input{\jobname.bbl}}{\lx@bibliography{#1}}'); DefMacro('\lx@ifusebbl{}{}{}', sub { my ($gullet, $bib_files, $bbl_clause, $bib_clause) = @_; $bib_files = ToString(Expand($bib_files)); return unless $bib_files; my $jobname = ToString(Expand(T_CS('\jobname'))); my $bbl_path = FindFile($jobname, type => 'bbl'); my $missing_bibs = ''; if (LookupValue("NO_BIBTEX")) { if (not $bbl_path) { Info('expected', "bbl", $_[0], "Couldn't find bbl file, bibliography may be empty."); return Tokens(); } else { return $bbl_clause->unlist; } } else { for my $bf (split(',', $bib_files)) { my $bib_path = FindFile($bf, type => 'bib'); if (not $bib_path) { $missing_bibs .= ',' unless length($missing_bibs) == 0; $missing_bibs .= $bf; } } if (length($missing_bibs) == 0 or not $bbl_path) { return $bib_clause->unlist; } else { Info('expected', $missing_bibs, $_[0], "Couldn't find all bib files, using " . $jobname . ".bbl instead"); return $bbl_clause->unlist; } } }); AssignMapping('BACKMATTER_ELEMENT', 'ltx:bibliography' => 'ltx:section'); AssignMapping('BACKMATTER_ELEMENT', 'ltx:index' => 'ltx:section'); sub noteBackmatterElement { my ($whatsit, $backelement) = @_; $whatsit->setProperties(backmatterelement => LookupMapping('BACKMATTER_ELEMENT', $backelement)); return; } sub adjustBackmatterElement { my ($document, $whatsit) = @_; if (my $asif = $whatsit->getProperty('backmatterelement')) { $document->setNode($document->find_insertion_point($asif)); } return; } DefConstructor('\lx@bibliography [] Semiverbatim', "" . "#title" . "", afterDigest => sub { $_[0]->begingroup; # wrapped so redefns don't take effect! beginBibliography($_[1]); $_[0]->endgroup; }, beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); }); # NOTE: This totally needs to be made extensible (parsing *.bst!?!? OMG!) our $BIBSTYLES = { plain => { citestyle => 'numbers', sort => 'true' }, unsrt => { citestyle => 'numbers', sort => 'false' }, alpha => { citestyle => 'AY', sort => 'true' }, abbrv => { citestyle => 'numbers', sort => 'true' }, plainnat => { citestyle => 'numbers', sort => 'true' }, unsrtnat => { citestyle => 'numbers', sort => 'false' }, alphanat => { citestyle => 'AY', sort => 'true' }, abbrvnat => { citestyle => 'numbers', sort => 'true' } }; sub setBibstyle { my ($style) = @_; $style = ToString($style); AssignValue(BIBSTYLE => $style); if (my $parms = $$BIBSTYLES{$style}) { AssignValue(CITE_STYLE => $$parms{citestyle}); AssignValue(CITE_SORT => $$parms{sort}); } return; } DefConstructor('\bibstyle{}', sub { my ($document, $style) = @_; setBibstyle($style); # Really ? if (my $bib = $document->findnode('//ltx:bibliography')) { $document->setAttribute($bib, bibstyle => LookupValue('BIBSTYLE')); $document->setAttribute($bib, citestyle => LookupValue('CITE_STYLE')); $document->setAttribute($bib, sort => LookupValue('CITE_SORT')); } }, afterDigest => sub { my $style = ToString($_[1]->getArg(1)); AssignValue(BIBSTYLE => $style, 'global'); if (my $parms = $$BIBSTYLES{$style}) { AssignValue(CITE_STYLE => $$parms{citestyle}); } else { Info('unexpected', $style, $_[0], "Unknown bibstyle '$style', it will be ignored"); } return; }); DefMacro('\bibliographystyle Semiverbatim', '\bibstyle{#1}'); DefConditional('\if@lx@inbibliography'); # Should be an environment, but people seem to want to misuse it. DefConstructorI('\thebibliography', undef, "" . "#title" . "", beforeDigest => sub { beforeDigestBibliography(); }, afterDigest => sub { my ($stomach, $whatsit) = @_; # NOTE that in some perverse situations (revtex?) # it seems to be allowable to omit the argument # It's ignorable for latexml anyway, so we'll just read it if its there . my $gullet = $stomach->getGullet; $gullet->skipSpaces; $gullet->readArg if $gullet->ifNext(T_BEGIN); beginBibliography($_[1]); }, beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); }, locked => 1); # Close the bibliography DefConstructorI('\endthebibliography', undef, "", 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'); Let('\vskip', '\vskip@in@bibliography'); # Moreover, some people use \item instead of \bibitem Let('\item', '\item@in@bibliography'); # And protect from redefinitions. Let('\newblock', '\lx@bibnewblock'); return; } DefMacroI('\par@in@bibliography', undef, sub { my ($gullet) = @_; $gullet->skipSpaces; my $tok = $gullet->readToken; # If next token is another \par, or a REAL \bibitem, if (Equals($tok, T_CS('\par')) || Equals($tok, T_CS('\bibitem'))) { ($tok); } # then this \par expands into what followed else { # Else, put it back, and start a bibitem. $gullet->unread($tok); (T_CS('\save@bibitem'), T_BEGIN, T_END); } }); DefMacro('\vskip@in@bibliography Glue', undef); DefMacroI('\item@in@bibliography', undef, '\save@bibitem{}'); # If we hit a real \bibitem, put \par & \bibitem back to correct defn, and then \bibitem. # A bibitem with now key or label... DefMacro('\restoring@bibitem', '\let\bibitem\save@bibitem\let\par\save@par\bibitem'); NewCounter('@bibitem', 'bibliography', idprefix => 'bib'); DefMacroI('\the@bibitem', undef, '\arabic{@bibitem}'); DefMacro('\@biblabel{}', '[#1]'); DefMacroI('\fnum@@bibitem', undef, '{\@biblabel{\the@bibitem}}'); # Hack for abused bibliographies; see below DefMacro('\bibitem', '\if@lx@inbibliography\else\expandafter\lx@mung@bibliography\expandafter{\@currenvir}\fi' . '\lx@bibitem', locked => 1); DefConstructor('\lx@bibitem[] Semiverbatim', "" . "#tags" . "", afterDigest => sub { my $tag = $_[1]->getArg(1); my $key = CleanBibKey($_[1]->getArg(2)); if ($tag) { $_[1]->setProperties(key => $key, RefStepID('@bibitem'), tags => Digest(T_BEGIN, T_CS('\def'), T_CS('\the@bibitem'), T_BEGIN, Revert($tag), T_END, Invocation(T_CS('\lx@make@tags'), T_OTHER('@bibitem')), T_END)); } else { $_[1]->setProperties(key => $key, RefStepCounter('@bibitem')); } }); # This attempts to handle the case where folks put \bibitem's within an enumerate or such. # We try to close the list and open the bibliography DefMacro('\lx@mung@bibliography{}', sub { my ($gullet, $env) = @_; my $tag = ToString($env); my @tokens = (); # If we're in some sort of list environment, maybe we can recover if (($tag eq 'enumerate') || ($tag eq 'itemize') || ($tag eq 'description')) { # nDamn! We're in a list {$tag}; try to close it! push(@tokens, Invocation('\end', $env), T_CS('\let'), T_CS('\end' . $tag), T_CS('\endthebibliography'), T_CS('\let'), T_CS('\end{' . $tag . '}'), T_CS('\end{thebibliography}')); } # else ? it probably isn't going to work?? # Now, try to open {thebibliography} push(@tokens, Invocation('\begin', Tokenize('thebibliography'), Tokens())); return Tokens(@tokens); }); DefConstructorI('\lx@bibnewblock', undef, ""); Let('\newblock', '\lx@bibnewblock'); 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. # contains everything that the citations produce, # including parens, pre-note, punctunation that precede the # and punctuation, post-note, parens, that follow it. # phrases # 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 # This format is used as follows: # If author and year is present, and a subset of the citations share the same authors, # then the format is used, but the year is repeated for each citation in the subset, # as a link to the bib entry. # Otherwise, the format is applied to each entry. # # The design is intended to support natbib, as well as plain LaTeX. AssignValue(CITE_STYLE => 'numbers'); AssignValue(CITE_OPEN => T_OTHER('[')); AssignValue(CITE_CLOSE => T_OTHER(']')); AssignValue(CITE_SEPARATOR => T_OTHER(',')); AssignValue(CITE_YY_SEPARATOR => T_OTHER(',')); AssignValue(CITE_NOTE_SEPARATOR => T_OTHER(',')); AssignValue(CITE_UNIT => undef); DefMacro('\@cite{}{}', '[{#1\if@tempswa , #2\fi}]'); DefConstructor('\@@cite []{}', "#2", mode => 'text'); # \@@bibref{what to show}{bibkeys}{phrase1}{phrase2} DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}', "#3#4", properties => sub { (bibrefs => CleanBibKey($_[2]), separator => ToString(Digest(LookupValue('CITE_SEPARATOR'))), yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR'))), bibunit => LookupValue('CITE_UNIT')); }); # Simple container for any phrases used in the bibref DefConstructor('\@@citephrase{}', "#1", 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'), undef, $keys, undef, undef), ($post ? ($ns, T_SPACE, $post) : ()), $close)); }); # NOTE: Eventually needs to be recognized by MakeBibliography DefConstructor('\nocite Semiverbatim', "", properties => sub { (bibrefs => CleanBibKey($_[1]), bibunit => LookupValue('CITE_UNIT')); }); #====================================================================== # C.11.4 Splitting the input #====================================================================== Let('\@@input', '\input'); # Save TeX's version. # LaTeX's \input is a bit different... DefMacroI('\input', undef, '\@ifnextchar\bgroup\@iinput\@@input'); DefPrimitive('\@iinput {}', sub { Input(Expand($_[1])); }); DefMacro('\@input{}', '\IfFileExists{#1}{\@@input\@filef@und}{\typeout{No file #1.}}'); DefMacro('\@input@{}', '\InputIfFileExists{#1}{}{\typeout{No file #1.}}'); DefMacro('\quote@name{}', '"\quote@@name#1\@gobble""'); DefMacro('\quote@@name{} Match:"', '#1\quote@@name'); DefMacro('\unquote@name{}', '\quote@@name#1\@gobble"'); # Note that even excluded files SHOULD have the effects of their inclusion # simulated by having read the corresponding aux file; # But we're not bothering with that. DefPrimitive('\include{}', sub { my ($stomach, $path) = @_; $path = ToString($path); my $table = LookupValue('including@only'); if (!$table || $$table{$path}) { Input($path); } return; }); # [note, this will load name.tex, if it exists, else name] DefPrimitive('\includeonly{}', sub { my ($stomach, $paths) = @_; $paths = ToString($paths); my $table = LookupValue('including@only'); AssignValue('including@only', $table = {}, 'global') unless $table; map { $$table{$_} = 1 } map { /^\s*(.*?)\s*$/ && $1; } split(/,/, $paths); return; }); # NOTE: In the long run, we want to SAVE the contents and associate them with the given file name # AND, arrange so that when a file is read, we'll use the contents! DefConstructorI(T_CS("\\begin{filecontents}"), "Semiverbatim", '', reversion => '', afterDigest => [sub { my ($stomach, $whatsit) = @_; my $filename = ToString($whatsit->getArg(1)); my @lines = (); my $gullet = $stomach->getGullet; my $line; while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents}')) { push(@lines, $line); } AssignValue($filename . '_contents' => join("\n", @lines), 'global'); NoteLog("Cached filecontents for $filename (" . scalar(@lines) . " lines)"); }]); DefConstructorI(T_CS("\\begin{filecontents*}"), "Semiverbatim", '', reversion => '', afterDigest => [sub { my ($stomach, $whatsit) = @_; my $filename = ToString($whatsit->getArg(1)); my @lines = (); my $gullet = $stomach->getGullet; my $line; while (defined($line = $gullet->readRawLine) && ($line ne '\end{filecontents*}')) { push(@lines, $line); } AssignValue($filename . '_contents' => join("\n", @lines), 'global'); NoteLog("Cached filecontents* for $filename (" . scalar(@lines) . " lines)"); }]); DefMacro('\endfilecontents', ''); DefPrimitive('\listfiles', undef); #====================================================================== # C.11.5 Index and Glossary #====================================================================== # ---- The index commands # Format of Index entries: # \index{entry!entry} gives multilevel index # Each entry: # foo@bar sorts on "foo" but prints "bar" # The entries can end with a |expression: # \index{...|(} this page starts a range for foo # \index{...|)} this page ends a range # The last two aren't handled in any particular way. # We _could_ mark start & end, and then the postprocessor would # need to fill in all likely links... ??? # \index{...|see{key}} cross reference. # \index{...|seealso{key}} cross reference. # \index{...|textbf} (etc) causes the number to be printed in bold! # # I guess the formula is that # \index{foo|whatever{pi}{pa}{po}} => \whatever{pi}{pa}{po}{page} # How should this get interpreted?? our %index_style = (textbf => 'bold', bf => 'bold', textrm => '', rm => '', textit => 'italic', it => 'italic', emph => 'italic'); # What else? # A bit screwy, but.... # Expand \index{a!b!...} into \@index{\@indexphrase{a}\@indexphrase{b}...} sub process_index_phrases { my ($gullet, $phrases, $inlist) = @_; my @expansion = (); # 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 || $inlist ? (T_OTHER('['), ($style ? T_OTHER($style) : ()), T_OTHER(']')) : ()), ($inlist ? (T_OTHER('['), T_OTHER($inlist), T_OTHER(']')) : ()), T_BEGIN, @expansion, T_END); return (T_BEGIN, T_CS('\normalfont'), @expansion, T_END); } 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[][]{}', "^#3", mode => 'text', reversion => '', sizer => 0); DefConstructor('\@indexphrase[]{}', "#2", properties => { key => sub { CleanIndexKey($_[1]); } }); DefConstructor('\@indexsee{}', "#1", properties => { name => sub { DigestIf('\seename') } }); DefConstructor('\@indexseealso{}', "#1", properties => { name => sub { DigestIf('\alsoname') } }); DefConstructor('\glossary{}', "#1", 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}', "" . "#title" . "#body" . "", beforeDigest => sub { Let('\item', '\index@item'); Let('\subitem', '\index@subitem'); Let('\subsubitem', '\index@subsubitem'); Let('\dotfill', '\index@dotfill'); }, beforeDigestEnd => sub { Digest(T_CS('\index@done')); }, afterDigestBegin => sub { my $docid = ToString(Expand(T_CS('\thedocument@ID'))); my $title = DigestIf('\indexname'); noteBackmatterElement($_[1], 'ltx:index'); $_[1]->setProperties(id => ($docid ? "$docid.idx" : 'idx'), title => $title, titlefont => $title->getFont); }, beforeConstruct => sub { adjustBackmatterElement($_[0], $_[1]); }); DefPrimitiveI('\indexspace', undef, undef); DefPrimitiveI('\makeindex', undef, undef); DefPrimitiveI('\makeglossary', undef, undef); #====================================================================== # C.11.6 Terminal Input and Output #====================================================================== DefPrimitive('\typeout{}', sub { my ($stomach, $stuff) = @_; Note(ToString(Expand($stuff))); return; }); DefPrimitive('\typein[]{}', undef); #********************************************************************** # C.12 Line and Page Breaking #********************************************************************** #====================================================================== # C.12.1 Line Breaking #====================================================================== DefPrimitive('\linebreak[]', undef); DefPrimitive('\nolinebreak[]', undef); DefPrimitiveI('\-', undef, undef); # We don't do hyphenation. # \hyphenation in TeX.pool DefPrimitiveI('\sloppy', undef, undef); DefPrimitiveI('\fussy', undef, undef); # sloppypar can be used as an environment, or by itself. DefMacro('\sloppypar', '\par\sloppy'); DefMacro('\endsloppypar', '\par'); DefMacroI('\nobreakdashes', undef, T_OTHER('-')); DefMacro('\showhyphens{}', '#1'); # ? #====================================================================== # C.12.2 Page Breaking #====================================================================== DefMacro('\pagebreak[]', '\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('\newlength DefToken', sub { my ($stomach, $cs) = @_; DefRegisterI($cs, undef, Glue(0)); }); DefPrimitive('\setlength {Variable}{Dimension}', sub { my ($stomach, $variable, $length) = @_; my ($defn, @params) = @$variable; if (ref $defn) { $defn->setValue($length, @params); } return; }); DefPrimitive('\addtolength {Variable}{Dimension}', sub { my ($stomach, $variable, $length) = @_; my ($defn, @params) = @$variable; if (ref $defn) { my $oldlength = $defn->valueOf(@params); $defn->setValue($defn->valueOf(@params)->add($length), @params); } return; }); DefMacro('\@settodim{}{}{}', '\setbox\@tempboxa\hbox{{#3}}#2#1\@tempboxa\setbox\@tempboxa\box\voidb@x'); DefMacroI('\settoheight', undef, '\@settodim\ht'); DefMacroI('\settodepth', undef, '\@settodim\dp'); DefMacroI('\settowidth', undef, '\@settodim\wd'); DefMacro('\@settopoint{}', '\divide#1\p@\multiply#1\p@'); # 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 #====================================================================== DefPrimitive('\hspace OptionalMatch:* {Dimension}', sub { my ($stomach, $star, $length) = @_; my $s = DimensionToSpaces($length); return unless defined $s; Box($s, undef, undef, Invocation(T_CS('\hskip'), $length), width => $length, isSpace => 1); }); DefPrimitive('\vspace OptionalMatch:* {}', undef); DefPrimitive('\addvspace {}', undef); DefPrimitive('\addpenalty {}', undef); # \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 {}', "#1", mode => 'text', bounded => 1, sizer => '#1', robust => 1, beforeDigest => sub { reenterTextMode(); }); our %makebox_alignment = (l => 'left', r => 'right', s => 'justified'); DefMacro('\makebox', '\@ifnextchar(\pic@makebox\@makebox', robust => 1); DefConstructor('\@makebox[Dimension][]{}', "#3", mode => 'text', bounded => 1, alias => '\makebox', sizer => '#3', beforeDigest => sub { reenterTextMode(); }, properties => sub { (($_[2] ? (align => $makebox_alignment{ ToString($_[2]) }) : ()), ($_[1] ? (width => $_[1]) : ())) }); DefRegister('\fboxrule', Dimension('.4pt')); DefRegister('\fboxsep', Dimension('3pt')); # Peculiar special case! # These are nominally text mode macros. However, there is a somewhat common idiom: # $ ... \framebox{$operator$} ... $ # in which case the operator gets boxed and really should be treated as a math object. # (and ultimately converted to mml:menclose) # So, we need to switch to text mode, as usual, but FIRST note whether we started in math mode! # Afterwards, if we were in math mode, and the content is math, we'll convert the whole thing # to a framed math object. # Second special issue: # Although framebox doesn't allow flowed content inside, it is also somewhat common # to put a vbox or some other block construct inside. # Seemingly, the ultimate html gets somewhat tangled (browser bugs?) # At any rate, since we're wrapping with an ltx:text, we'll try to unwrap it, # if the contents are a single child that can handle the framing. DefMacro('\fbox{}', '\@framebox{#1}', robust => 1); DefMacro('\framebox', '\@ifnextchar(\pic@framebox\@framebox', robust => 1); DefConstructor('\@framebox[Dimension][]{}', "?#mathframe(#inner)" . "(#3)", 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); } } } } ); DefPrimitive('\newsavebox DefToken', sub { my $n = LookupValue('allocated_boxes') + 1; AssignValue(allocated_boxes => $n, 'global'); DefRegisterI($_[1], undef, Number($n)); AssignValue('box' . $n, List()); }); RawTeX(<<'EOL'); \def\newsavebox#1{\@ifdefinable{#1}{\newbox#1}} \DeclareRobustCommand\savebox[1]{% \@ifnextchar(%) {\@savepicbox#1}{\@ifnextchar[{\@savebox#1}{\sbox#1}}}% \DeclareRobustCommand\sbox[2]{\setbox#1\hbox{% \color@setgroup#2\color@endgroup}} \def\@savebox#1[#2]{% \@ifnextchar [{\@isavebox#1[#2]}{\@isavebox#1[#2][c]}} \long\def\@isavebox#1[#2][#3]#4{% \sbox#1{\@imakebox[#2][#3]{#4}}} \def\@savepicbox#1(#2,#3){% \@ifnextchar[%] {\@isavepicbox#1(#2,#3)}{\@isavepicbox#1(#2,#3)[]}} \long\def\@isavepicbox#1(#2,#3)[#4]#5{% \sbox#1{\@imakepicbox(#2,#3)[#4]{#5}}} \def\lrbox#1{% \edef\reserved@a{% \endgroup \setbox#1\hbox{% \begingroup\aftergroup}% \def\noexpand\@currenvir{\@currenvir}% \def\noexpand\@currenvline{\on@line}}% \reserved@a \@endpefalse \color@setgroup \ignorespaces} \def\endlrbox{\unskip\color@endgroup} \DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax} EOL # A soft sorta \par that only closes an ltx:p, but not ltx:para DefConstructor('\lx@parboxnewline[]', sub { my ($document, $ignore, %props) = @_; $document->maybeCloseElement('ltx:p'); return; }, properties => { isBreak => 1 }); # 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[] OptionalUndigested OptionalUndigested {Dimension} VBoxContents', sub { my ($document, $attachment, $b, $c, $width, $body, %props) = @_; insertBlock($document, $body, width => $width, vattach => $props{vattach}, class => 'ltx_parbox'); return; }, sizer => '#5', properties => sub { (width => $_[4], vattach => translateAttachment($_[1]), height => $_[2]); }, mode => 'text', bounded => 1, robust => 1, beforeDigest => sub { AssignValue('\hsize' => $_[4]); Let('\\\\', '\lx@parboxnewline'); }); DefMacroI('\@parboxrestore', undef, Tokens()); DefConditional('\if@minipage'); DefEnvironment('{minipage}[][][]{Dimension}', sub { my ($document, $attachment, $b, $c, $width, %props) = @_; my $vattach = translateAttachment($attachment); insertBlock($document, $props{body}, para => 1, width => $width, vattach => $vattach, class => 'ltx_minipage'); return; }, sizer => '#body', 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('\\\\', '\lx@parboxnewline'); }); DefConstructor('\rule[Dimension]{Dimension}{Dimension}', "", properties => sub { (offset => $_[1], width => $_[2], height => $_[3]) }); DefConstructor('\raisebox{Dimension}[Dimension][Dimension]{}', "#4", 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 else { return LaTeXML::Core::Parameter::checkReaderFunction('Read' . $type); } } # This defines a Pair parameter type, # that reads a parenthesized, comma separated pair of subtype. # By default, the subtype is Float, but you can write Pair:Number, or Pair:Dimension sub ReadPair { my ($gullet, $itemtype, $xarg, $yarg) = @_; my $itemreader; if (!$itemtype) { $itemreader = \&ReadFloat; } else { $itemreader = ResolveReader($itemtype); } if (!$itemreader) { Error('misdefined', $itemtype, $gullet, "Can't find reader for Pair items from '$itemtype'"); return Pair(Dimension(0), Dimension(0)); } # Assume something like this? $gullet->skipSpaces; if ($gullet->ifNext(T_OTHER('('))) { $gullet->readToken; $gullet->skipSpaces; if ($gullet->ifNext(T_OTHER('!'))) { # maybe only for pstricks??? Error('unexpected', 'postscript', $gullet, "Cannot process escape to postscript"); $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces; return Pair(Dimension(0), Dimension(0)); } my $x = &$itemreader($gullet, $xarg); # This had also recognized ";" as a possible separator; if needed, will need to generalize # but we want to make readUntil accept only a single match $gullet->skipSpaces; $gullet->readUntil(T_OTHER(',')); $gullet->skipSpaces; my $y = &$itemreader($gullet, $yarg); $gullet->skipSpaces; $gullet->readUntil(T_OTHER(')')); $gullet->skipSpaces; return Pair($x, $y); } else { return; } } sub ptValue { my ($value) = @_; return ((defined $value) && (ref $value) ? $value->ptValue : $value); } sub pxValue { my ($value) = @_; return ((defined $value) && (ref $value) ? $value->pxValue : $value); } #---------------------------------------------------------------------- # Picture parameters. DefRegister('\unitlength', Dimension('1pt')); DefPrimitiveI('\thinlines', undef, sub { 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)); } # Scale $value by \unitlength sub picScale { my ($value) = @_; # Vain attempt at proper type coercion my $type = ref $value; my $unit = LookupValue('\unitlength'); if (!$value) { } elsif ($type eq 'LaTeXML::Common::Number') { return $unit->multiply($value); } elsif ($type eq 'LaTeXML::Common::Float') { return $unit->multiply($value); } elsif ($type eq 'LaTeXML::Common::Pair') { return Pair($unit->multiply($value->getX), $unit->multiply($value->getY)); } return $value->multiply($unit); } sub picProperties { my (%props) = @_; if (($props{stroke} || '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('\raisebox', '\pic@raisebox'); # ? needs special treatment within picture return; } sub after_picture { return; } # Ugh... Is this safe? Apparently, picture stuff is allowed w/o a {picture} environment??? Tag('ltx:picture', autoOpen => 0.5, autoClose => 1, afterOpen => sub { GenerateID(@_, 'pic'); }, afterClose => sub { my ($document, $node, $whatsit) = @_; # Make sure we get a size, in case autoOpen'd if ($whatsit) { my ($w, $h, $d) = $whatsit->getSize; $node->setAttribute(width => $w->pxValue) unless $node->hasAttribute('width'); $node->setAttribute(height => $h->add($d)->pxValue) unless $node->hasAttribute('height'); } }); # Note: Untex should prefix a setting of unitlength!!! # First pair is (width,height) # Second pair is the coordinate of the lower-left corner. # [Note that for SVG the root viewport origin is at the TOP-left corner! # but that is currently handled in the SVG postprocessing module] DefEnvironment('{picture} Pair OptionalPair', "" . "?#transform(#body)(#body)" . "", mode => 'text', beforeDigest => \&before_picture, properties => sub { my ($stomach, $size, $pos) = @_; picProperties(unitlength => LookupValue('\unitlength'), width => $size && picScale($size->getX), height => $size && picScale($size->getY), depth => Dimension(0), 'origin-x' => $pos && picScale($pos->getX), 'origin-y' => $pos && picScale($pos->getY), ($pos ? (transform => 'translate(' . picScale($pos->negate)->pxValue . ')') : ())); }, afterDigest => \&after_picture); DefMacroI(T_CS('\Gin@driver'), undef, Tokens()); DefMacro('\put Until:){}', '\lx@pic@put#1){#2\relax}'); DefConstructor('\lx@pic@put Pair{}', "#2", properties => sub { my ($w, $h, $d) = $_[2]->getSize; $w = undef if $w && ($w->ptValue == 0); (transform => 'translate(' . picScale($_[1])->pxValue . ')', innerwidth => $w, innerheight => $h, innerdepth => $d); }, alias => '\put', mode => 'text'); DefConstructor('\line Pair:Number {Float}', "", alias => '\line', properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); }); DefConstructor('\vector Pair:Number {Float}', "", alias => '\vector', properties => sub { picProperties(points => '0,0 ' . slopeToPicCoord($_[1], $_[2])->pxValue()); }); DefConstructor('\circle OptionalMatch:* {Float}', "", alias => '\circle', properties => sub { my ($stomach, $filled, $dia) = @_; picProperties(radius => picScale($dia)->multiply(0.5)->pxValue, ($filled ? 'fill' : 'stroke') => 'black'); }); DefConstructor('\oval [Float] Pair []', "", alias => '\oval', properties => sub { my ($stomach, $r, $size, $part) = @_; $size = picScale($size); my $halfsize = $size->multiply(0.5); $r = ($r ? picScale($r) : Dimension('40pt')); $r = $r->smaller($halfsize->getX->absolute); $r = $r->smaller($halfsize->getY->absolute); picProperties(size => $size, radius => Dimension($r->valueOf), x => $halfsize->getX->negate, y => $halfsize->getY->negate, width => Dimension($size->getX->valueOf), height => Dimension($size->getY->valueOf), ); }); DefConstructor('\qbezier [Number] Pair Pair Pair', "", alias => '\qbezier', properties => sub { picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); }); DefMacro('\bezier Until:(', '\ifx.#1.\lx@pic@bezier{0}(\else\lx@pic@bezier{#1}(\fi'); DefConstructor('\lx@pic@bezier {Number} Pair Pair Pair', "", alias => '\bezier', properties => sub { picProperties(pt => PairList(picScale($_[2]), picScale($_[3]), picScale($_[4]))); }); # Generic boxing command (frames, dash, etc) DefConstructor('\pic@makebox@ Undigested RequiredKeyVals Pair []{}', "?#framed()()" . "#box", reversion => '#1#3[#4]{#5}', beforeDigest => sub { reenterTextMode(); }, properties => sub { my ($stomach, $cs, $kv, $size, $pos, $box) = @_; my ($w, $h, $d) = $box->getSize; my ($ww, $hh) = ($w, $h); my ($x, $y) = (Dimension(0), Dimension(0)); my $ht = ($h ? ($d ? $h->add($d) : $h) : ($d ? $d : Dimension(0))); if ($size) { # && (($size->getX->valueOf != 0) || ($size->getY->valueOf != 0))) { $ww = picScale($size->getX); $hh = picScale($size->getY); $pos = ($pos ? lc(ToString($pos)) : ''); if ($pos =~ /l/) { $x = Number(0); } elsif ($pos =~ /r/) { $x = $ww->subtract($w); } else { $x = $ww->subtract($w)->divide(2); } if ($pos =~ /b/) { $y = Number(0); } elsif ($pos =~ /t/) { $y = $hh->subtract($ht); } else { $y = $hh->subtract($ht)->divide(2); } } my $fw = ($ww && $ww->valueOf ? $ww : $w); my $fh = ($hh && $hh->valueOf ? $hh : $h->add($d)); picProperties( box => $box, width => $w, height => $h, depth => $d, fwidth => $fw, fheight => $fh, xshift => $x->pxValue, yshift => $y->pxValue, framed => $kv->getValue('framed'), dash => $kv->getValue('dash') # dashed ); }); DefMacro('\pic@makebox', '\pic@makebox@{\makebox}{}'); DefMacro('\pic@framebox', '\pic@makebox@{\framebox}{framed=true}'); DefMacro('\lx@pic@dashbox{Float}', '\pic@makebox@{\dashbox(#1)}{framed=true,dash={#1}}'); DefMacro('\dashbox Until:(', '\ifx.#1.\lx@pic@dashbox{0}(\else\lx@pic@dashbox{#1}(\fi'); DefMacro('\frame{}', '\pic@makebox@{\framebox}{framed=true}(0,0)[bl]{#1}'); DefMacro('\pic@savebox DefToken Pair []{}', '\pic@@savebox{#1}{\pic@makebox #2[#3]{#4}}'); DefPrimitive('\pic@@savebox DefToken {}', sub { AssignValue('box' . ToString($_[1]), Digest($_[2])); return; }); DefMacro('\@savepicbox', '\pic@savebox'); DefConstructor('\pic@raisebox{Dimension}[Dimension][Dimension]{}', "#4", alias => '\raisebox'); our %alignments = (l => 'left', c => 'center', r => 'right'); # Not sure that ltx:p is the best to use here, but ... (see also \vbox, \vtop) # This should be fairly compact vertically. DefConstructor('\@shortstack@cr', "", properties => { isBreak => 1 }, reversion => Tokens(T_CS("\\\\"), T_CR), beforeDigest => sub { $_[0]->egroup; }, afterDigest => sub { $_[0]->bgroup; }); DefConstructor('\shortstack[]{} OptionalMatch:* [Dimension]', "#2", bounded => 1, sizer => '#2', beforeDigest => sub { reenterTextMode(); # then RE-RE-define this one!!! Let("\\\\", '\@shortstack@cr'); $_[0]->bgroup; }, afterDigest => sub { $_[0]->egroup; }, # Note: does not get layout=vertical, since linebreaks are explicit properties => { align => sub { ($_[1] ? $alignments{ ToString($_[1]) } : undef); }, vattach => 'top' }, # for size computation mode => 'text'); DefMacro('\multiput Pair Pair {}{}', sub { my ($gullet, $pos, $d, $nt, $body) = @_; my ($x, $y, $dx, $dy, $n) = map { ToString($_) } ($pos->getX, $pos->getY, $d->getX, $d->getY, $nt); my @exp = (); for (my $i = 0 ; $i < $n ; $i++) { push(@exp, T_CS('\put'), T_OTHER('('), Explode($x), T_OTHER(','), Explode($y), T_OTHER(')'), T_BEGIN, $body->unlist, T_END); $x += $dx; $y += $dy; } @exp; }); Tag('ltx:picture', afterOpen => sub { my ($document, $node, $thing) = @_; if ($thing) { $document->setAttribute($node, tex => UnTeX($thing)); } }); Tag('ltx:g', afterClose => sub { my ($document, $node) = @_; $node->parentNode->removeChild($node) unless $node->hasChildNodes; }); # \savebox -- already defined differntly in C.13 above ? #********************************************************************** # C.15 Font Selection #********************************************************************** #====================================================================== # C.15.1 Changing the Type Style #====================================================================== # Text styles. DefMacroI('\rmdefault', undef, 'cmr'); 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', 'cmr'); DefMacro('\f@series', 'm'); DefMacro('\f@shape', 'n'); DefMacro('\f@size', '10'); # These do NOT immediately effect the font! DefMacro('\fontfamily{}', '\edef\f@family{#1}'); DefMacro('\fontseries{}', '\edef\f@series{#1}'); DefMacro('\fontshape{}', '\edef\f@shape{#1}'); # For fonts not allowed in math!!! DefPrimitive('\not@math@alphabet@@ {}', sub { if ($STATE->lookupValue('IN_MATH')) { my $c = ToString($_[1]); Warn('unexpected', $c, $_[0], "Command $c invalid in math mode"); } return; }); # These DO immediately effect the font! DefMacroI('\mdseries', undef, '\not@math@alphabet@@{\mddefault}\fontseries{\mddefault}\selectfont'); DefMacroI('\bfseries', undef, '\not@math@alphabet@@{\bfdefault}\fontseries{\bfdefault}\selectfont'); DefMacroI('\rmfamily', undef, '\not@math@alphabet@@{\rmdefault}\fontfamily{\rmdefault}\selectfont'); DefMacroI('\sffamily', undef, '\not@math@alphabet@@{\sfdefault}\fontfamily{\sfdefault}\selectfont'); DefMacroI('\ttfamily', undef, '\not@math@alphabet@@{\ttdefault}\fontfamily{\ttdefault}\selectfont'); DefMacroI('\upshape', undef, '\not@math@alphabet@@{\updefault}\fontshape{\updefault}\selectfont'); DefMacroI('\itshape', undef, '\not@math@alphabet@@{\itdefault}\fontshape{\itdefault}\selectfont'); DefMacroI('\slshape', undef, '\not@math@alphabet@@{\sldefault}\fontshape{\sldefault}\selectfont'); DefMacroI('\scshape', undef, '\not@math@alphabet@@{\scdefault}\fontshape{\scdefault}\selectfont'); DefMacroI('\normalfont', undef, '\fontfamily{\rmdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont'); DefMacroI('\verbatim@font', undef, '\fontfamily{\ttdefault}\fontseries{\mddefault}\fontshape{\updefault}\selectfont'); Let('\reset@font', '\normalfont'); DefPrimitive('\selectfont', sub { my $family = ToString(Expand(T_CS('\f@family'))); my $series = ToString(Expand(T_CS('\f@series'))); my $shape = ToString(Expand(T_CS('\f@shape'))); if (my $sh = LaTeXML::Common::Font::lookupFontFamily($family)) { MergeFont(%$sh); } elsif (!LookupValue("reported_unrecognized_font_family_$family")) { AssignValue("reported_unrecognized_font_family_$family", 1, 'global'); Info('unexpected', $family, $_[0], "Unrecognized font family '$family'."); } if (my $sh = LaTeXML::Common::Font::lookupFontSeries($series)) { MergeFont(%$sh); } elsif (!LookupValue("reported_unrecognized_font_series_$series")) { AssignValue("reported_unrecognized_font_series_$series", 1, 'global'); Info('unexpected', $series, $_[0], "Unrecognized font series '$series'."); } if (my $sh = LaTeXML::Common::Font::lookupFontShape($shape)) { MergeFont(%$sh); } elsif (!LookupValue("reported_unrecognized_font_shape_$shape")) { AssignValue("reported_unrecognized_font_shape_$shape", 1, 'global'); Info('unexpected', $shape, $_[0], "Unrecognized font shape '$shape'."); } return; }); DefMacro('\usefont{}{}{}{}', '\fontencoding{#1}\fontfamily{#2}\fontseries{#3}\fontshape{#4}\selectfont'); # If these series or shapes appear in math, they revert it to roman, medium, upright (?) DefConstructor('\textmd@math{}', "#1", mode => 'text', bounded => 1, font => { series => 'medium' }, alias => '\textmd', beforeDigest => sub { DefMacro('\f@series', 'm'); }); DefConstructor('\textbf@math{}', "#1", mode => 'text', bounded => 1, font => { series => 'bold' }, alias => '\textbf', beforeDigest => sub { DefMacro('\f@series', 'b'); }); DefConstructor('\textrm@math{}', "#1", mode => 'text', bounded => 1, font => { family => 'serif' }, alias => '\textrm', beforeDigest => sub { DefMacro('\f@family', 'cm'); }); DefConstructor('\textsf@math{}', "#1", mode => 'text', bounded => 1, font => { family => 'sansserif' }, alias => '\textsf', beforeDigest => sub { DefMacro('\f@family', 'cmss'); }); DefConstructor('\texttt@math{}', "#1", mode => 'text', bounded => 1, font => { family => 'typewriter' }, alias => '\texttt', beforeDigest => sub { DefMacro('\f@family', 'cmtt'); }); DefConstructor('\textup@math{}', "#1", mode => 'text', bounded => 1, font => { shape => 'upright' }, alias => '\textup', beforeDigest => sub { DefMacro('\f@shape', ''); }); DefConstructor('\textit@math{}', "#1", mode => 'text', bounded => 1, font => { shape => 'italic' }, alias => '\textit', beforeDigest => sub { DefMacro('\f@shape', 'i'); }); DefConstructor('\textsl@math{}', "#1", mode => 'text', bounded => 1, font => { shape => 'slanted' }, alias => '\textsl', beforeDigest => sub { DefMacro('\f@shape', 'sl'); }); DefConstructor('\textsc@math{}', "#1", mode => 'text', bounded => 1, font => { shape => 'smallcaps' }, alias => '\textsc', beforeDigest => sub { DefMacro('\f@shape', 'sc'); }); DefConstructor('\textnormal@math{}', "#1", mode => 'text', bounded => 1, font => { family => 'serif', series => 'medium', shape => 'upright' }, alias => '\textnormal', beforeDigest => sub { DefMacro('\f@family', 'cmtt'); DefMacro('\f@series', 'm'); DefMacro('\f@shape', 'n'); }); # These really should be robust! which is a source of expand timing issues! DefMacro('\textmd{}', '\ifmmode\textmd@math{#1}\else{\mdseries #1}\fi', protected => 1); DefMacro('\textbf{}', '\ifmmode\textbf@math{#1}\else{\bfseries #1}\fi', protected => 1); DefMacro('\textrm{}', '\ifmmode\textrm@math{#1}\else{\rmfamily #1}\fi', protected => 1); DefMacro('\textsf{}', '\ifmmode\textsf@math{#1}\else{\sffamily #1}\fi', protected => 1); DefMacro('\texttt{}', '\ifmmode\texttt@math{#1}\else{\ttfamily #1}\fi', protected => 1); DefMacro('\textup{}', '\ifmmode\textup@math{#1}\else{\upshape #1}\fi', protected => 1); DefMacro('\textit{}', '\ifmmode\textit@math{#1}\else{\itshape #1}\fi', protected => 1); DefMacro('\textsl{}', '\ifmmode\textsl@math{#1}\else{\slshape #1}\fi', protected => 1); DefMacro('\textsc{}', '\ifmmode\textsc@math{#1}\else{\scshape #1}\fi', protected => 1); DefMacro('\textnormal{}', '\ifmmode\textnormal@math{#1}\else{\normalfont #1}\fi', protected => 1); DefPrimitive('\DeclareTextFontCommand{}{}', sub { my ($stomach, $cmd, $font) = @_; DefConstructorI($cmd, "{}", "?#isMath(#1)(#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'"); } }); DefPrimitive('\DeclareOldFontCommand{}{}{}', sub { my ($stomach, $cmd, $font, $mathcmd) = @_; DefMacroI($cmd, undef, sub { return (LookupValue('IN_MATH') ? $mathcmd->unlist : $font->unlist); }); # Tokens(T_CS('\ifmmode'), $mathcmd->unlist, T_CS('\else'), $font->unlist, T_CS('\fi'))); return; }); DefMacro('\newfont{}{}', '\font#1=#2\relax'); Let('\normalcolor', '\relax'); #====================================================================== # C.15.2 Changing the Type Size #====================================================================== # Handled in TeX.pool.ltxml #====================================================================== # C.15.3 Special Symbol #====================================================================== DefMacro('\symbol{}', '\char#1\relax'); # These in LaTeX, but not in the book... DefPrimitiveI('\textdollar', undef, "\$"); DefPrimitiveI('\textemdash', undef, "\x{2014}"); # EM DASH DefPrimitiveI('\textendash', undef, "\x{2013}"); # EN DASH DefPrimitiveI('\textexclamdown', undef, UTF(0xA1)); # INVERTED EXCLAMATION MARK DefPrimitiveI('\textquestiondown', undef, UTF(0xBF)); # INVERTED QUESTION MARK DefPrimitiveI('\textquotedblleft', undef, "\x{201C}"); # LEFT DOUBLE QUOTATION MARK DefPrimitiveI('\textquotedblright', undef, "\x{201D}"); # RIGHT DOUBLE QUOTATION MARK DefPrimitiveI('\textquotedbl', undef, "\""); # plain ascii DOUBLE QUOTATION DefPrimitiveI('\textquoteleft', undef, "\x{2018}"); # LEFT SINGLE QUOTATION MARK DefPrimitiveI('\textquoteright', undef, "\x{2019}"); # RIGHT SINGLE QUOTATION MARK DefPrimitiveI('\textsterling', undef, UTF(0xA3)); # POUND SIGN DefPrimitiveI('\textasteriskcentered', undef, "*"); DefPrimitiveI('\textbackslash', undef, UTF(0x5C)); # REVERSE SOLIDUS DefPrimitiveI('\textbar', undef, "|"); DefPrimitiveI('\textbraceleft', undef, "{"); DefPrimitiveI('\textbraceright', undef, "}"); DefPrimitiveI('\textbullet', undef, "\x{2022}"); # BULLET DefPrimitiveI('\textdaggerdbl', undef, "\x{2021}"); # DOUBLE DAGGER DefPrimitiveI('\textdagger', undef, "\x{2020}"); # DAGGER DefPrimitiveI('\textparagraph', undef, UTF(0xB6)); # PILCROW SIGN DefPrimitiveI('\textsection', undef, UTF(0xA7)); # SECTION SIGN 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('\textcapitalcompwordmark', undef, ""); # ??? DefPrimitiveI('\textascendercompwordmark', undef, ""); # ??? DefPrimitiveI('\textunderscore', undef, "_"); DefPrimitiveI('\textvisiblespace', undef, "\x{2423}"); # SYMBOL FOR SPACE; Not really the right symbol! DefPrimitiveI('\textellipsis', undef, "\x{2026}"); # HORIZONTAL ELLIPSIS DefPrimitiveI('\textregistered', undef, UTF(0xAE)); # REGISTERED SIGN DefPrimitiveI('\texttrademark', undef, "\x{2122}"); # TRADE MARK SIGN DefConstructor('\textsuperscript{}', "#1", mode => 'text'); # This is something coming from xetex/xelatex ? Why define this way? #DefConstructor('\realsuperscript{}', "#1"); DefConstructor('\realsuperscript{}', "#1", mode => 'text'); DefPrimitiveI('\textordfeminine', undef, UTF(0xAA)); # FEMININE ORDINAL INDICATOR DefPrimitiveI('\textordmasculine', undef, UTF(0xBA)); # MASCULINE ORDINAL INDICATOR DefPrimitiveI('\SS', undef, 'SS', # ? locked => 1); # cause it shows up in uclist! DefMacroI('\dag', undef, '\ifmmode{\dagger}\else\textdagger\fi'); DefMacroI('\ddag', undef, '\ifmmode{\ddagger}\else\textdaggerdbl\fi'); DefConstructor('\sqrtsign Digested', "#1"); DefPrimitiveI('\mathparagraph', undef, UTF(0xB6)); DefPrimitiveI('\mathsection', undef, UTF(0xA7)); DefPrimitiveI('\mathdollar', undef, '$'); DefPrimitiveI('\mathsterling', undef, UTF(0xA3)); DefPrimitiveI('\mathunderscore', undef, '_'); DefPrimitiveI('\mathellipsis', undef, "\x{2026}"); # Are these glyph "pieces" or use alone? DefMathI('\arrowvert', undef, "|", role => 'VERTBAR'); DefMathI('\Arrowvert', undef, "\x{2225}", role => 'VERTBAR'); # The following are glyph "pieces"... DefPrimitiveI('\braceld', undef, "\x{239D}"); # left brace down part DefPrimitiveI('\bracelu', undef, "\x{239B}"); # left brace up part DefPrimitiveI('\bracerd', undef, "\x{23A0}"); # right brace down part DefPrimitiveI('\braceru', undef, "\x{239E}"); # right brace up part DefMathI('\cdotp', undef, "\x{22C5}", role => 'MULOP'); DefMathI('\ldotp', undef, ".", role => 'MULOP'); DefMathI('\intop', undef, "\x{222B}", role => 'INTOP', meaning => 'integral', scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp); DefMathI('\ointop', undef, "\x{222E}", role => 'INTOP', meaning => 'contour-integral', scriptpos => \&doScriptpos, mathstyle => \&doVariablesizeOp); # WHat are these? They look like superscripted parentheses, or combining accents! # \lhook # \rhook Let('\gets', '\leftarrow'); DefPrimitiveI('\lmoustache', undef, "\x{23B0}"); DefPrimitiveI('\rmoustache', undef, "\x{23B1}"); DefMathI('\mapstochar', undef, "\x{21A6}", role => 'ARROW', meaning => 'maps-to'); DefMathI('\owns', undef, "\x{220B}", role => 'RELOP', meaning => 'contains'); # \skew{}{}{} ???? # \symbol lookup symbol in font by index? #********************************************************************** # Other stuff #********************************************************************** # Some stuff that got missed in the appendices ? RawTeX(<<'EoTeX'); \def\@namedef#1{\expandafter\def\csname #1\endcsname} \def\@nameuse#1{\csname #1\endcsname} \def\@cons#1#2{\begingroup\let\@elt\relax\xdef#1{#1\@elt #2}\endgroup} \def\@car#1#2\@nil{#1} \def\@cdr#1#2\@nil{#2} \def\@carcube#1#2#3#4\@nil{#1#2#3} \def\nfss@text#1{{\mbox{#1}}} \def\@sect#1#2#3#4#5#6[#7]#8{} EoTeX Let('\@begindocumenthook', '\@empty'); DefMacroI('\@preamblecmds', undef, Tokens()); DefMacro('\@ifdefinable DefToken {}', sub { my ($gullet, $token, $if) = @_; if (isDefinable($token)) { return $if->unlist } else { my ($slash, @s) = ExplodeText($token->toString); DefMacroI('\reserved@a', undef, Tokens(@s)); return (T_CS('\@notdefinable')); } }); Let('\@@ifdefinable', '\@ifdefinable'); DefMacro('\@rc@ifdefinable DefToken {}', sub { my ($gullet, $token, $if) = @_; Let('\@ifdefinable', '\@@ifdefinable'); return $if->unlist; }); DefMacroI('\@notdefinable', undef, <<'EOL'); \@latex@error{% Command \@backslashchar\reserved@a\space already defined. Or name \@backslashchar\@qend... illegal, see p.192 of the manual} EOL DefMacroI('\@qend', undef, Tokens(Explode('end'))); DefMacroI('\@qrelax', undef, Tokens(Explode('relax'))); DefMacroI('\@spaces', undef, '\space\space\space\space'); Let('\@sptoken', T_SPACE); DefMacroI('\@uclclist', undef, '\oe\OE\o\O\ae\AE\dh\DH\dj\DJ\l\L\ng\NG\ss\SS\th\TH'); RawTeX(<<'EOL'); \DeclareRobustCommand{\MakeUppercase}[1]{{% \def\i{I}\def\j{J}% \def\reserved@a##1##2{\let##1##2\reserved@a}% \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}% \let\UTF@two@octets@noexpand\@empty \let\UTF@three@octets@noexpand\@empty \let\UTF@four@octets@noexpand\@empty \protected@edef\reserved@a{\uppercase{#1}}% \reserved@a }} \DeclareRobustCommand{\MakeLowercase}[1]{{% \def\reserved@a##1##2{\let##2##1\reserved@a}% \expandafter\reserved@a\@uclclist\reserved@b{\reserved@b\@gobble}% \let\UTF@two@octets@noexpand\@empty \let\UTF@three@octets@noexpand\@empty \let\UTF@four@octets@noexpand\@empty \protected@edef\reserved@a{\lowercase{#1}}% \reserved@a }} \protected@edef\MakeUppercase#1{\MakeUppercase{#1}} \protected@edef\MakeLowercase#1{\MakeLowercase{#1}} EOL #====================================================================== DefMacroI('\@ehc', undef, "I can't help"); DefMacro('\@gobble{}', Tokens()); DefMacro('\@gobbletwo{}{}', Tokens()); DefMacro('\@gobblefour{}{}{}{}', Tokens()); DefMacro('\@firstofone{}', sub { $_[1]; }); Let('\@iden', '\@firstofone'); DefMacro('\@firstoftwo{}{}', sub { $_[1]; }); DefMacro('\@secondoftwo{}{}', sub { $_[2]; }); DefMacro('\@thirdofthree{}{}{}', sub { $_[3]; }); DefMacro('\@expandtwoargs{}{}{}', sub { ($_[1]->unlist, T_BEGIN, Expand($_[2])->unlist, T_END, T_BEGIN, Expand($_[3])->unlist, T_END); }); DefMacro('\@makeother{}', sub { my $letter = ToString($_[1]); $letter =~ s/^\\//; AssignCatcode($letter => CC_OTHER, 'local'); }); RawTeX(<<'EoTeX'); {\catcode`\^^M=13 \gdef\obeycr{\catcode`\^^M13 \def^^M{\\\relax}% \@gobblecr}% {\catcode`\^^M=13 \gdef\@gobblecr{\@ifnextchar \@gobble\ignorespaces}}% \gdef\restorecr{\catcode`\^^M5 }} EoTeX RawTeX(<<'EoTeX'); \begingroup \catcode`P=12 \catcode`T=12 \lowercase{ \def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}} \expandafter\endgroup\x \def\strip@pt{\expandafter\rem@pt\the} \def\strip@prefix#1>{} \def\@sanitize{\@makeother\ \@makeother\\\@makeother\$\@makeother\&% \@makeother\#\@makeother\^\@makeother\_\@makeother\%\@makeother\~} \def \@onelevel@sanitize #1{% \edef #1{\expandafter\strip@prefix \meaning #1}% } \def\dospecials{\do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\_\do\%\do\~} EoTeX DefMacroI('\nfss@catcodes', undef, <<'EOMacro'); \makeatletter \catcode`\ 9% \catcode`\^^I9% \catcode`\^^M9% \catcode`\\\z@ \catcode`\{\@ne \catcode`\}\tw@ \catcode`\#6% \catcode`\^7% \catcode`\%14% \@makeother\<% \@makeother\>% \@makeother\*% \@makeother\.% \@makeother\-% \@makeother\/% \@makeother\[% \@makeother\]% \@makeother\`% \@makeother\'% \@makeother\"% EOMacro DefMacroI('\ltx@hard@MessageBreak', undef, '^^J'); sub make_message { my ($cmd, @args) = @_; my $stomach = $STATE->getStomach; my $lead_arg = ToString(shift(@args)) || ''; $lead_arg =~ s/(?:\\\@?spaces?)+//g; my $type = $lead_arg || $cmd; $stomach->bgroup; Let('\protect', '\string'); # Note that the arg really should be digested to get to the underlying text, # but why tempt fate, when we're already making an error message? my $message = join(" ", map { ToString(Expand($_)) } @args); $type =~ s/(?:\\\@?spaces?)+/ /g; $message =~ s/(?:\\\@?spaces?)+/ /g; $stomach->egroup; return ('latex', $type, $stomach, $message); } DefPrimitive('\@onlypreamble{}', sub { onlyPreamble('\@onlypreamble'); }); # Don't bother enforcing this. DefPrimitive('\GenericError{}{}{}{}', sub { Error(make_message('\GenericError', $_[1], $_[2], $_[3], $_[4])); }); DefPrimitive('\GenericWarning{}{}', sub { Warn(make_message('\GenericWarning', $_[1], $_[2])); }); DefPrimitive('\GenericInfo{}{}', sub { Info(make_message('\GenericInfo', $_[1], $_[2])); }); Let('\MessageBreak', '\relax'); RawTeX(<<'EoTeX'); \gdef\PackageError#1#2#3{% \GenericError{% (#1)\@spaces\@spaces\@spaces\@spaces }{% Package #1 Error: #2% }{% See the #1 package documentation for explanation.% }{#3}% } \def\PackageWarning#1#2{% \GenericWarning{% (#1)\@spaces\@spaces\@spaces\@spaces }{% Package #1 Warning: #2% }% } \def\PackageWarningNoLine#1#2{% \PackageWarning{#1}{#2\@gobble}} \def\PackageInfo#1#2{% \GenericInfo{% (#1) \@spaces\@spaces\@spaces }{% Package #1 Info: #2% }% } \def\ClassError#1#2#3{% \GenericError{% (#1) \space\@spaces\@spaces\@spaces }{% Class #1 Error: #2% }{% See the #1 class documentation for explanation.% }{#3}% } \def\ClassWarning#1#2{% \GenericWarning{% (#1) \space\@spaces\@spaces\@spaces }{% Class #1 Warning: #2% }% } \def\ClassWarningNoLine#1#2{% \ClassWarning{#1}{#2\@gobble}} \def\ClassInfo#1#2{% \GenericInfo{% (#1) \space\space\@spaces\@spaces }{% Class #1 Info: #2% }% } \def\@latex@error#1#2{% \GenericError{% \space\space\space\@spaces\@spaces\@spaces }{% LaTeX Error: #1% }{% See the LaTeX manual or LaTeX Companion for explanation.% }{#2}% } \def\@latex@warning#1{% \GenericWarning{% \space\space\space\@spaces\@spaces\@spaces }{% LaTeX Warning: #1% }% } \def\@latex@warning@no@line#1{% \@latex@warning{#1\@gobble}} \def\@latex@info#1{% \GenericInfo{% \@spaces\@spaces\@spaces }{% LaTeX Info: #1% }% } \def\@latex@info@no@line#1{% \@latex@info{#1\@gobble}} EoTeX DefPrimitive('\@setsize{}{}{}{}', undef); DefMacro('\on@line', ' on input line \the\inputlineno'); Let('\@warning', '\@latex@warning'); Let('\@@warning', '\@latex@warning@no@line'); DefMacro('\G@refundefinedtrue', ''); DefMacro('\@nomath{}', '\relax\ifmmode\@font@warning{Command \noexpand#1invalid in math mode}\fi'); DefMacro('\@font@warning{}', '\GenericWarning{(Font)\@spaces\@spaces\@spaces\space\space}{LaTeX Font Warning: #1}'); #====================================================================== RawTeX(<<'EOTeX'); \chardef\@xxxii=32 \mathchardef\@Mi=10001 \mathchardef\@Mii=10002 \mathchardef\@Miii=10003 \mathchardef\@Miv=10004 \def\@fontenc@load@list{\@elt{T1,OT1}} EOTeX DefMacroI('\@vpt', undef, '5'); DefMacroI('\@vipt', undef, '6'); DefMacroI('\@viipt', undef, '7'); DefMacroI('\@viiipt', undef, '8'); DefMacroI('\@ixpt', undef, '9'); DefMacroI('\@xpt', undef, '10'); DefMacroI('\@xipt', undef, '10.95'); DefMacroI('\@xiipt', undef, '12'); DefMacroI('\@xivpt', undef, '14.4'); DefMacroI('\@xviipt', undef, '17.28'); DefMacroI('\@xxpt', undef, '20.74'); DefMacroI('\@xxvpt', undef, '24.88'); DefMacroI('\@tempa', undef, ''); DefMacroI('\@tempb', undef, ''); DefMacroI('\@tempc', undef, ''); DefMacroI('\@gtempa', undef, ''); RawTeX(<<'EOTeX'); \long\def \loop #1\repeat{% \def\iterate{#1\relax % Extra \relax \expandafter\iterate\fi }% \iterate \let\iterate\relax } \newdimen\@ydim \let\@@hyph=\- \newbox\@arstrutbox \newbox\@begindvibox \newcount\@botnum \newdimen\@botroom \newcount\@chclass \newcount\@chnum \newdimen\@clnht \newdimen\@clnwd \newdimen\@colht \newcount\@colnum \newdimen\@colroom \newbox\@curfield \newbox\@curline \newcount\@currtype \newcount\@curtab \newcount\@curtabmar \newbox\@dashbox \newcount\@dashcnt \newdimen\@dashdim \newcount\@dbltopnum \newdimen\@dbltoproom \let\@dischyph=\- \newcount\@enumdepth \newcount\@floatpenalty \newdimen\@fpmin \newcount \@fpstype \newcount\@highpenalty \newcount\@hightab \newbox\@holdpg \newinsert \@kludgeins \newcount\@lastchclass \newbox\@leftcolumn \newbox\@linechar \newdimen\@linelen \newcount\@lowpenalty \newdimen\@maxdepth \newcount\@medpenalty \newdimen\@mparbottom \@mparbottom\z@ \newinsert\@mpfootins \newcount\@mplistdepth \newcount\@multicnt \newcount\@nxttabmar \newbox\@outputbox \newdimen\@pagedp \newdimen\@pageht \newbox\@picbox \newdimen\@picht \newdimen \@reqcolroom \newskip\@rightskip \@rightskip \z@skip \newcount\@savsf \newdimen\@savsk \newcount\@secpenalty \def\@sqrt[#1]{\root #1\of} \newbox\@tabfbox \newcount\@tabpush \newdimen \@textfloatsheight \newdimen\@textmin \newcount\@topnum \newdimen\@toproom \newcount\@xarg \newdimen\@xdim \newcount\@yarg \newdimen\@ydim \newcount\@yyarg \newtoks\every@math@size \newif \if@fcolmade \newdimen\lower@bound \newcount\par@deathcycles \newdimen\upper@bound \newif\if@insert \newif\if@colmade \newif\if@specialpage \@specialpagefalse \newif\if@firstcolumn \@firstcolumntrue \newif\if@twocolumn \@twocolumnfalse \newif\if@twoside \@twosidefalse \newif\if@reversemargin \@reversemarginfalse \newif\if@mparswitch \@mparswitchfalse \newif\if@firstamp \@firstampfalse \newcount\col@number \@ne \newread\@inputcheck \newwrite\@unused \newwrite\@mainaux \newwrite\@partaux \let\@auxout=\@mainaux \openout\@mainaux\jobname.aux \newcount\@clubpenalty \@clubpenalty \clubpenalty \newif\if@filesw \@fileswtrue \newif\if@partsw \@partswfalse \def\@tempswafalse{\let\if@tempswa\iffalse} \def\@tempswatrue{\let\if@tempswa\iftrue} \let\if@tempswa\iffalse \newcount\@tempcnta \newcount\@tempcntb \newif\if@tempswa \newdimen\@tempdima \newdimen\@tempdimb \newdimen\@tempdimc \newbox\@tempboxa \newskip\@tempskipa \newskip\@tempskipb \newtoks\@temptokena \newskip\@flushglue \@flushglue = 0pt plus 1fil \newif\if@afterindent\@afterindenttrue \newbox\rootbox \newcount\@eqcnt \newcount\@eqpen \newif\if@eqnsw\@eqnswtrue \newskip\@centering \@centering = 0pt plus 1000pt \let\@eqnsel=\relax \long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax #2\relax}\fi} \long\def\@iwhilenum#1{\ifnum #1\expandafter\@iwhilenum \else\expandafter\@gobble\fi{#1}} \long\def\@whiledim#1\do #2{\ifdim #1\relax#2\@iwhiledim{#1\relax#2}\fi} \long\def\@iwhiledim#1{\ifdim #1\expandafter\@iwhiledim \else\expandafter\@gobble\fi{#1}} \long\def\@whilesw#1\fi#2{#1#2\@iwhilesw{#1#2}\fi\fi} \long\def\@iwhilesw#1\fi{#1\expandafter\@iwhilesw \else\@gobbletwo\fi{#1}\fi} \def\@nnil{\@nil} \def\@fornoop#1\@@#2#3{} \long\def\@for#1:=#2\do#3{% \expandafter\def\expandafter\@fortmp\expandafter{#2}% \ifx\@fortmp\@empty \else \expandafter\@forloop#2,\@nil,\@nil\@@#1{#3}\fi} \long\def\@forloop#1,#2,#3\@@#4#5{\def#4{#1}\ifx #4\@nnil \else #5\def#4{#2}\ifx #4\@nnil \else#5\@iforloop #3\@@#4{#5}\fi\fi} \long\def\@iforloop#1,#2\@@#3#4{\def#3{#1}\ifx #3\@nnil \expandafter\@fornoop \else #4\relax\expandafter\@iforloop\fi#2\@@#3{#4}} \def\@tfor#1:={\@tf@r#1 } \long\def\@tf@r#1#2\do#3{\def\@fortmp{#2}\ifx\@fortmp\space\else \@tforloop#2\@nil\@nil\@@#1{#3}\fi} \long\def\@tforloop#1#2\@@#3#4{\def#3{#1}\ifx #3\@nnil \expandafter\@fornoop \else #4\relax\expandafter\@tforloop\fi#2\@@#3{#4}} \long\def\@break@tfor#1\@@#2#3{\fi\fi} \def\remove@to@nnil#1\@nnil{} \def\remove@angles#1>{\set@simple@size@args} \def\remove@star#1*{#1} \def\@defaultunits{\afterassignment\remove@to@nnil} \newif\ifmath@fonts \math@fontstrue \newbox\@labels \newif\if@inlabel \@inlabelfalse \newif\if@newlist \@newlistfalse \newif\if@noparitem \@noparitemfalse \newif\if@noparlist \@noparlistfalse \newif\if@noitemarg \@noitemargfalse \newif\if@nmbrlist \@nmbrlistfalse \def\glb@settings{}% EOTeX DefMacroI('\@height', undef, 'height'); DefMacroI('\@width', undef, 'width'); DefMacroI('\@depth', undef, 'depth'); DefMacroI('\@minus', undef, 'minus'); DefMacroI('\@plus', undef, 'plus'); DefMacroI('\hb@xt@', undef, '\hbox to'); DefMacroI('\hmode@bgroup', undef, '\leavevmode\bgroup'); DefMacroI('\@backslashchar', undef, T_OTHER('\\')); DefMacroI('\@percentchar', undef, T_OTHER('%')); DefMacroI('\@charlb', undef, T_LETTER('{')); DefMacroI('\@charrb', undef, T_LETTER('}')); #====================================================================== DefMacroI('\check@mathfonts', undef, Tokens()); DefMacro('\fontsize{}{}', Tokens()); # https://tex.stackexchange.com/questions/112492/setfontsize-vs-fontsize#112501 DefMacro('\@setfontsize{}{}{}', '\let\@currsize#1'); DefMacroI('\@vpt', undef, T_OTHER('5')); DefMacroI('\@vipt', undef, T_OTHER('6')); DefMacroI('\@viipt', undef, T_OTHER('7')); DefMacroI('\@viiipt', undef, T_OTHER('8')); DefMacroI('\@ixpt', undef, T_OTHER('9')); DefMacro('\@xpt', '10'); DefMacro('\@xipt', '10.95'); DefMacro('\@xiipt', '12'); DefMacro('\@xivpt', '14.4'); DefMacro('\@xviipt', '17.28'); DefMacro('\@xxpt', '20.74'); DefMacro('\@xxvpt', '24.88'); DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); DefMacroI('\defaultscriptratio', undef, '.7'); DefMacroI('\defaultscriptscriptratio', undef, '.5'); #====================================================================== DefMacroI('\loggingoutput', undef, Tokens()); DefMacroI('\loggingall', undef, Tokens()); DefMacroI('\tracingfonts', undef, Tokens()); DefMacroI('\showoverfull', undef, Tokens()); DefMacroI('\showoutput', undef, Tokens()); 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? ((XEquals($token, (defined $next ? $next : T_END)) ? $if : $else)->unlist, (defined $next ? ($next) : ())); }); Let('\kernel@ifnextchar', '\@ifnextchar'); Let('\@ifnext', '\@ifnextchar'); # ???? # Hacky version matches multiple chars! but does NOT expand DefMacro('\@ifnext@n {} {}{}', sub { my ($gullet, $tokens, $if, $else) = @_; my @toks = $tokens->unlist; my @read = (); while (my $t = $gullet->readToken) { push(@read, $t); if ($t->equals($toks[0])) { shift(@toks); } else { last; } } return Tokens((@toks ? $else->unlist : $if->unlist), @read); }); DefMacro('\@ifstar {}{}', sub { my ($gullet, $if, $else) = @_; my $next = $gullet->readNonSpace; if (T_OTHER('*')->equals($next)) { $if; } else { ($else, ($next ? $next : ())); } }); DefMacro('\@dblarg {}', '\kernel@ifnextchar[{#1}{\@xdblarg{#1}}'); DefMacro('\@xdblarg {}{}', '#1[{#2}]{#2}'); DefMacro('\@testopt{}{}', sub { my ($gullet, $cmd, $option) = @_; ($gullet->ifNext(T_OTHER('[')) ? $cmd->unlist : ($cmd->unlist, T_OTHER('['), $option->unlist, T_OTHER(']'))); }); RawTeX(<<'EoTeX'); \def\@protected@testopt#1{%% \ifx\protect\@typeset@protect \expandafter\@testopt \else \@x@protect#1% \fi} EoTeX Let('\l@ngrel@x', '\relax'); # Never actually used anywhere, but... DefMacro('\@star@or@long{}', '\@ifstar{\let\l@ngrel@x\relax#1}{\let\l@ngrel@x\long#1}'); # maybe this is easiest just to punt. RawTeX(<<'EoTeX'); \def\in@#1#2{% \def\in@@##1#1##2##3\in@@{% \ifx\in@##2\in@false\else\in@true\fi}% \in@@#2#1\in@\in@@} \newif\ifin@ EoTeX DefMacro('\IfFileExists{}{}{}', sub { my ($gullet, $file, $if, $else) = @_; my $file_string = ToString(Expand($file)); if (FindFile($file_string)) { DefMacro('\@filef@und', '"' . $file_string . '" '); return ($if->unlist); } else { return ($else->unlist); } }); DefMacro('\InputIfFileExists{}{}{}', sub { my ($gullet, $file, $if, $else) = @_; my $file_string = ToString(Expand($file)); if (FindFile($file_string)) { DefMacro('\@filef@und', '"' . $file_string . '" '); Input($file_string); return ($if->unlist); } else { return ($else->unlist); } }); #====================================================================== # Hair DefPrimitiveI('\makeatletter', undef, sub { AssignCatcode('@' => CC_LETTER, 'local'); }); DefPrimitiveI('\makeatother', undef, sub { AssignCatcode('@' => CC_OTHER, 'local'); }); #********************************************************************** #********************************************************************** # Sundry (is this ams ?) DefPrimitiveI('\textprime', undef, UTF(0xB4)); # ACUTE ACCENT Let('\endgraf', '\par'); Let('\endline', '\cr'); #********************************************************************** # Should be defined in each (or many) package, but it's not going to # get set correctly or maintained, so... DefMacroI('\fileversion', undef, Tokens()); DefMacroI('\filedate', undef, Tokens()); # Ultimately these may be overridden by babel, or otherwise, # various of these are defined in various places by different classes. DefMacroI('\chaptername', undef, 'Chapter'); DefMacroI('\partname', undef, 'Part'); # The rest of these are defined in some classes, but not most. #DefMacroI('\sectionname', undef, 'Section'); #DefMacroI('\subsectionname', undef, 'Subsection'); #DefMacroI('\subsubsectionname', undef, 'Subsubsection'); #DefMacroI('\paragraphname', undef, 'Paragraph'); #DefMacroI('\subparagraphname', undef, 'Subparagraph'); DefMacroI('\appendixname', undef, 'Appendix'); # These aren't defined in LaTeX, # these definitions will give us more meaningful typerefnum's DefMacroI('\sectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces'); DefMacroI('\subsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces'); DefMacroI('\subsubsectiontyperefname', undef, '\lx@sectionsign\lx@ignorehardspaces'); DefMacroI('\paragraphtyperefname', undef, '\lx@paragraphsign\lx@ignorehardspaces'); DefMacroI('\subparagraphtyperefname', undef, '\lx@paragraphsign\lx@ignorehardspaces'); #********************************************************************** # Stuff that would appear in the aux file... maybe somebody uses it? DefMacro('\bibdata{}', Tokens()); DefMacro('\bibcite{}{}', Tokens()); DefMacro('\citation{}', Tokens()); DefMacro('\contentsline{}{}{}', Tokens()); DefMacro('\newlabel{}{}', Tokens()); DefMacroI('\stop', undef, sub { $_[0]->closeMouth(1); return; }); DefMacroI('\ignorespacesafterend', undef, Tokens()); Let('\mathgroup', '\fam'); Let('\mathalpha', '\relax'); #\def\mathhexbox#1#2#3{\mbox{$\m@th \mathchar"#1#2#3$}} DefPrimitive('\mathhexbox {}{}{}', sub { my ($stomach, $a, $b, $c) = @_; my $n = ToString($a) * 256 + ToString($b) * 16 + ToString($c); my ($role, $glyph) = decodeMathChar($n); return Box($glyph, LookupValue('font')->specialize($glyph)); }); DefMacroI('\nocorrlist', undef, ',.'); Let('\nocorr', '\relax'); Let('\check@icl', '\@empty'); Let('\check@icr', '\@empty'); DefMacro('\text@command{}', ''); # ? DefMacro('\check@nocorr@ Until:\nocorr Until:\@nil', ''); RawTeX('\newif\ifmaybe@ic'); DefMacroI('\maybe@ic', undef, ''); DefMacroI('\maybe@ic@', undef, ''); # \t@st@ic DefMacroI('\sw@slant', undef, ''); DefMacroI('\fix@penalty', undef, ''); DefPrimitiveI('\@@end', undef, sub { $_[0]->getGullet->flush; return; }); #********************************************************************** # Modern pdflatex seems to come with hyphenation tables predefined # for many languages. We don't need or use hyphenation tables, # but some (versions of some) software (babel), check for # the presence of these \l@ macros # But also see \iflanguage (re)defined in babel.def.ltxml RawTeX(<<'EoTeX'); \newlanguage\l@english \newlanguage\l@usenglishmax \newlanguage\l@USenglish \newlanguage\l@dumylang \newlanguage\l@nohyphenation \newlanguage\l@arabic \newlanguage\l@basque \newlanguage\l@bulgarian \newlanguage\l@coptic \newlanguage\l@welsh \newlanguage\l@czech \newlanguage\l@slovak \newlanguage\l@german \newlanguage\l@ngerman \newlanguage\l@danish \newlanguage\l@esperanto \newlanguage\l@spanish \newlanguage\l@catalan \newlanguage\l@galician \newlanguage\l@estonian \newlanguage\l@farsi \newlanguage\l@finnish \newlanguage\l@french \newlanguage\l@greek \newlanguage\l@monogreek \newlanguage\l@ancientgreek \newlanguage\l@croatian \newlanguage\l@hungarian \newlanguage\l@interlingua \newlanguage\l@ibycus \newlanguage\l@indonesian \newlanguage\l@icelandic \newlanguage\l@italian \newlanguage\l@latin \newlanguage\l@mongolian \newlanguage\l@dutch \newlanguage\l@norsk \newlanguage\l@polish \newlanguage\l@portuguese \newlanguage\l@pinyin \newlanguage\l@romanian \newlanguage\l@russian \newlanguage\l@slovenian \newlanguage\l@uppersorbian \newlanguage\l@serbian \newlanguage\l@swedish \newlanguage\l@turkish \newlanguage\l@ukenglish \newlanguage\l@ukrainiane EoTeX #********************************************************************** DefPrimitive('\protected@write Number {}{}', sub { my ($stomach, $port, $prelude, $tokens) = @_; $port = ToString($port); $stomach->bgroup; Let('\thepage', '\relax'); my @stuff = Digest($prelude); Let('\protect', '\@unexpandable@protect'); if (my $filename = LookupValue('output_file:' . $port)) { my $handle = $filename . '_contents'; my $contents = LookupValue($handle); AssignValue($handle => $contents . UnTeX($tokens) . "\n", 'global'); } else { Note(UnTeX($tokens)); } $stomach->egroup; return @stuff; }); #********************************************************************** # LaTeX now includes fixltx2e by default. # https://www.latex-project.org/news/latex2e-news/ltnews22.pdf # This package allows you to define the font used for # emphasis (\emph) within emphasis. # For latexml, that styling should be left to the ultimate output, # so we just define the command as a dummy. DefMacro('\eminnershape', ""); # Undoubtedly not good enough DefMacro('\TextOrMath{}{}', '\ifmmode#2\else#1\fi'); DefConstructor('\textsubscript{}', "#1", mode => 'text'); #********************************************************************** # We need this bit from utf8.def for textcomp DefPrimitive('\DeclareUnicodeCharacter Expanded {}', sub { my ($stomach, $hexcode, $expansion) = @_; my $char = $hexcode->toString(); if ($char =~ /^[0-9a-fA-F]+$/) { if ((my $cp = hex($char)) <= 0x10FFFF) { $char = UTF($cp); AssignCatcode($char, CC_ACTIVE); DefMacroI(T_ACTIVE($char), undef, $expansion); } else { Error('unexpected', $char, $stomach, "$char too large for Unicode. Values between 0 and 10FFFF are permitted."); } } else { Error('unexpected', $char, $stomach, "'$char' is not a hexadecimal number."); } }); # LaTeX now includes textcomp by default. RequirePackage('textcomp'); #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Expl3 "Experimental LaTeX 3" is no longer Experimental! # It is beginning to be built into latex.ltx # We WILL need a new strategy to keep up; probably based in some form # of pre-read/pre-processed latex.ltx ! # # For now, a few macros required by other packages will be included: DefMacroI(T_CS('\hook_gput_code:nnn'), '{}{}{}', ''); #********************************************************************** 1;