File Coverage

blib/lib/PDF/API2/Basic/PDF/Dict.pm
Criterion Covered Total %
statement 119 149 79.8
branch 52 82 63.4
condition 26 36 72.2
subroutine 13 14 92.8
pod 6 7 85.7
total 216 288 75.0


line stmt bran cond sub pod time code
1             # Code in the PDF::API2::Basic::PDF namespace was originally copied from the
2             # Text::PDF distribution.
3             #
4             # Copyright Martin Hosken
5             #
6             # Martin Hosken's code may be used under the terms of the MIT license.
7             # Subsequent versions of the code have the same license as PDF::API2.
8              
9             package PDF::API2::Basic::PDF::Dict;
10              
11 40     40   285 use base 'PDF::API2::Basic::PDF::Objind';
  40         86  
  40         4785  
12              
13 40     40   267 use strict;
  40         87  
  40         1155  
14 40     40   231 no warnings qw[ deprecated recursion uninitialized ];
  40         78  
  40         2875  
15              
16             our $VERSION = '2.043'; # VERSION
17              
18             our $mincache = 16 * 1024 * 1024;
19              
20 40     40   32489 use File::Temp;
  40         496650  
  40         3254  
21 40     40   443 use PDF::API2::Basic::PDF::Array;
  40         89  
  40         897  
22 40     40   18613 use PDF::API2::Basic::PDF::Filter;
  40         148  
  40         1373  
23 40     40   18516 use PDF::API2::Basic::PDF::Name;
  40         1280  
  40         69568  
24              
25             =head1 NAME
26              
27             PDF::API2::Basic::PDF::Dict - Low-level dictionary and stream objects
28              
29             =head1 INSTANCE VARIABLES
30              
31             There are various special instance variables which are used to look after,
32             particularly, streams. Each begins with a space:
33              
34             =over
35              
36             =item stream
37              
38             Holds the stream contents for output
39              
40             =item streamfile
41              
42             Holds the stream contents in an external file rather than in memory. This is
43             not the same as a PDF file stream. The data is stored in its unfiltered form.
44              
45             =item streamloc
46              
47             If both ' stream' and ' streamfile' are empty, this indicates where in the
48             source PDF the stream starts.
49              
50             =back
51              
52             =head1 METHODS
53              
54             =cut
55              
56             sub new {
57 1632     1632 1 2859 my $class = shift();
58 1632 50       3785 $class = ref($class) if ref($class);
59              
60 1632         5208 my $self = $class->SUPER::new(@_);
61 1632         4465 $self->{' realised'} = 1;
62 1632         4024 return $self;
63             }
64              
65             =head2 $type = $d->type($type)
66              
67             Get/Set the standard Type key. It can be passed, and will return, a text value rather than a Name object.
68              
69             =cut
70              
71             sub type {
72 28     28 1 62 my $self = shift();
73 28 50       87 if (scalar @_) {
74 28         52 my $type = shift();
75 28 50       120 $self->{'Type'} = ref($type) ? $type : PDF::API2::Basic::PDF::Name->new($type);
76             }
77 28 50       85 return unless exists $self->{'Type'};
78 28         84 return $self->{'Type'}->val();
79             }
80              
81             =head2 @filters = $d->filter(@filters)
82              
83             Get/Set one or more filters being used by the optional stream attached to the dictionary.
84              
85             =cut
86              
87             sub filter {
88 14     14 1 54 my ($self, @filters) = @_;
89              
90             # Developer's Note: the PDF specification allows Filter to be
91             # either a name or an array, but other parts of this codebase
92             # expect an array. If these are updated uncomment the
93             # commented-out lines in order to accept both types.
94              
95             # if (scalar @filters == 1) {
96             # $self->{'Filter'} = ref($filters[0]) ? $filters[0] : PDF::API2::Basic::PDF::Name->new($filters[0]);
97             # }
98             # elsif (scalar @filters) {
99 14 50       52 @filters = map { ref($_) ? $_ : PDF::API2::Basic::PDF::Name->new($_) } @filters;
  14         75  
100 14         84 $self->{'Filter'} = PDF::API2::Basic::PDF::Array->new(@filters);
101             # }
102             }
103              
104             # Undocumented alias, which may be removed in a future release
105 14     14 0 65 sub filters { return filter(@_); }
106              
107             =head2 $d->outobjdeep($fh)
108              
109             Outputs the contents of the dictionary to a PDF file. This is a recursive call.
110              
111             It also outputs a stream if the dictionary has a stream element. If this occurs
112             then this method will calculate the length of the stream and insert it into the
113             stream's dictionary.
114              
115             =cut
116              
117             sub outobjdeep {
118 1353     1353 1 2552 my ($self, $fh, $pdf) = @_;
119              
120 1353 100 100     6205 if (defined $self->{' stream'} or defined $self->{' streamfile'} or defined $self->{' streamloc'}) {
      66        
121 135 100 100     764 if ($self->{'Filter'} and $self->{' nofilt'}) {
    100 66        
122 9   66     76 $self->{'Length'} ||= PDF::API2::Basic::PDF::Number->new(length($self->{' stream'}));
123             }
124             elsif ($self->{'Filter'} or not defined $self->{' stream'}) {
125 15 50       118 $self->{'Length'} = PDF::API2::Basic::PDF::Number->new(0) unless defined $self->{'Length'};
126 15 50       89 $pdf->new_obj($self->{'Length'}) unless $self->{'Length'}->is_obj($pdf);
127             }
128             else {
129 111         423 $self->{'Length'} = PDF::API2::Basic::PDF::Number->new(length($self->{' stream'}));
130             }
131             }
132              
133 1353         3508 $fh->print('<< ');
134 1353         11721 foreach my $key (sort {
135 22025 100       49689 $a eq 'Type' ? -1 : $b eq 'Type' ? 1 :
    100          
    100          
    100          
136             $a eq 'Subtype' ? -1 : $b eq 'Subtype' ? 1 : $a cmp $b
137             } keys %$self) {
138 9356 100       28884 next if $key =~ m/^[\s\-]/o;
139 3231 50       7246 next unless $self->{$key};
140 3231         7201 $fh->print('/' . PDF::API2::Basic::PDF::Name::string_to_name($key, $pdf) . ' ');
141 3231         23005 $self->{$key}->outobj($fh, $pdf);
142 3231         22580 $fh->print(' ');
143             }
144 1353         8817 $fh->print('>>');
145              
146             # Now handle the stream (if any)
147 1353         6620 my (@filters, $loc);
148              
149 1353 50 33     3151 if (defined $self->{' streamloc'} and not defined $self->{' stream'}) {
150             # read a stream if in file
151 0         0 $loc = $fh->tell();
152 0         0 $self->read_stream();
153 0         0 $fh->seek($loc, 0);
154             }
155              
156 1353 50 100     4472 if (not $self->{' nofilt'} and defined $self->{'Filter'} and (defined $self->{' stream'} or defined $self->{' streamfile'})) {
      33        
      66        
157 15         34 my $hasflate = -1;
158 15         33 for my $i (0 .. scalar(@{$self->{'Filter'}{' val'}}) - 1) {
  15         60  
159 15         61 my $filter = $self->{'Filter'}{' val'}[$i]->val();
160             # hack to get around LZW patent
161 15 50       77 if ($filter eq 'LZWDecode') {
    100          
162 0 0       0 if ($hasflate < -1) {
163 0         0 $hasflate = $i;
164 0         0 next;
165             }
166 0         0 $filter = 'FlateDecode';
167 0         0 $self->{'Filter'}{' val'}[$i]{'val'} = $filter; # !!!
168             }
169             elsif ($filter eq 'FlateDecode') {
170 13         23 $hasflate = -2;
171             }
172 15         41 my $filter_class = "PDF::API2::Basic::PDF::Filter::$filter";
173 15         151 push (@filters, $filter_class->new());
174             }
175 15 50       78 splice(@{$self->{'Filter'}{' val'}}, $hasflate, 1) if $hasflate > -1;
  0         0  
176             }
177              
178 1353 100       3257 if (defined $self->{' stream'}) {
    100          
179 134         395 $fh->print(" stream\n");
180 134         929 $loc = $fh->tell();
181 134         1756 my $stream = $self->{' stream'};
182 134 100       317 unless ($self->{' nofilt'}) {
183 126         276 foreach my $filter (reverse @filters) {
184 15         76 $stream = $filter->outfilt($stream, 1);
185             }
186             }
187 134         325 $fh->print($stream);
188             ## $fh->print("\n"); # newline goes into endstream
189              
190             }
191             elsif (defined $self->{' streamfile'}) {
192 1 50       103 open(my $dictfh, "<", $self->{' streamfile'}) || die "Unable to open $self->{' streamfile'}";
193 1         11 binmode($dictfh, ':raw');
194              
195 1         6 $fh->print(" stream\n");
196 1         11 $loc = $fh->tell();
197 1         7 my $stream;
198 1         59 while (read($dictfh, $stream, 4096)) {
199 1 50       6 unless ($self->{' nofilt'}) {
200 0         0 foreach my $filter (reverse @filters) {
201 0         0 $stream = $filter->outfilt($stream, 0);
202             }
203             }
204 1         5 $fh->print($stream);
205             }
206 1         53 close $dictfh;
207 1 50       9 unless ($self->{' nofilt'}) {
208 0         0 $stream = '';
209 0         0 foreach my $filter (reverse @filters) {
210 0         0 $stream = $filter->outfilt($stream, 1);
211             }
212 0         0 $fh->print($stream);
213             }
214             ## $fh->print("\n"); # newline goes into endstream
215             }
216              
217 1353 100 100     5638 if (defined $self->{' stream'} or defined $self->{' streamfile'}) {
218 135         309 my $length = $fh->tell() - $loc;
219 135 100       799 unless ($self->{'Length'}{'val'} == $length) {
220 15         52 $self->{'Length'}{'val'} = $length;
221 15 50       67 $pdf->out_obj($self->{'Length'}) if $self->{'Length'}->is_obj($pdf);
222             }
223              
224 135         365 $fh->print("\nendstream"); # next is endobj which has the final cr
225             }
226             }
227              
228             =head2 $d->read_stream($force_memory)
229              
230             Reads in a stream from a PDF file. If the stream is greater than
231             C (defaults to 32768) bytes to be stored, then
232             the default action is to create a file for it somewhere and to use that
233             file as a data cache. If $force_memory is set, this caching will not
234             occur and the data will all be stored in the $self->{' stream'}
235             variable.
236              
237             =cut
238              
239             sub read_stream {
240 7     7 1 21 my ($self, $force_memory) = @_;
241              
242 7         12 my $fh = $self->{' streamsrc'};
243 7         25 my $len = $self->{'Length'}->val();
244              
245 7         16 $self->{' stream'} = '';
246              
247 7         12 my @filters;
248 7 100       17 if (defined $self->{'Filter'}) {
249 3         6 my $i = 0;
250 3         19 foreach my $filter ($self->{'Filter'}->elements()) {
251 3         9 my $filter_class = "PDF::API2::Basic::PDF::Filter::" . $filter->val();
252 3 0       9 unless ($self->{'DecodeParms'}) {
    0          
    50          
253 3         33 push(@filters, $filter_class->new());
254             }
255 0 0       0 elsif ($self->{'Filter'}->isa('PDF::API2::Basic::PDF::Name') and $self->{'DecodeParms'}->isa('PDF::API2::Basic::PDF::Dict')) {
256 0         0 push(@filters, $filter_class->new($self->{'DecodeParms'}));
257             }
258 0         0 elsif ($self->{'DecodeParms'}->isa('PDF::API2::Basic::PDF::Array')) {
259 0         0 my $parms = $self->{'DecodeParms'}->val->[$i];
260 0         0 push(@filters, $filter_class->new($parms));
261             }
262             else {
263 0         0 push(@filters, $filter_class->new());
264             }
265 3         19 $i++;
266             }
267             }
268              
269 7         14 my $last = 0;
270 7 50       31 if (defined $self->{' streamfile'}) {
271 0         0 unlink ($self->{' streamfile'});
272 0         0 $self->{' streamfile'} = undef;
273             }
274 7         63 seek $fh, $self->{' streamloc'}, 0;
275              
276 7         18 my $dictfh;
277 7         13 my $readlen = 4096;
278 7         30 for (my $i = 0; $i < $len; $i += $readlen) {
279 7         12 my $data;
280 7 50       20 unless ($i + $readlen > $len) {
281 0         0 read $fh, $data, $readlen;
282             }
283             else {
284 7         12 $last = 1;
285 7         412 read $fh, $data, $len - $i;
286             }
287              
288 7         29 foreach my $filter (@filters) {
289 3         15 $data = $filter->infilt($data, $last);
290             }
291              
292             # Start using a temporary file if the stream gets too big
293 7 50 66     50 if (not $force_memory and not defined $self->{' streamfile'} and (length($self->{' stream'}) + length($data)) > $mincache) {
      66        
294 0         0 $dictfh = File::Temp->new(TEMPLATE => 'pdfXXXXX', SUFFIX => 'dat', TMPDIR => 1);
295 0         0 $self->{' streamfile'} = $dictfh->filename();
296 0         0 print $dictfh $self->{' stream'};
297 0         0 undef $self->{' stream'};
298             }
299              
300 7 50       20 if (defined $self->{' streamfile'}) {
301 0         0 print $dictfh $data;
302             }
303             else {
304 7         26 $self->{' stream'} .= $data;
305             }
306             }
307              
308 7 50       16 close $dictfh if defined $self->{' streamfile'};
309 7         12 $self->{' nofilt'} = 0;
310 7         33 return $self;
311             }
312              
313             =head2 $d->val
314              
315             Returns the dictionary, which is itself.
316              
317             =cut
318              
319             sub val {
320 0     0 1   return $_[0];
321             }
322              
323             1;