File Coverage

blib/lib/App/paperback.pm
Criterion Covered Total %
statement 8 431 1.8
branch 0 186 0.0
condition 0 51 0.0
subroutine 3 33 9.0
pod 0 29 0.0
total 11 730 1.5


line stmt bran cond sub pod time code
1             package App::paperback;
2              
3 1     1   55898 use v5.10;
  1         3  
4 1     1   5 use strict;
  1         2  
  1         27  
5             # use warnings;
6             our $VERSION = "v1.03";
7              
8 1     1   23 use Exporter;
  1         2  
  1         4751  
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_4UP_13PLUS = (16,1,13,4,2,15,3,14,12,5,9,8,6,11,7,10);
44             my @P_4UP_9PLUS = (12,1,9,4,2,11,3,10,6,7,9999,9999,8,5);
45             my @P_4UP_5PLUS = (8,1,5,4,2,7,3,6);
46             my @P_4UP_1PLUS = (4,1,9999,9999,2,3);
47             my @X_A6_ON_A4 = (0,$CW,$CW,$AW,0,$CW,$CW,$AW,0,$CW,$CW,$AW,0,$CW,$CW,$AW);
48             my @Y_A6_ON_A4 = ($CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH,$CG,$CG,$CH,$CH);
49             my @X_QT_ON_LT = (0,$FW,$FW,$DW,0,$FW,$FW,$DW,0,$FW,$FW,$DW,0,$FW,$FW,$DW);
50             my @Y_QT_ON_LT = ($FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH,$FH);
51             my @X_QG_ON_LG = (0,$IW,$IW,$GW,0,$IW,$IW,$GW,0,$IW,$IW,$GW,0,$IW,$IW,$GW);
52             my @Y_QG_ON_LG = ($IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH,$IH);
53              
54             # Page reordering and position offset schemas for "2 up":
55             my @P_2UP_13PLUS = (1,16,2,15,3,14,4,13,5,12,6,11,7,10,8,9);
56             my @P_2UP_9PLUS = (1,12,2,11,3,10,4,9,5,8,6,7);
57             my @P_2UP_5PLUS = (1,8,2,7,3,6,4,5);
58             my @P_2UP_1PLUS = (1,4,2,3);
59             my @X_A5_ON_A4 = ($BH,$BH,0,0,$BH,$BH,0,0,$BH,$BH,0,0,$BH,$BH,0,0);
60             my @Y_A5_ON_A4 = ($BG,0,$AH,$BG,$BG,0,$AH,$BG,$BG,0,$AH,$BG,$BG,0,$AH,$BG);
61             my @X_HT_ON_LT = ($EH,$EH,0,0,$EH,$EH,0,0,$EH,$EH,0,0,$EH,$EH,0,0);
62             my @Y_HT_ON_LT = ($EW,0,$DH,$EW,$EW,0,$DH,$EW,$EW,0,$DH,$EW,$EW,0,$DH,$EW);
63             my @X_HG_ON_LG = ($HH,$HH,0,0,$HH,$HH,0,0,$HH,$HH,0,0,$HH,$HH,0,0);
64             my @Y_HG_ON_LG = ($HW,0,$GH,$HW,$HW,0,$GH,$HW,$HW,0,$GH,$HW,$HW,0,$GH,$HW);
65              
66             my ( $IN_FILE, $OUT_FILE );
67              
68              
69             ##########################################################
70             sub main {
71             ##########################################################
72 0     0 0   my $input = $ARGV[0];
73 0           my $num_pag_input; my $pgSizeInput;
74 0           my $numPagImposed = 0;
75 0           my $sayUsage = "Usage: paperback file.pdf (will produce 'file-paperback.pdf').";
76 0           my $sayVersion = "This is paperback ${VERSION}, (c) 2022 Hector M. Monacci.";
77 0           my $sayHelp = <<"END_MESSAGE";
78              
79             ${sayUsage}
80              
81             All pages in the input PDF file will be imposed on a new PDF with
82             bigger paper size, ready to be duplex-printed, folded and put together
83             into signatures, according to its original page size. Input PDF is
84             always assumed to be composed of vertical pages of the same size.
85              
86             Input page sizes allowed are A5, A6, Half Letter, Quarter Letter,
87             Half Legal and Quarter Legal. Other sizes give an error message.
88              
89             Only PDF v1.4 is supported as input, although many higher-labeled
90             PDF files are correctly handled since they are essentially v1.4 PDF
91             files stamped as something more modern.
92              
93             ISO 216 normalised (international) page sizes:
94              
95             Input page sizes A6 (105 x 148 mm) and A5 (148 x 210 mm) produce
96             an output page size of A4 (210 x 297 mm). Four A6 pages will be put
97             on each A4 page, or two A5 pages will be put on each A4 page.
98             Before that, input pages will be reordered and reoriented so as to
99             produce a final PDF fit for duplex 'long-edge-flip' printing.
100              
101             ANSI normalised (US) page sizes:
102              
103             Input page sizes Quarter Letter (4.25 x 5.5 in) and Half Letter (5.5
104             x 8.5 in) produce a Letter output page size (8.5 x 11 in). Input
105             page sizes Quarter Legal (4.25 x 7 in) and Half Legal (7 x 8.5 in)
106             produce a Legal output page size (8.5 x 14 in). Four Quarter-Letter
107             pages will be put on each Letter page, two Half-Letter pages will be
108             put on each Letter page, four Quarter-Legal pages will be put on each
109             Legal page, or two Half-Legal pages will be put on each Legal page.
110             Before that, input pages will be reordered and reoriented so as to
111             produce a final PDF fit for duplex 'long-edge-flip' printing.
112              
113             ${sayVersion}
114             END_MESSAGE
115              
116 0 0         die "[!] $sayUsage\n"
117             if ! defined $input;
118 0 0 0       do {print STDERR "$sayHelp" and exit}
  0 0          
119             if $input =~ "^-h\$" or $input =~ "^--help\$";
120 0 0 0       do {print STDERR "${sayVersion}\n" and exit}
  0 0          
121             if $input =~ "^-v\$" or $input =~ "^--version\$";
122 0 0         die "[!] File '$input' can't be found or read.\n"
123             unless -r $input;
124 0           ($num_pag_input, $pgSizeInput) = openInputFile($input);
125 0 0         die "[!] File '$input' is not a valid v1.4 PDF file.\n"
126             if $num_pag_input == 0;
127              
128 0           my ($pgPerOutputPage, @x, @y);
129 0           for ($pgSizeInput) {
130 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          
131 0           elsif ($_ eq "A5") { $pgPerOutputPage = 2; @x = @X_A5_ON_A4; @y = @Y_A5_ON_A4; }
  0            
  0            
132 0           elsif ($_ eq "QT") { $pgPerOutputPage = 4; @x = @X_QT_ON_LT; @y = @Y_QT_ON_LT; }
  0            
  0            
133 0           elsif ($_ eq "QG") { $pgPerOutputPage = 4; @x = @X_QG_ON_LG; @y = @Y_QG_ON_LG; }
  0            
  0            
134 0           elsif ($_ eq "HT") { $pgPerOutputPage = 2; @x = @X_HT_ON_LT; @y = @Y_HT_ON_LT; }
  0            
  0            
135 0           elsif ($_ eq "HG") { $pgPerOutputPage = 2; @x = @X_HG_ON_LG; @y = @Y_HG_ON_LG; }
  0            
  0            
136 0           else {die "[!] Bad page size ($pgSizeInput). See 'paperback -h' to learn more.\n"}
137             }
138              
139 0           my ($name) = $input =~ /(.+)\.[^.]+$/;
140 0           openOutputFile("${name}-paperback.pdf");
141 0           my ($rot_extra, @p);
142 0 0         if ($pgPerOutputPage == 4) {
143 0           $rot_extra = 0;
144 0 0         if ($num_pag_input >= 13) { @p = @P_4UP_13PLUS; }
  0 0          
    0          
145 0           elsif ($num_pag_input >= 9 ) { @p = @P_4UP_9PLUS; }
146 0           elsif ($num_pag_input >= 5 ) { @p = @P_4UP_5PLUS; }
147 0           else { @p = @P_4UP_1PLUS; }
148             } else {
149 0           $rot_extra = 90;
150 0 0         if ($num_pag_input >= 13) { @p = @P_2UP_13PLUS; }
  0 0          
    0          
151 0           elsif ($num_pag_input >= 9 ) { @p = @P_2UP_9PLUS; }
152 0           elsif ($num_pag_input >= 5 ) { @p = @P_2UP_5PLUS; }
153 0           else { @p = @P_2UP_1PLUS; }
154             }
155 0           my $lastSignature = $num_pag_input >> 4;
156 0           my ($rotation, $target_page);
157 0           for (my $thisSignature = 0; $thisSignature <= $lastSignature; ++$thisSignature) {
158 0           for (0..15) {
159 0 0         newPageInOutputFile() if $_ % $pgPerOutputPage == 0;
160 0           $target_page = $p[$_] + 16 * $thisSignature;
161 0 0         next if $target_page > $num_pag_input;
162              
163 0 0         $rotation = $_ % 4 > 1 ? $rot_extra + 180 : $rot_extra;
164 0           copyPageFromInputToOutput ({page => $target_page,
165             rotate => $rotation, x => $x[$_], y => $y[$_]});
166 0           ++$numPagImposed;
167 0 0         last if $numPagImposed == $num_pag_input;
168             }
169             }
170 0           closeInputFile();
171 0           closeOutputFile();
172             }
173              
174             main() if not caller();
175              
176              
177             ##########################################################
178             sub newPageInOutputFile {
179             ##########################################################
180 0 0   0 0   die "[!] No output file, you must call openOutputFile first" if !$Gpos;
181 0 0 0       writePage() if defined $Gstream and length($Gstream) > 0;
182              
183 0           ++$GobjNr;
184 0           $GpageObjNr = $GobjNr;
185 0           undef %GpageXObject;
186              
187 0           return;
188             }
189              
190              
191             ##########################################################
192             sub copyPageFromInputToOutput {
193             ##########################################################
194 0 0   0 0   die "[!] No output file, you have to call openOutputFile first" if !$Gpos;
195 0           my $param = $_[0];
196 0 0         my $pagenumber = $param->{'page'} or 1;
197 0 0         my $x = $param->{'x'} or 0;
198 0 0         my $y = $param->{'y'} or 0;
199 0 0         my $rotate = $param->{'rotate'} or 0;
200              
201 0           ++$GformNr;
202 0           my $name = "Fm${GformNr}";
203 0           my $refNr = getPage( $pagenumber );
204 0 0         die "[!] Page ${pagenumber} in ${GinFile} can't be used. Concatenate streams!"
205             if !defined $refNr;
206 0 0         die "[!] Page ${pagenumber} doesn't exist in file ${GinFile}" if !$refNr;
207              
208 0 0 0       $Gstream .= ( defined $x and defined $y and defined $rotate ) ?
209             "q\n" . calcMatrix($x, $y, $rotate) ."\n/Gs0 gs\n/${name} Do\nQ\n" :
210             "\n/Gs0 gs\n/${name} Do\n";
211              
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<<\/Gs0 4 0 R>>";
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             # Found one identical, use it:
252 0 0         return $Gresources{$resourceDict} if exists $Gresources{$resourceDict};
253 0           ++$GobjNr;
254             # Save first 10 resources:
255 0 0         $Gresources{$resourceDict} = $GobjNr if keys(%Gresources) < 10;
256 0           $resourceObject = $GobjNr;
257 0           $Gobject[$GobjNr] = $Gpos;
258 0           $resourceDict = "${GobjNr} 0 obj<<${resourceDict}>>endobj\n";
259 0           $Gpos += syswrite $OUT_FILE, $resourceDict;
260 0           return $resourceObject;
261             }
262              
263              
264             ##########################################################
265             sub writePageStream {
266             ##########################################################
267 0     0 0   ++$GobjNr;
268 0           $Gobject[$GobjNr] = $Gpos;
269 0           $Gpos += syswrite $OUT_FILE, "${GobjNr} 0 obj<
270             . ">>stream\n${Gstream}\nendstream\nendobj\n";
271 0           $Gobject[$GpageObjNr] = $Gpos;
272 0           $Gstream = "";
273 0           return;
274             }
275              
276              
277             ##########################################################
278             sub writePageResources {
279             ##########################################################
280 0     0 0   my $parent = $_[0]; my $resourceObject = $_[1];
  0            
281 0           $Gpos += syswrite $OUT_FILE, "${GpageObjNr} 0 obj<
282             . "R/Contents ${GobjNr} 0 R/Resources ${resourceObject} 0 R>>endobj\n";
283 0           push @{ $Gkids[0] }, $GpageObjNr;
  0            
284 0           return;
285             }
286              
287              
288             ##########################################################
289             sub writePage {
290             ##########################################################
291 0 0   0 0   if ( !$Gparents[0] ) {
292 0           ++$GobjNr;
293 0           $Gparents[0] = $GobjNr;
294             }
295 0           my $parent = $Gparents[0];
296 0           my $resourceObject = writePageResourceDict(createPageResourceDict());
297 0           writePageStream();
298 0           writePageResources($parent, $resourceObject);
299 0           ++$Gcounts[0];
300 0 0         writePageNodes(8) if $Gcounts[0] > 9;
301 0           return;
302             }
303              
304              
305             ##########################################################
306             sub closeOutputFile {
307             ##########################################################
308 0 0   0 0   return if !$Gpos;
309              
310 0 0         writePage() if $Gstream;
311 0           my $endNode = writeEndNode();
312              
313 0           my $out_line = "1 0 obj<>endobj\n";
314 0           $Gobject[1] = $Gpos;
315 0           $Gpos += syswrite $OUT_FILE, $out_line;
316 0           my $qty = $#Gobject;
317 0           my $startxref = $Gpos;
318 0           my $xrefQty = $qty + 1;
319 0           $out_line = "xref\n0 ${xrefQty}\n0000000000 65535 f \n";
320 0           $out_line .= sprintf "%.10d 00000 n \n", $_ for @Gobject[1..$qty];
321 0           $out_line .= "trailer\n<<\n/Size ${xrefQty}\n/Root 1 0 R\n"
322             . ">>\nstartxref\n${startxref}\n%%EOF\n";
323              
324 0           syswrite $OUT_FILE, $out_line;
325 0           close $OUT_FILE;
326              
327 0           $Gpos = 0;
328 0           return;
329             }
330              
331              
332             ##########################################################
333             sub writePageNodes {
334             ##########################################################
335 0     0 0   my $qtyChildren = $_[0];
336 0           my $i = 0;
337 0           my $j = 1;
338 0           my $nodeObj;
339              
340 0           while ( $qtyChildren < $#{ $Gkids[$i] } ) {
  0            
341             # Imprimir padre actual y pasar al siguiente nivel:
342 0 0         if ( !$Gparents[$j] ) {
343 0           ++$GobjNr;
344 0           $Gparents[$j] = $GobjNr;
345             }
346              
347             $nodeObj =
348 0           "${Gparents[$i]} 0 obj<
349 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$i] };
  0            
350 0           $nodeObj .= "]\n/Count ${Gcounts[$i]}>>endobj\n";
351 0           $Gobject[ $Gparents[$i] ] = $Gpos;
352 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
353              
354 0           $Gcounts[$j] += $Gcounts[$i];
355 0           $Gcounts[$i] = 0;
356 0           $Gkids[$i] = [];
357 0           push @{ $Gkids[$j] }, $Gparents[$i];
  0            
358 0           undef $Gparents[$i];
359 0           ++$i;
360 0           ++$j;
361             }
362 0           return;
363             }
364              
365              
366             ##########################################################
367             sub writeEndNode {
368             ##########################################################
369 0     0 0   my $nodeObj;
370 0           my $endNode = $Gparents[-1]; # content of the last element
371 0           my $si = $#Gparents; # index of the last element
372              
373 0 0         my $min = defined $Gparents[0] ? 0 : 1;
374 0           for ( my $i = $min ; $Gparents[$i] ne $endNode ; ++$i ) {
375 0 0         if ( defined $Gparents[$i] ) { # Only defined if there are kids
376             # Find parent of current parent:
377 0           my $node;
378 0           for ( my $j = $i + 1 ; ( !$node ) ; ++$j ) {
379 0 0         if ( $Gparents[$j] ) {
380 0           $node = $Gparents[$j];
381 0           $Gcounts[$j] += $Gcounts[$i];
382 0           push @{ $Gkids[$j] }, $Gparents[$i];
  0            
383             }
384             }
385              
386 0           $nodeObj = "${Gparents[$i]} 0 obj<
387 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$i] };
  0            
388 0           $nodeObj .= "]/Count ${Gcounts[$i]}>>endobj\n";
389 0           $Gobject[ $Gparents[$i] ] = $Gpos;
390 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
391             }
392             }
393              
394             # Arrange and print the end node:
395 0           $nodeObj = "${endNode} 0 obj<
396 0           $nodeObj .= "${_} 0 R " for @{ $Gkids[$si] };
  0            
397 0           $nodeObj .= "]/Count ${Gcounts[$si]}/MediaBox \[0 0 ";
398 0           $nodeObj .= "${GoWid} ${GoHei}\]>>endobj\n";
399 0           $Gobject[$endNode] = $Gpos;
400 0           $Gpos += syswrite $OUT_FILE, $nodeObj;
401 0           return $endNode;
402             }
403              
404              
405             ##########################################################
406             sub calcMatrix {
407             ##########################################################
408 0     0 0   my $x = $_[0]; my $y = $_[1]; my $rotate = $_[2];
  0            
  0            
409 0           my $str = "1 0 0 1 ${x} ${y} cm\n";
410              
411 0 0         if ($rotate) {
412 0           my $upperX = 0; my $upperY = 0;
  0            
413 0           my $radian = sprintf( "%.6f", $rotate / 57.2957795 ); # approx.
414 0           my $Cos = sprintf( "%.6f", cos($radian) );
415 0           my $Sin = sprintf( "%.6f", sin($radian) );
416 0           my $negSin = $Sin * -1;
417 0           $str .= "${Cos} ${Sin} ${negSin} ${Cos} ${upperX} ${upperY} cm\n";
418             }
419 0           return $str;
420             }
421              
422              
423             ##########################################################
424             sub getRoot {
425             ##########################################################
426 0     0 0   my ( $xref, $tempRoot, $buf );
427              
428 0           sysseek $IN_FILE, -50, 2;
429 0           sysread $IN_FILE, $buf, 100;
430 0 0         die "[!] File ${GinFile} is encrypted, cannot be used, aborting"
431             if $buf =~ m'Encrypt';
432 0 0         if ( $buf =~ m'\bstartxref\s+(\d+)' ) {
433 0           $xref = $1;
434             # stat[7] = filesize
435 0 0         die "[!] Invalid XREF, aborting" if $xref > (stat($GinFile))[7];
436 0           $tempRoot = getRootFromXrefSection( $xref );
437             }
438              
439 0 0         return 0 unless $tempRoot; # No Root object in ${GinFile}, aborting
440 0           saveOldObjects();
441 0           return $tempRoot;
442             }
443              
444              
445             ##########################################################
446             sub getRootFromXrefSection {
447             ##########################################################
448 0     0 0   my $xref = $_[0];
449 0           my ( $i, $rooty, $qty, $incoming_line, $buf );
450              
451 0           sysseek $IN_FILE, $xref += 5, 0;
452 0           ($incoming_line, $qty, $i) = extractContent();
453              
454 0           while ($qty) {
455 0           for ( my $l = 1 ; $l <= $qty ; ++$l ) {
456 0           sysread $IN_FILE, $incoming_line, 20;
457 0 0         if ( $incoming_line =~ m'^\s?(\d+) \d+ (\w)\s*' ) {
458 0 0 0       $GoldObject{$i} = int($1) unless $2 ne "n" or exists $GoldObject{$i};
459             }
460 0           ++$i;
461             }
462 0           ($incoming_line, $qty, $i) = extractContent();
463             }
464              
465 0           while ($incoming_line) {
466 0           $buf .= $incoming_line;
467 0 0         $rooty = $1 if $buf =~ m'\/Root\s+(\d+)\s{1,2}\d+\s{1,2}R'so;
468 0 0         last if $rooty;
469 0           sysread $IN_FILE, $incoming_line, 30;
470             }
471              
472 0           return $rooty;
473             }
474              
475              
476             ##########################################################
477             sub getObject {
478             ##########################################################
479 0     0 0   my $index = $_[0];
480 0 0         return 0 if ! defined $GoldObject{$index};
481 0           my $buf;
482 0           my ( $offs, $size ) = @{ $GoldObject{$index} };
  0            
483              
484 0           sysseek $IN_FILE, $offs, 0;
485 0           sysread $IN_FILE, $buf, $size;
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__