# -*- mode: Perl -*-
# /=====================================================================\ #
# | calc.sty | #
# | Implementation for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
## TODO: Refactor goals.
## There is a choice made in this binding, where the core macros of calc.sty are left undefined
## (e.g. \ratio, \minOf, etc)
## and are instead consumed incrementally. However, for that to succeed with the correct implementation
## of readXToken, we would need to define them explicitly, or we get "undefined cs was expanded" errors.
DefPrimitive('\minof', '');
DefPrimitive('\maxof', '');
DefPrimitive('\widthof', '');
DefPrimitive('\heightof', '');
DefPrimitive('\ratio', '');
DefPrimitive('\real', '');
# \setcounter{<ctr>}{<integer expression>}
DefPrimitive('\setcounter{}{}', sub {
my ($stomach, $ctr, $arg) = @_;
SetCounter($ctr, readExpression($stomach->getGullet, 'Number', $arg));
return; });
# \addtocounter{<ctr>}{<integer expression>}
DefPrimitive('\addtocounter{}{}', sub {
my ($stomach, $ctr, $arg) = @_;
AddToCounter($ctr, readExpression($stomach->getGullet, 'Number', $arg));
return; });
DefPrimitive('\setlength{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
return unless $defn && ($defn ne 'missing');
$defn->setValue(readExpression($stomach->getGullet, 'Glue', $arg), @args); });
DefPrimitive('\addtolength{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
return unless $defn && ($defn ne 'missing');
$defn->setValue($defn->valueOf(@args)->add(readExpression($stomach->getGullet, 'Glue', $arg)),
@args); });
DefPrimitive('\settowidth{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
my $box = Digest($arg);
return unless $defn && ($defn ne 'missing');
$defn->setValue($box->getWidth, @args); });
DefPrimitive('\settoheight{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
return unless $defn && ($defn ne 'missing');
my $box = Digest($arg);
$defn->setValue($box->getHeight, @args); });
DefPrimitive('\settodepth{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
return unless $defn && ($defn ne 'missing');
my $box = Digest($arg);
$defn->setValue($box->getDepth, @args); });
DefPrimitive('\settototalheight{Variable}{}', sub {
my ($stomach, $var, $arg) = @_;
my ($defn, @args) = @$var;
return unless $defn && ($defn ne 'missing');
my $box = Digest($arg);
$defn->setValue($box->getHeight + $box->getDepth, @args); });
#======================================================================
# Grammar as specified in calc.sty
# type = integer | dimen | glue
# <type expression> -> <type term>
# | <type expression><plus or minus><type term>
# <type term> -> <type term><type scan stop>
# | <type factor>
# | <type term><multiply or divide><integer>
# | <type term><multiply or divide><real number>
# | <type term><multiply or divide><max or min integer>
# <type scan stop> -> <empty>
# | <optional space>
# | \relax
# <type factor> -> <type>
# | <text dimen factor>
# | <max or min type>
# | ( <type expression> )
# <integer> -> <number>
# <max or min type> -> <max or min command>{<type expression>}{<type expression>}
# <max or min command> -> \maxof | \minof
# <text dimen factor> -> <text dimen command>{<text>}
# <text dimen command> -> \widthof | \heightof | \depthof | \totalheightof
# <multiply or divide> -> * | /
# <real number> -> \ratio{<dimen expression>}{<dimen expression>}
# | \real{<optional signs><decimal constant>}
# <plus or minus> -> + | -
# <decimal constant> -> . | , | <digit><decimal constant> |<decimal constant><digit>
# <digit> -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
# <optional signs> -> <optional spaces>
# | <optional signs><plus or minus><optional spaces>
#======================================================================
# LaTeXML implementation, not exactly the same grammar as above
# (but I'm not convinced that grammar is what the latex version does)
# Here, type = Number, Glue
sub readExpression {
my ($ogullet, $type, $tokens) = @_;
return $ogullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
my ($gullet) = @_;
$gullet->unread($tokens); # Really so weird?
my $term = readTerm($gullet, $type);
while (my $op = $gullet->readKeyword('+', '-')) {
my $term2 = readTerm($gullet, $type);
$term = ($op eq '+' ? $term->add($term2) : $term->subtract($term2)); }
return $term; }); }
sub readTerm {
my ($gullet, $type) = @_;
$gullet->skipSpaces;
my $factor = readValue($gullet, $type);
while (my $op = $gullet->readKeyword('*', '/')) {
my $factor2 = readValue($gullet, 'Number');
$factor = ($op eq '*'
? $factor->multiply($factor2)
: $factor->divide($factor2)); }
return $factor; }
sub readValue {
my ($gullet, $type) = @_;
$gullet->skipSpaces;
my $peek = $gullet->readXToken();
# Evaluate <Text Dimen Factors>
if (Equals($peek, T_CS('\widthof'))) {
my $box = Digest($gullet->readArg);
Error('unexpected', $peek, $gullet, "\\widthof not expected here (reading $type)")
if $type eq 'Number';
return $box->getWidth; }
elsif (Equals($peek, T_CS('\heightof'))) {
my $box = Digest($gullet->readArg);
Error('unexpected', $peek, $gullet, "\\heightof not expected here (reading $type)")
if $type eq 'Number';
return $box->getHeight; }
elsif (Equals($peek, T_CS('\depthof'))) {
my $box = Digest($gullet->readArg);
Error('unexpected', $peek, $gullet, "\\depthof not expected here (reading $type)")
if $type eq 'Number';
return $box->getDepth; }
elsif (Equals($peek, T_CS('\totalheightof'))) {
my $box = Digest($gullet->readArg);
Error('unexpected', $peek, $gullet, "\\totalheightof not expected here (reading $type)")
if $type eq 'Number';
return $box->getHeight->add($box->getDepth); }
# Evaluate <Real> values
elsif (Equals($peek, T_CS('\real'))) {
my $arg = $gullet->readArg;
return $gullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
my ($igullet) = @_;
$igullet->unread($arg); # Really so weird?
# Make a Float, but round it AS-IF it had been a fixpoint number
return Float(kround($igullet->readFloat()->valueOf * $UNITY) / $UNITY); }); }
elsif (Equals($peek, T_CS('\ratio'))) {
my $x = readExpression($gullet, 'Glue', $gullet->readArg);
my $y = readExpression($gullet, 'Glue', $gullet->readArg);
my $y_pt = $y->valueOf;
if ($y_pt == 0) {
Warn("unexpected", "<number>", "calc: divisor should never be zero!");
$y_pt = 1; }
return Float(1.0 * $x->valueOf / $y_pt); }
# Evaluate MinMax values
elsif (Equals($peek, T_CS('\minof'))) {
my $x = readExpression($gullet, $type, $gullet->readArg);
my $y = readExpression($gullet, $type, $gullet->readArg);
return $x->smaller($y); }
elsif (Equals($peek, T_CS('\maxof'))) {
my $x = readExpression($gullet, $type, $gullet->readArg);
my $y = readExpression($gullet, $type, $gullet->readArg);
return $x->larger($y); }
# Read & Evaluate parenthesized subexpressions
elsif (Equals($peek, T_OTHER('('))) {
return readExpression($gullet, $type, $gullet->readUntil(T_OTHER(')'))); }
# Else read literal values.
else {
$gullet->unread($peek);
if ($type eq 'Number') {
return $gullet->readNumber(); }
else { # Really should read glue, but downgrade to dimension????
return $gullet->readGlue(); } } }
#**********************************************************************
1;