File Coverage

blib/lib/Compress/LZW/Compressor.pm
Criterion Covered Total %
statement 74 76 97.3
branch 28 32 87.5
condition 3 3 100.0
subroutine 10 10 100.0
pod 2 2 100.0
total 117 123 95.1


line stmt bran cond sub pod time code
1             package Compress::LZW::Compressor;
2             # ABSTRACT: Scaling LZW compressor class
3             $Compress::LZW::Compressor::VERSION = '0.04';
4              
5              
6 4     4   33893 use Compress::LZW qw(:const);
  4         9  
  4         671  
7              
8 4     4   1141 use Types::Standard qw( Bool Int );
  4         114015  
  4         36  
9              
10 4     4   5132 use bytes;
  4         39  
  4         16  
11              
12 4     4   1004 use Moo;
  4         19231  
  4         25  
13 4     4   3799 use namespace::clean;
  4         17394  
  4         19  
14              
15             my $CHECKPOINT_BITS = 10_000;
16              
17              
18             has block_mode => (
19             is => 'ro',
20             default => 1,
21             isa => Bool,
22             );
23              
24              
25             has max_code_size => ( # max bits
26             is => 'ro',
27             default => 16,
28             isa => Type::Tiny->new(
29             parent => Int,
30             constraint => sub { $_ >= $INIT_CODE_SIZE and $_ < $MASK_BITS },
31             message => sub { "$_ isn't between $INIT_CODE_SIZE and $MASK_BITS" },
32             ),
33             );
34              
35              
36              
37             sub compress {
38 10     10 1 6854 my $self = shift;
39 10         20 my ( $str ) = @_;
40            
41 10         29 $self->reset;
42            
43 10         10 my $bytes_in;
44 10         17 my ( $checkpoint, $last_ratio ) = ( 0, 0 );
45            
46 10         17 my $seen = '';
47              
48 10         29 for ( 0 .. length($str) ){
49 11323687         8519810 my $char = substr($str, $_, 1);
50              
51 11323687         7047372 $bytes_in += 1;
52            
53 11323687 100       20223962 if ( exists $self->{code_table}{ $seen . $char } ){
54 10935836         9279977 $seen .= $char;
55             }
56             else {
57 387851         614864 $self->_buf_write( $self->{code_table}{ $seen } );
58            
59 387851         577390 $self->_new_code( $seen . $char );
60            
61 387851         329869 $seen = $char;
62              
63 387851 100 100     756329 if ( $self->{at_max_code} and $self->block_mode ){
64 48404 100       98837 if ( ! defined $checkpoint ){
    100          
65 3         11 $checkpoint = $self->{buf_pos} + $CHECKPOINT_BITS;
66             }
67             elsif ( $bytes_in > $checkpoint ){
68 114         165 my $ratio = $bytes_in / ( $self->{buf_pos} / 8 );
69 114 100       153 $last_ratio = 0 if !defined $last_ratio;
70              
71            
72 114 100       145 if ( $ratio >= $last_ratio ){
    50          
73 111         90 $last_ratio = $ratio;
74 111         279 $checkpoint = $self->{buf_pos} + $CHECKPOINT_BITS;
75             }
76             elsif ( $ratio < $last_ratio ){
77             # warn "Resetting code table ( $ratio < $last_ratio :: $self->{buf_pos} )";
78 3         7 $self->_buf_write( $RESET_CODE );
79 3         9 $self->_code_reset;
80              
81 3         7 undef $checkpoint;
82 3         17 undef $last_ratio;
83             }
84             }
85             }
86              
87             }
88             }
89              
90 10         36 $self->_buf_write( $self->{code_table}{ $seen } ); #last bit of input
91             # warn "final ratio: " . ($bytes_in / ($self->{buf_pos} / 8));
92            
93 10         595 return $self->{buf};
94             }
95              
96              
97              
98             sub reset {
99 10     10 1 20 my $self = shift;
100            
101             # replace buf with empty buffer after magic bytes
102 10 100       97 $self->{buf} = $MAGIC
103             . chr( $self->max_code_size | ( $self->block_mode ? $MASK_BLOCK : 0 ) );
104              
105 10         45 $self->{buf_pos} = length($self->{buf}) * 8;
106            
107 10         28 $self->_code_reset;
108             }
109              
110              
111             sub _code_reset {
112 13     13   19 my $self = shift;
113            
114 3328         5086 $self->{code_table} = {
115 13         49 map { chr($_) => $_ } 0 .. 255
116             };
117              
118 13         64618 $self->{at_max_code} = 0;
119 13         31 $self->{code_size} = $INIT_CODE_SIZE;
120 13 100       74 $self->{next_code} = $self->block_mode ? $BL_INIT_CODE : $NR_INIT_CODE;
121 13         62 $self->{next_increase} = 2 ** $self->{code_size};
122              
123             }
124              
125             sub _new_code {
126 387851     387851   285801 my $self = shift;
127 387851         297865 my ( $word ) = @_;
128              
129 387851 100       591148 if ( $self->{next_code} >= $self->{next_increase} ){
130              
131 48476 100       57870 if ( $self->{code_size} < $self->{max_code_size} ){
132 39         59 $self->{code_size} += 1;
133 39         76 $self->{next_increase} *= 2;
134             }
135             else {
136 48437         44259 $self->{at_max_code} = 1;
137             }
138             }
139            
140 387851 100       559329 if ( $self->{at_max_code} == 0 ){
141 339414         616880 $self->{code_table}{ $word } = $self->{next_code};
142 339414         335792 $self->{next_code} += 1;
143             }
144              
145             }
146              
147             sub _buf_write {
148 387864     387864   280981 my $self = shift;
149 387864         290130 my ( $code ) = @_;
150              
151 387864 50       487050 return unless defined $code;
152            
153 387864 50       636409 if ( $code > ( 2 ** $self->{code_size} ) ){
154 0         0 die "Code value $code too high for current code size $self->{code_size}";
155             }
156              
157 387864         313506 my $wpos = $self->{buf_pos};
158             # if ( $code == $RESET_CODE ){
159             # warn "wrote a reset code ($RESET_CODE) at $wpos";
160             # }
161             #~ warn "write $code \tat $code_size bits\toffset $wpos (byte ".int($wpos/8) . ')';
162            
163 387864 50       425078 if ( $code == 1 ){
164 0         0 vec( $self->{buf}, $wpos, 1 ) = 1;
165             }
166             else {
167 387864         445650 for my $bit ( 0 .. ($self->{code_size} - 1) ){
168            
169 5889692 100       7501900 if ( ($code >> $bit) & 1 ){
170 3141641         4727093 vec( $self->{buf}, $wpos + $bit, 1 ) = 1;
171             }
172             }
173             }
174            
175 387864         479537 $self->{buf_pos} += $self->{code_size};
176             }
177              
178             1;
179              
180             __END__