File Coverage

blib/lib/Data/Petitcom/QRCode.pm
Criterion Covered Total %
statement 156 161 96.8
branch 21 26 80.7
condition 11 15 73.3
subroutine 37 38 97.3
pod 0 7 0.0
total 225 247 91.0


line stmt bran cond sub pod time code
1 3     3   16 use strict;
  3         7  
  3         117  
2 3     3   15 use warnings;
  3         21  
  3         130  
3              
4             {
5             package Data::Petitcom::QRCode;
6              
7 3     3   40 use 5.10.0;
  3         10  
  3         131  
8 3     3   947 use bytes();
  3         11  
  3         58  
9              
10 3     3   14 use base qw{ Exporter };
  3         15  
  3         345  
11             our @EXPORT_OK = qw{ plot_qrcode };
12              
13 3     3   15 use Carp ();
  3         14  
  3         53  
14 3     3   9160 use Compress::Zlib ();
  3         310586  
  3         86  
15 3     3   31 use Digest::MD5;
  3         7  
  3         156  
16 3     3   3496 use POSIX qw{ ceil floor };
  3         20142  
  3         32  
17 3     3   8192 use GD::Barcode::QRcode;
  3         68060  
  3         306  
18              
19 3     3   44 use constant PTC_OFFSET_FILENAME => 0x0C;
  3         7  
  3         244  
20 3     3   18 use constant PTC_OFFSET_DATA => 0x24;
  3         15  
  3         132  
21 3     3   18 use constant PTC_OFFSET_RESOURCENAME => 0x2C;
  3         7  
  3         138  
22              
23 3     3   16 use constant PTC_QR_SIGNATURE => 'PT';
  3         8  
  3         140  
24 3     3   17 use constant DEFAULT_PTC_QR_VERSION_IMAGE => 20;
  3         7  
  3         117  
25 3     3   15 use constant DEFAULT_PTC_QR_VERSION_TERM => 4;
  3         7  
  3         125  
26 3     3   17 use constant DEFAULT_PTC_QR_IMAGE_MODULESIZE => 5;
  3         8  
  3         154  
27              
28 3     3   17 use constant QR_ECC => +{ L => 0, M => 1, Q => 2, H => 3 };
  3         6  
  3         624  
29 3         4949 use constant QR_VERSION => [
30             [ 0, 0, 0, 0 ],
31             [ 17, 14, 11, 7 ],
32             [ 32, 26, 20, 14 ],
33             [ 53, 42, 32, 24 ],
34             [ 78, 62, 46, 34 ],
35             [ 106, 84, 60, 44 ],
36             [ 134, 106, 74, 58 ],
37             [ 154, 122, 86, 64 ],
38             [ 192, 152, 108, 84 ],
39             [ 230, 180, 130, 98 ],
40             [ 271, 213, 151, 119 ],
41             [ 321, 251, 177, 137 ],
42             [ 367, 287, 203, 155 ],
43             [ 425, 331, 241, 177 ],
44             [ 458, 362, 258, 194 ],
45             [ 520, 412, 292, 220 ],
46             [ 586, 450, 322, 250 ],
47             [ 644, 504, 364, 280 ],
48             [ 718, 560, 394, 310 ],
49             [ 792, 624, 442, 338 ],
50             [ 858, 666, 482, 382 ],
51             [ 929, 711, 509, 403 ],
52             [ 1003, 779, 565, 439 ],
53             [ 1091, 857, 611, 461 ],
54             [ 1171, 911, 661, 511 ],
55             [ 1273, 997, 715, 535 ],
56             [ 1367, 1059, 751, 593 ],
57             [ 1465, 1125, 805, 625 ],
58             [ 1528, 1190, 868, 658 ],
59             [ 1628, 1264, 908, 698 ],
60             [ 1732, 1370, 982, 742 ],
61             [ 1840, 1452, 1030, 790 ],
62             [ 1952, 1538, 1112, 842 ],
63             [ 2068, 1628, 1168, 898 ],
64             [ 2188, 1722, 1228, 958 ],
65             [ 2303, 1809, 1283, 983 ],
66             [ 2431, 1911, 1351, 1051 ],
67             [ 2563, 1989, 1423, 1093 ],
68             [ 2699, 2099, 1499, 1139 ],
69             [ 2809, 2213, 1579, 1219 ],
70             [ 2953, 2331, 1663, 1273 ],
71 3     3   17 ];
  3         6  
72              
73             my %defaults = (
74             type => 'text',
75             ecc => 'M',
76             version => DEFAULT_PTC_QR_VERSION_IMAGE
77             );
78 12 100   12 0 70 eval "sub $_ { \@_ > 1 ? \$_[0]->{$_} = \$_[1] : \$_[0]->{$_} }" for keys %defaults;
  12 100   12 0 113  
  12 100   12 0 70  
79              
80             sub new {
81 6 50   6 0 1811 my $class = ref $_[0] ? ref shift : shift;
82 6         38 my $self = bless {@_}, $class;
83 6 50       69 $self->init() if ( $self->can('init') );
84 6         19 return $self;
85             }
86              
87             sub init {
88 6     6 0 15 my $self = shift;
89 6         34 for ( keys %defaults ) {
90 18   66     156 my $value = $self->{$_} || $defaults{$_};
91 18 50       965 ( $self->can($_) ) ? $self->$_($value) : ( $self->{$_} = $value );
92             }
93 6         14 return $self;
94             }
95              
96             sub plot_qrcode {
97 0     0 0 0 my ($ptc, %opts) = @_;
98 0         0 my $qrcode = __PACKAGE__->new(%opts);
99 0         0 return $qrcode->plot($ptc);
100             }
101              
102             sub plot {
103 5     5 0 19 my $self = shift;
104 5         11 my $raw_ptc = shift;
105 5         20 return $self->_generate_qrcode($raw_ptc);
106             }
107              
108             sub _generate_qrcode {
109 5     5   11 my $self = shift;
110 5         11 my $raw_ptc = shift;
111              
112 5         323 my $plot_type = $self->type;
113 5         139 my $ecc = $self->ecc;
114 5         137 my $version = $self->version;
115              
116 5         21 my $qr_bin = _create_qr_bin($raw_ptc);
117 5         28 my $max_qr_data_size = _max_qr_data_size( $version, QR_ECC->{$ecc} );
118 5         20 my $number_of_qr = _number_of_qr( bytes::length($qr_bin), $max_qr_data_size );
119 5 100       42 my $qr_opts = {
120             Ecc => $ecc,
121             Version => $version,
122             ModuleSize => ( $plot_type eq 'image' )
123             ? DEFAULT_PTC_QR_IMAGE_MODULESIZE
124             : 1
125             };
126              
127 5         12 my @qrcode = ();
128 5         15 for my $count_qr ( 1 .. $number_of_qr ) {
129 5         24 my $a_qr_data = bytes::substr( $qr_bin, ( $count_qr - 1 ) * $max_qr_data_size, $max_qr_data_size );
130              
131 5         32 my $a_qr_bin = PTC_QR_SIGNATURE;
132 5         18 $a_qr_bin .= pack 'C', $count_qr;
133 5         12 $a_qr_bin .= pack 'C', $number_of_qr;
134 5         33 $a_qr_bin .= Digest::MD5::md5($a_qr_data);
135 5         19 $a_qr_bin .= Digest::MD5::md5($qr_bin);
136 5         14 $a_qr_bin .= $a_qr_data;
137              
138 5         7 my $qrcode = undef;
139 5         10 given ($plot_type) {
140 5         78 when ('term') {
141 1         13 $qrcode = GD::Barcode::QRcode::Text->new( $a_qr_bin, $qr_opts )->term;
142             }
143 4         10 when ('image') {
144 1         13 my $gd = GD::Barcode::QRcode->new( $a_qr_bin, $qr_opts )->plot;
145 0         0 $gd->string(
146             GD::Font->Large,
147             5, 2, # 0, 0 => left-top
148             "$count_qr / $number_of_qr",
149             $gd->colorAllocate( 0, 0, 0 ), # black
150             );
151 0         0 $qrcode = $gd->png;
152             }
153 3         6 default {
154 3         25 $qrcode = GD::Barcode::QRcode::Text->new( $a_qr_bin, $qr_opts )->barcode;
155             }
156             }
157 4         1637 push @qrcode, $qrcode;
158             }
159              
160 4         101 return \@qrcode;
161             }
162              
163             sub _deflate_data {
164 5     5   10 my $code = shift;
165 5 50       33 my $deflater = Compress::Zlib::deflateInit()
166             or Carp::croak "deflateInit() failed: $!";
167              
168 5         2814 my $zdata = $deflater->deflate($code);
169 5         127 $zdata .= $deflater->flush();
170              
171 5         349 return $zdata;
172             }
173              
174             sub _create_qr_bin {
175 5   50 5   20 my $raw_ptc = shift || return;
176              
177 5         27 my $filename = unpack 'Z*', bytes::substr( $raw_ptc, PTC_OFFSET_FILENAME, 8 );
178 5         3062 my $resource = bytes::substr( $raw_ptc, PTC_OFFSET_RESOURCENAME, 4 );
179 5         36 my $data = bytes::substr( $raw_ptc, PTC_OFFSET_DATA );
180 5         38 my $zdata = _deflate_data($data);
181              
182 5         31 my $qr_bin = bytes::substr( $filename . "\x00" x 8, 0, 8 );
183 5         33 $qr_bin .= $resource;
184 5         22 $qr_bin .= pack 'I', bytes::length($zdata);
185 5         40 $qr_bin .= pack 'I', ( bytes::length($raw_ptc) - PTC_OFFSET_DATA );
186 5         24 $qr_bin .= $zdata;
187              
188 5         17 return $qr_bin;
189             }
190              
191             sub _max_qr_data_size {
192 17   100 17   3294 my $version = shift || DEFAULT_PTC_QR_VERSION_TERM;
193 17   100     63 my $ecc = shift // 1;
194              
195 17 100 100     139 Carp::croak "version between 1 and 24"
196             if ( $version < 1 || $version > 24 );
197 15 50 33     74 Carp::croak "ecc between 0 and 3"
198             if ( $ecc < 0 || $ecc > 3 );
199 15 100       88 Carp::croak "invalid combination of version x ecc: $version x $ecc"
200             if (QR_VERSION->[$version]->[$ecc] <= PTC_OFFSET_DATA);
201              
202 14         338 return QR_VERSION->[$version]->[$ecc] - PTC_OFFSET_DATA;
203             }
204              
205             sub _number_of_qr {
206 5     5   26 my ( $total_size, $part_size ) = @_;
207 5         58 return floor( $total_size / $part_size ) + 1;
208             }
209             }
210              
211             {
212             package GD::Barcode::QRcode::Text;
213              
214 3     3   23 use parent qw{ GD::Barcode::QRcode };
  3         7  
  3         28  
215 3     3   234 use bytes ();
  3         7  
  3         52  
216 3     3   2824 use Term::ANSIColor;
  3         18307  
  3         1359  
217              
218             sub new {
219 4     4   9 my $class = shift;
220 4         117 my $self = $class->SUPER::new(@_);
221 4         493195 bless $self, $class;
222 4         38 return $self;
223             }
224              
225             sub barcode {
226 4     4   14 my $self = shift;
227 4         39 return _trim_margin( $self->SUPER::barcode() );
228             }
229              
230             sub term {
231 1     1   5 my $self = shift;
232 1         5 my $qr_text = $self->barcode;
233 1         7 my $term_text = '';
234              
235 1         10 for my $i ( 0 .. ( bytes::length($qr_text) - 1 ) ) {
236 9900         232480 my $module = bytes::substr $qr_text, $i, 1;
237 9900 100       78819 $term_text .=
    100          
238             ( $module =~ /^[01]$/ )
239             ? colored( ' ', ($module) ? 'on_black' : 'on_white' ) # 2 spaces width
240             : $module;
241             }
242              
243 1         166 return $term_text;
244             }
245              
246             sub _trim_margin {
247 4     4   1259910 my $qr_text = shift;
248 4         224 my $qr_aryref = [ map { [ split //, $_ ] } split /\n/, $qr_text ];
  420         20124  
249 4         70 my $ret_text = '';
250              
251 4         28 for my $i ( 3 .. ( ( @$qr_aryref - 1 ) - 3 ) ) {
252 396         485 my $line = $qr_aryref->[$i];
253 396         754 for my $j ( 3 .. ( ( @$line - 1 ) - 3 ) ) {
254 39204         50697 $ret_text .= $line->[$j];
255             }
256 396         682 $ret_text .= "\n";
257             }
258              
259 4         3549 return $ret_text;
260             }
261             }
262              
263             1;