21 Getopt::Long::Configure("bundling");
24 my $usage = <<ENDUSAGE;
25 pdfstitch - Copyright (C) 2017 by Jakob Haufe <sur5r\@sur5r.net>
27 Usage: $0 [-hgpcs] [--genmeta] [--defaultcrop=<factor>] [--preview] [--crop] [--stitch] {PDF file|.stitch file}
29 -h, --help Display this message
30 -g, --genmeta Generate .stitch file for stitching based on given PDF
31 (default when called with a PDF)
32 -d, --defaultcrop=0.9 Set default crop factor for genmeta (defaults to 0.9)
33 -p, --preview Generate preview PDF containing overlays to analyze
35 -c, --crop Generate cropped PDF according to given .stitch
36 -s, --stitch Generate stitched PDF
37 (default when called with a .stitch file)
39 pdfstitch is free software under the GNU AGPL version 3. See LICENSE for details.
45 'g|genmeta' => \$genmeta,
46 'd|defaultcrop=s' => \$defaultcrop,
47 'p|preview' => \$preview,
49 's|stitch' => \$stitch
50 ) or die "Call with --help to see available options.\n";
54 die "--genmeta can only be combined with --defaultcrop!\n" if($genmeta and ($preview or $crop or $stitch));
56 die "--defaultcrop can only be combined with --genmeta!\n" if($defaultcrop and ($preview or $crop or $stitch));
58 die "No input file specified!\n" unless $ARGV[0];
60 my $infile = $ARGV[0];
62 die "$infile does not exist!\n" unless -e $infile;
63 die "$infile is not readable!\n" unless -r $infile;
65 if(not ($genmeta or $preview or $crop or $stitch))
67 my $magic = File::LibMagic->new();
70 if($magic->can('info_from_filename'))
72 $mime_type = $magic->info_from_filename($infile)->{mime_type}
76 # Fallback for File::Libmagic below 1.06
77 $mime_type = $magic->checktype_filename($infile);
79 if($mime_type =~ "^application/pdf")
81 print "Detected PDF, turning on --genmeta\n";
84 elsif ($mime_type =~ "^text/plain")
86 YAML::LoadFile($infile) or die "Failed to parse $infile as YAML!\n";
87 print "Detected YAML, turning on --stitch\n";
92 die "$infile has unsupported type: $mime_type\n";
98 print "Generating meta file for " . basename($infile) . ".\n";
99 $defaultcrop = '0.9' unless $defaultcrop;
100 my $outfile = basename($infile) . ".stitch";
102 die "$outfile exists, aborting!\n" if -e $outfile;
105 my $pdf = PDF::API2->open($infile);
107 my $page = $pdf->openpage(1);
108 my ($llx, $lly, $urx, $ury) = $page->get_mediabox;
111 input => basename($infile),
112 x => (($urx - $llx)*(1-$defaultcrop))/2,
113 y => (($ury - $lly)*(1-$defaultcrop))/2,
114 width => ($urx - $llx)*$defaultcrop,
115 height => ($ury - $lly)*$defaultcrop,
116 columns => int(sqrt($pdf->pages)),
117 rows => int(sqrt($pdf->pages)),
118 pageorder => [(1 .. $pdf->pages)],
121 foreach $page (1..$pdf->pages)
123 $meta->{pageoffsets}->{$page}->{x} = 0;
124 $meta->{pageoffsets}->{$page}->{y} = 0;
127 YAML::Bless($meta)->keys(['input','x','y','width','height','columns','rows', 'pageorder','pageoffsets']);
128 YAML::Bless($meta->{pageoffsets})->keys([1..$pdf->pages]);
129 YAML::DumpFile($outfile,$meta);
133 my $meta = YAML::LoadFile($infile);
134 my $inpdf = PDF::API2->open($meta->{input});
136 if($preview or $crop)
138 my $previewpdf = PDF::API2->new() if $preview;
139 my $croppedpdf = PDF::API2->new() if $crop;
144 $transparency = $previewpdf->egstate();
145 $transparency->transparency(0.8);
148 foreach my $pagenr (@{$meta->{pageorder}})
150 next if $pagenr eq "blank";
152 my $llx = $meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x};
153 my $lly = $meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y};
154 my $urx = $meta->{width};
155 my $ury = $meta->{height};
159 my $previewpage = $previewpdf->import_page($inpdf, $pagenr, 0);
160 my $previewcontent = $previewpage->gfx();
161 $previewcontent->egstate($transparency);
162 $previewcontent->rect($llx, $lly, $urx, $ury);
163 $previewcontent->rect($llx - 20, $lly + $ury, 20, 20); # upper left
164 $previewcontent->rect($llx + $urx, $lly + $ury, 20, 20); # upper right
165 $previewcontent->rect($llx - 20, $lly - 20, 20, 20); # lower left
166 $previewcontent->rect($llx + $urx, $lly - 20, 20, 20); # lower right
167 $previewcontent->fillcolor('%F000');
168 $previewcontent->fill();
172 my $croppage = $croppedpdf->import_page($inpdf, $pagenr, 0);
173 $croppage->cropbox($llx, $lly, $llx + $urx, $lly + $ury);
177 $previewpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-preview.pdf') if $preview;
178 $croppedpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-cropped.pdf') if $crop;
183 my $width = $meta->{width} * $meta->{columns};
184 my $height = $meta->{height} * $meta->{rows};
186 my $stitchedpdf = PDF::API2->new();
188 my $page = $stitchedpdf->page();
189 $page->mediabox($width, $height);
191 my $content = $page->gfx();
195 foreach my $pagenr (@{$meta->{pageorder}})
197 if($pagenr ne "blank")
199 my $xo = $stitchedpdf->importPageIntoForm($inpdf, $pagenr);
201 my $llx = $meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x};
202 my $lly = $meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y};
203 my $urx = $llx + $meta->{width};
204 my $ury = $lly + $meta->{height};
206 $xo->bbox($llx, $lly, $urx, $ury);
208 my $xpos = ($column) * $meta->{width} - ($meta->{x} + $meta->{pageoffsets}->{$pagenr}->{x});
209 my $ypos = $height - (($row+1) * $meta->{height}) - ($meta->{y} + $meta->{pageoffsets}->{$pagenr}->{y});
210 $content->formimage($xo, $xpos, $ypos);
213 if($column == $meta->{columns})
220 $stitchedpdf->saveas(basename($infile, ('.pdf.stitch','.stitch')) . '-stitched.pdf');