File Coverage

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


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