barcode1dtools 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/MIT-LICENSE
ADDED
@@ -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
|