barcode1dtools 0.9.2.1 → 0.9.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,9 +15,10 @@ module Barcode1DTools
15
15
  # Barcode1DTools is a library for generating and decoding
16
16
  # 1-dimensional barcode patterns for various code types.
17
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.
18
+ # UPC-E, UPC Supplemental 2, UPC Supplemental 5,
19
+ # Interleaved 2 of 5 (I 2/5), and Code 3 of 9, but will be
20
+ # expanded to include most 1D symbologies in the near
21
+ # future.
21
22
  #
22
23
  #== Example
23
24
  # ean13 = Barcode1DTools::EAN13.new('0012676510226', :line_character => 'x', :space_character => ' ')
@@ -117,3 +118,4 @@ require 'barcode1dtools/upc_a'
117
118
  require 'barcode1dtools/upc_e'
118
119
  require 'barcode1dtools/upc_supplemental_2'
119
120
  require 'barcode1dtools/upc_supplemental_5'
121
+ require 'barcode1dtools/code3of9'
@@ -0,0 +1,285 @@
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::Code3of9 - Create and decode bar patterns for
11
+ # Code 3 of 9 (also known as Code 39, USD-3, Alpha39, Code 3/9,
12
+ # Type 39, or USS Code 39) barcodes. The value encoded is a
13
+ # string, and a checksum digit may be added. You can add the
14
+ # option :checksum_included => true when initializing to specify
15
+ # that you have already included a checksum, or :skip_checksum =>
16
+ # true to specify that no checksum should be added or checked. A
17
+ # ChecksumError will be raised if :checksum_included => true,
18
+ # :skip_checksum is false, and the last digit is invalid as a
19
+ # checksum. Note that the default is "skip_checksum".
20
+ #
21
+ # Code 3 of 9 can encode digits, uppercase letters, and the symbols
22
+ # dash "-", period ".", dollar sign "$", forward slash "/", plus
23
+ # sign "+", percent sign "%", as well as a space " ".
24
+ #
25
+ # val = "THIS IS A TEST"
26
+ # bc = Barcode1DTools::Code3of9.new(val)
27
+ # pattern = bc.bars
28
+ # rle_pattern = bc.rle
29
+ # wn_pattern = bc.wn
30
+ # width = bc.width
31
+ # # Note that the check digit is actually one of the characters.
32
+ # check_digit = Barcode1DTools::Code3of9.generate_check_digit_for(num)
33
+ #
34
+ # The object created is immutable.
35
+ #
36
+ # Barcode1DTools::Code3of9 creates the patterns that you need to
37
+ # display Code 3 of 9 barcodes. It can also decode a simple w/n
38
+ # string.
39
+ #
40
+ # Code 3 of 9 barcodes consist of lines and spaces that are either
41
+ # "wide" or "narrow", with "wide" lines or spaces being twice the
42
+ # width of narrow lines or spaces.
43
+ #
44
+ # There are three formats for the returned pattern:
45
+ #
46
+ # bars - 1s and 0s specifying black lines and white spaces. Actual
47
+ # characters can be changed from "1" and 0" with options
48
+ # :line_character and :space_character.
49
+ #
50
+ # rle - Run-length-encoded version of the pattern. The first
51
+ # number is always a black line, with subsequent digits
52
+ # alternating between spaces and lines. The digits specify
53
+ # the width of each line or space.
54
+ #
55
+ # wn - The native format for this barcode type. The string
56
+ # consists of a series of "w" and "n" characters. The first
57
+ # item is always a black line, with subsequent characters
58
+ # alternating between spaces and lines. A "wide" item
59
+ # is twice the width of a "narrow" item.
60
+ #
61
+ # The "width" method will tell you the total end-to-end width, in
62
+ # units, of the entire barcode.
63
+ #
64
+ #== Miscellaneous Information
65
+ #
66
+ # Code 3 of 9 can encode text and digits. There is also a way to do
67
+ # "full ascii" mode, but it's not recommended. Full ascii mode uses
68
+ # some of the characters as shift characters, e.g. "a" is encoded as
69
+ # "+A". There's no indication that full ascii mode is being used, so
70
+ # it has to be handled by the application. This has been fixed in
71
+ # Code 93, by designation of four special characters which are used
72
+ # only for shifting. However, if you need to use a full character
73
+ # set, Code 128 is probably a better choice.
74
+ #
75
+ #== Rendering
76
+ #
77
+ # Code 3 of 9 may be rendered however the programmer wishes. Since
78
+ # there is a simple mapping between number of characters and length of
79
+ # code, a variable length code should be allowed to grow and shrink to
80
+ # assure the bars are neither too large or too small. Code 3 of 9 is
81
+ # often implemented as a font.
82
+ #
83
+ # There is no standard for human-readable text associated with the
84
+ # code, and in fact some applications leave out the human-readable
85
+ # element altogether. The text is typically shown below the barcode
86
+ # where applicable.
87
+
88
+ class Code3of9 < Barcode1D
89
+
90
+ # Character sequence - 0-based offset in this string is character
91
+ # number
92
+ CHAR_SEQUENCE = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%'
93
+
94
+ # Patterns for making bar codes
95
+ PATTERNS = {
96
+ '0' => { 'position' => 0, 'wn' => 'nnnwwnwnn' },
97
+ '1' => { 'position' => 1, 'wn' => 'wnnwnnnnw' },
98
+ '2' => { 'position' => 2, 'wn' => 'nnwwnnnnw' },
99
+ '3' => { 'position' => 3, 'wn' => 'wnwwnnnnn' },
100
+ '4' => { 'position' => 4, 'wn' => 'nnnwwnnnw' },
101
+ '5' => { 'position' => 5, 'wn' => 'wnnwwnnnn' },
102
+ '6' => { 'position' => 6, 'wn' => 'nnwwwnnnn' },
103
+ '7' => { 'position' => 7, 'wn' => 'nnnwnnwnw' },
104
+ '8' => { 'position' => 8, 'wn' => 'wnnwnnwnn' },
105
+ '9' => { 'position' => 9, 'wn' => 'nnwwnnwnn' },
106
+ 'A' => { 'position' => 10, 'wn' => 'wnnnnwnnw' },
107
+ 'B' => { 'position' => 11, 'wn' => 'nnwnnwnnw' },
108
+ 'C' => { 'position' => 12, 'wn' => 'wnwnnwnnn' },
109
+ 'D' => { 'position' => 13, 'wn' => 'nnnnwwnnw' },
110
+ 'E' => { 'position' => 14, 'wn' => 'wnnnwwnnn' },
111
+ 'F' => { 'position' => 15, 'wn' => 'nnwnwwnnn' },
112
+ 'G' => { 'position' => 16, 'wn' => 'nnnnnwwnw' },
113
+ 'H' => { 'position' => 17, 'wn' => 'wnnnnwwnn' },
114
+ 'I' => { 'position' => 18, 'wn' => 'nnwnnwwnn' },
115
+ 'J' => { 'position' => 19, 'wn' => 'nnnnwwwnn' },
116
+ 'K' => { 'position' => 20, 'wn' => 'wnnnnnnww' },
117
+ 'L' => { 'position' => 21, 'wn' => 'nnwnnnnww' },
118
+ 'M' => { 'position' => 22, 'wn' => 'wnwnnnnwn' },
119
+ 'N' => { 'position' => 23, 'wn' => 'nnnnwnnww' },
120
+ 'O' => { 'position' => 24, 'wn' => 'wnnnwnnwn' },
121
+ 'P' => { 'position' => 25, 'wn' => 'nnwnwnnwn' },
122
+ 'Q' => { 'position' => 26, 'wn' => 'nnnnnnwww' },
123
+ 'R' => { 'position' => 27, 'wn' => 'wnnnnnwwn' },
124
+ 'S' => { 'position' => 28, 'wn' => 'nnwnnnwwn' },
125
+ 'T' => { 'position' => 29, 'wn' => 'nnnnwnwwn' },
126
+ 'U' => { 'position' => 30, 'wn' => 'wwnnnnnnw' },
127
+ 'V' => { 'position' => 31, 'wn' => 'nwwnnnnnw' },
128
+ 'W' => { 'position' => 32, 'wn' => 'wwwnnnnnn' },
129
+ 'X' => { 'position' => 33, 'wn' => 'nwnnwnnnw' },
130
+ 'Y' => { 'position' => 34, 'wn' => 'wwnnwnnnn' },
131
+ 'Z' => { 'position' => 35, 'wn' => 'nwwnwnnnn' },
132
+ '-' => { 'position' => 36, 'wn' => 'nwnnnnwnw' },
133
+ '.' => { 'position' => 37, 'wn' => 'wwnnnnwnn' },
134
+ ' ' => { 'position' => 38, 'wn' => 'nwwnnnwnn' },
135
+ '$' => { 'position' => 39, 'wn' => 'nwnwnwnnn' },
136
+ '/' => { 'position' => 40, 'wn' => 'nwnwnnnwn' },
137
+ '+' => { 'position' => 41, 'wn' => 'nwnnnwnwn' },
138
+ '%' => { 'position' => 42, 'wn' => 'nnnwnwnwn' }
139
+ }
140
+
141
+ SIDE_GUARD_PATTERN = 'nwnnwnwnn'
142
+
143
+ WN_RATIO = 2
144
+
145
+ DEFAULT_OPTIONS = {
146
+ :w_character => 'w',
147
+ :n_character => 'n',
148
+ :line_character => '1',
149
+ :space_character => '0',
150
+ :wn_ratio => WN_RATIO,
151
+ :skip_checksum => true
152
+ }
153
+
154
+ class << self
155
+ # returns true or false
156
+ def can_encode?(value)
157
+ value.to_s =~ /\A[0-9A-Z\-\. \$\/\+%]*\z/
158
+ end
159
+
160
+ # Generates check digit given a string to encode. It assumes there
161
+ # is no check digit on the "value". The check "digit" is
162
+ # actually a character, and the "position" value in PATTERNS can
163
+ # be used to find the numeric value.
164
+ def generate_check_digit_for(value)
165
+ raise UnencodableCharactersError unless self.can_encode?(value)
166
+ mult = 1
167
+ sum = value.to_s.split('').inject(0) { |a,c| a + PATTERNS[c]['position'] }
168
+ CHAR_SEQUENCE[sum % 43,1]
169
+ end
170
+
171
+ # validates the check digit given a string - assumes check digit
172
+ # is last digit of string.
173
+ def validate_check_digit_for(value)
174
+ raise UnencodableCharactersError unless self.can_encode?(value)
175
+ md = value.to_s.match(/^(.*)(.)$/)
176
+ self.generate_check_digit_for(md[1]) == md[2]
177
+ end
178
+
179
+ # Decode a string in wn format. This will return a Code3of9
180
+ # object.
181
+ def decode(str, options = {})
182
+ if str =~ /[^wn]/
183
+ raise UnencodableCharactersError, "Pattern must contain only \"w\" and \"n\"."
184
+ end
185
+
186
+ if str.reverse =~ /^#{SIDE_GUARD_PATTERN}n.*?n#{SIDE_GUARD_PATTERN}$/
187
+ str.reverse!
188
+ end
189
+
190
+ unless str =~ /^#{SIDE_GUARD_PATTERN}n(.*?)#{SIDE_GUARD_PATTERN}$/
191
+ raise UnencodableCharactersError, "Start/stop pattern is not detected."
192
+ end
193
+
194
+ wn_pattern = $1
195
+
196
+ unless wn_pattern.size % 10 == 0
197
+ raise UnencodableCharactersError, "Wrong number of bars."
198
+ end
199
+
200
+ decoded_string = ''
201
+
202
+ wn_pattern.scan(/(.{9})n/).each do |chunk|
203
+
204
+ chunk = chunk[0]
205
+
206
+ found = false
207
+
208
+ PATTERNS.each do |char,hsh|
209
+ if chunk == hsh['wn']
210
+ decoded_string += char
211
+ found = true
212
+ break;
213
+ end
214
+ end
215
+
216
+ raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
217
+
218
+ end
219
+
220
+ Code3of9.new(decoded_string, options)
221
+ end
222
+ end
223
+
224
+ # Options are :line_character, :space_character, :w_character,
225
+ # :n_character, :skip_checksum, and :checksum_included.
226
+ def initialize(value, options = {})
227
+
228
+ @options = DEFAULT_OPTIONS.merge(options)
229
+
230
+ # Can we encode this value?
231
+ raise UnencodableCharactersError unless self.class.can_encode?(value)
232
+
233
+ value = value.to_s
234
+
235
+ if @options[:skip_checksum] && !@options[:checksum_included]
236
+ @encoded_string = value
237
+ @value = value
238
+ @check_digit = nil
239
+ elsif @options[:checksum_included]
240
+ @options[:skip_checksum] = nil
241
+ raise ChecksumError unless self.class.validate_check_digit_for(value)
242
+ @encoded_string = value
243
+ md = value.match(/\A(.*?)(.)\z/)
244
+ @value, @check_digit = md[1], md[2]
245
+ else
246
+ # need to add a checksum
247
+ @value = value
248
+ @check_digit = self.class.generate_check_digit_for(@value)
249
+ @encoded_string = "#{@value}#{@check_digit}"
250
+ end
251
+ end
252
+
253
+ # Returns a string of "w" or "n" ("wide" and "narrow")
254
+ def wn
255
+ @wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
256
+ end
257
+
258
+ # returns a run-length-encoded string representation
259
+ def rle
260
+ @rle ||= self.class.wn_to_rle(self.wn, @options)
261
+ end
262
+
263
+ # returns 1s and 0s (for "black" and "white")
264
+ def bars
265
+ @bars ||= self.class.rle_to_bars(self.rle, @options)
266
+ end
267
+
268
+ # returns the total unit width of the bar code
269
+ def width
270
+ @width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
271
+ end
272
+
273
+ private
274
+
275
+ # Creates the actual w/n pattern. Note that there is a narrow space
276
+ # between each character.
277
+ def wn_str
278
+ @wn_str ||=
279
+ ([SIDE_GUARD_PATTERN] +
280
+ @encoded_string.split('').collect { |c| PATTERNS[c]['wn'] } +
281
+ [SIDE_GUARD_PATTERN]).join('n')
282
+ end
283
+
284
+ end
285
+ end
@@ -146,8 +146,7 @@ module Barcode1DTools
146
146
 
147
147
  decoded_string = ''
148
148
 
149
- numeric_pattern.scan(/(.{10})/).each do |chunk|
150
- chunk = chunk[0]
149
+ numeric_pattern.scan(/.{10}/).each do |chunk|
151
150
 
152
151
  num1 = chunk[0,1] + chunk[2,1] + chunk[4,1] + chunk[6,1] + chunk[8,1]
153
152
  num2 = chunk[1,1] + chunk[3,1] + chunk[5,1] + chunk[7,1] + chunk[9,1]
@@ -0,0 +1,80 @@
1
+ #--
2
+ # Copyright 2012 Michael Chaney Consulting Corporation
3
+ #
4
+ # Released under the terms of the MIT License or the GNU
5
+ # General Public License, v. 2
6
+ #++
7
+
8
+ require 'test/unit'
9
+ require 'barcode1dtools'
10
+
11
+ class Barcode1DToolsCode3of9Test < Test::Unit::TestCase
12
+ def setup
13
+ end
14
+
15
+ def teardown
16
+ end
17
+
18
+ # Creates a random number from 1 to 10 digits long
19
+ def random_x_character_string(x)
20
+ num_chars = Barcode1DTools::Code3of9::CHAR_SEQUENCE.size
21
+ (0..x-1).inject('') { |a,c| a + Barcode1DTools::Code3of9::CHAR_SEQUENCE[rand(num_chars),1] }
22
+ end
23
+
24
+ def test_checksum_generation
25
+ assert_equal 'I', Barcode1DTools::Code3of9.generate_check_digit_for('THIS IS A TEST')
26
+ end
27
+
28
+ def test_checksum_validation
29
+ assert Barcode1DTools::Code3of9.validate_check_digit_for('THIS IS A TESTI')
30
+ end
31
+
32
+ def test_attr_readers
33
+ c3of9 = Barcode1DTools::Code3of9.new('THIS IS A TEST', :skip_checksum => false)
34
+ assert_equal 'I', c3of9.check_digit
35
+ assert_equal 'THIS IS A TEST', c3of9.value
36
+ assert_equal 'THIS IS A TESTI', c3of9.encoded_string
37
+ end
38
+
39
+ def test_checksum_error
40
+ # proper checksum is I
41
+ assert_raise(Barcode1DTools::ChecksumError) { Barcode1DTools::Code3of9.new('THIS IS A TEST0', :checksum_included => true) }
42
+ end
43
+
44
+ def test_skip_checksum
45
+ c3of9 = Barcode1DTools::Code3of9.new('THIS IS A TEST', :skip_checksum => true)
46
+ assert_nil c3of9.check_digit
47
+ assert_equal 'THIS IS A TEST', c3of9.value
48
+ assert_equal 'THIS IS A TEST', c3of9.encoded_string
49
+ end
50
+
51
+ def test_bad_character_errors
52
+ # Characters that cannot be encoded
53
+ assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code3of9.new('thisisnotgood', :checksum_included => false) }
54
+ end
55
+
56
+ # Only need to test wn, as bars and rle are based on wn
57
+ def test_barcode_generation
58
+ c3of9 = Barcode1DTools::Code3of9.new('THIS IS A TEST')
59
+ assert_equal "nwnnwnwnnnnnnnwnwwnnwnnnnwwnnnnnwnnwwnnnnnwnnnwwnnnwwnnnwnnnnnwnnwwnnnnnwnnnwwnnnwwnnnwnnnwnnnnwnnwnnwwnnnwnnnnnnnwnwwnnwnnnwwnnnnnnwnnnwwnnnnnnwnwwnnnwnnwnwnn", c3of9.wn
60
+ end
61
+
62
+ def test_decode_error
63
+ assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code3of9.decode('x') }
64
+ assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code3of9.decode('x'*60) }
65
+ # proper start & stop, but crap in middle
66
+ assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code3of9.decode(Barcode1DTools::Code3of9::SIDE_GUARD_PATTERN + 'wwwwwww' + Barcode1DTools::Code3of9::SIDE_GUARD_PATTERN) }
67
+ # wrong start/stop
68
+ assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code3of9.decode('nwwnwnwnwnwnwnw') }
69
+ end
70
+
71
+ def test_decoding
72
+ random_c3of9_str=random_x_character_string(rand(10)+5)
73
+ c3of9 = Barcode1DTools::Code3of9.new(random_c3of9_str, :skip_checksum => true)
74
+ c3of92 = Barcode1DTools::Code3of9.decode(c3of9.wn)
75
+ assert_equal c3of9.value, c3of92.value
76
+ # Should also work in reverse
77
+ c3of94 = Barcode1DTools::Code3of9.decode(c3of9.wn.reverse)
78
+ assert_equal c3of9.value, c3of94.value
79
+ end
80
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barcode1dtools
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- - 1
11
- version: 0.9.2.1
9
+ - 3
10
+ - 0
11
+ version: 0.9.3.0
12
12
  platform: ruby
13
13
  authors:
14
14
  - Michael Chaney
@@ -20,8 +20,7 @@ date: 2012-08-05 00:00:00 -05:00
20
20
  default_executable:
21
21
  dependencies: []
22
22
 
23
- description: " Barcode1D is a small library for handling many kinds of\n 1-dimensional barcodes. Currently implemented are\n Interleaved 2 of 5, EAN-13, EAN-8, UPC-A, UPC-E, UPC\n Supplemental 2, and UPC Supplemental 5. Patterns are\n created in either a simple format of bars and spaces or\n as a run-length encoded string. This only generates\n and decodes the patterns; actual display or reading\n from a device must be implemented by the programmer.\n\
24
- \t More symbologies will be added as time permits.\n"
23
+ description: " Barcode1D is a small library for handling many kinds of\n 1-dimensional barcodes. Currently implemented are Code 3 of 9,\n Interleaved 2 of 5, EAN-13, EAN-8, UPC-A, UPC-E, UPC Supplemental 2,\n and UPC Supplemental 5. Patterns are created in either a simple\n format of bars and spaces or as a run-length encoded string. This\n only generates and decodes the patterns; actual display or reading\n from a device must be implemented by the programmer. More\n symbologies will be added as time permits.\n"
25
24
  email: mdchaney@michaelchaney.com
26
25
  executables: []
27
26
 
@@ -30,6 +29,7 @@ extensions: []
30
29
  extra_rdoc_files: []
31
30
 
32
31
  files:
32
+ - lib/barcode1dtools/code3of9.rb
33
33
  - lib/barcode1dtools/ean13.rb
34
34
  - lib/barcode1dtools/ean8.rb
35
35
  - lib/barcode1dtools/interleaved2of5.rb
@@ -40,6 +40,7 @@ files:
40
40
  - lib/barcode1dtools.rb
41
41
  - MIT-LICENSE
42
42
  - test/test_barcode1d.rb
43
+ - test/test_barcode1dcode3of9.rb
43
44
  - test/test_barcode1dean13.rb
44
45
  - test/test_barcode1dean8.rb
45
46
  - test/test_barcode1di2of5.rb
@@ -86,6 +87,7 @@ specification_version: 3
86
87
  summary: Pattern generators for 1D barcodes
87
88
  test_files:
88
89
  - test/test_barcode1d.rb
90
+ - test/test_barcode1dcode3of9.rb
89
91
  - test/test_barcode1dean13.rb
90
92
  - test/test_barcode1dean8.rb
91
93
  - test/test_barcode1di2of5.rb