barcode1dtools 0.9.8.0 → 0.9.9.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 +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
|