File Coverage

blib/lib/PDF/Builder/Basic/PDF/Dict.pm
Criterion Covered Total %
statement 122 152 80.2
branch 53 84 63.1
condition 26 36 72.2
subroutine 13 14 92.8
pod 6 7 85.7
total 220 293 75.0


line stmt bran cond sub pod time code
1             #=======================================================================
2             #
3             # THIS IS A REUSED PERL MODULE, FOR PROPER LICENCING TERMS SEE BELOW:
4             #
5             # Copyright Martin Hosken <Martin_Hosken@sil.org>
6             #
7             # No warranty or expression of effectiveness, least of all regarding
8             # anyone's safety, is implied in this software or documentation.
9             #
10             # This specific module is licensed under the Perl Artistic License.
11             # Effective 28 January 2021, the original author and copyright holder,
12             # Martin Hosken, has given permission to use and redistribute this module
13             # under the MIT license.
14             #
15             #=======================================================================
16             package PDF::Builder::Basic::PDF::Dict;
17              
18 42     42   288 use base 'PDF::Builder::Basic::PDF::Objind';
  42         2426  
  42         5446  
19              
20 42     42   278 use strict;
  42         102  
  42         1278  
21 42     42   225 use warnings;
  42         111  
  42         4380  
22              
23             our $VERSION = '3.028'; # VERSION
24             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
25              
26             our $mincache = 16 * 1024 * 1024;
27              
28 42     42   37906 use File::Temp;
  42         661208  
  42         4643  
29 42     42   405 use PDF::Builder::Basic::PDF::Array;
  42         136  
  42         1241  
30 42     42   23638 use PDF::Builder::Basic::PDF::Filter;
  42         218  
  42         1777  
31 42     42   23281 use PDF::Builder::Basic::PDF::Name;
  42         165  
  42         91604  
32              
33             =head1 NAME
34              
35             PDF::Builder::Basic::PDF::Dict - PDF Dictionaries and Streams
36              
37             Inherits from L<PDF::Builder::Basic::PDF::Objind>
38              
39             =head1 INSTANCE VARIABLES
40              
41             There are various special instance variables which are used to look after,
42             particularly, streams. Each begins with a space:
43              
44             =over
45              
46             =item ' stream'
47              
48             Holds the stream contents for output
49              
50             =item ' streamfile'
51              
52             Holds the stream contents in an external file rather than in memory. This is
53             not the same as a PDF file stream. The data is stored in its unfiltered form.
54              
55             =item ' streamloc'
56              
57             If both ' stream' and ' streamfile' are empty, this indicates where in the
58             source PDF the stream starts.
59              
60             =back
61              
62             =head1 METHODS
63              
64             =head2 new
65              
66             $d = PDF::Builder::Basic::PDF->new()
67              
68             =over
69              
70             Creates a new instance of a dictionary. The usual practice is to call
71             C<PDFDict()> instead.
72              
73             =back
74              
75             =cut
76              
77             sub new {
78 2066     2066 1 3961 my $class = shift(); # have @_ used, later
79              
80 2066 50       5364 $class = ref($class) if ref($class);
81              
82 2066         8153 my $self = $class->SUPER::new(@_);
83 2066         6483 $self->{' realised'} = 1;
84 2066         6363 return $self;
85             }
86              
87             =head2 type
88              
89             $type = $d->type($type)
90              
91             =over
92              
93             Get/Set the standard Type key. It can be passed, and will return, a text value rather than a Name object.
94              
95             =back
96              
97             =cut
98              
99             sub type {
100 27     27 1 58 my $self = shift();
101 27 50       118 if (scalar @_) {
102 27         232 my $type = shift();
103 27 50       131 $self->{'Type'} = ref($type)? $type: PDF::Builder::Basic::PDF::Name->new($type);
104             }
105 27 50       115 return unless exists $self->{'Type'};
106 27         125 return $self->{'Type'}->val();
107             }
108              
109             # TBD per API2 PR #28, *may* need to copy sub find_prop from Page.pm to here
110              
111             =head2 filter
112              
113             @filters = $d->filter(@filters)
114              
115             =over
116              
117             Get/Set one or more filters being used by the optional stream attached to the dictionary.
118              
119             =back
120              
121             =cut
122              
123             sub filter {
124 14     14 1 59 my ($self, @filters) = @_;
125              
126             # Developer's Note: the PDF specification allows Filter to be
127             # either a name or an array, but other parts of this codebase
128             # expect an array. If these are updated, uncomment the
129             # commented-out lines in order to accept both types.
130              
131             # if (scalar @filters == 1) {
132             # $self->{'Filter'} = ref($filters[0])? $filters[0]: PDF::Builder::Basic::PDF::Name->new($filters[0]);
133             # } elsif (scalar @filters) {
134 14 50       187 @filters = map { ref($_)? $_: PDF::Builder::Basic::PDF::Name->new($_) } @filters;
  14         74  
135 14         148 $self->{'Filter'} = PDF::Builder::Basic::PDF::Array->new(@filters);
136             # }
137 14         41 return $self->{'Filter'};
138             }
139              
140             # Undocumented alias, which may be removed in a future release TBD
141 14     14 0 43 sub filters { return filter(@_); }
142              
143             =head2 outobjdeep
144              
145             $d->outobjdeep($fh, $pdf)
146              
147             =over
148              
149             Outputs the contents of the dictionary to a PDF file. This is a recursive call.
150              
151             It also outputs a stream if the dictionary has a stream element. If this occurs
152             then this method will calculate the length of the stream and insert it into the
153             stream's dictionary.
154              
155             =back
156              
157             =cut
158              
159             sub outobjdeep {
160 1642     1642 1 3610 my ($self, $fh, $pdf) = @_;
161              
162 1642 100 100     9672 if (defined $self->{' stream'} or defined $self->{' streamfile'} or defined $self->{' streamloc'}) {
      66        
163 164 100 100     1400 if ($self->{'Filter'} and $self->{' nofilt'}) {
    100 66        
164 10   66     87 $self->{'Length'} ||= PDF::Builder::Basic::PDF::Number->new(length($self->{' stream'}));
165             } elsif ($self->{'Filter'} or not defined $self->{' stream'}) {
166 14 50       90 $self->{'Length'} = PDF::Builder::Basic::PDF::Number->new(0) unless defined $self->{'Length'};
167 14 50       55 $pdf->new_obj($self->{'Length'}) unless $self->{'Length'}->is_obj($pdf);
168             } else {
169 140         990 $self->{'Length'} = PDF::Builder::Basic::PDF::Number->new(length($self->{' stream'}));
170             }
171             }
172              
173 1642         5025 $fh->print('<< ');
174 1642         18279 foreach my $key (sort {
175 37125 100       108759 $a eq 'Type' ? -1: $b eq 'Type' ? 1:
    100          
    100          
    100          
176             $a eq 'Subtype'? -1: $b eq 'Subtype'? 1: $a cmp $b
177             } keys %$self) {
178 13535 100       60475 next if $key =~ m/^[\s\-]/o;
179 4296 50       12371 next unless $self->{$key};
180             # some unblessed objects were sometimes getting through
181 4296 50       14981 next unless $self->{$key} =~ /^PDF::Builder/;
182 4296         12329 $fh->print('/' . PDF::Builder::Basic::PDF::Name::string_to_name($key, $pdf) . ' ');
183 4296         37234 $self->{$key}->outobj($fh, $pdf);
184 4296         9421 $fh->print(' ');
185             }
186 1642         21482 $fh->print('>>');
187              
188             # Now handle the stream (if any)
189 1642         9148 my (@filters, $loc);
190              
191 1642 50 33     4947 if (defined $self->{' streamloc'} and not defined $self->{' stream'}) {
192             # read a stream if in file
193 0         0 $loc = $fh->tell();
194 0         0 $self->read_stream();
195 0         0 $fh->seek($loc, 0);
196             }
197              
198 1642 50 100     7081 if (not $self->{' nofilt'} and defined $self->{'Filter'} and (defined $self->{' stream'} or defined $self->{' streamfile'})) {
      33        
      66        
199 14         34 my $hasflate = -1;
200 14         30 for my $i (0 .. scalar(@{$self->{'Filter'}{' val'}}) - 1) {
  14         76  
201 14         64 my $filter = $self->{'Filter'}{' val'}[$i]->val();
202             # hack to get around LZW patent
203 14 50       197 if ($filter eq 'LZWDecode') {
    100          
204 0 0       0 if ($hasflate < -1) {
205 0         0 $hasflate = $i;
206 0         0 next;
207             }
208 0         0 $filter = 'FlateDecode';
209 0         0 $self->{'Filter'}{' val'}[$i]{'val'} = $filter; # !!!
210             } elsif ($filter eq 'FlateDecode') {
211 13         26 $hasflate = -2;
212             }
213 14         30 my $filter_class = "PDF::Builder::Basic::PDF::Filter::$filter";
214 14         161 push (@filters, $filter_class->new());
215             }
216 14 50       67 splice(@{$self->{'Filter'}{' val'}}, $hasflate, 1) if $hasflate > -1;
  0         0  
217             }
218              
219 1642 100       4969 if (defined $self->{' stream'}) {
    100          
220 163         581 $fh->print("\nstream\n");
221 163         1302 $loc = $fh->tell();
222 163         2323 my $stream = $self->{' stream'};
223 163 100       553 unless ($self->{' nofilt'}) {
224 154         465 foreach my $filter (reverse @filters) {
225 14         90 $stream = $filter->outfilt($stream, 1);
226             }
227             }
228 163         533 $fh->print($stream);
229             ## $fh->print("\n"); # newline goes into endstream
230              
231             } elsif (defined $self->{' streamfile'}) {
232 1 50       94 open(my $dictfh, "<", $self->{' streamfile'}) || die "Unable to open $self->{' streamfile'}";
233 1         7 binmode($dictfh, ':raw');
234              
235 1         5 $fh->print("\nstream\n");
236 1         21 $loc = $fh->tell();
237 1         6 my $stream;
238 1         27 while (read($dictfh, $stream, 4096)) {
239 1 50       4 unless ($self->{' nofilt'}) {
240 0         0 foreach my $filter (reverse @filters) {
241 0         0 $stream = $filter->outfilt($stream, 0);
242             }
243             }
244 1         3 $fh->print($stream);
245             }
246 1         18 close $dictfh;
247 1 50       6 unless ($self->{' nofilt'}) {
248 0         0 $stream = '';
249 0         0 foreach my $filter (reverse @filters) {
250 0         0 $stream = $filter->outfilt($stream, 1);
251             }
252 0         0 $fh->print($stream);
253             }
254             ## $fh->print("\n"); # newline goes into endstream
255             }
256              
257 1642 100 100     8111 if (defined $self->{' stream'} or defined $self->{' streamfile'}) {
258 164         584 my $length = $fh->tell() - $loc;
259 164 100       1192 unless ($self->{'Length'}{'val'} == $length) {
260 14         35 $self->{'Length'}{'val'} = $length;
261 14 50       90 $pdf->out_obj($self->{'Length'}) if $self->{'Length'}->is_obj($pdf);
262             }
263              
264 164         493 $fh->print("\nendstream"); # next is endobj which has the final cr
265             }
266 1642         5745 return;
267             }
268              
269             =head2 read_stream
270              
271             $d->read_stream($force_memory)
272              
273             =over
274              
275             Reads in a stream from a PDF file. If the stream is greater than
276             C<PDF::Dict::mincache> (defaults to 32768) bytes to be stored, then
277             the default action is to create a file for it somewhere and to use that
278             file as a data cache. If $force_memory is set, this caching will not
279             occur and the data will all be stored in the $self->{' stream'}
280             variable.
281              
282             =back
283              
284             =cut
285              
286             sub read_stream {
287 7     7 1 19 my ($self, $force_memory) = @_;
288              
289 7         32 my $fh = $self->{' streamsrc'};
290 7         27 my $len = $self->{'Length'}->val();
291              
292 7         18 $self->{' stream'} = '';
293              
294 7         15 my @filters;
295 7 100       22 if (defined $self->{'Filter'}) {
296 3         7 my $i = 0;
297 3         27 foreach my $filter ($self->{'Filter'}->elements()) {
298 3         11 my $filter_class = "PDF::Builder::Basic::PDF::Filter::" . $filter->val();
299 3 0       11 unless ($self->{'DecodeParms'}) {
    0          
    50          
300 3         54 push(@filters, $filter_class->new());
301 0 0       0 } elsif ($self->{'Filter'}->isa('PDF::Builder::Basic::PDF::Name') and $self->{'DecodeParms'}->isa('PDF::Builder::Basic::PDF::Dict')) {
302 0         0 push(@filters, $filter_class->new($self->{'DecodeParms'}));
303 0         0 } elsif ($self->{'DecodeParms'}->isa('PDF::Builder::Basic::PDF::Array')) {
304 0         0 my $parms = $self->{'DecodeParms'}->val()->[$i];
305 0         0 push(@filters, $filter_class->new($parms));
306             } else {
307 0         0 push(@filters, $filter_class->new());
308             }
309 3         8 $i++;
310             }
311             }
312              
313 7         32 my $last = 0;
314 7 50       24 if (defined $self->{' streamfile'}) {
315 0         0 unlink ($self->{' streamfile'});
316 0         0 $self->{' streamfile'} = undef;
317             }
318 7         20 seek $fh, $self->{' streamloc'}, 0;
319              
320 7         12 my $dictfh;
321 7         14 my $readlen = 4096;
322 7         29 for (my $i = 0; $i < $len; $i += $readlen) {
323 7         20 my $data;
324 7 50       18 unless ($i + $readlen > $len) {
325 0         0 read($fh, $data, $readlen);
326             } else {
327 7         36 $last = 1;
328 7         30 read($fh, $data, $len - $i);
329             }
330              
331 7         19 foreach my $filter (@filters) {
332 3         16 $data = $filter->infilt($data, $last);
333             }
334              
335             # Start using a temporary file if the stream gets too big
336 7 50 66     69 if (not $force_memory and
      66        
337             not defined $self->{' streamfile'} and
338             (length($self->{' stream'}) + length($data)) > $mincache) {
339 0         0 $dictfh = File::Temp->new(TEMPLATE => 'pdfXXXXX', SUFFIX => 'dat', TMPDIR => 1);
340 0         0 $self->{' streamfile'} = $dictfh->filename();
341 0         0 print $dictfh $self->{' stream'};
342 0         0 undef $self->{' stream'};
343             }
344              
345 7 50       24 if (defined $self->{' streamfile'}) {
346 0         0 print $dictfh $data;
347             } else {
348 7         40 $self->{' stream'} .= $data;
349             }
350             }
351              
352 7 50       51 close $dictfh if defined $self->{' streamfile'};
353 7         18 $self->{' nofilt'} = 0;
354 7         41 return $self;
355             }
356              
357             =head2 val
358              
359             $d->val()
360              
361             =over
362              
363             Returns the dictionary, which is itself.
364              
365             =back
366              
367             =cut
368              
369             sub val {
370 0     0 1   return $_[0];
371             }
372              
373             1;