barcode1dtools 0.9.6.0 → 0.9.7.0
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/lib/barcode1dtools.rb +8 -2
- data/lib/barcode1dtools/codabar.rb +1 -2
- data/lib/barcode1dtools/code11.rb +1 -2
- data/lib/barcode1dtools/coop2of5.rb +222 -0
- data/lib/barcode1dtools/iata2of5.rb +223 -0
- data/lib/barcode1dtools/industrial2of5.rb +223 -0
- data/lib/barcode1dtools/matrix2of5.rb +222 -0
- data/lib/barcode1dtools/postnet.rb +223 -0
- data/test/test_barcode1dcoop2of5.rb +79 -0
- data/test/test_barcode1diata2of5.rb +79 -0
- data/test/test_barcode1dindustrial2of5.rb +79 -0
- data/test/test_barcode1dmatrix2of5.rb +79 -0
- data/test/test_barcode1dpostnet.rb +87 -0
- metadata +20 -4
@@ -0,0 +1,223 @@
|
|
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::Industrial2of5 - Create and decode bar patterns for
|
11
|
+
# Industrial 2 of 5. The value encoded is a number with digits 0-9.
|
12
|
+
# Internally, the value is treated as a string to preserve
|
13
|
+
# leading zeroes.
|
14
|
+
#
|
15
|
+
# Use :checksum_included => true if you have already added a
|
16
|
+
# checksum and wish to have it validated, or :skip_checksum =>
|
17
|
+
# false if you wish to have one added.
|
18
|
+
#
|
19
|
+
# Industrial 2 of 5 is low-density and limited. It should not be
|
20
|
+
# used in any new applications.
|
21
|
+
#
|
22
|
+
# val = "3423"
|
23
|
+
# bc = Barcode1DTools::Industrial2of5.new(val)
|
24
|
+
# pattern = bc.bars
|
25
|
+
# rle_pattern = bc.rle
|
26
|
+
# width = bc.width
|
27
|
+
#
|
28
|
+
# The object created is immutable.
|
29
|
+
#
|
30
|
+
# Barcode1DTools::Industrial2of5 creates the patterns that you need to
|
31
|
+
# display Industrial 2 of 5 barcodes. It can also decode a simple w/n
|
32
|
+
# string.
|
33
|
+
#
|
34
|
+
# Industrial2of5 characters consist of 3 bars and 2 spaces, with a narrow
|
35
|
+
# space between them. 2 of the bars/spaces in each symbol are wide.
|
36
|
+
#
|
37
|
+
# There are three formats for the returned pattern:
|
38
|
+
#
|
39
|
+
# bars - 1s and 0s specifying black lines and white spaces. Actual
|
40
|
+
# characters can be changed from "1" and 0" with options
|
41
|
+
# :line_character and :space_character.
|
42
|
+
#
|
43
|
+
# rle - Run-length-encoded version of the pattern. The first
|
44
|
+
# number is always a black line, with subsequent digits
|
45
|
+
# alternating between spaces and lines. The digits specify
|
46
|
+
# the width of each line or space.
|
47
|
+
#
|
48
|
+
# wn - The native format for this barcode type. The string
|
49
|
+
# consists of a series of "w" and "n" characters. The first
|
50
|
+
# item is always a black line, with subsequent characters
|
51
|
+
# alternating between spaces and lines. A "wide" item
|
52
|
+
# is twice the width of a "narrow" item.
|
53
|
+
#
|
54
|
+
# The "width" method will tell you the total end-to-end width, in
|
55
|
+
# units, of the entire barcode.
|
56
|
+
#
|
57
|
+
#== Rendering
|
58
|
+
#
|
59
|
+
# The standard w/n ratio seems to be 2:1. There seem to be no real
|
60
|
+
# standards for display.
|
61
|
+
|
62
|
+
class Industrial2of5 < Barcode1D
|
63
|
+
|
64
|
+
# Character sequence - 0-based offset in this string is character
|
65
|
+
# number
|
66
|
+
CHAR_SEQUENCE = "0123456789"
|
67
|
+
|
68
|
+
# Patterns for making bar codes. Note that the position
|
69
|
+
# weights are 1, 2, 4, and 7 and the last bit is parity.
|
70
|
+
# The patterns are for the bars, all spaces are narrow.
|
71
|
+
PATTERNS = {
|
72
|
+
'0'=> {'val'=>0 ,'wn'=>'nnnnwnwnn'},
|
73
|
+
'1'=> {'val'=>1 ,'wn'=>'wnnnnnnnw'},
|
74
|
+
'2'=> {'val'=>2 ,'wn'=>'nnwnnnnnw'},
|
75
|
+
'3'=> {'val'=>3 ,'wn'=>'wnwnnnnnn'},
|
76
|
+
'4'=> {'val'=>4 ,'wn'=>'nnnnwnnnw'},
|
77
|
+
'5'=> {'val'=>5 ,'wn'=>'wnnnwnnnn'},
|
78
|
+
'6'=> {'val'=>6 ,'wn'=>'nnwnwnnnn'},
|
79
|
+
'7'=> {'val'=>7 ,'wn'=>'nnnnnnwnw'},
|
80
|
+
'8'=> {'val'=>8 ,'wn'=>'wnnnnnwnn'},
|
81
|
+
'9'=> {'val'=>9 ,'wn'=>'nnwnnnwnn'}
|
82
|
+
}
|
83
|
+
|
84
|
+
GUARD_PATTERN_LEFT_WN = 'wnwnn'
|
85
|
+
GUARD_PATTERN_RIGHT_WN = 'wnnnw'
|
86
|
+
|
87
|
+
DEFAULT_OPTIONS = {
|
88
|
+
:line_character => '1',
|
89
|
+
:space_character => '0',
|
90
|
+
:w_character => 'w',
|
91
|
+
:n_character => 'n',
|
92
|
+
:wn_ratio => '2',
|
93
|
+
:skip_checksum => true
|
94
|
+
}
|
95
|
+
|
96
|
+
class << self
|
97
|
+
# Industrial2of5 can encode digits
|
98
|
+
def can_encode?(value)
|
99
|
+
value.to_s =~ /\A[0-9]+\z/
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_check_digit_for(value)
|
103
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
104
|
+
mult = 3
|
105
|
+
value = value.reverse.split('').inject(0) { |a,c| mult = 4 - mult ; a + c.to_i * mult }
|
106
|
+
(10 - (value % 10)) % 10
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_check_digit_for(value)
|
110
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
111
|
+
md = value.match(/^(\d+?)(\d)$/)
|
112
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
# Decode a string in rle format. This will return a Industrial2of5
|
116
|
+
# object.
|
117
|
+
def decode(str, options = {})
|
118
|
+
if str =~ /[^1-3]/ && str =~ /[^wn]/
|
119
|
+
raise UnencodableCharactersError, "Pattern must be rle or wn"
|
120
|
+
end
|
121
|
+
|
122
|
+
# ensure a wn string
|
123
|
+
if str =~ /[1-3]/
|
124
|
+
str = str.tr('123','nww')
|
125
|
+
end
|
126
|
+
|
127
|
+
if str.reverse =~ /\A#{GUARD_PATTERN_LEFT_WN}n.*?#{GUARD_PATTERN_RIGHT_WN}\z/
|
128
|
+
str.reverse!
|
129
|
+
end
|
130
|
+
|
131
|
+
# Note that every other character is an "n"
|
132
|
+
unless str =~ /\A([wn]n)+[wn]\z/ && str =~ /\A#{GUARD_PATTERN_LEFT_WN}n(.*?)#{GUARD_PATTERN_RIGHT_WN}\z/
|
133
|
+
raise UnencodableCharactersError, "Start/stop pattern is not detected."
|
134
|
+
end
|
135
|
+
|
136
|
+
wn_pattern = $1
|
137
|
+
|
138
|
+
# Each pattern is 5 bars and 4 spaces, with a space between.
|
139
|
+
unless wn_pattern.size % 10 == 0
|
140
|
+
raise UnencodableCharactersError, "Wrong number of bars."
|
141
|
+
end
|
142
|
+
|
143
|
+
decoded_string = ''
|
144
|
+
|
145
|
+
wn_pattern.scan(/(.{9})n/).each do |chunk|
|
146
|
+
|
147
|
+
chunk = chunk.first
|
148
|
+
found = false
|
149
|
+
|
150
|
+
PATTERNS.each do |char,hsh|
|
151
|
+
if chunk == hsh['wn']
|
152
|
+
decoded_string += char
|
153
|
+
found = true
|
154
|
+
break;
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
Industrial2of5.new(decoded_string, options)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
# Options are :line_character, :space_character, :w_character,
|
168
|
+
# :n_character, :checksum_included, and :skip_checksum.
|
169
|
+
def initialize(value, options = {})
|
170
|
+
|
171
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
172
|
+
|
173
|
+
# Can we encode this value?
|
174
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value)
|
175
|
+
|
176
|
+
@value = value.to_s
|
177
|
+
|
178
|
+
if @options[:skip_checksum]
|
179
|
+
@encoded_string = value.to_s
|
180
|
+
@value = value.to_s
|
181
|
+
@check_digit = nil
|
182
|
+
elsif @options[:checksum_included]
|
183
|
+
@encoded_string = value.to_s
|
184
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
185
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
186
|
+
@value, @check_digit = md[1], md[2].to_i
|
187
|
+
else
|
188
|
+
@value = value.to_s
|
189
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
190
|
+
@encoded_string = "#{@value}#{@check_digit}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns a string of "w" or "n" ("wide" and "narrow")
|
195
|
+
def wn
|
196
|
+
@wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
|
197
|
+
end
|
198
|
+
|
199
|
+
# returns a run-length-encoded string representation
|
200
|
+
def rle
|
201
|
+
@rle ||= self.class.wn_to_rle(self.wn, @options)
|
202
|
+
end
|
203
|
+
|
204
|
+
# returns 1s and 0s (for "black" and "white")
|
205
|
+
def bars
|
206
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
207
|
+
end
|
208
|
+
|
209
|
+
# returns the total unit width of the bar code
|
210
|
+
def width
|
211
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
# Creates the actual w/n pattern. Note that there is a narrow space
|
217
|
+
# between every single bar.
|
218
|
+
def wn_str
|
219
|
+
@wn_str ||= GUARD_PATTERN_LEFT_WN + 'n' + @encoded_string.split('').collect { |c| PATTERNS[c]['wn'] }.join('n') + 'n' + GUARD_PATTERN_RIGHT_WN
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,222 @@
|
|
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::Matrix2of5 - Create and decode bar patterns for
|
11
|
+
# Matrix 2 of 5. The value encoded is a number with digits 0-9.
|
12
|
+
# Internally, the value is treated as a string to preserve
|
13
|
+
# leading zeroes.
|
14
|
+
#
|
15
|
+
# Use :checksum_included => true if you have already added a
|
16
|
+
# checksum and wish to have it validated, or :skip_checksum =>
|
17
|
+
# true if you don't wish to add one or have it validated.
|
18
|
+
#
|
19
|
+
# Matrix 2 of 5 is low-density and limited. It should not be
|
20
|
+
# used in any new applications.
|
21
|
+
#
|
22
|
+
# val = "3423"
|
23
|
+
# bc = Barcode1DTools::Matrix2of5.new(val)
|
24
|
+
# pattern = bc.bars
|
25
|
+
# rle_pattern = bc.rle
|
26
|
+
# width = bc.width
|
27
|
+
#
|
28
|
+
# The object created is immutable.
|
29
|
+
#
|
30
|
+
# Barcode1DTools::Matrix2of5 creates the patterns that you need to
|
31
|
+
# display Matrix 2 of 5 barcodes. It can also decode a simple w/n
|
32
|
+
# string.
|
33
|
+
#
|
34
|
+
# Matrix2of5 characters consist of 3 bars and 2 spaces, with a narrow
|
35
|
+
# space between them. 2 of the bars/spaces in each symbol are wide.
|
36
|
+
#
|
37
|
+
# There are three formats for the returned pattern:
|
38
|
+
#
|
39
|
+
# bars - 1s and 0s specifying black lines and white spaces. Actual
|
40
|
+
# characters can be changed from "1" and 0" with options
|
41
|
+
# :line_character and :space_character.
|
42
|
+
#
|
43
|
+
# rle - Run-length-encoded version of the pattern. The first
|
44
|
+
# number is always a black line, with subsequent digits
|
45
|
+
# alternating between spaces and lines. The digits specify
|
46
|
+
# the width of each line or space.
|
47
|
+
#
|
48
|
+
# wn - The native format for this barcode type. The string
|
49
|
+
# consists of a series of "w" and "n" characters. The first
|
50
|
+
# item is always a black line, with subsequent characters
|
51
|
+
# alternating between spaces and lines. A "wide" item
|
52
|
+
# is twice the width of a "narrow" item.
|
53
|
+
#
|
54
|
+
# The "width" method will tell you the total end-to-end width, in
|
55
|
+
# units, of the entire barcode.
|
56
|
+
#
|
57
|
+
#== Rendering
|
58
|
+
#
|
59
|
+
# The standard w/n ratio seems to be 2:1. There seem to be no real
|
60
|
+
# standards for display.
|
61
|
+
|
62
|
+
class Matrix2of5 < Barcode1D
|
63
|
+
|
64
|
+
# Character sequence - 0-based offset in this string is character
|
65
|
+
# number
|
66
|
+
CHAR_SEQUENCE = "0123456789"
|
67
|
+
|
68
|
+
# Patterns for making bar codes. Note that the position
|
69
|
+
# weights are 1, 2, 4, and 7, and the last bit is parity.
|
70
|
+
# Each letter is an alternating bar then space, and there
|
71
|
+
# is a narrow space between each character.
|
72
|
+
PATTERNS = {
|
73
|
+
'0'=> {'val'=>0 ,'wn'=>'nnwwn'},
|
74
|
+
'1'=> {'val'=>1 ,'wn'=>'wnnnw'},
|
75
|
+
'2'=> {'val'=>2 ,'wn'=>'nwnnw'},
|
76
|
+
'3'=> {'val'=>3 ,'wn'=>'wwnnn'},
|
77
|
+
'4'=> {'val'=>4 ,'wn'=>'nnwnw'},
|
78
|
+
'5'=> {'val'=>5 ,'wn'=>'wnwnn'},
|
79
|
+
'6'=> {'val'=>6 ,'wn'=>'nwwnn'},
|
80
|
+
'7'=> {'val'=>7 ,'wn'=>'nnnww'},
|
81
|
+
'8'=> {'val'=>8 ,'wn'=>'wnnwn'},
|
82
|
+
'9'=> {'val'=>9 ,'wn'=>'nwnwn'}
|
83
|
+
}
|
84
|
+
|
85
|
+
GUARD_PATTERN_LEFT_WN = 'wnnnn'
|
86
|
+
GUARD_PATTERN_RIGHT_WN = 'wnnnn'
|
87
|
+
|
88
|
+
DEFAULT_OPTIONS = {
|
89
|
+
:line_character => '1',
|
90
|
+
:space_character => '0',
|
91
|
+
:w_character => 'w',
|
92
|
+
:n_character => 'n',
|
93
|
+
:wn_ratio => '2'
|
94
|
+
}
|
95
|
+
|
96
|
+
class << self
|
97
|
+
# Matrix2of5 can encode digits
|
98
|
+
def can_encode?(value)
|
99
|
+
value.to_s =~ /\A[0-9]+\z/
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_check_digit_for(value)
|
103
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
104
|
+
mult = 3
|
105
|
+
value = value.reverse.split('').inject(0) { |a,c| mult = 4 - mult ; a + c.to_i * mult }
|
106
|
+
(10 - (value % 10)) % 10
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_check_digit_for(value)
|
110
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
111
|
+
md = value.match(/^(\d+?)(\d)$/)
|
112
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
# Decode a string in rle format. This will return a Matrix2of5
|
116
|
+
# object.
|
117
|
+
def decode(str, options = {})
|
118
|
+
if str =~ /[^1-3]/ && str =~ /[^wn]/
|
119
|
+
raise UnencodableCharactersError, "Pattern must be rle or wn"
|
120
|
+
end
|
121
|
+
|
122
|
+
# ensure a wn string
|
123
|
+
if str =~ /[1-3]/
|
124
|
+
str = str.tr('123','nww')
|
125
|
+
end
|
126
|
+
|
127
|
+
if str.reverse =~ /\A#{GUARD_PATTERN_LEFT_WN}n.*?#{GUARD_PATTERN_RIGHT_WN}\z/
|
128
|
+
str.reverse!
|
129
|
+
end
|
130
|
+
|
131
|
+
unless str =~ /\A#{GUARD_PATTERN_LEFT_WN}n(.*?)#{GUARD_PATTERN_RIGHT_WN}\z/
|
132
|
+
raise UnencodableCharactersError, "Start/stop pattern is not detected."
|
133
|
+
end
|
134
|
+
|
135
|
+
wn_pattern = $1
|
136
|
+
|
137
|
+
# Each pattern is 3 bars and 2 spaces, with a space between.
|
138
|
+
unless wn_pattern.size % 6 == 0
|
139
|
+
raise UnencodableCharactersError, "Wrong number of bars."
|
140
|
+
end
|
141
|
+
|
142
|
+
decoded_string = ''
|
143
|
+
|
144
|
+
wn_pattern.scan(/(.{5})n/).each do |chunk|
|
145
|
+
|
146
|
+
chunk = chunk.first
|
147
|
+
found = false
|
148
|
+
|
149
|
+
PATTERNS.each do |char,hsh|
|
150
|
+
if chunk == hsh['wn']
|
151
|
+
decoded_string += char
|
152
|
+
found = true
|
153
|
+
break;
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
Matrix2of5.new(decoded_string, options)
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
# Options are :line_character, :space_character, :w_character,
|
167
|
+
# :n_character, :checksum_included, and :skip_checksum.
|
168
|
+
def initialize(value, options = {})
|
169
|
+
|
170
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
171
|
+
|
172
|
+
# Can we encode this value?
|
173
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value)
|
174
|
+
|
175
|
+
@value = value.to_s
|
176
|
+
|
177
|
+
if @options[:skip_checksum]
|
178
|
+
@encoded_string = value.to_s
|
179
|
+
@value = value.to_s
|
180
|
+
@check_digit = nil
|
181
|
+
elsif @options[:checksum_included]
|
182
|
+
@encoded_string = value.to_s
|
183
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
184
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
185
|
+
@value, @check_digit = md[1], md[2].to_i
|
186
|
+
else
|
187
|
+
@value = value.to_s
|
188
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
189
|
+
@encoded_string = "#{@value}#{@check_digit}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns a string of "w" or "n" ("wide" and "narrow")
|
194
|
+
def wn
|
195
|
+
@wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
|
196
|
+
end
|
197
|
+
|
198
|
+
# returns a run-length-encoded string representation
|
199
|
+
def rle
|
200
|
+
@rle ||= self.class.wn_to_rle(self.wn, @options)
|
201
|
+
end
|
202
|
+
|
203
|
+
# returns 1s and 0s (for "black" and "white")
|
204
|
+
def bars
|
205
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
206
|
+
end
|
207
|
+
|
208
|
+
# returns the total unit width of the bar code
|
209
|
+
def width
|
210
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# Creates the actual w/n pattern. Note that there is a narrow space
|
216
|
+
# between each character.
|
217
|
+
def wn_str
|
218
|
+
@wn_str ||= GUARD_PATTERN_LEFT_WN + 'n' + @encoded_string.split('').collect { |c| PATTERNS[c]['wn'] }.join('n') + 'n' + GUARD_PATTERN_RIGHT_WN
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,223 @@
|
|
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::PostNet - Create and decode bar patterns for
|
11
|
+
# PostNet. The value encoded is a zip code, which may be 5,
|
12
|
+
# 9, or 11 digits long.
|
13
|
+
#
|
14
|
+
# Use :checksum_included => true if you have already added a
|
15
|
+
# checksum and wish to have it validated, or :skip_checksum =>
|
16
|
+
# true if you don't wish to add one or have it validated.
|
17
|
+
#
|
18
|
+
# PostNet is used by the USPS for mail sorting, although it
|
19
|
+
# is technically deprecated.
|
20
|
+
#
|
21
|
+
# val = "37211"
|
22
|
+
# bc = Barcode1DTools::PostNet.new(val)
|
23
|
+
# pattern = bc.bars
|
24
|
+
# rle_pattern = bc.rle
|
25
|
+
# width = bc.width
|
26
|
+
#
|
27
|
+
# The object created is immutable.
|
28
|
+
#
|
29
|
+
# Barcode1DTools::PostNet creates the patterns that you need to
|
30
|
+
# display PostNet barcodes. It can also decode a simple w/n
|
31
|
+
# string. In this symbology, "wide" means "tall" and "narrow"
|
32
|
+
# means "short".
|
33
|
+
#
|
34
|
+
# There are three formats for the returned pattern:
|
35
|
+
#
|
36
|
+
# bars - 1s and 0s specifying black lines and white spaces. Actual
|
37
|
+
# characters can be changed from "1" and 0" with options
|
38
|
+
# :line_character and :space_character.
|
39
|
+
#
|
40
|
+
# rle - Run-length-encoded version of the pattern. The first
|
41
|
+
# number is always a black line, with subsequent digits
|
42
|
+
# alternating between spaces and lines. The digits specify
|
43
|
+
# the width of each line or space.
|
44
|
+
#
|
45
|
+
# wn - The native format for this barcode type. The string
|
46
|
+
# consists of a series of "w" and "n" characters. The first
|
47
|
+
# item is always a black line, with subsequent characters
|
48
|
+
# alternating between spaces and lines. A "wide" item
|
49
|
+
# is twice the width of a "narrow" item.
|
50
|
+
#
|
51
|
+
# The "width" method will tell you the total end-to-end width, in
|
52
|
+
# units, of the entire barcode.
|
53
|
+
#
|
54
|
+
#== Rendering
|
55
|
+
#
|
56
|
+
# See USPS documentation for exact specifications for display.
|
57
|
+
|
58
|
+
class PostNet < Barcode1D
|
59
|
+
|
60
|
+
# Character sequence - 0-based offset in this string is character
|
61
|
+
# number
|
62
|
+
CHAR_SEQUENCE = "0123456789"
|
63
|
+
|
64
|
+
# Patterns for making bar codes. Note that the position
|
65
|
+
# weights are 7, 4, 2, 1 and the last bit is parity.
|
66
|
+
# Each letter is an alternating bar then space, and there
|
67
|
+
# is a narrow space between each character.
|
68
|
+
PATTERNS = {
|
69
|
+
'0'=> {'val'=>0 ,'wn'=>'wwnnn'},
|
70
|
+
'1'=> {'val'=>1 ,'wn'=>'nnnww'},
|
71
|
+
'2'=> {'val'=>2 ,'wn'=>'nnwnw'},
|
72
|
+
'3'=> {'val'=>3 ,'wn'=>'nnwwn'},
|
73
|
+
'4'=> {'val'=>4 ,'wn'=>'nwnnw'},
|
74
|
+
'5'=> {'val'=>5 ,'wn'=>'nwnwn'},
|
75
|
+
'6'=> {'val'=>6 ,'wn'=>'nwwnn'},
|
76
|
+
'7'=> {'val'=>7 ,'wn'=>'wnnnw'},
|
77
|
+
'8'=> {'val'=>8 ,'wn'=>'wnnwn'},
|
78
|
+
'9'=> {'val'=>9 ,'wn'=>'wnwnn'}
|
79
|
+
}
|
80
|
+
|
81
|
+
GUARD_PATTERN_LEFT_WN = 'w'
|
82
|
+
GUARD_PATTERN_RIGHT_WN = 'w'
|
83
|
+
|
84
|
+
DEFAULT_OPTIONS = {
|
85
|
+
:line_character => '1',
|
86
|
+
:space_character => '0',
|
87
|
+
:w_character => 'w',
|
88
|
+
:n_character => 'n'
|
89
|
+
}
|
90
|
+
|
91
|
+
class << self
|
92
|
+
# PostNet can encode 5, 9, or 11 digits, plus a check digit.
|
93
|
+
def can_encode?(value)
|
94
|
+
value.to_s =~ /\A\d{5}(\d{4})?(\d{2})?\d?\z/
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate_check_digit_for(value)
|
98
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
99
|
+
value = value.split('').collect { |c| c.to_i }.inject(0) { |a,c| a + c }
|
100
|
+
(10 - (value % 10)) % 10
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_check_digit_for(value)
|
104
|
+
raise UnencodableCharactersError unless self.can_encode?(value)
|
105
|
+
md = value.match(/^(\d+?)(\d)$/)
|
106
|
+
self.generate_check_digit_for(md[1]) == md[2].to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
# Decode a string in rle format. This will return a PostNet
|
110
|
+
# object.
|
111
|
+
def decode(str, options = {})
|
112
|
+
if str =~ /[^1-3]/ && str =~ /[^wn]/
|
113
|
+
raise UnencodableCharactersError, "Pattern must be rle or wn"
|
114
|
+
end
|
115
|
+
|
116
|
+
# ensure a wn string
|
117
|
+
if str =~ /[1-3]/
|
118
|
+
str = str.tr('123','nww')
|
119
|
+
end
|
120
|
+
|
121
|
+
unless str =~ /\A#{GUARD_PATTERN_LEFT_WN}(.*?)#{GUARD_PATTERN_RIGHT_WN}\z/
|
122
|
+
raise UnencodableCharactersError, "Start/stop pattern is not detected."
|
123
|
+
end
|
124
|
+
|
125
|
+
wn_pattern = $1
|
126
|
+
|
127
|
+
# Each pattern is 5 bars
|
128
|
+
unless wn_pattern.size % 5 == 0
|
129
|
+
raise UnencodableCharactersError, "Wrong number of bars."
|
130
|
+
end
|
131
|
+
|
132
|
+
decoded_string = ''
|
133
|
+
|
134
|
+
wn_pattern.scan(/.{5}/).each do |chunk|
|
135
|
+
|
136
|
+
found = false
|
137
|
+
|
138
|
+
PATTERNS.each do |char,hsh|
|
139
|
+
if chunk == hsh['wn']
|
140
|
+
decoded_string += char
|
141
|
+
found = true
|
142
|
+
break;
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
PostNet.new(decoded_string, options)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
# Options are :line_character, :space_character, :w_character,
|
156
|
+
# :n_character, :checksum_included, and :skip_checksum.
|
157
|
+
def initialize(value, options = {})
|
158
|
+
|
159
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
160
|
+
|
161
|
+
# Can we encode this value?
|
162
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value)
|
163
|
+
|
164
|
+
@value = value.to_s
|
165
|
+
|
166
|
+
if @options[:skip_checksum]
|
167
|
+
@encoded_string = value.to_s
|
168
|
+
@value = value.to_s
|
169
|
+
@check_digit = nil
|
170
|
+
else
|
171
|
+
# Need to guess whether we need a checksum. If there
|
172
|
+
# are 5, 9, or 11 digits, we need one. If there are 6,
|
173
|
+
# 10, or 12 digits, then it's included. Otherwise it's
|
174
|
+
# not a valid number.
|
175
|
+
|
176
|
+
@value = value.to_s
|
177
|
+
|
178
|
+
if @options[:checksum_included] || [6,10,12].include?(@value.size)
|
179
|
+
@options[:checksum_included] = true
|
180
|
+
@encoded_string = value.to_s
|
181
|
+
raise ChecksumError unless self.class.validate_check_digit_for(@encoded_string)
|
182
|
+
md = @encoded_string.match(/^(\d+?)(\d)$/)
|
183
|
+
@value, @check_digit = md[1], md[2].to_i
|
184
|
+
elsif [5,9,11].include?(@value.size)
|
185
|
+
@check_digit = self.class.generate_check_digit_for(@value)
|
186
|
+
@encoded_string = "#{@value}#{@check_digit}"
|
187
|
+
else
|
188
|
+
# should be redundant
|
189
|
+
raise UnencodableCharactersError
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns a string of "w" or "n" ("wide" and "narrow")
|
195
|
+
def wn
|
196
|
+
@wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
|
197
|
+
end
|
198
|
+
|
199
|
+
# returns a run-length-encoded string representation
|
200
|
+
def rle
|
201
|
+
@rle ||= self.class.wn_to_rle(self.wn, @options)
|
202
|
+
end
|
203
|
+
|
204
|
+
# returns 1s and 0s (for "black" and "white")
|
205
|
+
def bars
|
206
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
207
|
+
end
|
208
|
+
|
209
|
+
# returns the total unit width of the bar code
|
210
|
+
def width
|
211
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
# Creates the actual w/n pattern. Note that there is a narrow space
|
217
|
+
# between each character.
|
218
|
+
def wn_str
|
219
|
+
@wn_str ||= GUARD_PATTERN_LEFT_WN + @encoded_string.split('').collect { |c| PATTERNS[c]['wn'] }.join + GUARD_PATTERN_RIGHT_WN
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|