barcode1dtools 0.9.6.0 → 0.9.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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