File Coverage

blib/lib/PDF/Builder/Resource/XObject/Form/BarCode/code128.pm
Criterion Covered Total %
statement 87 101 86.1
branch 39 62 62.9
condition 9 20 45.0
subroutine 9 9 100.0
pod 1 6 16.6
total 145 198 73.2


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::XObject::Form::BarCode::code128;
2              
3 2     2   1094 use base 'PDF::Builder::Resource::XObject::Form::BarCode';
  2         3  
  2         268  
4              
5 2     2   232 use strict;
  2         7  
  2         61  
6 2     2   10 use warnings;
  2         4  
  2         5181  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
10              
11             =head1 NAME
12              
13             PDF::Builder::Resource::XObject::Form::BarCode::code128 - Code 128 and EAN-128 barcode support
14              
15             Inherits from L<PDF::Builder::Resource::XObject::Form::BarCode>
16              
17             =head1 METHODS
18              
19             =head2 new
20              
21             $res = PDF::Builder::Resource::XObject::Form::BarCode::code128->new($pdf, %options)
22              
23             =over
24              
25             Returns a code128 object. Use 'ean' option to encode using EAN128 mode.
26             Note that this should be invoked via the Builder.pm method!
27              
28             =back
29              
30             =cut
31              
32             sub new {
33 1     1 1 6 my ($class, $pdf, %options) = @_;
34             # copy dashed option names to preferred undashed names
35 1 50 33     6 if (defined $options{'-ean'} && !defined $options{'ean'}) { $options{'ean'} = delete($options{'-ean'}); }
  0         0  
36 1 50 33     8 if (defined $options{'-code'} && !defined $options{'code'}) { $options{'code'} = delete($options{'-code'}); }
  1         4  
37 1 50 33     4 if (defined $options{'-type'} && !defined $options{'type'}) { $options{'type'} = delete($options{'-type'}); }
  0         0  
38              
39 1 50       3 $class = ref($class) if ref($class);
40              
41 1         10 my $self = $class->SUPER::new($pdf, %options);
42              
43 1         3 my @bars;
44 1 50       19 if ($options{'ean'}) {
45 0         0 @bars = $self->encode_ean128($options{'code'});
46             } else {
47 1         8 @bars = $self->encode_128($options{'type'}, $options{'code'});
48             }
49              
50 1         17 $self->drawbar(\@bars, $options{'caption'});
51              
52 1         8 return $self;
53             }
54              
55             # CODE-A Encoding Table
56             my $code128a = q| !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_| . join('', map { chr($_) } (0..31)) . qq/\xf3\xf2\x80\xcc\xcb\xf4\xf1\x8a\x8b\x8c\xff/;
57              
58             # CODE-B Encoding Table
59             my $code128b = q| !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|.qq/|}~\x7f\xf3\xf2\x80\xcc\xf4\xca\xf1\x8a\x8b\x8c\xff/;
60              
61             # CODE-C Encoding Table (00-99 are placeholders)
62             my $code128c = ("\xfe" x 100) . qq/\xcb\xca\xf1\x8a\x8b\x8c\xff/;
63              
64             # START A-C
65             my $bar128Sa = "\x8a";
66             my $bar128Sb = "\x8b";
67             my $bar128Sc = "\x8c";
68              
69             # FNC1-FNC4
70             my $bar128F1 = "\xf1";
71             my $bar128F2 = "\xf2";
72             my $bar128F3 = "\xf3";
73             my $bar128F4 = "\xf4";
74              
75             # CODE A-C
76             my $bar128Ca = "\xca";
77             my $bar128Cb = "\xcb";
78             my $bar128Cc = "\xcc";
79              
80             # SHIFT
81             my $bar128sh = "\x80";
82              
83             # STOP
84             my $bar128St = "\xff";
85              
86             # Note: The stop code (last position) is longer than the other codes because
87             # it also has the termination bar appended, rather than requiring it be added
88             # as a separate call.
89             my @bar128 = qw(
90             212222 222122 222221 121223 121322 131222 122213 122312 132212 221213
91             221312 231212 112232 122132 122231 113222 123122 123221 223211 221132
92             221231 213212 223112 312131 311222 321122 321221 312212 322112 322211
93             212123 212321 232121 111323 131123 131321 112313 132113 132311 211313
94             231113 231311 112133 112331 132131 113123 113321 133121 313121 211331
95             231131 213113 213311 213131 311123 311321 331121 312113 312311 332111
96             314111 221411 431111 111224 111422 121124 121421 141122 141221 112214
97             112412 122114 122411 142112 142211 241211 221114 413111 241112 134111
98             111242 121142 121241 114212 124112 124211 411212 421112 421211 212141
99             214121 412121 111143 111341 131141 114113 114311 411113 411311 113141
100             114131 311141 411131 b1a4a2 b1a2a4 b1a2c2 b3c1a1b
101             );
102              
103             sub encode_128_char_idx {
104 37     37 0 56 my ($code, $char) = @_;
105              
106 37         40 my $index;
107              
108 37 100       81 if (lc($code) eq 'a') {
    100          
    50          
109             # Ignore CODE-A request if we're already in CODE-A
110 6 50       9 return if $char eq $bar128Ca;
111              
112 6         8 $index = index($code128a, $char);
113             } elsif (lc($code) eq 'b') {
114             # Ignore CODE-B request if we're already in CODE-B
115 10 50       17 return if $char eq $bar128Cb;
116 10         18 $index = index($code128b, $char);
117             } elsif (lc($code) eq 'c') {
118             # Ignore CODE-C request if we're already in CODE-C
119 21 50       29 return if $char eq $bar128Cc;
120              
121 21 100       37 if ($char =~ /^([0-9][0-9])$/) {
122 15         25 $index = $1;
123             } else {
124 6         34 $index = index($code128c, $char);
125             }
126             }
127              
128 37         91 return ($bar128[$index], $index);
129             }
130              
131             sub encode_128_char {
132 10     10 0 23 my ($code, $char) = @_;
133              
134 10         22 my ($b) = encode_128_char_idx($code, $char);
135 10         22 return $b;
136             }
137              
138             sub encode_128_string {
139 5     5 0 10 my ($code, $string) = @_;
140              
141 5         9 my ($bar, $index, @bars, @checksum);
142 5         19 my @characters = split(//, $string);
143              
144 5         7 my $character;
145 5         20 while (defined($character = shift @characters)) {
146 26 100       53 if ($character =~ /[\xf1-\xf4]/) {
    50          
147             # CODE-C doesn't have FNC2-FNC4
148 1 50 33     4 if ($character =~ /[\xf2-\xf4]/ and $code eq 'c') {
149 0         0 ($bar, $index) = encode_128_char_idx($code, "\xCB");
150 0         0 push @bars, $bar;
151 0         0 push @checksum, $index;
152 0         0 $code = 'b';
153             }
154              
155 1         3 ($bar, $index) = encode_128_char_idx($code, $character);
156             } elsif ($character =~ /[\xCA-\xCC]/) {
157 0         0 ($bar, $index) = encode_128_char_idx($code, $character);
158 0 0       0 $code = ($character eq "\xCA"? 'a':
    0          
159             $character eq "\xCB"? 'b': 'c');
160             } else {
161 25 100       32 if ($code ne 'c') {
162             # SHIFT: Switch codes for the following character only
163 9 50       15 if ($character eq $bar128sh) {
164 0         0 ($bar, $index) = encode_128_char_idx($code, $character);
165 0         0 push @bars, $bar;
166 0         0 push @checksum, $index;
167 0         0 $character = shift(@characters);
168 0 0       0 ($bar, $index) = encode_128_char_idx($code eq 'a'? 'b': 'a', $character);
169             } else {
170 9         12 ($bar, $index) = encode_128_char_idx($code, $character);
171             }
172             } else {
173 16 100 50     46 $character .= shift(@characters) if $character =~ /\d/ and scalar @characters;
174 16 100 66     51 if ($character =~ /^[^\d]*$/ or $character =~ /^\d[^\d]*$/) {
175 1         3 ($bar, $index) = encode_128_char_idx($code, "\xCB");
176 1         3 push @bars, $bar;
177 1         2 push @checksum, $index;
178 1         2 $code = 'b';
179             }
180 16 100       30 if ($character =~ /^\d[^\d]*$/) {
181 1 50       3 unshift(@characters, substr($character, 1, 1)) if length($character) > 1;
182 1         2 $character = substr($character, 0, 1);
183             }
184 16         24 ($bar, $index) = encode_128_char_idx($code, $character);
185             }
186             }
187 26 100       77 $character = '' if $character =~ /[^\x20-\x7e]/;
188 26         52 push @bars, [$bar, $character];
189 26         44 push @checksum, $index;
190             }
191 5         27 return ([@bars], @checksum);
192             }
193              
194             sub encode_128 {
195 5     5 0 1696 my ($self, $code, $string) = @_;
196 5         8 my @bars;
197             my $checksum_value;
198              
199             # Default to Code C if all characters are digits (and there are at
200             # least two of them). Otherwise, default to Code B.
201 5 50 66     20 $code ||= $string =~ /^\d{2,}$/? 'c': 'b';
202              
203             # Allow the character set to be passed as a capital letter
204             # (consistent with the specification).
205 5 50       18 $code = lc($code) if $code =~ /^[A-C]$/;
206              
207             # Ensure a valid character set has been chosen.
208 5 50       15 die "Character set must be A, B, or C (not '$code')" unless $code =~ /^[a-c]$/;
209              
210 5 100       22 if ($code eq 'a') {
    100          
    50          
211 1         5 push @bars, encode_128_char($code, $bar128Sa);
212 1         3 $checksum_value = 103;
213             } elsif ($code eq 'b') {
214 2         7 push @bars, encode_128_char($code, $bar128Sb);
215 2         5 $checksum_value = 104;
216             } elsif ($code eq 'c') {
217 2         4 push @bars, encode_128_char($code, $bar128Sc);
218 2         5 $checksum_value = 105;
219             }
220 5         8 my ($bar, @checksum_values) = encode_128_string($code, $string);
221              
222 5         8 push @bars, @{$bar};
  5         11  
223              
224             # Calculate the checksum value
225 5         16 foreach my $i (1 .. scalar @checksum_values) {
226 27         48 $checksum_value += $i * $checksum_values[$i - 1];
227             }
228 5         931 $checksum_value %= 103;
229 5         18 push @bars, $bar128[$checksum_value];
230 5         11 push @bars, encode_128_char($code, $bar128St);
231              
232 5         47 return @bars;
233             }
234              
235             sub encode_ean128 {
236 1     1 0 960 my ($self, $string) = @_;
237              
238 1         5 $string =~ s/[^a-zA-Z\d]+//g;
239 1         6 $string =~ s/(\d+)([a-zA-Z]+)/$1\xcb$2/g;
240 1         2 $string =~ s/([a-zA-Z]+)(\d+)/$1\xcc$2/g;
241              
242 1         5 return $self->encode_128('c', "\xf1$string");
243             }
244              
245             1;