Class: Barcode1DTools::Code3of9

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

Overview

Barcode1DTools::Code3of9 - Create and decode bar patterns for Code 3 of 9 (also known as Code 39, USD-3, Alpha39, Code 3/9, Type 39, or USS Code 39) barcodes. The value encoded is a string, and a checksum digit may be added. You can add the option :checksum_included => true when initializing to specify that you have already included a checksum, or :skip_checksum => true to specify that no checksum should be added or checked. A ChecksumError will be raised if :checksum_included => true, :skip_checksum is false, and the last digit is invalid as a checksum. Note that the default is “skip_checksum”.

Code 3 of 9 can encode digits, uppercase letters, and the symbols dash “-”, period “.”, dollar sign “$”, forward slash “/”, plus sign “+”, percent sign “%”, as well as a space “ ”.

Example

val = "THIS IS A TEST"
bc = Barcode1DTools::Code3of9.new(val)
pattern = bc.bars
rle_pattern = bc.rle
wn_pattern = bc.wn
width = bc.width
# Note that the check digit is actually one of the characters.
check_digit = Barcode1DTools::Code3of9.generate_check_digit_for(num)

The object created is immutable.

Barcode1DTools::Code3of9 creates the patterns that you need to display Code 3 of 9 barcodes. It can also decode a simple w/n string.

Code 3 of 9 barcodes consist of lines and spaces that are either “wide” or “narrow”, with “wide” lines or spaces being twice the width of narrow lines or spaces.

Formats

There are three 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.

wn - The native format for this barcode type. The string consists of a series of “w” and “n” characters. The first item is always a black line, with subsequent characters alternating between spaces and lines. A “wide” item is twice the width of a “narrow” item.

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

Miscellaneous Information

Code 3 of 9 can encode text and digits. There is also a way to do “full ascii” mode, but it’s not recommended. Full ascii mode uses some of the characters as shift characters, e.g. “a” is encoded as “+A”. There’s no indication that full ascii mode is being used, so it has to be handled by the application. This has been fixed in Code 93, by designation of four special characters which are used only for shifting. However, if you need to use a full character set, Code 128 is probably a better choice.

Note: Please note that Code 3 of 9 is not suggested for new applications due to the fact that the code is sparse and doesn’t encode a full range of characters without using the “full ascii extensions”, which cause it to be even more sparse. For newer 1D applications use Code 128.

Rendering

Code 3 of 9 may be rendered however the programmer wishes. Since there is a simple mapping between number of characters and length of code, a variable length code should be allowed to grow and shrink to assure the bars are neither too large or too small. Code 3 of 9 is often implemented as a font.

There is no standard for human-readable text associated with the code, and in fact some applications leave out the human-readable element altogether. The text is typically shown below the barcode where applicable.

Constant Summary collapse

CHAR_SEQUENCE =

Character sequence - 0-based offset in this string is character number

'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%'
PATTERNS =

Patterns for making bar codes

{
  '0' => { 'position' => 0,  'wn' => 'nnnwwnwnn' },
  '1' => { 'position' => 1,  'wn' => 'wnnwnnnnw' },
  '2' => { 'position' => 2,  'wn' => 'nnwwnnnnw' },
  '3' => { 'position' => 3,  'wn' => 'wnwwnnnnn' },
  '4' => { 'position' => 4,  'wn' => 'nnnwwnnnw' },
  '5' => { 'position' => 5,  'wn' => 'wnnwwnnnn' },
  '6' => { 'position' => 6,  'wn' => 'nnwwwnnnn' },
  '7' => { 'position' => 7,  'wn' => 'nnnwnnwnw' },
  '8' => { 'position' => 8,  'wn' => 'wnnwnnwnn' },
  '9' => { 'position' => 9,  'wn' => 'nnwwnnwnn' },
  'A' => { 'position' => 10, 'wn' => 'wnnnnwnnw' },
  'B' => { 'position' => 11, 'wn' => 'nnwnnwnnw' },
  'C' => { 'position' => 12, 'wn' => 'wnwnnwnnn' },
  'D' => { 'position' => 13, 'wn' => 'nnnnwwnnw' },
  'E' => { 'position' => 14, 'wn' => 'wnnnwwnnn' },
  'F' => { 'position' => 15, 'wn' => 'nnwnwwnnn' },
  'G' => { 'position' => 16, 'wn' => 'nnnnnwwnw' },
  'H' => { 'position' => 17, 'wn' => 'wnnnnwwnn' },
  'I' => { 'position' => 18, 'wn' => 'nnwnnwwnn' },
  'J' => { 'position' => 19, 'wn' => 'nnnnwwwnn' },
  'K' => { 'position' => 20, 'wn' => 'wnnnnnnww' },
  'L' => { 'position' => 21, 'wn' => 'nnwnnnnww' },
  'M' => { 'position' => 22, 'wn' => 'wnwnnnnwn' },
  'N' => { 'position' => 23, 'wn' => 'nnnnwnnww' },
  'O' => { 'position' => 24, 'wn' => 'wnnnwnnwn' },
  'P' => { 'position' => 25, 'wn' => 'nnwnwnnwn' },
  'Q' => { 'position' => 26, 'wn' => 'nnnnnnwww' },
  'R' => { 'position' => 27, 'wn' => 'wnnnnnwwn' },
  'S' => { 'position' => 28, 'wn' => 'nnwnnnwwn' },
  'T' => { 'position' => 29, 'wn' => 'nnnnwnwwn' },
  'U' => { 'position' => 30, 'wn' => 'wwnnnnnnw' },
  'V' => { 'position' => 31, 'wn' => 'nwwnnnnnw' },
  'W' => { 'position' => 32, 'wn' => 'wwwnnnnnn' },
  'X' => { 'position' => 33, 'wn' => 'nwnnwnnnw' },
  'Y' => { 'position' => 34, 'wn' => 'wwnnwnnnn' },
  'Z' => { 'position' => 35, 'wn' => 'nwwnwnnnn' },
  '-' => { 'position' => 36, 'wn' => 'nwnnnnwnw' },
  '.' => { 'position' => 37, 'wn' => 'wwnnnnwnn' },
  ' ' => { 'position' => 38, 'wn' => 'nwwnnnwnn' },
  '$' => { 'position' => 39, 'wn' => 'nwnwnwnnn' },
  '/' => { 'position' => 40, 'wn' => 'nwnwnnnwn' },
  '+' => { 'position' => 41, 'wn' => 'nwnnnwnwn' },
  '%' => { 'position' => 42, 'wn' => 'nnnwnwnwn' }
}
SIDE_GUARD_PATTERN =

Guard pattern

'nwnnwnwnn'
FULL_ASCII_LOOKUP =

This table is useful for implementing “full ascii” mode. It is a 128 element array that will return the character or characters to represent the ascii character at a particular code point.

[
  '%U', '$A', '$B', '$C', '$D', '$E', '$F', '$G', '$H', '$I',
  '$J', '$K', '$L', '$M', '$N', '$O', '$P', '$Q', '$R', '$S',
  '$T', '$U', '$V', '$W', '$X', '$Y', '$Z', '%A', '%B', '%C',
  '%D', '%E', ' ', '/A', '/B', '/C', '/D', '/E', '/F', '/G',
  '/H', '/I', '/J', '/K', '/L', '-', '.', '/O', '0', '1', '2',
  '3', '4', '5', '6', '7', '8', '9', '/Z', '%F', '%G', '%H',
  '%I', '%J', '%V', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
  'U', 'V', 'W', 'X', 'Y', 'Z', '%K', '%L', '%M', '%N', '%O',
  '%W', '+A', '+B', '+C', '+D', '+E', '+F', '+G', '+H', '+I',
  '+J', '+K', '+L', '+M', '+N', '+O', '+P', '+Q', '+R', '+S',
  '+T', '+U', '+V', '+W', '+X', '+Y', '+Z', '%P', '%Q', '%R',
  '%S', '%T'
]
FULL_ASCII_REVERSE_LOOKUP =

This is a reverse lookup. Given a character or character pair from a “full ascii” representation this will give the ascii code point as well as a character name.

{
  '%U' => { 'position' => 0,  'name' => '<NUL>' },
  '$A' => { 'position' => 1,  'name' => '<SOH>' },
  '$B' => { 'position' => 2,  'name' => '<STX>' },
  '$C' => { 'position' => 3,  'name' => '<ETX>' },
  '$D' => { 'position' => 4,  'name' => '<EOT>' },
  '$E' => { 'position' => 5,  'name' => '<ENQ>' },
  '$F' => { 'position' => 6,  'name' => '<ACK>' },
  '$G' => { 'position' => 7,  'name' => '<BEL>' },
  '$H' => { 'position' => 8,  'name' => '<BS>' },
  '$I' => { 'position' => 9,  'name' => '<HT>' },
  '$J' => { 'position' => 10, 'name' => '<LF>' },
  '$K' => { 'position' => 11, 'name' => '<VT>' },
  '$L' => { 'position' => 12, 'name' => '<FF>' },
  '$M' => { 'position' => 13, 'name' => '<CR>' },
  '$N' => { 'position' => 14, 'name' => '<SO>' },
  '$O' => { 'position' => 15, 'name' => '<SI>' },
  '$P' => { 'position' => 16, 'name' => '<DLE>' },
  '$Q' => { 'position' => 17, 'name' => '<DC1>' },
  '$R' => { 'position' => 18, 'name' => '<DC2>' },
  '$S' => { 'position' => 19, 'name' => '<DC3>' },
  '$T' => { 'position' => 20, 'name' => '<DC4>' },
  '$U' => { 'position' => 21, 'name' => '<NAK>' },
  '$V' => { 'position' => 22, 'name' => '<SYN>' },
  '$W' => { 'position' => 23, 'name' => '<ETB>' },
  '$X' => { 'position' => 24, 'name' => '<CAN>' },
  '$Y' => { 'position' => 25, 'name' => '<EM>' },
  '$Z' => { 'position' => 26, 'name' => '<SUB>' },
  '%A' => { 'position' => 27, 'name' => '<ESC>' },
  '%B' => { 'position' => 28, 'name' => '<FS>' },
  '%C' => { 'position' => 29, 'name' => '<GS>' },
  '%D' => { 'position' => 30, 'name' => '<RS>' },
  '%E' => { 'position' => 31, 'name' => '<US>' },
  ' '  => { 'position' => 32, 'name' => ' ' },
  '/A' => { 'position' => 33, 'name' => '!' },
  '/B' => { 'position' => 34, 'name' => '"' },
  '/C' => { 'position' => 35, 'name' => '#' },
  '/D' => { 'position' => 36, 'name' => '$' },
  '/E' => { 'position' => 37, 'name' => '%' },
  '/F' => { 'position' => 38, 'name' => '&' },
  '/G' => { 'position' => 39, 'name' => "'" },
  '/H' => { 'position' => 40, 'name' => '(' },
  '/I' => { 'position' => 41, 'name' => ')' },
  '/J' => { 'position' => 42, 'name' => '*' },
  '/K' => { 'position' => 43, 'name' => '+' },
  '/L' => { 'position' => 44, 'name' => ',' },
  '-'  => { 'position' => 45, 'name' => '-' },
  '.'  => { 'position' => 46, 'name' => '.' },
  '/O' => { 'position' => 47, 'name' => '/' },
  '0'  => { 'position' => 48, 'name' => '0' },
  '1'  => { 'position' => 49, 'name' => '1' },
  '2'  => { 'position' => 50, 'name' => '2' },
  '3'  => { 'position' => 51, 'name' => '3' },
  '4'  => { 'position' => 52, 'name' => '4' },
  '5'  => { 'position' => 53, 'name' => '5' },
  '6'  => { 'position' => 54, 'name' => '6' },
  '7'  => { 'position' => 55, 'name' => '7' },
  '8'  => { 'position' => 56, 'name' => '8' },
  '9'  => { 'position' => 57, 'name' => '9' },
  '/Z' => { 'position' => 58, 'name' => ':' },
  '%F' => { 'position' => 59, 'name' => ';' },
  '%G' => { 'position' => 60, 'name' => '<' },
  '%H' => { 'position' => 61, 'name' => '=' },
  '%I' => { 'position' => 62, 'name' => '>' },
  '%J' => { 'position' => 63, 'name' => '?' },
  '%V' => { 'position' => 64, 'name' => '@' },
  'A'  => { 'position' => 65, 'name' => 'A' },
  'B'  => { 'position' => 66, 'name' => 'B' },
  'C'  => { 'position' => 67, 'name' => 'C' },
  'D'  => { 'position' => 68, 'name' => 'D' },
  'E'  => { 'position' => 69, 'name' => 'E' },
  'F'  => { 'position' => 70, 'name' => 'F' },
  'G'  => { 'position' => 71, 'name' => 'G' },
  'H'  => { 'position' => 72, 'name' => 'H' },
  'I'  => { 'position' => 73, 'name' => 'I' },
  'J'  => { 'position' => 74, 'name' => 'J' },
  'K'  => { 'position' => 75, 'name' => 'K' },
  'L'  => { 'position' => 76, 'name' => 'L' },
  'M'  => { 'position' => 77, 'name' => 'M' },
  'N'  => { 'position' => 78, 'name' => 'N' },
  'O'  => { 'position' => 79, 'name' => 'O' },
  'P'  => { 'position' => 80, 'name' => 'P' },
  'Q'  => { 'position' => 81, 'name' => 'Q' },
  'R'  => { 'position' => 82, 'name' => 'R' },
  'S'  => { 'position' => 83, 'name' => 'S' },
  'T'  => { 'position' => 84, 'name' => 'T' },
  'U'  => { 'position' => 85, 'name' => 'U' },
  'V'  => { 'position' => 86, 'name' => 'V' },
  'W'  => { 'position' => 87, 'name' => 'W' },
  'X'  => { 'position' => 88, 'name' => 'X' },
  'Y'  => { 'position' => 89, 'name' => 'Y' },
  'Z'  => { 'position' => 90, 'name' => 'Z' },
  '%K' => { 'position' => 91, 'name' => '[' },
  '%L' => { 'position' => 92, 'name' => '\\' },
  '%M' => { 'position' => 93, 'name' => ']' },
  '%N' => { 'position' => 94, 'name' => '^' },
  '%O' => { 'position' => 95, 'name' => '_' },
  '%W' => { 'position' => 96, 'name' => '`' },
  '+A' => { 'position' => 97, 'name' => 'a' },
  '+B' => { 'position' => 98, 'name' => 'b' },
  '+C' => { 'position' => 99, 'name' => 'c' },
  '+D' => { 'position' => 100, 'name' => 'd' },
  '+E' => { 'position' => 101, 'name' => 'e' },
  '+F' => { 'position' => 102, 'name' => 'f' },
  '+G' => { 'position' => 103, 'name' => 'g' },
  '+H' => { 'position' => 104, 'name' => 'h' },
  '+I' => { 'position' => 105, 'name' => 'i' },
  '+J' => { 'position' => 106, 'name' => 'j' },
  '+K' => { 'position' => 107, 'name' => 'k' },
  '+L' => { 'position' => 108, 'name' => 'l' },
  '+M' => { 'position' => 109, 'name' => 'm' },
  '+N' => { 'position' => 110, 'name' => 'n' },
  '+O' => { 'position' => 111, 'name' => 'o' },
  '+P' => { 'position' => 112, 'name' => 'p' },
  '+Q' => { 'position' => 113, 'name' => 'q' },
  '+R' => { 'position' => 114, 'name' => 'r' },
  '+S' => { 'position' => 115, 'name' => 's' },
  '+T' => { 'position' => 116, 'name' => 't' },
  '+U' => { 'position' => 117, 'name' => 'u' },
  '+V' => { 'position' => 118, 'name' => 'v' },
  '+W' => { 'position' => 119, 'name' => 'w' },
  '+X' => { 'position' => 120, 'name' => 'x' },
  '+Y' => { 'position' => 121, 'name' => 'y' },
  '+Z' => { 'position' => 122, 'name' => 'z' },
  '%P' => { 'position' => 123, 'name' => '{' },
  '%Q' => { 'position' => 124, 'name' => '|' },
  '%R' => { 'position' => 125, 'name' => '}' },
  '%S' => { 'position' => 126, 'name' => '~' },
  '%T' => { 'position' => 127, 'name' => '<DEL>' },
  '%X' => { 'position' => 127, 'name' => '<DEL>' },
  '%Y' => { 'position' => 127, 'name' => '<DEL>' },
  '%Z' => { 'position' => 127, 'name' => '<DEL>' }
}
WN_RATIO =

Default w/n ratio.

2
DEFAULT_OPTIONS =
{
  :w_character => 'w',
  :n_character => 'n',
  :line_character => '1',
  :space_character => '0',
  :wn_ratio => WN_RATIO,
  :skip_checksum => true
}

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 = {}) ⇒ Code3of9

Create a new Code3of9 barcode object. Options are :line_character, :space_character, :w_character, :n_character, :skip_checksum, and :checksum_included.



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
# File 'lib/barcode1dtools/code3of9.rb', line 417

def initialize(value, options = {})

  @options = DEFAULT_OPTIONS.merge(options)

  # Can we encode this value?
  raise UnencodableCharactersError unless self.class.can_encode?(value)

  value = value.to_s

  if @options[:skip_checksum] && !@options[:checksum_included]
    @encoded_string = value
    @value = value
    @check_digit = nil
  elsif @options[:checksum_included]
    @options[:skip_checksum] = nil
    raise ChecksumError unless self.class.validate_check_digit_for(value)
    @encoded_string = value
    md = value.match(/\A(.*?)(.)\z/)
    @value, @check_digit = md[1], md[2]
  else
    # need to add a checksum
    @value = value
    @check_digit = self.class.generate_check_digit_for(@value)
    @encoded_string = "#{@value}#{@check_digit}"
  end
end

Class Method Details

.can_encode?(value) ⇒ Boolean

Returns true if the given value can be encoded in the Code 3 of 9 symbology. See CHAR_SEQUENCE for a full list of characters and their positions.

Returns:

  • (Boolean)


326
327
328
# File 'lib/barcode1dtools/code3of9.rb', line 326

def can_encode?(value)
  value.to_s =~ /\A[0-9A-Z\-\. \$\/\+%]*\z/
end

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

Decode a string in wn format. This will return a Code3of9 object.



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
# File 'lib/barcode1dtools/code3of9.rb', line 351

def decode(str, options = {})
  if str =~ /[^wn]/
    raise UnencodableCharactersError, "Pattern must contain only \"w\" and \"n\"."
  end

  if str.reverse =~ /^#{SIDE_GUARD_PATTERN}n.*?n#{SIDE_GUARD_PATTERN}$/
    str.reverse!
  end

  unless str =~ /^#{SIDE_GUARD_PATTERN}n(.*?)#{SIDE_GUARD_PATTERN}$/
    raise UnencodableCharactersError, "Start/stop pattern is not detected."
  end

  wn_pattern = $1

  unless wn_pattern.size % 10 == 0
    raise UnencodableCharactersError, "Wrong number of bars."
  end

  decoded_string = ''

  wn_pattern.scan(/(.{9})n/).each do |chunk|

    chunk = chunk[0]

    found = false

    PATTERNS.each do |char,hsh|
      if chunk == hsh['wn']
        decoded_string += char
        found = true
        break;
      end
    end

    raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found

  end

  Code3of9.new(decoded_string, options)
end

.decode_full_ascii(str) ⇒ Object

Decodes a “full ascii” string from Code 3 of 9 into standard ascii. Note that this will silently fail if a string is malformed.



409
410
411
# File 'lib/barcode1dtools/code3of9.rb', line 409

def decode_full_ascii(str)
  str.scan(/[\$%\/+]?[A-Z0-9 \.\-]/).collect { |c| FULL_ASCII_REVERSE_LOOKUP[c]['position'] }.pack('C*')
end

.encode_full_ascii(str) ⇒ Object

Provide encoding into the “full ascii” format. This allows us to encode any ascii character (0-127) in a Code 3 of 9, but it is up to the application to anticipate and handle this. In this encoding, four of the characters ($, %, /, and +) are used as “shift” characters, paired with a letter A-Z that encodes a character that’s not available in Code 3 of 9. Because no special characters are used, it’s not possible to know if this encoding is used.



402
403
404
# File 'lib/barcode1dtools/code3of9.rb', line 402

def encode_full_ascii(str)
  str.bytes.collect { |c| FULL_ASCII_LOOKUP[c] }.join
end

.generate_check_digit_for(value) ⇒ Object

Generates check digit given a string to encode. It assumes there is no check digit on the “value”. The check “digit” is actually a character, and the “position” value in PATTERNS can be used to find the numeric value.



334
335
336
337
338
339
# File 'lib/barcode1dtools/code3of9.rb', line 334

def generate_check_digit_for(value)
  raise UnencodableCharactersError unless self.can_encode?(value)
  mult = 1
  sum = value.to_s.split('').inject(0) { |a,c| a + PATTERNS[c]['position'] }
  CHAR_SEQUENCE[sum % 43,1]
end

.validate_check_digit_for(value) ⇒ Object

Validates the check digit given a string - assumes check digit is last digit of string.



343
344
345
346
347
# File 'lib/barcode1dtools/code3of9.rb', line 343

def validate_check_digit_for(value)
  raise UnencodableCharactersError unless self.can_encode?(value)
  md = value.to_s.match(/^(.*)(.)$/)
  self.generate_check_digit_for(md[1]) == md[2]
end

Instance Method Details

#barsObject

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



455
456
457
# File 'lib/barcode1dtools/code3of9.rb', line 455

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

#rleObject

Returns a run-length-encoded string representation



450
451
452
# File 'lib/barcode1dtools/code3of9.rb', line 450

def rle
  @rle ||= self.class.wn_to_rle(self.wn, @options)
end

#widthObject

Returns the total unit width of the bar code



460
461
462
# File 'lib/barcode1dtools/code3of9.rb', line 460

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

#wnObject

Returns a string of “w” or “n” (“wide” and “narrow”)



445
446
447
# File 'lib/barcode1dtools/code3of9.rb', line 445

def wn
  @wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
end