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 41     41   274 use base 'PDF::API2::Basic::PDF::Objind';
  41         89  
  41         3980  
12              
13 41     41   245 use strict;
  41         82  
  41         938  
14 41     41   208 no warnings qw[ deprecated recursion uninitialized ];
  41         84  
  41         2522  
15              
16             our $VERSION = '2.045'; # VERSION
17              
18             our $mincache = 16 * 1024 * 1024;
19              
20 41     41   28901 use File::Temp;
  41         465674  
  41         3003  
21 41     41   309 use PDF::API2::Basic::PDF::Array;
  41         83  
  41         864  
22 41     41   17103 use PDF::API2::Basic::PDF::Filter;
  41         148  
  41         1346  
23 41     41   16932 use PDF::API2::Basic::PDF::Name;
  41         1251  
  41         67150  
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 2755 my $class = shift();
58 1632 50       3568 $class = ref($class) if ref($class);
59              
60 1632         4638 my $self = $class->SUPER::new(@_);
61 1632         4146 $self->{' realised'} = 1;
62 1632         3932 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 52 my $self = shift();
73 28 50       73 if (scalar @_) {
74 28         50 my $type = shift();
75 28 50       142 $self->{'Type'} = ref($type) ? $type : PDF::API2::Basic::PDF::Name->new($type);
76             }
77 28 50       76 return unless exists $self->{'Type'};
78 28         71 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 41 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       40 @filters = map { ref($_) ? $_ : PDF::API2::Basic::PDF::Name->new($_) } @filters;
  14         67  
100 14         63 $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 47 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 2438 my ($self, $fh, $pdf) = @_;
119              
120 1353 100 100     6433 if (defined $self->{' stream'} or defined $self->{' streamfile'} or defined $self->{' streamloc'}) {
      66        
121 135 100 100     861 if ($self->{'Filter'} and $self->{' nofilt'}) {
    100 66        
122 9   66     53 $self->{'Length'} ||= PDF::API2::Basic::PDF::Number->new(length($self->{' stream'}));
123             }
124             elsif ($self->{'Filter'} or not defined $self->{' stream'}) {
125 15 50       71 $self->{'Length'} = PDF::API2::Basic::PDF::Number->new(0) unless defined $self->{'Length'};
126 15 50       57 $pdf->new_obj($self->{'Length'}) unless $self->{'Length'}->is_obj($pdf);
127             }
128             else {
129 111         420 $self->{'Length'} = PDF::API2::Basic::PDF::Number->new(length($self->{' stream'}));
130             }
131             }
132              
133 1353         3556 $fh->print('<< ');
134 1353         11642 foreach my $key (sort {
135 22841 100       51486 $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 9611 100       29169 next if $key =~ m/^[\s\-]/o;
139 3371 50       6748 next unless $self->{$key};
140 3371         7144 $fh->print('/' . PDF::API2::Basic::PDF::Name::string_to_name($key, $pdf) . ' ');
141 3371         24032 $self->{$key}->outobj($fh, $pdf);
142 3371         21090 $fh->print(' ');
143             }
144 1353         8809 $fh->print('>>');
145              
146             # Now handle the stream (if any)
147 1353         6634 my (@filters, $loc);
148              
149 1353 50 33     3083 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     4440 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         25 for my $i (0 .. scalar(@{$self->{'Filter'}{' val'}}) - 1) {
  15         50  
159 15         60 my $filter = $self->{'Filter'}{' val'}[$i]->val();
160             # hack to get around LZW patent
161 15 50       83 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         25 $hasflate = -2;
171             }
172 15         35 my $filter_class = "PDF::API2::Basic::PDF::Filter::$filter";
173 15         146 push (@filters, $filter_class->new());
174             }
175 15 50       59 splice(@{$self->{'Filter'}{' val'}}, $hasflate, 1) if $hasflate > -1;
  0         0  
176             }
177              
178 1353 100       3153 if (defined $self->{' stream'}) {
    100          
179 134         411 $fh->print(" stream\n");
180 134         894 $loc = $fh->tell();
181 134         1638 my $stream = $self->{' stream'};
182 134 100       324 unless ($self->{' nofilt'}) {
183 126         280 foreach my $filter (reverse @filters) {
184 15         60 $stream = $filter->outfilt($stream, 1);
185             }
186             }
187 134         352 $fh->print($stream);
188             ## $fh->print("\n"); # newline goes into endstream
189              
190             }
191             elsif (defined $self->{' streamfile'}) {
192 1 50       51 open(my $dictfh, "<", $self->{' streamfile'}) || die "Unable to open $self->{' streamfile'}";
193 1         8 binmode($dictfh, ':raw');
194              
195 1         6 $fh->print(" stream\n");
196 1         10 $loc = $fh->tell();
197 1         6 my $stream;
198 1         39 while (read($dictfh, $stream, 4096)) {
199 1 50       8 unless ($self->{' nofilt'}) {
200 0         0 foreach my $filter (reverse @filters) {
201 0         0 $stream = $filter->outfilt($stream, 0);
202             }
203             }
204 1         6 $fh->print($stream);
205             }
206 1         19 close $dictfh;
207 1 50       8 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     5678 if (defined $self->{' stream'} or defined $self->{' streamfile'}) {
218 135         343 my $length = $fh->tell() - $loc;
219 135 100       771 unless ($self->{'Length'}{'val'} == $length) {
220 15         33 $self->{'Length'}{'val'} = $length;
221 15 50       51 $pdf->out_obj($self->{'Length'}) if $self->{'Length'}->is_obj($pdf);
222             }
223              
224 135         351 $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 19 my ($self, $force_memory) = @_;
241              
242 7         14 my $fh = $self->{' streamsrc'};
243 7         24 my $len = $self->{'Length'}->val();
244              
245 7         16 $self->{' stream'} = '';
246              
247 7         23 my @filters;
248 7 100       24 if (defined $self->{'Filter'}) {
249 3         5 my $i = 0;
250 3         20 foreach my $filter ($self->{'Filter'}->elements()) {
251 3         12 my $filter_class = "PDF::API2::Basic::PDF::Filter::" . $filter->val();
252 3 0       10 unless ($self->{'DecodeParms'}) {
    0          
    50          
253 3         41 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         9 $i++;
266             }
267             }
268              
269 7         13 my $last = 0;
270 7 50       19 if (defined $self->{' streamfile'}) {
271 0         0 unlink ($self->{' streamfile'});
272 0         0 $self->{' streamfile'} = undef;
273             }
274 7         64 seek $fh, $self->{' streamloc'}, 0;
275              
276 7         19 my $dictfh;
277 7         15 my $readlen = 4096;
278 7         27 for (my $i = 0; $i < $len; $i += $readlen) {
279 7         9 my $data;
280 7 50       27 unless ($i + $readlen > $len) {
281 0         0 read $fh, $data, $readlen;
282             }
283             else {
284 7         14 $last = 1;
285 7         431 read $fh, $data, $len - $i;
286             }
287              
288 7         33 foreach my $filter (@filters) {
289 3         17 $data = $filter->infilt($data, $last);
290             }
291              
292             # Start using a temporary file if the stream gets too big
293 7 50 66     58 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         25 $self->{' stream'} .= $data;
305             }
306             }
307              
308 7 50       19 close $dictfh if defined $self->{' streamfile'};
309 7         13 $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;