File Coverage

blib/lib/QRCode/Encoder.pm
Criterion Covered Total %
statement 186 207 89.8
branch 37 58 63.7
condition 8 18 44.4
subroutine 18 20 90.0
pod 3 3 100.0
total 252 306 82.3


line stmt bran cond sub pod time code
1             package QRCode::Encoder;
2 4     4   697025 use v5.24;
  4         17  
3 4     4   23 use warnings;
  4         25  
  4         302  
4 4     4   2068 use experimental qw< signatures >;
  4         16072  
  4         23  
5             { our $VERSION = '0.005' }
6              
7 4     4   3310 use Math::ReedSolomon::Encoder qw< rs_correction_string >;
  4         9098  
  4         357  
8 4     4   2274 use QRCode::Encoder::QRSpec qw< :all >;
  4         47  
  4         965  
9 4     4   2318 use QRCode::Encoder::Matrix qw< add_matrix >;
  4         13  
  4         326  
10 4     4   29 use Exporter qw< import >;
  4         8  
  4         13384  
11             our @EXPORT_OK = qw<
12             qr_best_params
13             qr_encode
14             qr_mode
15             >;
16             our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
17              
18 7     7 1 17 sub qr_mode ($octets) {
  7         38  
  7         13  
19 7 100       63 return 'numeric' if $octets =~ m{\A \d* \z}mxs;
20 1 50       9 return 'alphanumeric'
21             if $octets =~ m{\A [0-9A-Z\x20\x24\x25\x2a\x2b\x2d-\x2f\x3a]+ \z}mxs;
22 0 0       0 return 'kanji'
23             if $octets =~ m{\A
24             (?: # start of a pair
25             (?: [\x81-\x9f\xe0-\xea] [\x40-\x7e\x80-\xfc])
26             | (?: \xeb [\x40-\x7e\x80\xbf])
27             )+
28             \z}mxs;
29 0         0 return 'byte';
30             }
31              
32 7     7 1 489666 sub qr_best_params (@args) {
  7         20  
  7         16  
33 7         31 state $rank_for = { L => 1, M => 2, Q => 3, H => 4 };
34 7 50       48 my %args = scalar(@args) % 2 ? (octets => @args) : @args;
35 7         30 my $size = length($args{octets});
36 7   33     47 my $mode = $args{mode} // qr_mode($args{octets});
37              
38 7         17 my $version = $args{version};
39 7   50     34 my $min_version = $args{'min-version'} // 1;
40 7 50 0     70 $version //= 40 if $min_version eq 40;
41              
42 7         19 my $level = $args{level};
43 7   100     32 my $min_level = $args{min_level} // 'L';
44 7 100 50     51 $level //= 'H' if $min_level eq 'H';
45              
46 7 100       26 if (defined($level)) {
    100          
47 3 50       17 my $minv = qrspec_min_version_for($mode, $size, $level)
48             or die "no suitable version for $mode/$size/$level";
49 3 50       10 if (defined($version)) { # just check
50 0 0       0 die "version $version insufficient for $mode/$size/$level"
51             if $version < $minv;
52             }
53             else {
54 3   50     32 my $req = $args{min_version} // 1;
55 3 50       10 $version = $minv < $req ? $req : $minv;
56             }
57             }
58             elsif (defined($version)) {
59 2         5 my $min_rank = $rank_for->{$min_level};
60 2         6 for my $candidate (qw< H Q M L >) {
61 2 50       7 last if $rank_for->{$candidate} < $min_rank;
62 2         12 my $minv = qrspec_min_version_for($mode, $size, $candidate);
63 2 50       7 if ($minv <= $version) {
64 2         4 $level = $candidate;
65 2         5 last;
66             }
67             }
68 2 50       7 die "no level for $mode/$size/$version (min rank: $min_rank)"
69             unless defined $level;
70             }
71             else { # nothing is defined, go for the smaller size
72 2         4 $level = $min_level;
73 2         5 my $min_rank = $rank_for->{$level};
74 2         11 my $minv = qrspec_min_version_for($mode, $size, $level);
75 2   50     10 my $req = $args{'min-version'} // 1;
76 2 50       6 $version = $minv <= $req ? $req : $minv;
77 2         5 for my $candidate (qw< L M Q H >) {
78 8 100       25 next if $rank_for->{$candidate} <= $min_rank;
79 6         60 my $altv = qrspec_min_version_for($mode, $size, $candidate);
80 6 100       15 last if $altv > $minv;
81 4         9 $level = $candidate;
82             }
83             }
84              
85             return (
86 7         125 %args,
87             mode => $mode,
88             level => $level,
89             version => $version,
90             );
91             }
92              
93 4     4 1 252570 sub qr_encode (@args) {
  4         15  
  4         11  
94 4         19 my %args = qr_best_params(@args);
95 4         15 my $mode = $args{mode};
96 4         12 my $level = $args{level};
97 4         11 my $size = length($args{octets});
98 4   33     17 $args{version} //= qrspec_min_version_for($mode, $size, $level);
99 4         20 _add_encoded(\%args);
100 4         17 _add_codewords(\%args);
101 4         19 _add_error_correction(\%args);
102 4         26 add_matrix(\%args);
103 4         19 _add_plot(\%args);
104 4         38 return \%args;
105             }
106              
107 4     4   11 sub _add_plot ($args) {
  4         9  
  4         10  
108             $args->{plot} = [
109 144 100       268 map { [ map { $_ & 0x01 ? '*' : ' ' } $_->@* ] } $args->{matrix}->@*
  5580         10487  
110 4         16 ];
111 4         15 return $args;
112             }
113              
114 4     4   17 sub _add_encoded ($args) {
  4         14  
  4         9  
115 4         24 state $encoder_for = {
116             numeric => \&_qr_encode_numeric,
117             alphanumeric => \&_qr_encode_alphanumeric,
118             byte => \&_qr_encode_byte,
119             kanji => \&_qr_encode_kanji,
120             };
121              
122 4         13 my $mode = $args->{mode};
123 4 50       16 my $encoder = $encoder_for->{$mode} or die "missing mode <$mode>\n";
124 4         16 my $mi = qrspec_mode_indicator($mode);
125              
126 4         12 my $version = $args->{version};
127 4         12 my $size = length($args->{octets});
128              
129 4         48 my $lis = qrspec_length_indicator($mode, $args->{version});
130 4         21 my $li = _dec2bin(length($args->{octets}), $lis);
131            
132 4         31 $args->{encoded} = $mi . $li . $encoder->($args->{octets});
133              
134 4         14 return $args;
135             }
136              
137 4     4   9 sub _add_codewords ($args) {
  4         8  
  4         7  
138 4         11 my $bit_stream = $args->{encoded};
139 4         24 my $data_size = qrspec_data_size($args->@{qw< version level >});
140 4         10 my $needed_bits = length($bit_stream);
141 4         12 my $residual_bits = 8 * $data_size - $needed_bits;
142 4 50       14 die "not enough bits, wrong version?\n" if $residual_bits < 0;
143 4 50       29 my $terminator_size = $residual_bits >= 4 ? 4 : $residual_bits;
144 4         18 $bit_stream .= '0' x $terminator_size;
145 4         9 $residual_bits -= $terminator_size;
146 4 50       30 if (my $pad1 = $residual_bits % 8) {
147 4         14 $bit_stream .= '0' x $pad1;
148 4         8 $residual_bits -= $pad1;
149             }
150 4         15 while ($residual_bits > 0) {
151 36         66 $bit_stream .= '11101100';
152 36 100       78 last if $residual_bits == 8;
153 35         53 $bit_stream .= '00010001';
154 35         74 $residual_bits -= 16;
155             }
156 4         14 $args->{bit_stream} = $bit_stream;
157 4         29 $args->{codewords} = pack 'B*', $bit_stream;
158 4         10 return $args;
159             }
160              
161 4     4   18 sub _add_error_correction ($args) {
  4         8  
  4         9  
162 4         27 my @blocks = qrspec_ecc_spec($args->@{qw< version level >});
163 4         13 $args->{ecc} = \@blocks;
164 4         19 my $expanded = '';
165 4         13 my $codewords = $args->{codewords};
166 4         8 my $i = 0;
167 4         11 my (@codewords, @eccs);
168 4         13 for my $block (@blocks) {
169 5         22 my ($ecc, $data, $count) = $block->@{qw< ecc data count >};
170 5         19 while ($count-- > 0) {
171 8         27 my $cw = substr($codewords, $i, $data);
172 8         21 push @codewords, $cw;
173 8         36 push @eccs, rs_correction_string($cw, $ecc);
174 8         64972 $i += $data;
175             }
176             }
177 4         25 $args->{expanded} = _linearize(\@codewords) . _linearize(\@eccs);
178 4         33 $args->{remainder} = qrspec_remainder($args->{version});
179 4         18 return $args;
180             }
181              
182 8     8   17 sub _linearize ($strings) {
  8         14  
  8         14  
183 8 100       47 return $strings->[0] if $strings->@* == 1;
184 2         4 my $retval = '';
185 2         4 my $i = 0;
186 2         5 my $n = length($strings->[-1]);
187 2         5 while ($i < $n) {
188 40         74 for my $string ($strings->@*) {
189 200 100       405 next if $i >= length($string);
190 196         364 $retval .= substr($string, $i, 1);
191             }
192 40         82 ++$i;
193             }
194 2         14 return $retval;
195             }
196              
197 28     28   43 sub _dec2bin ($v, $n) { substr(unpack('B*', pack('N', $v)), -$n, $n) }
  28         55  
  28         42  
  28         43  
  28         175  
198              
199 3     3   6 sub _qr_encode_numeric ($octets) {
  3         22  
  3         7  
200 3         8 state $n_bits_for = [ 4, 7, 10 ];
201 3         6 my $i = 0; # index of start of substr, advanced each iteration
202 3         7 my $r = length($octets); # number of residual octets to take
203 3         7 my $bits = '';
204 3         12 while ($r > 0) {
205 18 50       53 my $l = $r >= 3 ? 3 : $r;
206 18         50 $bits .= _dec2bin(substr($octets, $i, $l), $n_bits_for->[$l - 1]);
207 18         39 $r -= $l;
208 18         42 $i += $l;
209             }
210 3         14 return $bits;
211             }
212              
213 1     1   3 sub _qr_encode_alphanumeric ($octets) {
  1         3  
  1         2  
214 1         33 state $chars = [ 0 .. 9, 'A' .. 'Z', split //, ' $%*+-./:' ];
215 1         16 state $value_for = { map { $chars->[$_] => $_ } 0 .. $chars->$#* };
  45         112  
216 1         9 my $i = 0; # index of start of substr, advanced each iteration
217 1         3 my $r = length($octets); # number of residual octets to take
218 1         2 my $bits = '';
219 1         4 while ($r > 0) {
220 6 100       12 if ($r == 1) {
221 1         5 $bits .= _dec2bin($value_for->{substr($octets, $i, 1)}, 6);
222 1         14 $r = 0;
223             }
224             else {
225 5         14 my $value = $value_for->{substr($octets, $i++, 1)} * 45;
226 5         11 $value += $value_for->{substr($octets, $i++, 1)};
227 5         11 $bits .= _dec2bin($value, 11);
228 5         29 $r -= 2;
229             }
230             }
231 1         6 return $bits;
232             }
233              
234 0     0     sub _qr_encode_kanji ($octets) {
  0            
  0            
235 0           my $i = 0;
236 0           my $r = length($octets);
237 0           my $bits = '';
238 0           while ($r > 0) {
239 0           my $v = unpack('n', substr($octets, $i, 2));
240 0 0         $v -= ($v <= 0x9FFC) ? 0x8140 : 0xC140;
241 0           $v = ($v >> 8) * 0xC0 + ($v & 0xFF);
242 0           $bits .= _dec2bin($v, 13);
243 0           $r -= 2;
244 0           $i += 2;
245             }
246 0           return $bits;
247             }
248              
249 0     0     sub _qr_encode_byte ($octets) { unpack 'B*', $octets }
  0            
  0            
  0            
250              
251              
252             1;