File Coverage

blib/lib/App/paperback.pm
Criterion Covered Total %
statement 5 429 1.1
branch 0 186 0.0
condition 0 51 0.0
subroutine 2 32 6.2
pod 0 29 0.0
total 7 727 0.9


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