# -*- mode: Perl -*-
# /=====================================================================\ #
# | pgfsys latexml driver | #
# | 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. | #
# |---------------------------------------------------------------------| #
# | Thanks to Silviu Vlad Oprea <s.oprea@jacobs-university.de> | #
# | of the arXMLiv group for initial implementation | #
# | http://arxmliv.kwarc.info/ | #
# | Released under the Gnu Public License | #
# | Released to the Public Domain | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use LaTeXML::Util::Image;
use List::Util qw(min max);
#=====================================================================
# TODO:
# * Need to be consistent about when things like pgf_scopecount, etc are bumped
# * ALL \pgfsys@<drivercmd> level commands should be Constructors
# or expand into Constructors, which must yeild the svg
# AND revert into the original \pgfsys@<drivercmd>
# so that image generation can work.
# * Since \pgfsys@literal CAN defer the evaluation of its argument,
# (or even discard it?) any such counter maintenance has to be done outside of it.
#=====================================================================
# Utilities
sub addToCount {
my ($reg, $value, $option) = @_;
$option = 'local' if !$option;
AssignValue($reg => (LookupValue($reg) || 0) + $value, $option);
return; }
sub SVGNextObject {
my $n = (LookupValue('svg_objcount') || 0) + 1;
AssignValue(svg_objcount => $n, 'global');
return $n; }
#====================================================================#
#= 0. Environment specific stuff ====================================#
#====================================================================#
DefPrimitive('\lxSVG@installcommands', sub {
# Local definitions.
Let(T_CS('\hbox'), T_CS('\lxSVG@hbox'));
Let(T_CS('\vbox'), T_CS('\lxSVG@vbox'));
Let(T_CS('\lower'), T_CS('\lxSVG@lower'));
Let(T_CS('\raise'), T_CS('\lxSVG@raise'));
Let(T_CS('\hskip'), T_CS('\lxSVG@hskip'));
Let(T_CS('\halign'), T_CS('\lxSVG@halign'));
AssignValue(TEXT_MODE_BINDINGS => []);
return; });
# Redefine to disappear from UnTeX, since it appears a zillion times...
# (but is this needed ???)
DefPrimitive('\lx@inpgf@ignorespaces SkipSpaces', sub { (); });
# Order might matter here
DefMacro('\lxSVG@picture', sub {
Let('\ignorespaces', '\lx@inpgf@ignorespaces'); # Use local defn
AssignValue('pgf_SVGpath' => '', 'global');
(T_CS('\begingroup'), T_CS('\lxSVG@installcommands'),
# T_CS('\pgfsysprotocol@setcurrentprotocol'), T_CS('\pgfutil@empty')
); });
DefMacro('\endlxSVG@picture', '\endgroup');
# use overflow visible since we haven't got a good estimate of the TRUE size of the thing!
DefConstructor('\lxSVG@insertpicture{}',
'<ltx:picture>' .
'<svg:svg version="1.1" width="#pxwidth" height="#pxheight" '
. 'viewBox="#minx #miny #pxwidth #pxheight" overflow="visible">'
. '<svg:g transform="matrix(1 0 0 -1 0 #flipnmove)">'
. '#1'
. '</svg:g>'
. '</svg:svg>'
. '</ltx:picture>',
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $margin = Dimension('2pt');
my $margin2 = Dimension('4pt');
# \pgf@picmaxx,\pgf@pixmaxy are now the SIZE of the picture!!!!!!
my $minx = LookupValue('\pgf@picminx')->subtract($margin);
my $miny = LookupValue('\pgf@picminy')->subtract($margin);
my $width = LookupValue('\pgf@picmaxx')->add($margin2);
my $height = LookupValue('\pgf@picmaxy')->add($margin2);
my $w = max($width->pxValue, 1);
my $h = max($height->pxValue, 1);
# Note viewbox is minx, miny, width, height (NOT maxx,maxy!)
$whatsit->setProperty(minx => $minx->pxValue);
$whatsit->setProperty(miny => $miny->pxValue);
$whatsit->setProperty(width => $width);
$whatsit->setProperty(height => $height);
$whatsit->setProperty(depth => Dimension(0));
$whatsit->setProperty(pxwidth => $w);
$whatsit->setProperty(pxheight => $h);
$whatsit->setProperty(flipnmove => $h + 2 * $miny->pxValue);
# or tikz macro (see corescopes)
return; },
# \pgfpicture seems to make a 0 sized box, which throws off our postprocessor
reversion => sub {
my ($whatsit) = @_;
my $w = $whatsit->getProperty('width')->ptValue;
my $h = $whatsit->getProperty('height')->ptValue;
(T_CS('\hbox'), T_OTHER('to'), T_OTHER($w), T_OTHER('pt'), T_BEGIN,
T_CS('\vbox'), T_OTHER('to'), T_OTHER($h), T_OTHER('pt'), T_BEGIN,
T_CS('\pgfpicture'), T_CS('\makeatletter'), $whatsit->getArg(1)->revert, T_CS('\endpgfpicture'),
T_END, T_END) }); # ?
DefParameterType('SVGMoveableBox', sub {
my ($gullet) = @_;
$gullet->skipSpaces;
my ($box, @stuff) = $STATE->getStomach->invokeToken($gullet->readXToken(1));
Error(":expected:<box> A <svghbox> was supposed to be here, got "
. Stringify($box))
unless $box && $box->isa('LaTeXML::Core::Whatsit')
&& ($box->getDefinition->getCSName =~ /^(\\lxSVG\@hbox||)$/);
$box; });
# Check whether a svg:foreignObject is open,
# but don't check beyond an svg:svg node, in case we're nested.
sub foreignObjectCheck {
my ($doc) = @_;
my $node = $doc->getNode;
while ($node) {
my $n = $doc->getNodeQName($node);
return if $n eq 'svg:svg';
return $node if $n eq 'svg:foreignObject';
$node = $node->parentNode; }
return; }
DefConstructor('\lxSVG@raise Dimension SVGMoveableBox', sub {
my ($doc, $dim, $box) = @_;
if (foreignObjectCheck($doc)) {
$doc->openElement('ltx:text',
yoffset => $dim->negate->pxValue . 'px',
_noautoclose => '1');
$doc->absorb($box);
$doc->closeElement('ltx:text'); }
else { $doc->absorb($box); } },
alias => '\raise',
sizer => sub { raisedSizer($_[0]->getArg(2), $_[0]->getArg(1)); });
DefConstructor('\lxSVG@lower Dimension SVGMoveableBox', sub {
my ($doc, $dim, $box) = @_;
if (foreignObjectCheck($doc)) {
$doc->openElement('ltx:text',
yoffset => $dim->pxValue . 'px',
_noautoclose => '1');
$doc->absorb($box);
$doc->closeElement('ltx:text'); }
else { $doc->absorb($box); } },
alias => '\lower',
sizer => sub { raisedSizer($_[0]->getArg(2), $_[0]->getArg(1)->negate); });
DefConstructor('\lxSVG@hskip Glue', sub {
my ($doc, $skip) = @_;
if (foreignObjectCheck($doc)) {
$doc->openElement('ltx:text',
'xoffset' => $skip->pxValue . 'px',
'_noautoclose' => '0'); } },
alias => '\hskip');
# Like regular \hbox, \vbox, but we don't want any extra element around it.
DefConstructor('\lxSVG@hbox BoxSpecification HBoxContents', '#2',
mode => 'text', bounded => 1,
# Workaround for $ in alignment; an explicit \hbox gives us a normal $.
# And also things like \centerline that will end up bumping up to block level!
sizer => '#2',
alias => '\hbox',
beforeDigest => sub { reenterTextMode(); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $spec = $whatsit->getArg(1);
my $box = $whatsit->getArg(2);
if (my $w = GetKeyVal($spec, 'to')) {
$whatsit->setWidth($w); }
elsif (my $s = GetKeyVal($spec, 'spread')) {
$whatsit->setWidth($box->getWidth->add($s)); }
return; });
DefConstructor('\lxSVG@vbox BoxSpecification VBoxContents', '#2',
sizer => '#2',
alias => '\vbox',
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $spec = $whatsit->getArg(1);
my $box = $whatsit->getArg(2);
if (my $h = GetKeyVal($spec, 'to')) {
$whatsit->setHeight($h); }
elsif (my $s = GetKeyVal($spec, 'spread')) {
$whatsit->setHeight($box->getHeight->add($s)); }
return; },
mode => 'text');
#=====================================================================#
# 1. Beginning and ending a stream ===================================#
#=====================================================================#
DefMacro('\pgfsys@typesetpicturebox{}', <<'EoTeX');
% \pgf@picmaxx,\pgf@pixmaxy are now the SIZE of the picture!!!!!!
\advance\pgf@picmaxy by-\pgf@picminy\relax%
\advance\pgf@picmaxx by-\pgf@picminx\relax%
\ht#1=\pgf@picmaxy%
\wd#1=\pgf@picmaxx%
\dp#1=0pt%
\leavevmode%\message{width: \the\pgf@picmaxx, height:\the\pgf@picmaxy}%%
\lxSVG@insertpicture{\box#1}%
EoTeX
DefMacro('\pgfsys@beginpicture', '');
DefMacro('\pgfsys@endpicture', '');
# # Note that this is overly generous with svg:foreignObject.
# # It is used to recursively contain svg:svg elements
# # also simple strings end up there (they _could_ have exotic latexml attributes...)
DefConstructor('\pgfsys@hbox{Number}', sub {
my ($doc, $boxnumber, %prop) = @_;
my $box = $prop{thebox};
return unless $box;
my $font = $box->getFont;
my ($w, $h, $d) = $box->getSize;
my ($fw, $fh, $fd) = $font && $font->getNominalSize;
# Fishing for the correct y position (maybe w/o the depth?)
# my $y = $h->pxValue; # Seemingly should NOT have ->add($d)
my $y = $h->add(($font ? $fd : $d))->pxValue; # Seemingly should NOT have ->add($d)
# But if the box's height has been set to 0, still need adjustment; Use font?
$y = $fh->add($fd)->pxValue if !$y && $font; # Seemingly SHOULD have ->add($fd)
$doc->openElement('svg:g',
transform => 'matrix(1 0 0 -1 0 ' . $y . ')',
class => 'ltx_svg_fog');
my $strokeattr = $doc->findnode('ancestor-or-self::*[@stroke][1]/@stroke', $doc->getElement);
my $sw = $doc->openElement('svg:switch');
my $H = $h->add($d)->pxValue;
my $fo = $doc->openElement('svg:foreignObject',
# often the size is set to 0, which inhibits display COMPLETELY!
# Using 100% works, though, and seems apparently best to use for height all the time(?)
width => $w->pxValue || '100%',
height => '100%',
overflow => 'visible',
color => $strokeattr && $strokeattr->getValue);
my $p = $doc->openElement('ltx:p');
$doc->absorb($box);
$doc->maybeCloseElement('ltx:text');
# If the nodes we just created are still there & open (see halign)
# then close them now.
$doc->maybeCloseElement('ltx:p');
if (my $n = $doc->isCloseable('svg:foreignObject')) {
if ($n->isSameNode($fo)) {
$doc->closeElement('svg:foreignObject');
$doc->closeElement('svg:switch');
$doc->closeElement('svg:g');
# Check the children of the inserted ltx:p
# We should at least try to denest svg ?
# If svg:switch/svg:foreignObject/ltx:p/ltx:picture/svg:svg/svg:g
# should be replaced by just the final svg:g !!! (?)
my @ch = element_nodes($p);
if ((scalar(@ch) == 1) && ($doc->getNodeQName($ch[0]) eq 'ltx:picture')) {
my @gch = element_nodes($ch[0]);
if ((scalar(@gch) == 1) && ($doc->getNodeQName($gch[0]) eq 'svg:svg')) {
my ($g) = element_nodes($gch[0]);
$doc->replaceNode($sw, $g);
} } } }
# Could conceivably be simplifying ltx:text ???
},
reversion => sub {
my $box = $_[0]->getProperty('thebox');
return ($box ? $box->revert : ()); },
afterDigest => sub {
my ($stomach, $whatsit) = @_;
$whatsit->setProperty(thebox => LookupValue('box' . $whatsit->getArg(1)->valueOf)); }
);
#====================================================================#
#= 2. Path construction =============================================#
#====================================================================#
#=====================================================================
# Helpers
# Adding an element to the current SVG path
sub addToSVGPath {
my ($operation, @points) = @_;
my $newPath = join(' ', $operation, map { $_->pxValue } @points);
if (my $currentPath = LookupValue('pgf_SVGpath')) {
AssignValue(pgf_SVGpath => $currentPath . ' ' . $newPath, 'global'); }
else {
AssignValue(pgf_SVGpath => $newPath, 'global'); }
return; }
#=====================================================================
# Implementation
# Start a path at a specific point (x,y) or to move the current point of the
# current path to (x,yp) without drawing anything upon stroking (the current
# path is 'interrupted'
DefConstructor('\pgfsys@moveto{Dimension}{Dimension}', '',
afterDigest => sub { addToSVGPath('M', $_[1]->getArgs); });
# Continue the current path to (#1,#2) with a line.
DefConstructor('\pgfsys@lineto{Dimension}{Dimension}', '',
afterDigest => sub { addToSVGPath('L', $_[1]->getArgs); });
# Continue the current path with a bezier curver to (#5,#6). The
# control points of the curve are at (#1,#2) and (#3,#4).
DefConstructor('\pgfsys@curveto{Dimension}{Dimension}{Dimension}'
. '{Dimension}{Dimension}{Dimension}', '',
afterDigest => sub { addToSVGPath('C', $_[1]->getArgs); });
# Append a rectangle to the current path whose lower left corner is at
# (#1,#2) and whose width/height is given by (#3,#4).
DefConstructor('\pgfsys@rect{Dimension}{Dimension}{Dimension}{Dimension}', '',
afterDigest => sub {
my ($x, $y, $w, $h) = $_[1]->getArgs;
addToSVGPath('M', $x, $y);
addToSVGPath('h', $w);
addToSVGPath('v', $h);
addToSVGPath('h', $w->negate);
addToSVGPath('Z'); });
# Close the current path. This results in joining the current point of
# the path with the point specified by the last moveto
# operation.
DefConstructor('\pgfsys@closepath', '',
afterDigest => sub { addToSVGPath('Z'); });
#=====================================================================#
#= 3. Canvas transformation ==========================================#
#=====================================================================#
# Perform a concatenation of the low-level current transformation
# matrix with the matrix given by the values #1 to #6. The
# transformation matrix is a transformation on a homogeneous
# 2D-coordinate system.
DefMacro('\pgfsys@transformcm {Float}{Float}{Float}{Float}{Dimension}{Dimension}',
'\lxSVG@transformcm{#1}{#2}{#3}{#4}{#5}{#6}'
. '\lxSVG@@transformcm{#1}{#2}{#3}{#4}{#5}{#6}');
DefConstructor('\lxSVG@transformcm {Float}{Float}{Float}{Float}{Dimension}{Dimension}', '',
alias => '\pgfsys@transformcm');
DefMacro('\lxSVG@@transformcm {Float}{Float}{Float}{Float}{Dimension}{Dimension}', sub {
my ($gullet, @args) = @_;
my $transform = 'transform=matrix('
. join(' ',
(map { $_->valueOf } @args[0 .. 3]),
(map { $_->pxValue } @args[4 .. 5])) . ')';
Invocation('\lxSVG@begingroup', TokenizeInternal($transform)); });
#=====================================================================#
#= 4. Stroking, filling, and clipping ================================#
#=====================================================================#
DefConstructor('\pgfsys@clipnext', '',
afterDigest => sub { AssignValue(pgf_clipnext => 1); });
DefMacro('\pgfsys@stroke', '\lxSVG@stroke\lxSVG@drawpath{fill:none}');
DefMacro('\pgfsys@fill', '\lxSVG@fill\lxSVG@drawpath{stroke:none}');
DefMacro('\pgfsys@fillstroke', '\lxSVG@fillstroke\lxSVG@drawpath{}');
DefConstructor('\lxSVG@stroke', '', alias => '\pgfsys@stroke', sizer => 0);
DefConstructor('\lxSVG@fill', '', alias => '\pgfsys@fill', sizer => 0);
DefConstructor('\lxSVG@fillstroke', '', alias => '\pgfsys@fillstroke', sizer => 0);
#======================================================================
# Warning: assignments in Macro!!!
DefMacro('\lxSVG@drawpath{}', sub {
my ($gullet, $arg) = @_;
my $path = LookupValue('pgf_SVGpath') || '';
AssignValue(pgf_SVGpath => '', 'global');
my $op;
if (LookupValue('pgf_clipnext')) {
AssignValue(pgf_clipnext => 0);
addToCount('pgf_scopecount', 1, 'global');
$op = Invocation('\lxSVG@drawpath@clipped', $path, $arg); }
else {
$op = Invocation('\lxSVG@drawpath@unclipped', $path, $arg); }
Invocation('\pgfsysprotocol@literal', $op)->unlist; });
# Seemingly can get called with no path, which is invalid
DefConstructor('\lxSVG@drawpath@unclipped{}{}',
'?#1(<svg:path d="#1" style="#2" />)()',
reversion => '');
DefConstructor('\lxSVG@drawpath@clipped{}{}',
'<svg:clipPath id="pgfcp#obj">'
. '<svg:path id="pgfpath#obj" d="#1" />'
. '</svg:clipPath>'
. '<svg:use xlink:href="##pgfpath#obj" style="#2" />'
. '<svg:g clip-path="url(##pgfcp#obj)">',
reversion => '',
properties => { obj => sub { SVGNextObject(); } });
# #======================================================================
DefMacro('\pgfsys@discardpath', '\lxSVG@discardpath\lxSVG@@discardpath');
DefConstructor('\lxSVG@discardpath', '', alias => '\pgfsys@discardpath', sizer => 0);
DefMacro('\lxSVG@@discardpath', sub {
my $path = LookupValue('pgf_SVGpath') || '';
AssignValue(pgf_SVGpath => '', 'global');
if (LookupValue('pgf_clipnext')) {
AssignValue(pgf_clipnext => 0);
addToCount('pgf_scopecount', 1, 'global');
Invocation('\pgfsysprotocol@literal',
Invocation(T_CS('\lxSVG@discardpath@clipped'), $path))->unlist; }
else {
(); } });
DefConstructor('\lxSVG@discardpath@clipped{}',
'<svg:clipPath id="pgfcp#obj">'
. '<svg:path d="#1" />'
. '</svg:clipPath>'
. '<svg:g clip-path="url(##pgfcp#obj)">',
reversion => '', sizer => 0,
properties => { obj => sub { SVGNextObject(); } });
#=====================================================================#
#= 5. Graphic state option ===========================================#
#=====================================================================#
#=====================================================================
# There's something fishy about counting <svg:g>'s.
# They need to be kept track of consistently at the same level (macro, primitive..)
DefPrimitive('\lxSVG@incrgroup{}', sub { addToCount('pgf_scopecount', +1, 'global'); });
DefPrimitive('\lxSVG@decrgroup{}', sub { addToCount('pgf_scopecount', -1, 'global'); });
# Open an <svg:g>, with some sort of options
# keeping track of how many have been opened.
# Note this is basically \pgf@sys@svg@gs
# Note silliness if invoked outside picture.
DefMacro('\lxSVG@begingroup{}',
'\ifpgfpicture\lxSVG@incrgroup\pgfsysprotocol@literal{\lxSVG@begingroup@{#1}}\fi');
DefMacro('\lxSVG@endgroup',
'\lxSVG@decrgroup\pgfsysprotocol@literal{\lxSVG@endgroup@}');
DefMacro('\lxSVG@endgroups', sub {
map { T_CS('\lxSVG@endgroup') } 1 .. LookupValue('pgf_scopecount'); });
DefConstructor('\lxSVG@begingroup@ RequiredKeyVals',
'<svg:g %&GetKeyVals(#1)>',
reversion => '', sizer => 0);
DefConstructor('\lxSVG@endgroup@',
'</svg:g>',
reversion => '', sizer => 0);
#=====================================================================
# Implementation
DefMacro('\pgfsys@setlinewidth{}',
'\lxSVG@setlinewidth{#1}\lxSVG@begingroup{stroke-width={#1}}');
DefMacro('\pgfsys@buttcap',
'\lxSVG@buttcap\lxSVG@begingroup{stroke-linecap=butt}');
DefMacro('\pgfsys@roundcap',
'\lxSVG@roundcap\lxSVG@begingroup{stroke-linecap=round}');
DefMacro('\pgfsys@rectcap',
'\lxSVG@rectcap\lxSVG@begingroup{stroke-linecap=rect}');
DefMacro('\pgfsys@miterjoin',
'\lxSVG@miterjoin\lxSVG@begingroup{stroke-linejoin=miter}');
DefMacro('\pgfsys@setmiterlimit{}',
'\lxSVG@setmiterlimit{#1}\lxSVG@begingroup{stroke-miterlimit={#1}}');
DefMacro('\pgfsys@roundjoin',
'\lxSVG@roundjoin\lxSVG@begingroup{stroke-linejoin=round}');
DefMacro('\pgfsys@beveljoin',
'\lxSVG@beveljoin\lxSVG@begingroup{stroke-linejoin=bevel}');
DefMacro('\pgfsys@setdash{}{}',
'\lxSVG@setdash{#1}{#2}'
. '\edef\pgf@test@dashpattern{#1}'
. '\lxSVG@begingroup{stroke-dasharray={\ifx\pgf@test@dashpattern\pgfutil@empty none\else#1\fi},'
. ' stroke-dashoffset={#2}}');
DefMacro('\pgfsys@eoruletrue',
'\lxSVG@eoruletrue\lxSVG@begingroup{fill-rule=evenodd}');
DefMacro('\pgfsys@eorulefalse',
'\lxSVG@eorulefalse\lxSVG@begingroup{fill-rule=nonzero}');
# Constructors just for reversion
DefConstructor('\lxSVG@setlinewidth{}', '', alias => '\pgfsys@setlinewidth', sizer => 0);
DefConstructor('\lxSVG@buttcap', '', alias => '\pgfsys@buttcap', sizer => 0);
DefConstructor('\lxSVG@roundcap', '', alias => '\pgfsys@roundcap', sizer => 0);
DefConstructor('\lxSVG@rectcap', '', alias => '\pgfsys@rectcap', sizer => 0);
DefConstructor('\lxSVG@miterjoin', '', alias => '\pgfsys@miterjoin', sizer => 0);
DefConstructor('\lxSVG@setmiterlimit{}', '', alias => '\pgfsys@setmiterlimit', sizer => 0);
DefConstructor('\lxSVG@roundjoin', '', alias => '\pgfsys@roundjoin', sizer => 0);
DefConstructor('\lxSVG@beveljoin', '', alias => '\pgfsys@beveljoin', sizer => 0);
DefConstructor('\lxSVG@setdash{}{}', '', alias => '\pgfsys@setdash', sizer => 0);
DefConstructor('\lxSVG@eoruletrue', '', alias => '\pgfsys@eoruletrue', sizer => 0);
DefConstructor('\lxSVG@eorulefalse', '', alias => '\pgfsys@eorulefalse', sizer => 0);
#=====================================================================#
#= 6. Color ==========================================================#
#=====================================================================#
DefMacro('\lxSVG@setcolor{}{}', '\ifpgfpicture\lxSVG@begingroup{#1={#2}}\fi');
# Need to defer until after color/xcolor are loaded!
AtBeginDocument('\def\XC@mcolor{\pgfsetcolor{.}}');
#=====================================================================
# Implementation
DefMacro('\pgfsys@color@rgb{}{}{}',
'\pgfsys@color@rgb@stroke{#1}{#2}{#3}\pgfsys@color@rgb@fill{#1}{#2}{#3}');
DefMacro('\lxSVG@RGB{}{}{}', sub { Explode(Color('rgb', $_[1], $_[2], $_[3])->toHex); });
DefMacro('\lxSVG@CMYK{}{}{}{}', sub { Explode(Color('cmyk', $_[1], $_[2], $_[3], $_[4])->toHex); });
DefMacro('\lxSVG@CMY{}{}{}', sub { Explode(Color('cmy', $_[1], $_[2], $_[3])->toHex); });
DefMacro('\lxSVG@GRAY{}', sub { Explode(Color('gray', $_[1])->toHex); });
DefMacro('\pgfsys@color@rgb@stroke{}{}{}',
'\lxSVG@color@rgb@stroke{#1}{#2}{#3}\lxSVG@begingroup{stroke=\lxSVG@RGB{#1}{#2}{#3}}');
DefMacro('\pgfsys@color@rgb@fill{}{}{}',
'\lxSVG@color@rgb@fill{#1}{#2}{#3}\lxSVG@begingroup{fill=\lxSVG@RGB{#1}{#2}{#3}}');
DefMacro('\pgfsys@color@cmyk@stroke{}{}{}{}',
'\lxSVG@color@cmyk@stroke{#1}{#2}{#3}{#4}\lxSVG@begingroup{stroke=\lxSVG@CMYK{#1}{#2}{#3}{#4}}');
DefMacro('\pgfsys@color@cmyk@fill{}{}{}{}',
'\lxSVG@color@cmyk@fill{#1}{#2}{#3}{#4}\lxSVG@begingroup{fill=\lxSVG@CMYK{#1}{#2}{#3}{#4}}');
DefMacro('\pgfsys@color@cmy@stroke{}{}{}',
'\lxSVG@color@cmy@stroke{#1}{#2}{#3}\lxSVG@begingroup{stroke=\lxSVG@CMY{#1}{#2}{#3}}');
DefMacro('\pgfsys@color@cmy@fill{}{}{}',
'\lxSVG@color@cmy@fill{#1}{#2}{#3}\lxSVG@begingroup{fill=\lxSVG@CMY{#1}{#2}{#3}}');
DefMacro('\pgfsys@color@gray@stroke{}',
'\lxSVG@color@gray@stroke{#1}\lxSVG@begingroup{stroke=\lxSVG@GRAY{#1}}');
DefMacro('\pgfsys@color@gray@fill{}',
'\lxSVG@color@gray@fill{#1}\lxSVG@begingroup{fill=\lxSVG@GRAY{#1}}');
DefConstructor('\lxSVG@color@rgb@stroke{}{}{}', '', alias => '\pgfsys@color@rgb@stroke', sizer => 0);
DefConstructor('\lxSVG@color@rgb@fill{}{}{}', '', alias => '\pgfsys@color@rgb@fill', sizer => 0);
DefConstructor('\lxSVG@color@cmyk@stroke{}{}{}{}', '', alias => '\pgfsys@color@cmyk@stroke', sizer => 0);
DefConstructor('\lxSVG@color@cmyk@fill{}{}{}{}', '', alias => '\pgfsys@color@cmyk@fill', sizer => 0);
DefConstructor('\lxSVG@color@cmy@stroke{}{}{}', '', alias => '\pgfsys@color@cmy@stroke', sizer => 0);
DefConstructor('\lxSVG@color@cmy@fill{}{}{}', '', alias => '\pgfsys@color@cmy@fill', sizer => 0);
DefConstructor('\lxSVG@color@gray@stroke{}', '', alias => '\pgfsys@color@gray@stroke', sizer => 0);
DefConstructor('\lxSVG@color@gray@fill{}', '', alias => '\pgfsys@color@gray@fill', sizer => 0);
#====================================================================#
#= 7. Pattern =======================================================#
#====================================================================#
# \pgfsys@declarepattern{name}{x1}{y1}{x2}{y2}{x step}{y step}{code}{flag}
DefMacro('\pgfsys@declarepattern{}'
. '{Dimension}{Dimension}{Dimension}{Dimension}{Dimension}{Dimension}'
. '{}{Number}', sub {
my ($gullet, $name, $x1, $y1, $x2, $y2, $x_step, $y_step, $code, $flag) = @_;
AssignValue('\pgf@xa' => $x1); AssignValue('\pgf@ya' => $y1);
AssignValue('\pgf@xb' => $x2); AssignValue('\pgf@yb' => $y2);
AssignValue('\pgf@xc' => $x_step); AssignValue('\pgf@yc' => $y_step);
my $op = ($flag->valueOf == 1 ? '\lxSVG@coloredpattern' : '\lxSVG@uncoloredpattern');
return Invocation('\pgfsysprotocol@literal',
Invocation($op, $name, $x_step, $y_step, $code)); });
DefMacro('\pgfsys@setpatternuncolored{}{}{}{}', sub {
addToCount('pgf_scopecount', 1, 'global');
Invocation('\pgfsysprotocol@literal',
Invocation('\lxSVG@setpatternuncolored@', @_[1 .. $#_])); });
DefMacro('\pgfsys@setpatterncolored{}',
'\lxSVG@setcolor{fill}{url(\#pgfpat#1)}');
DefConstructor('\lxSVG@coloredpattern{}{Dimension}{Dimension}{}',
'<svg:defs><svg:pattern ' .
'id ="pgfpat#1" ' .
'patternUnits="userSpaceOnUse" ' .
'width="#x_step" height="#y_step">' .
'#4' .
'</svg:pattern></svg:defs>',
properties => {
x_step => sub { $_[2]->pxValue; },
y_step => sub { $_[3]->pxValue; } });
DefConstructor('\lxSVG@uncoloredpattern{}{Dimension}{Dimension}{}',
'<svg:defs>' .
'<svg:pattern '
. 'id ="pgfpat#1" '
. 'patternUnits="userSpaceOnUse" '
. 'width="#x_step" height="#y_step">'
. '<svg:symbol id="pgfsym#1">#4</svg:symbol>'
. '</svg:pattern></svg:defs>',
properties => {
x_step => sub { $_[2]->pxValue; },
y_step => sub { $_[3]->pxValue; } });
DefConstructor('\lxSVG@setpatternuncolored@{}{}{}{}',
'<svg:defs><svg:pattern id="pgfupat#obj" xlink:href="##pgfpat#1">'
. '<svg:g stroke="#color" fill="#color">'
. '<svg:use xlink:href="##pgfsym#1"/>'
. '</svg:g>'
. '</svg:pattern></svg:defs>'
. '<svg:g fill="url(##pgfupat#obj)">',
properties => {
obj => sub { SVGNextObject(); },
color => sub { Color('rgb', $_[2], $_[3], $_[4]); } });
#====================================================================#
#= 8. Scoping =======================================================#
#====================================================================#
# Saves the current graphic state on a graphic state stack. All
# changes to the graphic state parameters mentioned for \pgfsys@stroke
# and \pgfsys@fill will be local to the current graphic state and will
# the old values will be restored after endscope is used.
# Do we really need an <svg:g> w/no attributes???
#DefMacro('\pgfsys@beginscope', '\lxSVG@beginscope\lxSVG@begingroup{}');
DefMacro('\pgfsys@beginscope', '\lxSVG@beginscope');
DefMacro('\pgfsys@endscope', '\lxSVG@endgroups\lxSVG@endscope');
DefConstructor('\lxSVG@beginscope', '',
afterDigest => sub {
my ($stomach) = @_;
AssignValue(pgf_scopecount_save => LookupValue('pgf_scopecount') || 0);
AssignValue(pgf_scopecount => 0, 'global');
$stomach->begingroup; },
alias => '\pgfsys@beginscope', sizer => 0);
DefConstructor('\lxSVG@endscope', '',
afterDigest => sub {
my ($stomach) = @_;
$stomach->endgroup;
AssignValue(pgf_scopecount => LookupValue('pgf_scopecount_save') || 0, 'global'); },
alias => '\pgfsys@endscope', sizer => 0);
#=====================================================================#
#= 9. Image ==========================================================#
#=====================================================================#
#=====================================================================
# Helpers
RawTeX('\newbox\lxSVG@imgbox');
#=====================================================================
# Implementation
DefMacro('\pgfsys@defineimage',
'\edef\pgf@image{\lxSVG@includegraphics{\pgf@imagewidth}{\pgf@imageheight}{\pgf@filename}}');
DefConstructor('\lxSVG@includegraphics{}{} Semiverbatim',
"<ltx:graphics graphic='#graphic' candidates='#candidates' options='#options'/>",
beforeConstruct => sub {
my ($document, $whatsit) = @_;
if (!foreignObjectCheck($document)) {
$document->openElement('svg:foreignObject',
width => $whatsit->getWidth, height => $whatsit->getHeight);
$whatsit->setProperty(OpenedForeignObject => 1); } },
afterConstruct => sub {
my ($document, $whatsit) = @_;
if ($whatsit->getProperty('OpenedForeignObject')) {
$document->closeElement('svg:foreignObject'); } },
sizer => \&image_graphicx_sizer,
properties => sub {
my ($stomach, $w, $h, $graphic) = @_;
$w = ToString($w);
$h = ToString($h);
my $options = join(',', ($w ? ('width=' . $w) : ()), ($h ? ('height=' . $h) : ()));
$graphic = ToString($graphic); $graphic =~ s/^\s+//; $graphic =~ s/\s+$//;
my @candidates = pathname_findall($graphic, types => ['*'],
paths => LookupValue('GRAPHICSPATHS'));
if (my $base = LookupValue('SOURCEDIRECTORY')) {
@candidates = map { pathname_relative($_, $base) } @candidates; }
(graphic => $graphic,
candidates => join(',', @candidates),
options => $options); },
alias => '\includegraphics');
#=====================================================================#
#= 10. Shading =======================================================#
#=====================================================================#
#=====================================================================
# Helpers
DefMacro('\lxSVG@sh@create', sub {
(T_CS('\lxSVG@sh@intervals'), Expand(T_CS('\pgf@sys@shading@ranges')),
T_BEGIN,
T_BEGIN, T_CS('\pgf@sys@shading@end@pos'), T_END,
T_BEGIN, T_CS('\pgf@sys@shading@end@pos'), T_END,
T_BEGIN, T_CS('\pgf@sys@shading@end@rgb'), T_END,
T_BEGIN, T_CS('\pgf@sys@shading@end@rgb'), T_END,
T_END, T_BEGIN, T_END); });
DefPrimitive('\lxSVG@sh@color{Float}{Float}{Float}', sub {
AssignValue(pgf_sh_color => Color('rgb', map { $_->valueOf } @_[1 .. $#_]));
return; });
DefMacro('\lxSVG@sh@interval@{}',
'\lxSVG@sh@stashstop{\lxSVG@sh@stop{#1}{\pgf@sys@shading@end@pos}}');
DefPrimitive('\lxSVG@sh@stashstop{}', sub {
PushValue('pgf_sh_stops' => Digest($_[1]));
return; });
DefConstructor('\lxSVG@sh@stop{Dimension}{Dimension}',
"<svg:stop offset='#offset' stop-color='#stopcolor'/>",
properties => sub {
my ($r, $g, $b) = LookupValue('pgf_sh_color')->rgb->components;
(offset => $_[1]->pxValue / $_[2]->pxValue,
stopcolor => LookupValue('pgf_sh_color')); });
DefMacro('\lxSVG@sh@interval{}{}{}{}', sub {
(T_CS('\lxSVG@sh@color'), $_[3],
Invocation(T_CS('\lxSVG@sh@interval@'), $_[1])); });
DefMacro('\lxSVG@sh@intervals{}', sub {
my ($gullet, $point) = @_;
$point = Expand($point);
return if !ToString($point);
(T_CS('\lxSVG@sh@interval'), $point,
T_CS('\lxSVG@sh@intervals')); });
# These should avoid creating so many constructors at run time
DefPrimitive('\lxSVG@sh@defstripes{}{Number}', sub {
my ($stomach, $name, $flag) = @_;
my $stops = List(@{ LookupValue('pgf_sh_stops') });
AssignValue(pgf_sh_stops => []);
my $x = LookupValue('\pgf@x')->pxValue;
my $y = LookupValue('\pgf@y')->pxValue;
DefPrimitiveI('\@pgfshading' . $name->ToString . '!', undef, sub {
my $objcount = SVGNextObject();
DefConstructor('\lxSVG@sh@defs',
'<svg:defs><svg:linearGradient id="pgfsh' . $objcount . '" '
. ($flag->valueOf == 1 ? 'gradientTransform="rotate(90)"' : '') . '>'
. '#stops'
. '</svg:linearGradient></svg:defs>',
properties => { stops => $stops }
);
DefConstructor('\lxSVG@sh',
'<svg:rect width="' . $x . '" height="' . $y . '" '
. 'style="fill:url(##pgfsh' . $objcount . ');stroke:none" />');
DefMacro('\lxSVG@pos', sub { Invocation(T_CS('\pgfpoint'), $x, $y); });
return; }, scope => 'global');
return; });
DefPrimitive('\lxSVG@sh@defcircles{}', sub {
my ($stomach, $name) = @_;
my $stops = List(@{ LookupValue('pgf_sh_stops') });
AssignValue(pgf_sh_stops => []);
my $endpos = Dimension(ToString Expand T_CS '\pgf@sys@shading@end@pos')->pxValue;
my $x = LookupValue('\pgf@x')->pxValue * 8 / ($endpos * 16) + 0.5;
my $y = LookupValue('\pgf@y')->pxValue * 8 / ($endpos * 16) + 0.5;
DefPrimitiveI('\@pgfshading' . $name->ToString . '!', undef, sub {
my $objcount = SVGNextObject();
DefConstructor('\lxSVG@sh@defs',
'<svg:defs><svg:radialGradient id="pgfsh' . $objcount . '" '
. 'fx="' . $x . '" fy="' . $y . '">'
. '#stops'
. '</svg:radialGradient></svg:defs>',
properties => { stops => $stops }
);
DefConstructor('\lxSVG@sh',
'<svg:circle cx="' . $endpos . '" cy="' . $endpos . '" r="' . $endpos . '" '
. 'style="fill:url(##pgfsh' . $objcount . ');stroke:none" />');
DefMacro('\lxSVG@pos', sub {
Invocation(T_CS('\pgfpoint'), 2 * $endpos, 2 * $endpos) });
return; }, scope => 'global');
return; });
DefConstructor('\lxSVG@sh@insert{Dimension}{Dimension}{}',
'<svg:g transform="translate(#x #y)">#3</svg:g>',
properties => {
# x => sub { $_[1]->pxValue; },
# y => sub { $_[2]->pxValue; } });
#### What's going on here? Why pt's instead of px ? (what are units?)
### (with px shading's way off)
x => sub { $_[1]->ptValue; },
y => sub { $_[2]->ptValue; } });
# \lxSVG@process{Dimension}{Dimension}
DefMacro('\lxSVG@process{}{}',
'\ifdim\pgf@picmaxx<#1\global\pgf@picmaxx=#1\fi'
. '\ifdim\pgf@picmaxy<#2\global\pgf@picmaxy=#2\fi'
. '\ifdim\pgf@picminx>#1\global\pgf@picminx=#1\fi'
. '\ifdim\pgf@picminy>#2\global\pgf@picminy=#2\fi');
#=====================================================================
# Implementation
DefMacro('\pgfsys@shadinginsidepgfpicture{}', <<'EoTeX');
%\message{Using shading \string#1}
#1\lxSVG@sh@defs%
\pgf@process{\lxSVG@pos}%
\pgf@x=-.5\pgf@x\relax\pgf@y=-.5\pgf@y\relax%
% \lxSVG@process{\pgf@x}{\pgf@y}%
% \pgf@x=-1\pgf@x\relax\pgf@y=-1\pgf@y\relax%
% \lxSVG@process{\pgf@x}{\pgf@y}%
\lxSVG@sh@insert{\pgf@x}{\pgf@y}{\lxSVG@sh}
EoTeX
DefMacro('\pgfsys@shadingoutsidepgfpicture{}', <<'EoTeX');
\begingroup\lxSVG@installcommands%
#1%
\setbox\pgfpic=\hbox to0pt{%
\lxSVG@sh@defs%
\lxSVG@sh%
}%
\pgf@process{\lxSVG@pos}%
\pgf@picminx=0pt%
\pgf@picminy=0pt%
\pgf@picmaxx=\pgf@x%
\pgf@picmaxy=\pgf@y%
\pgfsys@typesetpicturebox{\pgfpic}%
\endgroup
EoTeX
# \pgfsys@horishading{}{Dimension}{}
DefMacro('\pgfsys@horishading{}{}{}', sub {
my ($gullet, $name, $height, $specs) = @_;
(Invocation(T_CS('\pgf@parsefunc'), $specs),
T_CS('\lxSVG@sh@create'),
Invocation(T_CS('\pgf@process'), Invocation(T_CS('\pgfpoint'),
T_CS('\pgf@sys@shading@end@pos'), $height)),
Invocation(T_CS('\lxSVG@sh@defstripes'),
$name, Number(0))); });
## \pgfsys@vertshading{}{Dimension}{}
DefMacro('\pgfsys@vertshading{}{}{}', sub {
my ($gullet, $name, $height, $specs) = @_;
(Invocation(T_CS('\pgf@parsefunc'), $specs),
T_CS('\lxSVG@sh@create'),
Invocation(T_CS('\pgf@process'), Invocation(T_CS('\pgfpoint'),
T_CS('\pgf@sys@shading@end@pos'), $height)),
Invocation(T_CS('\lxSVG@sh@defstripes'), $name, Number(1))); });
DefMacro('\pgfsys@radialshading{}{}{}', sub {
my ($gullet, $name, $point, $specs) = @_;
(Invocation(T_CS('\pgf@parsefunc'), $specs),
T_CS('\lxSVG@sh@create'),
Invocation(T_CS('\pgf@process'), $point),
Invocation(T_CS('\lxSVG@sh@defcircles'), $name)); });
# Wow... postscript function...
DefMacro('\pgfsys@functionalshading{}{}{}', sub {
my ($gullet, $name, $ll, $ur, $psfct) = @_;
Let(T_CS('\lxSVG@sh@defs'), T_CS('\relax'));
Let(T_CS('\lxSVG@sh'), T_CS('\relax'));
Let(T_CS('\lxSVG@pos'), T_CS('\relax')); });
#=====================================================================#
#= 11. Transparency ==================================================#
#=====================================================================#
DefMacro('\pgfsys@stroke@opacity{}',
'\lxSVG@stroke@opacity{#1}\lxSVG@begingroup{stroke-opacity={#1}}');
DefMacro('\pgfsys@fill@opacity{}',
'\lxSVG@fill@opacity{#1}\lxSVG@begingroup{fill-opacity={#1}}');
DefMacro('\pgfsys@fadingfrombox{}{}',
'\lxSVG@fadingfrombox{#1}{#2}'); # NOT IMPLEMENTED
DefMacro('\pgfsys@usefading{}{}{}{}{}{}{}',
'\lxSVG@usefading{#1}{#2}{#3}{#4}{#5}{#6}{#7}'); # NOT IMPLEMENTED
DefMacro('\pgfsys@transparencygroupfrombox{}',
'\lxSVG@transparencygroupfrombox{#1}'); # NOT IMPLEMENTED
DefMacro('\pgfsys@definemask',
'\lxSVG@definemask'); # NOT IMPLEMENTED
# For reversion
DefConstructor('\lxSVG@stroke@opacity{}', '', alias => '\pgfsys@stroke@opacity', sizer => 0);
DefConstructor('\lxSVG@fill@opacity{}', '', alias => '\pgfsys@fill@opacity', sizer => 0);
DefConstructor('\lxSVG@fadingfrombox{}{}', '', alias => '\pgfsys@fadingfrombox', sizer => 0);
DefConstructor('\lxSVG@usefading{}{}{}{}{}{}{}', '', alias => '\pgfsys@usefading', sizer => 0);
DefConstructor('\lxSVG@transparencygroupfrombox{}', '', alias => '\pgfsys@transparencygroupfrombox{}', sizer => 0);
DefConstructor('\lxSVG@definemask', '', alias => '\pgfsys@definemask', sizer => 0);
#=====================================================================#
#= 12. Reusable objects ==============================================#
#=====================================================================#
DefConstructor('\pgfsys@invoke{}', '#1');
DefMacro('\pgfsys@markposition{}', '');
#=====================================================================#
#= 13. Invisibility ==================================================#
#=====================================================================#
RawTeX('\def\pgfsys@begininvisible#1\pgfsys@endinvisible{}'); # well...
#=====================================================================#
#= 14. The protocol subsystem ========================================#
#=====================================================================#
# # Adds the literal text to the current protocol, after it has been
# # \edef-ed. This command will always protocol.
# DefPrimitive('\pgfsysprotocol@literalbuffered{}', sub {});
# # First calls \pgfsysprotocol@literalbuffered on literal text . Then,
# # if protocolling is currently switched off, the literal text is passed
# # on to pgfsys@invoke, which just inserts it into the document.
# DefMacro('\pgfsysprotocol@literal{}', '');
# # Stores the current protocol in macro name for later use.
# DefPrimitive('\pgfsysprotocol@getcurrentprotocol{}', sub {});
# # Sets the current protocol to macro name.
# DefMacro('\pgfsysprotocol@setcurrentprotocol{}', sub{});
# # Inserts the text stored in the current protocol into the file.
# DefMacro('\pgfsysprotocol@invokecurrentprotocol', sub{});
# # First inserts the current protocol, then sets the current protocol to
# # the empty string.
# DefMacro('\pgfsysprotocol@flushcurrentprotocol', sub{});
#=====================================================================#
#= 15. Overflowing ===================================================#
#=====================================================================#
#=====================================================================#
#= XX. Matrix ========================================================#
#=====================================================================#
# Here we're basically groping to figure out how to adapt
# LaTeXML's Alignment structures to the tikz code.
# Note that svg doesn't have a table/array structure.
#
# I guess we should just be putting <svg:g> in, but with appropriate class?
# For this to work, we'll have to come in after the fact, sum up things & specify cell sizes.
# Also, it seems that we've already ended up with a foreign object box?
#
# But there are still some major problems:
# On top of that, we're somehow getting out-of-sync with the
# bgroup/egroup & begingroup/endgroup pairs that tikz/pgf is adding
# and the ones embedded in our alignment machinery.
# I _THINK_ we're running afoul of when the last item of \matrix ends with \\ ???
#
# It also appears that in order to align the cells,
# tikz makes their width 0; unfortunately this makes them
# disappear in the svg (overflow=visible doesn't seem to help)
DefConstructor('\lxSVG@halign BoxSpecification',
'#body',
reversion => '\halign #1{#2\cr#3}',
# bounded => 1,
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my $gullet = $stomach->getGullet;
my $t = $gullet->readNonSpace;
Error('expected', '\bgroup', $stomach, "Missing \\halign box") unless Equals($t, T_BEGIN);
print STDERR "\nHALIGN IN PGF!\n";
# Read the template up till something equivalent to \cr
my @template = ();
# Only expand certain things; See TeX book p.238
while (($t = $gullet->readToken) && !Equals($t, T_CS('\cr'))) {
if ($t->equals(T_CS('\tabskip'))) { # Read the tabskip assignment
$gullet->readKeyword('=');
my $value = $gullet->readGlue; } # Discard! In principle, should store in template!
elsif ($t->equals(T_CS('\span'))) { # ex-span-ded next token.
$gullet->unread($gullet->readXToken(0)); }
else {
push(@template, $t); } }
# Convert the template
my $ismath = $STATE->lookupValue('IN_MATH');
my $before = 1; # true if we're before a # in current column
my @pre = ();
my @post = ();
my @cols = ();
my @nonreps = ();
foreach my $t (@template, T_ALIGN) { # put & at end, to save column!
if ($t->equals(T_PARAM)) {
$before = 0; }
elsif ($t->equals(T_ALIGN)) {
if ($before) { @nonreps = @cols; @cols = (); } # A & while we're before a column means Repeated columns
else { # Finished column spec; add it
# Try some magic for math, so we can create a valid math matrix (maybe!)
# DAMN \halign can't be in math, anyway.
# So, to get a matrix, we'll have to rewrite the alignment!
if ($ismath) {
push(@pre, T_MATH); unshift(@post, T_MATH); }
push(@cols, { before => Tokens(stripDupMath(beforeCellUnlist(Tokens(@pre)))),
after => Tokens(stripDupMath(afterCellUnlist(Tokens(@post)))) });
@pre = @post = (); $before = 1; } }
elsif ($before) {
push(@pre, $t) if @pre || !$t->equals(T_SPACE); }
else {
push(@post, $t) if @post || !$t->equals(T_SPACE); } }
my $template = LaTeXML::Core::Alignment::Template->new((@nonreps ?
(columns => [@nonreps], repeated => [@cols])
: (columns => [@cols])));
# print STDERR "Template = ".Stringify(Tokens(@template))."\n => ".$template->show."\n";
# Now read & digest the body.
# Note that the body MUST end with a \cr, and that we've made Special Arrangments
# with \alignment@cr to recognize the end of the \halign
# and sneak a \@finish@alignment in!!!!!
# (otherwise none of the row/column/alignment constructors know when to end, as written)
my $spec = $whatsit->getArg(1);
tikzAlignmentBindings($template, undef,
attributes => {
width => orNull(GetKeyVal($spec, 'to')) });
$stomach->bgroup; # This will be closed by the \halign's closing }
# but tikz is sticking in an extra \begingroup ????
# $stomach->begingroup;
$gullet->unread(T_CS('\@start@alignment'));
$whatsit->setBody($stomach->digestNextBody, undef); # extra undef as dummy "trailer"
if (my $s = GetKeyVal($spec, 'spread')) {
$whatsit->setWidth($whatsit->getBody->getWidth->add($s)); }
return; });
sub tikzAlignmentBindings {
my ($template, $mode) = @_;
$mode = LookupValue('MODE') unless $mode;
# my $ismath = $mode =~ /math$/;
# my $container = ($ismath ? 'ltx:XMArray' : 'ltx:tabular');
# my $rowtype = ($ismath ? 'ltx:XMRow' : 'ltx:tr');
# my $coltype = ($ismath ? 'ltx:XMCell' : 'ltx:td');
my $container = 'svg:g';
my $rowtype = 'svg:g';
my $coltype = 'svg:g';
AssignValue(Alignment => LaTeXML::Core::Alignment->new(
template => $template,
openContainer => \&openTikzAlignment,
closeContainer => sub { $_[0]->closeElement($container); },
openRow => \&openTikzAlignmentRow,
closeRow => sub { $_[0]->closeElement($rowtype); },
openColumn => \&openTikzAlignmentCol,
closeColumn => sub { $_[0]->closeElement($coltype); }));
# These would MASK tikz own new bindings
# BUT it only seems sensible that we'll want to get tikz to call OUR bindings at some level!
Let(T_ALIGN, '\lxSVG@alignment@align');
Let("\\\\", '\lxSVG@alignment@newline');
Let('\tabularnewline', '\lxSVG@alignment@newline');
Let('\cr', '\@alignment@cr');
Let('\crcr', '\@alignment@cr');
Let('\hline', '\@alignment@hline');
# Let(T_MATH, ($ismath ? '\@dollar@in@mathmode' : '\@dollar@in@textmode'));
Let('\@open@row', '\default@open@row');
Let('\@close@row', '\default@close@row');
return; }
sub openTikzAlignment {
my ($doc, %props) = @_;
# If we've already got a foreignObject opened, close it.
if (my $fo = foreignObjectCheck($doc)) {
$doc->removeNode($fo);
my $x;
# SHOULD be wrapped in an svg:switch inside an svg:g; remove them both!
if (($x = $doc->getNode) && ($doc->getNodeQName($x) eq 'svg:switch')) {
$doc->removeNode($x); }
if (($x = $doc->getNode) && ($doc->getNodeQName($x) eq 'svg:g')
&& (($x->getAttribute('class') || '') eq 'ltx_svg_fog')) {
$doc->removeNode($x); } }
# $doc->closeNode($fo); }
return $doc->openElement('svg:g', class => 'ltx_tikzmatrix', %props); }
sub openTikzAlignmentRow {
my ($doc, %props) = @_;
$props{class} = ($props{class} ? $props{class} . ' ' : '') . 'ltx_tikzmatrix_row';
return $doc->openElement('svg:g', class => 'ltx_tikzmatrix', %props); }
sub openTikzAlignmentCol {
my ($doc, %props) = @_;
$props{class} = ($props{class} ? $props{class} . ' ' : '') . 'ltx_tikzmatrix_col';
if (my $align = $props{align}) {
delete $props{align};
$props{class} .= ' ltx_align_' . $align; }
return $doc->openElement('svg:g', class => 'ltx_tikzmatrix', %props); }
# These may need to sneak in tikz' code somewhere?
DefMacroI('\lxSVG@alignment@align', undef,
'\@close@inner@column\@close@column'
# nextcell will end up attached to inner@column@after
# . '\pgfmatrixnextcell'
. '\@alignment@align@marker'
. '\@open@column\@open@inner@column');
# \lxSVG@alignment@newline OptionalMatch:* [Dimension]
DefMacro('\lxSVG@alignment@newline OptionalMatch:* []',
# almost works; except at the END of the \matrix !!!
'\pgfmatrixendrow'
. '\@close@inner@column\@close@column\@close@row'
. '\@alignment@newline@marker'
. '\@open@row\@open@column\@open@inner@column');
# .'\pgfutil@ifnextchar\egroup{}{\@open@row\@open@column\@open@inner@column}');
# .'\pgfutil@ifnextchar\egroup{\begingroup}{\@open@row\@open@column\@open@inner@column}');
#=====================================================================
# Dealing with quick commands
# Coordinates
# Let(T_CS('\pgfqpoint'),T_CS('\pgfpoint'));
# Let(T_CS('\pgfqpointxy'),T_CS('\pgfpointxy'));
# Let(T_CS('\pgfqpointxyz'),T_CS('\pgfpointxyz'));
# Let(T_CS('\pgfqpointscale'),T_CS('\pgfpointscale'));
# Path construction
1;