File Coverage

blib/lib/PDF/Builder/Basic/PDF/Dict.pm
Criterion Covered Total %
statement 121 151 80.1
branch 52 82 63.4
condition 26 36 72.2
subroutine 13 14 92.8
pod 6 7 85.7
total 218 290 75.1


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