]> git.sur5r.net Git - pdfstitch/blob - pdfstitch
Improve preview overlay usability
[pdfstitch] / pdfstitch
1 #!/usr/bin/env perl
2
3 use 5.20.0;
4 use strict;
5 use utf8;
6 use warnings qw(all);
7
8 use File::Basename;
9 use File::LibMagic;
10 use Getopt::Long;
11 use PDF::API2;
12 use YAML;
13
14 my $help = '';
15 my $genmeta = '';
16 my $preview = '';
17 my $crop = '';
18 my $stitch = '';
19 my $defaultcrop = '';
20
21 Getopt::Long::Configure("bundling");
22
23
24 my $usage = <<ENDUSAGE;
25 pdfstitch - Copyright (C) 2017 by Jakob Haufe <sur5r\@sur5r.net>
26
27 Usage: $0 [-hgpcs] [--genmeta] [--defaultcrop=<factor>] [--preview] [--crop] [--stitch] {PDF file|.stitch file}
28
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
34                         cropping
35  -c, --crop             Generate cropped PDF according to given .stitch
36  -s, --stitch           Generate stitched PDF
37                         (default when called with a .stitch file)
38
39 pdfstitch is free software under the GNU AGPL version 3. See LICENSE for details.
40 ENDUSAGE
41
42 GetOptions
43 (
44     'h|help' => \$help,
45     'g|genmeta' => \$genmeta,
46     'd|defaultcrop=s' => \$defaultcrop,
47     'p|preview' => \$preview,
48     'c|crop' => \$crop,
49     's|stitch' => \$stitch
50 ) or die "Call with --help to see available options.\n";
51
52 die $usage if $help;
53
54 die "--genmeta can only be combined with --defaultcrop!\n" if($genmeta and ($preview or $crop or $stitch));
55
56 die "--defaultcrop can only be combined with --genmeta!\n" if($defaultcrop and ($preview or $crop or $stitch));
57
58 die "No input file specified!\n" unless $ARGV[0];
59
60 my $infile = $ARGV[0];
61
62 die "$infile does not exist!\n" unless -e $infile;
63 die "$infile is not readable!\n" unless -r $infile;
64
65 if(not ($genmeta or $preview or $crop or $stitch))
66 {
67     my $magic = File::LibMagic->new();
68     my $mime_type;
69
70     if($magic->can('info_from_filename'))
71     {
72         $mime_type = $magic->info_from_filename($infile)->{mime_type}
73     }
74     else
75     {
76         # Fallback for File::Libmagic below 1.06
77         $mime_type = $magic->checktype_filename($infile);
78     }
79     if($mime_type =~ "^application/pdf")
80     {
81         print "Detected PDF, turning on --genmeta\n";
82         $genmeta = 1;
83     }
84     elsif ($mime_type =~  "^text/plain")
85     {
86         YAML::LoadFile($infile) or die "Failed to parse $infile as YAML!\n";
87         print "Detected YAML, turning on --stitch\n";
88         $stitch = 1;
89     }
90     else
91     {
92         die "$infile has unsupported type: $mime_type\n";
93     }
94 }
95
96 if($genmeta)
97 {
98     print "Generating meta file for " . basename($infile) . ".\n";
99     $defaultcrop = '0.9' unless $defaultcrop;
100     my $outfile = basename($infile) . ".stitch";
101
102     die "$outfile exists, aborting!\n" if -e $outfile;
103
104
105     my $pdf = PDF::API2->open($infile);
106
107     my $page = $pdf->openpage(1);
108     my ($llx, $lly, $urx, $ury) = $page->get_mediabox;
109
110     my $meta = {
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)],
119     };
120
121     foreach $page (1..$pdf->pages)
122     {
123         $meta->{pageoffsets}->{$page}->{x} = 0;
124         $meta->{pageoffsets}->{$page}->{y} = 0;
125     }
126
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);
130 }
131 else
132 {
133     my $meta = YAML::LoadFile($infile);
134     my $inpdf = PDF::API2->open($meta->{input});
135
136     if($preview or $crop)
137     {
138         my $previewpdf = PDF::API2->new() if $preview;
139         my $croppedpdf = PDF::API2->new() if $crop;
140         my $transparency;
141
142         if($preview)
143         {
144             $transparency = $previewpdf->egstate();
145             $transparency->transparency(0.8);
146         }
147
148         foreach my $pagenr (@{$meta->{pageorder}})
149         {
150             next if $pagenr eq "blank";
151
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};
156
157             if($preview)
158             {
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();
169             }
170             if($crop)
171             {
172                 my $croppage = $croppedpdf->import_page($inpdf, $pagenr, 0);
173                 $croppage->cropbox($llx, $lly, $llx + $urx, $lly + $ury);
174             }
175         }
176
177         $previewpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-preview.pdf') if $preview;
178         $croppedpdf->saveas(basename($infile, ('.pdf.stitch', '.stitch')) . '-cropped.pdf') if $crop;
179     }
180
181     if($stitch)
182     {
183         my $width = $meta->{width} * $meta->{columns};
184         my $height = $meta->{height} * $meta->{rows};
185
186         my $stitchedpdf = PDF::API2->new();
187
188         my $page = $stitchedpdf->page();
189         $page->mediabox($width, $height);
190
191         my $content = $page->gfx();
192         my $column = 0;
193         my $row = 0;
194
195         foreach my $pagenr (@{$meta->{pageorder}})
196         {
197             if($pagenr ne "blank")
198             {
199                 my $xo = $stitchedpdf->importPageIntoForm($inpdf, $pagenr);
200
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};
205
206                 $xo->bbox($llx, $lly, $urx, $ury);
207
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);
211             }
212             $column++;
213             if($column == $meta->{columns})
214             {
215                 $row++;
216                 $column=0;
217             }
218         }
219
220         $stitchedpdf->saveas(basename($infile, ('.pdf.stitch','.stitch')) . '-stitched.pdf');
221     }
222 }
223