diff options
-rwxr-xr-x | pdftool.pl | 409 |
1 files changed, 292 insertions, 117 deletions
@@ -1,98 +1,178 @@ -#! /usr/bin/perl +#! /usr/bin/perl -w -use Getopt::Long; + +use Getopt::Long qw(:config posix_default no_ignore_case bundling); +use Pod::Usage; use IPC::Open2; -use warnings; +use POSIX qw(floor); use strict; -Getopt::Long::Configure ("bundling"); -# Give an array if the command has least one argument. -my @pdf2ps = 'pdftops'; -my @pscrop = ('psnup2.pl', '-s1', '-l1'); -my @psresize = 'psresize'; -my @psnup = 'psnup'; -my @psbook = 'psbook'; -my @ps2pdf = 'ps2pdf'; +=head1 NAME + +pdftool.pl - a PDF swiss army knife + +=head1 SYNOPSIS + +B<pdftool.pl> [-s I<pages>] [-p I<paper>] [-m I<margin>] [-c] [-b] +[-n I<nup>] [-q] [I<infile> [I<outfile>]] + +=head1 DESCRIPTION + +I<PDFTool> + +=head1 OPTIONS + +=over 8 + +=item B<-s, --select> + +=item B<-p, --paper> + +=item B<-m, --margin> + +=item B<-c, --crop> + +=item B<-b, --book> + +=item B<-n, --nup> + +=item B<-q, --quiet> + +I<PDFTool> normally prints the page numbers of the pages output; this +option suppresses this. + +=item B<--help> + +Display a brief help. + +=item B<--man> + +Display the manual page. + +=back + +=head1 REQUIRE + +Requires psutils installed and available in the command line +http://www.tardis.ed.ac.uk/~ajcd/psutils/ + +=head1 AUTHOR + +Public domain, (c) Guilhem Moulin. + +=head1 VERSION + +Version: 0.1, 25 September 2010 + +=cut + + my $tmpdir = '/tmp'; +# +# Options & arguments +# -my $nup = 1; -my $margin = 1; -#TODO: units +my $select; +my $paper; +my $margin; my $crop; my $book; -my $papersize; - -GetOptions( "nup|n=i" => \$nup, - "1" => sub { $nup = 1 }, - "2" => sub { $nup = 2 }, - "4" => sub { $nup = 4 }, - "8" => sub { $nup = 8 }, - "crop|c" => \$crop, - "book|b" => \$book, - "papersize|p=s" => \$papersize, - "margin|m=s" => \$margin ); - -die "I can handle only one file at the same time :P" - if $#ARGV > 0; - -die "How should I put a non-positive number of pages per sheet?" - if $nup <= 0; - -unless (defined $papersize) { - $papersize = `paperconf` or die "Can't guess papersize"; - chomp $papersize; +my $nup = 1; +my $quiet; +my $man; +my $help; + +# TODO: select pages +# TODO: choose the output type +GetOptions( "select|s=s" => \$select, + "paper|p=s" => \$paper, + "margin|m=s" => \$margin, + "crop|c" => \$crop, + "book|b" => \$book, + "nup|n=i" => \$nup, + "1" => sub { $nup = 1 }, + "2" => sub { $nup = 2 }, + "3" => sub { $nup = 3 }, + "4" => sub { $nup = 4 }, + "5" => sub { $nup = 5 }, + "6" => sub { $nup = 6 }, + "7" => sub { $nup = 7 }, + "8" => sub { $nup = 8 }, + "9" => sub { $nup = 9 }, + "q|quiet" => \$quiet, + "help" => \$help, + "man" => \$man ) + or pod2usage(2); + +pod2usage(1) if (defined $help or $#ARGV > 1); +pod2usage(-exitstatus => 0, -verbose => 2) if defined $man; + +# Input and output files +my ($infile, $outfile) = @ARGV; + + + +# +# Default values +# + +# Default output papersize +$paper = "a4" unless defined $paper; + + +# Default margin +unless (defined $margin) { + $margin = 0; + $margin = "1cm" if defined $crop; } -for (my $n = $nup; $n > 1; $n /= 2) { - die "nup should be a power of two" - unless $n % 2 == 0; -} +# Default unit: PostScript point +&topoints (\$margin); -my $filename = $ARGV[0]; -$filename = "-" unless defined $filename; -my $filename2 = $filename; -$filename2 = "(stdin)" if $filename eq "-"; +# Inner and outer margins +my ($mresize, $mnup) = (0,0); +if ($nup > 1 && not defined $book) { + $mresize = $margin/2; + $mnup = $mresize; +} else { + $mresize = $margin; +} -my ($mresize, $mcrop, $mnup) = (0,0,0); -if (defined $crop) { - $mresize = 0; - if (not defined $book && $nup > 1) { - $mcrop = $margin/2; - $mnup = $mcrop; - } else { - $mcrop = $margin; - $mnup = 0; - } +# Open input and output files +my $infile_display; +if (defined $infile && $infile ne "-") { + open FIN, '<', "$infile" or die "Can't read `$infile': $!"; + $infile_display = $infile; } else { - $mcrop = 0; - if (not defined $book && $nup > 1) { - $mresize = $margin/2; - $mnup = $mresize; - } else { - $mresize = $margin; - $mnup = 0; - } + undef $infile; + *FIN = *STDIN; + $infile_display = "(stdin)"; } +if (defined $outfile && $outfile ne "-") { + open FOUT, '>', "$outfile" or die "Can't create `$outfile': $!"; +} else { + *FOUT = *STDOUT; +} -open(FILE, $filename) - or die "Can't read `$filename'"; - +# +# Detect filetype +# my $filetype; -my $fstline = <FILE>; -if (defined $fstline && $fstline =~ /%!PS.*/) { +my $fstline = <FIN>; +if (defined $fstline && $fstline =~ /^%!PS.*/) { $filetype = "PS"; -} elsif (defined $fstline && $fstline =~ /%PDF.*/) { +} elsif (defined $fstline && $fstline =~ /^%PDF.*/) { $filetype = "PDF"; } else { - die "Can't recognize the type of `$filename2'"; + die "Can't recognize the type of `$infile_display'"; } # Auxiliary files, to remove @@ -101,80 +181,127 @@ my @auxfiles; # Auxiliary file descriptors, to close my @auxfds; - # Pids, to waid for my @pids; my $pid; -if ($filename eq "-") { - # Need to copy the whole input to an auxiliary file, since - # conversion from pdf to ps requires random access to the data - - # TODO: only for pdfs - $filename = "$tmpdir/pdftool-stdin-" . int(rand 2**16) . lc ".$filetype"; - open AUXFD, '>', "$filename" - or die "Can't read write into `$filename'"; - push @auxfiles, "$filename"; - - # cat > $filename - seek STDIN, 0, 0; - while (<FILE>) { - print AUXFD $_; + + +# +# Conversion from PDF to PS, if necessary +# +my @cmd; +if ($filetype eq "PDF") { + unless (defined $infile) { + # Need to copy the whole input to an auxiliary file, since + # conversion from PDF to PS requires random access to the data + + $infile = "$tmpdir/pdftool-stdin-" . + int(rand 2**16) . lc ".$filetype"; + + open FIN2, '>', "$infile" + or die "Can't write into `$infile': $!"; + push @auxfiles, "$infile"; + + # cat > $filename + # TODO: useless seek + seek FIN, 0, 0 or die $!; + while (<FIN>) { + print FIN2 $_ or die "Can't print: $!"; + } + close FIN2; } - close AUXFD; + close FIN; + + # Convert to PS + @cmd = ('pdftops', "$infile", '-'); + push @cmd, '-q' if defined $quiet; + $pid = open *PSIN, "-|", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; + push @pids, $pid; +} else { + # TODO: useless seek + seek FIN, 0, 0 or die $!; + *PSIN = *FIN; } -close FILE; -# Convert to ps, or read the input file -if ($filetype eq "PDF") { - $pid = open *INPS, "-|", @pdf2ps, "$filename", '-' - or die "Error with `@pdf2ps $filename -'"; +# +# Select, if necessary +# +# TODO: preselection, during the conversion from pdf? +if (defined $select) { + @cmd = ('psselect', "-p$select"); + push @cmd, '-q' if defined $quiet; + $pid = open2 *PSSELECT, "<&PSIN", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; push @pids, $pid; } else { - open *INPS, '<', "$filename" - or die "Can't open `$filename'"; + *PSSELECT = *PSIN; } -push @auxfds, fileno INPS; -# Resize file to our papersize -$pid = open2 *PSRESIZE, "<&INPS", @psresize, "-p$papersize" - or die "Error with `@psresize -p$papersize'"; -# Note: open2 closes the filehandles for us :) + +# +# Resize file to our paper +# +@cmd = ('./psresize2.pl', "-p$paper", "-m$mresize"); +push @cmd, "-c" if defined $crop; +push @cmd, '-q' if defined $quiet; + +$pid = open2 *PSRESIZE, "<&PSSELECT", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; push @pids, $pid; +# Note: open2 closes the filehandles for us :) + -# Pscrop -if (defined $crop) { - $pid = open2 *PSCROP, "<&PSRESIZE", @pscrop, "-p$papersize", "-m${mnup}" - or die "Error with `@pscrop -p$papersize -m${mnup}cm'"; +# +# PSBook +# +if (defined $book) { + @cmd = ('psbook'); + push @cmd, '-q' if defined $quiet; + $pid = open2 *PSBOOK, "<&PSRESIZE", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; push @pids, $pid; } else { - *PSCROP = *PSRESIZE; + *PSBOOK = *PSRESIZE; } -# Psbook -if (defined $book) { - $pid = open2 *PSBOOK, "<&PSCROP", "@psbook" - or die "Error with `@psbook"; + + +# +# PSNup +# +if ($nup > 1) { + @cmd = ('psnup', "-p$paper", "-m$mnup", "-$nup"); + push @cmd, '-q' if defined $quiet; + $pid = open2 *PSOUT, "<&PSBOOK", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; push @pids, $pid; } else { - *PSBOOK = *PSCROP; + *PSOUT = *PSBOOK; } -# Psnup -# TODO: sometimes unecessary -$pid = open2 *PSNUP, "<&PSBOOK", @psnup, "-p$papersize", "-m${mnup}cm", "-$nup" - or die "Error with `@psnup -p$papersize -m${mnup}cm -$nup'"; -push @pids, $pid; -# Convert back to pdf -# TODO: not always stdout -# TODO: return same type as input -$pid = open2 ">&STDOUT", "<&PSNUP", @ps2pdf, "-sPAPERSIZE=$papersize", '-', '-' - or die "Error with `@ps2pdf -sPAPERSIZE=$papersize - -'"; -push @pids, $pid; + +# +# Final file +# +if ($filetype eq "PDF") { + # Convert back to PDF + + @cmd = ('ps2pdf', "-dEmbedAllFonts=true", "-sPAPERSIZE=$paper", '-', '-'); + $pid = open2 ">&FOUT", "<&PSOUT", @cmd + or die "Can't run: `" . &printcmd (@cmd) . "'"; + push @pids, $pid; +} else { + # cat > FOUT + while (<PSOUT>) { + print FOUT $_ or die "Can't print: $!"; + } +} # Avoid zombies @@ -183,7 +310,55 @@ map {waitpid $_, 0} @pids; # Close auxiliary filehandles map {close $_} @auxfds; +close FIN; +close FOUT; # Delete auxiliary files unlink @auxfiles; + + + + +# ========================================================= + +# +# In-place convert the given length to PostScript points +# +sub topoints { + my $l = $_; + return unless defined $$l; + + $$l =~ /^([+-]?\d*\.?\d+)(\w*)$/ or die "Unable to parse `$$l'"; + + my $r = $1; + if ($2 eq "" or $2 eq "pt") { + # nothing + } elsif ($2 eq "in") { + $r *= 72; + } elsif ($2 eq "cm") { + $r *= 72/2.54; + } elsif ($2 eq "mm") { + $r *= 72/25.4; + } else { + die "Unknown unit: `$2'"; + } + $$l = floor ($r + .5); +} + + +# +# Print a command just like you'd do in a shell +# +sub printcmd { + my @cmd; + + for (@_) { + my $s = $_; + $s =~ s/"/\\"/; + $s = "\"$s\"" if $s =~ /[ ()';#{}*?~&|`]/; + push @cmd, $s; + } + + join ' ', @cmd; +} |