summaryrefslogtreecommitdiffstats
path: root/pdftool.pl
diff options
context:
space:
mode:
Diffstat (limited to 'pdftool.pl')
-rwxr-xr-xpdftool.pl386
1 files changed, 228 insertions, 158 deletions
diff --git a/pdftool.pl b/pdftool.pl
index 51c57b2..45fccaf 100755
--- a/pdftool.pl
+++ b/pdftool.pl
@@ -7,7 +7,7 @@
# See http://sam.zoy.org/wtfpl/COPYING for more details.
-$VERSION = "0.5, 06 January 2012";
+$VERSION = "0.5.2, 21 January 2012";
use Getopt::Long qw /:config posix_default no_ignore_case gnu_compat
bundling auto_version auto_help/;
@@ -30,15 +30,17 @@ B<pdftool.pl> [B<-w> I<width> ] [B<-h> I<height>] [B<-p> I<paper>]
[B<-m> I<margin>] [B<-b> I<border>] [B<-c>] [B<--book>] [B<--column>]
[B<-n> I<num>] [B<--auto-rotate>] [B<--ps>] [B<-q>] [I<infile> [I<outfile>]]
+B<pdftool.pl> [I<OPTIONS>] I<infile1> ... [I<OPTIONS>] I<infilen> [I<OPTIONS>] I<outfile>
+
=head1 DESCRIPTION
B<PDFTool> combines the tools in the PSUtils bundle in a nice way. The
-input should be either a Portable Document Format (PDF) file, or a
-PostScript file.
+inputs should be either Portable Document Format (PDF) files, or
+PostScript files.
If no input file is given, or if a single hyphen-minus (I<->) is given as
file name, B<PDFTool> reads the PDF or PostScript data from the standard
-input. In a PDF is sent to the standard input, an auxiliary file is
+input. If a PDF is sent to the standard input, an auxiliary file is
created (because reading a PDF requires random access to the data), and removed
afterwards. Also, if the option B<-c> (cropping) is set while the input is
not a regular seekable file, an auxiliary file is created, and removed
@@ -55,7 +57,16 @@ file name, B<PDFTool> sends the data to the standard output. By
default, B<PDFTool> outputs a PDF document; Use B<--ps> to
get a PostScript instead.
-B<PDFTool> does the following passes on the input document:
+If more than n>2 files are passed to B<PDFTool>, the n-1 first will be
+processed (using the set of options given immediately before each
+argument) and then merged. A global set of options can be passed after the
+last input file: they will be applied to all the inputs, (but the local
+options take precedence).
+NOTE: The options B<-p>, B<-w>, B<-h>, B<--auto-rotate> and B<--ps>
+refer to the output file only, and should only be set once.
+
+
+B<PDFTool> does the following passes on the input documents:
=over 4
@@ -177,6 +188,8 @@ C<Tumble> if you prefer to turn the pages like those of a book -
It has no effect if the input is a portrait document.
This flag might not be suitable if you plan to read the output document
on your screen.
+NOTE: Using this flag with multiple input files might lead to weird
+outputs!
=item B<--ps>
@@ -247,145 +260,174 @@ conditions.
-my @gs = ('gs', '-dSAFER');
-
#
# Options & arguments
#
-my %config;
-
-GetOptions( "select|s=s" => sub { $config{select} = $_[1] },
- "w|width=s" => sub { $config{outwidth} = $_[1] },
- "h|height=s" => sub { $config{outheight} = $_[1] },
- "p|paper=s" => sub { my ($w,$h) = &papersize ($_[1]);
- $config{outwidth} = $w;
- $config{outheight} = $h; },
- "W|Width=s" => sub { $config{inwidth} = $_[1] },
- "H|Height=s" => sub { $config{inheight} = $_[1] },
- "P|Paper=s" => sub { my ($w,$h) = &papersize ($_[1]);
- $config{inwidth} = $w;
- $config{inheight} = $h; },
- "margin|m=s" => sub { $config{margin} = $_[1] },
- "border|b=s" => sub { $config{border} = $_[1] },
- "crop|c" => sub { $config{crop} = 1 },
- "book" => sub { $config{book} = 1 },
- "ps" => sub { $config{psout} = 1 },
- "nup|n=i" => sub { $config{nup} = $_[1] },
- "auto-rotate"=> sub { $config{autorotate} = 1 },
- "1" => sub { $config{nup} = 1 },
- "2" => sub { $config{nup} = 2 },
- "3" => sub { $config{nup} = 3 },
- "4" => sub { $config{nup} = 4 },
- "5" => sub { $config{nup} = 5 },
- "6" => sub { $config{nup} = 6 },
- "7" => sub { $config{nup} = 7 },
- "8" => sub { $config{nup} = 8 },
- "9" => sub { $config{nup} = 9 },
- "column" => sub { $config{column} = 1 },
- "q|quiet" => sub { $config{quiet} = 1},
- "man" => sub { pod2usage(-exitstatus => 0, -verbose => 2) }
-) or pod2usage(2);
-
-pod2usage(2) if ($#ARGV > 1);
-
-# Input and output files
-my ($infile, $outfile) = @ARGV;
-$config{infile} = $infile;
-$config{outfile} = $outfile;
-
-
-
-#
-# Default values
-#
-
-$config{nup} = 1 unless exists $config{nup};
-
-# Default margin & border
-$config{margin} = 0 unless exists $config{margin};
-unless (exists $config{border}) {
- if (exists $config{crop}) {
- $config{border} = '1cm';
- } else {
- $config{border} = 0;
+my @inputs;
+my %out; # Global option, for the output file
+
+my $index = 1;
+while (@ARGV) {
+ my %config;
+ GetOptions( "select|s=s" => sub { $config{select} = $_[1] },
+ "w|width=s" => sub { $out{width} = $_[1] },
+ "h|height=s" => sub { $out{height} = $_[1] },
+ "p|paper=s" => sub { my ($w,$h) = &papersize ($_[1]);
+ $out{width} = $w;
+ $out{height} = $h; },
+ "W|Width=s" => sub { $config{inwidth} = $_[1] },
+ "H|Height=s" => sub { $config{inheight} = $_[1] },
+ "P|Paper=s" => sub { my ($w,$h) = &papersize ($_[1]);
+ $config{inwidth} = $w;
+ $config{inheight} = $h; },
+ "margin|m=s" => sub { $config{margin} = $_[1] },
+ "border|b=s" => sub { $config{border} = $_[1] },
+ "crop|c" => sub { $config{crop} = 1 },
+ "book" => sub { $config{book} = 1 },
+ "ps" => sub { $out{ps} = 1 },
+ "nup|n=i" => sub { $config{nup} = $_[1] },
+ "auto-rotate"=> sub { $out{autorotate} = 1 },
+ "1" => sub { $config{nup} = 1 },
+ "2" => sub { $config{nup} = 2 },
+ "3" => sub { $config{nup} = 3 },
+ "4" => sub { $config{nup} = 4 },
+ "5" => sub { $config{nup} = 5 },
+ "6" => sub { $config{nup} = 6 },
+ "7" => sub { $config{nup} = 7 },
+ "8" => sub { $config{nup} = 8 },
+ "9" => sub { $config{nup} = 9 },
+ "column" => sub { $config{column} = 1 },
+ "q|quiet" => sub { $config{quiet} = 1},
+ "man" => sub { pod2usage(-exitstatus => 0,
+ -verbose => 2) }
+ ) or pod2usage(2);
+ if (@ARGV) {
+ $config{infile} = $ARGV[0];
+ $config{index} = $index;
+ $index++;
+ shift;
}
+ push @inputs, \%config;
}
-# Default output papersize
-unless (exists $config{outwidth} and exists $config{outheight}) {
- my ($w,$h) = &papersize ("a4");
- $config{outwidth} = $w;
- $config{outheight} = $h;
-}
-
-# Default unit: PostScript point
-map { $config{$_} = &topoints($config{$_}) if exists $config{$_} }
- qw /outwidth outheight inwidth inheight margin border/;
-die "Error: Margins are too big.\n"
- if $config{outwidth} <= 2*$config{margin}
- or $config{outheight} <= $config{margin};
-
-
-
-#
-# Check options
-#
-die "Error: Bad page range: `" .$config{select}. "'.\n" if
- defined $config{select} && not $config{select} =~ /^(_?\d*-?_?\d*,)*_?\d*-?_?\d*$/;
-
-die "Error: Bad nup: `" .$config{nup}. "'.\n" if
- defined $config{nup} && not ($config{nup} =~ /^\d+$/ && $config{nup} > 0);
+*LOG = *STDERR;
+my @gs = ('gs', '-dSAFER');
+# Global variables
+my @auxfiles; # Auxiliary files, to remove
+my @fds; # File handles, to close
+my @pids; # Pids, to waid for
+my $return = 0; # Return value
+
+
+my $FOUT;
+if ($#inputs > 0) {
+ # The last file with the expected result, and the last set of
+ # options was the global one
+ my $global = pop @inputs;
+
+ pod2usage(2) unless defined $global->{infile};
+ if ($global->{infile} ne "-") {
+ open $FOUT, '>', $global->{infile}
+ or die "Error: Cannot create `" .$global->{infile}. "': $!\n";
+ push @fds, $FOUT;
+ delete $global->{infile};
+ }
-#
-# Open input and output files
-#
-my ($FIN, $FOUT);
-if (defined $config{infile} && $config{infile} ne "-") {
- open $FIN, '<', $config{infile}
- or die "Error: Cannot read `" .$config{infile}. "': $!\n";
-} else {
- delete $config{infile};
- $FIN = *STDIN;
+ foreach my $config (@inputs) {
+ foreach (keys %$global) {
+ $config->{$_} = $global->{$_} unless exists $config->{$_};
+ }
+ }
}
-
-
-if (defined $config{outfile} && $config{outfile} ne "-") {
- open $FOUT, '>', $config{outfile}
- or die "Error: Cannot create `" .$config{outfile}. "': $!\n";
-} else {
- $FOUT = *STDOUT;
+elsif ($#inputs < 0) {
+ @inputs = ({ infile => '-' });
}
+$FOUT = *STDOUT unless defined $FOUT;
-*LOG = *STDERR;
-
-
-
-# Auxiliary files, to remove
-my @auxfiles;
-
-# Pids, to waid for
-my @pids;
-
-# Return value
-my $return = 0;
try {
- my $FD = &pdftops($FIN, \%config);
-
- $FD = &psselect($FD, \%config) if exists $config{select};
-
- my ($FD2, $bbox) = &psbbox($FD, \%config);
-
- $FD2 = &psbook($FD2, \%config) if exists $config{book};
-
- my ($FD3, $landscape, $rotate) = &psnup ($FD2, $bbox, \%config);
-
- &pswrite ($FD3, $FOUT, $landscape, $rotate, \%config);
+ my @ins;
+ my ($landscape, $rotate);
+ foreach my $config (@inputs) {
+ #
+ # Default values
+ #
+
+ $config->{nup} = 1 unless exists $config->{nup};
+
+ # Default margin & border
+ $config->{margin} = 0 unless exists $config->{margin};
+ unless (exists $config->{border}) {
+ if (exists $config->{crop}) {
+ $config->{border} = '1cm';
+ } else {
+ $config->{border} = 0;
+ }
+ }
+
+ # Default output papersize
+ unless (exists $out{width} and exists $out{height}) {
+ my ($w,$h) = &papersize ("a4");
+ $out{width} = $w;
+ $out{height} = $h;
+ }
+
+ # Default unit: PostScript point
+ map { $config->{$_} = &topoints($config->{$_}) if exists $config->{$_} }
+ qw /inwidth inheight margin border/;
+ map { $out{$_} = &topoints($out{$_}) if exists $out{$_} }
+ qw /width height/;
+ die "Error: Margins are too big.\n"
+ if $out{width} <= 2*$config->{margin}
+ or $out{height} <= $config->{margin};
+
+
+
+ #
+ # Check options
+ #
+ die "Error: Bad page range: `" .$config->{select}. "'.\n" if
+ defined $config->{select} &&
+ not $config->{select} =~ /^(_?\d*-?_?\d*,)*_?\d*-?_?\d*$/;
+
+ die "Error: Bad nup: `" .$config->{nup}. "'.\n" if
+ defined $config->{nup} &&
+ not ($config->{nup} =~ /^\d+$/ && $config->{nup} > 0);
+
+ #
+ # Open input file
+ #
+ my $FIN;
+ if (defined $config->{infile} && $config->{infile} ne "-") {
+ open $FIN, '<', $config->{infile}
+ or die "Error: Cannot read `" .$config->{infile}. "': $!\n";
+ push @fds, $FIN;
+ } else {
+ $FIN = *STDIN;
+ }
+
+
+ #
+ # Process the file
+ #
+ my $FD = &pdftops($FIN, $config);
+
+ $FD = &psselect($FD, $config) if exists $config->{select};
+
+ my ($FD2, $bbox) = &psbbox($FD, $config);
+
+ $FD2 = &psbook($FD2, $config) if exists $config->{book};
+
+ my $FD3;
+ ($FD3, $landscape, $rotate) = &psnup ($FD2, $bbox, $config);
+ push @ins, $FD3;
+ }
+ &write ($landscape, $rotate, @ins);
}
-
+
catch Error with {
# Print the error message
@@ -407,22 +449,20 @@ finally {
} @pids;
# Close opened file handles
- map { close $_ or die "Cannot close: $!" }
- ( $FIN, $FOUT );
+ map { close $_ or die "Cannot close: $!" } @fds;
# Delete auxiliary files
unlink @auxfiles;
exit $return;
};
-
+
# Useless, but Perl doesn't see that this filehandle is used more than
# once
close IN; # automatically closed by `open3'
close OUT;
-
# =========================================================
@@ -452,7 +492,8 @@ sub pdftops {
} elsif (defined $_ && $_ =~ /^%PDF/) {
$filetype = "PDF";
} else {
- die "Error: Input file has an unknown magic number.\n";
+ die "Error: Input file `" .$config->{infile}.
+ "' has an unknown magic number.\n";
}
unless (my $pid = fork) {
@@ -476,14 +517,18 @@ sub pdftops {
#
# Conversion from PDF to PS
#
- unless (defined $infile) {
+ my $infile;
+ if (defined $config->{infile} && $config->{infile} ne '-') {
+ $infile = $config->{infile};
+ }
+ else {
# Need to copy the whole input to an auxiliary file, since
# conversion from PDF to PS requires random access to the data
$infile = catfile( tmpdir(), "pdftool-stdin-$$.". lc $filetype );
open my $AUX, '>', $infile
- or die "Cannot write into `" .$infile. "': $!\n";
+ or die "Error: Cannot write into `" .$infile. "': $!\n";
push @auxfiles, $infile;
# cat > $infile
@@ -578,7 +623,7 @@ sub psbbox {
# The input is not seekable: have to create a seekable auxiliary
# file
- my $auxfile = catfile( tmpdir(), "pdftool-stdin-$$.ps" );
+ my $auxfile = catfile( tmpdir(), "pdftool-in$config->{index}-$$.ps" );
open my $AUX, '>', $auxfile
or die "Cannot write into `" .$auxfile. "': $!\n";
@@ -705,15 +750,14 @@ sub psnup {
my ($OUT, $landscape, $rotate);
if ((($bbox->[2]-$bbox->[0] > $bbox->[3]-$bbox->[1])
- and not ($config->{outwidth}-2*$config->{margin} >
- $config->{outheight}-2*$config->{margin}))
+ and not ($out{width}-2*$config->{margin} >
+ $out{height}-2*$config->{margin}))
or
- (not (exists $config->{autorotate})
+ (not (exists $out{autorotate})
and not ($bbox->[2]-$bbox->[0] > $bbox->[3]-$bbox->[1])
- and ($config->{outwidth}-2*$config->{margin} >
- $config->{outheight}-2*$config->{margin}))) {
- ($config->{outheight}, $config->{outwidth}) =
- ($config->{outwidth}, $config->{outheight});
+ and ($out{width}-2*$config->{margin} >
+ $out{height}-2*$config->{margin}))) {
+ ($out{height}, $out{width}) = ($out{width}, $out{height});
$landscape = 1;
}
@@ -723,8 +767,8 @@ sub psnup {
# combinations of width*height in both normal and rotated form, and
# minimise the wasted space.
#
- my ($ow,$oh) = ($config->{outwidth}-2*$config->{margin},
- $config->{outheight}-2*$config->{margin});
+ my ($ow,$oh) = ($out{width}-2*$config->{margin},
+ $out{height}-2*$config->{margin});
my ($iw, $ih) = ($bbox->[2]-$bbox->[0], $bbox->[3]-$bbox->[1]);
my ($horiz, $vert, $scale, $hshift, $vshift);
@@ -821,7 +865,7 @@ sub psnup {
($ow,$oh) = ($oh,$ow) if $rotate%2;
my $pagespecs;
- if (not (exists $config->{autorotate}) || $ow < $oh) {
+ if (not (exists $out{autorotate}) || $ow < $oh) {
$pagespecs = $config->{nup} . ':' . join ('+', @ospecs);
} else {
$pagespecs = 2*$config->{nup} . ':' . join ('+', @ospecs)
@@ -841,25 +885,51 @@ sub psnup {
#
-# Final file: setup the correct orientation/page size, and convert to
-# PDF if necessary
+# Final file: setup the correct orientation/page size
#
-sub pswrite {
- my ($IN, $OUT, $landscape, $rotate, $config) = @_;
+sub write {
+ my ($landscape, $rotate, @ins) = @_;
+ my $IN;
+
+ my @args;
+ my $index = 1;
+ if ($#ins > 0) {
+ # We have to create temporary files for every PostScript file
+ foreach my $FIN (@ins) {
+ my $auxfile = catfile( tmpdir(),
+ "pdftool-gsin$index-$$.ps" );
+
+ open my $AUX, '>', $auxfile
+ or die "Error: Cannot write into `" .$auxfile. "': $!\n";
+ push @auxfiles, $auxfile;
+ while (<$FIN>) {
+ print $AUX ($_) or die "Cannot print: $!";
+ }
+ push @args, '-f', $auxfile;
+ close $FIN or die "Cannot close: $!";
+ $index++;
+ }
+ open $IN, '<', File::Spec->devnull() or
+ die "Cannot open the null device: $!"
+ }
+ else {
+ @args = ('-f', '-');
+ $IN = $ins[0];
+ }
- my ($ow, $oh) = ($config->{outwidth}, $config->{outheight});
+ my ($ow, $oh) = ($out{width}, $out{height});
($ow,$oh) = ($oh,$ow) if $rotate%2;
my $pagedevice;
- if (not (exists $config->{autorotate}) || $oh < $oh || $landscape) {
- $rotate = ($rotate+1)%4 if (exists $config->{autorotate}) and $oh < $ow;
- $pagedevice = "/Orientation $rotate /PageSize [$config->{outwidth} $config->{outheight}]";
+ if (not (exists $out{autorotate}) || $oh < $oh || $landscape) {
+ $rotate = ($rotate+1)%4 if (exists $out{autorotate}) and $oh < $ow;
+ $pagedevice = "/Orientation $rotate /PageSize [$out{width} $out{height}]";
} else {
- $pagedevice = "/PageSize [$config->{outwidth} $config->{outheight}]";
+ $pagedevice = "/PageSize [$out{width} $out{height}]";
}
my $device = '-sDEVICE=pdfwrite';
- $device = '-sDEVICE=ps2write' if exists $config->{psout};
+ $device = '-sDEVICE=ps2write' if exists $out{ps};
my @cmd = (@gs, '-dQUIET', '-dBATCH', '-dNOPAUSE', $device,
'-dAutoRotatePages=/None',
'-dDetectDuplicateImages=false',
@@ -871,9 +941,9 @@ sub pswrite {
'-dMonoImageFilter=/FlateEncode',
'-sOutputFile=%stdout%',
'-c', "<< $pagedevice >> setpagedevice",
- '-f', '-');
+ @args);
- (*IN,*OUT) = ($IN,$OUT);
+ (*IN,*OUT) = ($IN,$FOUT);
my $pid = open3 '<&IN', '>&OUT', '>&LOG', @cmd;
push @pids, [$pid, @cmd];
}