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   317 use base 'PDF::Builder::Basic::PDF::Objind';
  41         83  
  41         4131  
19              
20 41     41   288 use strict;
  41         84  
  41         888  
21 41     41   199 use warnings;
  41         79  
  41         3460  
22              
23             our $VERSION = '3.025'; # VERSION
24             our $LAST_UPDATE = '3.024'; # manually update whenever code is changed
25              
26             our $mincache = 16 * 1024 * 1024;
27              
28 41     41   33164 use File::Temp;
  41         503780  
  41         3084  
29 41     41   345 use PDF::Builder::Basic::PDF::Array;
  41         89  
  41         940  
30 41     41   18470 use PDF::Builder::Basic::PDF::Filter;
  41         160  
  41         1573  
31 41     41   18633 use PDF::Builder::Basic::PDF::Name;
  41         123  
  41         70680  
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 3594 my $class = shift(); # have @_ used, later
69              
70 2029 50       4702 $class = ref($class) if ref($class);
71              
72 2029         6096 my $self = $class->SUPER::new(@_);
73 2029         5406 $self->{' realised'} = 1;
74 2029         5188 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 61 my $self = shift();
85 27 50       87 if (scalar @_) {
86 27         58 my $type = shift();
87 27 50       123 $self->{'Type'} = ref($type)? $type: PDF::Builder::Basic::PDF::Name->new($type);
88             }
89 27 50       96 return unless exists $self->{'Type'};
90 27         80 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 50 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       46 @filters = map { ref($_)? $_: PDF::Builder::Basic::PDF::Name->new($_) } @filters;
  14         64  
111 14         90 $self->{'Filter'} = PDF::Builder::Basic::PDF::Array->new(@filters);
112             # }
113 14         38 return $self->{'Filter'};
114             }
115              
116             # Undocumented alias, which may be removed in a future release
117 14     14 0 57 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 3151 my ($self, $fh, $pdf) = @_;
131              
132 1652 100 100     7954 if (defined $self->{' stream'} or defined $self->{' streamfile'} or defined $self->{' streamloc'}) {
      66        
133 166 100 100     1109 if ($self->{'Filter'} and $self->{' nofilt'}) {
    100 66        
134 3   66     23 $self->{'Length'} ||= PDF::Builder::Basic::PDF::Number->new(length($self->{' stream'}));
135             } elsif ($self->{'Filter'} or not defined $self->{' stream'}) {
136 14 50       96 $self->{'Length'} = PDF::Builder::Basic::PDF::Number->new(0) unless defined $self->{'Length'};
137 14 50       75 $pdf->new_obj($self->{'Length'}) unless $self->{'Length'}->is_obj($pdf);
138             } else {
139 149         607 $self->{'Length'} = PDF::Builder::Basic::PDF::Number->new(length($self->{' stream'}));
140             }
141             }
142              
143 1652         4625 $fh->print('<< ');
144 1652         15352 foreach my $key (sort {
145 34733 100       81035 $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       40978 next if $key =~ m/^[\s\-]/o;
149 4304 50       9173 next unless $self->{$key};
150 4304         9932 $fh->print('/' . PDF::Builder::Basic::PDF::Name::string_to_name($key, $pdf) . ' ');
151 4304         31780 $self->{$key}->outobj($fh, $pdf);
152 4304         8404 $fh->print(' ');
153             }
154 1652         11436 $fh->print('>>');
155              
156             # Now handle the stream (if any)
157 1652         8577 my (@filters, $loc);
158              
159 1652 50 33     4059 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     5909 if (not $self->{' nofilt'} and defined $self->{'Filter'} and (defined $self->{' stream'} or defined $self->{' streamfile'})) {
      33        
      66        
167 14         40 my $hasflate = -1;
168 14         29 for my $i (0 .. scalar(@{$self->{'Filter'}{' val'}}) - 1) {
  14         54  
169 14         59 my $filter = $self->{'Filter'}{' val'}[$i]->val();
170             # hack to get around LZW patent
171 14 50       76 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         30 $hasflate = -2;
180             }
181 14         55 my $filter_class = "PDF::Builder::Basic::PDF::Filter::$filter";
182 14         131 push (@filters, $filter_class->new());
183             }
184 14 50       74 splice(@{$self->{'Filter'}{' val'}}, $hasflate, 1) if $hasflate > -1;
  0         0  
185             }
186              
187 1652 100       4418 if (defined $self->{' stream'}) {
    100          
188 165         531 $fh->print("\nstream\n");
189 165         1141 $loc = $fh->tell();
190 165         2291 my $stream = $self->{' stream'};
191 165 100       434 unless ($self->{' nofilt'}) {
192 162         364 foreach my $filter (reverse @filters) {
193 14         65 $stream = $filter->outfilt($stream, 1);
194             }
195             }
196 165         455 $fh->print($stream);
197             ## $fh->print("\n"); # newline goes into endstream
198              
199             } elsif (defined $self->{' streamfile'}) {
200 1 50       55 open(my $dictfh, "<", $self->{' streamfile'}) || die "Unable to open $self->{' streamfile'}";
201 1         11 binmode($dictfh, ':raw');
202              
203 1         7 $fh->print("\nstream\n");
204 1         9 $loc = $fh->tell();
205 1         7 my $stream;
206 1         39 while (read($dictfh, $stream, 4096)) {
207 1 50       14 unless ($self->{' nofilt'}) {
208 0         0 foreach my $filter (reverse @filters) {
209 0         0 $stream = $filter->outfilt($stream, 0);
210             }
211             }
212 1         5 $fh->print($stream);
213             }
214 1         28 close $dictfh;
215 1 50       9 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     6042 if (defined $self->{' stream'} or defined $self->{' streamfile'}) {
226 166         406 my $length = $fh->tell() - $loc;
227 166 100       1007 unless ($self->{'Length'}{'val'} == $length) {
228 14         35 $self->{'Length'}{'val'} = $length;
229 14 50       65 $pdf->out_obj($self->{'Length'}) if $self->{'Length'}->is_obj($pdf);
230             }
231              
232 166         390 $fh->print("\nendstream"); # next is endobj which has the final cr
233             }
234 1652         4792 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 20 my ($self, $force_memory) = @_;
250              
251 7         13 my $fh = $self->{' streamsrc'};
252 7         30 my $len = $self->{'Length'}->val();
253              
254 7         17 $self->{' stream'} = '';
255              
256 7         11 my @filters;
257 7 100       20 if (defined $self->{'Filter'}) {
258 3         6 my $i = 0;
259 3         22 foreach my $filter ($self->{'Filter'}->elements()) {
260 3         11 my $filter_class = "PDF::Builder::Basic::PDF::Filter::" . $filter->val();
261 3 0       18 unless ($self->{'DecodeParms'}) {
    0          
    50          
262 3         32 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         8 $i++;
272             }
273             }
274              
275 7         12 my $last = 0;
276 7 50       40 if (defined $self->{' streamfile'}) {
277 0         0 unlink ($self->{' streamfile'});
278 0         0 $self->{' streamfile'} = undef;
279             }
280 7         19 seek $fh, $self->{' streamloc'}, 0;
281              
282 7         11 my $dictfh;
283 7         13 my $readlen = 4096;
284 7         35 for (my $i = 0; $i < $len; $i += $readlen) {
285 7         14 my $data;
286 7 50       24 unless ($i + $readlen > $len) {
287 0         0 read($fh, $data, $readlen);
288             } else {
289 7         12 $last = 1;
290 7         24 read($fh, $data, $len - $i);
291             }
292              
293 7         17 foreach my $filter (@filters) {
294 3         13 $data = $filter->infilt($data, $last);
295             }
296              
297             # Start using a temporary file if the stream gets too big
298 7 50 66     67 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       21 if (defined $self->{' streamfile'}) {
308 0         0 print $dictfh $data;
309             } else {
310 7         34 $self->{' stream'} .= $data;
311             }
312             }
313              
314 7 50       27 close $dictfh if defined $self->{' streamfile'};
315 7         14 $self->{' nofilt'} = 0;
316 7         32 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;