diff options
-rwxr-xr-x | pdftool.pl | 386 |
1 files changed, 228 insertions, 158 deletions
@@ -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]; } |