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   66428 use v5.10;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         34  
5             # use warnings;
6             our $VERSION = "v1.01";
7              
8 1     1   14 use Exporter;
  1         3  
  1         5193  
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 ($pagesPerSheet, @x, @y);
129 0           for ($pgSizeInput) {
130 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          
131 0           elsif ($_ eq "A5") { $pagesPerSheet = 2; @x = @X_A5_ON_A4; @y = @Y_A5_ON_A4; }
  0            
  0            
132 0           elsif ($_ eq "QT") { $pagesPerSheet = 4; @x = @X_QT_ON_LT; @y = @Y_QT_ON_LT; }
  0            
  0            
133 0           elsif ($_ eq "QG") { $pagesPerSheet = 4; @x = @X_QG_ON_LG; @y = @Y_QG_ON_LG; }
  0            
  0            
134 0           elsif ($_ eq "HT") { $pagesPerSheet = 2; @x = @X_HT_ON_LT; @y = @Y_HT_ON_LT; }
  0            
  0            
135 0           elsif ($_ eq "HG") { $pagesPerSheet = 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 $num_pliegos = $num_pag_input >> 4;
142 0           my ($rot_extra, @p);
143 0 0         if ($pagesPerSheet == 4) {
144 0           $rot_extra = 0;
145 0 0         if ($num_pag_input >= 13) { @p = @P_4UP_13PLUS; }
  0 0          
    0          
146 0           elsif ($num_pag_input >= 9 ) { @p = @P_4UP_9PLUS; }
147 0           elsif ($num_pag_input >= 5 ) { @p = @P_4UP_5PLUS; }
148 0           else { @p = @P_4UP_1PLUS; }
149             } else {
150 0           $rot_extra = 90;
151 0 0         if ($num_pag_input >= 13) { @p = @P_2UP_13PLUS; }
  0 0          
    0          
152 0           elsif ($num_pag_input >= 9 ) { @p = @P_2UP_9PLUS; }
153 0           elsif ($num_pag_input >= 5 ) { @p = @P_2UP_5PLUS; }
154 0           else { @p = @P_2UP_1PLUS; }
155             }
156 0           my ($rotation, $target_page);
157 0           for (0..$num_pliegos) {
158 0           for (0..15) {
159 0 0         newPageInOutputFile() if $_ % $pagesPerSheet == 0;
160 0           $target_page = $p[$_] + ($numPagImposed >> 4) * 16;
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;
538 0           $GoHei = $DH;
539 0           return "QT";
540             }
541 0 0 0       if ($multi > 123_443 and $multi < 125_443) { # ISO A6: 124443
542 0           $GoWid = $AW;
543 0           $GoHei = $AH;
544 0           return "A6";
545             }
546 0 0 0       if ($multi > 152_720 and $multi < 154_720) { # US 1/4 legal: 153720
547 0           $GoWid = $GW;
548 0           $GoHei = $GH;
549 0           return "QG";
550             }
551 0 0 0       if ($multi > 241_352 and $multi < 243_352) { # US 1/2 Letter ("statement"): 242352
552 0           $GoWid = $DW;
553 0           $GoHei = $DH;
554 0           return "HT";
555             }
556 0 0 0       if ($multi > 248_305 and $multi < 250_305) { # ISO A5: 249305
557 0           $GoWid = $AW;
558 0           $GoHei = $AH;
559 0           return "A5";
560             }
561 0 0 0       if ($multi > 307_448 and $multi < 309_448) { # US 1/2 legal: 308448
562 0           $GoWid = $GW;
563 0           $GoHei = $GH;
564 0           return "HG";
565             }
566 0 0 0       if ($multi > 483_704 and $multi < 485_704) { # US letter: 484704
567 0           return "USletter, ${measuresInMm}";
568             }
569 0 0 0       if ($multi > 499_395 and $multi < 501_395) { # ISO A4: 500395
570 0           return "A4, ${measuresInMm}";
571             }
572 0 0 0       if ($multi > 615_896 and $multi < 617_896) { # US legal: 616896
573 0           return "USlegal, ${measuresInMm}";
574             }
575 0           return "unknown, ${measuresInMm}";
576             }
577              
578              
579             ##########################################################
580             sub getPage {
581             ##########################################################
582 0     0 0   my $pagenumber = $_[0];
583              
584 0           my ( $reference, $formRes, $formCont );
585              
586             # Find root:
587 0           my $elObje = getObject($Groot);
588              
589             # Find pages:
590 0 0         die "[!] Didn't find Pages section in '${GinFile}' - aborting"
591             unless $elObje =~ m'/Pages\s+(\d+)\s{1,2}\d+\s{1,2}R'os;
592 0           $elObje = getObject($1);
593              
594 0           $elObje = xformObjForThisPage($elObje, $pagenumber);
595 0           ($formRes, $formCont) = getResources( $elObje );
596              
597 0           $reference = writeRes($elObje, $formRes, $formCont);
598              
599 0           writeToBeCreated($elObje);
600              
601 0           return $reference;
602             }
603              
604              
605             ##########################################################
606             sub writeRes {
607             ##########################################################
608 0     0 0   my ($elObje, $formRes, $formCont) = ($_[0], $_[1], $_[2]);
609 0           my $out_line;
610              
611 0           $elObje = getObject($formCont);
612 0           $elObje =~ m'^(\d+ \d+ obj\s*<<)(.+)(>>\s*stream)'so;
613 0           my $strPos = length($1) + length($2) + length($3);
614 0           my $newPart = "<
615             . "/BBox \[ $GformBox[0] $GformBox[1] $GformBox[2] $GformBox[3]\] ${2}";
616 0           ++$GobjNr;
617 0           $Gobject[$GobjNr] = $Gpos;
618 0           my $reference = $GobjNr;
619 0           renew_ddR_and_populate_to_be_created($newPart);
620 0           $out_line = "${reference} 0 obj\n${newPart}>>\nstream";
621 0           $out_line .= substr( $elObje, $strPos );
622 0           $Gpos += syswrite $OUT_FILE, $out_line;
623 0           return $reference;
624             }
625              
626              
627             ##########################################################
628             sub xformObjForThisPage {
629             ##########################################################
630 0     0 0   my $elObje = $_[0]; my $pagenumber = $_[1];
  0            
631 0           my ( $vector, @pageObj, @pageObjBackup, $pageAccumulator);
632              
633 0 0         return 0 unless $elObje =~ m'/Kids\s*\[([^\]]+)'os;
634 0           $vector = $1;
635              
636 0           $pageAccumulator = 0;
637              
638 0           push @pageObj, $1 while $vector =~ m'(\d+)\s{1,2}\d+\s{1,2}R'go;
639 0           while ( $pageAccumulator < $pagenumber ) {
640 0           @pageObjBackup = @pageObj;
641 0           undef @pageObj;
642 0           for (@pageObjBackup) {
643 0           $elObje = getObject($_);
644 0 0         if ( $elObje =~ m'/Count\s+(\d+)'os ) {
645 0 0         if ( ( $pageAccumulator + $1 ) < $pagenumber ) {
646 0           $pageAccumulator += $1;
647             } else {
648 0 0         $vector = $1 if $elObje =~ m'/Kids\s*\[([^\]]+)'os ;
649 0           push @pageObj, $1 while $vector =~ m'(\d+)\s{1,2}\d+\s{1,2}R'gso;
650 0           last;
651             }
652             } else {
653 0           ++$pageAccumulator;
654             }
655 0 0         last if $pageAccumulator == $pagenumber;
656             }
657             }
658 0           return $elObje;
659             }
660              
661              
662             ##########################################################
663             sub getResources {
664             ##########################################################
665 0     0 0   my $obj = $_[0];
666 0           my ($resources, $formCont);
667              
668             # Assume all input PDF pages have the same dimensions as first MediaBox found:
669 0 0         if (! @GformBox) {
670 0 0         if ( $obj =~ m'MediaBox\s*\[\s*([\S]+)\s+([\S]+)\s+([\S]+)\s+([\S]+)\s*\]'os ) {
671 0           @GformBox = ($1, $2, $3, $4);
672             }
673             }
674              
675 0 0         if ( $obj =~ m'/Contents\s+(\d+)'so ) {
    0          
676 0           $formCont = $1;
677             } elsif ( $obj =~ m'/Contents\s*\[\s*(\d+)\s{1,2}\d+\s{1,2}R\s*\]'so ) {
678 0           $formCont = $1;
679             }
680              
681 0           $resources = getResourcesFromObj($obj);
682              
683 0           return ($resources, $formCont);
684             }
685              
686              
687             ##########################################################
688             sub getResourcesFromObj {
689             ##########################################################
690 0     0 0   my $obj = $_[0];
691 0           my $resources;
692              
693 0 0         if ( $obj =~ m'^(.+/Resources)'so ) {
694 0 0         return $1 if $obj =~ m'Resources(\s+\d+\s{1,2}\d+\s{1,2}R)'os; # Reference (95%)
695             # The resources are a dictionary. The whole is copied (morfologia.pdf):
696 0           my $k;
697 0           ( undef, $obj ) = split /\/Resources/, $obj;
698 0           $obj =~ s/<
699 0           $obj =~ s/>>/>>#/gs;
700 0           for ( split /#/, $obj ) {
701 0 0         if ( m'\S's ) {
702 0           $resources .= $_;
703 0 0         ++$k if m'<<'s;
704 0 0         --$k if m'>>'s;
705 0 0         last if $k == 0;
706             }
707             }
708             }
709 0           return $resources;
710             }
711              
712              
713             ##########################################################
714             sub openInputFile {
715             ##########################################################
716 0     0 0   $GinFile = $_[0];
717 0           my ( $elObje, $inputPageSize );
718              
719 0 0         open( $IN_FILE, q{<}, $GinFile )
720             or die "[!] Couldn't open ${GinFile}";
721 0           binmode $IN_FILE;
722              
723             # Find root
724 0           $Groot = getRoot();
725              
726             # Find input page size:
727 0           $inputPageSize = getInputPageDimensions();
728              
729             # Find pages
730 0 0         return 0 unless eval { $elObje = getObject($Groot); 1; };
  0            
  0            
731 0 0         if ( $elObje =~ m'/Pages\s+(\d+)\s{1,2}\d+\s{1,2}R'os ) {
732 0           $elObje = getObject($1);
733 0 0         return ($1, $inputPageSize) if $elObje =~ m'/Count\s+(\d+)'os;
734             }
735 0           return 0;
736             }
737              
738              
739             ##########################################################
740             sub saveOldObjects {
741             ##########################################################
742 0     0 0   my $bytes = (stat($GinFile))[7]; # stat[7] = filesize
743             # Objects are sorted numerically (<=>) and in reverse order ($b $a)
744             # according to their offset in the file: last first
745 0           my @offset = sort { $GoldObject{$b} <=> $GoldObject{$a} } keys %GoldObject;
  0            
746              
747 0           my $saved;
748              
749 0           for (@offset) {
750 0           $saved = $GoldObject{$_};
751 0           $bytes -= $saved;
752 0 0         $GoldObject{$_} = [ $saved, $bytes ] if ($_ !~ m'^xref');
753 0           $bytes = $saved;
754             }
755             }
756              
757              
758             ##########################################################
759             sub renew_ddR_and_populate_to_be_created {
760             ##########################################################
761             # $xform translates an old object number to a new one
762             # and populates a table with what must be created
763 0 0   0     my $xform = sub { return $Gold{$1} if exists $Gold{$1};
764 0           push @Gto_be_created, [ $1, ++$GobjNr ];
765 0           return $Gold{$1} = $GobjNr;
766 0     0 0   };
767 0           $_[0] =~ s/\b(\d+)\s{1,2}\d+\s{1,2}R\b/&$xform . ' 0 R'/oegs;
  0            
768 0           return;
769             }
770              
771              
772             ##########################################################
773             sub extractContent {
774             ##########################################################
775 0     0 0   my ($incoming_line, $qty, $i, $c);
776              
777 0           sysread $IN_FILE, $c, 1;
778 0           sysread $IN_FILE, $c, 1 while $c =~ m!\s!s;
779 0   0       while ( (defined $c) and ($c ne "\n") and ($c ne "\r") ) {
      0        
780 0           $incoming_line .= $c;
781 0           sysread $IN_FILE, $c, 1;
782             }
783 0 0         if ( $incoming_line =~ m'^(\d+)\s+(\d+)' ) {
784 0           $i = $1;
785 0           $qty = $2;
786             }
787              
788 0           return ($incoming_line, $qty, $i);
789             }
790              
791              
792             ##########################################################
793             sub openOutputFile {
794             ##########################################################
795 0 0   0 0   closeOutputFile() if $Gpos;
796              
797 0           my $outputfile = $_[0];
798 0           my $pdf_signature = "%PDF-1.4\n%\â\ã\Ï\Ó\n"; # Keep it far from file beginning!
799              
800 0 0         open( $OUT_FILE, q{>}, $outputfile )
801             or die "[!] Couldn't open file ${outputfile}";
802 0           binmode $OUT_FILE;
803 0           $Gpos = syswrite $OUT_FILE, $pdf_signature;
804              
805 0           $GobjNr = 2; # Objeto reservado 1 para raíz y 2 para nodo lateral inicial
806 0           $Gparents[0] = 2;
807              
808 0           setInitGrState();
809 0           return;
810             }
811              
812              
813             ##########################################################
814             sub closeInputFile {
815             ##########################################################
816 0     0 0   close $IN_FILE;
817             }
818              
819             1;
820              
821             __END__