#!/usr/bin/env perl use 5.20.0; use strict; use utf8; use warnings qw(all); use File::Basename; use File::LibMagic; use Getopt::Long; use PDF::API2; use YAML; my $help = ''; my $genmeta = ''; my $preview = ''; my $crop = ''; my $stitch = ''; my $defaultcrop = ''; Getopt::Long::Configure("bundling"); my $usage = < Usage: $0 [-hgpcs] [--genmeta] [--defaultcrop=] [--preview] [--crop] [--stitch] {PDF file|.stitch file} -h, --help Display this message -g, --genmeta Generate .stitch file for stitching based on given PDF (default when called with a PDF) -d, --defaultcrop=0.9 Set default crop factor for genmeta (defaults to 0.9) -p, --preview Generate preview PDF containing overlays to analyze cropping -c, --crop Generate cropped PDF according to given .stitch -s, --stitch Generate stitched PDF (default when called with a .stitch file) pdfstitch is free software under the GNU AGPL version 3. See LICENSE for details. ENDUSAGE GetOptions ( 'h|help' => \$help, 'g|genmeta' => \$genmeta, 'd|defaultcrop=s' => \$defaultcrop, 'p|preview' => \$preview, 'c|crop' => \$crop, 's|stitch' => \$stitch ) or die "Call with --help to see available options.\n"; die $usage if $help; die "--genmeta can only be combined with --defaultcrop!\n" if($genmeta and ($preview or $crop or $stitch)); die "--defaultcrop can only be combined with --genmeta!\n" if($defaultcrop and ($preview or $crop or $stitch)); die "No input file specified!\n" unless $ARGV[0]; my $infile = $ARGV[0]; die "$infile does not exist!\n" unless -e $infile; die "$infile is not readable!\n" unless -r $infile; if(not ($genmeta or $preview or $crop or $stitch)) { my $magic = File::LibMagic->new(); my $mime_type; if($magic->can('info_from_filename')) { $mime_type = $magic->info_from_filename($infile)->{mime_type} } else { # Fallback for File::Libmagic below 1.06 $mime_type = $magic->checktype_filename($infile); } if($mime_type =~ "^application/pdf") { print "Detected PDF, turning on --genmeta\n"; $genmeta = 1; } elsif ($mime_type =~ "^text/plain") { YAML::LoadFile($infile) or die "Failed to parse $infile as YAML!\n"; print "Detected YAML, turning on --stitch\n"; $stitch = 1; } else { die "$infile has unsupported type: $mime_type\n"; } } if($genmeta) { print "Generating meta file for " . basename($infile) . ".\n"; $defaultcrop = '0.9' unless $defaultcrop; my $outfile = basename($infile) . ".stitch"; die "$outfile exists, aborting!\n" if -e $outfile; my $pdf = PDF::API2->open($infile); my $page = $pdf->openpage(1); my ($llx, $lly, $urx, $ury) = $page->get_mediabox; my $meta = { input => basename($infile), x => (($urx - $llx)*(1-$defaultcrop))/2, y => (($ury - $lly)*(1-$defaultcrop))/2, width => ($urx - $llx)*$defaultcrop, height => ($ury - $lly)*$defaultcrop, columns => int(sqrt($pdf->pages)), rows => int(sqrt($pdf->pages)), pageorder => [(1 .. $pdf->pages)], }; foreach $page (1..$pdf->pages) { $meta->{pageoffsets}->{$page}->{x} = 0; $meta->{pageoffsets}->{$page}->{y} = 0; } YAML::Bless($meta)->keys(['input','x','y','width','height','columns','rows', 'pageorder','pageoffsets']); YAML::Bless($meta->{pageoffsets})->keys([1..$pdf->pages]); YAML::DumpFile($outfile,$meta); } else { my $meta = YAML::LoadFile($infile); my $inpdf = PDF::API2->open($meta->{input}); if($preview or $crop) { my $previewpdf = PDF::API2->new() if $preview; my $croppedpdf = PDF::API2->new() if $crop; my $transparency; if($preview) { $transparency = $previewpdf->egstate(); $transparency->transparency(0.8); } foreach my $pagenr (@{$meta->{pageorder}}) { next if $pagenr eq "blank"; my $llx = $meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x}; my $lly = $meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y}; my $urx = $meta->{width}; my $ury = $meta->{height}; if($preview) { my $previewpage = $previewpdf->import_page($inpdf, $pagenr, 0); my $previewcontent = $previewpage->gfx(); $previewcontent->egstate($transparency); $previewcontent->rect($llx, $lly, $urx, $ury); $previewcontent->rect($llx - 20, $lly + $ury, 20, 20); # upper left $previewcontent->rect($llx + $urx, $lly + $ury, 20, 20); # upper right $previewcontent->rect($llx - 20, $lly - 20, 20, 20); # lower left $previewcontent->rect($llx + $urx, $lly - 20, 20, 20); # lower right $previewcontent->fillcolor('%F000'); $previewcontent->fill(); } if($crop) { my $croppage = $croppedpdf->import_page($inpdf, $pagenr, 0); $croppage->cropbox($llx, $lly, $llx + $urx, $lly + $ury); } } $previewpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-preview.pdf') if $preview; $croppedpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-cropped.pdf') if $crop; } if($stitch) { my $width = $meta->{width} * $meta->{columns}; my $height = $meta->{height} * $meta->{rows}; my $stitchedpdf = PDF::API2->new(); my $page = $stitchedpdf->page(); $page->mediabox($width, $height); my $content = $page->gfx(); my $column = 0; my $row = 0; foreach my $pagenr (@{$meta->{pageorder}}) { if($pagenr ne "blank") { my $xo = $stitchedpdf->importPageIntoForm($inpdf, $pagenr); my $llx = $meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x}; my $lly = $meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y}; my $urx = $llx + $meta->{width}; my $ury = $lly + $meta->{height}; $xo->bbox($llx, $lly, $urx, $ury); my $xpos = ($column) * $meta->{width} - ($meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x}); my $ypos = $height - (($row+1) * $meta->{height}) - ($meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y}); $content->formimage($xo, $xpos, $ypos); } $column++; if($column == $meta->{columns}) { $row++; $column=0; } } $stitchedpdf->saveas(basename($infile, ('.pdf.stitch','.stitch')) . '-stitched.pdf'); } }