File Coverage

blib/lib/PDF/Builder/Basic/PDF/Filter/LZWDecode.pm
Criterion Covered Total %
statement 175 184 95.1
branch 54 78 69.2
condition 8 15 53.3
subroutine 15 15 100.0
pod 3 4 75.0
total 255 296 86.1


line stmt bran cond sub pod time code
1             package PDF::Builder::Basic::PDF::Filter::LZWDecode;
2              
3 44     44   290 use strict;
  44         97  
  44         1348  
4 44     44   245 use warnings;
  44         97  
  44         1473  
5 44     44   232 use Carp;
  44         88  
  44         2420  
6 44     44   253 use POSIX;
  44         93  
  44         336  
7 44     44   80359 use base 'PDF::Builder::Basic::PDF::Filter::FlateDecode';
  44         126  
  44         80588  
8              
9             our $VERSION = '3.024'; # VERSION
10             our $LAST_UPDATE = '3.023'; # manually update whenever code is changed
11              
12             =head1 NAME
13              
14             PDF::Builder::Basic::PDF::Filter::LZWDecode - compress and uncompress stream filters for Lempel-Ziv-Welch
15              
16             =cut
17              
18             sub new {
19 10     10 1 28467 my ($class, $decode_parms) = @_;
20              
21 10         32 my $self = { DecodeParms => $decode_parms, };
22              
23 10         17 bless $self, $class;
24 10         31 $self->_reset_code();
25 10         688 return $self;
26             }
27              
28             sub infilt {
29 6     6 1 38 my ($self, $data, $is_last) = @_;
30              
31 6         8 my ($code, $result);
32 6         8 my $partial_code = $self->{'partial_code'};
33 6         10 my $partial_bits = $self->{'partial_bits'};
34              
35 6         7 my $early_change = 1;
36 6 50 66     17 if ($self->{'DecodeParms'} and $self->{'DecodeParms'}->{'EarlyChange'}) {
37 0         0 $early_change = $self->{'DecodeParms'}->{'EarlyChange'}->val();
38             }
39 6         35 $self->{'table'} = [ map { chr } 0 .. $self->{'clear_table'} - 1 ];
  1536         2414  
40              
41 6         108 while ( $data ne q{} ) {
42             ($code, $partial_code, $partial_bits) =
43             $self->read_dat(\$data, $partial_code, $partial_bits,
44 29546         48951 $self->{'code_length'});
45 29546 50       46242 last unless defined $code;
46              
47 29546 50       39135 unless ($early_change) {
48 0 0 0     0 if ($self->{next_code} == (1 << $self->{code_length})
49             and $self->{code_length} < 12) {
50 0         0 $self->{'code_length'}++;
51             }
52             }
53              
54 29546 100       57457 if ($code == $self->{'clear_table'}) {
    100          
    100          
55 13         21 $self->{'code_length'} = $self->{'initial_code_length'};
56 13         40 $self->{'next_code'} = $self->{'eod_marker'} + 1;
57 13         38 next;
58             } elsif ($code == $self->{'eod_marker'}) {
59 6         12 last;
60             } elsif ($code > $self->{'eod_marker'}) {
61 1061         1804 $self->{'table'}[$self->{'next_code'}] = $self->{'table'}[$code];
62             $self->{'table'}[$self->{'next_code'}] .=
63 1061         1675 substr($self->{'table'}[$code + 1], 0, 1);
64 1061         1337 $result .= $self->{'table'}[$self->{'next_code'}];
65 1061         1255 $self->{'next_code'}++;
66             } else {
67 28466         42018 $self->{'table'}[$self->{'next_code'}] = $self->{'table'}[$code];
68 28466         36300 $result .= $self->{'table'}[$self->{'next_code'}];
69 28466         32054 $self->{'next_code'}++;
70             }
71              
72 29527 50       41129 if ($early_change) {
73 29527 100 100     61763 if ($self->{'next_code'} == (1 << $self->{'code_length'})
74             and $self->{code_length} < 12) {
75 25         42 $self->{'code_length'}++;
76             }
77             }
78             }
79 6         8 $self->{'partial_code'} = $partial_code;
80 6         10 $self->{'partial_bits'} = $partial_bits;
81              
82 6 100       15 if ($self->_predictor_type() == 2) {
83 2         7 return $self->_depredict($result);
84             }
85 4         81 return $result;
86             }
87              
88             sub outfilt {
89 6     6 1 75 my ($self, $str, $is_end) = @_;
90 6         11 my $max_code = 32767;
91 6         8 my $bytes_in = 0;
92 6         9 my $checkpoint = 0;
93 6         8 my $last_ratio = 0;
94 6         8 my $seen = q{};
95 6         13 $self->{'buf'} = q{};
96 6         8 $self->{'buf_pos'} = 0;
97 6         18 $self->_write_code($self->{'clear_table'});
98              
99 6 100       15 if ($self->_predictor_type() == 2) {
100 2         39 $str = $self->_predict($str);
101             }
102              
103 6         16 for my $i (0 .. length($str)) {
104 31085         41310 my $char = substr($str, $i, 1);
105 31085         33782 $bytes_in += 1;
106              
107 31085 100       51714 if (exists $self->{'table'}{ $seen . $char }) {
108 1564         1776 $seen .= $char;
109 1564         2099 next;
110             }
111              
112 29521         57588 $self->_write_code($self->{'table'}{$seen});
113              
114 29521         60765 $self->_new_code($seen . $char);
115              
116 29521         38423 $seen = $char;
117              
118 29521 100       45940 if ($self->{'at_max_code'}) {
119 7         21 $self->_write_code($self->{'clear_table'});
120 7         25 $self->_reset_code();
121              
122 7         16 undef $checkpoint;
123 7         12 undef $last_ratio;
124             }
125             }
126 6         15 $self->_write_code($self->{'table'}{$seen}); #last bit of input
127 6         27 $self->_write_code($self->{'eod_marker'});
128 6         28 my $padding = length($self->{'buf'}) % 8;
129 6 100       12 if ($padding > 0) {
130 5         8 $padding = 8 - $padding;
131 5         12 $self->{'buf'} .= '0' x $padding;
132             }
133 6         867 return pack 'B*', $self->{'buf'};
134             }
135              
136             sub _reset_code {
137 17     17   31 my $self = shift;
138              
139 17         41 $self->{'initial_code_length'} = 9;
140 17         32 $self->{'max_code_length'} = 12;
141 17         38 $self->{'code_length'} = $self->{'initial_code_length'};
142 17         30 $self->{'clear_table'} = 256;
143 17         39 $self->{'eod_marker'} = $self->{'clear_table'} + 1;
144 17         39 $self->{'next_code'} = $self->{'eod_marker'} + 1;
145 17         43 $self->{'next_increase'} = 2**$self->{'code_length'};
146 17         24 $self->{'at_max_code'} = 0;
147 17         194 $self->{'table'} = { map { chr $_ => $_ } 0 .. $self->{'clear_table'} - 1 };
  4352         10822  
148 17         393 return;
149             }
150              
151             sub _new_code {
152 29521     29521   39673 my ($self, $word) = @_;
153              
154 29521 50       46022 if ($self->{'at_max_code'} == 0) {
155 29521         54562 $self->{'table'}{$word} = $self->{'next_code'};
156 29521         34764 $self->{'next_code'} += 1;
157             }
158              
159 29521 100       45687 if ($self->{'next_code'} >= $self->{'next_increase'}) {
160 32 100       83 if ($self->{'code_length'} < $self->{'max_code_length'}) {
161 25         33 $self->{'code_length'} += 1;
162 25         35 $self->{'next_increase'} *= 2;
163             } else {
164 7         14 $self->{'at_max_code'} = 1;
165             }
166             }
167 29521         35959 return;
168             }
169              
170             sub _write_code {
171 29546     29546   38924 my ($self, $code) = @_;
172              
173 29546 50       41588 if (not defined $code) { return; }
  0         0  
174              
175 29546 50       47961 if ($code > (2**$self->{'code_length'})) {
176 0         0 croak
177             "Code $code too large for current code length $self->{'code_length'}";
178             }
179              
180 29546         46436 for my $bit (reverse 0 .. ($self->{'code_length'} - 1)) {
181 331003 100       431821 if (($code >> $bit) & 1) {
182 119581         156096 $self->{'buf'} .= '1';
183             } else {
184 211422         273758 $self->{'buf'} .= '0';
185             }
186             }
187              
188 29546         41930 $self->{'buf_pos'} += $self->{'code_length'};
189 29546         35550 return;
190             }
191              
192             sub read_dat {
193 29546     29546 0 39781 my ($self, $data_ref, $partial_code, $partial_bits, $code_length) = @_;
194              
195 29546 100       41852 if (not defined $partial_bits) { $partial_bits = 0; }
  6         8  
196 29546 100       40312 if (not defined $partial_code) { $partial_code = 0; }
  6         7  
197              
198 29546         42064 while ($partial_bits < $code_length ) {
199 41378 50       57825 return (undef, $partial_code, $partial_bits) unless length($$data_ref);
200 41378         56087 $partial_code = ($partial_code << 8 ) + unpack('C', $$data_ref);
201 41378         54827 substr($$data_ref, 0, 1, q{});
202 41378         61556 $partial_bits += 8;
203             }
204              
205 29546         35432 my $code = $partial_code >> ($partial_bits - $code_length);
206 29546         36197 $partial_code &= (1 << ($partial_bits - $code_length)) - 1;
207 29546         31503 $partial_bits -= $code_length;
208              
209 29546         42865 return ($code, $partial_code, $partial_bits);
210             }
211              
212             sub _predictor_type {
213 12     12   19 my ($self) = @_;
214 12 50 66     46 if ($self->{'DecodeParms'} and $self->{'DecodeParms'}->{'Predictor'}) {
215 4         18 my $predictor = $self->{'DecodeParms'}->{'Predictor'}->val();
216 4 50 33     17 if ($predictor == 1 or $predictor == 2) {
    0          
217 4         12 return $predictor;
218             } elsif ($predictor == 3) {
219 0         0 croak 'Floating point TIFF predictor not yet supported';
220             } else {
221 0         0 croak "Invalid predictor: $predictor";
222             }
223             }
224 8         18 return 1;
225             }
226              
227             sub _depredict {
228 2     2   5 my ($self, $data) = @_;
229 2         3 my $param = $self->{'DecodeParms'};
230 2 50       6 my $alpha = $param->{'Alpha'} ? $param->{'Alpha'}->val() : 0;
231             my $bpc =
232 2 50       3 $param->{'BitsPerComponent'} ? $param->{'BitsPerComponent'}->val() : 8;
233 2 100       6 my $colors = $param->{'Colors'} ? $param->{'Colors'}->val() : 1;
234 2 50       5 my $columns = $param->{'Columns'} ? $param->{'Columns'}->val() : 1;
235 2 50       14 my $rows = $param->{'Rows'} ? $param->{'Rows'}->val() : 0;
236              
237 2         3 my $comp = $colors + $alpha;
238 2         8 my $bpp = ceil($bpc * $comp / 8);
239 2         2 my $max = 256;
240 2 50       5 if ($bpc == 8) {
241 2         9 my @data = unpack('C*', $data);
242 2         5 for my $j (0 .. $rows - 1) {
243 4         7 my $count = $bpp * ($j * $columns + 1);
244 4         6 for my $i ($bpp .. $columns * $bpp - 1) {
245 72         84 $data[$count] =
246             ($data[$count] + $data[$count - $bpp]) % $max;
247 72         90 $count++;
248             }
249             }
250 2         6 $data = pack('C*', @data);
251 2         15 return $data;
252             }
253 0         0 return $data;
254             }
255              
256             sub _predict {
257 3     3   23 my ($self, $data) = @_;
258 3         7 my $param = $self->{'DecodeParms'};
259 3 50       6 my $alpha = $param->{'Alpha'} ? $param->{'Alpha'}->val() : 0;
260             my $bpc =
261 3 50       7 $param->{'BitsPerComponent'} ? $param->{'BitsPerComponent'}->val() : 8;
262 3 100       8 my $colors = $param->{'Colors'} ? $param->{'Colors'}->val() : 1;
263 3 50       10 my $columns = $param->{'Columns'} ? $param->{'Columns'}->val() : 1;
264 3 50       8 my $rows = $param->{'Rows'} ? $param->{'Rows'}->val() : 0;
265              
266 3         4 my $comp = $colors + $alpha;
267 3         31 my $bpp = ceil($bpc * $comp / 8);
268 3         8 my $max = 256;
269 3 50       5 if ($bpc == 8) {
270 3         13 my @data = unpack('C*', $data);
271 3         10 for my $j (0 .. $rows - 1) {
272 6         9 my $count = $bpp * $columns * ($j + 1) - 1;
273 6         12 for my $i ($bpp .. $columns * $bpp - 1) {
274 78         90 $data[$count] -= $data[$count - $bpp];
275 78 100       105 if ($data[$count] < 0) { $data[$count] += $max; }
  7         8  
276 78         92 $count--;
277             }
278             }
279 3         12 $data = pack('C*', @data);
280 3         12 return $data;
281             }
282 0           return $data;
283             }
284              
285             1;