File Coverage

blib/lib/App/paperback.pm
Criterion Covered Total %
statement 5 439 1.1
branch 0 184 0.0
condition 0 36 0.0
subroutine 2 33 6.0
pod 0 30 0.0
total 7 722 0.9


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