barcode1dtools 0.9.2.1 → 0.9.3.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.
@@ -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