package PDF::Builder::Basic::PDF::Filter::LZWDecode; use strict; use warnings; use Carp; use POSIX; use base 'PDF::Builder::Basic::PDF::Filter::FlateDecode'; our $VERSION = '3.025'; # VERSION our $LAST_UPDATE = '3.023'; # manually update whenever code is changed =head1 NAME PDF::Builder::Basic::PDF::Filter::LZWDecode - compress and uncompress stream filters for Lempel-Ziv-Welch =cut sub new { my ($class, $decode_parms) = @_; my $self = { DecodeParms => $decode_parms, }; bless $self, $class; $self->_reset_code(); return $self; } sub infilt { my ($self, $data, $is_last) = @_; my ($code, $result); my $partial_code = $self->{'partial_code'}; my $partial_bits = $self->{'partial_bits'}; my $early_change = 1; if ($self->{'DecodeParms'} and $self->{'DecodeParms'}->{'EarlyChange'}) { $early_change = $self->{'DecodeParms'}->{'EarlyChange'}->val(); } $self->{'table'} = [ map { chr } 0 .. $self->{'clear_table'} - 1 ]; while ( $data ne q{} ) { ($code, $partial_code, $partial_bits) = $self->read_dat(\$data, $partial_code, $partial_bits, $self->{'code_length'}); last unless defined $code; unless ($early_change) { if ($self->{next_code} == (1 << $self->{code_length}) and $self->{code_length} < 12) { $self->{'code_length'}++; } } if ($code == $self->{'clear_table'}) { $self->{'code_length'} = $self->{'initial_code_length'}; $self->{'next_code'} = $self->{'eod_marker'} + 1; next; } elsif ($code == $self->{'eod_marker'}) { last; } elsif ($code > $self->{'eod_marker'}) { $self->{'table'}[$self->{'next_code'}] = $self->{'table'}[$code]; $self->{'table'}[$self->{'next_code'}] .= substr($self->{'table'}[$code + 1], 0, 1); $result .= $self->{'table'}[$self->{'next_code'}]; $self->{'next_code'}++; } else { $self->{'table'}[$self->{'next_code'}] = $self->{'table'}[$code]; $result .= $self->{'table'}[$self->{'next_code'}]; $self->{'next_code'}++; } if ($early_change) { if ($self->{'next_code'} == (1 << $self->{'code_length'}) and $self->{code_length} < 12) { $self->{'code_length'}++; } } } $self->{'partial_code'} = $partial_code; $self->{'partial_bits'} = $partial_bits; if ($self->_predictor_type() == 2) { return $self->_depredict($result); } return $result; } sub outfilt { my ($self, $str, $is_end) = @_; my $max_code = 32767; my $bytes_in = 0; my $checkpoint = 0; my $last_ratio = 0; my $seen = q{}; $self->{'buf'} = q{}; $self->{'buf_pos'} = 0; $self->_write_code($self->{'clear_table'}); if ($self->_predictor_type() == 2) { $str = $self->_predict($str); } for my $i (0 .. length($str)) { my $char = substr($str, $i, 1); $bytes_in += 1; if (exists $self->{'table'}{ $seen . $char }) { $seen .= $char; next; } $self->_write_code($self->{'table'}{$seen}); $self->_new_code($seen . $char); $seen = $char; if ($self->{'at_max_code'}) { $self->_write_code($self->{'clear_table'}); $self->_reset_code(); undef $checkpoint; undef $last_ratio; } } $self->_write_code($self->{'table'}{$seen}); #last bit of input $self->_write_code($self->{'eod_marker'}); my $padding = length($self->{'buf'}) % 8; if ($padding > 0) { $padding = 8 - $padding; $self->{'buf'} .= '0' x $padding; } return pack 'B*', $self->{'buf'}; } sub _reset_code { my $self = shift; $self->{'initial_code_length'} = 9; $self->{'max_code_length'} = 12; $self->{'code_length'} = $self->{'initial_code_length'}; $self->{'clear_table'} = 256; $self->{'eod_marker'} = $self->{'clear_table'} + 1; $self->{'next_code'} = $self->{'eod_marker'} + 1; $self->{'next_increase'} = 2**$self->{'code_length'}; $self->{'at_max_code'} = 0; $self->{'table'} = { map { chr $_ => $_ } 0 .. $self->{'clear_table'} - 1 }; return; } sub _new_code { my ($self, $word) = @_; if ($self->{'at_max_code'} == 0) { $self->{'table'}{$word} = $self->{'next_code'}; $self->{'next_code'} += 1; } if ($self->{'next_code'} >= $self->{'next_increase'}) { if ($self->{'code_length'} < $self->{'max_code_length'}) { $self->{'code_length'} += 1; $self->{'next_increase'} *= 2; } else { $self->{'at_max_code'} = 1; } } return; } sub _write_code { my ($self, $code) = @_; if (not defined $code) { return; } if ($code > (2**$self->{'code_length'})) { croak "Code $code too large for current code length $self->{'code_length'}"; } for my $bit (reverse 0 .. ($self->{'code_length'} - 1)) { if (($code >> $bit) & 1) { $self->{'buf'} .= '1'; } else { $self->{'buf'} .= '0'; } } $self->{'buf_pos'} += $self->{'code_length'}; return; } sub read_dat { my ($self, $data_ref, $partial_code, $partial_bits, $code_length) = @_; if (not defined $partial_bits) { $partial_bits = 0; } if (not defined $partial_code) { $partial_code = 0; } while ($partial_bits < $code_length ) { return (undef, $partial_code, $partial_bits) unless length($$data_ref); $partial_code = ($partial_code << 8 ) + unpack('C', $$data_ref); substr($$data_ref, 0, 1, q{}); $partial_bits += 8; } my $code = $partial_code >> ($partial_bits - $code_length); $partial_code &= (1 << ($partial_bits - $code_length)) - 1; $partial_bits -= $code_length; return ($code, $partial_code, $partial_bits); } sub _predictor_type { my ($self) = @_; if ($self->{'DecodeParms'} and $self->{'DecodeParms'}->{'Predictor'}) { my $predictor = $self->{'DecodeParms'}->{'Predictor'}->val(); if ($predictor == 1 or $predictor == 2) { return $predictor; } elsif ($predictor == 3) { croak 'Floating point TIFF predictor not yet supported'; } else { croak "Invalid predictor: $predictor"; } } return 1; } sub _depredict { my ($self, $data) = @_; my $param = $self->{'DecodeParms'}; my $alpha = $param->{'Alpha'} ? $param->{'Alpha'}->val() : 0; my $bpc = $param->{'BitsPerComponent'} ? $param->{'BitsPerComponent'}->val() : 8; my $colors = $param->{'Colors'} ? $param->{'Colors'}->val() : 1; my $columns = $param->{'Columns'} ? $param->{'Columns'}->val() : 1; my $rows = $param->{'Rows'} ? $param->{'Rows'}->val() : 0; my $comp = $colors + $alpha; my $bpp = ceil($bpc * $comp / 8); my $max = 256; if ($bpc == 8) { my @data = unpack('C*', $data); for my $j (0 .. $rows - 1) { my $count = $bpp * ($j * $columns + 1); for my $i ($bpp .. $columns * $bpp - 1) { $data[$count] = ($data[$count] + $data[$count - $bpp]) % $max; $count++; } } $data = pack('C*', @data); return $data; } return $data; } sub _predict { my ($self, $data) = @_; my $param = $self->{'DecodeParms'}; my $alpha = $param->{'Alpha'} ? $param->{'Alpha'}->val() : 0; my $bpc = $param->{'BitsPerComponent'} ? $param->{'BitsPerComponent'}->val() : 8; my $colors = $param->{'Colors'} ? $param->{'Colors'}->val() : 1; my $columns = $param->{'Columns'} ? $param->{'Columns'}->val() : 1; my $rows = $param->{'Rows'} ? $param->{'Rows'}->val() : 0; my $comp = $colors + $alpha; my $bpp = ceil($bpc * $comp / 8); my $max = 256; if ($bpc == 8) { my @data = unpack('C*', $data); for my $j (0 .. $rows - 1) { my $count = $bpp * $columns * ($j + 1) - 1; for my $i ($bpp .. $columns * $bpp - 1) { $data[$count] -= $data[$count - $bpp]; if ($data[$count] < 0) { $data[$count] += $max; } $count--; } } $data = pack('C*', @data); return $data; } return $data; } 1;