#!/usr/bin/env perl
use strict;
$^W=1; # turn warning on
#
# pdfcrop.pl
#
# Copyright (C) 2002, 2004, 2005, 2008-2012 Heiko Oberdiek.
# 2020-2023 Oberdiek Package Support Group
#
# This program may be distributed and/or modified under the
# conditions of the LaTeX Project Public License, version 1.3c or later.
#
# See file "README.md" for a list of files that belong to this project.
#
# This file "pdfcrop.pl" may be renamed to "pdfcrop"
# for installation purposes.
#
my $prj = 'pdfcrop';
my $file = "$prj.pl";
my $program = uc($&) if $file =~ /^\w+/;
my $version = "1.42";
my $date = "2023/04/15";
my $author = "Heiko Oberdiek, Oberdiek Package Support Group";
my $copyright = "Copyright (c) 2002-2023 by $author.";
#
# Reqirements: Perl5, Ghostscript
# History:
# 2002/10/30 v1.0: First release.
# 2002/10/30 v1.1: Option --hires added.
# 2002/11/04 v1.2: "nul" instead of "/dev/null" for windows.
# 2002/11/23 v1.3: Use of File::Spec module's "devnull" call.
# 2002/11/29 v1.4: Option --papersize added.
# 2004/06/24 v1.5: Clear map file entries so that pdfTeX
# does not touch the fonts.
# 2004/06/26 v1.6: Use mgs.exe instead of gswin32c.exe for MIKTEX.
# 2005/03/11 v1.7: Support of spaces in file names
# (open("-|") is used for ghostscript call).
# 2008/01/09 v1.8: Fix for moving the temporary file to the output
# file across file system boundaries.
# 2008/04/05 v1.9: Options --resolution and --bbox added.
# 2008/07/16 v1.10: Support for XeTeX added with new options
# --pdftex, --xetex, and --xetexcmd.
# 2008/07/22 v1.11: Workaround for open("-|").
# 2008/07/23 v1.12: Workarounds for the workaround (error detection, ...).
# 2008/07/24 v1.13: open("-|")/workaround removed.
# Input files with unsafe file names are linked/copied
# to temporary file with safe file name.
# 2008/09/12 v1.14: Error detection for invalid Bounding Boxes.
# 2009/07/14 v1.15: Fix for negative coordinates in Bounding Boxes
# (David Menestrina).
# 2009/07/16 v1.16: Security fixes:
# * -dSAFER added for Ghostscript,
# * -no-shell-escape added for pdfTeX/XeTeX.
# 2009/07/17 v1.17: Security fixes:
# * Backticks and whitespace are forbidden
# for options --(gs|pdftex|xetex)cmd.
# * Validation of options --papersize and --resolution.
# 2009/07/18 v1.18: * Restricted mode added.
# * Option --version added.
# 2009/09/24 v1.19: * Ghostscript detection rewritten.
# * Cygwin: `gs' is preferred to `gswin32c'.
# 2009/10/06 v1.20: * File name sanitizing in .tex file.
# 2009/12/21 v1.21: * Option --ini added for IniTeX mode.
# * Option --luatex and --luatexcmd added for LuaTeX.
# 2009/12/29 v1.22: * Syntax description for option --bbox fixed
# (Lukas Prochazka).
# 2010/01/09 v1.23: * Options --bbox-odd and -bbox-even added.
# 2010/08/16 v1.24: * Workaround added for buggy ghostscript ports
# that print the BoundingBox data twice.
# 2010/08/26 v1.25: * Fix for the case that the PDF file contains
# an entry /CropBox different to /MediaBox.
# * \pageinclude implemented for XeTeX.
# * XeTeX: --clip does not die, but this option
# is ignored, because XeTeX always clip.
# 2010/08/26 v1.26: * XeTeX's \XeTeXpdffile expects keyword
# `media', not `mediabox'.
# * New option --pdfversion.
# Default is `auto' that means the PDF version
# is inherited from the input file. Before
# pdfcrop has used the TeX engine's default.
# * Option --luatex fixed (extra empty page at end).
# 2010/09/03 v1.27: * Workaround of v1.24 fixed.
# 2010/09/06 v1.28: * The Windows registry is searched if Ghostscript
# is not found via PATH.
# * Windows only: support of spaces in command
# names in unrestricted mode.
# 2010/09/06 v1.29: * Find the latest Ghostscript version in registry.
# 2010/09/15 v1.30: * Warning of pdfTeX because of \pdfobjcompresslevel
# avoided when reducing \pdfminorversion.
# * Fix for TeX syntax characters in input file names.
# 2010/09/17 v1.31: * Passing the input file name via hex string to TeX.
# * Again input file names restricted for Ghostscript
# command line, switch then to symbol link/copy
# method.
# 2011/08/10 v1.32: * Detection for gswin64c.exe added.
# 2012/02/01 v1.33: * Input file can be `-' (standard input).
# 2012/04/18 v1.34: * Format of --version changed
# from naked version number to a line with
# program name, date and version.
# 2012/10/15 v1.35: * Additional debug infos added for Perl version.
# 2012/10/16 v1.36: * More error codes added.
# 2012/10/16 v1.37: * Extended error messages if available.
# * Fix for broken v1.36.
# 2012/11/02 v1.38: * Fix for unsufficient cleanup, if function `cleanup' is
# prematurely called in `eval' for `symlink' checking.
# 2020/05/24 v1.39: * adapted to pdfversion 2.0, corrected luatex support,
# corrected a problem with xetex.
# 2020/06/06 v1.40: * improved ghostscript detection on windows when a bash is used
# added direct pdf version support to xetex.
# 2023/04/13 v1.41: * allow gswin64c in restricted mode, fix typos in messages issues 14, 17
# add -q option, issue 7;
# don't print whole help msg for unknown options, issue 7.
# do not create two pages with xetex, issue 3
# 2023/04/15 v1.42: * update help text issue 18
### program identification
my $title = "$program $version, $date - $copyright\n";
### make ENV safer
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
# Windows detection (no SIGHUP)
my $Win = 0;
$Win = 1 if $^O =~ /mswin32/i;
$Win = 1 if $^O =~ /windows/i;
$Win = 1 if $^O =~ /cygwin/i;
use Config;
my $archname = $Config{'archname'};
$archname = 'unknown' unless defined $Config{'archname'};
sub usage ($) {
my $ret = shift;
find_ghostscript();
my $usage = <<"END_OF_USAGE";
${title}Syntax: \L$program\E [options] <input[.pdf]> [output file]
Function: Margins are calculated and removed for each page in the file.
Options: (defaults:)
--help print usage
--version print version number
--(no)verbose verbose printing ($bool[$::opt_verbose])
--(no)quiet silence normal output ($bool[$::opt_quiet])
--(no)debug debug information ($bool[$::opt_debug])
--gscmd <name> call of Ghostscript ($::opt_gscmd)
--pdftex | --xetex | --luatex
use pdfTeX | use XeTeX | use LuaTeX ($::opt_tex)
--pdftexcmd <name> call of pdfTeX ($::opt_pdftexcmd)
--xetexcmd <name> call of XeTeX ($::opt_xetexcmd)
--luatexcmd <name> call of LuaTeX ($::opt_luatexcmd)
--margins "<left> <top> <right> <bottom>" ($::opt_margins)
add extra margins, unit is bp. If only one number is
given, then it is used for all margins; in the case
of two numbers, they are also used for right and bottom.
--(no)clip clipping support, if margins are set ($bool[$::opt_clip])
(not available for --xetex)
--(no)hires use `%%HiResBoundingBox' ($bool[$::opt_hires])
instead of `%%BoundingBox'
--(no)ini use iniTeX variant of the TeX compiler ($bool[$::opt_initex])
Expert options:
--restricted turn on restricted mode ($bool[$restricted])
--papersize <foo> parameter for gs's -sPAPERSIZE=<foo>,
use only with older gs versions <7.32 ($::opt_papersize)
--resolution <xres>x<yres> ($::opt_resolution)
--resolution <res> pass argument to Ghostscript's option -r
Example: --resolution 72
--bbox "<left> <bottom> <right> <top>" ($::opt_bbox)
override bounding box found by Ghostscript
with origin at the lower left corner
--bbox-odd Same as --bbox, but for odd pages only ($::opt_bbox_odd)
--bbox-even Same as --bbox, but for even pages only ($::opt_bbox_even)
--pdfversion <x.y> | auto | none
Set the PDF version to x.y, x= 1 or 2, y= 0-9.
If `auto' is given as value, then the
PDF version is taken from the header
of the input PDF file.
An empty value or `none' uses the
default of the TeX engine. ($::opt_pdfversion)
--uncompress create uncompressed pdf, for debugging ($bool[$::opt_uncompress])
Input file: If the name is `-', then the standard input is used and
the output file name must be explicitly given.
Examples:
\L$program\E --margins 10 input.pdf output.pdf
\L$program\E --margins '5 10 5 20' --clip input.pdf output.pdf
In case of errors:
Try option --verbose first to get more information.
In case of bugs:
Please, use option --debug for bug reports.
END_OF_USAGE
if ($ret) {
die $usage;
}
else {
print $usage;
exit(0);
}
}
### process options
my @OrgArgv = @ARGV;
use Getopt::Long;
GetOptions(
"help!",
"version!",
"debug!",
"verbose!",
"quiet!",
"gscmd=s",
"pdftexcmd=s",
"xetexcmd=s",
"luatexcmd=s",
"pdftex" => sub { $::opt_tex = 'pdftex'; },
"xetex" => sub { $::opt_tex = 'xetex'; },
"luatex" => sub { $::opt_tex = 'luatex'; },
"initex!",
"margins=s",
"clip!",
"hires!",
"papersize=s",
"resolution=s",
"bbox=s",
"bbox-odd=s" => \$::opt_bbox_odd,
"bbox-even=s" => \$::opt_bbox_even,
"restricted" => sub { $restricted = 1; },
"pdfversion=s" => \$::opt_pdfversion,
"uncompress!",
) or die "Try $0 --help for more information\n";
!$::opt_help or usage(0);
if ($::opt_version) {
print "$prj $date v$version\n";
exit(0);
}
$::opt_pdfversion =~ /^(|none|auto|(([1-2])\.(\d)))$/
or die "!!! Error: Invalid value `$::opt_pdfversion' for option `--pdfversion'!\n";
print "* Option `pdfversion': $::opt_pdfversion\n" if $::opt_debug;
$::opt_pdfversion = $2 if $2;
$::opt_pdfmajorversion = $3 if $2;
$::opt_pdfminorversion = $4 if $2;
$::opt_pdfversion = '' if $::opt_pdfversion eq 'none';
find_ghostscript();
if ($::opt_bbox) {
$::opt_bbox =~ s/^\s+//;
$::opt_bbox =~ s/\s+$//;
$::opt_bbox =~ s/\s+/ /;
if ($::opt_bbox =~ /^-?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+$/) {
print "* Explicit Bounding Box: $::opt_bbox\n" if $::opt_debug;
}
else {
die "$Error Parse error (option --bbox \"$::opt_bbox\")!\n";
}
}
if ($::opt_bbox_odd) {
$::opt_bbox_odd =~ s/^\s+//;
$::opt_bbox_odd =~ s/\s+$//;
$::opt_bbox_odd =~ s/\s+/ /;
if ($::opt_bbox_odd =~ /^-?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+$/) {
print "* Explicit Bounding Box for odd pages: $::opt_bbox_odd\n"
if $::opt_debug;
}
else {
die "$Error Parse error (option --bbox-odd \"$::opt_bbox_odd\")!\n";
}
}
if ($::opt_bbox_even) {
$::opt_bbox_even =~ s/^\s+//;
$::opt_bbox_even =~ s/\s+$//;
$::opt_bbox_even =~ s/\s+/ /;
if ($::opt_bbox_even =~ /^-?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+$/) {
print "* Explicit Bounding Box for even pages: $::opt_bbox_even\n"
if $::opt_debug;
}
else {
die "$Error Parse error (option --bbox-even \"$::opt_bbox_even\")!\n";
}
}
@ARGV <= 2 or die "$Error Too many files!\n";
### input file
$inputfile = shift @ARGV;
if ($inputfile eq '-') {
@ARGV == 1 or die "$Error Missing output file name!\n";
print "* Input file: <stdin>\n" if $::opt_debug;
}
else {
if (! -f $inputfile) {
if (-f "$inputfile.pdf") {
$inputfile .= ".pdf";
}
else {
die "$Error Input file `$inputfile' not found!\n";
}
}
print "* Input file: $inputfile\n" if $::opt_debug;
}
### papersize validation (security)
if ($::opt_papersize ne '') {
$::opt_papersize =~ /^[0-9A-Za-z]+$/
or die "$Error Invalid papersize ($::opt_papersize)!\n";
$papersizes{$::opt_papersize}
or die "$Error Unknown papersize ($::opt_papersize),"
. " see Ghostscript's documentation for option `-r'!\n";
}
### resolution validation (security)
if ($::opt_resolution ne '') {
$::opt_resolution =~ /^\d+(x\d+)?$/
or die "$Error Invalid resolution ($::opt_resolution),"
. " see Ghostscript's documentation!\n";
}
### command name validation (security)
my %cmd = (
'gscmd' => \$::opt_gscmd,
'pdftexcmd' => \$::opt_pdftexcmd,
'luatexcmd' => \$::opt_luatexcmd,
'xetexcmd' => \$::opt_xetexcmd
);
foreach my $cmd (keys %cmd) {
my $cmd_ref = $cmd{$cmd};
$$cmd_ref =~ s/^\s+//;
$$cmd_ref =~ s/\s+$//;
my $val = $$cmd_ref;
next unless $val;
next unless $val;
if ($val =~ /`/) {
die "$Error Forbidden backtick for option `--$cmd' ($val)!\n";
}
if ($val =~ /\s/) {
my $err = 0;
if ($restricted or not $Win) {
$err = 1;
}
else {
$err = 1 if $val =~ /\.exe.*\s/i;
$err = 1 if $val =~ /\s[-\/@+]/;
}
die "$Error Forbidden whitespace for option `--$cmd' ($val)!\n" if $err;
}
}
if ($restricted) {
if ($::opt_pdftexcmd and $::opt_pdftexcmd ne 'pdftex') {
die "$Error pdfTeX program name must not be changed in restricted mode!\n";
}
if ($::opt_xetexcmd and $::opt_xetexcmd ne 'xetex') {
die "$Error XeTeX program name must not be changed in restricted mode!\n";
}
if ($::opt_luatexcmd and $::opt_luatexcmd ne 'luatex') {
die "$Error LuaTeX program name must not be changed in restricted mode!\n";
}
if ($::opt_gscmd) {
$::opt_gscmd =~ /^(gs|mgs|gswin32c|gs386|gsos2|gswin64c)$/
or $::opt_gscmd =~ /^gs[\-_]?(\d|\d[\.-_]?\d\d)c?$/
or die "$Error: Invalid Ghostscript program name in restricted mode!\n";
}
}
if ($Win and $::opt_gscmd =~ /\s/) {
$::opt_gscmd = "\"$::opt_gscmd\"";
}
### cleanup system
my @unlink_files = ();
my $exit_code = 1;
sub clean {
print "* Cleanup\n" if $::opt_debug;
if ($::opt_debug) {
print "* Temporary files: @unlink_files\n";
}
else {
for (; @unlink_files>0; ) {
unlink shift @unlink_files;
}
}
}
sub cleanup {
clean();
exit($exit_code);
}
$SIG{'INT'} = \&cleanup;
$SIG{'__DIE__'} = \&clean;
### Calculation of BoundingBoxes
# use safe file name for use within cmd line of gs (unknown shell: space, ...)
# and XeTeX (hash, curly braces, ...)
my $inputfilesafe = $inputfile;
if ($inputfile eq '-') {
$inputfilesafe = "$tmp-stdin.pdf";
print "* Temporary input file: $inputfilesafe\n" if $::opt_debug;
push @unlink_files, $inputfilesafe;
open(OUT, '>', $inputfilesafe)
or die sprintf "$Error Cannot write `%s' (%s)!\n",
$inputfilesafe, exterr;
binmode(OUT);
my $size = 0;
my $len = 0;
my $buf = '';
my $buf_size = 65536;
do {
$len = read STDIN, $buf, $buf_size;
if (not defined $len) {
my $msg = exterr;
die "$Error Reading standard input ($msg)!\n";
}
print OUT $buf
or die sprintf "$Error Writing `%s` (%s)!\n",
$inputfilesafe, exterr;
$size += $len;
}
while ($len);
close(OUT);
print "* File size (STDIN): $size\n" if $::opt_debug;
}
elsif (not $inputfile =~ /^[\w\d\.\-\:\/@]+$/) { # /[\s\$~'"#{}%]/
$inputfilesafe = "$tmp-img.pdf";
push @unlink_files, $inputfilesafe;
my $symlink_exists = eval {
local $SIG{'__DIE__'};
symlink("", "");
1;
};
print "* Input file name `$inputfile' contains special characters.\n"
. "* " . ($symlink_exists ? "Link" : "Copy")
. " input file to temporary file `$inputfilesafe'.\n"
if $::opt_verbose;
if ($symlink_exists) {
symlink($inputfile, $inputfilesafe)
or die sprintf "$Error Link from `%s' to `%s' failed (%s)!\n",
$inputfile, $inputfilesafe, exterr;
}
else {
use File::Copy;
copy($inputfile, $inputfilesafe)
or die sprintf "$Error Copy from `%s' to `%s' failed (%s)\n",
$inputfile, $inputfilesafe, exterr;
}
}
if ($::opt_pdfversion eq 'auto') {
open(IN, '<', $inputfilesafe)
or die sprintf "!!! Error: Cannot open `%s' (%s)!\n",
$inputfilesafe, exterr;
my $buf;
read(IN, $buf, 1024)
or die sprintf "!!! Error: Cannot read the header of `%s' failed (%s)!\n",
$inputfilesafe, exterr;
close(IN);
if ($buf =~ /%PDF-((\d).(\d))\s/) {
$::opt_pdfversion = $1;
$::opt_pdfmajorversion = $2;
$::opt_pdfminorversion = $3;
print "* PDF header: %PDF-$::opt_pdfversion\n" if $::opt_verbose;
$::opt_pdfminorversion = 2 if ($::opt_pdfmajorversion < 2 && $::opt_pdfminorversion < 2);
$::opt_pdfversion = $::opt_pdfmajorversion . '.' . $::opt_pdfminorversion;
}
else {
die "!!! Error: Cannot find PDF header of `$inputfilesafe'!\n";
}
}
print '* Using PDF version: ',
($::opt_pdfversion ? $::opt_pdfversion : "engine's default"),
"\n" if $::opt_debug;
my @gsargs = (
"-sDEVICE=bbox",
"-dBATCH",
"-dNOPAUSE"
);
push @gsargs, "-sPAPERSIZE=$::opt_papersize" if $::opt_papersize;
push @gsargs, "-r$::opt_resolution" if $::opt_resolution;
push @gsargs,
"-c",
"save",
"pop",
"-f",
$inputfilesafe
;
my $tmpfile = "$tmp.tex";
push @unlink_files, $tmpfile;
open(TMP, ">$tmpfile")
or die sprintf "$Error Cannot write tmp file `%s' (%s)!\n",
$tmpfile, exterr;
print TMP <<'END_TMP';
\catcode37 14 % percent
\catcode33 12 % exclam
\catcode34 12 % quote
\catcode35 6 % hash
\catcode39 12 % apostrophe
\catcode40 12 % left parenthesis
\catcode41 12 % right parenthesis
\catcode45 12 % minus
\catcode46 12 % period
\catcode60 12 % less
\catcode61 12 % equals
\catcode62 12 % greater
\catcode64 12 % at
\catcode91 12 % left square
\catcode93 12 % right square
\catcode96 12 % back tick
\catcode123 1 % left curly brace
\catcode125 2 % right curly brace
\catcode126 12 % tilde
\catcode`\#=6 %
\escapechar=92 %
\def\IfUndefined#1#2#3{%
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname#1\endcsname\relax
#2%
\else
#3%
\fi
}
END_TMP
my $pdffilehex = unpack 'H*', $inputfilesafe;
$pdffilehex = "\U$pdffilehex\E";
print TMP "\\def\\pdffilehex{$pdffilehex}\n";
print TMP <<'END_TMP';
\IfUndefined{pdfunescapehex}{%
\begingroup
\gdef\pdffile{}%
\def\do#1#2{%
\ifx\relax#2\relax
\ifx\relax#1\relax
\else
\errmessage{Invalid hex string, should not happen!}%
\fi
\else
\lccode`0="#1#2\relax
\lowercase{%
\xdef\pdffile{\pdffile0}%
}%
\expandafter\do
\fi
}%
\expandafter\do\pdffilehex\relax\relax
\endgroup
}{%
\edef\pdffile{\pdfunescapehex{\pdffilehex}}%
}
\immediate\write-1{Input file: \pdffile}
END_TMP
if ($::opt_tex eq 'luatex') {
print TMP <<'END_TMP';
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname directlua\endcsname\relax
\errmessage{LuaTeX not found!}%
\else
\fi
END_TMP
}
print "* Running Ghostscript for BoundingBox calculation ...\n"
if $::opt_verbose;
print "* Ghostscript call: $::opt_gscmd @gsargs\n" if $::opt_debug;
my @bbox;
my @bbox_all;
my @bbox_odd;
my @bbox_even;
if ($::opt_bbox) {
$::opt_bbox =~ /([-\d\.]+) ([-\d\.]+) ([-\d\.]+) ([-\d\.]+)/;
@bbox_all = ($1, $2, $3, $4);
}
if ($::opt_bbox_odd) {
$::opt_bbox_odd =~ /([-\d\.]+) ([-\d\.]+) ([-\d\.]+) ([-\d\.]+)/;
@bbox_odd = ($1, $2, $3, $4);
}
if ($::opt_bbox_even) {
$::opt_bbox_even =~ /([-\d\.]+) ([-\d\.]+) ([-\d\.]+) ([-\d\.]+)/;
@bbox_even = ($1, $2, $3, $4);
}
sub getbbox ($$$$$) {
my $page = shift;
my $a = shift;
my $b = shift;
my $c = shift;
my $d = shift;
if ($page % 2 == 1) {
if ($::opt_bbox_odd) {
return @bbox_odd;
}
}
else {
if ($::opt_bbox_even) {
return @bbox_even;
}
}
if ($::opt_bbox) {
return @bbox_all;
}
return ($a, $b, $c, $d);
}
my $page = 0;
my $gs_pipe = "$::opt_gscmd -dSAFER @gsargs 2>&1";
$gs_pipe .= " 1>$null" unless $::opt_verbose;
$gs_pipe .= " |";
open(GS, $gs_pipe)
or die sprintf "$Error Cannot call Ghostscript: `%s' (%s)!\n",
$::opt_gscmd, exterr;
my $bb = ($::opt_hires) ? "%%HiResBoundingBox" : "%%BoundingBox";
my $previous_line = 'Previous line';
# Ghostscript workaround for buggy ports that prints
# the bounding box data twice on STDERR.
while (<GS>) {
print $_ if $::opt_verbose;
if (/^$bb:\s*(-?[\.\d]+) (-?[\.\d]+) (-?[\.\d]+) (-?[\.\d]+)/o) {
}
else {
$previous_line = $_;
next;
}
next if $previous_line eq $_;
$previous_line = $_;
$page++;
@bbox = getbbox($page, $1, $2, $3, $4);
my $empty = 0;
$empty = 1 if $bbox[0] >= $bbox[2];
$empty = 1 if $bbox[1] >= $bbox[3];
if ($empty) {
print <<"END_WARNING";
!!! Warning: Empty Bounding Box is returned by Ghostscript!
!!! Page $page: @bbox
!!! Either there is a problem with the page or with Ghostscript.
!!! Recovery is tried by embedding the page in its original size.
$empty = 0;
$empty = 1 if $bb[0] >= $bb[2];
$empty = 1 if $bb[1] >= $bb[3];
if ($empty) {
print <<"END_WARNING";
!!! Warning: The final Bounding Box is empty!
!!! Page: $page: @bb
!!! Probably caused by too large negative margin values.
!!! Recovery by ignoring margin values.
if ($page == 0) {
die "$Error Ghostscript does not report bounding boxes!\n";
}
### Run pdfTeX/XeTeX
push @unlink_files, "$tmp.log";
my $cmd;
my $texname;
if ($::opt_tex eq 'pdftex') {
$cmd = $::opt_pdftexcmd;
$texname = 'pdfTeX';
}
elsif ($::opt_tex eq 'luatex') {
$cmd = $::opt_luatexcmd;
$texname = 'LuaTeX';
}
else {
$cmd = $::opt_xetexcmd;
$texname = 'XeTeX';
}
$cmd = "\"$cmd\"" if $Win and $cmd =~ /\s/;
$cmd .= ' -no-shell-escape';
if ($::opt_initex) {
$cmd .= ' --ini --etex';
}
if ($::opt_verbose) {
$cmd .= " -interaction=nonstopmode $tmp";
}
else {
$cmd .= " -interaction=batchmode $tmp";
}
print "* Running $texname ...\n" if $::opt_verbose;
print "* $texname call: $cmd\n" if $::opt_debug;
if ($::opt_verbose) {
system($cmd);
}
else {
`$cmd`;
}
if ($? == -1) {
die sprintf "$Error %s run failed to execute (%s)!\n",
$texname, exterr;
}
elsif ($? & 127) {
die sprintf "$Error %s run died with signal %d!\n",
$texname, ($? & 127);
}
elsif ($? != 0) {
die sprintf "$Error %s run failed with value %d!\n",
$texname, ($? >> 8);
}
### Check pdf version of temp file
if ($::opt_pdfversion) {
open(PDF, '+<', "$tmp.pdf")
or die sprintf "!!! Error: Cannot open `%s' (%s)!\n",
"$tmp.pdf", exterr;
my $header;
read PDF, $header, 9
or die sprintf "!!! Error: Cannot read header of `%s' (%s)!\n",
"$tmp.pdf", exterr;
$header =~ /^%PDF-(\d\.\d)\s$/ or die "!!! Error: Cannot find header of `$tmp.pdf'!\n";
if ($1 ne $::opt_pdfversion) {
seek PDF, 5, 0
or die sprintf "!!! Error: Cannot seek in `%s' (%s)!\n",
"$tmp.pdf", exterr;
print PDF $::opt_pdfversion
or die sprintf "!!! Error: Cannot write in `%s' (%s)!\n",
"$tmp.pdf", exterr;
print "* PDF version correction in output file: $::opt_pdfversion\n"
if $::opt_debug;
}
close(PDF);
}
### Move temp file to output
if (!rename("$tmp.pdf", $outputfile)) {
use File::Copy;
move "$tmp.pdf", $outputfile or
die "$Error Cannot move `$tmp.pdf' to `$outputfile'!\n";
}
print "==> $page page", (($page == 1) ? "" : "s"),
" written on `$outputfile'.\n" if ! $::opt_quiet;