File Coverage

blib/lib/PDF/Tiny.pm
Criterion Covered Total %
statement 515 613 84.0
branch 228 364 62.6
condition 81 136 59.5
subroutine 39 44 88.6
pod 0 30 0.0
total 863 1187 72.7


line stmt bran cond sub pod time code
1             package PDF::Tiny;
2              
3 1     1   1622 use 5.01;
  1         4  
4              
5             $VERSION = '0.09'; # Update the POD, too!
6              
7             # Fields
8             sub vers () { 0 }
9             sub fh () { 1 }
10             sub trai () { 2 } # trailer
11             sub id () { 3 } # original doc ID
12             sub stxr () { 4 } # startxref, used for /Prev when appending
13             sub file () { 5 } # file name
14             sub size () { 6 } # object count + 1
15             sub free () { 7 } # array of free object ids
16              
17             # Hash fields; must be consecutive
18             sub xrft () { 8 } # xref table
19             sub mods () { 9 } # modified objects
20             sub objs () {10 }
21              
22             sub impo () {12 } # imported objects
23              
24             sub DEBUG () { 0 }
25              
26             sub croak {
27 0     0 0 0 die "$_[0] at " . join(' line ', (caller(DEBUG ? 0 : 1+$_[1]))[1,2])
28             . ".\n";
29             }
30              
31             $null = ['null'];
32              
33 1     1   6 use warnings; no warnings qw 'numeric uninitialized';
  1     1   2  
  1         37  
  1         8  
  1         6  
  1         7349  
34              
35             # REGEXPS FOR PARSING
36              
37             $S = '[\0\t\cj\cl\cm ]'; # PDF whitespace chars
38             $_S = '[\0\t\cl ]'; #PDF whitespace chars except line breaks
39             $N = '(?:\cm\cj?|\cj)'; # PDF line break chars
40             $D = '[\(\)<>\[\]\{\}\/]'; # PDF delimiter characters (except %);
41             $R = '[^\0\t\cj\cl\cm \(\)<>\[\]\{\}\/%]'; # PDF regular characters
42              
43              
44             # CONSTRUCTOR
45              
46             sub new {
47 18     18 0 3979 my $class = shift;
48 18         50 my ($file, %opts);
49 18 100       59 if (@_ == 1) {
50 16         41 $file = shift;
51             }
52             else {
53 2         6 %opts = @_;
54 2         5 $file = $opts{filename};
55             }
56 18         53 my $self = [];
57 18         52 $self->[file] = $file;
58 18         105 $self->[$_] = {} for xrft..objs; # This is why they must be consecutive.
59 18         44 $self->[free] = [];
60 18         39 bless $self, $class;
61 18 100       48 if (defined $file) {
62 16 50       468 open my $fh, "<", $file or croak "Cannot open $file: $!";
63 16         68 binmode $self->[fh] = $fh;
64 16 50       290 defined read $fh, my $read, 1024 or croak "Cannot read $file: $!";
65 16 50       157 if ($read !~ /%PDF-([0-9.]+)/) {
66 0         0 croak "The file $file is not a PDF";
67             }
68 16         69 $self->[vers] = $1;
69 16         66 _parse_xref($self);
70 16         46 $self->[size] = $self->[trai][1]{Size}[1];
71 16 100       65 if ($self->[trai][1]{ID}) {
72 12         42 $self->[id] = $self->[trai][1]{ID}[1][0][1];
73             }
74             }
75             else {
76 2   100     11 $self->[vers] = $opts{version} || 1.4;
77 2         80 $self->[trai] = make_dict(my $trailer_hash = {});
78 2 50       10 if (!$opts{empty}) {
79 2         38 $$trailer_hash{Root} = make_ref("1 0");
80 2         36 @{$self->[objs]}{"1 0","2 0"} =
  2         9  
81             ( make_dict({
82             Type => make_name("Catalog"), Pages => make_ref("2 0")
83             }),
84             make_dict({
85             Type => make_name("Pages"),
86             Kids => make_array([]),
87             Count => make_num(0)
88             })
89             );
90 2         5 $self->[size] = 3;
91             }
92 0         0 else { $self->[size] = 1; }
93             }
94 18         101 $self;
95             }
96              
97             sub _parse_xref {
98 16     16   37 my($self) = shift;
99 16         45 my $fh = $self->[fh];
100 16 100       82 seek $fh, -1024,2 or seek $fh, 0,0;
101 16 50       107 read $fh, my $read, 1024
102             or croak "Cannot read $self->[file]: $!", 1;
103 16         166 $read =~ /startxref$N(\d+)$N%%EOF$N?$/o;
104              
105 16         60 $self->[stxr] = my $startxref = $1;
106 16         36 my $xref = $self->[xrft];
107            
108 16         25 my $trailer;
109 16         48 while(defined $startxref){
110             # read from the position indicated by $startxref, up to the word
111             # "startxref"
112            
113 17 50       89 seek $fh, $startxref, 0
114             or croak "Cannot seek in $self->[file]: $!",1;
115 17 50       185 read $fh, my $read, 1024, length $read
116             or croak "Cannot read $self->[file]: $!", 1;
117 17 100       347 if ($read =~ /^$S*[0-9]/o) { # cross-reference stream
118 3         18 my $obj = _read_obj($self, $startxref);
119 3         20 my $stream = $self->decode_stream($obj);
120 3         16 $trailer = $$obj[1];
121 3         9 my $hash = $$trailer[1];
122 3         8 my @widths = map $$_[1], @{$$hash{W}[1]};
  3         34  
123 3         15 my $width = $widths[0] + $widths[1] + $widths[2];
124 3         26 my $unpack = join '', map "H".$_*2, @widths;
125             my @indices = $$hash{Index}
126 1         10 ? map $$_[1], @{$$hash{Index}[1]}
127 3 100       18 : (0, $$hash{Size}[1]);
128 3         16 my ($ix, $last) = splice @indices, 0, 2;
129 3         12 $last += $ix - 1;
130 3         18 while (length $stream) {
131 33         290 my($type,$where,$gen)
132             = map hex,
133             unpack $unpack,
134             substr $stream, 0,
135             $width, '';
136 33 50       136 $widths[0] or $type = 1;
137              
138 33 100       120 if ($type == 1) {
    100          
139 17         65 my $obj_ref = "$ix $gen";
140             !exists $$xref{$obj_ref}
141 17 50       101 and $$xref{$obj_ref} = $where;
142             }
143             elsif ($type == 2) {
144 14         36 my $obj_ref = "$ix 0";
145             !exists $$xref{$obj_ref}
146 14 50       96 and $$xref{$obj_ref} =
147             ["$where 0", $gen];
148             }
149             else { # free
150 2 50 33     15 push @{$self->[free]}, "$ix $gen"
  0         0  
151             if $ix && $gen != 65535
152             }
153 33 50       203 if ($ix++ > $last) {
154 0         0 ($ix, $last) = splice @indices,0,2;
155 0         0 $last += $ix - 1;
156             }
157             }
158             }
159             else {
160 14         76 while($read !~ /startxref/){
161 0 0       0 read $fh, $read, 1024, length $read
162             or croak "Cannot read $self->[file]: $!", 1;
163             }
164 14         85 $read =~ /xref(.*?)trailer(.*)/s;
165 14         52 my $xreftext =$1;
166              
167 14         116 $trailer = parse_string("$2",qr/^startxref\z/);
168              
169             # remove initial line, and read the numbers,
170             # repeating as necessary
171              
172 14         167 while ($xreftext=~ s/^$N?(\d+) (\d+).*?$N//o) {
173 14         90 for ($1..$1+$2-1) {
174             #$xreftext =~ s/(.{20})//s; # get 20 bytes
175 282         509 my $_1 = substr($xreftext,0,20,'');
176 282         551 my $obj_ref = "$_ " . substr($_1,11,5)*1;
177 282 100       501 if (substr ($_1, 17,1) eq 'n') {
178             !exists $$xref{$obj_ref}
179 251 50       773 and $$xref{$obj_ref} =
180             substr($_1,0,10);
181             # (See PDF Reference [5th ed.], p. 70.)
182             }
183             else { # free
184 31 100       97 push @{$self->[free]}, $obj_ref
  17         42  
185             unless substr($_1,11,5) == 65535
186             }
187             }
188             }
189             }
190 17 100       69 unless ($self->[trai]) {
191 16         38 $self->[trai] = $trailer;
192             exists $$trailer[1]{Encrypt}
193 16 50       58 and croak "$self->[file] is encrypted", 1;
194             }
195              
196 17         36 $trailer = $$trailer[1];
197 17 100       111 $startxref = defined $$trailer{Prev} ? $$trailer{Prev}[1] : undef;
198             }
199              
200             }
201              
202             # HIGH-LEVEL METHODS
203              
204             sub page_count {
205 1     1 0 10 $_[0]->get_obj("/Root", "/Pages", "/Count")->[1]
206             }
207              
208             sub _walk_pages {
209 11     11   15 my $self = shift;
210 11   50     42 my $pages = shift || $self->get_obj("/Root", "/Pages")
211             || return wantarray ? () : 0;
212 11         18 my @pages; # output
213 11         28 my $kids = $self->get_obj($pages, "/Kids");
214 11 50       31 if ($self->get_obj($pages, "/Count")->[1] == @{$$kids[1]}) {
  11         38  
215 11         17 return @{$$kids[1]}
  11         38  
216             }
217 0         0 my $kid;
218 0         0 for (0 .. $#{$$kids[1]}){
  0         0  
219 0         0 $kid = $$kids[1][$_];
220 0 0       0 push @pages, ${$self->get_obj($kid, '/Type')}[1] eq 'Pages'
  0         0  
221             ? _walk_pages($self, $kid)
222             : $kid;
223             }
224 0         0 return @pages;
225             }
226              
227             sub delete_page {
228 0     0 0 0 my ($self, $num,) = @'_;
229 0         0 my $root = $self->get_obj("/Root");
230 0         0 my $pages_id = $$root[1]{Pages}[1];
231 0         0 my $pages = $self->get_obj($pages_id);
232 0         0 my $pages_array = $self->get_obj($pages, '/Kids');
233 0         0 my $count = $self->get_obj($pages, "/Count");
234 0 0       0 if (@{$pages_array->[1]} != $count->[1]) {
  0         0  
235             # Flatten the pages array. Other structures just require too much code.
236 0         0 _flatten_pages($self, $pages_id, $pages, $pages_array);
237             }
238 0         0 splice @{$pages_array->[1]}, $num, 1;
  0         0  
239 0         0 $count->[1]--;
240             _:
241             }
242              
243             sub import_page {
244 4     4 0 33 my ($self, $source_pdf, $num, $whither) = @'_;
245 4         16 my @pages = _walk_pages($source_pdf);
246 4   33     24 my $page_to_import =
247             $source_pdf->get_obj(($pages[$num] || croak "No such page: $num")->[1]);
248              
249             # We cannot simply use import_obj. import_obj will follow the /Parent
250             # link and import the entire page tree from the source PDF.
251             # Furthermore, if the values of /Resources, /MediaBox and /CropBox are
252             # inherited from the parent node that we are not importing, they need to
253             # be transferred to the page object itself.
254 4         14 my $temp_copy = [@$page_to_import];
255 4         8 $temp_copy->[1] = {%{ $temp_copy->[1] }};
  4         20  
256 4         12 my $node = $temp_copy;
257 4   33     45 while (!$temp_copy->[1]{Resources} || !$temp_copy->[1]{MediaBox}
      66        
258             || !$temp_copy->[1]{CropBox} and $node->[1]{Parent}) {
259 4         15 $node = $source_pdf->get_obj($node, '/Parent');
260             $node->[1]{$_} and !$temp_copy->[1]{$_}
261             and $temp_copy->[1]{$_} = $node->[1]{$_}
262 4   66     72 for qw< Resources MediaBox CropBox >;
      50        
263             }
264 4         10 delete $temp_copy->[1]{Parent};
265 4         17 my $page_id =
266             $self->add_obj(my $real_copy=$self->import_obj($source_pdf, $temp_copy));
267            
268 4         17 my $root = $self->get_obj("/Root");
269 4         14 my $pages_id = $$root[1]{Pages}[1];
270 4         18 $real_copy->[1]{Parent} = ['ref',$pages_id];
271 4         13 my $pages = $self->get_obj($pages_id);
272 4         13 my $pages_array = $self->get_obj($pages, '/Kids');
273 4         12 my $count = $self->get_obj($pages, "/Count");
274 4 50       11 if (@{$pages_array->[1]} != $count->[1]) {
  4         15  
275             # Flatten the pages array. Other structures just require too much code.
276 0         0 _flatten_pages($self, $pages_id, $pages, $pages_array);
277             }
278 4   66     7 splice @{$pages_array->[1]}, $whither//@{$pages_array->[1]}, 0,
  4         15  
  4         21  
279             ['ref',$page_id];
280 4         7 $count->[1]++;
281             _:
282             }
283             sub _flatten_pages {
284 0     0   0 my ($self, $pages_id, $pages, $pages_array) = @ '_;
285 0         0 my @pages = _walk_pages($self, $pages);
286 0         0 for (@pages) {
287 0         0 my $page = $self->get_obj($_);
288 0 0       0 next if $page->[1]{Parent}[1] eq $pages_id;
289 0         0 my $node = $page;
290 0   0     0 while (!$page->[1]{Resources} || !$page->[1]{MediaBox}
      0        
291             || !$page->[1]{CropBox} and $node->[1]{Parent}[1] ne $pages_id) {
292 0         0 $node = $self->get_obj($node, '/Parent');
293             $node->[1]{$_} and $page->[1]{$_} = $node->[1]{$_}
294 0   0     0 for qw< Resources MediaBox CropBox >;
295             }
296 0         0 $page->[1]{Parent}[1] = $pages_id;
297             }
298 0         0 $pages_array->[1] = \@pages;
299             }
300              
301             sub append {
302 4     4 0 36 my $self = shift;
303 4 50       16 if (!defined $self->[file]) {
304 0         0 croak "No file to write to!"
305             }
306 4 50       7 if (!%{$self->[mods]}) {
  4         26  
307 0         0 return;
308             }
309 4 50       16 if ($self->[trai][1]{Type}) {
310 0         0 croak "Cannot append to files with cross-reference streams";
311             }
312 4 50       116 open my $fh, ">>", $self->[file]
313             or croak "Cannot open $self->[file] for writing: $!";
314 4         14 binmode $fh;
315 4         19 local ($\,$,);
316 4         40 print $fh "\n"; # The existing %%EOF might not have \n after it
317              
318             # Update the doc ID now. If it already exists, it might be an indirect
319             # object, in which case changes to it must included in the objects that we
320             # append to the file before we reach the trailer.
321 4         19 my $id_array = $self->vivify_obj('array',"/ID");
322 4 50 33     8 if (@{$$id_array[1]} == 2
  4   33     22  
323             and $self->vivify_obj('str', $id_array, 0)->[1] ne $self->[id]
324             || $self->vivify_obj('str', $id_array, 1)->[1] ne $self->[id]) {
325             # User has assigned his own id. Leave it alone.
326             }
327             else {
328 4   33     12 $self->vivify_obj('str', $id_array, 0)->[1]
329             ||= time."" ^ "".rand ^ "".(0+$self);
330 4         12 $self->vivify_obj('str', $id_array, 1)->[1]
331             ^= time."" ^ "".rand ^ "".(0+$self);
332 4         12 @{$$id_array[1]} = @{$$id_array[1]}[0,1];
  4         11  
  4         13  
333             }
334              
335 4         8 my %offsets;
336 4         8 my @ids = grep $self->[objs]{$_}, sort {$a<=>$b} keys %{$self->[mods]};
  2         15  
  4         28  
337 4         13 for (@ids) {
338 4         10 my $obj = $self->[objs]{$_};
339 4         67 $offsets{$_} = tell $fh;
340            
341 4 100       14 if ($$obj[0] eq 'stream') {
342 1         4 print $fh join_tokens(
343             $_,'obj',
344             _serialize($obj)
345             ), $$obj[2], "\nendstream endobj\n"
346             }
347             else {
348 3         11 print $fh join_tokens(
349             $_,'obj',
350             _serialize($obj),
351             "endobj"
352             ), "\n";
353             }
354             }
355 4         47 my $startxref = tell $fh;
356 4         11 print $fh "xref\n";
357             # Divide the ids into chunks of consecutive numbers
358 4         17 my @chunks = ['0 65535'];
359 4         12 $offsets{'0 65535'} = $self->[free][0];
360 4         11 for (@ids) {
361 4 100       18 if ($chunks[-1][-1] + 1 != $_) {
362 3         8 push @chunks, [];
363             }
364 4         5 push @{$chunks[-1]}, $_
  4         11  
365             }
366 4         10 for (@chunks) {
367 7         35 printf $fh "%d %s\n", $$_[0], scalar @$_;
368             printf $fh "%010d %05d %s \n",
369             $offsets{$_}, /\ (\d+)/, $_ == 0 ? "f" : "n"
370 7 100       66 for @$_;
371             }
372 4         10 my $trailerhash = $self->[trai]->[1];
373 4         15 $trailerhash->{Prev} = ['num', $self->[stxr]];
374 4         14 $trailerhash->{Size} = ['num', $self->[size]];
375 4         15 print $fh join_tokens(trailer=>serialize($self->[trai])),
376             "\nstartxref\n$startxref\n%%EOF\n";
377 4 50       116 close $fh or croak "Cannot close $self->[file]: $!";
378             }
379              
380             sub print {
381 4     4 0 1460 my $self = shift;
382 4         18 my %args = @_;
383 4   66     28 $args{fh} // $args{filename} // croak "No file to write to!";
      50        
384 4         7 my $fh;
385 4 100       15 if ($args{filename}) {
386             open $fh, ">", $args{filename}
387 2 50       187 or croak "Cannot open $args{filename} for writing: $!";
388             }
389 2         4 else { $fh = $args{fh} }
390 4         17 binmode $fh;
391 4         21 local ($\,$,);
392 4         20 my $pos = length(my $buf = "%PDF-$self->[vers]\n%\xff\xff\xff\xff\n");
393 4         19 print $fh $buf;
394              
395             # Generate the doc ID now. If it already exists, it might be an indirect
396             # object, in which case changes to it must included in the objects that we
397             # append to the file before we reach the trailer.
398 4         19 my $id_array = $self->vivify_obj('array',"/ID");
399 4 100 66     9 if (@{$$id_array[1]} == 2
  4         25  
400             and $self->vivify_obj('str', $id_array, 0)->[1] ne $self->[id]) {
401             # User has assigned his own id. Leave it alone.
402             }
403             else {
404 1         22 @{$$id_array[1]} = (['str', time."" ^ "".rand ^ "".(0+$self)])x2;
  1         4  
405             }
406              
407             # We assume that if this points to a cross-reference stream’s dictionary
408             # then we will not be emitting that cross-reference stream.
409 4         15 delete @{ $self->[trai][1] }{qw< XRefStm Length Filter DecodeParms F
  4         23  
410             FFilter FDecodeParms DL Type Size Index
411             Prev W >};
412              
413 4         18 my @trailer = _serialize($self->[trai]);
414 4         12 my %seen;
415             my @ids;
416 4         17 for (2..$#trailer) {
417 48 100       104 next unless $trailer[$_] eq 'R';
418 7         36 my $id = sprintf '%d %d',@trailer[$_-2,$_-1];
419 7 50       24 next if $seen{$id}++;
420 7         15 push @ids, $id;
421             }
422 4         11 my %offsets;
423 4         14 while (@ids) {
424 50         124 my $id = shift @ids;
425 50         133 my $del = !$self->[objs]{$id};
426 50 50       170 my $obj = $self->get_obj($id) or next;
427             my @tokens = (my $flat = $obj->[0] eq 'flat')
428             ? tokenize($obj->[1],qr/^(?:endobj|stream)\z/)
429 50 50       299 : $obj->[0] eq 'tokens' ? @{$obj->[1]} : _serialize($obj);
  0 50       0  
430 50         191 for (2..$#tokens) {
431 501 100       1246 next unless $tokens[$_] eq 'R';
432 54         257 my $id = sprintf '%d %d',@tokens[$_-2,$_-1];
433 54 100       199 next if $seen{$id}++;
434 43         117 push @ids, $id;
435             }
436 50         163 $offsets{$id} = $pos;
437 50 100       148 if ($$obj[0] eq 'stream') {
438 8         30 $pos += length($buf = join_tokens(
439             $id,'obj',
440             @tokens
441             ) . $$obj[2] . "\nendstream endobj\n"
442             );
443 8         59 print $fh $buf;
444             }
445             else {
446 42         142 $pos += length ($buf = join_tokens(
447             $id,'obj',
448             @tokens,
449             "endobj"
450             ) . "\n"
451             );
452 42         245 print $fh $buf;
453             }
454 50 100       525 delete $self->[objs]{$id} if $del; # Avoid reading the whole file into
455             } # memory at once.
456 4         43 for (sort {$a<=>$b} keys %offsets) {
  146         243  
457 50         80 $ids[$_] = $_;
458             }
459 4         15 my @free = $ids[0] = '0 65535';
460 4         15 for (1..$#ids-1) {
461 58 100       115 next if $ids[$_];
462 12         25 push @free, $ids[$_] = "$_ 0";
463             }
464 4         9 my %next_free;
465 4         13 for (0..$#free) {
466 16         45 $next_free{$free[$_]} = 0+$free[$_+1];
467             }
468 4         13 my $startxref = $pos;
469 4         22 printf $fh "xref\n0 %d\n", scalar @ids;
470 4         14 for (@ids) {
471             printf $fh "%010d %05d %s \n",
472             exists $next_free{$_}
473             ? ($next_free{$_}, /\ (\d+)/, "f")
474 66 100       363 : ($offsets {$_}, /\ (\d+)/, "n")
475             }
476 4         14 my $trailerhash = $self->[trai]->[1];
477 4         13 delete $trailerhash->{Prev};
478 4         19 $trailerhash->{Size} = ['flat', 1+$ids[-1]];
479 4         17 print $fh join_tokens(trailer=>serialize($self->[trai])),
480             "\nstartxref\n$startxref\n%%EOF\n";
481 4 100       63 if ($args{filename}) {
482 2 50       133 close $fh or croak "Cannot close $args{filename}: $!";
483             }
484             }
485              
486             # LOW-LEVEL METHODS
487              
488 1     1 0 10 sub version :lvalue { $_[0][vers] }
489             #sub xref { $_[0][xrft] }
490              
491             sub modified {
492 4     4 0 27 my $self = shift;
493 4 50       12 @_ or return $self->[mods];
494 4 100 66     28 if (@_ == 1 && $_[0] !~ m.^/.) {
495 2 50 66     15 croak "$_[0] is not an object id"
496             unless $_[0] =~ /^[0-9]+ [0-9]+\z/ || $_[0] eq 'trailer';
497 2         8 $self->[mods]{$_[0]}++;
498             return
499 2         5 }
500 2         8 my (undef, $last_ref) = _get_obj($self, 0, @_);
501 2 50       16 $last_ref and $self->[mods]{$last_ref}++;
502 2         7 $self->[mods];
503             }
504              
505 0     0 0 0 sub objects { $_[0][objs] }
506 2     2 0 67 sub trailer { $_[0][trai] }
507              
508             sub read_obj {
509 71     71 0 127 my $self = shift;
510 71         123 my $id = shift;
511 71   100     108 { return $self->[objs]{$id} || next }
  71         211  
512 66 50       341 croak "$id is not a valid id" unless $id =~ /^[0-9]+ [0-9]+\z/;
513 66 50       187 if (!$self->[fh]) {
514 0         0 croak "No file open";
515             }
516 66   50     215 my $loc = $self->[xrft]{$id} || return $null;
517 66 50       174 if (ref $loc) { # handle object streams here
518 0         0 my ($strmid, $ix) = @$loc;
519             # Since we have to decompress the stream and calculate the offsets, let’s
520             # go ahead and extract all the objects into the objects hash, in flat
521             # format. We may have reached this code because somebody manually
522             # deleted an objects entry in order to re-read it, so only extract
523             # objects that are not already in the hash.
524 0         0 my $obj = $self->get_obj($strmid);
525 0         0 my $stream = \$self->decode_stream($obj);
526 0         0 my $count = $self->get_obj($$obj[1], "/N")->[1];
527 0         0 my $first = $self->get_obj($$obj[1], "/First")->[1];
528 0         0 my @numbers =
529             split /(?:$S++|%[^\cm\cj]*[\cm\cj])+/, substr $$stream, 0, $first, '';
530 0         0 while (@numbers) {
531 0         0 my ($id, $off) = splice @numbers, 0, 2;
532 0         0 $id .= " 0";
533 0 0 0     0 $self->[objs]{$id} ||=
534             ['flat',
535             substr $$stream, $off, @numbers ? $numbers[1]-$off : length $$stream]
536             }
537 0         0 return $self->[objs]{$id}
538             }
539             # otherwise use the seek-and-read approach
540 66         176 _read_obj($self, $loc, $id);
541             }
542             sub _read_obj {
543 69     69   186 my ($self, $loc, $id) = @_;
544 69         333 seek $self->[fh], $loc, 0;
545 69 50       818 read $self->[fh], my $buf, 1024 or croak "Cannot read $self->[file]: $!";
546              
547             my @tokens = tokenize($buf, qr/^(?:endobj|stream)\z/,
548             sub {
549 177 50   177   3867 defined read $self->[fh], $buf, 1024, length $buf
550             or croak "Cannot read $self->[file]: $!"
551 69         588 });
552 69         614 my $read_id = 0+shift(@tokens) . ' ' . (0+shift@tokens);
553 69 50 66     355 if ($id and $read_id ne $id) {
554 0         0 croak "$self->[file]: Found $read_id at offset $loc instead of $id";
555             }
556 69         130 shift @tokens; # remove “obj”
557 69         118 my $obj;
558 69 100       191 if ($tokens[-1] eq 'stream') {
559 11         46 my $pos = tell $self->[fh];
560 11         40 $obj = _interpret_token(\@tokens);
561 11         93 $buf =~ s/^\cm?\cj//;
562             # Create the new obj now, to avoid having to copy a huge buffer on pre-
563             # COW perls.
564 11         54 my $new_obj = ['stream', $obj, $buf];
565             # Have to use get_obj here, not $obj[1]{Length}[1], as /Length could be
566             # an indirect reference.
567 11         48 my $stream_length = $self->get_obj($obj, '/Length')->[1];
568 11 100       62 if (length $buf < $stream_length) {
569 4         18 seek $self->[fh], $pos, 0;
570 4 50       54 read $self->[fh], $new_obj->[2], $stream_length-length $buf, length $buf
571             or croak "Cannot read $self->[file]: $!";
572             }
573             else {
574 7         35 substr $new_obj->[2], $stream_length, = '';
575             }
576 11         39 $obj = $new_obj;
577             }
578             else {
579 58         102 pop @tokens; # remove ‘endobj’
580 58         164 $obj = ['tokens', \@tokens];
581             }
582 69         407 $self->[objs]{$read_id} = $obj
583             }
584              
585             sub get_obj {
586 152     152 0 405 splice @_, 1, 0, 0;
587 152         317 (&_get_obj)[0]
588             }
589             sub _get_obj {
590 184     184   284 my $self = shift;
591 184         295 my $vivify = shift;
592 184         314 my $obj = shift;
593             # $obj may be any of:
594             # • "4 0"
595             # • "/Root"
596             # • ['dict', { ... }]
597             # • ['array', { ... }]
598             # • ['ref', "4 0 R"]
599             # • ['anything else', ...]
600 184         280 my $lastref;
601             {
602 184 100       255 if (ref $obj) {
  259 100       825  
603 126 100       339 if ($$obj[0] eq 'ref') {
604 36         72 $obj = $$obj[1]; redo
605 36         59 }
606             }
607             elsif ($obj =~ m.^/.) {
608 39         114 my $subobj = $self->[trai][1]{substr $obj, 1};
609 39 100       94 if (!$subobj) {
610 2 50       8 if ($vivify) {
611 2 50       14 $obj = $self->[trai][1]{substr $obj, 1} =_viv($vivify, @_ ? $_[0]: ())
612             }
613             else {
614             return
615 0         0 }
616             }
617 37         79 else { $obj = $subobj }
618 39         69 redo; # $obj may be ['ref', '1894 0']
619             }
620             else {
621 94         173 $lastref = $obj;
622 94   66     356 $obj = $self->[objs]{$obj} || $self->read_obj($obj);
623             }
624             }
625 184 50       439 $obj or return;
626 184         457 while (@_) {
627 107 100       252 if ($$obj[0] eq 'stream') { $obj = $$obj[1] } # for get_obj($stream,$key)
  7         15  
628 107         272 _unflatten($obj);
629 107         200 my $key = shift;
630 107 100       406 my $subobj = $key =~ m.^/. ? $$obj[1]{substr $key, 1} : $$obj[1][$key];
631 107 100       236 if (!$subobj) {
632 4 100       16 if ($vivify) {
633 1 50       7 $obj = $key =~ m.^/. ? $$obj[1]{substr $key, 1} : $$obj[1][$key] =
    50          
634             _viv($vivify, @_ ? $_[0]: ())
635             }
636             else {
637             return
638 3         22 }
639             }
640 103         169 else { $obj = $subobj }
641 104 100 66     534 if ($obj && $$obj[0] eq 'ref') {
642 31         62 $lastref = $$obj[1];
643 31   66     136 $obj = $self->[objs]{$$obj[1]} || $self->read_obj($$obj[1]);
644             }
645             }
646 181         457 _unflatten($obj);
647 181 50       468 $obj->[0] eq 'null' and return;
648 181         707 $obj, $lastref;
649             }
650             sub _unflatten {
651 292     292   452 my $obj = shift;
652 292 50       881 if ($$obj[0] eq 'flat') {
    100          
653 0         0 @$obj = @{ _interpret_token([tokenize($$obj[1])]) };
  0         0  
654             }
655             elsif($$obj[0] eq 'tokens') {
656 67         105 @$obj = @{ _interpret_token($$obj[1]) };
  67         167  
657             }
658             }
659             sub _viv {
660 3     3   17 my ($type, $key) = @_;
661 3 0       43 [defined $key
    50          
    50          
    50          
    100          
    50          
    50          
662             ? $key =~ m.^/. ? ('dict',{}) : ('array',[])
663             : ($type, $type eq 'dict' ? {}
664             : $type =~ /^(?:array|tokens)\z/ ? []
665             : $type eq 'num' ? 0
666             : $type eq 'null' ? ()
667             : $type eq 'stream' ? (['dict',{}], '') : '')
668             ];
669             }
670              
671             sub vivify_obj {
672 30     30 0 139 my $self = $_[0];
673 30 50       124 if ($_[1] !~ /^[a-z]+\z/) {
674 0         0 croak "First arg to vivify_obj must be a type";
675             }
676 30         75 my($obj, $lastref) = &_get_obj;
677 30 100       76 $lastref and $$self[mods]{$lastref}++;
678 30         177 $obj;
679             }
680              
681             sub get_page {
682 7     7 0 11 my $self = shift;
683 7         24 my @pages = _walk_pages($self);
684 7         20 $self->get_obj($pages[$_[0]])
685             }
686              
687             # The import cache looks like this:
688             # # src dest src dest
689             # { $other_pdf => { '2 0' => '1 0', '12 0' => '13 0', ... },
690             # $another_pdf => { '1 0' => '3 0', '12 0' => '13 0', ... },
691             # ...
692             # }
693             # where src is the PDF imported from and dest is the PDF that owns the
694             # cache.
695             sub import_obj {
696 4     4 0 13 my ($self, $spdf, $obj) = @'_;
697             my $cach =
698             ($self->[impo] ||=
699 2         650 do { require Hash'Util'FieldHash; &Hash'Util'FieldHash'fieldhash({}) })
  2         876  
700 4   66     31 ->{$spdf} ||= {};
      100        
701 4         72 my $ret;
702 4 50       13 if (!ref $obj) {
703 0 0       0 croak "$obj is not an object id" unless $obj =~ /^[0-9]+ [0-9]+\z/;
704 0 0       0 if ($cach->{$obj}) {
705 0         0 return $cach->{$obj}
706             }
707             # Assign a new number now. In the loop below, we assume that all
708             # objects have had new numbers assigned already, and that the objects
709             # just need cloning.
710             # Temporarily assign an empty array.
711 0         0 $ret = $cach->{$obj} = $self->add_obj([]);
712             }
713 4         13 my $return_id = !ref $obj;
714 4         12 my @objs = $obj;
715 4         14 while (@objs) {
716 20         41 my $obj = shift @objs;
717 20         30 my $id;
718 20 100       49 if (!ref $obj) {
719 16         30 $id = $obj;
720 16         43 $obj = $spdf->read_obj($obj);
721             }
722             my @tokens = ($obj->[0] eq 'flat')
723             ? tokenize($obj->[1],qr/^stream\z/)
724 20 100       85 : $obj->[0] eq 'tokens' ? @{$obj->[1]} : _serialize($obj);
  6 50       36  
725 20         65 for (2..$#tokens) {
726 222 100       503 next unless $tokens[$_] eq 'R';
727 20         90 my $id = sprintf '%d %d',@tokens[$_-2,$_-1];
728 20 100       55 if (!$cach->{$id}) {
729             # Temporarily assign an empty array.
730 16         47 $cach->{$id} = $self->add_obj([]);
731             # Add to the list of ids to process.
732 16         37 push @objs, $id;
733             }
734 20         77 @tokens[$_-2,$_-1] = split / /, $cach->{$id};
735             }
736             # Clone the object.
737             # If an object id is in @objs at this point, it can only be because it
738             # has had a new id assigned already.
739             my $clone =
740             $id && ($cach->{$id} || die "Internal error: $obj got uncached")
741 20 100 66     127 ? $self->[objs]{$cach->{$id}} # cached empty array
742             : []; # cloning the top-level object with no cache
743 20   66     59 $ret ||= $clone;
744              
745             ## We are not supporting flat streams yet (if ever).
746             #if ($$obj[0] eq 'flat' && $tokens[-1] eq "stream\n") {
747             # pop @tokens;
748             # @$clone = ('stream', ['tokens', \@tokens, ...???
749             #}
750              
751 20 100       53 if ($$obj[0] eq 'stream') {
752             # tokenize() above will have ended up putting a "stream\n" token on the
753             # end, which we do not want in the dictionary.
754 5         7 pop @tokens;
755 5         31 @$clone = ('stream', ['tokens', \@tokens], $$obj[2]);
756             }
757             else {
758 15         95 @$clone = ('tokens', \@tokens);
759             }
760             }
761 4 50       21 _unflatten($ret) if ref $ret;
762 4         23 $ret;
763             }
764              
765             sub add_obj {
766 23     23 0 47 my $self = shift;
767 23   33     35 my $id = shift @{$self->[free]} || $self->[size]++ . ' 0';
768 23         68 $self->[objs]{$id} = shift;
769 23         63 $self->[mods]{$id}++;
770 23         89 $id;
771             }
772              
773             sub decode_stream :lvalue{
774 3     3 0 9 my $self = shift;
775 3         11 my $stream = $self->get_obj(@_);
776 3         14 my @filters = $self->get_obj($stream, "/Filter");
777 3 100       15 if (@filters) {
778 2 50       10 if ($filters[0][0] eq 'array') {
779 0         0 @filters = map $self->get_obj($filters[0],$_)->[1],0..$#{$filters[0][1]};
  0         0  
780             }
781 2         7 else { @filters = $filters[0][1] }
782             }
783 3   66     14 my @params = $self->get_obj($stream, "/DecodeParms")
784             || $self->get_obj($stream, "/DP"); # unofficial but Acrobat sup-
785 3 100       15 if (@params) { # ports it
786 2 50       11 if ($params[0][0] eq 'array') {
787             @params = map scalar $self->get_obj($params[0], $_),
788 0         0 0..$#{$params[0][1]};
  0         0  
789             }
790             }
791 3         15 $stream = \$stream->[2];
792 3         11 for (@filters) {
793 2         11 $stream = _unfilter($self, $stream, $_, shift @params);
794             }
795 3         19 $$stream
796             }
797              
798             sub _unfilter {
799 2     2   9 my ($self, $stream, $filter, $params) = @_;
800 2 50       10 $filter eq 'FlateDecode'
801             or croak "The $filter filter is not supported", 1;
802 2         9 my ($predictor, $bpc, $cols, $colours) = (1, 8, 1, 1);
803 2 50       8 if ($params) {
804             $params->[1]{Predictor}
805 2 50       12 and $predictor = $self->get_obj($params, "/Predictor")->[1];
806 2 50 33     22 $predictor == 1 || $predictor >= 10
807             || croak "Predictor functions other than PNG are not supported", 1;
808             $params->[1]{BitsPerComponent}
809 2 50       12 and $bpc = $self->get_obj($params, "/BitsPerComponent")->[1];
810 2 50       15 $$params[1]{Columns} and $cols=$self->get_obj($params, "/Columns")->[1];
811 2 50       12 $$params[1]{Colours} and $colours=$self->get_obj($params,"/Colors")->[1];
812 2 50       11 $bpc % 8 and croak "BitsPerComponent values that are not multiples of"
813             . " 8 are not supported", 1;
814 2         6 $bpc >>= 3; # bytes per component
815 2         7 $bpc *= $colours;
816             }
817 2         690 require Compress::Zlib;
818 2 50       58576 my $x = Compress'Zlib'inflateInit()
819             or croak "Could not create an inflation stream (whatever that is)", 1;
820 2         507 my ($flate_output, $flate_stat) = inflate $x my $copy = $$stream;
821 2 50       101 croak "Inflation failed for some reason", 1
822             unless $flate_stat == &Compress'Zlib'Z_STREAM_END;
823 2 50       47 if ($predictor >= 10) { # rats
824 2         8 my $output = '';
825 2         11 my $rowsize = 1 + $bpc * $cols;
826 2         11 my $prev = "\0"x($rowsize-1);
827 2         13 for my $row (1..length($flate_output) / $rowsize) {
828 20         77 my $filter = vec $flate_output, ($row-1) * $rowsize, 8;
829 20         88 my $samples = substr $flate_output, ($row-1) * $rowsize + 1, $rowsize-1;
830 20 50       65 if ($filter == 2) { # Up (first ’cos it’s the most common)
    0          
    0          
    0          
    0          
831 20         59 for (0..$rowsize-2) {
832 80         320 vec ($samples, $_, 8) += vec $prev, $_, 8;
833             }
834             }
835             elsif (!$filter) { # Nothing
836             }
837             elsif ($filter == 1) { # Sub (left)
838 0         0 for (0..$rowsize-2) {
839 0         0 vec ($samples, $_, 8) += vec $samples, $_ - $bpc, 8;
840             }
841             }
842             elsif ($filter == 3) { # Avg
843 0         0 for (0..$rowsize-2) {
844 0         0 vec ($samples, $_, 8) +=
845             (vec($prev, $_, 8) + vec $samples, $_ - $bpc, 8) / 2;
846             }
847             }
848             elsif ($filter == 4) { # Paeth
849 0         0 for (0..$rowsize-2) {
850 0         0 my ($a,$b,$c) = (vec($samples, $_ - $bpc, 8),
851             vec($prev , $_ , 8),
852             vec $prev , $_ - $bpc, 8 );
853 0         0 my $p = $a + $b - $c;
854 0         0 my ($pa, $pb, $pc) = (abs($p - $a), abs($p - $b), abs($p - $c));
855 0 0 0     0 vec $samples, $_, 8 =>=
    0          
856             $pa <= $pb && $pa <= $pc ? $a : $pb <= $pc ? $b : $c
857             }
858             }
859 0         0 else { croak "Invalid PNG filter value: $filter", 1 }
860 20         82 $output .= $prev = $samples;
861             }
862 2         25 \$output;
863             }
864             else {
865 0         0 \$flate_output;
866             }
867             }
868              
869              
870             # FUNCTIONS
871              
872             *tokenise = *tokenize;
873             sub tokenize { # This function tokenizes.
874             # accepts three arguments: the text to parse, the token to stop
875             # on (such as 'endobj') and a function to supply more text
876             # the 2nd and 3rd args are optional
877              
878 84     84 0 221 for (shift) {
879 84         136 my $endtoken=shift;
880 84         144 my $more = shift;
881 84         152 my @tokens;
882             my $prev_length;
883 84         157 TOKEN: while (1) {
884 1489 100 100     10312 if ($more and length() < 500) {
    50 33        
885 168         463 &$more();
886             }
887             elsif(!length or length == $prev_length) {
888 0         0 last TOKEN;
889             }
890 1489         2775 $prev_length = length;
891 1489         6060 s/^(?:$S++|%[^\cm\cj]*$N)+//o;
892 1489 100       6788 if (s _^(($R+)|<<|>>|[\[\]\{\}]|/$R*)__o) {
893 1440         3489 push @tokens, $1;
894 1440 100 66     8580 last TOKEN if defined $endtoken && length $2
      100        
895             && $1 =~ $endtoken;
896             next TOKEN
897 1357         2888 }
898 49 100       194 if (s.^\(..) { # remove paren.
899 15         210 &$more()
900             until s/(
901             (?:\\.|[^()\\])++# escaped char or non-\()
902             |
903             \((?1)\) # parenthesized stuff
904             )*\) # final closing paren
905             //xs;
906 14         76 push @tokens, "($1)";
907             next
908 14         39 }
909 34 100       179 s.^(<[^>]*>).. and push @tokens, $1;
910 34         127 &$more() while /^<[^>]*\z/;
911             }
912 83         995 return @tokens;
913             }
914             }
915              
916             sub join_tokens {
917             # PDF lines are not supposed to be longer than 255 (outside of content
918             # streams). I don’t know whether that includes the line ending. I assume
919             # it does.
920 82     82 0 181 my $ret = '';
921 82         142 my $line = '';
922 82         166 for (@_) {
923             # We assume that only strings can get too long to fit on a line. After
924             # all, they are the only token that can be split between lines.
925 1047 100 100     2818 if (length() + length $line > 254 && /^$S*([(<])/o) {
926 3         14 my $hex = $1 eq '<';
927             # Put a line break before the string.
928 3         11 $ret .= "$line\n";
929 3         9 $line = '';
930             # To keep this code simple, just ignore the fact that strings can have
931             # embedded line breaks. Just split it up into pieces that are small
932             # enough to fit on a line.
933 3         16 while (length > 254) {
934             # Don’t split it between an escaper and an escapee.
935 65         171 my $piecepiece = substr $_, 0, 253;
936 65 50       457 chop $piecepiece unless $piecepiece =~ /^[^\\]*(?:\\.[^\\]*)*\z/s;
937 65 100       240 $ret .= $hex ? "$piecepiece\n" : "$piecepiece\\\n";
938 65         242 substr $_, 0, length $piecepiece, = '';
939             }
940 3         16 $ret .= "$_\n";
941             }
942             else {
943             # Wherever whitespace is mandatory, use a line break, to avoid that more
944             # complicated string-splitting logic above. (Speeeeeeeed!) (I hope.)
945             # PDF::Extract won’t be able to read it. That’s the least of
946             # its problems.
947 1044 100       2184 for (ref eq 'SCALAR' ? $$_ : $_) {
948 1044 100 100     6713 if (length($line) and $line !~ /$D\z/o && !/^$D/o
      100        
949             ||length($line) + length > 254) {
950 430         787 $ret .= "$line\n";
951 430         707 $line = '';
952             }
953 1044         2336 $line .= $_;
954             }
955             }
956             }
957 82         460 "$ret$line";
958             }
959              
960             sub parse_string {
961 15     15 0 71 parse_tokens( tokenize @_[0,1] )
962             }
963              
964             sub parse_tokens {
965 14     14 0 35 my @newtokens;
966 14 50       63 wantarray or return _interpret_token(\@_);
967 0         0 while (scalar ( @_)){
968 0         0 push @newtokens, _interpret_token(\@_);
969             }
970 0         0 return @newtokens;
971             }
972              
973             sub _interpret_token { # pass an array ref
974             # interpret_token removes the first token or set of tokens from an
975             # array and returns the token in 'parsed object' format.
976              
977 556     556   872 my $tokens = shift;
978 556         1075 for (shift @$tokens) {
979              
980             # references:
981              
982 556 100 100     4751 if ($_ =~ /^\d+$/ and
    100 100        
    100 100        
    100 66        
    100 66        
    100          
    50          
    0          
    0          
    0          
983             @$tokens >= 2 && $$tokens[0] =~ /^\d+$/
984             && $$tokens[1] eq 'R') {
985 132         421 my $to_return = ['ref',
986             "$_ " . (shift @$tokens)];
987 132         218 shift @$tokens; # shift off the 'R'
988 132         375 return $to_return;
989             }
990              
991             # names
992              
993             elsif (s.^/..) { # if it begins with "/"
994             # replace #XX sequences with real chars:
995 72         157 s/#([a-f\d]{2})/chr hex $1/gie;
  0         0  
996 72         290 return ['name', $_];
997             }
998              
999             # dictionaries:
1000              
1001             elsif ($_ eq '<<') {
1002 82         156 my %tmp_hash;
1003 82         222 while(scalar @$tokens){
1004 385         714 my $name = shift @$tokens;
1005 385 100       824 if ($name eq '>>') {
1006 82         376 return ['dict', \%tmp_hash];
1007             }else {
1008 303         905 $name =~ s.^/..;
1009             # replace #XX sequences with real chars:
1010 303         603 $name =~ s/#([a-f\d]{2})/chr hex $1/gie;
  0         0  
1011 303         605 $tmp_hash{$name} =
1012             _interpret_token($tokens);
1013             delete $tmp_hash{$name}
1014 303 50       1164 if $tmp_hash{$name}[0] eq 'null'
1015             }
1016             }
1017             }
1018              
1019             # arrays:
1020              
1021             elsif ($_ eq '[') {
1022 55         107 my @tmp_array;
1023 55         142 while(scalar @$tokens){
1024 216 100       508 if ($$tokens[0] eq ']') {
1025 55         108 shift @$tokens; #shift off the "]"
1026 55         238 return ['array', \@tmp_array];
1027             }else {
1028 161         360 push @tmp_array, _interpret_token($tokens);
1029             }
1030             }
1031             }
1032              
1033             # strings
1034              
1035             elsif (s/^\(//){ #if it begins with a '('
1036             #i.e., if it's a string
1037 14         60 s/\)$//; # remove final ")"
1038             # and remove wack escapes:
1039 14         131 s,($N)|\\($N|\d{1\,3}|.), my $match = $2;
  34         73  
1040 34         45 my $unescaped = $1;
1041 34 0       865 $unescaped ? "\cj" : # EOL
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    100          
1042             $match =~ /$N/o ? '' : # \EOL
1043             $match=~/\d/?chr oct$match : # octal
1044             $match eq 'n' ? "\cj" : # CR
1045             $match eq 'r' ? "\cm" : # LF
1046             $match eq 't' ? "\t" : # tab
1047             $match eq 'b' ? "\010" : # backspace
1048             $match eq 'f' ? "\x0c" : # form feed
1049             $match eq '(' ? "(" : # (
1050             $match eq ')' ? ')' : # )
1051             $match eq '\\' ? '\\' : # |
1052             length $match ? $match : # ignore backslash as per Adobe's instructions
1053             $fullmatch # anything else
1054             ,goes;
1055 14         83 return ['str', $_];
1056             }
1057              
1058             # numbers:
1059              
1060             elsif (/^[+\-]?\d+$/ or
1061             /^[+\-]?[\d\.]+$/ && y/.// == 1) {
1062 180         756 return ['num',$_];
1063             }
1064              
1065             # hex strings
1066              
1067             elsif (s/^
1068 21         73 s/>$//; # remove final ">"
1069 21         93 s/$S//g; #remove whitespace
1070 21         155 return ['str', pack "H*", $_];
1071             }
1072              
1073             # booleans:
1074              
1075             elsif ($_ eq 'true') {
1076 0         0 return ['bool', 1];
1077             }
1078             elsif($_ eq 'false'){
1079 0         0 return ['bool',''];
1080             }
1081              
1082             # null:
1083              
1084             elsif ($_ eq 'null') {
1085 0         0 return ['null', ];
1086             }
1087              
1088              
1089             # in case something went wrong:
1090              
1091 0         0 else { return ['?',$_]; }
1092             }
1093             }
1094              
1095             *serialise = *serialize;
1096             sub serialize {
1097 20     20 0 65 join_tokens(&_serialize)
1098             }
1099             sub _serialize;
1100             sub _serialize {
1101 469     469   837 for (shift) {
1102             # numbers
1103 469 100       1132 if($$_[0]eq'num'){ for ($$_[1]) {
  137         261  
1104 137 100 66     594 !$_||$_==-0 and return 0;
1105 101 50       719 /^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]*)\z/ and return $_;
1106 0         0 my $ret = 0+$_;
1107 0 0       0 return $ret unless $ret =~ /e([+-][0-9]+)/;
1108 0         0 $ret = sprintf"%.$1f",$ret;
1109 0         0 $ret =~ s/\.?0+\z//;
1110 0         0 return $ret;
1111             }}
1112            
1113             # names
1114 332 100       729 if($$_[0]eq'name'){
1115 60         144 for (my $copy = $$_[1]) {
1116 60         240 s/($D|$S|#)/sprintf'#%02x',ord$1/ego;
  0         0  
1117 60         213 return "/$_";
1118             }
1119             }
1120            
1121             # dictionaries
1122 272 100       686 if ($$_[0] eq 'dict') {
1123 65         140 my (@ret,$key,$key_copy);
1124 65         100 for $key (sort keys %{$$_[1]}) {
  65         380  
1125 237         841 ($key_copy=$key)
1126 0         0 =~s/($D|$S|#)/sprintf'#%02x',ord$1/ego;
1127 237         643 push @ret,"/$key_copy", _serialize $$_[1]{$key};
1128             }
1129 65         480 return"<<",@ret,">>";
1130             }
1131            
1132             # indirect references
1133 207 100       760 $$_[0] eq 'ref' and return split(/ /,$$_[1]), "R";
1134              
1135             # arrays
1136 105 100       255 if ($$_[0]eq'array'){
1137 44         74 my (@ret);
1138 44         77 for(@{$$_[1]}){
  44         157  
1139 126         272 push @ret, _serialize$_;
1140             }
1141 44         228 return "[",@ret,"]";
1142             }
1143            
1144             # screams
1145 61 100       163 if ($$_[0]eq'stream'){
1146 14         37 return _serialize($$_[1]), "stream\n"
1147             }
1148            
1149             # strings
1150 47 100       117 if($$_[0]eq 'str'){
1151             # copy it so we don't modify the object being flattened
1152 34         80 for (my $ret = $$_[1]) {
1153 34 50       137 s/([\\()\r])/$1 eq "\r" ? '\r' : "\\$1"/ge;
  3         14  
1154 34         145 return"($_)";
1155             }
1156             }
1157            
1158 13 100       40 $$_[0]eq'tokens'&&return@{$$_[1]};
  6         46  
1159            
1160             # booleans
1161 7 50       29 $$_[0]eq'bool'&&return+(false=>'true')[$$_[1]];
1162            
1163 7 50       33 $$_[0]eq'flat'&&return\$$_[1];
1164            
1165 0 0       0 $$_[0]eq'null'&&return'null';
1166            
1167             # If we get this far, then there's probably an empty array element or hash value which is not supposed to be there, so we shouldn't return anything.
1168             }
1169             }
1170              
1171             for (qw< bool num str name array dict ref>) {
1172 2     2 0 35 eval "sub make_$_ { ['$_', \$_[0] ] }"
  0     0 0 0  
  7     7 0 113  
  4     4 0 70  
  2     2 0 37  
  5     5 0 48  
  2     2 0 57  
1173             }
1174              
1175              
1176              
1177              
1178             !()__END__()!
1179