# -*- mode: Perl -*-
# /=====================================================================\ #
# | graphics | #
# | 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 LaTeXML::Util::Pathname;
use LaTeXML::Util::Image;
#======================================================================
# (See LaTeXML::Post::Graphics for suggested postprocessing)
# Package options: draft, final, hiderotate, hidescale, hiresbb
DefParameterType('GraphixDimension', sub {
my ($gullet) = @_;
if ($gullet->ifNext(T_OTHER('!'))) {
$gullet->readToken();
undef; } # essentially: let other dimensions determine size.
else {
$gullet->readDimension; } },
optional => 1);
DefConstructor('\scalebox{}[] Digested',
"<ltx:inline-block angle='#angle' width='#width' height='#height' depth='#depth'"
. " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'"
. " xscale='#xscale' yscale='#yscale'"
. " xtranslate='#xtranslate' ytranslate='#ytranslate'>"
. "#3"
. "</ltx:inline-block>",
properties => sub {
my ($stomach, $xscale, $yscale, $box) = @_;
$xscale = ToString($xscale);
$yscale = ($yscale ? ToString($yscale) : $xscale);
my ($w, $h, $d) = $box->getSize;
return () unless $w;
(width => $w->multiply($xscale),
height => $h->multiply($yscale),
depth => $d->multiply($yscale),
xscale => $xscale,
yscale => $yscale,
xtranslate => $w->multiply(($xscale - 1) / 2),
ytranslate => $h->add($d)->multiply(($yscale - 1) / 2)); },
mode => 'text');
DefConstructor('\resizebox OptionalMatch:* {GraphixDimension}{GraphixDimension} Digested', sub {
my ($document, $star, $width, $height, $box, %props) = @_;
insertBlock($document, $box,
width => $props{width},
height => $props{height},
depth => $props{depth}); }, # ?
properties => sub {
my ($stomach, $star, $width, $height, $box) = @_;
(($width ? (width => $width) : ()),
($height ? (height => $height) : ()),
depth => Dimension(0)); }, # ?
mode => 'text');
# TeX wants to rotate CCW, by default, about the left baseline,
# but some macros allow you to specify the point or corner about which to rotate.
# It then allocates the width of the resulting box.
# CSS3's rotation wants to rotate CW about the CENTER of the box!
# The resulting box should have it's origin at the leftmost corner
# But we're shifting the box from the position CSS thought that it WAS (before rotation!)
# However, I THINK that it is using the GIVEN width & height
# to determine the center!
# Presumably CSS has lost the sense of the box's baseline?
# Note that our transformable-attributes apply: translate, scale, rotate, in that order.
# NOTE: There's a bizarre interaction between these CSS transformations
# and position:absolute that I haven't sorted out!
sub rotatedProperties {
my ($box, $angle, %options) = @_;
$angle = $angle->valueOf if ref $angle;
my $cwangle = -$angle;
my ($width, $height, $depth) = $box->getSize;
return () unless $width;
my ($w, $h, $d) = map { $_->valueOf } $width, $height, $depth;
my $x0 = 0;
my $y0 = 0;
if ($options{x}) { $x0 = $options{x}; $x0 = $x0->valueOf if ref $x0; }
if ($options{y}) { $y0 = $options{y}; $y0 = $y0->valueOf if ref $y0; }
if (my $origin = ToString($options{origin})) {
if ($origin =~ /l/) { $x0 = 0; }
elsif ($origin =~ /r/) { $x0 = $w; }
elsif ($origin =~ /c/) { $x0 = $w / 2; $y0 = ($h - $d) / 2; }
if ($origin =~ /t/) { $y0 = $h; }
elsif ($origin =~ /b/) { $y0 = -$d; }
elsif ($origin =~ /B/) { $y0 = 0; } }
## print STDERR "ROTATE ".$width->ptValue." x ".$height->ptValue." + ".$depth->ptValue
## ." by $angle about ".Dimension($x0)->ptValue.", ".Dimension($y0)->ptValue."\n";
my $H = $h + $d;
my $rad = $angle * 3.1415926 / 180; # close enough
my $s = sin($rad);
my $c = cos($rad);
my $wp = abs($w * $c) + abs($H * $s);
my @cornerys = (
(-$d - $y0) * $c + (+0 - $x0) * $s + $y0, # bottom left
(-$d - $y0) * $c + ($w - $x0) * $s + $y0, # bottom right
(+$h - $y0) * $c + ($w - $x0) * $s + $y0, # top right
(+$h - $y0) * $c + (+0 - $x0) * $s + $y0); # top left
my $hp = max(@cornerys);
my $dp = -min(@cornerys);
my $xsh = Dimension(($wp - $w) / 2)->ptValue . 'pt';
# my $ysh = Dimension(-($hp + $dp - $h - $d) / 2)->ptValue . 'pt';
# my $ysh = Dimension(($h + $d - $hp + $dp) / 2)->ptValue . 'pt';
# Not sure about the geometry here!
my $ysh = Dimension(($h - $hp + $dp) / 2 + $d)->ptValue . 'pt';
if ($options{smash}) {
# $wp = $hp = $dp = 0; }
$wp = 0; }
# Since $dp & $ysh both try to have the same effect
# (& I don't think the CSS box really has a depth anymore)
$hp += $dp; $dp = 0;
my $widthp = Dimension($wp);
my $heightp = Dimension($hp);
my $depthp = Dimension($dp);
## print STDERR " => ".$widthp->ptValue." x ".$heightp->ptValue." + ".$depthp->ptValue." => ".$xsh.", ".$ysh."\n";
## print STDERR "\nROTATE $angle of ".ToString($box)."\n"
## ." == ".ToString($width).' x '.ToString($height).' + '.ToString($depth)."\n"
## ." => ".ToString($widthp).' x '.ToString($heightp).' + '.ToString($depthp)."\n";
return (
angle => $angle,
width => $widthp,
height => $heightp,
depth => $depthp,
innerwidth => $width,
innerheight => $height,
innerdepth => $depth,
xtranslate => $xsh,
ytranslate => $ysh,
); }
# NOTE: Need to implement the origin of rotation!
# [code so far only reads them]
DefKeyVal('Grot', 'origin', ''); # c,l,r,t,b,B
DefKeyVal('Grot', 'x', 'Dimension');
DefKeyVal('Grot', 'y', 'Dimension');
DefKeyVal('Grot', 'units', '');
DefConstructor('\rotatebox OptionalKeyVals:Grot {Float} Digested',
"<ltx:inline-block angle='#angle' width='#width' height='#height' depth='#depth'"
. " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'"
. " xscale='#xscale' yscale='#yscale'"
. " xtranslate='#xtranslate' ytranslate='#ytranslate'>"
. "#3"
. "</ltx:inline-block>",
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my ($kv, $angle, $box) = $whatsit->getArgs();
$whatsit->setProperties(rotatedProperties($box, $angle, ($kv ? $kv->getHash : ()))); });
DefConstructor('\reflectbox Digested',
"<ltx:inline-block angle='#angle' width='#width' height='#height' depth='#depth'"
. " innerwidth='#innerwidth' innerheight='#innerheight' innerdepth='#innerdepth'"
. " xscale='#xscale' yscale='#yscale'"
. " xtranslate='#xtranslate' ytranslate='#ytranslate'>"
. "#1"
. "</ltx:inline-block>",
properties => sub {
my ($stomach, $box) = @_;
my ($w, $h, $d) = $box->getSize;
return () unless $w;
(width => $w,
height => $h,
depth => $d,
xscale => -1,
yscale => 1); },
mode => 'text');
DefConstructor('\graphicspath DirectoryList', sub {
my ($document, $paths, %props) = @_;
foreach my $path (@{ $props{paths} }) {
$document->insertPI('latexml', graphicspath => $path); } },
properties => sub {
my ($stomach, $paths) = @_;
my @paths = ();
foreach my $dir ($paths->getValues) {
my $path = pathname_absolute(pathname_canonical(ToString($dir)));
push(@paths, $path);
PushValue(GRAPHICSPATHS => $path); }
return (paths => [@paths]); });
# Basically, we're transforming the graphics options into graphicx format.
DefMacro('\includegraphics OptionalMatch:* [][] Semiverbatim',
'\@includegraphics#1[#2][#3]{#4}');
DefConstructor('\@includegraphics OptionalMatch:* [][] Semiverbatim',
"<ltx:graphics graphic='#graphic' candidates='#candidates' options='#options'/>",
sizer => \&image_graphicx_sizer,
properties => sub {
my ($stomach, $starred, $op1, $op2, $graphic) = @_;
my $bb = ($op2 ? ToString($op1) . " " . ToString($op2) : ($op1 ? "0 0 " . ToString($op1) : ''));
$bb =~ s/,/ /g;
my $options = ($starred ? ($bb ? "viewport=$bb, clip" : "clip") : '');
$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');
# Also unimplemented... probably nothing useful to pass thru anyway?
DefConstructor('\DeclareGraphicsExtensions{}', '');
DefConstructor('\DeclareGraphicsRule{}{}{} Undigested', '');
# Nothing done about the keyval package...
#======================================================================
1;