File Coverage

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


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