summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xpdftool.pl355
1 files changed, 195 insertions, 160 deletions
diff --git a/pdftool.pl b/pdftool.pl
index 4c4b8ba..42e792d 100755
--- a/pdftool.pl
+++ b/pdftool.pl
@@ -18,7 +18,8 @@ pdftool.pl - a PDF swiss army knife
B<pdftool.pl> [-s I<pages>] [-w I<width> ] [-h I<heigth>] [-p I<paper>]
[-W I<width>] [-H I<heigth>] [-P I<paper>] [-p I<paper>] [-m I<margin>]
-[-c] [-b] [-n I<nup>] [-q] [I<infile> [I<outfile>]]
+[-b I<border>] [-c] [--book] [--column] [-n I<nup>] [-q]
+[I<infile> [I<outfile>]]
=head1 DESCRIPTION
@@ -30,9 +31,9 @@ If no input file is given, or if a single hyphen-minus (B<->) is given as
file name, I<PDFTool> will read the PDF or PostScript data from the standard
input. In that case, and if the input data is in PDF format, an
auxiliary file will be created (since the conversion from PDF to PS
-requires random access to the data), and removed afterwards.
-An other auxiliary file may be created by I<PSResize2>, see its man page
-for details.
+requires random access to the data), and removed afterwards. Also, if the crop
+option (B<-c>) is set, an auxiliary file will be created, and removed
+afterwards.
If no output file is given, or if a single hyphen-minus (B<->) is given as
file name, I<PDFTool> will send the data (of the same type as the input) to
@@ -46,11 +47,9 @@ The document will be treated as follows:
=item Selection of the page range,
-=item Resizing to the given papersize, adding a margin, and croping,
-
=item Rearranging pages for printing books or booklets,
-=item Putting several pages per sheets,
+=item Putting multiple pages per sheets,
=item Conversion from PS to PDF (if necessary).
@@ -88,16 +87,20 @@ B<mm>. The default unit is B<pt>.
Specify the paper size of the output file, as an alternative to B<-w>
and B<-h>. Can be set to B<a0>, B<a1>, B<a2>, B<a3>, B<a4>, B<a5>, B<b5>,
-B<letter>, B<legal>, B<tabloid>, B<statement>, B<executive>, B<folio>,
-B<quarto>, or B<10x14>. The default output paper size is B<a4>.
+B<letter>, B<legal>, B<ledger>, B<tabloid>, B<statement>, B<executive>,
+B<folio>, B<quarto>, or B<10x14>. The default output paper size is B<a4>.
=item B<-W, --Width>
-Same as the option B<-w>, but for the input file.
+Same as the option B<-w>, but for the input file. This option is useless if
+the crop option (B<-c>) is set.
+
=item B<-H, --Height>
-Same as the option B<-h>, but for the input file.
+Same as the option B<-h>, but for the input file. This option is useless if
+the crop option (B<-c>) is set.
+
=item B<-P, --Paper>
@@ -106,38 +109,41 @@ I<PDFTool> will try to guess this value from the header of the file,
and fail if the information is missing. This option is useless if the
crop option (B<-c>) is set.
-=item B<-m, --margin>
+=item B<-b, --border>
+
+Add a margin around each logical page on a sheet. Possible units are B<pt>,
+B<in>, B<cm> and B<mm>. The default unit is B<pt>. The default border is
+B<1cm> if the crop option (B<-c>) is set, and B<0> otherwise.
-Add a margin to the output file. Possible units are B<pt>, B<in>, B<cm>
-and B<mm>. The default unit is B<pt>. The default margin is B<1cm> if the
-crop option (B<-c>) is set, and B<0> otherwise.
+=item B<-m, --margin>
-If the nup option (B<-n>I<nup>) is set to more than one page per sheet,
-and if the booklet option (B<-b>) is not set, the margin will be the
-same beetween two logical pages, and between a logical page and the
-sheet border.
-Otherwise, the margin beetween two logical pages will be twice bigger
-the one between a logical page and the sheet border.
+Add a margin around the whole page. Possible units are B<pt>, B<in>, B<cm>
+and B<mm>. The default unit is B<pt>. The default margin is B<0>.
=item B<-c, --crop>
-If this option is set, I<PSResize2> will interpret the PostScript code to
-calculate the maximal effective bounding box. This operation may be quite
-demanding for the CPU.
+If this option is set, the PostScript code will interpreted to calculate the
+maximal effective bounding box. This operation may be quite demanding for the
+CPU.
-=item B<-b, --book>
+=item B<--book>
Rearrange pages for printing books or booklets.
=item B<-n, --nup>
-Puts multiple logical pages (has to be a power of two) onto each physical
-sheet of paper. The inner margin might be same as the outer one (depending
-on the booklet option B<-b>), see B<-m> for details.
+Puts multiple logical pages onto each physical sheet of paper. The inner
+margin might be same as the outer one (depending on the booklet option
+B<-b>), see B<-m> for details.
If I<nup> is less than 10, the option B<->I<nup> may be used as an
alternative.
+=item B<--column>
+
+Changes the order to `column-major', where successive pages are placed in
+columns down the paper.
+
=item B<-q, --quiet>
I<PDFTool> normally prints the page numbers of the pages output; this
@@ -160,29 +166,27 @@ Display the manual page.
=head1 EXAMPLES
The following comand can be used to remotely crop a PDF file, convert it
-to A4 paper, and rearrange the pages for printing a booklet:
+to A4 paper, and rearrange the pages to print a booklet:
-ssh remote pdftool.pl -cpA4 -b2 < in.pdf > out.pdf
+ssh remote pdftool.pl -cpA4 --book -2 -b2cm -m-1cm < in.pdf > out.pdf
=head1 REQUIRE
Requires PSUtils installed and available in the command line
http://www.tardis.ed.ac.uk/~ajcd/psutils/.
-PSResize2 is also required, mostly for the crop option B<-c>.
-
=head1 AUTHOR
Public domain, (c) Guilhem Moulin.
=head1 VERSION
-Version: 0.2, 12 December 2010
+Version: 0.3, 27 December 2010
=cut
# TODO: inline it in the header
-$main::VERSION = "0.2, 12 December 2010";
+$main::VERSION = "0.3, 27 December 2010";
@@ -194,15 +198,14 @@ my $tmpdir = '/tmp';
my $select;
my ($outwidth,$outheight, $inwidth,$inheight);
-my $margin;
+my ($margin, $border);
my $crop;
my $book;
my $nup = 1;
-my $rotdir = 'L';
+my $column;
my $quiet;
my $man;
-# TODO: choose the output type
GetOptions( "select|s=s" => \$select,
"w|width=s" => \$outwidth,
"h|height=s" => \$outheight,
@@ -211,8 +214,9 @@ GetOptions( "select|s=s" => \$select,
"H|Height=s" => \$inheight,
"P|Paper=s" => sub { &papersize ($_[1],\$inwidth,\$inheight) },
"margin|m=s" => \$margin,
+ "border|b=s" => \$border,
"crop|c" => \$crop,
- "book|b" => \$book,
+ "book" => \$book,
"nup|n=i" => \$nup,
"1" => sub { $nup = 1 },
"2" => sub { $nup = 2 },
@@ -223,6 +227,7 @@ GetOptions( "select|s=s" => \$select,
"7" => sub { $nup = 7 },
"8" => sub { $nup = 8 },
"9" => sub { $nup = 9 },
+ "column" => \$column,
"q|quiet" => \$quiet,
"man" => \$man )
or pod2usage(2);
@@ -239,10 +244,14 @@ my ($infile, $outfile) = @ARGV;
# Default values
#
-# Default margin
-unless (defined $margin) {
- $margin = 0;
- $margin = "1cm" if defined $crop;
+# Default margin & border
+$margin = 0 unless defined $margin;
+unless (defined $border) {
+ if (defined $crop) {
+ $border = '1cm';
+ } else {
+ $border = 0;
+ }
}
# Default output papersize
@@ -252,23 +261,8 @@ unless (defined $margin) {
# Default unit: PostScript point
map {&topoints ($_)} ( \$outwidth, \$outheight,
\$inwidth, \$inheight,
- \$margin );
-
-# Inner and outer margins
-my ($mresize, $mnup) = (0,0);
-if ($nup > 1 && not defined $book) {
- $mresize = $margin/2;
- $mnup = $mresize;
-} else {
- $mresize = $margin;
-}
-
-# TODO: would be nice to generalize $nup to any integer that psnup would
-# accept.
-my $i = -1;
-while (1<<++$i < $nup) {};
-die "nup has to be a power of two" if 1<<$i > $nup;
-$nup = $i;
+ \$margin, \$border );
+die "Margins are too big" if $outwidth <= $margin*2 or $outheight <= $margin*2;
# Open input and output files
@@ -531,29 +525,6 @@ if (defined $crop) {
#
-# Calculate PStoPS specification
-#
-if (($outwidth > $bbox[3] - $bbox[1]) xor ($bbox[2] - $bbox[0] > $outheight)) {
- ($outwidth, $outheight) = ($outheight, $outwidth);
-}
-my ($x0,$x1) = &calculate_coordinates($outwidth , $margin);
-my ($y0,$y1) = &calculate_coordinates($outheight, $margin);
-
-my $rotation;
-my $spec = 0 . &calc_pstops_page(@bbox, $x0, $y0, $x1, $y1);
-
-
-#
-# Run the program and filter the output
-#
-@cmd = ('pstops', "-w$outwidth", "-h$outheight", "$spec");
-push @cmd, '-q' if defined $quiet;
-my $pid = open3 "<&IN", *OUT, ">&LOG", @cmd;
-open *IN, "<&OUT" or die "Can't dup: $!";
-
-
-
-#
# PSBook
#
if (defined $book) {
@@ -569,22 +540,25 @@ if (defined $book) {
#
# PSNup
-#
-if (1<<$nup > 1) {
- my ($inwidth, $inheight) = ($outwidth, $outheight);
- if ($nup % 2) {
- ($outwidth, $outheight) = ($outheight, $outwidth);
- }
- $nup = 1<<$nup;
- @cmd = ('psnup', "-W$inwidth", "-H$inheight",
- "-w$outwidth", "-h$outheight",
- "-m$mnup", "-$nup");
- push @cmd, '-q' if defined $quiet;
+#
+($outheight, $outwidth) = ($outwidth, $outheight)
+ if (($bbox[2]-$bbox[0] > $bbox[3]-$bbox[1])
+ xor ($outwidth-2*$margin > $outheight-2*$margin));
- my $pid = open2 *OUT, "<&IN", @cmd;
- open *IN, "<&OUT" or die "Can't dup: $!";
- push @pids, [$pid, @cmd];
-}
+my ($horiz, $vert, $rotate, $scale, $hshift, $vshift)
+ = &calc_layout ($nup, $border, \@bbox,
+ $outwidth-2*$margin, $outheight-2*$margin);
+
+my @specs = &calc_specs ($horiz, $vert, $rotate, $scale,
+ [$outwidth-2*$margin, $outheight-2*$margin,
+ $hshift, $vshift]);
+
+my $pagespecs = "$nup:" . join ('+', @specs);
+@cmd = ('pstops', '-w', $bbox[2], '-h', $bbox[3], $pagespecs);
+push @cmd, '-q' if defined $quiet;
+my $pid = open2 *OUT, "<&IN", @cmd;
+open *IN, "<&OUT" or die "Can't dup: $!";
+push @pids, [$pid, @cmd];
@@ -593,12 +567,13 @@ if (1<<$nup > 1) {
#
@cmd = ('gs', "-sDEVICE=pdfwrite", "-sOutputFile=%stdout%", "-dBATCH",
"-dNOPAUSE", "-dAutoRotatePages=/None",
- "-c", "<< /Orientation 0 /PageSize [$outwidth $outheight] >> setpagedevice",
+ "-c", "<< /Orientation $rotate /PageSize [$outwidth $outheight] >> setpagedevice",
"-f", "-");
$pid = open2 ">&FOUT", "<&IN", @cmd;
push @pids, [$pid, @cmd];
+
# Avoid zombies
map { my ($pid, @cmd) = @$_;
# print STDERR "PID: ", $pid, " Cmd: `", &printcmd (@cmd), "'";
@@ -626,71 +601,105 @@ close GSIN;
# =========================================================
#
-# Calculate an item of the pstops specification
+# Finding the best layout is an optimisation problem. We try all of the
+# combinations of width*height in both normal and rotated form, and
+# minimise the wasted space.
#
-sub calc_pstops_page {
- my ($fx0, $fy0, $fx1, $fy1,
- $tx0, $ty0, $tx1, $ty1) = @_;
-
- # From and to width / height
- my ($wf, $hf) = ($fx1 - $fx0, $fy1 - $fy0);
- my ($wt, $ht) = ($tx1 - $tx0, $ty1 - $ty0);
+sub calc_layout {
+ my ($nup, $border, $bbox, $outwidth, $outheight) = @_;
- # Check if rotation required (in our case, should always be 0)
- my $rotation = (($wf > $hf) xor ($wt > $ht));
-
- # Scale factor width / height
- my ($sw, $sh);
- if ($rotation) {
- ($sw, $sh) = ($ht / $wf, $wt / $hf);
- } else {
- ($sw, $sh) = ($wt / $wf, $ht / $hf);
- }
+ my ($inwidth, $inheight) = ($bbox[2]-$bbox[0], $bbox[3]-$bbox[1]);
+ my ($horiz, $vert, $rotate, $scale, $hshift, $vshift);
+
+ my $tolerance = 100000; # layout tolerance
+ my $best = $tolerance;
- # We take the smallest scale
- my $scale = ($sw > $sh) ? $sh : $sw;
+ for (my $hor = 1; $hor; $hor = &nextdiv($hor, $nup)) {
+ my $ver = $nup / $hor;
+ # try normal orientation first
+ my $scl = &min ($outheight/($inheight*$ver), $outwidth/($inwidth*$hor));
- # Calculate the centers of the boxes
- my ($cxf, $cyf) = ( .5 * ($fx0 + $fx1), .5 * ($fy0 + $fy1) );
- my ($cxt, $cyt) = ( .5 * ($tx0 + $tx1), .5 * ($ty0 + $ty1) );
-
- # First, PStoPs scales, then rotates, then moves
- ($cxf, $cyf) = ($cxf * $scale, $cyf * $scale);
- if ($rotation) {
- if ($rotdir eq 'L') {
- ($cxf, $cyf) = (-$cyf, $cxf);
- } else {
- ($cxf, $cyf) = ($cyf, -$cxf);
+ my $optim = ($outwidth-$scl*$inwidth*$hor)*($outwidth-$scl*$inwidth*$hor)
+ + ($outheight-$scl*$inheight*$ver)*($outheight-$scl*$inheight*$ver);
+ if ($optim < $best) {
+ $best = $optim;
+ # recalculate scale to allow for internal borders
+ $scale = &min (($outheight-2*$border*$ver)/($inheight*$ver),
+ ($outwidth-2*$border*$hor)/($inwidth*$hor));
+ $hshift = ($outwidth/$hor - ($bbox[2]+$bbox[0])*$scale)/2;
+ $vshift = ($outheight/$ver - ($bbox[3]+$bbox[1])*$scale)/2;
+ ($horiz, $vert) = ($hor, $ver);
+ $rotate = 0;
+ }
+ # try rotated orientation
+ $scl = &min ($outheight/($inwidth*$hor), $outwidth/($inheight*$ver));
+ $optim = ($outheight-$scl*$inwidth*$hor)*($outheight-$scl*$inwidth*$hor)
+ + ($outwidth-$scl*$inheight*$ver)*($outwidth-$scl*$inheight*$ver);
+ if ($optim < $best) {
+ $best = $optim;
+ # recalculate scale to allow for internal borders
+ $scale = &min (($outheight-2*$border*$hor)/($inwidth*$hor),
+ ($outwidth-2*$border*$ver)/($inheight*$ver));
+ $hshift = ($outwidth/$ver - ($bbox[3]+$bbox[1])*$scale)/2;
+ $vshift = ($outheight/$hor - ($bbox[2]+$bbox[0])*$scale)/2;
+ ($horiz, $vert) = ($ver, $hor);
+ $rotate = 3;
}
- } else {
- $rotdir = '';
}
- my ($movex, $movey) = ($cxt - $cxf, $cyt - $cyf);
+
+ # fail if nothing better than worst tolerance was found
+ die "Can't find acceptable layout for $nup-up" if $best == $tolerance;
- # Generate the summary
- return sprintf( '%s@%.3f(%.3f,%.3f)', $rotdir, $scale, $movex, $movey);
+ return ($horiz, $vert, $rotate, $scale, $hshift, $vshift);
}
#
-# Calculate the begining and ending coordinates, after shaving 2 times
-# the margin
+# Construct pstops specification list
#
-sub calculate_coordinates {
- my ($length, $margin) = @_;
- my $skip = $length - $margin;
- my $outwidth = $skip - $margin;
- return ( &round( &round($skip) - $outwidth ), &round($skip) );
-}
+sub calc_specs {
+ my ($horiz, $vert, $rotate, $scale, $bbox) = @_;
+ my ($outwidth, $outheight, $hshift, $vshift) = @$bbox;
+ my @specs;
+ for (my $pageno = 0; $pageno < $horiz*$vert; $pageno++) {
+ my ($up, $across); # pageno index
+ my $rot = "";
+ my ($xoff, $yoff);
-#
-# Round a float number
-#
-sub round {
- return floor ($_[0] + .5);
+
+ if ($rotate) {
+ if (defined $column) {
+ # column=0; leftright=1; topbottom=0;
+ $across = $pageno % $horiz;
+ $up = floor ($pageno / $horiz);
+ } else {
+ # column=1; leftright=1; topbottom=0;
+ $across = floor($pageno / $vert);
+ $up = $pageno % $vert;
+ }
+ $rot = 'L';
+ $xoff = $margin + ($across+1)*$outwidth/$horiz - $hshift;
+ } else {
+ if (defined $column) {
+ # column=1; leftright=1; topbottom=1;
+ $across = floor($pageno / $vert);
+ $up = $vert-1 - floor($pageno % $vert);
+ } else {
+ # column=0; leftright=1; topbottom=1;
+ $across = $pageno % $horiz;
+ $up = $vert-1 - floor($pageno / $horiz);
+ }
+ $xoff = $margin + $across*$outwidth/$horiz + $hshift;
+ }
+ $yoff = $margin + $up*$outheight/$vert + $vshift;
+
+ push @specs, sprintf ("%d%s@%.3f(%.3f,%.3f)",
+ $pageno, $rot, $scale, $xoff, $yoff);
+ }
+ return @specs;
}
@@ -729,31 +738,35 @@ sub papersize {
$p = lc $p;
if ($p eq "a0") {
- ($$w,$$h) = ("841mm", "1189mm");
+ ($$w,$$h) = (2384, 3370); # 84.1cm * 118.9cm
} elsif ($p eq "a1") {
- ($$w,$$h) = ("594mm", "841mm");
+ ($$w,$$h) = (1684, 2384); # 59.4cm * 84.1cm
} elsif ($p eq "a2") {
- ($$w,$$h) = ("420mm", "594mm");
+ ($$w,$$h) = (1191, 1684); # 42cm * 59.4cm
} elsif ($p eq "a3") {
- ($$w,$$h) = ("297mm", "420mm");
+ ($$w,$$h) = (842, 1191); # 29.7cm * 42cm
} elsif ($p eq "a4") {
- ($$w,$$h) = ("210mm", "297mm");
+ ($$w,$$h) = (595, 842); # 21cm * 29.7cm
} elsif ($p eq "a5") {
- ($$w,$$h) = ("148mm", "210mm");
+ ($$w,$$h) = (421, 595); # 14.85cm * 21cm
+ } elsif ($p eq "b5") {
+ ($$w,$$h) = (516, 729); # 18.2cm * 25.72cm
} elsif ($p eq "letter") {
- ($$w,$$h) = ("8.5in", "11in");
+ ($$w,$$h) = (612, 792); # 8.5in * 11in
} elsif ($p eq "legal") {
- ($$w,$$h) = ("8.5in", "14in");
+ ($$w,$$h) = (612, 1008); # 8.5in * 14in
+ } elsif ($p eq "ledger") {
+ ($$w,$$h) = (1224, 792); # 17in * 11in
} elsif ($p eq "tabloid") {
- ($$w,$$h) = ("11in", "17in");
+ ($$w,$$h) = (792, 1224); # 11in * 17in
} elsif ($p eq "statement") {
- ($$w,$$h) = ("5.5in", "8.5in");
+ ($$w,$$h) = (396, 612); # 5.5in * 8.5in
} elsif ($p eq "executive") {
- ($$w,$$h) = ("7.25in", "10.5in");
+ ($$w,$$h) = (540, 720); # 7.6in * 10in
} elsif ($p eq "folio") {
- ($$w,$$h) = ("8.27in", "13in");
+ ($$w,$$h) = (612, 936); # 8.5in * 13in
} elsif ($p eq "quarto") {
- ($$w,$$h) = ("9in", "11in");
+ ($$w,$$h) = (610, 780); # 8.5in * 10.83in
} elsif ($p eq "10x14") {
($$w,$$h) = ("10in", "14in");
} else {
@@ -779,3 +792,25 @@ sub printcmd {
join ' ', @cmd;
}
+
+
+sub nextdiv {
+ my ($n, $m) = @_;
+ while (++$n <= $m) {
+ return $n if ($m % $n == 0)
+ }
+ return 0;
+}
+
+
+sub min {
+ my ($n, $m) = @_;
+ return $n if $n < $m;
+ return $m;
+}
+
+
+sub round {
+ return floor ($_[0] + .5);
+}
+