File Coverage

blib/lib/PDF/Tiny.pm
Criterion Covered Total %
statement 515 614 83.8
branch 228 364 62.6
condition 76 133 57.1
subroutine 39 45 86.6
pod 0 31 0.0
total 858 1187 72.2


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