File Coverage

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


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