barcode1dtools 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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