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.
- data/MIT-LICENSE +21 -0
- data/lib/barcode1dtools.rb +119 -0
- data/lib/barcode1dtools/ean13.rb +389 -0
- data/lib/barcode1dtools/ean8.rb +252 -0
- data/lib/barcode1dtools/interleaved2of5.rb +248 -0
- data/lib/barcode1dtools/upc_a.rb +143 -0
- data/lib/barcode1dtools/upc_e.rb +274 -0
- data/lib/barcode1dtools/upc_supplemental_2.rb +221 -0
- data/lib/barcode1dtools/upc_supplemental_5.rb +246 -0
- data/test/test_barcode1d.rb +56 -0
- data/test/test_barcode1dean13.rb +102 -0
- data/test/test_barcode1dean8.rb +95 -0
- data/test/test_barcode1di2of5.rb +86 -0
- data/test/test_barcode1dupca.rb +102 -0
- data/test/test_barcode1dupce.rb +127 -0
- data/test/test_barcode1dupcsupp2.rb +89 -0
- data/test/test_barcode1dupcsupp5.rb +94 -0
- metadata +93 -0
@@ -0,0 +1,143 @@
|
|
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
|
+
require 'barcode1dtools/ean13'
|
9
|
+
|
10
|
+
module Barcode1DTools
|
11
|
+
|
12
|
+
# Barcode1DTools::UPC_A - Create pattern for UPC-A barcodes
|
13
|
+
#
|
14
|
+
# The value encoded is an 11-digit integer, and a checksum digit
|
15
|
+
# will be added. You can add the option :checksum_included => true
|
16
|
+
# when initializing to specify that you have already included a
|
17
|
+
# checksum.
|
18
|
+
#
|
19
|
+
# # Note that this number is a UPC-A, with the number system of 08,
|
20
|
+
# # manufacturer's code of "28999", product code of "00682", and a
|
21
|
+
# # checksum of "3" (not included)
|
22
|
+
# num = '82899900682'
|
23
|
+
# bc = Barcode1DTools::UPC_A.new(num)
|
24
|
+
# pattern = bc.bars
|
25
|
+
# rle_pattern = bc.rle
|
26
|
+
# width = bc.width
|
27
|
+
# check_digit = Barcode1DTools::UPC_A.generate_check_digit_for(num)
|
28
|
+
#
|
29
|
+
# A UPC-A barcode is an EAN-13 with an initial digit of "0" (that
|
30
|
+
# is the left digit of the number system designator). Like the
|
31
|
+
# EAN-13, the code is broken into a single-digit number system,
|
32
|
+
# 5 digit manufacturer code, 5 digit product code, and single
|
33
|
+
# digit checksum.
|
34
|
+
#
|
35
|
+
# The number system is:
|
36
|
+
# 0, 1, 6, 7, 8 - standard UPC codes
|
37
|
+
# 2 - a product weight- generally calculated at the store.
|
38
|
+
# 3 - pharmaceuticals
|
39
|
+
# 4 - used for loyalty cards at stores
|
40
|
+
# 5 - coupons
|
41
|
+
# 9 - coupons
|
42
|
+
# 978 - for books, with a 10-digit ISBN coming after 978
|
43
|
+
#
|
44
|
+
# For code 2, the manufacturer code becomes an item number, and the
|
45
|
+
# product number is used for either the weight or the price, with
|
46
|
+
# the first digit determining which.
|
47
|
+
#
|
48
|
+
# For 5 and 9, the manufacturer code is the same as a standard
|
49
|
+
# UPC. The first three digits of the product code are used as a
|
50
|
+
# "family code" set by the manufacturer, and the last two digits
|
51
|
+
# are a "coupon code" which determines the amount of the discount
|
52
|
+
# based on a table released by the GS1 US (formerly the UCC).
|
53
|
+
# Code 5 coupons may be doubled or tripled, but code 9 may not.
|
54
|
+
#
|
55
|
+
#== Rendering
|
56
|
+
#
|
57
|
+
# The UPC-A is typically rendered at 1-1.5 inch across, and half
|
58
|
+
# an inch high. The number system digit and checksum digit are
|
59
|
+
# shown on the left and right sides of the code. The other two
|
60
|
+
# sets of five digits are rendered at the bottom of the barcode.
|
61
|
+
# The alignment can be either bottom of the text flush with
|
62
|
+
# bottom of barcode, or middle of text aligned with bottom of
|
63
|
+
# barcode. The two sets of five digits are separated by the two
|
64
|
+
# middle guard bars which always extend to the bottom. There
|
65
|
+
# should be at least 9 spaces of quiet area on either side of
|
66
|
+
# the code.
|
67
|
+
|
68
|
+
class UPC_A < EAN13
|
69
|
+
|
70
|
+
class << self
|
71
|
+
# Returns true or false - must be 11-13 digits. This
|
72
|
+
# also handles the case where the leading 0 is added.
|
73
|
+
def can_encode?(value, options = nil)
|
74
|
+
if !options
|
75
|
+
value.to_s =~ /^0?[0-9]{11,12}$/
|
76
|
+
elsif (options[:checksum_included])
|
77
|
+
value.to_s =~ /^0?[0-9]{12}$/
|
78
|
+
else
|
79
|
+
value.to_s =~ /^0?[0-9]{11}$/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generates check digit given a string to encode. It assumes there
|
84
|
+
# is no check digit on the "value".
|
85
|
+
def generate_check_digit_for(value)
|
86
|
+
super('0' + value)
|
87
|
+
end
|
88
|
+
|
89
|
+
# validates the check digit given a string - assumes check digit
|
90
|
+
# is last digit of string.
|
91
|
+
def validate_check_digit_for(value)
|
92
|
+
raise UnencodableCharactersError unless self.can_encode?(value, :checksum_included => true)
|
93
|
+
md = value.match(/^(0?\d{11})(\d)$/)
|
94
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def decode(value)
|
98
|
+
ean = super(value)
|
99
|
+
if ean.value[0,1] == '0'
|
100
|
+
new(ean.value[1,11])
|
101
|
+
else
|
102
|
+
raise UnencodableCharactersError
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Options are :line_character, :space_character, and
|
108
|
+
# :checksum_included.
|
109
|
+
def initialize(value, options = {})
|
110
|
+
|
111
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
112
|
+
|
113
|
+
# Can we encode this value?
|
114
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value, @options)
|
115
|
+
|
116
|
+
if @options[:checksum_included]
|
117
|
+
@encoded_string = value.to_s
|
118
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
119
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
120
|
+
@value, @check_digit = md[1], md[2].to_i
|
121
|
+
else
|
122
|
+
# need to add a checksum
|
123
|
+
@value = value.to_s
|
124
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
125
|
+
@encoded_string = "#{@value}#{@check_digit}"
|
126
|
+
end
|
127
|
+
|
128
|
+
md = @value.match(/^(\d)(\d{5})(\d{5})/)
|
129
|
+
@number_system, @manufacturers_code, @product_code = md[1], md[2], md[3]
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns a run-length-encoded string representation
|
133
|
+
def rle
|
134
|
+
if @rle
|
135
|
+
@rle
|
136
|
+
else
|
137
|
+
md = @encoded_string.match(/^(\d{6})(\d{6})/)
|
138
|
+
@rle = gen_rle('0', md[1], md[2])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,274 @@
|
|
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
|
+
require 'barcode1dtools/upc_a'
|
9
|
+
|
10
|
+
module Barcode1DTools
|
11
|
+
|
12
|
+
# Barcode1DTools::UPC_E - Create pattern for UPC-E barcodes
|
13
|
+
#
|
14
|
+
# The value encoded is an 6-digit integer, and a checksum digit
|
15
|
+
# will be added. You can add the option :checksum_included => true
|
16
|
+
# when initializing to specify that you have already included a
|
17
|
+
# checksum.
|
18
|
+
#
|
19
|
+
# num = '394932'
|
20
|
+
# bc = Barcode1DTools::UPC_E.new(num)
|
21
|
+
# pattern = bc.bars
|
22
|
+
# rle_pattern = bc.rle
|
23
|
+
# width = bc.width
|
24
|
+
# check_digit = Barcode1DTools::UPC_E.generate_check_digit_for(num)
|
25
|
+
#
|
26
|
+
# A UPC-E barcode is an abbreviated form of UPC-A, but can encode
|
27
|
+
# only a few codes. The checksum is derived from the full UPC-A
|
28
|
+
# digit sequence rather than the 6 digits of the UPC-E. The checksum
|
29
|
+
# is encoded as the parity, with the bar patterns the same as the left
|
30
|
+
# side of a standard UPC-A.
|
31
|
+
#
|
32
|
+
# The last digit of the UPC-E determines the pattern used to convert
|
33
|
+
# to a UPC-A.
|
34
|
+
#
|
35
|
+
# UPC-E UPC-A equivalent
|
36
|
+
# 2 digits for manufacturer code (plus last digit), 3 digits for product
|
37
|
+
# XXNNN0 0XX000-00NNN
|
38
|
+
# XXNNN1 0XX100-00NNN
|
39
|
+
# XXNNN2 0XX200-00NNN
|
40
|
+
# 3 digits for manufacturer code, 2 digits for product
|
41
|
+
# XXXNN3 0XXX00-000NN
|
42
|
+
# 4 digits for manufacturer code, 1 digit for product
|
43
|
+
# XXXXN4 0XXXX0-0000N
|
44
|
+
# 5 digits for manufacturer code, 1 digit for product (5-9)
|
45
|
+
# XXXXX5 0XXXXX-00005
|
46
|
+
# XXXXX6 0XXXXX-00006
|
47
|
+
# XXXXX7 0XXXXX-00007
|
48
|
+
# XXXXX8 0XXXXX-00008
|
49
|
+
# XXXXX9 0XXXXX-00009
|
50
|
+
#
|
51
|
+
#== Rendering
|
52
|
+
#
|
53
|
+
# The UPC-E is made for smaller items. Generally, they are rendered
|
54
|
+
# with the number system digit (0) on the left of the bars and the
|
55
|
+
# checksum on the right. The 6-digit payload is shown below the bars
|
56
|
+
# with the end guard bars extending half-way down the digits. The
|
57
|
+
# number system and check digit might be rendered in a slightly smaller
|
58
|
+
# font size. The UPC-E uses the same bar patterns as the left half of
|
59
|
+
# a regular UPC-A, but there is no middle pattern and the right guard
|
60
|
+
# pattern has an extra line/space pair.
|
61
|
+
|
62
|
+
class UPC_E < Barcode1D
|
63
|
+
|
64
|
+
LEFT_PATTERNS = UPC_A::LEFT_PATTERNS
|
65
|
+
LEFT_PATTERNS_RLE = UPC_A::LEFT_PATTERNS_RLE
|
66
|
+
|
67
|
+
PARITY_PATTERNS = {
|
68
|
+
'0' => 'eeeooo',
|
69
|
+
'1' => 'eeoeoo',
|
70
|
+
'2' => 'eeooeo',
|
71
|
+
'3' => 'eeoooe',
|
72
|
+
'4' => 'eoeeoo',
|
73
|
+
'5' => 'eooeeo',
|
74
|
+
'6' => 'eoooee',
|
75
|
+
'7' => 'eoeoeo',
|
76
|
+
'8' => 'eoeooe',
|
77
|
+
'9' => 'eooeoe',
|
78
|
+
};
|
79
|
+
|
80
|
+
LEFT_GUARD_PATTERN = '101'
|
81
|
+
RIGHT_GUARD_PATTERN = '010101'
|
82
|
+
LEFT_GUARD_PATTERN_RLE = '111'
|
83
|
+
RIGHT_GUARD_PATTERN_RLE = '111111'
|
84
|
+
|
85
|
+
DEFAULT_OPTIONS = {
|
86
|
+
:line_character => '1',
|
87
|
+
:space_character => '0'
|
88
|
+
}
|
89
|
+
|
90
|
+
# For UPC-E
|
91
|
+
attr_reader :number_system
|
92
|
+
attr_reader :manufacturers_code
|
93
|
+
attr_reader :product_code
|
94
|
+
attr_reader :upca_value
|
95
|
+
|
96
|
+
class << self
|
97
|
+
# Returns true or false - must be 6-8 digits. This
|
98
|
+
# also handles the case where the leading 0 is added.
|
99
|
+
def can_encode?(value, options = nil)
|
100
|
+
if !options
|
101
|
+
value.to_s =~ /^0?[0-9]{6,7}$/
|
102
|
+
elsif (options[:checksum_included])
|
103
|
+
value.to_s =~ /^0?[0-9]{7}$/
|
104
|
+
else
|
105
|
+
value.to_s =~ /^0?[0-9]{6}$/
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Generates check digit given a string to encode. It assumes there
|
110
|
+
# is no check digit on the "value".
|
111
|
+
def generate_check_digit_for(value)
|
112
|
+
UPC_A.generate_check_digit_for(self.upce_value_to_upca_value(value))
|
113
|
+
end
|
114
|
+
|
115
|
+
# validates the check digit given a string - assumes check digit
|
116
|
+
# is last digit of string.
|
117
|
+
def validate_check_digit_for(value)
|
118
|
+
raise UnencodableCharactersError unless self.can_encode?(value, :checksum_included => true)
|
119
|
+
md = value.match(/^(0?\d{6})(\d)$/)
|
120
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
def decode(str)
|
124
|
+
if str.length == 51
|
125
|
+
# bar pattern
|
126
|
+
str = bars_to_rle(str)
|
127
|
+
elsif str.length == 33 && str =~ /^[1-9]+$/
|
128
|
+
# rle
|
129
|
+
else
|
130
|
+
raise UnencodableCharactersError, "Pattern must be 51 unit bar pattern or 33 character rle."
|
131
|
+
end
|
132
|
+
|
133
|
+
# See if the string is reversed
|
134
|
+
if str[0..5] == RIGHT_GUARD_PATTERN_RLE && str[30..32] == LEFT_GUARD_PATTERN_RLE
|
135
|
+
str.reverse!
|
136
|
+
end
|
137
|
+
|
138
|
+
# Check the guard patterns
|
139
|
+
unless (str[0..2] == LEFT_GUARD_PATTERN_RLE && str[27..32] == RIGHT_GUARD_PATTERN_RLE)
|
140
|
+
raise UnencodableCharactersError, "Missing or incorrect guard patterns"
|
141
|
+
end
|
142
|
+
|
143
|
+
parity_sequence = ''
|
144
|
+
digits = ''
|
145
|
+
initial_offset = LEFT_GUARD_PATTERN_RLE.length
|
146
|
+
|
147
|
+
# Decode
|
148
|
+
(0..5).each do |offset|
|
149
|
+
found = false
|
150
|
+
digit_rle = str[(initial_offset + offset*4),4]
|
151
|
+
['o','e'].each do |parity|
|
152
|
+
('0'..'9').each do |digit|
|
153
|
+
if LEFT_PATTERNS_RLE[digit][parity] == digit_rle
|
154
|
+
parity_sequence += parity
|
155
|
+
digits += digit
|
156
|
+
found = true
|
157
|
+
break
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
raise UndecodableCharactersError, "Invalid sequence: #{digit_rle}" unless found
|
162
|
+
end
|
163
|
+
|
164
|
+
# Now, find the parity digit
|
165
|
+
parity_digit = nil
|
166
|
+
('0'..'9').each do |x|
|
167
|
+
if PARITY_PATTERNS[x] == parity_sequence
|
168
|
+
parity_digit = x
|
169
|
+
break
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
raise UndecodableCharactersError, "Weird parity: #{parity_sequence}" unless parity_digit
|
174
|
+
|
175
|
+
UPC_E.new('0' + digits + parity_digit, :checksum_included => true)
|
176
|
+
end
|
177
|
+
|
178
|
+
def upce_value_to_upca_value(value, options = {})
|
179
|
+
raise UnencodableCharactersError unless self.can_encode?(value, options)
|
180
|
+
# remove the check digit if it was included
|
181
|
+
value = value.to_i % 10 if options[:checksum_included]
|
182
|
+
value = sprintf('%06d', value.to_i)
|
183
|
+
if value =~ /(\d\d)(\d\d\d)([012])/
|
184
|
+
upca_value = "0#{$1}#{$3}0000#{$2}"
|
185
|
+
elsif value =~ /(\d\d\d)(\d\d)(3)/
|
186
|
+
upca_value = "0#{$1}00000#{$2}"
|
187
|
+
elsif value =~ /(\d\d\d\d)(\d)(4)/
|
188
|
+
upca_value = "0#{$1}00000#{$2}"
|
189
|
+
elsif value =~ /(\d\d\d\d\d)([5-9])/
|
190
|
+
upca_value = "0#{$1}0000#{$2}"
|
191
|
+
else
|
192
|
+
raise UnencodableCharactersError, "Cannot change UPC-E #{value} to UPC-A"
|
193
|
+
end
|
194
|
+
upca_value
|
195
|
+
end
|
196
|
+
|
197
|
+
def upca_value_to_upce_value(value, options = {})
|
198
|
+
raise UnencodableCharactersError unless UPC_A.can_encode?(value, options)
|
199
|
+
value = value % 10 if options[:checksum_included]
|
200
|
+
value = sprintf('%011d', value.to_i)
|
201
|
+
if value =~ /^0(\d\d\d\d[1-9])0000([5-9])/
|
202
|
+
upce_value = "0#{$1}#{$2}"
|
203
|
+
elsif value =~ /^0(\d\d\d[1-9])00000(\d)/
|
204
|
+
upce_value = "0#{$1}#{$2}4"
|
205
|
+
elsif value =~ /^0(\d\d)([012])0000(\d\d\d)/
|
206
|
+
upce_value = "0#{$1}#{$3}#{$2}"
|
207
|
+
elsif value =~ /^0(\d\d[3-9])00000(\d\d)/
|
208
|
+
upce_value = "0#{$1}#{$2}3"
|
209
|
+
else
|
210
|
+
raise UnencodableCharactersError, "Cannot change UPC-A #{value} to UPC-E"
|
211
|
+
end
|
212
|
+
upce_value
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Options are :line_character, :space_character, and
|
217
|
+
# :checksum_included.
|
218
|
+
def initialize(value, options = {})
|
219
|
+
|
220
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
221
|
+
|
222
|
+
# Can we encode this value?
|
223
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value, @options)
|
224
|
+
|
225
|
+
if @options[:checksum_included]
|
226
|
+
@encoded_string = value.to_s
|
227
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
228
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
229
|
+
@value, @check_digit = md[1], md[2].to_i
|
230
|
+
else
|
231
|
+
# need to add a checksum
|
232
|
+
@value = value.to_s
|
233
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
234
|
+
@encoded_string = "#{@value}#{@check_digit}"
|
235
|
+
end
|
236
|
+
|
237
|
+
@upca_value = self.class.upce_value_to_upca_value(@value)
|
238
|
+
md = @upca_value.match(/^(\d)(\d{5})(\d{5})/)
|
239
|
+
@number_system, @manufacturers_code, @product_code = md[1], md[2], md[3]
|
240
|
+
end
|
241
|
+
|
242
|
+
# not usable with EAN-style codes
|
243
|
+
def wn
|
244
|
+
raise NotImplementedError
|
245
|
+
end
|
246
|
+
|
247
|
+
# returns a run-length-encoded string representation
|
248
|
+
def rle
|
249
|
+
if @rle
|
250
|
+
@rle
|
251
|
+
else
|
252
|
+
md = @encoded_string.match(/(\d{6})(\d)$/)
|
253
|
+
@rle = gen_rle(md[1], md[2])
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# returns 1s and 0s (for "black" and "white")
|
258
|
+
def bars
|
259
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
260
|
+
end
|
261
|
+
|
262
|
+
# returns the total unit width of the bar code
|
263
|
+
def width
|
264
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
265
|
+
end
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
def gen_rle(payload, parity_digit)
|
270
|
+
(LEFT_GUARD_PATTERN_RLE + (0..5).collect { |n| LEFT_PATTERNS_RLE[payload[n,1]][PARITY_PATTERNS[parity_digit][n,1]] }.join('') + RIGHT_GUARD_PATTERN_RLE)
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,221 @@
|
|
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
|
+
require 'barcode1dtools/upc_a'
|
9
|
+
|
10
|
+
module Barcode1DTools
|
11
|
+
|
12
|
+
# Barcode1DTools::UPC_Supplemental_2 - Create pattern for UPC
|
13
|
+
# Supplemental 2 barcodes
|
14
|
+
#
|
15
|
+
# The value encoded is an 2-digit integer, and a checksum digit
|
16
|
+
# will be added. You can add the option :checksum_included => true
|
17
|
+
# when initializing to specify that you have already included a
|
18
|
+
# checksum.
|
19
|
+
#
|
20
|
+
# num = '24'
|
21
|
+
# bc = Barcode1DTools::UPC_Supplemental_2.new(num)
|
22
|
+
# pattern = bc.bars
|
23
|
+
# rle_pattern = bc.rle
|
24
|
+
# width = bc.width
|
25
|
+
# check_digit = Barcode1DTools::UPC_E.generate_check_digit_for(num)
|
26
|
+
#
|
27
|
+
# This type of barcode consists of 2 digits, and a check digit
|
28
|
+
# (simply a modulus 4 of the number encoded) that is encoded in
|
29
|
+
# the "parity" of the two barcode digits. The bar patterns are the
|
30
|
+
# same as the left half of a standard UPC-A.
|
31
|
+
#
|
32
|
+
# The 2-digit supplement is generally used on periodicals as an
|
33
|
+
# "issue number", so that the UPC-A code may remain the same
|
34
|
+
# across issues. The two are scanned together, and typically the
|
35
|
+
# scanner will return the two digits of the supplemental barcode
|
36
|
+
# immediately following the check digit from the main UPC-A. You
|
37
|
+
# will likely need to use the Barcode::UPC_A module in addition
|
38
|
+
# to this one to create the full code.
|
39
|
+
#
|
40
|
+
#== Rendering
|
41
|
+
#
|
42
|
+
# The 2-digit supplement is positioned to the right of the main UPC
|
43
|
+
# code, and the human-readable digits are usually printed above the
|
44
|
+
# supplemental barcode. UPC-A is generally rendered at one inch
|
45
|
+
# across, then there's a 1/8th inch gap, then the supplemental. A
|
46
|
+
# UPC-A is 95 units wide, so the gap is 24 units wide. The
|
47
|
+
# supplemental barcode is 20 units wide. The author hasn't viewed
|
48
|
+
# the specification, but note that the UPC (and more generally EAN)
|
49
|
+
# barcode system never a bar or space of more than four units
|
50
|
+
# width. Given that, the gap should likely be at last 10 units
|
51
|
+
# wide.
|
52
|
+
|
53
|
+
class UPC_Supplemental_2 < Barcode1D
|
54
|
+
|
55
|
+
LEFT_PATTERNS = UPC_A::LEFT_PATTERNS
|
56
|
+
LEFT_PATTERNS_RLE = UPC_A::LEFT_PATTERNS_RLE
|
57
|
+
|
58
|
+
# parity patterns, essentially binary counting where "e" is "1"
|
59
|
+
# and "o" is "0"
|
60
|
+
PARITY_PATTERNS = {
|
61
|
+
'0' => 'oo',
|
62
|
+
'1' => 'oe',
|
63
|
+
'2' => 'eo',
|
64
|
+
'3' => 'ee',
|
65
|
+
};
|
66
|
+
|
67
|
+
LEFT_GUARD_PATTERN = '1011'
|
68
|
+
MIDDLE_GUARD_PATTERN = '01'
|
69
|
+
LEFT_GUARD_PATTERN_RLE = '112'
|
70
|
+
MIDDLE_GUARD_PATTERN_RLE = '11'
|
71
|
+
|
72
|
+
DEFAULT_OPTIONS = {
|
73
|
+
:line_character => '1',
|
74
|
+
:space_character => '0'
|
75
|
+
}
|
76
|
+
|
77
|
+
class << self
|
78
|
+
# Returns true or false - must be 1-3 digits. This
|
79
|
+
# also handles the case where the leading 0 is added.
|
80
|
+
def can_encode?(value, options = nil)
|
81
|
+
if !options
|
82
|
+
value.to_s =~ /^\d{1,3}$/
|
83
|
+
elsif (options[:checksum_included])
|
84
|
+
value.to_s =~ /^\d\d\d?$/
|
85
|
+
else
|
86
|
+
value.to_s =~ /^\d\d?$/ && (0..99).include?(value.to_i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Generates check digit given a string to encode. It assumes there
|
91
|
+
# is no check digit on the "value".
|
92
|
+
def generate_check_digit_for(value)
|
93
|
+
value.to_i % 4
|
94
|
+
end
|
95
|
+
|
96
|
+
# validates the check digit given a string - assumes check digit
|
97
|
+
# is last digit of string.
|
98
|
+
def validate_check_digit_for(value)
|
99
|
+
raise UnencodableCharactersError unless self.can_encode?(value, :checksum_included => true)
|
100
|
+
md = value.match(/^(\d\d)(\d)$/)
|
101
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
def decode(str)
|
105
|
+
if str.length == 20
|
106
|
+
# bar pattern
|
107
|
+
str = bars_to_rle(str)
|
108
|
+
elsif str.length == 13 && str =~ /^[1-9]+$/
|
109
|
+
# rle
|
110
|
+
else
|
111
|
+
raise UnencodableCharactersError, "Pattern must be 20 unit bar pattern or 13 character rle."
|
112
|
+
end
|
113
|
+
|
114
|
+
# This string is "aaabbbbccdddd" where "aaa" is the left
|
115
|
+
# guard pattern, "bbbb" is the first digit, "cc" is the
|
116
|
+
# intra-digit guard pattern, and "dddd" is the second
|
117
|
+
# digit.
|
118
|
+
|
119
|
+
# See if the string is reversed
|
120
|
+
if str[10..12] == LEFT_GUARD_PATTERN_RLE.reverse && str[4..5] == MIDDLE_GUARD_PATTERN_RLE.reverse
|
121
|
+
str.reverse!
|
122
|
+
end
|
123
|
+
|
124
|
+
# Check the guard patterns
|
125
|
+
unless (str[0..2] == LEFT_GUARD_PATTERN_RLE && str[7..8] == MIDDLE_GUARD_PATTERN_RLE)
|
126
|
+
raise UnencodableCharactersError, "Missing or incorrect guard patterns"
|
127
|
+
end
|
128
|
+
|
129
|
+
parity_sequence = ''
|
130
|
+
digits = ''
|
131
|
+
|
132
|
+
# Decode
|
133
|
+
[str[3..6], str[9..12]].each do |digit_rle|
|
134
|
+
found = false
|
135
|
+
['o','e'].each do |parity|
|
136
|
+
('0'..'9').each do |digit|
|
137
|
+
if LEFT_PATTERNS_RLE[digit][parity] == digit_rle
|
138
|
+
parity_sequence += parity
|
139
|
+
digits += digit
|
140
|
+
found = true
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
raise UndecodableCharactersError, "Invalid sequence: #{digit_rle}" unless found
|
146
|
+
end
|
147
|
+
|
148
|
+
# Now, find the parity digit
|
149
|
+
parity_digit = nil
|
150
|
+
('0'..'3').each do |x|
|
151
|
+
if PARITY_PATTERNS[x] == parity_sequence
|
152
|
+
parity_digit = x
|
153
|
+
break
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
raise UndecodableCharactersError, "Weird parity: #{parity_sequence}" unless parity_digit
|
158
|
+
|
159
|
+
UPC_Supplemental_2.new(digits + parity_digit, :checksum_included => true)
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
# Options are :line_character, :space_character, and
|
165
|
+
# :checksum_included.
|
166
|
+
def initialize(value, options = {})
|
167
|
+
|
168
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
169
|
+
|
170
|
+
# Can we encode this value?
|
171
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value, @options)
|
172
|
+
|
173
|
+
if @options[:checksum_included]
|
174
|
+
@encoded_string = value.to_s
|
175
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
176
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
177
|
+
@value, @check_digit = md[1].to_i, md[2].to_i
|
178
|
+
else
|
179
|
+
# need to add a checksum
|
180
|
+
@value = value.to_i
|
181
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
182
|
+
@encoded_string = sprintf('%02d%1d',@value,@check_digit)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# not usable with EAN-style codes
|
187
|
+
def wn
|
188
|
+
raise NotImplementedError
|
189
|
+
end
|
190
|
+
|
191
|
+
# returns a run-length-encoded string representation
|
192
|
+
def rle
|
193
|
+
if @rle
|
194
|
+
@rle
|
195
|
+
else
|
196
|
+
md = @encoded_string.match(/(\d\d)(\d)$/)
|
197
|
+
@rle = gen_rle(md[1], md[2])
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# returns 1s and 0s (for "black" and "white")
|
202
|
+
def bars
|
203
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
204
|
+
end
|
205
|
+
|
206
|
+
# returns the total unit width of the bar code
|
207
|
+
def width
|
208
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def gen_rle(payload, parity_digit)
|
214
|
+
LEFT_GUARD_PATTERN_RLE +
|
215
|
+
LEFT_PATTERNS_RLE[payload[0,1]][PARITY_PATTERNS[parity_digit][0,1]] +
|
216
|
+
MIDDLE_GUARD_PATTERN_RLE +
|
217
|
+
LEFT_PATTERNS_RLE[payload[1,1]][PARITY_PATTERNS[parity_digit][1,1]]
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|