barcode1dtools 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Michael Chaney Consulting Corporation
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,119 @@
1
+ #--
2
+ # Copyright 2012 Michael Chaney Consulting Corporation
3
+ #
4
+ # Released under the terms of the MIT License or the GNU
5
+ # General Public License, v. 2
6
+ #++
7
+
8
+ # encoding: utf-8
9
+
10
+ $:.unshift(File.dirname(__FILE__))
11
+
12
+ module Barcode1DTools
13
+ #= barcode1dtools.rb
14
+ #
15
+ # Barcode1DTools is a library for generating and decoding
16
+ # 1-dimensional barcode patterns for various code types.
17
+ # The library currently includes EAN-13, EAN-8, UPC-A,
18
+ # UPC-E, UPC Supplemental 2, UPC Supplemental 5, and
19
+ # Interleaved 2 of 5 (I 2/5), but will be expanded to
20
+ # include most 1D symbologies in the near future.
21
+ #
22
+ #== Example
23
+ # ean13 = Barcode1DTools::EAN13.new('0012676510226', :line_character => 'x', :space_character => ' ')
24
+ # => #<Barcode1DTools::EAN13:0x10030d670 @check_digit=10, @manufacturers_code="12676", @encoded_string="001267651022610", @number_system="00", @value="0012676510226", @product_code="51022", @options={:line_character=>"1", :space_character=>"0"}>
25
+ # ean13.bars
26
+ # "x x xx x xx x x xx x xxxx xxx xx x xxxx x x x xxx xx xx xxx x xx xx xx xx x x x x"
27
+ # ean13.rle
28
+ # "11132112221212211141312111411111123122213211212221221114111"
29
+ # another_ean = EAN.decode(ean13.rle)
30
+ #
31
+ #== Standard Options
32
+ # When creating a barcode, there are a number of options available:
33
+ #
34
+ # 1. checksum_included - The checksum is included in the value
35
+ # and does not need to be generated. This checksum will be
36
+ # validated and an error raised if it is not proper.
37
+ # 2. skip_checksum - Do not include a checksum if it is optional.
38
+ # This option is not applicable to most barcode types and
39
+ # will be ignored unless it is applicable.
40
+ # 3. line_character, space_character - when generating a bar
41
+ # pattern, determines the characters which will represent bars
42
+ # and spaces in the pattern. These default to "1" for lines and
43
+ # "0" for spaces.
44
+ # 4. w_character, n_character - When generating a w/n pattern,
45
+ # determines the characters to be used for wide and narrow
46
+ # bars and spaces. Defaults to "w" and "n". Not applicable to
47
+ # all barcode types.
48
+ #
49
+ #== Standard Object Accessors
50
+ # 1. Barcode1D#value - The actual value of the payload. If there
51
+ # is a checksum, it is not part of the value. This may be a
52
+ # string or an integer depending on the type of barcode.
53
+ # 2. Barcode1D#check_digit - The checksum digit (or digits).
54
+ # This is an integer.
55
+ # 3. Barcode1D#encoded_string - The entire literal value that is
56
+ # encoded, including check digit(s).
57
+ # 4. Barcode1D#options - The options passed to the initializer.
58
+
59
+
60
+ # Errors for barcodes
61
+ class Barcode1DError < StandardError; end
62
+ class UnencodableError < Barcode1DError; end
63
+ class ValueTooLongError < UnencodableError; end
64
+ class ValueTooShortError < UnencodableError; end
65
+ class UnencodableCharactersError < UnencodableError; end
66
+ class ChecksumError < Barcode1DError; end
67
+ class NotImplementedError < Barcode1DError; end
68
+ class UndecodableCharactersError < Barcode1DError; end
69
+
70
+ class Barcode1D
71
+
72
+ attr_reader :check_digit
73
+ attr_reader :value
74
+ attr_reader :encoded_string
75
+ attr_reader :options
76
+
77
+ class << self
78
+
79
+ # Generate bar pattern string from rle string
80
+ def rle_to_bars(rle_str, options = {})
81
+ str=0
82
+ rle_str.split('').inject('') { |a,c| str = 1 - str; a + (str.to_s * c.to_i) }.tr('01', bar_pair(options))
83
+ end
84
+
85
+ # Generate rle pattern from bar string
86
+ def bars_to_rle(bar_str, options = {})
87
+ bar_str.scan(/(.)(\1*)/).collect { |char,rest| 1+rest.length }.join
88
+ end
89
+
90
+ # Generate rle pattern from wn string
91
+ def wn_to_rle(wn_str, options = {})
92
+ wn_str.tr(wn_pair(options), (options[:wn_ratio] || 2).to_s + '1')
93
+ end
94
+
95
+ # Generate wn pattern from rle string
96
+ def rle_to_wn(rle_str, options = {})
97
+ rle_str.tr('123456789', 'nwwwwwwww').tr('wn', wn_pair(options))
98
+ end
99
+
100
+ # Get an "wn" pair from the options
101
+ def wn_pair(options = {})
102
+ (options[:w_character] || 'w') + (options[:n_character] || 'n')
103
+ end
104
+
105
+ # Get a bar pair from the options
106
+ def bar_pair(options = {})
107
+ (options[:space_character] || '0').to_s + (options[:line_character] || '1').to_s
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ require 'barcode1dtools/interleaved2of5'
114
+ require 'barcode1dtools/ean13'
115
+ require 'barcode1dtools/ean8'
116
+ require 'barcode1dtools/upc_a'
117
+ require 'barcode1dtools/upc_e'
118
+ require 'barcode1dtools/upc_supplemental_2'
119
+ require 'barcode1dtools/upc_supplemental_5'
@@ -0,0 +1,389 @@
1
+ #--
2
+ # Copyright 2012 Michael Chaney Consulting Corporation
3
+ #
4
+ # Released under the terms of the MIT License or the GNU
5
+ # General Public License, v. 2
6
+ #++
7
+
8
+ module Barcode1DTools
9
+
10
+ # Barcode1DTools::EAN_13 - Create pattern for EAN-13 barcodes
11
+
12
+ # The value encoded is an
13
+ # integer, and a checksum digit will be added. You can add the option
14
+ # :checksum_included => true when initializing to specify that you
15
+ # have already included a checksum.
16
+ #
17
+ # # Note that this number is a UPC-A, with the number system of 08,
18
+ # # manufacturer's code of "28999", product code of "00682", and a
19
+ # # checksum of "3" (not included)
20
+ # num = '082899900682'
21
+ # bc = Barcode1DTools::EAN13.new(num)
22
+ # pattern = bc.bars
23
+ # rle_pattern = bc.rle
24
+ # width = bc.width
25
+ # check_digit = Barcode1DTools::EAN13.generate_check_digit_for(num)
26
+ #
27
+ # The object created is immutable.
28
+ #
29
+ # There are two formats for the returned pattern (wn format is
30
+ # not available):
31
+ #
32
+ # bars - 1s and 0s specifying black lines and white spaces. Actual
33
+ # characters can be changed from "1" and 0" with options
34
+ # :line_character and :space_character. Each character
35
+ # in the string renders to a single unit width.
36
+ #
37
+ # rle - Run-length-encoded version of the pattern. The first
38
+ # number is always a black line, with subsequent digits
39
+ # alternating between spaces and lines. The digits specify
40
+ # the width of each line or space.
41
+ #
42
+ # The "width" method will tell you the total end-to-end width, in
43
+ # units, of the entire barcode.
44
+ #
45
+ # Unlike some of the other barcodes, e.g. Code 3 of 9, there is no "wnstr" for
46
+ # EAN & UPC style barcodes because the bars and spaces are variable width from
47
+ # 1 to 3 units.
48
+ #
49
+ # Note that JAN codes (Japanese) are simply EAN-13's, and they always start with
50
+ # "49". The table below shows "49" to be "Japan".
51
+ #
52
+ # Also note that many books use a "bookland" code, perhaps along with a UPC
53
+ # Supplemental. The bookland code is really an EAN-13 with the initial 3 digits
54
+ # of "978". The next 9 digits are the first 9 digits of the ISBN, and of course
55
+ # we still include the final check digit. An ISBN is 10 digits, however, the
56
+ # final digit is also a check digit, so it is not necessary.
57
+ #
58
+ # MISCELLANEOUS INFORMATION
59
+ #
60
+ # An EAN-13 with an initial "number system" digit of "0" is a UPC-A.
61
+ # The BarcodeTools::UPC_A module actually just uses this EAN13 module.
62
+ #
63
+ # A EAN-13 barcode has 4 elements:
64
+ # 1. A two-digit "number system" designation
65
+ # 2. A 5-digit manufacturer's code
66
+ # 3. A 5-digit product code
67
+ # 4. A single digit checksum
68
+ #
69
+ # There is some flexibility in EAN-13 on the digit layout. Sometimes,
70
+ # the first three digits indicate numbering system, i.e. some number
71
+ # systems are further split up. An example is "74", which is used for
72
+ # Central America with "740" for Guatemala, "741" for El Salvador, etc.
73
+ #
74
+ # Here is the complete table from www.barcodeisland.com:
75
+ #
76
+ # 00-13: USA & Canada 590: Poland 780: Chile
77
+ # 20-29: In-Store Functions 594: Romania 784: Paraguay
78
+ # 30-37: France 599: Hungary 785: Peru
79
+ # 40-44: Germany 600 & 601: South Africa 786: Ecuador
80
+ # 45: Japan (also 49) 609: Mauritius 789: Brazil
81
+ # 46: Russian Federation 611: Morocco 80 - 83: Italy
82
+ # 471: Taiwan 613: Algeria 84: Spain
83
+ # 474: Estonia 619: Tunisia 850: Cuba
84
+ # 475: Latvia 622: Egypt 858: Slovakia
85
+ # 477: Lithuania 625: Jordan 859: Czech Republic
86
+ # 479: Sri Lanka 626: Iran 860: Yugloslavia
87
+ # 480: Philippines 64: Finland 869: Turkey
88
+ # 482: Ukraine 690-692: China 87: Netherlands
89
+ # 484: Moldova 70: Norway 880: South Korea
90
+ # 485: Armenia 729: Israel 885: Thailand
91
+ # 486: Georgia 73: Sweden 888: Singapore
92
+ # 487: Kazakhstan 740: Guatemala 890: India
93
+ # 489: Hong Kong 741: El Salvador 893: Vietnam
94
+ # 49: Japan (JAN-13) 742: Honduras 899: Indonesia
95
+ # 50: United Kingdom 743: Nicaragua 90 & 91: Austria
96
+ # 520: Greece 744: Costa Rica 93: Australia
97
+ # 528: Lebanon 746: Dominican Republic 94: New Zealand
98
+ # 529: Cyprus 750: Mexico 955: Malaysia
99
+ # 531: Macedonia 759: Venezuela 977: ISSN
100
+ # 535: Malta 76: Switzerland 978: ISBN
101
+ # 539: Ireland 770: Colombia 979: ISMN
102
+ # 54: Belgium & Luxembourg 773: Uruguay 980: Refund receipts
103
+ # 560: Portugal 775: Peru 981 & 982: CCC
104
+ # 569: Iceland 777: Bolivia 99: Coupons
105
+ # 57: Denmark 779: Argentina
106
+ #
107
+ # ISSN - International Standard Serial Number for Periodicals
108
+ # ISBN - International Standard Book Numbering
109
+ # ISMN - International Standard Music Number
110
+ # CCC - Common Currency Coupons
111
+ #
112
+ # RENDERING
113
+ #
114
+ # When rendered, the initial digit of the number system is shown to the
115
+ # left and above the rest of the digits. The other two sets of six
116
+ # digits each are shown at the bottom of the code, aligned with the
117
+ # bottom of the code, and with the middle guard pattern bars extending
118
+ # down between them. The lower digits may be aligned flush with the
119
+ # bottom of the barcode, or the center of the text may be aligned with the
120
+ # bottom of the barcode.
121
+
122
+ class EAN13 < Barcode1D
123
+
124
+ # patterns to create the bar codes:
125
+
126
+ # left side, odd/even
127
+ LEFT_PATTERNS = {
128
+ '0' => { 'o' => '0001101', 'e' => '0100111'},
129
+ '1' => { 'o' => '0011001', 'e' => '0110011'},
130
+ '2' => { 'o' => '0010011', 'e' => '0011011'},
131
+ '3' => { 'o' => '0111101', 'e' => '0100001'},
132
+ '4' => { 'o' => '0100011', 'e' => '0011101'},
133
+ '5' => { 'o' => '0110001', 'e' => '0111001'},
134
+ '6' => { 'o' => '0101111', 'e' => '0000101'},
135
+ '7' => { 'o' => '0111011', 'e' => '0010001'},
136
+ '8' => { 'o' => '0110111', 'e' => '0001001'},
137
+ '9' => { 'o' => '0001011', 'e' => '0010111'},
138
+ };
139
+
140
+ # All left patterns start with a space and end with a bar
141
+ LEFT_PATTERNS_RLE = {
142
+ '0' => { 'o' => '3211', 'e' => '1123'},
143
+ '1' => { 'o' => '2221', 'e' => '1222'},
144
+ '2' => { 'o' => '2122', 'e' => '2212'},
145
+ '3' => { 'o' => '1411', 'e' => '1141'},
146
+ '4' => { 'o' => '1132', 'e' => '2311'},
147
+ '5' => { 'o' => '1231', 'e' => '1321'},
148
+ '6' => { 'o' => '1114', 'e' => '4111'},
149
+ '7' => { 'o' => '1312', 'e' => '2131'},
150
+ '8' => { 'o' => '1213', 'e' => '3121'},
151
+ '9' => { 'o' => '3112', 'e' => '2113'},
152
+ };
153
+
154
+ LEFT_PARITY_PATTERNS = {
155
+ '0' => 'oooooo',
156
+ '1' => 'ooeoee',
157
+ '2' => 'ooeeoe',
158
+ '3' => 'ooeeeo',
159
+ '4' => 'oeooee',
160
+ '5' => 'oeeooe',
161
+ '6' => 'oeeeoo',
162
+ '7' => 'oeoeoe',
163
+ '8' => 'oeoeeo',
164
+ '9' => 'oeeoeo',
165
+ };
166
+
167
+ # right side
168
+ RIGHT_PATTERNS = {
169
+ '0' => '1110010',
170
+ '1' => '1100110',
171
+ '2' => '1101100',
172
+ '3' => '1000010',
173
+ '4' => '1011100',
174
+ '5' => '1001110',
175
+ '6' => '1010000',
176
+ '7' => '1000100',
177
+ '8' => '1001000',
178
+ '9' => '1110100',
179
+ };
180
+
181
+ # All right patterns start with a bar and end with a space
182
+ RIGHT_PATTERNS_RLE = {
183
+ '0' => '3211',
184
+ '1' => '2221',
185
+ '2' => '2122',
186
+ '3' => '1411',
187
+ '4' => '1132',
188
+ '5' => '1231',
189
+ '6' => '1114',
190
+ '7' => '1312',
191
+ '8' => '1213',
192
+ '9' => '3112',
193
+ };
194
+
195
+ # AAAAHHHHHHHHH side + middle + side is 666, the number of the beast
196
+ SIDE_GUARD_PATTERN='101';
197
+ MIDDLE_GUARD_PATTERN='01010';
198
+
199
+ # Starts with bar
200
+ SIDE_GUARD_PATTERN_RLE='111';
201
+ # Starts with space
202
+ MIDDLE_GUARD_PATTERN_RLE='11111';
203
+
204
+ DEFAULT_OPTIONS = {
205
+ :line_character => '1',
206
+ :space_character => '0'
207
+ }
208
+
209
+ # Specific for EAN
210
+ attr_reader :number_system
211
+ attr_reader :manufacturers_code
212
+ attr_reader :product_code
213
+
214
+ class << self
215
+ # returns true or false - must be 12-13 digits
216
+ def can_encode?(value, options = nil)
217
+ if !options
218
+ value.to_s =~ /^[0-9]{12,13}$/
219
+ elsif (options[:checksum_included])
220
+ value.to_s =~ /^[0-9]{13}$/
221
+ else
222
+ value.to_s =~ /^[0-9]{12}$/
223
+ end
224
+ end
225
+
226
+ # Generates check digit given a string to encode. It assumes there
227
+ # is no check digit on the "value".
228
+ def generate_check_digit_for(value)
229
+ raise UnencodableCharactersError unless self.can_encode?(value, :checksum_included => false)
230
+ mult = 3
231
+ value = value.split('').inject(0) { |a,c| mult = 4 - mult ; a + c.to_i * mult }
232
+ (10 - (value % 10)) % 10
233
+ end
234
+
235
+ # validates the check digit given a string - assumes check digit
236
+ # is last digit of string.
237
+ def validate_check_digit_for(value)
238
+ raise UnencodableCharactersError unless self.can_encode?(value, :checksum_included => true)
239
+ md = value.match(/^(\d{12})(\d)$/)
240
+ self.generate_check_digit_for(md[1]) == md[2].to_i
241
+ end
242
+
243
+ # Decode a string representing an rle or bar pattern EAN-13.
244
+ # Note that the string might be backward or forward. This
245
+ # will return an EAN13 object.
246
+ def decode(str)
247
+ if str.length == 95
248
+ # bar pattern
249
+ str = bars_to_rle(str)
250
+ elsif str.length == 59
251
+ # rle
252
+ else
253
+ raise UnencodableCharactersError, "Pattern must be 95 unit bar pattern or 59 character rle."
254
+ end
255
+
256
+ # Check the guard patterns
257
+ unless str[0..2] == SIDE_GUARD_PATTERN_RLE && str[56..58] == SIDE_GUARD_PATTERN_RLE && str[27..31] == MIDDLE_GUARD_PATTERN_RLE
258
+ raise UnencodableCharactersError, "Missing or incorrect guard patterns"
259
+ end
260
+
261
+ # Now I have an rle pattern, simply need to decode
262
+ # according to the LEFT_PATTERNS_RLE, keeping track
263
+ # of the parity for each position.
264
+
265
+ # Set up the decoder
266
+ left_parity_sequence = ''
267
+ left_digits = ''
268
+ right_parity_sequence = ''
269
+ right_digits = ''
270
+ left_initial_offset = SIDE_GUARD_PATTERN_RLE.length
271
+ right_initial_offset = SIDE_GUARD_PATTERN_RLE.length + (4*6) + MIDDLE_GUARD_PATTERN_RLE.length
272
+
273
+ # Decode the left side
274
+ (0..5).each do |left_offset|
275
+ found = false
276
+ digit_rle = str[(left_initial_offset + left_offset*4),4]
277
+ ['o','e'].each do |parity|
278
+ ('0'..'9').each do |digit|
279
+ if LEFT_PATTERNS_RLE[digit][parity] == digit_rle
280
+ left_parity_sequence += parity
281
+ left_digits += digit
282
+ found = true
283
+ break
284
+ end
285
+ end
286
+ end
287
+ raise UndecodableCharactersError, "Invalid sequence: #{digit_rle}" unless found
288
+ end
289
+
290
+ # Decode the right side
291
+ (0..5).each do |right_offset|
292
+ found = false
293
+ digit_rle = str[(right_initial_offset + right_offset*4),4]
294
+ ['o','e'].each do |parity|
295
+ ('0'..'9').each do |digit|
296
+ if LEFT_PATTERNS_RLE[digit][parity] == digit_rle
297
+ right_parity_sequence += parity
298
+ right_digits += digit
299
+ found = true
300
+ break
301
+ end
302
+ end
303
+ end
304
+ raise UndecodableCharactersError, "Invalid sequence: #{digit_rle}" unless found
305
+ end
306
+
307
+ # If left parity sequence is 'eeeeee', the string is reversed
308
+ if left_parity_sequence == 'eeeeee'
309
+ left_digits, right_digits, left_parity_sequence = right_digits.reverse, left_digits.reverse, right_parity_sequence.reverse.tr('eo','oe')
310
+ end
311
+
312
+ # Now, find the parity digit
313
+ parity_digit = nil
314
+ ('0'..'9').each do |x|
315
+ if LEFT_PARITY_PATTERNS[x] == left_parity_sequence
316
+ parity_digit = x
317
+ break
318
+ end
319
+ end
320
+
321
+ raise UndecodableCharactersError, "Weird parity: #{left_parity_sequence}" unless parity_digit
322
+
323
+ # Debugging
324
+ #puts "Left digits: #{left_digits} Left parity: #{left_parity_sequence}"
325
+ #puts "Right digits: #{right_digits} Right parity: #{right_parity_sequence}"
326
+ #puts "Parity: #{parity_digit}"
327
+
328
+ EAN13.new(parity_digit + left_digits + right_digits, :checksum_included => true)
329
+ end
330
+ end
331
+
332
+ # Options are :line_character, :space_character, and
333
+ # :checksum_included.
334
+ def initialize(value, options = {})
335
+
336
+ @options = DEFAULT_OPTIONS.merge(options)
337
+
338
+ # Can we encode this value?
339
+ raise UnencodableCharactersError unless self.class.can_encode?(value, @options)
340
+
341
+ if @options[:checksum_included]
342
+ @encoded_string = value.to_s
343
+ raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
344
+ md = @encoded_string.match(/^(\d+?)(\d)$/)
345
+ @value, @check_digit = md[1], md[2].to_i
346
+ else
347
+ # need to add a checksum
348
+ @value = value.to_s
349
+ @check_digit = self.class.generate_check_digit_for(@value)
350
+ @encoded_string = "#{@value}#{@check_digit}"
351
+ end
352
+
353
+ md = @value.match(/^(\d{2})(\d{5})(\d{5})/)
354
+ @number_system, @manufacturers_code, @product_code = md[1], md[2], md[3]
355
+ end
356
+
357
+ # not usable with EAN codes
358
+ def wn
359
+ raise NotImplementedError
360
+ end
361
+
362
+ # returns a run-length-encoded string representation
363
+ def rle
364
+ if @rle
365
+ @rle
366
+ else
367
+ md = @encoded_string.match(/^(\d)(\d{6})(\d{6})/)
368
+ @rle = gen_rle(md[1], md[2], md[3])
369
+ end
370
+ end
371
+
372
+ # returns 1s and 0s (for "black" and "white")
373
+ def bars
374
+ @bars ||= self.class.rle_to_bars(self.rle, @options)
375
+ end
376
+
377
+ # returns the total unit width of the bar code
378
+ def width
379
+ @width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
380
+ end
381
+
382
+ private
383
+
384
+ def gen_rle(parity_digit, left_half, right_half)
385
+ (SIDE_GUARD_PATTERN_RLE + (0..5).collect { |n| LEFT_PATTERNS_RLE[left_half[n,1]][LEFT_PARITY_PATTERNS[parity_digit][n,1]] }.join('') + MIDDLE_GUARD_PATTERN_RLE + right_half.split('').collect { |c| RIGHT_PATTERNS_RLE[c] }.join('') + SIDE_GUARD_PATTERN_RLE)
386
+ end
387
+
388
+ end
389
+ end