barcode1dtools 0.9.8.0 → 0.9.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/barcode1dtools.rb +2 -2
- data/lib/barcode1dtools/code128.rb +500 -0
- data/test/test_barcode1dcode128.rb +76 -0
- metadata +15 -13
data/lib/barcode1dtools.rb
CHANGED
@@ -18,8 +18,7 @@ module Barcode1DTools
|
|
18
18
|
# Supplemental 2, UPC Supplemental 5, Interleaved 2 of 5 (I 2/5),
|
19
19
|
# COOP 2 of 5, Matrix 2 of 5, Industrial 2 of 5, IATA 2 of 5,
|
20
20
|
# PostNet, Plessey, MSI (Modified Plessey), Code 3 of 9, Code 93,
|
21
|
-
# Code 11,
|
22
|
-
# symbologies in the near future.
|
21
|
+
# Code 11, Code 128, and Codabar.
|
23
22
|
#
|
24
23
|
#== Example
|
25
24
|
# ean13 = Barcode1DTools::EAN13.new('0012676510226', :line_character => 'x', :space_character => ' ')
|
@@ -130,3 +129,4 @@ require 'barcode1dtools/matrix2of5'
|
|
130
129
|
require 'barcode1dtools/postnet'
|
131
130
|
require 'barcode1dtools/plessey'
|
132
131
|
require 'barcode1dtools/msi'
|
132
|
+
require 'barcode1dtools/code128'
|
@@ -0,0 +1,500 @@
|
|
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::Code128 - Create and decode bar patterns for
|
11
|
+
# Code 128. Code 128 is the king of 1D bar codes and should be
|
12
|
+
# used for any alpha-numeric application. Code 128 can encode
|
13
|
+
# any character from 0 to 255, although it is most efficient
|
14
|
+
# when using only 0 to 95 or 32 to 127. It is also very
|
15
|
+
# efficient at encoding only digits, although Interleaved 2 of 5
|
16
|
+
# is also a good choice with potentially less overhead.
|
17
|
+
#
|
18
|
+
# Code 128 barcodes always include a checksum, and the checksum
|
19
|
+
# is calculated from the encoded value rather than the payload.
|
20
|
+
# Because of this, there are no options for including a check
|
21
|
+
# digit or validating one. It is always included.
|
22
|
+
#
|
23
|
+
# val = "29382-38"
|
24
|
+
# bc = Barcode1DTools::Code128.new(val)
|
25
|
+
# pattern = bc.bars
|
26
|
+
# rle_pattern = bc.rle
|
27
|
+
# width = bc.width
|
28
|
+
#
|
29
|
+
# The object created is immutable.
|
30
|
+
#
|
31
|
+
# Barcode1DTools::Code128 creates the patterns that you need to
|
32
|
+
# display Code 128 barcodes. It can also decode a simple rle or
|
33
|
+
# bar pattern string.
|
34
|
+
#
|
35
|
+
# Code128 characters consist of 3 bars and 3 spaces.
|
36
|
+
#
|
37
|
+
# There are two 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
|
+
# The "width" method will tell you the total end-to-end width, in
|
49
|
+
# units, of the entire barcode.
|
50
|
+
#
|
51
|
+
#== Rendering
|
52
|
+
#
|
53
|
+
# The quiet zone on each side should be at least the greater of 10
|
54
|
+
# unit widths or 6.4mm. Typically a textual rendition of the
|
55
|
+
# payload is shown underneath the bars.
|
56
|
+
|
57
|
+
class Code128 < Barcode1D
|
58
|
+
|
59
|
+
# Patterns for making bar codes
|
60
|
+
PATTERNS = [
|
61
|
+
'212222', '222122', '222221', '121223', '121322', '131222',
|
62
|
+
'122213', '122312', '132212', '221213', '221312', '231212',
|
63
|
+
'112232', '122132', '122231', '113222', '123122', '123221',
|
64
|
+
'223211', '221132', '221231', '213212', '223112', '312131',
|
65
|
+
'311222', '321122', '321221', '312212', '322112', '322211',
|
66
|
+
'212123', '212321', '232121', '111323', '131123', '131321',
|
67
|
+
'112313', '132113', '132311', '211313', '231113', '231311',
|
68
|
+
'112133', '112331', '132131', '113123', '113321', '133121',
|
69
|
+
'313121', '211331', '231131', '213113', '213311', '213131',
|
70
|
+
'311123', '311321', '331121', '312113', '312311', '332111',
|
71
|
+
'314111', '221411', '431111', '111224', '111422', '121124',
|
72
|
+
'121421', '141122', '141221', '112214', '112412', '122114',
|
73
|
+
'122411', '142112', '142211', '241211', '221114', '413111',
|
74
|
+
'241112', '134111', '111242', '121142', '121241', '114212',
|
75
|
+
'124112', '124211', '411212', '421112', '421211', '212141',
|
76
|
+
'214121', '412121', '111143', '111341', '131141', '114113',
|
77
|
+
'114311', '411113', '411311', '113141', '114131', '311141',
|
78
|
+
'411131', '211412', '211214', '211232',
|
79
|
+
'2331112'
|
80
|
+
]
|
81
|
+
|
82
|
+
# Quicker decoding
|
83
|
+
PATTERN_LOOKUP = (0..106).inject({}) { |a,c| a[PATTERNS[c]] = c; a }
|
84
|
+
|
85
|
+
# For ease. These can also be looked up in any
|
86
|
+
# ASCII_TO_CODE_x hashes symbolically, e.g.
|
87
|
+
# START_A == ASCII_TO_CODE_A[:start_a]
|
88
|
+
START_A = 103
|
89
|
+
START_B = 104
|
90
|
+
START_C = 105
|
91
|
+
SHIFT = 98
|
92
|
+
CODE_A = 101
|
93
|
+
CODE_B = 100
|
94
|
+
CODE_C = 99
|
95
|
+
STOP = 106
|
96
|
+
FNC_1 = 102
|
97
|
+
FNC_2 = 97
|
98
|
+
FNC_3 = 96
|
99
|
+
# Note that FNC_4 is 100 in set B and 101 in set A
|
100
|
+
|
101
|
+
GUARD_PATTERN_RIGHT_RLE = PATTERNS[STOP]
|
102
|
+
START_A_RLE = PATTERNS[START_A]
|
103
|
+
START_B_RLE = PATTERNS[START_B]
|
104
|
+
START_C_RLE = PATTERNS[START_C]
|
105
|
+
|
106
|
+
LOW_ASCII_LABELS = [
|
107
|
+
'NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL',
|
108
|
+
'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI', 'DLE',
|
109
|
+
'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB', 'CAN',
|
110
|
+
'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US'
|
111
|
+
]
|
112
|
+
|
113
|
+
# Code A encodes ASCII NUL (\x0) to _ (\x5f, dec. 95). Note
|
114
|
+
# that they are not sequential - it starts with space through
|
115
|
+
# underscore, then has nul to \x1f. Finally, it has FNC 1-4.
|
116
|
+
CODE_A_TO_ASCII = ((32..95).to_a + (0..31).to_a).collect { |c| [c].pack('C') } + [ :fnc_3, :fnc_2, :shift_b, :code_c, :code_b, :fnc_4, :fnc_1, :start_a, :start_b, :start_c, :stop ]
|
117
|
+
ASCII_TO_CODE_A = (0..(CODE_A_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_A_TO_ASCII[c]] = c; a }
|
118
|
+
CODE_A_TO_HIGH_ASCII = CODE_A_TO_ASCII.collect { |a| a.is_a?(Symbol) ? a : [a.unpack("C").first+128].pack("C") }
|
119
|
+
HIGH_ASCII_TO_CODE_A = (0..(CODE_A_TO_HIGH_ASCII.length-1)).inject({}) { |a,c| a[CODE_A_TO_HIGH_ASCII[c]] = c; a }
|
120
|
+
|
121
|
+
# Code B encodes ASCII space (\x20, dec. 32) to DEL (\x7f,
|
122
|
+
# dec. 127). This is identical to Code A for the first 63
|
123
|
+
# characters. It also includes FNC 1-4 with FNC 4 in a
|
124
|
+
# different position than in set A.
|
125
|
+
CODE_B_TO_ASCII = (32..127).collect { |c| [c].pack('C') } + [ :fnc_3, :fnc_2, :shift_a, :code_c, :fnc_4, :code_a, :fnc_1, :start_a, :start_b, :start_c, :stop ]
|
126
|
+
ASCII_TO_CODE_B = (0..(CODE_B_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_B_TO_ASCII[c]] = c; a }
|
127
|
+
CODE_B_TO_HIGH_ASCII = CODE_B_TO_ASCII.collect { |a| a.is_a?(Symbol) ? a : [a.unpack("C").first+128].pack("C") }
|
128
|
+
HIGH_ASCII_TO_CODE_B = (0..(CODE_B_TO_HIGH_ASCII.length-1)).inject({}) { |a,c| a[CODE_B_TO_HIGH_ASCII[c]] = c; a }
|
129
|
+
|
130
|
+
# Code C encodes digit pairs 00 to 99 as well as FNC 1.
|
131
|
+
CODE_C_TO_ASCII = ("00".."99").to_a + [ :code_b, :code_a, :fnc_1, :start_a, :start_b, :start_c, :stop ]
|
132
|
+
ASCII_TO_CODE_C = (0..(CODE_C_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_C_TO_ASCII[c]] = c; a }
|
133
|
+
|
134
|
+
DEFAULT_OPTIONS = {
|
135
|
+
:line_character => '1',
|
136
|
+
:space_character => '0'
|
137
|
+
}
|
138
|
+
|
139
|
+
class << self
|
140
|
+
# Code128 can encode anything
|
141
|
+
def can_encode?(value)
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
def generate_check_digit_for(value)
|
146
|
+
md = parse_code128(value)
|
147
|
+
start = md[1].unpack('C')
|
148
|
+
mult=0
|
149
|
+
[md[2].unpack('C*').inject(start.first) { |a,c| (mult+=1)*c+a } % 103].pack('C')
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate_check_digit_for(value)
|
153
|
+
payload, check_digit = split_payload_and_check_digit(value)
|
154
|
+
self.generate_check_digit_for(payload) == check_digit
|
155
|
+
end
|
156
|
+
|
157
|
+
def split_payload_and_check_digit(value)
|
158
|
+
md = value.to_s.match(/\A(.*)(.)\z/)
|
159
|
+
[md[1], md[2]]
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns match data - 1: start character 2: payload
|
163
|
+
# 3: check digit 4: stop character
|
164
|
+
def parse_code128(str)
|
165
|
+
str.match(/\A([\x67-\x69])([\x00-\x66]*?)(?:([\x00-\x66])(\x6a))?\z/)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Convert a code128 encoded string to an ASCII/Latin-1
|
169
|
+
# representation. The return value is an array if there
|
170
|
+
# are any FNC codes included. Use the option
|
171
|
+
# :no_latin1 => true to simply return FNC 4 instead of
|
172
|
+
# coding the following characters to the high Latin-1
|
173
|
+
# range. Use :raw_array => true if you wish to see an
|
174
|
+
# array of the actual characters in the code. It will
|
175
|
+
# turn any ASCII/Latin-1 characters to their standard
|
176
|
+
# representation, but it also includes all start, shift,
|
177
|
+
# code change, etc. characters. Useful for debugging.
|
178
|
+
def code128_to_latin1(str, options = {})
|
179
|
+
ret = []
|
180
|
+
in_high_latin1 = false
|
181
|
+
shift_codeset = nil
|
182
|
+
shift_latin1 = false
|
183
|
+
current_codeset = 'A'
|
184
|
+
current_lookup = CODE_A_TO_ASCII
|
185
|
+
md = parse_code128(str)
|
186
|
+
raise UndecodeableCharactersError unless md
|
187
|
+
start_item = CODE_A_TO_ASCII[md[1].unpack('C').first]
|
188
|
+
if start_item == :start_a
|
189
|
+
current_codeset = 'A'
|
190
|
+
current_lookup = CODE_A_TO_ASCII
|
191
|
+
elsif start_item == :start_b
|
192
|
+
current_codeset = 'B'
|
193
|
+
current_lookup = CODE_B_TO_ASCII
|
194
|
+
elsif start_item == :start_c
|
195
|
+
current_codeset = 'C'
|
196
|
+
current_lookup = CODE_C_TO_ASCII
|
197
|
+
end
|
198
|
+
ret.push(start_item) if options[:raw_array]
|
199
|
+
md[2].unpack("C*").each do |char|
|
200
|
+
if shift_codeset
|
201
|
+
this_item = shift_codeset[char]
|
202
|
+
shift_codeset = nil
|
203
|
+
else
|
204
|
+
this_item = current_lookup[char]
|
205
|
+
end
|
206
|
+
if this_item.is_a? Symbol
|
207
|
+
# Symbols might be change code (code_a, code_b, code_c),
|
208
|
+
# shift for a single item (shift_a, shift_b),
|
209
|
+
# or an fnc 1-4. If it's fnc_4, handle the high latin-1.
|
210
|
+
# Might also be the start code.
|
211
|
+
if this_item == :code_a
|
212
|
+
current_codeset = 'A'
|
213
|
+
current_lookup = CODE_A_TO_ASCII
|
214
|
+
elsif this_item == :code_b
|
215
|
+
current_codeset = 'B'
|
216
|
+
current_lookup = CODE_B_TO_ASCII
|
217
|
+
elsif this_item == :code_c
|
218
|
+
current_codeset = 'C'
|
219
|
+
current_lookup = CODE_C_TO_ASCII
|
220
|
+
elsif this_item == :shift_a
|
221
|
+
shift_codeset = CODE_A_TO_ASCII
|
222
|
+
elsif this_item == :shift_b
|
223
|
+
shift_codeset = CODE_B_TO_ASCII
|
224
|
+
elsif this_item == :fnc_4 && !options[:no_latin1]
|
225
|
+
if shift_latin1
|
226
|
+
in_high_latin1 = !in_high_latin1
|
227
|
+
shift_latin1 = false
|
228
|
+
else
|
229
|
+
shift_latin1 = true
|
230
|
+
end
|
231
|
+
elsif !options[:raw_array]
|
232
|
+
ret.push this_item
|
233
|
+
end
|
234
|
+
ret.push(this_item) if options[:raw_array]
|
235
|
+
elsif in_high_latin1 && ['A', 'B'].include?(current_codeset)
|
236
|
+
# Currently processing as latin-1. If we find the shift,
|
237
|
+
# handle as regular character.
|
238
|
+
if shift_latin1
|
239
|
+
ret.push this_item
|
240
|
+
shift_latin1 = false
|
241
|
+
else
|
242
|
+
ret.push [this_item.unpack('C').first+128].pack('C')
|
243
|
+
end
|
244
|
+
elsif shift_latin1
|
245
|
+
# One character as latin-1
|
246
|
+
ret.push [this_item.unpack('C').first+128].pack('C')
|
247
|
+
shift_latin1 = false
|
248
|
+
else
|
249
|
+
# regular character
|
250
|
+
ret.push this_item
|
251
|
+
end
|
252
|
+
end
|
253
|
+
unless options[:raw_array]
|
254
|
+
ret = ret.inject([]) { |a,c| (a.size==0 || a.last.is_a?(Symbol) || c.is_a?(Symbol)) ? a.push(c) : (a[a.size-1] += c); a }
|
255
|
+
end
|
256
|
+
# Make sure it's Latin-1 for Ruby 1.9+
|
257
|
+
if RUBY_VERSION >= "1.9"
|
258
|
+
ret = ret.collect { |c| c.force_encoding('ISO-8859-1') }
|
259
|
+
end
|
260
|
+
if options[:raw_array]
|
261
|
+
ret.push(md[2].unpack('C').first)
|
262
|
+
ret.push(:stop)
|
263
|
+
end
|
264
|
+
ret
|
265
|
+
end
|
266
|
+
|
267
|
+
# Pass an array or string for encoding. The result is
|
268
|
+
# a string that is a Code 128 representation of the input.
|
269
|
+
# We do optimize, but perhaps not perfectly. The
|
270
|
+
# optimization should cover 99% of cases very well,
|
271
|
+
# although I'm sure an edge case could be created that
|
272
|
+
# would be suboptimal.
|
273
|
+
def latin1_to_code128(str, options = {})
|
274
|
+
if str.is_a?(String)
|
275
|
+
str = [str]
|
276
|
+
elsif !str.is_a?(Array)
|
277
|
+
raise UnencodableCharactersError
|
278
|
+
end
|
279
|
+
arr = str.inject([]) { |a,c| c.is_a?(Symbol) ? a.push(c) : a.push(c.to_s.split('')) ; a}.flatten
|
280
|
+
# Now, create a set of maps to see how this will map to each
|
281
|
+
# code set.
|
282
|
+
map_a = arr.collect { |c| ASCII_TO_CODE_A[c] ? 'a' : HIGH_ASCII_TO_CODE_A[c] ? 'A' : '-' }
|
283
|
+
map_b = arr.collect { |c| ASCII_TO_CODE_B[c] ? 'b' : HIGH_ASCII_TO_CODE_B[c] ? 'B' : '-' }
|
284
|
+
last_is_digit=false
|
285
|
+
map_c = arr.collect do |c|
|
286
|
+
if c.is_a?(Symbol) && c == :fnc_1
|
287
|
+
if last_is_digit
|
288
|
+
last_is_digit = false
|
289
|
+
['-','-']
|
290
|
+
else
|
291
|
+
'c'
|
292
|
+
end
|
293
|
+
elsif c.is_a?(String) && c >= '0' && c <= '9'
|
294
|
+
if last_is_digit
|
295
|
+
last_is_digit = false
|
296
|
+
['c','C']
|
297
|
+
else
|
298
|
+
last_is_digit = true
|
299
|
+
nil
|
300
|
+
end
|
301
|
+
elsif last_is_digit
|
302
|
+
last_is_digit = false
|
303
|
+
['-','-']
|
304
|
+
else
|
305
|
+
'-'
|
306
|
+
end
|
307
|
+
end.flatten.compact
|
308
|
+
map_c.push('-') if last_is_digit
|
309
|
+
# Let's do it
|
310
|
+
map_a = map_a.join + '-'
|
311
|
+
map_b = map_b.join + '-'
|
312
|
+
map_c = map_c.join
|
313
|
+
codepoints = ''
|
314
|
+
# Strategy - create an a/b map first. We'll do this based on
|
315
|
+
# the least switching required which can be determined via a
|
316
|
+
# regexp ("aaa--aa" has two switches, for instance). After
|
317
|
+
# that is created, we can then go in and fill in runs from
|
318
|
+
# C - runs of 6 or more in the middle or 4 or more at either
|
319
|
+
# end. "444a" would just be encoded in set B, for instance,
|
320
|
+
# but "4444a" would be encoded in C then B.
|
321
|
+
# In the real world, switching between A and B is rare so
|
322
|
+
# we're not trying too hard to optimize it here.
|
323
|
+
in_codeset = nil
|
324
|
+
x = 0
|
325
|
+
while x < map_a.length - 1 do
|
326
|
+
map_a_len = map_a.index('-',x).to_i - x
|
327
|
+
map_b_len = map_b.index('-',x).to_i - x
|
328
|
+
if map_a_len >= map_b_len
|
329
|
+
codepoints += map_a[x,map_a_len]
|
330
|
+
x += map_a_len
|
331
|
+
else
|
332
|
+
codepoints += map_b[x,map_b_len]
|
333
|
+
x += map_b_len
|
334
|
+
end
|
335
|
+
end
|
336
|
+
# Now, fix up runs of C. Look for runs of 4+ at the ends
|
337
|
+
# and 6+ in the middle. The runs must have cC in them, as
|
338
|
+
# there's no gains from changing FNC 1 to set C.
|
339
|
+
runs = map_c.split(/(c[cC]+Cc*)/)
|
340
|
+
offset = 0
|
341
|
+
0.upto(runs.length-1) do |x|
|
342
|
+
if x==0 || x==runs.length-1
|
343
|
+
# only needs to be 4
|
344
|
+
if runs[x] =~ /c[cC]+C/
|
345
|
+
codepoints[offset,runs[x].length] = runs[x]
|
346
|
+
end
|
347
|
+
offset += runs[x].length
|
348
|
+
else
|
349
|
+
# must be 6+
|
350
|
+
if runs[x] =~ /c[cC]{3,}C/
|
351
|
+
codepoints[offset,runs[x].length] = runs[x]
|
352
|
+
end
|
353
|
+
offset += runs[x].length
|
354
|
+
end
|
355
|
+
end
|
356
|
+
#{ :map_a => map_a, :map_b => map_b, :map_c => map_c, :codepoints => codepoints }
|
357
|
+
# Now, create the string
|
358
|
+
ret = []
|
359
|
+
current_set = codepoints[0,1].downcase
|
360
|
+
ret.push(current_set == 'a' ? START_A : current_set == 'b' ? START_B : START_C)
|
361
|
+
0.upto(codepoints.length-1) do |x|
|
362
|
+
if codepoints[x,1].downcase != current_set
|
363
|
+
current_set = codepoints[x,1].downcase
|
364
|
+
ret.push(current_set == 'a' ? CODE_A : current_set == 'b' ? CODE_B : CODE_C)
|
365
|
+
end
|
366
|
+
if current_set == 'c' && codepoints[x,1] == 'c'
|
367
|
+
# ignore capital Cs
|
368
|
+
if arr[x] == :fnc_1
|
369
|
+
ret.push(FNC_1)
|
370
|
+
else
|
371
|
+
ret.push((arr[x]+arr[x+1]).to_i)
|
372
|
+
end
|
373
|
+
elsif ['A','B'].include?(codepoints[x,1])
|
374
|
+
# Find FNC_4 and push it (101 in A and 100 in B)
|
375
|
+
# now push the letter looked up in CODE_x_TO_HIGH_ASCII
|
376
|
+
if codepoints[x,1] == 'A'
|
377
|
+
ret.push(HIGH_ASCII_TO_CODE_A[:fnc_4])
|
378
|
+
ret.push(HIGH_ASCII_TO_CODE_A[arr[x]])
|
379
|
+
else
|
380
|
+
ret.push(HIGH_ASCII_TO_CODE_B[:fnc_4])
|
381
|
+
ret.push(HIGH_ASCII_TO_CODE_B[arr[x]])
|
382
|
+
end
|
383
|
+
elsif ['a','b'].include?(codepoints[x,1])
|
384
|
+
# find the letter in CODE_x_TO_ASCII and push it
|
385
|
+
if codepoints[x,1] == 'a'
|
386
|
+
ret.push(ASCII_TO_CODE_A[arr[x]])
|
387
|
+
else
|
388
|
+
ret.push(ASCII_TO_CODE_B[arr[x]])
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
check_digit = generate_check_digit_for(ret.pack('C*'))
|
393
|
+
ret.push(check_digit.unpack('C').first)
|
394
|
+
ret.push(ASCII_TO_CODE_A[:stop])
|
395
|
+
ret.pack('C*')
|
396
|
+
end
|
397
|
+
|
398
|
+
# Decode a string in rle format. This will return a Code128
|
399
|
+
# object.
|
400
|
+
def decode(str, options = {})
|
401
|
+
if str =~ /[^1-4]/ && str =~ /[^wn]/
|
402
|
+
raise UnencodableCharactersError, "Pattern must be rle or bar pattern"
|
403
|
+
end
|
404
|
+
|
405
|
+
# ensure an rle string
|
406
|
+
if str !~ /\A[1-4]+\z/
|
407
|
+
str = bars_to_rle(str)
|
408
|
+
end
|
409
|
+
|
410
|
+
if str.reverse =~ /\A(#{START_A_RLE}|#{START_B_RLE}|#{START_C_RLE})(.*?)(#{GUARD_PATTERN_RIGHT_RLE})\z/
|
411
|
+
str.reverse!
|
412
|
+
end
|
413
|
+
|
414
|
+
unless str =~ /\A(#{START_A_RLE}|#{START_B_RLE}|#{START_C_RLE})(.*?)(#{GUARD_PATTERN_RIGHT_RLE})\z/
|
415
|
+
raise UnencodableCharactersError, "Start/stop pattern is not detected."
|
416
|
+
end
|
417
|
+
|
418
|
+
# Each pattern is 3 bars and 3 spaces, with an extra bar
|
419
|
+
# at the end.
|
420
|
+
unless (str.size - 1) % 6 == 0
|
421
|
+
raise UnencodableCharactersError, "Wrong number of bars."
|
422
|
+
end
|
423
|
+
|
424
|
+
points = []
|
425
|
+
|
426
|
+
str.scan(/.{6}/).each do |chunk|
|
427
|
+
|
428
|
+
found = false
|
429
|
+
|
430
|
+
char = PATTERN_LOOKUP[chunk]
|
431
|
+
if char
|
432
|
+
points.push(char)
|
433
|
+
elsif chunk == '233111'
|
434
|
+
# stop
|
435
|
+
points.push(STOP)
|
436
|
+
else
|
437
|
+
raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
decoded_string = code128_to_latin1(points.pack('C*'))
|
443
|
+
|
444
|
+
Code128.new(decoded_string, options)
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
|
449
|
+
# Options are :line_character, :space_character, and
|
450
|
+
# :raw_value.
|
451
|
+
def initialize(value, options = {})
|
452
|
+
|
453
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
454
|
+
|
455
|
+
if options[:raw_value]
|
456
|
+
@encoded_string = value
|
457
|
+
@value = self.class.code128_to_latin1(value, options)
|
458
|
+
else
|
459
|
+
@value = value.to_s
|
460
|
+
# In ruby 1.9, change to Latin-1 if it's in another encoding.
|
461
|
+
# Really, the caller needs to handle this.
|
462
|
+
if RUBY_VERSION >= "1.9" && !['US-ASCII','ISO-8859-1'].include?(value.encoding)
|
463
|
+
value = value.encode('ISO-8859-1')
|
464
|
+
end
|
465
|
+
raise UnencodableCharactersError unless self.class.can_encode?(value)
|
466
|
+
@encoded_string = self.class.latin1_to_code128(@value, options)
|
467
|
+
end
|
468
|
+
|
469
|
+
md = self.class.parse_code128(@encoded_string)
|
470
|
+
@check_digit = md[3]
|
471
|
+
end
|
472
|
+
|
473
|
+
# variable bar width, no w/n string
|
474
|
+
def wn
|
475
|
+
raise NotImplementedError
|
476
|
+
end
|
477
|
+
|
478
|
+
# returns a run-length-encoded string representation
|
479
|
+
def rle
|
480
|
+
@rle ||= gen_rle(@encoded_string, @options)
|
481
|
+
end
|
482
|
+
|
483
|
+
# returns 1s and 0s (for "black" and "white")
|
484
|
+
def bars
|
485
|
+
@bars ||= self.class.rle_to_bars(self.rle, @options)
|
486
|
+
end
|
487
|
+
|
488
|
+
# returns the total unit width of the bar code
|
489
|
+
def width
|
490
|
+
@width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
|
491
|
+
end
|
492
|
+
|
493
|
+
private
|
494
|
+
|
495
|
+
def gen_rle(encoded_string, options)
|
496
|
+
@rle_str ||= @encoded_string.split('').collect { |c| PATTERNS[c.unpack('C').first] }.join
|
497
|
+
end
|
498
|
+
|
499
|
+
end
|
500
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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 Barcode1DToolsCode128Test < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
end
|
17
|
+
|
18
|
+
def random_x_character_full_ascii_string(x)
|
19
|
+
(1..x).collect { rand(128) }.pack('C*')
|
20
|
+
end
|
21
|
+
|
22
|
+
def random_x_character_latin1_string(x)
|
23
|
+
str=(1..x).collect { rand(256) }.pack('C*')
|
24
|
+
if RUBY_VERSION >= "1.9"
|
25
|
+
str.force_encoding('ISO-8859-1')
|
26
|
+
else
|
27
|
+
str
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def random_crazy_code128_value(x)
|
32
|
+
funcs = [ :fnc_1, :fnc_2, :fnc_3, :fnc_4 ]
|
33
|
+
(1..x).collect { |y| y.odd? ? funcs[rand(funcs.length)] : random_x_character_full_ascii_string(rand(5)+5) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Test encode/decode
|
37
|
+
def test_full_ascii
|
38
|
+
random_string = random_x_character_full_ascii_string(rand(20) + 10)
|
39
|
+
encoded1 = Barcode1DTools::Code128.latin1_to_code128(random_string)
|
40
|
+
assert_equal [random_string], Barcode1DTools::Code128.code128_to_latin1(encoded1)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_latin1
|
44
|
+
random_string = random_x_character_latin1_string(rand(20) + 10)
|
45
|
+
encoded1 = Barcode1DTools::Code128.latin1_to_code128(random_string)
|
46
|
+
assert_equal [random_string], Barcode1DTools::Code128.code128_to_latin1(encoded1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_crazy_code128
|
50
|
+
random_string = random_crazy_code128_value(rand(5) + 10)
|
51
|
+
encoded1 = Barcode1DTools::Code128.latin1_to_code128(random_string, :no_latin1 => true)
|
52
|
+
assert_equal random_string, Barcode1DTools::Code128.code128_to_latin1(encoded1, :no_latin1 => true)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_attr_readers
|
56
|
+
c128 = Barcode1DTools::Code128.new('Hello!')
|
57
|
+
assert_equal 'Hello!', c128.value
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_decode_error
|
61
|
+
assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code128.decode('x') }
|
62
|
+
assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code128.decode('x'*60) }
|
63
|
+
# wrong start/stop
|
64
|
+
assert_raise(Barcode1DTools::UnencodableCharactersError) { Barcode1DTools::Code128.decode('22222222222222222') }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_decoding
|
68
|
+
random_c128_str=random_x_character_latin1_string(rand(10)+5)
|
69
|
+
c128 = Barcode1DTools::Code128.new(random_c128_str)
|
70
|
+
c1282 = Barcode1DTools::Code128.decode(c128.rle)
|
71
|
+
assert_equal c128.value, c1282.value
|
72
|
+
# Should also work in reverse
|
73
|
+
c1284 = Barcode1DTools::Code128.decode(c128.rle.reverse)
|
74
|
+
assert_equal c128.value, c1284.value
|
75
|
+
end
|
76
|
+
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:
|
4
|
+
hash: 35
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
9
|
+
- 9
|
10
10
|
- 0
|
11
|
-
version: 0.9.
|
11
|
+
version: 0.9.9.0
|
12
12
|
platform: ruby
|
13
13
|
authors:
|
14
14
|
- Michael Chaney
|
@@ -16,20 +16,19 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-09-
|
19
|
+
date: 2012-09-22 00:00:00 -05:00
|
20
20
|
default_executable:
|
21
21
|
dependencies: []
|
22
22
|
|
23
23
|
description: "\t Barcode1D is a small library for handling many kinds of\n\
|
24
|
-
\t 1-dimensional barcodes. Currently implemented are Code
|
25
|
-
\t 93, Code 11, Codabar, Interleaved 2 of 5, COOP 2 of 5
|
26
|
-
\t 5, Industrial 2 of 5, IATA 2 of 5, PostNet, Plessey, MSI
|
27
|
-
\t Plessey), EAN-13, EAN-8, UPC-A, UPC-E, UPC Supplemental 2
|
28
|
-
\t Supplemental 5. Patterns are created in either a simple
|
29
|
-
\t bars and spaces or as a run-length encoded string. This
|
30
|
-
\t generates and decodes the patterns; actual display or reading
|
31
|
-
\t device must be implemented by the programmer
|
32
|
-
\t be added as time permits.\n"
|
24
|
+
\t 1-dimensional barcodes. Currently implemented are Code 128, Code 3\n\
|
25
|
+
\t of 9, Code 93, Code 11, Codabar, Interleaved 2 of 5, COOP 2 of 5,\n\
|
26
|
+
\t Matrix 2 of 5, Industrial 2 of 5, IATA 2 of 5, PostNet, Plessey, MSI\n\
|
27
|
+
\t (Modified Plessey), EAN-13, EAN-8, UPC-A, UPC-E, UPC Supplemental 2,\n\
|
28
|
+
\t and UPC Supplemental 5. Patterns are created in either a simple\n\
|
29
|
+
\t format of bars and spaces or as a run-length encoded string. This\n\
|
30
|
+
\t only generates and decodes the patterns; actual display or reading\n\
|
31
|
+
\t from a device must be implemented by the programmer.\n"
|
33
32
|
email: mdchaney@michaelchaney.com
|
34
33
|
executables: []
|
35
34
|
|
@@ -40,6 +39,7 @@ extra_rdoc_files: []
|
|
40
39
|
files:
|
41
40
|
- lib/barcode1dtools/codabar.rb
|
42
41
|
- lib/barcode1dtools/code11.rb
|
42
|
+
- lib/barcode1dtools/code128.rb
|
43
43
|
- lib/barcode1dtools/code3of9.rb
|
44
44
|
- lib/barcode1dtools/code93.rb
|
45
45
|
- lib/barcode1dtools/coop2of5.rb
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- test/test_barcode1d.rb
|
62
62
|
- test/test_barcode1dcodabar.rb
|
63
63
|
- test/test_barcode1dcode11.rb
|
64
|
+
- test/test_barcode1dcode128.rb
|
64
65
|
- test/test_barcode1dcode3of9.rb
|
65
66
|
- test/test_barcode1dcode93.rb
|
66
67
|
- test/test_barcode1dcoop2of5.rb
|
@@ -118,6 +119,7 @@ test_files:
|
|
118
119
|
- test/test_barcode1d.rb
|
119
120
|
- test/test_barcode1dcodabar.rb
|
120
121
|
- test/test_barcode1dcode11.rb
|
122
|
+
- test/test_barcode1dcode128.rb
|
121
123
|
- test/test_barcode1dcode3of9.rb
|
122
124
|
- test/test_barcode1dcode93.rb
|
123
125
|
- test/test_barcode1dcoop2of5.rb
|