our
$LATEXCMD
=
'latex'
;
sub
new {
my
(
$class
,
%options
) =
@_
;
my
$self
=
$class
->SUPER::new(
%options
);
$$self
{magnification} =
$options
{magnification} || 1.33333;
$$self
{maxwidth} =
$options
{maxwidth} || 800;
$$self
{DPI} =
$options
{DPI} || 100;
$$self
{background} =
$options
{background} ||
"#FFFFFF"
;
$$self
{imagetype} =
$options
{imagetype} ||
'png'
;
$$self
{padding} =
$options
{padding} || 2;
$$self
{clippingfudge} = 3;
$$self
{clippingrule} = 0.90;
if
((
$$self
{imagetype} eq
'svg'
) && (!
defined
$options
{use_dvisvgm}) && which(
'dvisvgm'
)) {
$$self
{use_dvisvgm} = 1; }
elsif
(
$$self
{use_dvisvgm} && ((
$$self
{imagetype} ne
'svg'
) || !which(
'dvisvgm'
))) {
$$self
{use_dvisvgm} = 0; }
if
((
$$self
{imagetype} eq
'png'
) && (!
defined
$options
{use_dvipng}) && which(
'dvipng'
)) {
$$self
{use_dvipng} = 1; }
elsif
(
$$self
{use_dvipng} && ((
$$self
{imagetype} ne
'png'
) || !which(
'dvipng'
))) {
$$self
{use_dvipng} = 0; }
my
$mag
=
int
(
$$self
{magnification} * 1000);
my
$dpi
=
int
(
$$self
{DPI} *
$$self
{magnification});
my
$fmt
=
'%'
;
if
(
$$self
{use_dvisvgm}) {
$$self
{dvicmd} =
"dvisvgm --page=1- --bbox=1pt --scale=$$self{magnification} --no-fonts -o imgx-${fmt}3p"
;
$$self
{dvicmd_output_name} =
'imgx-%03d.svg'
;
$$self
{dvicmd_output_type} =
'svg'
;
$$self
{frame_output} = 0; }
elsif
(
$$self
{use_dvipng}) {
$$self
{dvicmd} =
"dvipng -bg Transparent -T tight -q -D$dpi -o imgx-${fmt}03d.png"
;
$$self
{dvicmd_output_name} =
'imgx-%03d.png'
;
$$self
{dvicmd_output_type} =
'png32'
;
$$self
{frame_output} = 1; }
else
{
$$self
{dvicmd} =
"dvips -q -S1 -i -E -j0 -x$mag -o imgx"
;
$$self
{dvicmd_output_name} =
'imgx%03d'
;
$$self
{dvicmd_output_type} =
'eps'
;
$$self
{frame_output} = 1; }
return
$self
; }
sub
canProcess {
my
(
$self
) =
@_
;
if
(!image_can_image()) {
Error(
'expected'
,
'Image::Magick'
,
undef
,
"No available image processing module found; Skipping."
,
"Please install one of: "
.
join
(
','
, image_classes()));
return
; }
if
((
$LATEXCMD
=~ /^(\S+)/) && !which($1)) {
Error(
'expected'
,
$LATEXCMD
,
undef
,
"No latex command ($LATEXCMD) found; Skipping."
,
"Please install TeX to generate images from LaTeX"
);
return
; }
return
1; }
sub
toProcess {
return
(); }
sub
extractTeX {
return
""
; }
sub
format_tex {
return
""
; }
sub
setTeXImage {
my
(
$self
,
$doc
,
$node
,
$path
,
$width
,
$height
,
$depth
) =
@_
;
$node
->setAttribute(
'imagesrc'
, pathname_to_url(
$path
));
$node
->setAttribute(
'imagewidth'
,
$width
);
$node
->setAttribute(
'imageheight'
,
$height
);
$node
->setAttribute(
'imagedepth'
,
$depth
)
if
defined
$depth
;
return
; }
sub
cleanTeX {
my
(
$self
,
$tex
) =
@_
;
return
unless
defined
$tex
;
my
$style
=
''
;
if
(
$tex
=~ s/^\s*(\\displaystyle|\\textstyle|\\scriptstyle|\\scriptscriptstyle)\s*//) {
$style
= $1; }
$tex
=~ s/^(?:\\\s*,|\\!\s*|\\>\s*|\\;\s*|\\:\s*|\\ \s*|\\\/\s*)*//;
$tex
=~ s/(?:\\\s*,|\\!\s*|\\>\s*|\\;\s*|\\:\s*|\\ \s*|\\\/\s*)*$//;
$tex
=~ s/(?<!\\)((?:\\\\)*)\%[^\n]*\n/$1/gs;
$tex
=
$style
.
' '
.
$tex
if
$style
;
return
$tex
; }
sub
generateImages {
my
(
$self
,
$doc
,
@nodes
) =
@_
;
my
$jobname
=
"ltxmlimg"
;
my
$orig_cwd
= pathname_cwd();
my
$sep
=
$Config::Config
{path_sep};
my
%table
= ();
my
(
$ntotal
,
$nuniq
) = (0, 0);
foreach
my
$node
(
@nodes
) {
my
$tex
=
$self
->extractTeX(
$doc
,
$node
);
next
if
!(
defined
$tex
) || (
$tex
=~ /^\s*$/);
$ntotal
++;
my
$dest
=
$self
->desiredResourcePathname(
$doc
,
$node
,
undef
,
$$self
{imagetype});
my
$key
= (
ref
$self
) .
':'
.
$$self
{imagetype} .
':'
.
$tex
;
my
$entry
=
$table
{
$key
};
if
(!
$entry
) {
$nuniq
++;
$entry
=
$table
{
$key
} = {
tex
=>
$tex
,
key
=>
$key
,
nodes
=> [],
dest
=> [] }; }
if
(
$dest
&& (!
grep
{
$dest
eq
$_
} @{
$$entry
{dest} })) {
push
(@{
$$entry
{dest} },
$dest
); }
push
(@{
$$entry
{nodes} },
$node
); }
return
$doc
unless
$nuniq
;
return
unless
$self
->canProcess;
my
$destdir
=
$doc
->getDestinationDirectory;
my
@pending
= ();
foreach
my
$key
(
sort
keys
%table
) {
my
$store
=
$doc
->cacheLookup(
$key
);
if
(
$store
&& (
$store
=~ /^(.*);(\d+);(\d+);(\d+)$/)) {
next
if
-f pathname_absolute($1,
$destdir
); }
push
(
@pending
,
$table
{
$key
}); }
Debug(
"LaTeXImages: $nuniq unique; "
.
scalar
(
@pending
) .
" new"
)
if
$LaTeXML::DEBUG
{images};
if
(
@pending
) {
File::Temp->safe_level(File::Temp::MEDIUM);
my
$workdir
= File::Temp->newdir(
"LaTeXMLXXXXXX"
,
TMPDIR
=> 1);
my
@searchpaths
=
$doc
->getSearchPaths;
my
$installation_path
= pathname_installation();
my
$texfile
= pathname_make(
dir
=>
$workdir
,
name
=>
$jobname
,
type
=>
'tex'
);
my
$TEX
;
if
(!
open
(
$TEX
,
'>'
,
$texfile
)) {
Error(
'I/O'
,
$texfile
,
undef
,
"Cant write to '$texfile'"
,
"Response was: $!"
);
return
$doc
; }
my
(
$pre_preamble
,
$add_to_body
) =
$self
->pre_preamble(
$doc
);
binmode
$TEX
,
':encoding(UTF-8)'
;
print
$TEX
$pre_preamble
if
$pre_preamble
;
print
$TEX
"\\makeatletter\n"
;
print
$TEX
$self
->preamble(
$doc
) .
"\n"
;
print
$TEX
"\\makeatother\n"
;
print
$TEX
"\\begin{document}\n"
;
print
$TEX
$add_to_body
if
$add_to_body
;
foreach
my
$entry
(
@pending
) {
print
$TEX
"$$entry{tex}\\clearpage\n"
; }
print
$TEX
"\\end{document}\n"
;
close
(
$TEX
);
pathname_chdir(
$workdir
);
my
$ltxcommand
=
"$LATEXCMD $jobname > $jobname.ltxoutput"
;
my
$ltxerr
;
{
local
$ENV
{TEXINPUTS} =
join
(
$sep
,
'.'
,
@searchpaths
,
pathname_concat(
$installation_path
,
'texmf'
),
(
$ENV
{TEXINPUTS} ||
$sep
));
$ltxerr
=
system
(
$ltxcommand
);
}
pathname_chdir(
$orig_cwd
);
if
((
$ltxerr
!= 0) || (!-f
"$workdir/$jobname.dvi"
)) {
$workdir
->unlink_on_destroy(0)
if
$LaTeXML::DEBUG
{images};
Error(
'shell'
,
$LATEXCMD
,
undef
,
"LaTeX command '$ltxcommand' failed"
,
(
$ltxerr
== 0 ?
"No dvi file generated"
:
"returned code $ltxerr (!= 0): $@"
),
(
$LaTeXML::DEBUG
{images}
?
"See $workdir/$jobname.log"
:
"Re-run with --debug=images to see TeX log"
));
return
$doc
; }
my
@dimensions
= ();
my
$LOG
;
if
(
open
(
$LOG
,
'<'
,
"$workdir/$jobname.log"
)) {
while
(<
$LOG
>) {
if
(/^\s
*LXIMAGE
\s*(\d+)\s*=\s*([\+\-\d\.]+)pt\s
*x
\s*([\+\-\d\.]+)pt\s*\+\s*([\+\-\d\.]+)pt\s*$/) {
$dimensions
[$1] = [$2, $3, $4]; } }
close
(
$LOG
); }
else
{
Warn(
'expected'
,
'dimensions'
,
undef
,
"Couldn't read log file $workdir/$jobname.log to extract image dimensions"
,
"Response was: $!"
); }
pathname_chdir(
$workdir
);
my
$dvicommand
=
"$$self{dvicmd} $jobname.dvi > $jobname.dvioutput"
;
my
$dvierr
;
{
local
$ENV
{TEXINPUTS} =
join
(
$sep
,
'.'
,
@searchpaths
,
pathname_concat(
$installation_path
,
'texmf'
),
(
$ENV
{TEXINPUTS} ||
$sep
));
$dvierr
=
system
(
$dvicommand
);
}
pathname_chdir(
$orig_cwd
);
if
(
$dvierr
!= 0) {
Error(
'shell'
,
$$self
{dvicmd},
undef
,
"Shell command '$dvicommand' (for dvi conversion) failed (see $workdir for clues)"
,
"Response was: $!"
);
return
$doc
; }
my
$pixels_per_pt
=
$$self
{magnification} *
$$self
{DPI} / 72.27;
my
(
$index
,
$ndigits
) = (0, 1 +
int
(
log
(
$doc
->cacheLookup((
ref
$self
) .
':_max_image_'
) || 1) /
log
(10)));
foreach
my
$entry
(
@pending
) {
my
$src
=
"$workdir/"
.
sprintf
(
$$self
{dvicmd_output_name}, ++
$index
);
if
(-f
$src
) {
my
@dests
= @{
$$entry
{dest} };
push
(
@dests
,
$self
->generateResourcePathname(
$doc
,
$$entry
{nodes}[0],
undef
,
$$self
{imagetype}))
unless
@dests
;
foreach
my
$dest
(
@dests
) {
my
$absdest
=
$doc
->checkDestination(
$dest
);
my
(
$ww
,
$hh
,
$dd
) =
map
{
$_
*
$pixels_per_pt
} @{
$dimensions
[
$index
] };
my
(
$w
,
$h
);
if
(
$$self
{frame_output}) {
(
$w
,
$h
) =
$self
->convert_image(
$doc
,
$src
,
$absdest
);
next
unless
defined
$w
&&
defined
$h
; }
else
{
pathname_copy(
$src
,
$absdest
);
$w
=
int
(
$ww
+ 0.5);
$h
=
int
(
$hh
+
$dd
+ 0.5); }
my
$d
=
int
(0.5 + (
$dd
|| 0) +
$$self
{padding});
if
(((
$w
== 1) && (
$ww
> 1)) || ((
$h
== 1) && (
$hh
> 1))) {
Warn(
'expected'
,
'image'
,
undef
,
"Image for '$$entry{tex}' was cropped to nothing!"
); }
$doc
->cacheStore(
$$entry
{key},
"$dest;$w;$h;$d"
); }
}
else
{
Warn(
'expected'
,
'image'
,
undef
,
"Missing image '$src'; See $workdir/$jobname.log"
); } } }
foreach
my
$entry
(
values
%table
) {
next
unless
(
$doc
->cacheLookup(
$$entry
{key}) ||
''
) =~ /^(.*);(\d+);(\d+);(\d+)$/;
my
(
$image
,
$width
,
$height
,
$depth
) = ($1, $2, $3, $4);
my
$reldest
= pathname_relative(
$image
,
$doc
->getDestinationDirectory);
foreach
my
$node
(@{
$$entry
{nodes} }) {
$self
->setTeXImage(
$doc
,
$node
,
$reldest
,
$width
,
$height
,
$depth
); } }
$doc
->closeCache;
return
$doc
; }
sub
pre_preamble {
my
(
$self
,
$doc
) =
@_
;
my
@classdata
=
$self
->find_documentclass_and_packages(
$doc
);
my
(
$class
,
$class_options
,
$oldstyle
) = @{
shift
(
@classdata
) };
$class_options
=
"[$class_options]"
if
$class_options
&& (
$class_options
!~ /^\[.*\]$/);
$class_options
=
''
unless
defined
$class_options
;
my
$documentcommand
= (
$oldstyle
?
"\\documentstyle"
:
"\\documentclass"
);
my
$packages
=
''
;
my
$dest
=
$doc
->getDestination;
my
$description
= (
$dest
?
"% Destination $dest"
:
""
);
my
$pts_per_pixel
= 72.27 /
$$self
{DPI} /
$$self
{magnification};
my
%loaded_check
= ();
foreach
my
$pkgdata
(
@classdata
) {
my
(
$package
,
$package_options
) =
@$pkgdata
;
next
if
$loaded_check
{
$package
};
$loaded_check
{
$package
} = 1;
if
(
$oldstyle
) {
next
if
$package
=~ /latexml/;
$packages
.=
"\\RequirePackage{$package}\n"
; }
else
{
if
(
$package
eq
'english'
) {
$packages
.=
"\\usepackage[english]{babel}\n"
; }
else
{
$package_options
=
"[$package_options]"
if
$package_options
&& (
$package_options
!~ /^\[.*\]$/);
$packages
.=
"\\usepackage$package_options\{$package}\n"
; } } }
my
$w
= ceil(
$$self
{maxwidth} *
$pts_per_pixel
);
my
$gap
= (
$$self
{padding} +
$$self
{clippingfudge}) *
$pts_per_pixel
;
my
$th
= (
$$self
{frame_output}
?
$$self
{clippingrule} *
$pts_per_pixel
: 0);
my
$preambles
=
$self
->find_preambles(
$doc
);
my
$result_add_to_body
=
"\\makeatletter\\thispagestyle{empty}\\pagestyle{empty}\n"
;
$result_add_to_body
.=
"\\let\\\@\@toccaption\\\@gobble\n\\let\\\@\@caption\\\@gobble\n"
.
"\\let\\cite\\\@gobble\n\\def\\\@\@bibref#1#2#3#4{}\n"
;
$result_add_to_body
.=
"\\renewcommand{\\cite}[2][]{}\n"
unless
$oldstyle
;
if
(
$class
=~ /^JHEP$/i) {
$class
=
'article'
; }
elsif
(
$class
=~ /revtex/) {
$result_add_to_body
.=
"\\\@ifundefined{\@author\@def}{\\author{}}{}\n"
; }
if
(
$class
ne
'article'
) {
$result_add_to_body
.=
"\\title{}\\date{}\n"
; }
$result_add_to_body
.=
"\\makeatother\n"
;
my
$result_preamble
=
<<"EOPreamble";
\\batchmode
\\def\\inlatexml{true}
$documentcommand$class_options\{$class}
$description
$packages
\\makeatletter
\\setlength{\\hoffset}{0pt}\\setlength{\\voffset}{0pt}
\\setlength{\\textwidth}{${w}pt}
\\newcount\\lxImageNumber\\lxImageNumber=0\\relax
\\newbox\\lxImageBox
\\newdimen\\lxImageBoxSep
\\setlength\\lxImageBoxSep{${gap}\\p\@}
\\newdimen\\lxImageBoxRule
\\setlength\\lxImageBoxRule{${th}\\p\@}
\\def\\lxShowImage{%
\\global\\advance\\lxImageNumber1\\relax
\\\@tempdima\\wd\\lxImageBox
\\advance\\\@tempdima-\\lxImageBoxSep
\\advance\\\@tempdima-\\lxImageBoxSep
\\typeout{LXIMAGE \\the\\lxImageNumber\\space= \\the\\\@tempdima\\space x \\the\\ht\\lxImageBox\\space + \\the\\dp\\lxImageBox}%
\\\@tempdima\\lxImageBoxRule
\\advance\\\@tempdima\\lxImageBoxSep
\\advance\\\@tempdima\\dp\\lxImageBox
\\hbox{%
\\lower\\\@tempdima\\hbox{%
\\vbox{%
\\hrule\\\@height\\lxImageBoxRule%
\\hbox{%
\\vrule\\\@width\\lxImageBoxRule%
\\vbox{%
\\vskip\\lxImageBoxSep
\\box\\lxImageBox
\\vskip\\lxImageBoxSep
}%
\\vrule\\\@width\\lxImageBoxRule%
}%
\\hrule\\\@height\\lxImageBoxRule%
}%
}%
}%
}%
\\def\\lxBeginImage{\\setbox\\lxImageBox\\hbox\\bgroup\\color\@begingroup\\kern\\lxImageBoxSep}
\\def\\lxEndImage{\\kern\\lxImageBoxSep\\color\@endgroup\\egroup}
$preambles
\\makeatother
EOPreamble
return
(
$result_preamble
,
$result_add_to_body
); }
sub
convert_image {
my
(
$self
,
$doc
,
$src
,
$dest
) =
@_
;
my
(
$bg
,
$fg
) = (
$$self
{background},
'black'
);
my
$image
= image_object(
antialias
=>
'True'
,
background
=>
$bg
,
density
=>
$$self
{DPI});
my
$err
=
$image
->Read(
$$self
{dvicmd_output_type} .
':'
.
$src
);
if
(
$err
) {
Warn(
'imageprocessing'
,
'read'
,
undef
,
"Image conversion failed to read '$src'"
,
"Response was: $err"
);
return
; }
my
(
$ww
,
$hh
) =
$image
->Get(
'width'
,
'height'
);
$image
->Border(
width
=> 1,
height
=> 1,
fill
=>
$bg
);
$image
->Trim(
fuzz
=>
'75%'
);
$image
->Set(
fuzz
=>
'0%'
);
my
$fudge
=
int
(0.5 +
$$self
{clippingfudge} +
$$self
{clippingrule});
$image
->Shave(
width
=>
$fudge
,
height
=>
$fudge
);
my
(
$w
,
$h
) =
$image
->Get(
'width'
,
'height'
);
$image
->Set(
page
=>
"${w}x${h}+0+0"
);
$image
->Transparent(
color
=>
$bg
);
Debug(
"LaTeXImages: Converting $src => $dest ($w x $h)"
)
if
$LaTeXML::DEBUG
{images};
if
(
$$self
{imagetype} eq
'png'
) {
$dest
=
"png32:$dest"
; }
$image
->Write(
filename
=>
$dest
);
return
(
$w
,
$h
); }
sub
DESTROY {
if
(
my
$tmpdir
= File::Spec->tmpdir()) {
if
(-d
$tmpdir
&&
opendir
(
my
$tmpdir_fh
,
$tmpdir
)) {
my
@empty_magick
=
grep
{ -z
$_
}
map
{
"$tmpdir/$_"
}
readdir
(
$tmpdir_fh
);
closedir
(
$tmpdir_fh
);
unlink
$_
foreach
@empty_magick
;
} }
return
; }
1;