Class: Barcode1DTools::Code128

Inherits:
Barcode1D show all
Defined in:
lib/barcode1dtools/code128.rb

Overview

Barcode1DTools::Code128 - Create and decode bar patterns for Code 128. Code 128 is the king of 1D bar codes and should be used for any alpha-numeric application. Code 128 can encode any character from 0 to 255, although it is most efficient when using only 0 to 95 or 32 to 127. It is also very efficient at encoding only digits, although Interleaved 2 of 5 is also a good choice with potentially less overhead.

Code 128 barcodes always include a checksum, and the checksum is calculated from the encoded value rather than the payload. Because of this, there are no options for including a check digit or validating one. It is always included.

val = “29382-38” bc = Barcode1DTools::Code128.new(val) pattern = bc.bars rle_pattern = bc.rle width = bc.width

The object created is immutable.

Barcode1DTools::Code128 creates the patterns that you need to display Code 128 barcodes. It can also decode a simple rle or bar pattern string.

Code128 characters consist of 3 bars and 3 spaces.

There are two formats for the returned pattern:

bars - 1s and 0s specifying black lines and white spaces.  Actual
       characters can be changed from "1" and 0" with options
       :line_character and :space_character.

rle -  Run-length-encoded version of the pattern.  The first
       number is always a black line, with subsequent digits
       alternating between spaces and lines.  The digits specify
       the width of each line or space.

The “width” method will tell you the total end-to-end width, in units, of the entire barcode.

Rendering

The quiet zone on each side should be at least the greater of 10 unit widths or 6.4mm. Typically a textual rendition of the payload is shown underneath the bars.

Constant Summary collapse

PATTERNS =

Patterns for making bar codes

[
  '212222', '222122', '222221', '121223', '121322', '131222',
  '122213', '122312', '132212', '221213', '221312', '231212',
  '112232', '122132', '122231', '113222', '123122', '123221',
  '223211', '221132', '221231', '213212', '223112', '312131',
  '311222', '321122', '321221', '312212', '322112', '322211',
  '212123', '212321', '232121', '111323', '131123', '131321',
  '112313', '132113', '132311', '211313', '231113', '231311',
  '112133', '112331', '132131', '113123', '113321', '133121',
  '313121', '211331', '231131', '213113', '213311', '213131',
  '311123', '311321', '331121', '312113', '312311', '332111',
  '314111', '221411', '431111', '111224', '111422', '121124',
  '121421', '141122', '141221', '112214', '112412', '122114',
  '122411', '142112', '142211', '241211', '221114', '413111',
  '241112', '134111', '111242', '121142', '121241', '114212',
  '124112', '124211', '411212', '421112', '421211', '212141',
  '214121', '412121', '111143', '111341', '131141', '114113',
  '114311', '411113', '411311', '113141', '114131', '311141',
  '411131', '211412', '211214', '211232',
  '2331112'
]
PATTERN_LOOKUP =

Quicker decoding

(0..106).inject({}) { |a,c| a[PATTERNS[c]] = c; a }
START_A =

For ease. These can also be looked up in any ASCII_TO_CODE_x hashes symbolically, e.g. START_A == ASCII_TO_CODE_A

103
START_B =
104
START_C =
105
SHIFT =
98
CODE_A =
101
CODE_B =
100
CODE_C =
99
STOP =
106
FNC_1 =
102
FNC_2 =
97
FNC_3 =
96
GUARD_PATTERN_RIGHT_RLE =

Note that FNC_4 is 100 in set B and 101 in set A

START_A_RLE =
START_B_RLE =
START_C_RLE =
LOW_ASCII_LABELS =
[
  'NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL',
  'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI', 'DLE',
  'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB', 'CAN',
  'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US'
]
CODE_A_TO_ASCII =

Code A encodes ASCII NUL (x0) to _ (x5f, dec. 95). Note that they are not sequential - it starts with space through underscore, then has nul to x1f. Finally, it has FNC 1-4.

((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 ]
ASCII_TO_CODE_A =
(0..(CODE_A_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_A_TO_ASCII[c]] = c; a }
CODE_A_TO_HIGH_ASCII =
CODE_A_TO_ASCII.collect { |a| a.is_a?(Symbol) ? a : [a.unpack("C").first+128].pack("C") }.collect { |c| RUBY_VERSION < "1.9" || c.is_a?(Symbol) ? c : c.force_encoding('ISO-8859-1') }
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 }
CODE_B_TO_ASCII =

Code B encodes ASCII space (x20, dec. 32) to DEL (x7f, dec. 127). This is identical to Code A for the first 63 characters. It also includes FNC 1-4 with FNC 4 in a different position than in set A.

(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 ]
ASCII_TO_CODE_B =
(0..(CODE_B_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_B_TO_ASCII[c]] = c; a }
CODE_B_TO_HIGH_ASCII =
CODE_B_TO_ASCII.collect { |a| a.is_a?(Symbol) ? a : [a.unpack("C").first+128].pack("C") }.collect { |c| RUBY_VERSION < "1.9" || c.is_a?(Symbol) ? c : c.force_encoding('ISO-8859-1') }
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 }
CODE_C_TO_ASCII =

Code C encodes digit pairs 00 to 99 as well as FNC 1.

("00".."99").to_a + [ :code_b, :code_a, :fnc_1, :start_a, :start_b, :start_c, :stop ]
ASCII_TO_CODE_C =
(0..(CODE_C_TO_ASCII.length-1)).inject({}) { |a,c| a[CODE_C_TO_ASCII[c]] = c; a }
DEFAULT_OPTIONS =
{
  :line_character => '1',
  :space_character => '0'
}

Instance Attribute Summary

Attributes inherited from Barcode1D

#check_digit, #encoded_string, #options, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Barcode1D

bar_pair, bars_to_rle, rle_to_bars, rle_to_wn, wn_pair, wn_to_rle

Constructor Details

#initialize(value, options = {}) ⇒ Code128

Options are :line_character, :space_character, and :raw_value.



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/barcode1dtools/code128.rb', line 454

def initialize(value, options = {})

  @options = DEFAULT_OPTIONS.merge(options)

  if options[:raw_value]
    @encoded_string = value
    @value = self.class.code128_to_latin1(value, options)
  else
    if value.is_a?(Array)
      @value = value
    else
      @value = [value.to_s]
    end
    # In ruby 1.9, change to Latin-1 if it's in another encoding.
    # Really, the caller needs to handle this.
    if RUBY_VERSION >= "1.9"
      @value = @value.collect do |item|
        if item.is_a?(Symbol)
          item
        else
          item = item.to_s
          if ['US-ASCII','ISO-8859-1'].include?(item.encoding)
            item
          else
            item.encode('ISO-8859-1')
          end
        end
      end
    end
    raise UnencodableCharactersError unless self.class.can_encode?(value)
    @encoded_string = self.class.latin1_to_code128(@value, options)
  end

  md = self.class.parse_code128(@encoded_string)
  @check_digit = md[3]
end

Class Method Details

.can_encode?(value) ⇒ Boolean

Code128 can encode anything

Returns:

  • (Boolean)


141
142
143
# File 'lib/barcode1dtools/code128.rb', line 141

def can_encode?(value)
  true
end

.code128_to_latin1(str, options = {}) ⇒ Object

Convert a code128 encoded string to an ASCII/Latin-1 representation. The return value is an array if there are any FNC codes included. Use the option :no_latin1 => true to simply return FNC 4 instead of coding the following characters to the high Latin-1 range. Use :raw_array => true if you wish to see an array of the actual characters in the code. It will turn any ASCII/Latin-1 characters to their standard representation, but it also includes all start, shift, code change, etc. characters. Useful for debugging.

Raises:

  • (UndecodeableCharactersError)


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/barcode1dtools/code128.rb', line 178

def code128_to_latin1(str, options = {})
  ret = []
  in_high_latin1 = false
  shift_codeset = nil
  shift_latin1 = false
  current_codeset = 'A'
  current_lookup = CODE_A_TO_ASCII
  md = parse_code128(str)
  raise UndecodeableCharactersError unless md
  start_item = CODE_A_TO_ASCII[md[1].unpack('C').first]
  if start_item == :start_a
    current_codeset = 'A'
    current_lookup = CODE_A_TO_ASCII
  elsif start_item == :start_b
    current_codeset = 'B'
    current_lookup = CODE_B_TO_ASCII
  elsif start_item == :start_c
    current_codeset = 'C'
    current_lookup = CODE_C_TO_ASCII
  end
  ret.push(start_item) if options[:raw_array]
  md[2].unpack("C*").each do |char|
    if shift_codeset
      this_item = shift_codeset[char]
      shift_codeset = nil
    else
      this_item = current_lookup[char]
    end
    if this_item.is_a? Symbol
      # Symbols might be change code (code_a, code_b, code_c),
      # shift for a single item (shift_a, shift_b),
      # or an fnc 1-4.  If it's fnc_4, handle the high latin-1.
      # Might also be the start code.
      if this_item == :code_a
        current_codeset = 'A'
        current_lookup = CODE_A_TO_ASCII
      elsif this_item == :code_b
        current_codeset = 'B'
        current_lookup = CODE_B_TO_ASCII
      elsif this_item == :code_c
        current_codeset = 'C'
        current_lookup = CODE_C_TO_ASCII
      elsif this_item == :shift_a
        shift_codeset = CODE_A_TO_ASCII
      elsif this_item == :shift_b
        shift_codeset = CODE_B_TO_ASCII
      elsif this_item == :fnc_4 && !options[:no_latin1]
        if shift_latin1
          in_high_latin1 = !in_high_latin1
          shift_latin1 = false
        else
          shift_latin1 = true
        end
      elsif !options[:raw_array]
        ret.push this_item
      end
      ret.push(this_item) if options[:raw_array]
    elsif in_high_latin1 && ['A', 'B'].include?(current_codeset)
      # Currently processing as latin-1.  If we find the shift,
      # handle as regular character.
      if shift_latin1
        ret.push this_item
        shift_latin1 = false
      else
        ret.push [this_item.unpack('C').first+128].pack('C')
      end
    elsif shift_latin1
      # One character as latin-1
      ret.push [this_item.unpack('C').first+128].pack('C')
      shift_latin1 = false
    else
      # regular character
      ret.push this_item
    end
  end
  unless options[:raw_array]
    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 }
  end
  # Make sure it's Latin-1 for Ruby 1.9+
  if RUBY_VERSION >= "1.9"
    ret = ret.collect { |c| c.is_a?(Symbol) ? c : c.force_encoding('ISO-8859-1') }
  end
  if options[:raw_array]
    ret.push(md[2].unpack('C').first)
    ret.push(:stop)
  end
  ret
end

.decode(str, options = {}) ⇒ Object

Decode a string in rle format. This will return a Code128 object.



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/barcode1dtools/code128.rb', line 403

def decode(str, options = {})
  if str =~ /[^1-4]/ && str =~ /[^wn]/
    raise UnencodableCharactersError, "Pattern must be rle or bar pattern"
  end

  # ensure an rle string
  if str !~ /\A[1-4]+\z/
    str = bars_to_rle(str)
  end

  if str.reverse =~ /\A(#{START_A_RLE}|#{START_B_RLE}|#{START_C_RLE})(.*?)(#{GUARD_PATTERN_RIGHT_RLE})\z/
    str.reverse!
  end

  unless str =~ /\A(#{START_A_RLE}|#{START_B_RLE}|#{START_C_RLE})(.*?)(#{GUARD_PATTERN_RIGHT_RLE})\z/
    raise UnencodableCharactersError, "Start/stop pattern is not detected."
  end

  # Each pattern is 3 bars and 3 spaces, with an extra bar
  # at the end.
  unless (str.size - 1) % 6 == 0
    raise UnencodableCharactersError, "Wrong number of bars."
  end

  points = []

  str.scan(/.{6}/).each do |chunk|

    found = false

    char = PATTERN_LOOKUP[chunk]
    if char
      points.push(char)
    elsif chunk == '233111'
      # stop
      points.push(STOP)
    else
      raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found
    end

  end

  decoded_string = code128_to_latin1(points.pack('C*'), options)

  Code128.new(decoded_string, options)
end

.generate_check_digit_for(value) ⇒ Object



145
146
147
148
149
150
# File 'lib/barcode1dtools/code128.rb', line 145

def generate_check_digit_for(value)
  md = parse_code128(value)
  start = md[1].unpack('C')
  mult=0
  [md[2].unpack('C*').inject(start.first) { |a,c| (mult+=1)*c+a } % 103].pack('C')
end

.latin1_to_code128(str, options = {}) ⇒ Object

Pass an array or string for encoding. The result is a string that is a Code 128 representation of the input. We do optimize, but perhaps not perfectly. The optimization should cover 99% of cases very well, although I’m sure an edge case could be created that would be suboptimal.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/barcode1dtools/code128.rb', line 273

def latin1_to_code128(str, options = {})
  if str.is_a?(String)
    str = [str]
  elsif !str.is_a?(Array)
    raise UnencodableCharactersError
  end
  arr = str.inject([]) { |a,c| c.is_a?(Symbol) ? a.push(c) : a.push(c.to_s.split('')) ; a}.flatten
  # Now, create a set of maps to see how this will map to each
  # code set.
  map_a = arr.collect { |c| ASCII_TO_CODE_A[c] ? 'a' : HIGH_ASCII_TO_CODE_A[c] ? 'A' : '-' }
  map_b = arr.collect { |c| ASCII_TO_CODE_B[c] ? 'b' : HIGH_ASCII_TO_CODE_B[c] ? 'B' : '-' }
  last_is_digit=false
  map_c = arr.collect do |c|
    if c.is_a?(Symbol) && c == :fnc_1
      if last_is_digit
        last_is_digit = false
        ['-','-']
      else
        'c'
      end
    elsif c.is_a?(String) && c >= '0' && c <= '9'
      if last_is_digit
        last_is_digit = false
        ['c','C']
      else
        last_is_digit = true
        nil
      end
    elsif last_is_digit
      last_is_digit = false
      ['-','-']
    else
      '-'
    end
  end.flatten.compact
  map_c.push('-') if last_is_digit
  # Let's do it
  map_a = map_a.join + '-'
  map_b = map_b.join + '-'
  map_c = map_c.join
  codepoints = ''
  # Strategy - create an a/b map first.  We'll do this based on
  # the least switching required which can be determined via a
  # regexp ("aaa--aa" has two switches, for instance).  After
  # that is created, we can then go in and fill in runs from
  # C - runs of 6 or more in the middle or 4 or more at either
  # end.  "444a" would just be encoded in set B, for instance,
  # but "4444a" would be encoded in C then B.
  # In the real world, switching between A and B is rare so
  # we're not trying too hard to optimize it here.
  in_codeset = nil
  x = 0
  while x < map_a.length - 1 do
    map_a_len = map_a.index('-',x).to_i - x
    map_b_len = map_b.index('-',x).to_i - x
    if map_a_len==0 && map_b_len==0
      raise "Ack!  Bad mapping: #{map_a} & #{map_b}"
    end
    if map_a_len >= map_b_len
      codepoints += map_a[x,map_a_len]
      x += map_a_len
    else
      codepoints += map_b[x,map_b_len]
      x += map_b_len
    end
  end
  # Now, fix up runs of C.  Look for runs of 4+ at the ends
  # and 6+ in the middle.  The runs must have cC in them, as
  # there's no gains from changing FNC 1 to set C.
  runs = map_c.split(/(c[cC]+Cc*)/)
  offset = 0
  0.upto(runs.length-1) do |x|
    if x==0 || x==runs.length-1
      # only needs to be 4
      if runs[x] =~ /c[cC]+C/
        codepoints[offset,runs[x].length] = runs[x]
      end
      offset += runs[x].length
    else
      # must be 6+
      if runs[x] =~ /c[cC]{3,}C/
        codepoints[offset,runs[x].length] = runs[x]
      end
      offset += runs[x].length
    end
  end
  #{ :map_a => map_a, :map_b => map_b, :map_c => map_c, :codepoints => codepoints }
  # Now, create the string
  ret = []
  current_set = codepoints[0,1].downcase
  ret.push(current_set == 'a' ? START_A : current_set == 'b' ? START_B : START_C)
  0.upto(codepoints.length-1) do |x|
    if codepoints[x,1].downcase != current_set
      current_set = codepoints[x,1].downcase
      ret.push(current_set == 'a' ? CODE_A : current_set == 'b' ? CODE_B : CODE_C)
    end
    if current_set == 'c' && codepoints[x,1] == 'c'
      # ignore capital Cs
      if arr[x] == :fnc_1
        ret.push(FNC_1)
      else
        ret.push((arr[x]+arr[x+1]).to_i)
      end
    elsif ['A','B'].include?(codepoints[x,1])
      # Find FNC_4 and push it (101 in A and 100 in B)
      # now push the letter looked up in CODE_x_TO_HIGH_ASCII
      if codepoints[x,1] == 'A'
        ret.push(HIGH_ASCII_TO_CODE_A[:fnc_4])
        ret.push(HIGH_ASCII_TO_CODE_A[arr[x]])
      else
        ret.push(HIGH_ASCII_TO_CODE_B[:fnc_4])
        ret.push(HIGH_ASCII_TO_CODE_B[arr[x]])
      end
    elsif ['a','b'].include?(codepoints[x,1])
      # find the letter in CODE_x_TO_ASCII and push it
      if codepoints[x,1] == 'a'
        ret.push(ASCII_TO_CODE_A[arr[x]])
      else
        ret.push(ASCII_TO_CODE_B[arr[x]])
      end
    end
  end
  check_digit = generate_check_digit_for(ret.pack('C*'))
  ret.push(check_digit.unpack('C').first)
  ret.push(ASCII_TO_CODE_A[:stop])
  ret.pack('C*')
end

.parse_code128(str) ⇒ Object

Returns match data - 1: start character 2: payload 3: check digit 4: stop character



164
165
166
# File 'lib/barcode1dtools/code128.rb', line 164

def parse_code128(str)
  str.match(/\A([\x67-\x69])([\x00-\x66]*?)(?:([\x00-\x66])(\x6a))?\z/)
end

.split_payload_and_check_digit(value) ⇒ Object



157
158
159
160
# File 'lib/barcode1dtools/code128.rb', line 157

def split_payload_and_check_digit(value)
  md = value.to_s.match(/\A(.*)(.)\z/)
  [md[1], md[2]]
end

.validate_check_digit_for(value) ⇒ Object



152
153
154
155
# File 'lib/barcode1dtools/code128.rb', line 152

def validate_check_digit_for(value)
  payload, check_digit = split_payload_and_check_digit(value)
  self.generate_check_digit_for(payload) == check_digit
end

Instance Method Details

#barsObject

returns 1s and 0s (for “black” and “white”)



502
503
504
# File 'lib/barcode1dtools/code128.rb', line 502

def bars
  @bars ||= self.class.rle_to_bars(self.rle, @options)
end

#rleObject

returns a run-length-encoded string representation



497
498
499
# File 'lib/barcode1dtools/code128.rb', line 497

def rle
  @rle ||= gen_rle(@encoded_string, @options)
end

#widthObject

returns the total unit width of the bar code



507
508
509
# File 'lib/barcode1dtools/code128.rb', line 507

def width
  @width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
end

#wnObject

variable bar width, no w/n string



492
493
494
# File 'lib/barcode1dtools/code128.rb', line 492

def wn
  raise NotImplementedError
end