File Coverage

blib/lib/Imager/Barcode128.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Imager::Barcode128;
2             $Imager::Barcode128::VERSION = '0.0101';
3 1     1   562 use strict;
  1         2  
  1         35  
4 1     1   378 use Moo;
  1         8928  
  1         4  
5 1     1   1518 use Imager;
  1         29040  
  1         8  
6 1     1   239 use Ouch;
  0            
  0            
7             use Exporter;
8             use base 'Exporter';
9              
10             use constant CodeA => chr(0xf4);
11             use constant CodeB => chr(0xf5);
12             use constant CodeC => chr(0xf6);
13             use constant FNC1 => chr(0xf7);
14             use constant FNC2 => chr(0xf8);
15             use constant FNC3 => chr(0xf9);
16             use constant FNC4 => chr(0xfa);
17             use constant Shift => chr(0xfb);
18             use constant StartA => chr(0xfc);
19             use constant StartB => chr(0xfd);
20             use constant StartC => chr(0xfe);
21             use constant Stop => chr(0xff);
22              
23             our @EXPORT_OK = qw(FNC1 FNC2 FNC3 FNC4 Shift);
24             our %EXPORT_TAGS = (all => \@EXPORT_OK);
25              
26             our @ENCODING = qw(11011001100 11001101100 11001100110 10010011000 10010001100 10001001100 10011001000 10011000100 10001100100 11001001000 11001000100 11000100100 10110011100 10011011100 10011001110 10111001100 10011101100 10011100110 11001110010 11001011100 11001001110 11011100100 11001110100 11101101110 11101001100 11100101100 11100100110 11101100100 11100110100 11100110010 11011011000 11011000110 11000110110 10100011000 10001011000 10001000110 10110001000 10001101000 10001100010 11010001000 11000101000 11000100010 10110111000 10110001110 10001101110 10111011000 10111000110 10001110110 11101110110 11010001110 11000101110 11011101000 11011100010 11011101110 11101011000 11101000110 11100010110 11101101000 11101100010 11100011010 11101111010 11001000010 11110001010 10100110000 10100001100 10010110000 10010000110 10000101100 10000100110 10110010000 10110000100 10011010000 10011000010 10000110100 10000110010 11000010010 11001010000 11110111010 11000010100 10001111010 10100111100 10010111100 10010011110 10111100100 10011110100 10011110010 11110100100 11110010100 11110010010 11011011110 11011110110 11110110110 10101111000 10100011110 10001011110 10111101000 10111100010 11110101000 11110100010 10111011110 10111101110 11101011110 11110101110 11010000100 11010010000 11010011100 1100011101011);
27              
28             our %CODE_CHARS = (
29             A => [ (map { chr($_) } 040..0137, 000..037), FNC3, FNC2, Shift, CodeC, CodeB, FNC4, FNC1, StartA, StartB, StartC, Stop ],
30             B => [ (map { chr($_) } 040..0177), FNC3, FNC2, Shift, CodeC, FNC4, CodeA, FNC1, StartA, StartB, StartC, Stop ],
31             C => [ ("00".."99"), CodeB, CodeA, FNC1, StartA, StartB, StartC, Stop ]
32             );
33              
34             # Provide string equivalents to the constants
35             our %FUNC_CHARS = ('CodeA' => CodeA,
36             'CodeB' => CodeB,
37             'CodeC' => CodeC,
38             'FNC1' => FNC1,
39             'FNC2' => FNC2,
40             'FNC3' => FNC3,
41             'FNC4' => FNC4,
42             'Shift' => Shift,
43             'StartA' => StartA,
44             'StartB' => StartB,
45             'StartC' => StartC,
46             'Stop' => Stop );
47              
48             # Convert the above into a 2-dimensional hash
49             our %CODE = ( A => { map { $CODE_CHARS{A}[$_] => $_ } 0..106 },
50             B => { map { $CODE_CHARS{B}[$_] => $_ } 0..106 },
51             C => { map { $CODE_CHARS{C}[$_] => $_ } 0..106 } );
52              
53              
54             =head1 NAME
55              
56             Imager::Barcode128 - Create GS1-128 compliant bar codes using Imager
57              
58             =head1 VERSION
59              
60             version 0.0101
61              
62             =head1 SYNOPSIS
63              
64             use Imager::Barcode128;
65              
66             my $barcode = Imager::Barcode128->new( text => 'My cool barcode' );
67             $barcode->draw;
68             $barcode->image->save(file => 'barcode.png');
69              
70             =head1 DESCRIPTION
71              
72             If you want to generate GS1-128 compliant bar codes using L then look no further!
73              
74             =head1 EXPORTS
75              
76             By default this module exports nothing. However, there are a number of constants that represent special characters used in the CODE 128 symbology that you may wish to include. For example if you are using the EAN-128 or UCC-128 code, the string to encode begins with the FNC1 character. To encode the EAN-128 string "00 0 0012345 555555555 8", you would do the following:
77              
78             my $barcode = Imager::Barcode128->new(text => FNC1.'00000123455555555558');
79              
80             To have this module export one or more of these characters, specify them on the use statement or use the special token ':all' instead to include all of them. Examples:
81              
82             use Imager::Barcode128 qw(FNC1 Shift);
83             use Imager::Barcode128 qw(:all);
84              
85             Here is the complete list of the exportable characters. They are assigned to high-order ASCII characters purely arbitrarily for the purposes of this module; the values used do not reflect any part of the GS1-128 standard.
86              
87             FNC1 0xf7
88             FNC2 0xf8
89             FNC3 0xf9
90             FNC4 0xfa
91             Shift 0xfb
92              
93             =head1 METHODS
94              
95             =head2 new(text => 'Product #45')
96              
97             Constructor.
98              
99             =over
100              
101             =item image
102              
103             The L object to draw the bar code on to. Required.
104              
105             =item text
106              
107             The text to be encoded into the bar code. Required.
108              
109             =item x
110              
111             The x coordinate of the top left corner to start drawing the bar code. Defaults to 0.
112              
113             =item y
114              
115             The y coordinate of the top left corner to start drawing the bar code. Defaults to 0.
116              
117             =back
118              
119             =cut
120              
121             =head2 x()
122              
123             Get or set the x coordinate of the top left corner of where to start drawing the bar code.
124              
125             =cut
126              
127             has x => (
128             is => 'rw',
129             default => sub { 0 },
130             );
131              
132             =head2 y()
133              
134             Get or set the y coordinate of the top left corner of where to start drawing the bar code.
135              
136             =cut
137              
138             has y => (
139             is => 'rw',
140             default => sub { 0 },
141             );
142              
143             =head2 color()
144              
145             Get or set the color of the bar code. Defaults to C. You can also pass an L object.
146              
147             =cut
148              
149             has color => (
150             is => 'rw',
151             default => sub { 'black' },
152             );
153              
154             =head2 scale()
155              
156             Get or set the scale of the bar code. Defaults to C<2>. Not recommended to set it to less than 2.
157              
158             A bar in the bar code is 1 pixel wide per unit of scale.
159              
160             =cut
161              
162             has scale => (
163             is => 'rw',
164             default => sub { 2 },
165             );
166              
167             =head2 height()
168              
169             Get or set the height of the bar code. Defaults to the height of the C.
170              
171             =cut
172              
173             has height => (
174             is => 'rw',
175             lazy => 1,
176             default => sub {
177             my $self = shift;
178             return $self->has_image ? $self->image->getheight : 100;
179             },
180             );
181              
182             =head2 image()
183              
184             Get or set the L object. Defaults to a 100px tall image with a white background. The image will be however long it needs to be to contain the bar code.
185              
186             =cut
187              
188             has image => (
189             is => 'rw',
190             lazy => 1,
191             predicate => 1,
192             default => sub {
193             my $self = shift;
194             my $x = length($self->_barcode) * $self->scale;
195             my $image = Imager->new(xsize => $x, ysize => $self->height);
196             $image->box(color => 'white', filled => 1);
197             return $image;
198             },
199             );
200              
201             =head2 text()
202              
203             Get or set the text to be encoded into the bar code.
204              
205             =cut
206              
207             has text => (
208             is => 'rw',
209             required => 1,
210             );
211              
212             has _code => ( # private
213             is => 'rw',
214             default => sub { '' },
215             isa => sub {
216             ouch('invalid code', 'Code must be one of A, B, or C.') unless ($_[0] eq 'A' || $_[0] eq 'B' || $_[0] eq 'C' || $_[0] eq '');
217             },
218             );
219              
220             has _encoded => ( # private
221             is => 'rw',
222             default => sub { [] },
223             );
224              
225             has _barcode => ( # private
226             is => 'rw',
227             lazy => 1,
228             default => sub {
229             my $self = shift;
230             return $self->barcode
231             },
232             );
233              
234             =head2 draw()
235              
236             Draws a barcode on the image. Returns C<$self> for method chaining.
237              
238             =cut
239              
240             sub draw {
241             my $self = shift;
242             my @barcode = split //, $self->barcode;
243             my $x = $self->x;
244             my $y = $self->y;
245             my $scale = $self->scale;
246             my $image = $self->image;
247             my $height = $self->height;
248             my $color = $self->color;
249             foreach my $element (@barcode) {
250             $x += $scale;
251             next unless $element eq '#';
252             $image->box(
253             color => $color,
254             xmin => $x - $scale,
255             ymin => $y,
256             xmax => $x,
257             ymax => $y + $height,
258             filled => 1,
259             );
260             }
261             return $self;
262             }
263              
264             sub barcode {
265             my $self = shift;
266             $self->encode;
267             my @encoded = @{ $self->_encoded };
268             ouch('no encoded text',"No encoded text found") unless @encoded;
269             return $self->_barcode(join '', map { $_ = $ENCODING[$_]; tr/01/ \#/; $_ } @encoded); # cache it in case we need it for other things
270             }
271              
272             sub encode {
273             my ($self, $preferred_code) = @_;
274             ouch('invalid preffered code',"Invalid preferred code ``$preferred_code''") if defined $preferred_code && !exists $CODE{$preferred_code};
275             my $text = $self->text;
276             $self->_code('');
277             my $encoded = $self->_encoded([]);
278             my $sanity = 0;
279             while (length $text) {
280             ouch('overflow',"Sanity Check Overflow") if $sanity++ > 1000;
281             my @chars;
282             if (defined $preferred_code && $preferred_code && (@chars = _encodable($preferred_code, $text))) {
283             $self->start($preferred_code);
284             push @$encoded, map { $CODE{$preferred_code}{$_} } @chars;
285             }
286             elsif (@chars = _encodable('C', $text)) {
287             $self->start('C');
288             push @$encoded, map { $CODE{C}{$_} } @chars;
289             }
290             else {
291             my %x = map { $_ => [ _encodable($_, $text) ] } qw(A B);
292             my $code = (@{$x{A}} >= @{$x{B}} ? 'A' : 'B'); # prefer A if equal
293             $self->start($code);
294             @chars = @{ $x{$code} };
295             push @$encoded, map { $CODE{$code}{$_} } @chars;
296             }
297             ouch('no encoding', "Unable to find encoding for ``$text''") unless @chars;
298             substr($text, 0, length join '', @chars) = '';
299             }
300             $self->stop;
301             }
302              
303             sub start {
304             my ($self, $new_code) = @_;
305             my $old_code = $self->_code;
306             if ($old_code ne '') {
307             my $func = $FUNC_CHARS{"Code$new_code"} or ouch('cannot switch codes', "Unable to switch from ``$old_code'' to ``$new_code''");
308             push @{ $self->_encoded }, $CODE{$old_code}{$func};
309             }
310             else {
311             my $func = $FUNC_CHARS{"Start$new_code"} or ouch('bad start code',"Unable to start with ``$new_code''");
312             @{ $self->_encoded } = $CODE{$new_code}{$func};
313             }
314             $self->_code($new_code);
315             }
316              
317             sub stop {
318             my ($self) = @_;
319             my $encoded = $self->_encoded;
320             my $sum = $encoded->[0];
321             for (my $i = 1; $i < @{ $encoded }; ++$i) {
322             $sum += $i * $encoded->[$i];
323             }
324             my $stop = Stop;
325             push @{ $encoded }, ($sum % 103), $CODE{C}{$stop};
326             }
327              
328             sub _encodable {
329             my ($code, $string) = @_;
330             my @chars;
331             while (length $string) {
332             my $old = $string;
333             push @chars, $1 while($code eq 'C' && $string =~ s/^(\d\d)//);
334             my $char;
335             while (defined($char = substr($string, 0, 1))) {
336             last if $code ne 'C' && $string =~ /^\d\d\d\d\d\d/;
337             last unless exists $CODE{$code}{$char};
338             push @chars, $char;
339             $string =~ s/^\Q$char\E//;
340             }
341             last if $old eq $string; # stop if no more changes made to $string
342             }
343             return @chars;
344             }
345              
346             =head1 EXCEPTIONS
347              
348             This module will throw an L if anything goes wrong. Under normal circumstances you should not expect to need to handle exceptions.
349              
350             =head1 TODO
351              
352             None that I can think of at this time.
353              
354             =head2 SEE ALSO
355              
356             Most of the logic of this module was stolen from an older module called L. I build this because I wanted to generate the bar codes with L rather than L.
357              
358             =head1 PREREQS
359              
360             L
361             L
362             L
363              
364             =head1 SUPPORT
365              
366             =over
367              
368             =item Repository
369              
370             L
371              
372             =item Bug Reports
373              
374             L
375              
376             =back
377              
378              
379             =head1 AUTHOR
380              
381             =over
382              
383             =item JT Smith
384              
385             =back
386              
387             =head1 LEGAL
388              
389             Imager::Barcode128 is Copyright 2015 Plain Black Corporation (L) and is licensed under the same terms as Perl itself.
390              
391             =cut
392              
393             1;