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
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
|