# -*- mode: Perl -*-
# /=====================================================================\ #
# | xylatexml.tex LaTeXML driver for xy | #
# | Implementation for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use Math::Trig;
# NOTE: since *.tex is read with \input, the .ltxml may be loaded more than once.
no warnings 'redefine';
DebuggableFeature('xy', 'xy processing');
RequirePackage('color');
# Short of setting up font metrics for the special xy fonts
RawTeX(<<'EoTeX');
\xyprovide{latexml}{LaTeXML Driver}{0.8.6}{Bruce Miller}{\url{https://dlmf.nist.gov/LaTeXML/}}{}%
\newdriver{%
\xyaddsupport{latexml}\lx@xy@latexmlon
\xyaddsupport{curve}\lx@xy@curveon
\xyaddsupport{frame}\lx@xy@frameon
\xyaddsupport{tips}\lx@xy@tipson
\xyaddsupport{line}\lx@xy@lineon
\xyaddsupport{rotate}\lx@xy@rotateon
\xyaddsupport{color}\lx@xy@coloron
\xyaddsupport{crayon}\lx@xy@crayonon
\xyaddsupport{matrix}\lx@xy@matrixon
\xyaddsupport{arrow}\lx@xy@arrowon
\xyaddsupport{graph}\lx@xy@graphon
\xyaddsupport{arc}\lx@xy@arcon
\xyaddsupport{knot}\lx@xy@polyon
\xyaddsupport{poly}\lx@xy@knoton
\xyaddsupport{tile}\lx@xy@tileon
\xyaddsupport{web}\lx@xy@webon
}%
\newdimen\xydashl@\xydashl@=5pt\relax%
\newdimen\xydashh@\xydashh@=2.0pt\relax%
\newdimen\xydashw@\xydashw@=0.4pt\relax%
\newdimen\xybsqll@\xybsqll@=3.53554pt\relax%
\newdimen\xybsqlh@\xybsqlh@=1.46448pt\relax%
\newdimen\xybsqlw@\xybsqlw@=0.4pt\relax%
EoTeX
# No need for most of these (Remove them!)
# Hopefully I don't need to do all the enable/disabling our \lx@xy@... macros!
DefMacro('\lx@xy@latexmlon', '\message{LaTeXML enabling LaTeXML xy driver}');
DefMacro('\lx@xy@curveon', '\message{LaTeXML enabling curve}');
DefMacro('\lx@xy@frameon', '\message{LaTeXML enabling frame}');
DefMacro('\lx@xy@tipson', '\message{LaTeXML enabling tips}');
DefMacro('\lx@xy@lineon', '\message{LaTeXML enabling line}');
DefMacro('\lx@xy@rotateon', '\message{LaTeXML enabling rotate}');
DefMacro('\lx@xy@coloron', '\xystandardcolors@\message{LaTeXML enabling color}');
DefMacro('\lx@xy@crayonon', '\installCrayolaColors@\message{LaTeXML enabling crayon}');
DefMacro('\lx@xy@matrixon', '\message{LaTeXML enabling matrix}');
DefMacro('\lx@xy@arrowon', '\message{LaTeXML enabling arrow}');
DefMacro('\lx@xy@graphon', '\message{LaTeXML enabling graph}');
DefMacro('\lx@xy@arcon', '\message{LaTeXML enabling arc}');
DefMacro('\lx@xy@polyon', '\message{LaTeXML enabling knot}');
DefMacro('\lx@xy@knoton', '\message{LaTeXML enabling poly}');
DefMacro('\lx@xy@tileon', '\message{LaTeXML enabling tile}');
DefMacro('\lx@xy@webon', '\message{LaTeXML enabling web}');
# Helper for building SVG paths
sub xy_packpath {
return join(' ', map { (ref $_ ? $_->pxValue : $_); } @_); }
#======================================================================
# line patterns for dashes
# These *should* be adjusting the pattern according to the length of the line
# and whether the line continues a previous line. See xypdf.pdf
# Q: Will we ever need more complex patterns, like dot-dash, etc?
our $xy_linepattern_dashes = '5';
our $xy_linepattern_dots = '1 2';
AssignValue(xy_linepattern => undef);
DefPrimitive('\lx@xy@solidpat', sub { AssignValue(xy_linepattern => undef); });
DefPrimitive('\lx@xy@dashpat', sub { AssignValue(xy_linepattern => $xy_linepattern_dashes); });
DefPrimitive('\lx@xy@dotpat', sub { AssignValue(xy_linepattern => $xy_linepattern_dots); });
# These versions should make an adjustmeht for CLOSED lines (but don't yet)
DefPrimitive('\lx@xy@cldashpat', sub { AssignValue(xy_linepattern => $xy_linepattern_dashes); });
DefPrimitive('\lx@xy@cldotpat', sub { AssignValue(xy_linepattern => $xy_linepattern_dots); });
AssignValue(xy_fill => 0);
AssignValue(xy_stroke => 1);
DefPrimitive('\lx@xy@stroke@on', sub { AssignValue(xy_stroke => 1); });
DefPrimitive('\lx@xy@stroke@off', sub { AssignValue(xy_stroke => 0); });
DefPrimitive('\lx@xy@fill@on', sub { AssignValue(xy_fill => 1); });
DefPrimitive('\lx@xy@fill@off', sub { AssignValue(xy_fill => 0); });
# \xylocalColor@{#2}{#3}
# \xycolor@{#2 #3}
DefMacro('\xycolor@{}', undef); # Which of these is for what?
DefMacro('\xylocalColor@{}{}',
'\def\preStyle@@{\addtostyletoks@{\bgroup\lx@xy@usecolor{#1}{#2}}}'
. '\def\postStyle@@{\addtostyletoks@{\egroup}}'
. '\modXYstyle@');
DefPrimitive('\lx@xy@usecolor{}{}', sub {
my ($stomach, $spec, $model) = @_;
MergeFont(color => ParseColor($model, $spec)); });
# Use for beforeConstruct
sub xy_FillStroke {
my ($document, $whatsit) = @_;
my $font = $whatsit->getProperty('font');
my $color = $font && $font->getColor || Black;
$whatsit->setProperty(stroke => ($whatsit->getProperty('do_stroke') ? $color : 'none'));
$whatsit->setProperty(fill => ($whatsit->getProperty('do_fill') ? $color : 'none'));
# for line extension Read \xylinethick@ ? => stroke-width ???
return; }
sub xy_getOrientation {
my $c = ToString(Expand(T_CS('\cosDirection')));
my $s = ToString(Expand(T_CS('\sinDirection')));
$c =~ s/^\-\+/-/; $c =~ s/^\-\-/+/;
$s =~ s/^\-\+/-/; $s =~ s/^\-\-/+/;
return ($c + 0.0, $s + 0.0); }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# XY Kernel
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Directionals
# xy's K-angle measures the angular position along a square centered at the origin.
# 0 is at the lower-left corner (-270deg), while 4K being upper-right.
# The units are equally spaced along the perimeter
# Given the K-angle, compute cos,sin
sub xy_direction {
my ($kangle) = @_;
my $K = 1024;
$kangle = (ToString($kangle) + 8 * $K) % (8 * $K);
my $q = $kangle / 2 / $K;
my $quad = int($q);
my $delta = 2 * ($q - $quad) - 1;
my $norm = 1 / sqrt(1 + $delta * $delta);
my ($dx, $dy);
if ($quad == 0) { $dx = $delta; $dy = -1; } # right, along bottom of square
elsif ($quad == 1) { $dx = 1; $dy = $delta; } # up, along right side
elsif ($quad == 2) { $dx = -$delta; $dy = 1; } # left, along top
else { $dx = -1; $dy = -$delta; } # down, along left side
return ($dx * $norm, $dy * $norm); }
# Given a \Direction (K-angle),
# xy computes the char in xy font whose line fragment is at that angle.
# It then computes the cos & sin from that char's size.
# We can't compute proper direction w/o font metrics of these goofy fonts,
# but we'll fake it from \Direction.
# We still compute the char codes, in case they're ever used somewhere.
RawTeX(<<'EoTeX');
\def\imposeDirection@i{%
\count@@=\K@ \multiply\count@@ by8 \advance\count@@\Direction
\count@=\count@@ \advance\count@\KK@ \divide\count@64 \advance\count@\m@ne
\loop@\ifnum127<\count@ \advance\count@-128 \repeat@
\chardef\DirectionChar\count@
\advance\count@@16 \divide\count@@\KK@ \advance\count@@\m@ne
\loop@\ifnum127<\count@@ \advance\count@@-128 \repeat@
\chardef\SemiDirectionChar\count@@
% \setbox8=\hbox{\xydashfont\SemiDirectionChar\/}%
% \quotient@@\cosDirection{\sd@X\wd8}\xydashl@
% \setbox8=\hbox{\xydashfont\count@=\SemiDirectionChar\advance\count@-64
% \ifnum\count@<\z@ \advance\count@128 \fi \char\count@\/}%
% \quotient@@\sinDirection{\sd@Y\wd8}\xydashl@
\lx@xy@calculate@direction
}
EoTeX
DefPrimitive('\lx@xy@calculate@direction', sub {
my ($c, $s) = xy_direction(LookupRegister('\Direction')->valueOf);
DefMacroI('\cosDirection', undef, Tokens(Explode(sprintf('%.6f', $c))));
DefMacroI('\sinDirection', undef, Tokens(Explode(sprintf('%.6f', $s))));
return; });
# Since SO much xy code works through \straight@,
# it may make the best sense to work from there,
# rather than redefining so many callers.
# However, \straight@ works primarily by taking an object (eg "line segment")
# ALREADY digested in a box, which it then copies (\copy) it, repeatedly/strategically,
# accumuilating into an \hbox or \vbox.
RawTeX(<<'EoTeX');
\def\straight@typeset{%
%\message{START STRAIGHT}%
\ifInvisible@ \let\next@=\relax
%\message{Invisible}%
% \else \ifdim 1\Direction<-2\K@ \DN@{\straightv@}%
% \else\ifdim 1\Direction<\z@ \DN@{\straighth@}%
% \else\ifdim 1\Direction<2\K@ \DN@{\straightv@}%
% \else \DN@{\straighth@}%
% \fi\fi\fi\fi \checkoverlap@@ \next@}
\else \DN@{\lx@xy@straight@typeset}%
\fi \checkoverlap@@ \next@}
% NOTE: Adapt this from \straighth@, \straightv@ to do both directions!!!
\def\lx@xy@straight@typeset{\setbox\z@=\hbox{%
\setbox8=\copy\lastobjectbox@
\A@=\wd8\relax \B@=\dp8\relax \advance\B@\ht8\relax
\ifdim \A@=\z@ \count@@=\m@ne
\else \dimen@=\sd@X\d@X \divide\dimen@\A@ \count@@=\dimen@ \fi
\Spread@@
\ifdim\d@X>\z@ \advance\X@c-\wd8\relax\fi
\dimen@=-\sd@X\wd8\relax
\multiply\dimen@\K@dYdX \divide\dimen@\K@
\ifdim\d@X>\z@ \advance\Y@c\dimen@ \advance\Y@c-\Leftness@\dimen@
\else \advance\Y@c\Leftness@\dimen@ \fi
\dimen@=\wd8\relax \A@=\sd@X\d@X \advance\A@-\dimen@
\B@=\sd@X\dimen@ \multiply\B@\K@dYdX \divide\B@\K@
\advance\B@-\d@Y \B@=\sd@Y\B@
\count@=\count@@ \advance\count@\m@ne
\ifnum\z@<\count@ \divide\A@\count@ \divide\B@\count@ \fi
\A@=-\sd@X\A@ \B@=\sd@Y\B@ \wd8=\A@
% \message{Box 8 (\the\wd8 x \the\ht8 + \the\dp8)}%
% \showbox8\relax
% \message{PRESUMED stepping \the\A@,\the\B@: \the\count@@ steps}%
% \kern\X@c \count@=\z@
\count@=\z@
\loop@\ifnum\count@<\count@@ \advance\count@\@ne
% \message{SEGMENT}%
% \raise\Y@c\copy8\relax
% Clobbering \X@c !!?!
\lx@xy@move@to{\X@c}{\Y@c}{\copy8}\advance\X@c\A@\relax
\advance\Y@c\B@ \repeat@}%
% \message{END STRAIGHT}%
\ht\z@=\z@ \wd\z@=\z@ \dp\z@=\z@ {\Drop@@}}
EoTeX
# SVG Constructors:
# This postions #3 at #1,#2 in svg;
# it is useful when we can't rely ohn \kern,\hskip,\raise,\lower to position correctly
DefConstructor('\lx@xy@move@to{Dimension}{Dimension}{}',
"^<svg:g transform='translate(#x,#y)'>#3</svg:g>",
properties => sub {
my ($stomach, $x, $y) = @_;
Debug("MOVE " . ToString($x) . ',' . ToString($y)) if $LaTeXML::DEBUG{xy};
(x => $x->pxValue, y => $y->pxValue);
});
#======================================================================
# lines
# Reminder: SVG path lower-case commands are relative to previous position;
# upper-case are absolute (but in user coordinates, of course)
# SVG Constructors:
# Note that (most of) these constructors are equivalents
# to glyphs in the special XY fonts, and thus have
# peculiar sizes & positioning.
# They are often preceded by \kern,\raise,\lower within the xy code.
# Using these results in slower execution and MANY small svg:path elements,
# usually wrapped in a few svg:g's for positioning.
# We need to find a way to either combing these during digestion,
# or after construction.
# Draw a dot for a dotted line.
DefConstructor('\zerodot',
"^<svg:path d='M -2 -1 l 1 1' stroke='#stroke' fill='#fill'/>", # left half a \jot (??)
beforeConstruct => \&xy_FillStroke,
properties => sub {
Debug("DOT") if $LaTeXML::DEBUG{xy};
(do_stroke => LookupValue('xy_stroke'), do_fill => 0,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
); });
# RawTeX(<<'EoTeX');
# \def\Droprule@{\advance\X@p-\X@c
# \message{DROPRULE!!!!}%
# \setboxz@h{\kern\X@c \vrule width\X@p depth-\Y@c height\Y@p}%
# \ht\z@=\z@ \wd\z@=\z@ \dp\z@=\z@ \Drop@@}
# EoTeX
DefMacro('\Droprule@',
'\setboxz@h{\lx@xy@droprule}\advance\X@p-\X@c\Drop@@');
DefConstructor('\lx@xy@droprule',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes' class='droprule'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($Xp, $Yp, $Xc, $Yc) = map { LookupRegister($_); } '\X@p', '\Y@p', '\X@c', '\Y@c';
# my $y = $Yp->subtract($Yc);
my $y = $Yp;
# $Yp = $Yc = Dimension(0);
# Y position is off here???
Debug("DROPRULE FRAGMENT") if $LaTeXML::DEBUG{xy};
# (path => xy_packpath('M', $Xc, $y, 'L', $Xp, $y),
(path => xy_packpath('M', $Xc, $Yc, 'L', $Xp, $Yp),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
# Draw a line segment, \xydashl@ long
DefConstructor('\line@@',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xydashl@');
my $x = $l->multiply($c);
my $y = $l->multiply($s);
Debug("LINE FRAGMENT") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', 0, 0, 'L', $x, $y),
width => $x, height => $y, depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefConstructor('\squiggle@@',
"^<svg:path d='#path' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xybsqll@');
my $r = $l->multiply(0.66);
my $w = $l->multiply($c);
my $h = $l->multiply($s);
Debug("SQUIGGLE FRAGMENT") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', $w->negate, $h->negate, # Yes, cancel the \kern!
'a', $r, $r, 60, 0, 0, $w, $h,
'a', $r, $r, 60, 0, 1, $w, $h),
width => $w->multiply(2), height => $h->multiply(2), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
#======================================================================
# Combining straignt line fragments into a single svg:path
DefMacro('\solidSpread@', '\lx@xy@solidpat\lx@xy@drawline');
DefMacro('\dottedSpread@{}', '\lx@xy@dotpat\lx@xy@drawline');
DefMacro('\dashedSpread@', '\lx@xy@dashpat\lx@xy@drawline');
DefMacro('\squiggledSpread@', '\lx@xy@solidpat\lx@xy@drawsquiggles');
DefMacro('\dashsquiggledSpread@', '\lx@xy@dashpat\lx@xy@drawsquiggles');
# The Until:\repeat@ discards the whole iteration construct within \straight@
DefMacro('\lx@xy@drawline Until:\repeat@', '\lx@xy@drawline@');
DefConstructor('\lx@xy@drawline@',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($x0, $y0, $x1, $y1) = map { LookupRegister($_); } '\X@p', '\Y@p', '\X@c', '\Y@c';
Debug("LINE " . ToString($x0) . ',' . ToString($y0) . ' to ' . ToString($x1) . ',' . ToString($y1)) if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', $x0, $y0, 'L', $x1, $y1),
width => $x1->subtract($x0), height => $y1->subtract($y0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0, dashes => LookupValue('xy_linepattern')); });
# # Maybe a zigzag is close enough to a smooth squiggle?
# # At least for development/debugging.
DefMacro('\lx@xy@drawsquiggles Until:\repeat@', '\lx@xy@drawsquiggles@');
DefConstructor('\lx@xy@drawsquiggles@',
"^?#path(<svg:path d='#path' stroke='#stroke' fill='#fill'/>)()",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($x0, $y0, $x1, $y1) = map { LookupRegister($_); } '\X@p', '\Y@p', '\X@c', '\Y@c';
my $l = LookupRegister('\xybsqll@');
# Note: We perversely use the pattern to control dashes
my $dashed = LookupValue('xy_linepattern');
my $w = $x1->subtract($x0);
my $h = $y1->subtract($y0);
my $r = $l->multiply(0.66);
my $n = int((0.5 + sqrt($w->valueOf * $w->valueOf + $h->valueOf * $h->valueOf)) / $l->valueOf);
$n++ if ($n % 1);
$n += 2 if $dashed && !($n % 4); # make odd
my $dx = $w->divide($n);
my $dy = $h->divide($n);
my @path = ();
my ($x, $y) = ($x0, $y0);
# NOTE: Work out how to use 'A' path steps to make it smooth
my $step = ($dashed ? 4 : 2);
my $xstep = $dx->multiply($step);
my $ystep = $dy->multiply($step);
for (my $i = 0 ; $i < $n ; $i += $step) {
push(@path, 'M', $x, $y) if $dashed || ($i == 0);
push(@path, 'a', $r, $r, 60, 0, 0, $dx, $dy);
push(@path, 'a', $r, $r, 60, 0, 1, $dx, $dy);
$x = $x->add($xstep); $y = $y->add($ystep); }
# Debug("SQUIGGLE $x0, $y0 to $x1, $y1 in $n parts: " . join(' ', @path)) if $LaTeXML::DEBUG{xy};
(path => xy_packpath(@path),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
#======================================================================
# Tips
# Redefinitions:
# These badsically ammount to replacements for the glyphs in xyatipfont, xybtipfont
DefMacro('\atip@@', '\lx@xy@tip{1}'); # upper part of ">" tip
DefMacro('\btip@@', '\lx@xy@tip{-1}'); # lower part of ">" tip
DefMacro('\Tip@@', '\lx@xy@tip{1.5}\lx@xy@tip{-1.5}'); # large tip
DefMacro('\Ttip@@', '\lx@xy@tip{2}\lx@xy@tip{-2}'); # extra large tip
DefMacro('\stopper@@', '\lx@xy@stopper'); # "|" tip
DefMacro('\hook@@', '\lx@xy@hook{0}'); # "(" tip
DefMacro('\ahook@@', '\lx@xy@hook{1}'); # "(" tip shifed up
DefMacro('\bhook@@', '\lx@xy@hook{-1}'); # "(" tip shifted down
DefMacro('\aturn@@', '\lx@xy@turn{1}'); # quarter circle tip up
DefMacro('\bturn@@', '\lx@xy@turn{-1}'); # quarter circle tip down
DefMacro('\solidpoint@', '\pointlike@{\lx@xy@fill@on\lx@xy@point}\jot'); # filled circle
DefMacro('\hollowpoint@', '\pointlike@{\lx@xy@fill@off\lx@xy@point}\jot'); # empty circle
# Redefined to position w/o kerns.
RawTeX(<<'EoTeX');
%\xydef@\Tip@{\kern2.5pt \vrule height2.5pt depth2.5pt width\z@
% \Tip@@ \kern2.5pt \egroup
\def\Tip@{%
\Tip@@ \egroup
\U@c=2.5pt \D@c=2.5pt \L@c=2.5pt \R@c=2.5pt \Edge@c={\circleEdge}%
\def\Leftness@{.5}\def\Upness@{.5}%
\def\Drop@@{\styledboxz@}\def\Connect@@{\straight@{\dottedSpread@\jot}}}
%\xydef@\Ttip@{\kern3.2pt \vrule height3.2pt depth3.2pt width\z@
% \Ttip@@ \kern3.2pt \egroup
\def\Ttip@{%
\Ttip@@ \egroup
\U@c=3.2pt \D@c=3.2pt \L@c=3.2pt \R@c=3.2pt \Edge@c={\circleEdge}%
\def\Leftness@{.5}\def\Upness@{.5}%
\def\Drop@@{\styledboxz@}\def\Connect@@{\straight@{\dottedSpread@\jot}}}
EoTeX
# SVG Constructors:
# The styles for the tips extension (from extra sets of tip fonts)
# * cm are same? shorter, wider than xy
# * eu are same? shorter, wider than xy (maybe thinner?)
# * lu are same? shorter, narrower than xy.
# The styles seem only to affect the basic >,< tips
our %xy_tips_factors = (
xy => [1, 1], cm => [0.5, 1.7], eu => [0.5, 1.5], lu => [0.5, 0.5]);
AssignValue(xy_tips_style => 'xy');
# Draw HALF an arrow tip, stretched according to #1; upper/lower depending on sign
DefConstructor('\lx@xy@tip{}',
"^<svg:path d='#path' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $stretch) = @_;
$stretch = ToString($stretch);
my $style = LookupValue('xy_tips_style') || 'xy';
my $factors = $xy_tips_factors{$style} || $xy_tips_factors{xy};
my ($lf, $wf) = @$factors;
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xydashl@')->multiply($lf);
my $w = LookupRegister('\xydashh@')->multiply($wf * ($stretch || 1));
my $r = $l->multiply(2);
my $dx = $l->negate->multiply($c)->subtract($w->multiply($s));
my $dy = $l->negate->multiply($s)->add($w->multiply($c));
Debug("TIP") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', 0, 0, 'A', $r, $r, 45, 0, ($stretch < 0 ? 1 : 0), $dx, $dy),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefConstructor('\lx@xy@stopper',
"^<svg:path d='#path' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $w) = @_;
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xydashl@');
my $dx = $l->multiply($s)->negate;
my $dy = $l->multiply($c);
Debug("STOPPER") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', $dx, $dy, 'L', $dx->negate, $dy->negate),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefConstructor('\lx@xy@hook{Number}',
"^<svg:path d='#path' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $offset) = @_;
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xybsqll@');
$offset = $offset->valueOf;
my $x0 = ($offset ? Dimension(0) : $l);
my $y0 = $l->multiply($offset + 1);
my $y1 = $y0->subtract($l->multiply(2));
Debug("HOOK") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', $x0->multiply($c)->subtract($y0->multiply($s)),
$x0->multiply($s)->add($y0->multiply($c)),
'A', $l, $l, 180, 0, 1,
$x0->multiply($c)->subtract($y1->multiply($s)),
$x0->multiply($s)->add($y1->multiply($c))),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefConstructor('\lx@xy@turn{Number}',
"^<svg:path d='#path' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $offset) = @_;
my ($c, $s) = xy_getOrientation();
my $l = LookupRegister('\xybsqll@');
$offset = $offset->valueOf;
Debug("TURN") if $LaTeXML::DEBUG{xy};
(path => xy_packpath('M', 0, 0,
'A', $l, $l, 90, 0, ($offset > 0 ? 0 : 1),
$l->multiply(-($c + $offset * $s)),
$l->multiply($offset * $c - $s)),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefConstructor('\lx@xy@point',
"^<svg:circle cx='#x' cy='#y' r='#radius' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my $l = LookupRegister('\xybsqll@');
Debug("POINT") if $LaTeXML::DEBUG{xy};
(radius => $l->multiply(0.5)->pxValue,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill')); });
#======================================================================
# circles
Let('\lx@xy@CIRfull@orig', '\CIRfull@');
Let('\lx@xy@CIRcw@orig', '\CIRcw@');
Let('\lx@xy@CIRacw@orig', '\CIRacw@');
DefMacro('\CIRfull@', '\lx@xy@circdir{}\lx@xy@CIRfull@orig');
DefMacro('\CIRcw@', '\lx@xy@circdir{-1}\lx@xy@CIRcw@orig');
DefMacro('\CIRacw@', '\lx@xy@circdir{+1}\lx@xy@CIRacw@orig');
DefPrimitive('\lx@xy@circdir{}', sub { AssignValue(xy_circle_dir => ToString($_[1])); });
# \count@@,\count@
# \CIRin@@,\CIRout@@
# \CIRorient@@ one of \CIRfull@, \empty, \CIRacw@, \CIRcw@
# (which set \CIRtest@@ to test inclusion of arc fragments)
DefConstructor('\cirbuild@',
"^ ?#full(<svg:circle cx='#x' cy='#y' r='#r' stroke='#stroke' fill='#fill'/>)"
. "(<svg:path d='#path' stroke='#stroke' fill='#fill'/>)",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my $r = LookupRegister('\R@');
my $d = $r->multiply(2);
my $xc = $r;
my $yc = Dimension(0);
my $cd = LookupValue('xy_circle_dir'); # 0 full; +1 ccw; -1 cw;
my $path;
if (!$cd) { # Full circle
Debug("CIRCLE full") if $LaTeXML::DEBUG{xy};
}
else {
my ($d1, $d2) = map { LookupRegister($_)->valueOf; } '\count@@', '\count@';
my ($a1, $a2);
if ($cd > 0) { # ccw, angles go positive
if ($d1 < $d2) { $a1 = ($d1 - 4) * 45; $a2 = ($d2 - 4) * 45; }
else { $a1 = ($d1 - 4) * 45; $a2 = ($d2 - 4 + 8) * 45; } }
else { # cw, angles go negative
if ($d1 < $d2) { $a1 = ($d2 - 4) * 45; $a2 = ($d1 - 4 + 8) * 45 }
else { $a1 = ($d2 - 4) * 45; $a2 = ($d1 - 4) * 45; } }
my $theta1 = $a1 * pi / 180;
my $theta2 = $a2 * pi / 180;
my $x0 = $xc->add($r->multiply(cos($theta1)));
my $y0 = $yc->add($r->multiply(sin($theta1)));
my $x1 = $xc->add($r->multiply(cos($theta2)));
my $y1 = $yc->add($r->multiply(sin($theta2)));
Debug("CIRCLE " . ($cd > 0 ? 'ccw' : 'cw') . ", d1=$d1,d2=$d2 => $a1 to $a2") if $LaTeXML::DEBUG{xy};
my $a = $a2 - $a1;
$path = xy_packpath('M', $x1, $y1, 'A', $r, $r, $a, ($a > 180 ? 1 : 0), 0, $x0, $y0); }
(full => !$cd, path => $path,
x => $xc->pxValue, y => $yc->pxValue, r => $r->pxValue,
width => $d, depth => $r, height => $r,
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Extensions
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# curve: Curve and Spline extension
# pdf driver redefines
# \splinesolid@, \splinedashed@, \splinedotted@
# in terms of \xP@spline (followed by line pattern cmd)
# \@crv@, \xysplinespecialcases@
# \splinedoubled@, \splinetrebled@, \splinedbldotted@
# See \crvconnect@ ????
# There are a bunch of \spline(solid|doubled|trebled|ribboned|dashed|dotted)@
# along with a generic(?) \splineset@@ which seems to expect to work with
# \splinebox@,\splinetol@,\xycrvdrop,\xycrvconn@ ?
# (will be eg. the expansion of \dir{--})
# This code is a hybrid of stock xy & pdf driver
# (1) Avoid the special case treatment, we'll attenpt to parse the crop/connection
DefMacro('\xysplinespecialcases@', '\splineset@@');
DefMacro('\splineset@@', <<'EoTeX');
\message{********************************^^J}%
\readsplineparams@
%\message{What are \the\dimen5, \the\dimen7?}%
\ifdim\dimen5<\dimen7
\ifx\splineinfo@\squineinfo@
% Turn the quadratic into cubic
\L@c\dimexpr(\X@p+2\A@)/3\relax
\U@c\dimexpr(\Y@p+2\B@)/3\relax
\R@c\dimexpr(\X@c+2\A@)/3\relax
\D@c\dimexpr(\Y@c+2\B@)/3\relax
\fi
\lx@xy@shavespline
%%% Wrong test for no curve!
%%\ifdim\X@p=\X@c\else
%%\ifdim\Y@p=\Y@c\else
\lx@xy@crv@decipher
\lx@xy@spline@
%\fi\fi
\fi
EoTeX
# Apparently \xycrvdrop@ can be empty (for lines) else something to drop at each point
# like maybe: =<8pt>{.} for spaced points?
# Apparently \xycrvconn@ can be empty (solid line) else indicates doubled,trebled, dotted,dashed
# like maybe: !/-5pt/\dir{>} or !C\dir{:}
# This seems wrong to try to match & reinterpret match the drop/connection commands,
# but xy itself does it from time to time!
# But we need to recognize more patterns!
DefPrimitive('\lx@xy@crv@decipher', sub {
my ($stomach) = @_;
my $drop = ToString(LookupDefinition(T_CS('\xycrvdrop@'))->getExpansion);
my $conn = ToString(LookupDefinition(T_CS('\xycrvconn@'))->getExpansion);
$drop =~ s/^\s+//; $drop =~ s/\s+$//;
$conn =~ s/^\s+//; $conn =~ s/\s+$//;
if ($drop) {
if ($drop =~ /^\s*=\<([^\>]*)\>\{([^\}]*)\}/) { # Dotted, with given spacing (ignore spacing for now)
my ($s, $d) = ($1, $2);
if ($s =~ /^[\+\-\d\.]*\w+$/) {
my $pattern = '1 ' . int(Dimension($s)->pxValue);
AssignValue(xy_linepattern => $pattern); } }
elsif ($drop eq '{\zerodot}') {
AssignValue(xy_linepattern => $xy_linepattern_dots); }
else {
Info('xy', 'ignored', $stomach, "LaTeXML ignoring curve drop: '$drop'"); } }
if ($conn) {
$conn =~ s/^!\w//;
if ($conn =~ s/^\\dir(\d?)\{([^\}]*)\}//) {
my ($n, $t) = ($1, $2);
if ($t eq ':') { $n = 2; $t = '.'; }
if ($t eq '=') { $n = 2; $t = '-'; }
if ($n) {
AssignValue(xy_multiplicity => $n); }
if ($t eq '-') { }
elsif ($t eq '--') {
AssignValue(xy_linepattern => $xy_linepattern_dashes); }
elsif ($t eq '.') {
AssignValue(xy_linepattern => $xy_linepattern_dots); } }
else {
Info('xy', 'ignored', $stomach, "LaTeXML ignoring curve connection: $conn") if $conn; } }
return; });
sub xy_linediff {
my ($sep, $x0, $y0, $x1, $y1) = @_;
my $diffx = $x1->valueOf - $x0->valueOf;
my $diffy = $y1->valueOf - $y0->valueOf;
my $length = sqrt($diffx * $diffx + $diffy * $diffy);
my $dx = $sep->multiply($diffy / $length);
my $dy = $sep->multiply($diffx / $length);
return ($dx, $dy); }
# For doubled/trebled curves (whether solid, dotted or dashed)
# we should be able to draw 2 or 3 offset curves,
# but we need to adjust the end and control points.
# Hmmm.....
# Currently just a simplistic guess.
DefConstructor('\lx@xy@spline@',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($x0, $y0, $x1, $y1, $L, $U, $R, $D, $A, $B) = map { LookupRegister($_); }
'\X@p', '\Y@p', '\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c', '\A@', '\B@';
Debug("SPLINE") if $LaTeXML::DEBUG{xy};
my $mult = LookupValue('xy_multiplicity') || 1;
my @path = ();
if ($mult % 2 == 1) {
push(@path, 'M', $x0, $y0, 'C', $L, $U, $R, $D, $x1, $y1); }
if ($mult > 1) {
my $sep = LookupRegister('\xydashh@');
$sep = $sep->multiply(0.5) if $mult == 2;
my ($dx0, $dy0) = xy_linediff($sep, $x0, $y0, $L, $U);
my ($dx1, $dy1) = xy_linediff($sep, $L, $U, $R, $D);
my ($dx2, $dy2) = xy_linediff($sep, $R, $D, $x1, $y1);
my $dx1a = $dx0->add($dx1)->multiply(0.5);
my $dy1a = $dy0->add($dy1)->multiply(0.5);
my $dx1b = $dx1->add($dx2)->multiply(0.5);
my $dy1b = $dy1->add($dy2)->multiply(0.5);
push(@path, 'M', $x0->subtract($dy0), $y0->add($dx0),
# 'C', $L, $U, $R, $D,
'C', $L->subtract($dx1a), $U->subtract($dy1a), $R->subtract($dx1b), $D->subtract($dy1b),
# 'C', $L->subtract($dy1a), $U->subtract($dx1a), $R->subtract($dy1b), $D->subtract($dx1b),
$x1->add($dy2), $y1->subtract($dx2));
push(@path, 'M', $x0->add($dy0), $y0->subtract($dx0),
# 'C', $L, $U, $R, $D,
'C', $L->add($dx1a), $U->add($dy1a), $R->add($dx1b), $D->add($dy1b),
# 'C', $L->add($dy1a), $U->add($dx1a), $R->add($dy1b), $D->add($dx1b),
$x1->subtract($dy2), $y1->add($dx2)); }
(path => xy_packpath(@path),
# width => $x, height => $y, depth => Dimension(0),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
dashes => LookupValue('xy_linepattern'),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
DefPrimitive('\lx@xy@shavespline', sub {
my $pt = Dimension('1pt')->valueOf;
my $a = LookupRegister('\dimen', 5)->valueOf / $pt;
my $b = LookupRegister('\dimen', 7)->valueOf / $pt;
if (($a != 0) || ($b != 0)) {
my ($Xp, $Yp, $Lp, $Up, $Rp, $Dp) = map { LookupRegister($_)->valueOf; }
'\X@p', '\Y@p', '\L@p', '\U@p', '\R@p', '\D@p';
my ($Xc, $Yc, $Lc, $Uc, $Rc, $Dc) = map { LookupRegister($_)->valueOf; }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
my $xu = $Lc - $Xp;
my $xv = $Rc - $xu - $Lc;
my $xw = $Xc - 3 * $Rc + 3 * $Lc - $Xp;
my $yu = $Uc - $Yp;
my $yv = $Dc - $yu - $Uc;
my $yw = $Yc - 3 * $Dc + 3 * $Uc - $Yp;
my $aab = (2 * $a + $b);
my $abb = ($a + 2 * $b);
AssignRegister('\X@p' => Dimension($Xp + $a * (3 * $xu + (3 * $xv + $xw * $a) * $a)));
AssignRegister('\Y@p' => Dimension($Yp + $a * (3 * $yu + (3 * $yv + $yw * $a) * $a)));
AssignRegister('\L@c' => Dimension($Xp + $aab * $xu + $a * ($abb * $xv + $xw * $a * $b)));
AssignRegister('\U@c' => Dimension($Yp + $aab * $yu + $a * ($abb * $yv + $yw * $a * $b)));
AssignRegister('\R@c' => Dimension($Xp + $abb * $xu + $b * ($aab * $xv + $xw * $b * $a)));
AssignRegister('\D@c' => Dimension($Yp + $abb * $yu + $b * ($aab * $yv + $yw * $b * $a)));
AssignRegister('\X@c' => Dimension($Xp + $b * (3 * $xu + (3 * $xv + $xw * $b) * $b)));
AssignRegister('\Y@c' => Dimension($Yp + $b * (3 * $yu + (3 * $yv + $yw * $b) * $b)));
} });
DefMacro('\buildcircle@', '\lx@xy@crv@decipher\lx@xy@buildcircle@');
DefConstructor('\lx@xy@buildcircle@',
"^<svg:ellipse cx='#x' cy='#y' rx='#rx' ry='#ry' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my $rx = LookupRegister('\R@');
my $ry = LookupRegister('\L@');
my $w = $rx->multiply(2);
my $h = $ry;
my $d = $ry;
my $xc = $rx;
## my $yc = $ry;
my $yc = $ry->add(LookupRegister('\xydashl@')); # Apparent Weird offset???
Debug("ELLIPSE " . ToString($rx) . ' x ' . ToString($ry) . ' @ ' . ToString($xc) . ',' . ToString($yc))
if $LaTeXML::DEBUG{xy};
(x => $xc->pxValue, y => $yc->pxValue,
rx => $rx->pxValue, ry => $ry->pxValue,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
dashes => LookupValue('xy_linepattern'),
do_stroke => LookupValue('xy_stroke'), do_fill => 0); });
#======================================================================
# frame: Frame and Bracket extension
# ToDo:
# frames as object modifiers
# Curiously, the normal \frmDrop@ doesn't involve \styledboxz@, so we don't get colors?
RawTeX(<<'EoTeX');
\def\frmDrop@#1{%
\ifx\frmradius@@\z@ \addtoDrop@@{\let\frmradius@@=\z@}%
\else \expandafter\addtoDrop@@\expandafter{%
\expandafter\def\expandafter\frmradius@@\expandafter{\frmradius@@}}\fi
\addtoDrop@@{\setboxz@h{#1}\styledboxz@}}
EoTeX
# Shadowed & filled in terms of \framed@@
DefMacro('\frame@fill@@{}', '\lx@xy@fill@on\lx@xy@stroke@off\framed@@{#1}');
DefMacro('\frame@emph@@{}', '\lx@xy@fill@on\lx@xy@stroke@on\lx@xy@solidpat\framed@@{#1}');
# Rewrapped to use dots or dashes
# (the way xy implements dots/dashes is quite opaque)
DefMacro('\csname frm{.}\endcsname', '\addtoDrop@@{\lx@xy@dotpat}\csname frm{-}\endcsname');
DefMacro('\csname frm{o-}\endcsname', '\addtoDrop@@{\lx@xy@dashpat}\csname frm{-}\endcsname');
DefMacro('\csname frm{--}\endcsname', '\addtoDrop@@{\lx@xy@dashpat}\csname frm{-}\endcsname');
DefMacro('\csname frm{.o}\endcsname', '\addtoDrop@@{\lx@xy@dotpat}\csname frm{o}\endcsname');
DefMacro('\csname frm{-o}\endcsname', '\addtoDrop@@{\lx@xy@dashpat}\csname frm{o}\endcsname');
DefMacro('\csname frm{.e}\endcsname', '\addtoDrop@@{\lx@xy@dotpat}\csname frm{e}\endcsname');
DefMacro('\csname frm{-e}\endcsname', '\addtoDrop@@{\lx@xy@dashpat}\csname frm{e}\endcsname');
# Rectangular frame (including rounded corners)
DefConstructor('\framed@@{Dimension}',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $r) = @_;
my ($X, $Y, $L, $U, $R, $D) = map { LookupRegister($_); }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
my $w = $L->add($R);
my $h = $U->add($D);
my $x0 = $X->subtract($L);
my $y0 = $Y->subtract($D);
my $x1 = $x0->add($w);
my $y1 = $y0->add($h);
$r = $r->smaller($w->multiply(0.5))->smaller($h->multiply(0.5));
my $path;
if ($r->valueOf <= 0) {
$path = xy_packpath('M', $x0, $y0, 'L', $x1, $y0, 'L', $x1, $y1, 'L', $x0, $y1, 'Z'); }
else {
my $wm = $w->subtract($r)->valueOf;
my $hm = $h->subtract($r)->valueOf;
$path = xy_packpath(
'M', $x0->add($r), $y0,
'A', $r, $r, 90, 0, 0, $x0, $y0->add($r),
($hm > 0 ? ('L', $x0, $y1->subtract($r)) : ()),
'A', $r, $r, 90, 0, 0, $x0->add($r), $y1,
($wm > 0 ? ('L', $x1->subtract($r), $y1) : ()),
'A', $r, $r, 90, 0, 0, $x1, $y1->subtract($r),
($hm > 0 ? ('L', $x1, $y0->add($r)) : ()),
'A', $r, $r, 90, 0, 0, $x1->subtract($r), $y0,
'Z'); }
Debug("FRAMED") if $LaTeXML::DEBUG{xy};
(path => $path,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
dashes => LookupValue('xy_linepattern'),
); });
DefConstructor('\circled@ {Dimension}',
"^<svg:circle cx='#x' cy='#y' r='#radius' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $r) = @_;
my ($X, $Y, $L, $U, $R, $D) = map { LookupRegister($_); }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
my $w = $L->add($R);
my $h = $U->add($D);
if (!$r->valueOf) {
$r = $w->larger($h)->multiply(0.5); }
my $x0 = $X;
my $y0 = $Y;
Debug("CIRCLED " . ToString($r) . ' at ' . ToString($x0) . ',' . ToString($y0)) if $LaTeXML::DEBUG{xy};
(x => $x0->pxValue, y => $y0->pxValue, radius => $r->pxValue,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
dashes => LookupValue('xy_linepattern'),
); });
DefConstructor('\ellipsed@ {Dimension}{Dimension}',
"^<svg:ellipse cx='#x' cy='#y' rx='#rx' ry='#ry' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $rx, $ry) = @_;
my ($X, $Y, $L, $U, $R, $D) = map { LookupRegister($_); }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
my $w = $L->add($R);
my $h = $U->add($D);
my $x0 = $X;
my $y0 = $Y;
Debug("ELLIPSED " . ToString($rx) . ' x ' . ToString($ry)) if $LaTeXML::DEBUG{xy};
(x => $x0->pxValue, y => $y0->pxValue,
rx => $rx->pxValue, ry => $ry->pxValue,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
dashes => LookupValue('xy_linepattern'),
); });
DefConstructor('\shaded@@{Dimension}',
"^<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $r) = @_;
my ($X, $Y, $L, $U, $R, $D) = map { LookupRegister($_); }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
my $w = $L->add($R);
my $h = $U->add($D);
my $x0 = $X->subtract($L)->add($r);
my $y0 = $Y->subtract($D);
my $x1 = $x0->add($w);
my $y1 = $y0->add($h);
$r = $r->smaller($w->multiply(0.5))->smaller($h->multiply(0.5));
my $path = xy_packpath('M', $x0, $y0, 'L', $x1, $y0, 'L', $x1, $y1);
Debug("SHADED") if $LaTeXML::DEBUG{xy};
(path => $path,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
); });
DefMacro('\lbraced', '\lx@xy@bracketed{\{}{L}');
DefMacro('\rbraced', '\lx@xy@bracketed{\}}{R}');
DefMacro('\ubraced', '\lx@xy@bracketed{\{}{U}');
DefMacro('\dbraced', '\lx@xy@bracketed{\{}{D}');
DefMacro('\lparenthesized', '\lx@xy@bracketed{(}{L}');
DefMacro('\rparenthesized', '\lx@xy@bracketed{)}{R}');
DefMacro('\uparenthesized', '\lx@xy@bracketed{(}{U}');
DefMacro('\dparenthesized', '\lx@xy@bracketed{(}{D}');
DefConstructor('\lx@xy@bracketed{}{}',
"^<svg:text transform='translate(#x,#y) rotate(#angle) scale(#xscale,#yscale)' stroke='#stroke'>#1</svg:text>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $char, $orientation) = @_;
my ($X, $Y, $L, $U, $R, $D) = map { LookupRegister($_); }
'\X@c', '\Y@c', '\L@c', '\U@c', '\R@c', '\D@c';
$orientation = uc(ToString($orientation));
my ($w0, $h0, $d0) = $char->getSize;
my $ht0 = $h0->add($d0);
my $w = $L->add($R);
my $h = $U->add($D);
my $x = 0;
my $y = 0;
my $xscale = 1;
my $yscale = 1;
my $angle = 0;
if ($orientation eq 'L') {
$x = $w0->negate->pxValue;
$yscale = $h->valueOf / $ht0->valueOf; }
elsif ($orientation eq 'R') {
$x = $w->pxValue;
$yscale = $h->valueOf / $ht0->valueOf; }
elsif ($orientation eq 'U') {
$x = $w->add($ht0)->multiply(0.5)->pxValue;
$y = $h->add($w0)->pxValue;
$angle = -90; $yscale = $w->valueOf / $ht0->valueOf; }
elsif ($orientation eq 'D') {
$x = $w->subtract($ht0)->multiply(0.5)->pxValue;
$y = $h->negate->subtract($w0)->pxValue;
$angle = 90; $yscale = $w->valueOf / $ht0->valueOf; }
Debug("BRACKETED " . ToString($char)) if $LaTeXML::DEBUG{xy};
(x => $x, y => $y, angle => $angle, xscale => $xscale, yscale => $yscale,
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
); });
DefConstructor('\blacked@@',
"^<svg:rect x='#x' y='#y' width='#w' height='#h' stroke='#stroke' fill='#fill'/>",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($w, $h, $b) = map { LookupRegister($_); } '\dimen@', '\dimen@ii', '\B@';
my $d = $b->negate;
my $y = $b;
my $ht = $h->add($d);
Debug("BLACKED") if $LaTeXML::DEBUG{xy};
(x => 0, y => $y->pxValue, w => $w->pxValue, h => $ht->pxValue,
width => $w, height => $h, depth => $d,
do_stroke => LookupValue('xy_stroke'), do_fill => 1); });
#======================================================================
# cmtip, tips extensions
# The size seems to be ignored (in dvips driver)
AssignValue(xy_tips_pending_style => 'xy');
DefPrimitive('\SelectTips{}{}', sub {
my $style = ToString($_[1]);
if ($style && $xy_tips_factors{$style}) { # Known style?
AssignValue(xy_tips_pending_style => $style);
AssignValue(xy_tips_style => $style); } });
DefPrimitive('\UseTips', sub {
AssignValue(xy_tips_style => LookupValue('xy_tips_pending_style') || 'xy'); });
DefPrimitive('\NoTips', sub {
AssignValue(xy_tips_style => 'xy'); });
#======================================================================
# line: Line styles extension
# Question: Should line thickness only apply to poly lines?
# Use our defn, NOT the stub one.
Let('\xy@polystyle@@', '\xy@polystyle@');
Let('\xylinewidth@@', '\xylinewidth@');
DefMacro('\xypolyline@Special', '\lx@xy@stroke@on\lx@xy@fill@off\lx@xy@poly');
DefMacro('\xypolyfill@Special', '\lx@xy@stroke@off\lx@xy@fill@on\lx@xy@poly');
DefMacro('\xypolyeofill@Special', '\lx@xy@stroke@off\lx@xy@fill@on\lx@xy@poly');
DefMacro('\xypolydot@Special', '\lx@xy@stroke@on\lx@xy@dotpat\lx@xy@fill@off\lx@xy@poly');
DefMacro('\xypolydash@Special', '\lx@xy@stroke@on\lx@xy@dashpat\lx@xy@fill@off\lx@xy@poly');
our @xy_cap_codes = (qw(butt round square));
our @xy_join_codes = (qw(miter round bevel));
DefConstructor('\lx@xy@poly{}',
"^?#path(<svg:path d='#path' stroke='#stroke' fill='#fill' stroke-dasharray='#dashes'"
. " stroke-width='#thickness' stroke-linecap='#cap' stroke-linejoin='#join' stroke-miterlimit='#miter'/>)()",
beforeConstruct => \&xy_FillStroke,
properties => sub {
my ($stomach, $points) = @_;
# $style ???
# NOTE: these are in pts, we want pixels
my $pt = Dimension('1pt')->pxValue;
my @points = map { roundto($_ * $pt); } split(/\s+/, ToString($points));
my @path = ('M', shift(@points), shift(@points));
while (@points) {
push(@path, 'L', shift(@points), shift(@points)); }
my $th = LookupRegister('\xylinethick@');
my ($cap, $join, $miter) = map { ToString(Digest($_)); } '\xylinecap@', '\xylinejoin@', '\xylinemiter@';
Debug("POLYLINE; Thickness " . ToString($th)) if $LaTeXML::DEBUG{xy};
(path => xy_packpath(@path),
width => Dimension(0), height => Dimension(0), depth => Dimension(0),
do_stroke => LookupValue('xy_stroke'), do_fill => LookupValue('xy_fill'),
dashes => LookupValue('xy_linepattern'),
thickness => $th->pxValue, cap => $xy_cap_codes[$cap], join => $xy_join_codes[$join], miter => $miter,
); });
#======================================================================
# rotate: Rotate and Scale extension
# Edges don't seem to have been adjusted (tho' xy should have?)
DefConstructor('\xyscale@@{}{}',
"^<svg:g transform='#transform'> #box</svg:g>",
properties => sub {
my ($stomach, $xscale, $yscale) = @_;
my ($Xp, $Yp, $Xc, $Yc, $Lc, $Up, $Rp) = map { LookupRegister($_); }
'\X@p', '\Y@p', '\X@c', '\Y@c', '\L@c', '\U@p', '\R@p';
my $box = LookupValue('box0');
AssignValue(box0 => undef);
my $transform =
'translate(' . $Lc->subtract($Rp)->pxValue . ',' . $Up->negate->pxValue . ')'
. ' scale(' . ToString($xscale) . ',' . ToString($yscale) . ')'
. ' translate(' . $Rp->subtract($Lc)->pxValue . ',' . $Up->pxValue . ')';
Debug("SCALE by $transform") if $LaTeXML::DEBUG{xy};
return (transform => $transform, box => $box); });
DefConstructor('\xyRotate@@{}',
"^<svg:g transform='rotate(#angle)'>#box</svg:g>",
properties => sub {
my ($stomach, $kangle) = @_;
my $box = LookupValue('box0');
AssignValue(box0 => undef);
# BAD approximation!
my $angle = ToString($kangle) * 45 / 1024 - 135;
Debug("ROTATE by $angle") if $LaTeXML::DEBUG{xy};
return (angle => $angle, box => $box); });
DefConstructor('\xyRotate@@{}',
"^<svg:g transform='#transform'>#box</svg:g>",
properties => sub {
my ($stomach, $kangle) = @_;
my ($Xp, $Yp, $Xc, $Yc, $Lc, $Up, $Rp) = map { LookupRegister($_); }
'\X@p', '\Y@p', '\X@c', '\Y@c', '\L@c', '\U@p', '\R@p';
my $box = LookupValue('box0');
AssignValue(box0 => undef);
my ($c, $s) = xy_direction($kangle);
my $angle = int(atan2($s, $c) * 180 / pi);
my $transform =
'translate(' . $Lc->subtract($Rp)->pxValue . ',' . $Up->negate->pxValue . ')'
. ' rotate(' . $angle . ')'
. ' translate(' . $Rp->subtract($Lc)->pxValue . ',' . $Up->pxValue . ')';
Debug("ROTATE by $transform") if $LaTeXML::DEBUG{xy};
return (transform => $transform, box => $box); });
# Align with current direction? But what is the argument?
DefConstructor('\doSpecialRotate@@ Until:@@',
"^<svg:g transform='#transform'>#box</svg:g>",
properties => sub {
my ($stomach, $arg) = @_;
my ($Xp, $Yp, $Xc, $Yc, $Lc, $Up, $Rp) = map { LookupRegister($_); }
'\X@p', '\Y@p', '\X@c', '\Y@c', '\L@c', '\U@p', '\R@p';
my $box = LookupValue('box0');
AssignValue(box0 => undef);
my ($c, $s) = xy_getOrientation();
my $angle = atan2($s, $c) * 180 / pi;
my $transform =
'translate(' . $Lc->subtract($Rp)->pxValue . ',' . $Up->negate->pxValue . ')'
. ' rotate(' . $angle . ')'
. ' translate(' . $Rp->subtract($Lc)->pxValue . ',' . $Up->pxValue . ')';
Debug("ROTATE by $transform SPECIAL for " . ToString($arg)) if $LaTeXML::DEBUG{xy};
return (transform => $transform, box => $box); });
#======================================================================
# Matrix extension
# Note that xy is rather (too) clever :>
# It typesets a matrix using \halign (obviously)
# then takes it apart using low level \unskip,\lastbox, magic
# in order to measure the rows & columns.
# We haven't emulated \lastbox so well
# Extract the needed column widths & row heights from our own Alignment calculations
Let('\lx@xy@prentry@@norm@save', '\prentry@@norm');
DefMacro('\prentry@@norm', '\lx@xy@prentry@@norm@save\lx@xy@notealignment');
# Record the Alignment object being built by an \xymatrix
# (since it will havev become unbound by the time we need it)
# [Global is OK(?) since nested xymatrix supposedly doesn't work?]
DefPrimitive('\lx@xy@notealignment', sub {
my $alignment = LookupValue('Alignment');
# do NOT prune, else rows & column sizes are off.
$alignment->setProperty(preserve_structure => 1) if $alignment;
AssignValue('xymatrix_alignment' => $alignment, 'global'); });
DefPrimitive('\xymatrix@measureit', sub {
if (my $alignment = LookupValue('xymatrix_alignment')) {
$alignment->normalizeAlignment;
my $i = 1;
my $rmx = Dimension(0);
foreach my $r (@{ $$alignment{rowheights} }) {
$rmx = $rmx->larger($r);
DefMacroI(T_CS('\Hrow@' . $i++), undef, Tokens($r->revert)); }
DefMacroI(T_CS('\H@max'), undef, Tokens($rmx->revert));
# Add fake last row, since even preserve_structure may remove empty last row!
DefMacroI(T_CS('\Hrow@' . $i), undef, Tokens(Dimension(0)->revert));
my $j = 1;
my $cmx = Dimension(0);
foreach my $c (@{ $$alignment{columnwidths} }) {
$cmx = $cmx->larger($c);
DefMacroI(T_CS('\Wcol@' . $j++), undef, Tokens($c->revert)); }
DefMacroI(T_CS('\W@max'), undef, Tokens($cmx->revert));
DefMacroI(T_CS('\HW@max'), undef, Tokens($rmx->larger($cmx)->revert));
AssignRegister('\Col' => Number(0), 'global');
AssignRegister('\Row' => Number(0), 'global');
AssignRegister('\count@@' => Number(0), 'global');
}
return; });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1;