File Coverage

blib/lib/App/paperback.pm
Criterion Covered Total %
statement 8 424 1.8
branch 0 174 0.0
condition 0 54 0.0
subroutine 3 33 9.0
pod 0 29 0.0
total 11 714 1.5


line stmt bran cond sub pod time code
1             package App::paperback;
2              
3 1     1   60021 use v5.10;
  1         3  
4 1     1   7 use strict;
  1         2  
  1         27  
5             # use warnings;
6             our $VERSION = 0.40;
7              
8 1     1   22 use Exporter;
  1         3  
  1         5040  
9             our @ISA = qw(Exporter);
10             our @EXPORT = qw(openInputFile openOutputFile newPageInOutputFile
11             copyPageFromInputToOutput closeInputFile closeOutputFile);
12              
13             my ($GformNr, $GinFile, $GpageObjNr, $Groot, $Gpos, $GobjNr, $Gstream, $GoWid, $GoHei);
14             my (@Gkids, @Gcounts, @GformBox, @Gobject, @Gparents, @Gto_be_created);
15             my (%Gold, %Gresources, %GpageXObject, %GoldObject);
16              
17             # ISO 216 paper sizes in pt:
18             my $AH = 841.8898; # [A] A4 ~ 297 mm (H)
19             my $AW = 595.2756; # [A] A4 ~ 210 mm (W)
20             my $BH = $AW; # [B] A5 ~ 210 mm (H)
21             my $BW = 419.5276; # [B] A5 ~ 148 mm (W)
22             my $CH = $BW; # [C] A6 ~ 148 mm (H)
23             my $CW = 297.6378; # [C] A6 ~ 105 mm (W)
24             # + 1 mm (2.8346 pt) to account for rounding in ISO 216 (148+148=296):
25             my $CG = 422.3622; # [C] A6 $CH + 1 mm (H)
26             my $BG = $CG; # [B] A5 $BW + 1 mm (W)
27              
28             # US paper sizes in pt:
29             my $DH = 792; # [D] US Letter Full (H)
30             my $DW = 612; # [D] US Letter Full (W)
31             my $EH = $DW; # [E] US Letter Half (H)
32             my $EW = 396; # [E] US Letter Half (W)
33             my $FH = $EW; # [F] US Letter Quarter (H)
34             my $FW = 306; # [F] US Letter Quarter (W)
35             my $GH = 1008; # [G] US Legal Full (H)
36             my $GW = $DW; # [G] US Legal Full (W)
37             my $HH = $DW; # [H] US Legal Half (H)
38             my $HW = 504; # [H] US Legal Half (W)
39             my $IH = $HW; # [I] US Legal Quarter (H)
40             my $IW = $FW; # [I] US Legal Quarter (W)
41              
42             # Page reordering and position offset schemas for "4 up":
43             my @P_4_UP = (16,1,13,4,2,15,3,14,12,5,9,8,6,11,7,10);
44             my @X_A6_ON_A4 = (0,$CW,$CW,$AW,0,$CW,$CW,$AW,0,$CW,$CW,$AW,0,$CW,$CW,$AW);
45             my @Y_A6_ON_A4 = ($CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH);
46             my @X_QT_ON_LT = (0,$FW,$FW,$DW,0,$FW,$FW,$DW,0,$FW,$FW,$DW,0,$FW,$FW,$DW);
47             my @Y_QT_ON_LT = ($FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH);
48             my @X_QG_ON_LG = (0,$IW,$IW,$GW,0,$IW,$IW,$GW,0,$IW,$IW,$GW,0,$IW,$IW,$GW);
49             my @Y_QG_ON_LG = ($IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH);
50              
51             # Page reordering and position offset schemas for "2 up":
52             my @P_2_UP = (16,1,15,2,14,3,13,4,12,5,11,6,10,7,9,8);
53             my @Y_A5_ON_A4 = ($BG,0,$AH,$BG,$BG,0,$AH,$BG,$BG,0,$AH,$BG,$BG,0,$AH,$BG);
54             my @X_A5_ON_A4 = ($BH,$BH,0,0,$BH,$BH,0,0,$BH,$BH,0,0,$BH,$BH,0,0);
55             my @Y_HT_ON_LT = ($EW,0,$DH,$EW,$EW,0,$DH,$EW,$EW,0,$DH,$EW,$EW,0,$DH,$EW);
56             my @X_HT_ON_LT = ($EH,$EH,0,0,$EH,$EH,0,0,$EH,$EH,0,0,$EH,$EH,0,0);
57             my @Y_HG_ON_LG = ($HW,0,$GH,$HW,$HW,0,$GH,$HW,$HW,0,$GH,$HW,$HW,0,$GH,$HW);
58             my @X_HG_ON_LG = ($HH,$HH,0,0,$HH,$HH,0,0,$HH,$HH,0,0,$HH,$HH,0,0);
59              
60             my ( $IN_FILE, $OUT_FILE );
61              
62              
63             ##########################################################
64             sub main {
65             ##########################################################
66 0     0 0   my $input = $ARGV[0];
67 0           my $num_pag_input; my $pgSizeInput;
68 0           my $numPagImposed = 0;
69 0           my $sayUsage = "Usage: paperback file.pdf (will produce 'file-paperback.pdf').";
70 0           my $sayVersion = "This is paperback v${VERSION}, (c) 2022 Hector M. Monacci.";
71 0           my $sayHelp = <<"END_MESSAGE";
72              
73             ${sayUsage}
74              
75             All pages in the input PDF file will be imposed on a new PDF with
76             bigger paper size, ready to be duplex-printed, folded and put together
77             into signatures, according to its original page size. Input PDF is
78             always assumed to be composed of vertical pages of the same size.
79              
80             Input page sizes allowed are A5, A6, Half Letter, Quarter Letter,
81             Half Legal and Quarter Legal. Other sizes give an error message.
82              
83             Only PDF v1.4 is supported as input, although many higher-labeled
84             PDF files are correctly handled since they are essentially v1.4 PDF
85             files stamped as something more modern.
86              
87             ISO 216 normalised (international) page sizes:
88              
89             Input page sizes A6 (105 x 148 mm) and A5 (148 x 210 mm) produce
90             an output page size of A4 (210 x 297 mm). Four A6 pages will be put
91             on each A4 page, or two A5 pages will be put on each A4 page.
92             Before that, input pages will be reordered and reoriented so as to
93             produce a final PDF fit for duplex 'long-edge-flip' printing.
94              
95             ANSI normalised (US) page sizes:
96              
97             Input page sizes Quarter Letter (4.25 x 5.5 in) and Half Letter (5.5
98             x 8,5 in) produce a Letter output page size (8.5 x 11 in). Input
99             page sizes Quarter Legal (4.25 x 7 in) and Half Legal (7 x 8,5 in)
100             produce a Legal output page size (8.5 x 14 in). Four Quarter-Letter
101             pages will be put on each Letter page, two Half-Letter pages will be
102             put on each Letter page, four Quarter-Legal pages will be put on each
103             Legal page, or two Half-Legal pages will be put on each Legal page.
104             Before that, input pages will be reordered and reoriented so as to
105             produce a final PDF fit for duplex 'long-edge-flip' printing.
106              
107             ${sayVersion}
108             END_MESSAGE
109              
110 0 0         die "[!] $sayUsage\n"
111             if ! defined $input;
112 0 0 0       do {print STDERR "$sayHelp" and exit}
  0 0          
113             if $input =~ "^-h\$" or $input =~ "^--help\$";
114 0 0 0       do {print STDERR "${sayVersion}\n" and exit}
  0 0          
115             if $input =~ "^-v\$" or $input =~ "^--version\$";
116 0 0         die "[!] File '$input' can't be found or read.\n"
117             unless -r $input;
118 0           ($num_pag_input, $pgSizeInput) = openInputFile($input);
119 0 0         die "[!] File '$input' is not a valid v1.4 PDF file.\n"
120             if $num_pag_input == 0;
121              
122 0           my ($rot_extra, $pagesPerSheet, @x, @y, @p);
123 0           for ($pgSizeInput) {
124 0 0         if ($_ eq "A6") { $pagesPerSheet = 4; @x = @X_A6_ON_A4; @y = @Y_A6_ON_A4; }
  0 0          
  0 0          
  0 0          
    0          
    0          
125 0           elsif ($_ eq "A5") { $pagesPerSheet = 2; @x = @X_A5_ON_A4; @y = @Y_A5_ON_A4; }
  0            
  0            
126 0           elsif ($_ eq "QT") { $pagesPerSheet = 4; @x = @X_QT_ON_LT; @y = @Y_QT_ON_LT; }
  0            
  0            
127 0           elsif ($_ eq "QG") { $pagesPerSheet = 4; @x = @X_QG_ON_LG; @y = @Y_QG_ON_LG; }
  0            
  0            
128 0           elsif ($_ eq "HT") { $pagesPerSheet = 2; @x = @X_HT_ON_LT; @y = @Y_HT_ON_LT; }
  0            
  0            
129 0           elsif ($_ eq "HG") { $pagesPerSheet = 2; @x = @X_HG_ON_LG; @y = @Y_HG_ON_LG; }
  0            
  0            
130 0           else {die "[!] Bad page size ($pgSizeInput). See 'paperback -h' to learn more.\n"}
131             }
132              
133 0           my $rotation;
134             my $target_page;
135 0           my ($name) = $input =~ /(.+)\.[^.]+$/;
136 0           my $num_pliegos;
137              
138 0           openOutputFile("${name}-paperback.pdf");
139 0           $num_pliegos = $num_pag_input >> 4;
140 0 0         ($rot_extra, @p) = $pagesPerSheet == 4 ? (0, @P_4_UP) : (90, @P_2_UP);
141 0           for (0..$num_pliegos) {
142 0           for (0..15) {
143 0 0 0       newPageInOutputFile() unless $_ % $pagesPerSheet > 0 or $numPagImposed == 0;
144 0           $target_page = $p[$_] + ($numPagImposed >> 4) * 16;
145 0 0         next if $target_page > $num_pag_input;
146              
147 0 0         $rotation = $_ % 4 > 1 ? $rot_extra + 180 : $rot_extra;
148 0           copyPageFromInputToOutput ({page => $target_page,
149             rotate => $rotation, x => $x[$_], y => $y[$_]});
150 0           ++$numPagImposed;
151 0 0         last if $numPagImposed == $num_pag_input;
152             }
153             }
154 0           closeInputFile();
155 0           closeOutputFile();
156             }
157              
158             main() if not caller();
159              
160              
161             ##########################################################
162             sub newPageInOutputFile {
163             ##########################################################
164 0 0   0 0   die "[!] No output file, you have to call openOutputFile first" if !$Gpos;
165 0 0 0       writePage() if defined $Gstream and length($Gstream) > 0;
166              
167 0           ++$GobjNr;
168 0           $GpageObjNr = $GobjNr;
169 0           undef %GpageXObject;
170              
171 0           return;
172             }
173              
174              
175             ##########################################################
176             sub copyPageFromInputToOutput {
177             ##########################################################
178 0 0   0 0   die "[!] No output file, you have to call openOutputFile first" if !$Gpos;
179 0           my $param = $_[0];
180 0 0         my $pagenumber = $param->{'page'} or 1;
181 0 0         my $x = $param->{'x'} or 0;
182 0 0         my $y = $param->{'y'} or 0;
183 0 0         my $rotate = $param->{'rotate'} or 0;
184              
185 0           my $fSource = "${GinFile}_${pagenumber}";
186 0           ++$GformNr;
187 0           my $name = "Fm${GformNr}";
188 0           my $refNr = getPage( $pagenumber );
189 0 0         die "[!] ${fSource} can't be used. Concatenate streams!" if !defined $refNr;
190 0 0         die "[!] Page ${pagenumber} doesn't exist in file ${GinFile}" if !$refNr;
191              
192 0 0 0       $Gstream .= ( defined $x and defined $y and defined $rotate ) ?
193             "q\n" . calcMatrix($x, $y, $rotate) ."\n/Gs0 gs\n/${name} Do\nQ\n" :
194             "\n/Gs0 gs\n/${name} Do\n";
195              
196 0           $GpageXObject{$name} = $refNr;
197              
198 0           return;
199             }
200              
201              
202             ##########################################################
203             sub setInitGrState {
204             ##########################################################
205 0     0 0   ++$GobjNr;
206              
207 0           $Gobject[$GobjNr] = $Gpos;
208 0           my $out_line = "${GobjNr} 0 obj<
209             . ">>endobj\n";
210 0           $Gpos += syswrite $OUT_FILE, $out_line;
211 0           return;
212             }
213              
214              
215             ##########################################################
216             sub createPageResourceDict {
217             ##########################################################
218 0     0 0   my $resourceDict = "/ProcSet[/PDF/Text]";
219 0 0         if ( %GpageXObject ) {
220 0           $resourceDict .= "/XObject<<";
221 0           $resourceDict .= "/$_ $GpageXObject{$_} 0 R" for sort keys %GpageXObject;
222 0           $resourceDict .= ">>";
223             }
224 0           $resourceDict .= "/ExtGState<<\/Gs0 4 0 R>>";
225 0           return $resourceDict;
226             }
227              
228              
229             ##########################################################
230             sub writePageResourceDict {
231             ##########################################################
232 0     0 0   my $resourceDict = $_[0];
233 0           my $resourceObject;
234              
235             # Found one identical, use it:
236 0 0         return $Gresources{$resourceDict} if exists $Gresources{$resourceDict};
237 0           ++$GobjNr;
238             # Save first 10 resources:
239 0 0         $Gresources{$resourceDict} = $GobjNr if keys(%Gresources) < 10;
240 0           $resourceObject = $GobjNr;
241 0           $Gobject[$GobjNr] = $Gpos;
242 0           $resourceDict = "${GobjNr} 0 obj<<${resourceDict}>>endobj\n";
243 0           $Gpos += syswrite $OUT_FILE, $resourceDict;
244 0           return $resourceObject;
245             }
246              
247              
248             ##########################################################
249             sub writePageStream {
250             ##########################################################
251 0     0 0   ++$GobjNr;
252 0           $Gobject[$GobjNr] = $Gpos;
253 0           $Gpos += syswrite $OUT_FILE, "${GobjNr} 0 obj<
254             . ">>stream\n${Gstream}\nendstream\nendobj\n";
255 0           $Gobject[$GpageObjNr] = $Gpos;
256 0           $Gstream = "";
257 0           return;
258             }
259              
260              
261             ##########################################################
262             sub writePageResources {
263             ##########################################################
264 0     0 0   my $parent = $_[0]; my $resourceObject = $_[1];
  0            
265 0           $Gpos += syswrite $OUT_FILE, "${GpageObjNr} 0 obj<
266             . "R/Contents ${GobjNr} 0 R/Resources ${resourceObject} 0 R>>endobj\n";
267 0           push @{ $Gkids[0] }, $GpageObjNr;
  0            
268 0           return;
269             }
270              
271              
272             ##########################################################
273             sub writePage {
274             ##########################################################
275 0 0   0 0   if ( !$Gparents[0] ) {
276 0           ++$GobjNr;
277 0           $Gparents[0] = $GobjNr;
278             }
279 0           my $parent = $Gparents[0];
280 0           my $resourceObject = writePageResourceDict(createPageResourceDict());
281 0           writePageStream();
282 0           writePageResources($parent, $resourceObject);
283 0           ++$Gcounts[0];
284 0 0         writePageNodes(8) if $Gcounts[0] > 9;
285 0           return;
286             }
287              
288              
289             ##########################################################
290             sub closeOutputFile {
291             ##########################################################
292 0 0   0 0   return if !$Gpos;
293              
294 0 0         writePage() if $Gstream;
295 0           my $endNode = writeEndNode();
296              
297 0           my $out_line = "1 0 obj<>endobj\n";
298 0           $Gobject[1] = $Gpos;
299 0           $Gpos += syswrite $OUT_FILE, $out_line;
300 0           my $qty = $#Gobject;
301 0           my $startxref = $Gpos;
302 0           my $xrefQty = $qty + 1;
303 0           $out_line = "xref\n0 ${xrefQty}\n0000000000 65535 f \n";
304 0           $out_line .= sprintf "%.10d 00000 n \n", $_ for @Gobject[1..$qty];
305 0           $out_line .= "trailer\n<<\n/Size ${xrefQty}\n/Root 1 0 R\n"
306             . ">>\nstartxref\n${startxref}\n%%EOF\n";
307              
308 0           syswrite $OUT_FILE, $out_line;
309 0           close $OUT_FILE;
310              
311 0           $Gpos = 0;
312 0           return;
313             }
314              
315              
316             ##########################################################
317             sub writePageNodes {
318             ##########################################################
319 0     0 0   my $qtyChildren = $_[0];
320 0           my $i = 0;
321 0           my $j = 1;
322 0           my $nodeObj;
323              
324 0           while ( $qtyChildren < $#{ $Gkids[$i] } ) {
  0            
325             # Imprimir padre actual y pasar al siguiente nivel:
326 0 0         if ( !$Gparents[$j] ) {
327 0           ++$GobjNr;
328 0           $Gparents[$j] = $GobjNr;
329             }
330              
331             $nodeObj =
332 0           "${Gparents[$i]} 0 obj<
333 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$i] };
  0            
334 0           $nodeObj .= "]\n/Count ${Gcounts[$i]}>>endobj\n";
335 0           $Gobject[ $Gparents[$i] ] = $Gpos;
336 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
337              
338 0           $Gcounts[$j] += $Gcounts[$i];
339 0           $Gcounts[$i] = 0;
340 0           $Gkids[$i] = [];
341 0           push @{ $Gkids[$j] }, $Gparents[$i];
  0            
342 0           undef $Gparents[$i];
343 0           ++$i;
344 0           ++$j;
345             }
346 0           return;
347             }
348              
349              
350             ##########################################################
351             sub writeEndNode {
352             ##########################################################
353 0     0 0   my $nodeObj;
354 0           my $endNode = $Gparents[-1]; # content of the last element
355 0           my $si = $#Gparents; # index of the last element
356              
357 0 0         my $min = defined $Gparents[0] ? 0 : 1;
358 0           for ( my $i = $min ; $Gparents[$i] ne $endNode ; ++$i ) {
359 0 0         if ( defined $Gparents[$i] ) { # Only defined if there are kids
360             # Find parent of current parent:
361 0           my $node;
362 0           for ( my $j = $i + 1 ; ( !$node ) ; ++$j ) {
363 0 0         if ( $Gparents[$j] ) {
364 0           $node = $Gparents[$j];
365 0           $Gcounts[$j] += $Gcounts[$i];
366 0           push @{ $Gkids[$j] }, $Gparents[$i];
  0            
367             }
368             }
369              
370 0           $nodeObj = "${Gparents[$i]} 0 obj<
371 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$i] };
  0            
372 0           $nodeObj .= "]/Count ${Gcounts[$i]}>>endobj\n";
373 0           $Gobject[ $Gparents[$i] ] = $Gpos;
374 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
375             }
376             }
377              
378             # Arrange and print the end node:
379 0           $nodeObj = "${endNode} 0 obj<
380 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$si] };
  0            
381 0           $nodeObj .= "]/Count ${Gcounts[$si]}/MediaBox \[0 0 ";
382 0           $nodeObj .= "${GoWid} ${GoHei}\]>>endobj\n";
383 0           $Gobject[$endNode] = $Gpos;
384 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
385 0           return $endNode;
386             }
387              
388              
389             ##########################################################
390             sub calcMatrix {
391             ##########################################################
392 0     0 0   my $x = $_[0]; my $y = $_[1]; my $rotate = $_[2];
  0            
  0            
393 0           my $str = "1 0 0 1 ${x} ${y} cm\n";
394              
395 0 0         if ($rotate) {
396 0           my $upperX = 0; my $upperY = 0;
  0            
397 0           my $radian = sprintf( "%.6f", $rotate / 57.2957795 ); # approx.
398 0           my $Cos = sprintf( "%.6f", cos($radian) );
399 0           my $Sin = sprintf( "%.6f", sin($radian) );
400 0           my $negSin = $Sin * -1;
401 0           $str .= "${Cos} ${Sin} ${negSin} ${Cos} ${upperX} ${upperY} cm\n";
402             }
403 0           return $str;
404             }
405              
406              
407             ##########################################################
408             sub getRoot {
409             ##########################################################
410 0     0 0   my ( $xref, $tempRoot, $buf );
411              
412 0           sysseek $IN_FILE, -50, 2;
413 0           sysread $IN_FILE, $buf, 100;
414 0 0         die "[!] File ${GinFile} is encrypted, cannot be used, aborting"
415             if $buf =~ m'Encrypt'o;
416 0 0         if ( $buf =~ m'\bstartxref\s+(\d+)'o ) {
417 0           $xref = $1;
418             # stat[7] = filesize
419 0 0         die "[!] Invalid XREF, aborting" if $xref > (stat($GinFile))[7];
420 0           $tempRoot = getRootFromXrefSection( $xref );
421             }
422              
423 0 0         $tempRoot or return 0; # No Root object in ${GinFile}, aborting
424 0           saveOldObjects();
425 0           return $tempRoot;
426             }
427              
428              
429             ##########################################################
430             sub getRootFromXrefSection {
431             ##########################################################
432 0     0 0   my $xref = $_[0];
433 0           my ( $i, $rooty, $qty, $incoming_line, $buf );
434              
435 0           sysseek $IN_FILE, $xref += 5, 0;
436 0           ($incoming_line, $qty, $i) = extractContent();
437              
438 0           while ($qty) {
439 0           for ( my $l = 1 ; $l <= $qty ; ++$l ) {
440 0           sysread $IN_FILE, $incoming_line, 20;
441 0 0         if ( $incoming_line =~ m'^\s?(\d+) \d+ (\w)\s*'o ) {
442 0 0 0       $GoldObject{$i} = int($1) unless $2 ne "n" or exists $GoldObject{$i};
443             }
444 0           ++$i;
445             }
446 0           ($incoming_line, $qty, $i) = extractContent();
447             }
448              
449 0           while ($incoming_line) {
450 0           $buf .= $incoming_line;
451 0 0         $rooty = $1 if $buf =~ m'\/Root\s+(\d+)\s{1,2}\d+\s{1,2}R'so;
452 0 0         last if $rooty;
453 0           sysread $IN_FILE, $incoming_line, 30;
454             }
455              
456 0           return $rooty;
457             }
458              
459              
460             ##########################################################
461             sub getObject {
462             ##########################################################
463 0     0 0   my $index = $_[0];
464 0 0         return 0 if ! defined $GoldObject{$index};
465 0           my $buf;
466 0           my ( $offs, $size ) = @{ $GoldObject{$index} };
  0            
467              
468 0           sysseek $IN_FILE, $offs, 0;
469 0           sysread $IN_FILE, $buf, $size;
470 0           return $buf;
471             }
472              
473              
474             ##########################################################
475             sub writeToBeCreated {
476             ##########################################################
477 0     0 0   my $elObje = $_[0];
478 0           my ( $out_line, $part, $strPos);
479              
480 0           for (@Gto_be_created) {
481 0           my $old_one = $_->[0];
482 0           my $new_one = $_->[1];
483 0           $elObje = getObject($old_one);
484 0 0         if ( $elObje =~ m'^(\d+ \d+ obj\s*<<)(.+)(>>\s*stream)'os ) {
485 0           $part = $2;
486 0           $strPos = length($1) + length($2) + length($3);
487 0           renew_ddR_and_populate_to_be_created($part);
488 0           $out_line = "${new_one} 0 obj\n<<${part}>>stream";
489 0           $out_line .= substr( $elObje, $strPos );
490             } else {
491 0 0         $elObje = substr( $elObje, length($1) ) if $elObje =~ m'^(\d+ \d+ obj\s*)'os;
492 0           renew_ddR_and_populate_to_be_created($elObje);
493 0           $out_line = "${new_one} 0 obj ${elObje}";
494             }
495 0           $Gobject[$new_one] = $Gpos;
496 0           $Gpos += syswrite $OUT_FILE, $out_line;
497             }
498 0           undef @Gto_be_created;
499 0           return;
500             }
501              
502              
503             ##########################################################
504             sub getInputPageDimensions {
505             ##########################################################
506             # Find root:
507 0     0 0   my $elObje = getObject($Groot);
508              
509             # Find pages:
510 0 0         return "unknown" unless $elObje =~ m'/Pages\s+(\d+)\s{1,2}\d+\s{1,2}R'os;
511 0           $elObje = getObject($1);
512              
513 0           $elObje = xformObjForThisPage($elObje, 1);
514 0           (undef, undef) = getResources( $elObje );
515 0 0 0       return 0 if ! defined $GformBox[2] or ! defined $GformBox[3];
516 0           my $multi = int($GformBox[2]) * int($GformBox[3]);
517 0           my $measuresInMm = int($GformBox[2] / 72 * 25.4) . " x "
518             . int($GformBox[3] / 72 * 25.4) . " mm";
519              
520 0 0 0       if ($multi > 119_780 and $multi < 121_780) { # US 1/4 letter: 120780
521 0           $GoWid = $DW;
522 0           $GoHei = $DH;
523 0           return "QT";
524             }
525 0 0 0       if ($multi > 123_443 and $multi < 125_443) { # ISO A6: 124443
526 0           $GoWid = $AW;
527 0           $GoHei = $AH;
528 0           return "A6";
529             }
530 0 0 0       if ($multi > 152_720 and $multi < 154_720) { # US 1/4 legal: 153720
531 0           $GoWid = $GW;
532 0           $GoHei = $GH;
533 0           return "QG";
534             }
535 0 0 0       if ($multi > 241_352 and $multi < 243_352) { # USstatement: 242352
536 0           $GoWid = $DW;
537 0           $GoHei = $DH;
538 0           return "HT";
539             }
540 0 0 0       if ($multi > 248_305 and $multi < 250_305) { # ISO A5: 249305
541 0           $GoWid = $AW;
542 0           $GoHei = $AH;
543 0           return "A5";
544             }
545 0 0 0       if ($multi > 307_448 and $multi < 309_448) { # US 1/2 legal: 308448
546 0           $GoWid = $GW;
547 0           $GoHei = $GH;
548 0           return "HG";
549             }
550 0 0 0       if ($multi > 483_704 and $multi < 485_704) { # US letter: 484704
551 0           return "USletter, ${measuresInMm}";
552             }
553 0 0 0       if ($multi > 499_395 and $multi < 501_395) { # ISO A4: 500395
554 0           return "A4, ${measuresInMm}";
555             }
556 0 0 0       if ($multi > 615_896 and $multi < 617_896) { # US legal: 616896
557 0           return "USlegal, ${measuresInMm}";
558             }
559 0           return "unknown, ${measuresInMm}";
560             }
561              
562              
563             ##########################################################
564             sub getPage {
565             ##########################################################
566 0     0 0   my $pagenumber = $_[0];
567              
568 0           my ( $reference, $formRes, $formCont );
569              
570 0           my $fSource = "${GinFile}_${pagenumber}";
571              
572             # Find root:
573 0           my $elObje = getObject($Groot);
574              
575             # Find pages:
576 0 0         die "[!] Didn't find Pages section in '${GinFile}' - aborting"
577             unless $elObje =~ m'/Pages\s+(\d+)\s{1,2}\d+\s{1,2}R'os;
578 0           $elObje = getObject($1);
579              
580 0           $elObje = xformObjForThisPage($elObje, $pagenumber);
581 0           ($formRes, $formCont) = getResources( $elObje );
582              
583 0           $reference = writeRes($elObje, $formRes, $formCont);
584              
585 0           writeToBeCreated($elObje);
586              
587 0           return $reference;
588             }
589              
590              
591             ##########################################################
592             sub writeRes {
593             ##########################################################
594 0     0 0   my $elObje = $_[0]; my $formRes = $_[1]; my $formCont = $_[2];
  0            
  0            
595 0           my $out_line;
596              
597 0           $elObje = getObject($formCont);
598 0           $elObje =~ m'^(\d+ \d+ obj\s*<<)(.+)(>>\s*stream)'so;
599 0           my $strPos = length($1) + length($2) + length($3);
600 0           my $newPart = "<
601             . "/BBox \[ $GformBox[0] $GformBox[1] $GformBox[2] $GformBox[3]\] ${2}";
602 0           ++$GobjNr;
603 0           $Gobject[$GobjNr] = $Gpos;
604 0           my $reference = $GobjNr;
605 0           renew_ddR_and_populate_to_be_created($newPart);
606 0           $out_line = "${reference} 0 obj\n${newPart}>>\nstream";
607 0           $out_line .= substr( $elObje, $strPos );
608 0           $Gpos += syswrite $OUT_FILE, $out_line;
609 0           return $reference;
610             }
611              
612              
613             ##########################################################
614             sub xformObjForThisPage {
615             ##########################################################
616 0     0 0   my $elObje = $_[0]; my $pagenumber = $_[1];
  0            
617 0           my ( $vector, @pageObj, @pageObjBackup, $pageAccumulator);
618              
619 0 0         return 0 unless $elObje =~ m'/Kids\s*\[([^\]]+)'os;
620 0           $vector = $1;
621              
622 0           $pageAccumulator = 0;
623              
624 0           push @pageObj, $1 while $vector =~ m'(\d+)\s{1,2}\d+\s{1,2}R'go;
625 0           while ( $pageAccumulator < $pagenumber ) {
626 0           @pageObjBackup = @pageObj;
627 0           undef @pageObj;
628 0           for (@pageObjBackup) {
629 0           $elObje = getObject($_);
630 0 0         if ( $elObje =~ m'/Count\s+(\d+)'os ) {
631 0 0         if ( ( $pageAccumulator + $1 ) < $pagenumber ) {
632 0           $pageAccumulator += $1;
633             } else {
634 0 0         $vector = $1 if $elObje =~ m'/Kids\s*\[([^\]]+)'os ;
635 0           push @pageObj, $1 while $vector =~ m'(\d+)\s{1,2}\d+\s{1,2}R'gso;
636 0           last;
637             }
638             } else {
639 0           ++$pageAccumulator;
640             }
641 0 0         last if $pageAccumulator == $pagenumber;
642             }
643             }
644 0           return $elObje;
645             }
646              
647              
648             ##########################################################
649             sub getResources {
650             ##########################################################
651 0     0 0   my $obj = $_[0];
652 0           my ($resources, $formCont);
653              
654             # Assume all input PDF pages have the same dimensions as first MediaBox found:
655 0 0         if (! @GformBox) {
656 0 0         if ( $obj =~ m'MediaBox\s*\[\s*([\S]+)\s+([\S]+)\s+([\S]+)\s+([\S]+)\s*\]'os ) {
657 0           @GformBox = ($1, $2, $3, $4);
658             }
659             }
660              
661 0 0         if ( $obj =~ m'/Contents\s+(\d+)'so ) {
    0          
662 0           $formCont = $1;
663             } elsif ( $obj =~ m'/Contents\s*\[\s*(\d+)\s{1,2}\d+\s{1,2}R\s*\]'so ) {
664 0           $formCont = $1;
665             }
666              
667 0           $resources = getResourcesFromObj($obj);
668              
669 0           return ($resources, $formCont);
670             }
671              
672              
673             ##########################################################
674             sub getResourcesFromObj {
675             ##########################################################
676 0     0 0   my $obj = $_[0];
677 0           my $resources;
678              
679 0 0         if ( $obj =~ m'^(.+/Resources)'so ) {
680 0 0         return $1 if $obj =~ m'Resources(\s+\d+\s{1,2}\d+\s{1,2}R)'os; # Reference (95%)
681             # The resources are a dictionary. The whole is copied (morfologia.pdf):
682 0           my $k;
683 0           ( undef, $obj ) = split /\/Resources/, $obj;
684 0           $obj =~ s/<
685 0           $obj =~ s/>>/>>#/gs;
686 0           for ( split /#/, $obj ) {
687 0 0         if ( m'\S's ) {
688 0           $resources .= $_;
689 0 0         ++$k if m'<<'s;
690 0 0         --$k if m'>>'s;
691 0 0         last if $k == 0;
692             }
693             }
694             }
695 0           return $resources;
696             }
697              
698              
699             ##########################################################
700             sub openInputFile {
701             ##########################################################
702 0     0 0   $GinFile = $_[0];
703 0           my ( $elObje, $inputPageSize );
704              
705 0 0         open( $IN_FILE, q{<}, $GinFile )
706             or die "[!] Couldn't open ${GinFile}";
707 0           binmode $IN_FILE;
708              
709             # Find root
710 0           $Groot = getRoot();
711              
712             # Find input page size:
713 0           $inputPageSize = getInputPageDimensions();
714              
715             # Find pages
716 0 0         return 0 unless eval { $elObje = getObject($Groot); 1; };
  0            
  0            
717 0 0         if ( $elObje =~ m'/Pages\s+(\d+)\s{1,2}\d+\s{1,2}R'os ) {
718 0           $elObje = getObject($1);
719 0 0         return ($1, $inputPageSize) if $elObje =~ m'/Count\s+(\d+)'os;
720             }
721 0           return 0;
722             }
723              
724              
725             ##########################################################
726             sub saveOldObjects {
727             ##########################################################
728 0     0 0   my $bytes = (stat($GinFile))[7]; # stat[7] = filesize
729             # Objects are sorted numerically (<=>) and in reverse order ($b $a)
730             # according to their offset in the file: last first
731 0           my @offset = sort { $GoldObject{$b} <=> $GoldObject{$a} } keys %GoldObject;
  0            
732              
733 0           my $saved;
734              
735 0           for (@offset) {
736 0           $saved = $GoldObject{$_};
737 0           $bytes -= $saved;
738 0 0         $GoldObject{$_} = [ $saved, $bytes ] if ($_ !~ m'^xref'o);
739 0           $bytes = $saved;
740             }
741             }
742              
743              
744             ##########################################################
745             sub renew_ddR_and_populate_to_be_created {
746             ##########################################################
747             # $xform translates an old object number to a new one
748             # and populates a table with what must be created
749 0 0   0     my $xform = sub { return $Gold{$1} if exists $Gold{$1};
750 0           push @Gto_be_created, [ $1, ++$GobjNr ];
751 0           return $Gold{$1} = $GobjNr;
752 0     0 0   };
753 0           $_[0] =~ s/\b(\d+)\s{1,2}\d+\s{1,2}R\b/&$xform . ' 0 R'/oegs;
  0            
754 0           return;
755             }
756              
757              
758             ##########################################################
759             sub extractContent {
760             ##########################################################
761 0     0 0   my ($incoming_line, $qty, $i, $c);
762              
763 0           sysread $IN_FILE, $c, 1;
764 0           sysread $IN_FILE, $c, 1 while $c =~ m!\s!s;
765 0   0       while ( (defined $c) and ($c ne "\n") and ($c ne "\r") ) {
      0        
766 0           $incoming_line .= $c;
767 0           sysread $IN_FILE, $c, 1;
768             }
769 0 0         if ( $incoming_line =~ m'^(\d+)\s+(\d+)'o ) {
770 0           $i = $1;
771 0           $qty = $2;
772             }
773              
774 0           return ($incoming_line, $qty, $i);
775             }
776              
777              
778             ##########################################################
779             sub openOutputFile {
780             ##########################################################
781 0 0   0 0   closeOutputFile() if $Gpos;
782              
783 0           my $outputfile = $_[0];
784 0           my $pdf_signature = "%PDF-1.4\n%\â\ã\Ï\Ó\n"; # Keep it far from file beginning!
785              
786 0 0         open( $OUT_FILE, q{>}, $outputfile )
787             or die "[!] Couldn't open file ${outputfile}";
788 0           binmode $OUT_FILE;
789 0           $Gpos = syswrite $OUT_FILE, $pdf_signature;
790              
791 0           $GobjNr = 2; # Objeto reservado 1 para raíz y 2 para nodo lateral inicial
792 0           $Gparents[0] = 2;
793              
794 0           newPageInOutputFile();
795 0           setInitGrState();
796 0           return;
797             }
798              
799              
800             ##########################################################
801             sub closeInputFile {
802             ##########################################################
803 0     0 0   close $IN_FILE;
804             }
805              
806             1;
807              
808             __END__