Class: Barcode1DTools::MSI

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

Overview

Barcode1DTools::MSI - Create and decode bar patterns for MSI. The value encoded is a string which may contain the digits 0-9.

There are four possible check digit calculations, and you may use the option :check_digit => ‘x’ to choose which one to use. “x” may be one of “mod 10”, “mod 11”, “mod 1010”, or “mod 1110”. The default is “mod 10”. For a mod 11 check digit, you may use :check_style => ‘ibm’ or ‘ncr’.

MSI is a terrible symbology in modern terms and should not be used in any new applications.

val = “2898289238” bc = Barcode1DTools::MSI.new(val) pattern = bc.bars rle_pattern = bc.rle width = bc.width

The object created is immutable.

Barcode1DTools::MSI creates the patterns that you need to display MSI barcodes. It can also decode a simple w/n string.

MSI characters consist of 4 bars and 4 spaces. The representation is simply binary where a binary “0” is represented as a narrow bar followed by a wide space and a binary “1” is a wide bar followed by a narrow space. The bits are ordered descending, so 9 is 1001 binary, “wn nw nw wn” in w/n format.

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.

Rendering

The author is aware of no standards for display.

Constant Summary collapse

CHAR_SEQUENCE =

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

"0123456789"
PATTERNS =

Patterns for making bar codes

{
  '0'=> {'val'=>0 ,'wn'=>'nwnwnwnw'},
  '1'=> {'val'=>1 ,'wn'=>'nwnwnwwn'},
  '2'=> {'val'=>2 ,'wn'=>'nwnwwnnw'},
  '3'=> {'val'=>3 ,'wn'=>'nwnwwnwn'},
  '4'=> {'val'=>4 ,'wn'=>'nwwnnwnw'},
  '5'=> {'val'=>5 ,'wn'=>'nwwnnwwn'},
  '6'=> {'val'=>6 ,'wn'=>'nwwnwnnw'},
  '7'=> {'val'=>7 ,'wn'=>'nwwnwnwn'},
  '8'=> {'val'=>8 ,'wn'=>'wnnwnwnw'},
  '9'=> {'val'=>9 ,'wn'=>'wnnwnwwn'}
}
GUARD_PATTERN_LEFT_WN =
'wn'
GUARD_PATTERN_RIGHT_WN =
'nwn'
DEFAULT_OPTIONS =
{
  :line_character => '1',
  :space_character => '0',
  :w_character => 'w',
  :n_character => 'n',
  :wn_ratio => '2',
  :check_digit => 'mod 10',
  :check_style => 'ibm'
}

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

Options are :line_character, :space_character, :w_character, :n_character, :checksum_included, and :skip_checksum.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/barcode1dtools/msi.rb', line 211

def initialize(value, options = {})

  @options = DEFAULT_OPTIONS.merge(options)

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

  if @options[:skip_checksum]
    @encoded_string = value.to_s
    @value = value.to_s
    @check_digit = nil
  elsif @options[:checksum_included]
    raise ChecksumError unless self.class.validate_check_digit_for(value, options)
    @encoded_string = value.to_s
    @value, @check_digit = self.class.split_payload_and_check_digits(value, options)
  else
    @value = value.to_s
    @check_digit = self.class.generate_check_digit_for(@value, options)
    @encoded_string = "#{@value}#{@check_digit}"
  end
end

Class Method Details

.can_encode?(value) ⇒ Boolean

MSI can encode digits

Returns:

  • (Boolean)


102
103
104
# File 'lib/barcode1dtools/msi.rb', line 102

def can_encode?(value)
  value.to_s =~ /\A\d+\z/
end

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

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
# File 'lib/barcode1dtools/msi.rb', line 161

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

  # ensure a wn string
  if str =~ /[1-3]/
    str = str.tr('123','nww')
  end

  if str.reverse =~ /\A#{GUARD_PATTERN_LEFT_WN}.*?#{GUARD_PATTERN_RIGHT_WN}\z/
    str.reverse!
  end

  unless str =~ /\A#{GUARD_PATTERN_LEFT_WN}(.*?)#{GUARD_PATTERN_RIGHT_WN}\z/
    raise UnencodableCharactersError, "Start/stop pattern is not detected."
  end

  wn_pattern = $1

  # Each pattern is 4 bars and 4 spaces, with a space between.
  unless wn_pattern.size % 8 == 0
    raise UnencodableCharactersError, "Wrong number of bars."
  end

  decoded_string = ''

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

    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

  MSI.new(decoded_string, options)
end

.generate_check_digit_for(value, options = {}) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/barcode1dtools/msi.rb', line 106

def generate_check_digit_for(value, options = {})
  if options[:check_digit] == 'mod 10'
    generate_mod10_check_digit_for(value).to_s
  elsif options[:check_digit] == 'mod 11'
    generate_mod11_check_digit_for(value, options[:check_style]).to_s
  elsif options[:check_digit] == 'mod 1010'
    mod10 = generate_mod10_check_digit_for(value)
    mod10_2 = generate_mod10_check_digit_for(value + mod10.to_s)
    "#{mod10}#{mod10_2}"
  elsif options[:check_digit] == 'mod 1110'
    mod11 = generate_mod11_check_digit_for(value, options[:check_style])
    mod10_2 = generate_mod10_check_digit_for(value + mod11.to_s)
    "#{mod11}#{mod10_2}"
  end
end

.generate_mod10_check_digit_for(value) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/barcode1dtools/msi.rb', line 136

def generate_mod10_check_digit_for(value)
  value = value.to_s
  valarr = value.scan(/\d\d?/)
  if value.size.odd?
    odd = valarr.collect { |c| c[0,1] }
    even = valarr.collect { |c| c[1,1] }
  else
    odd = valarr.collect { |c| c[1,1] }
    even = valarr.collect { |c| c[0,1] }
  end
  odd = (odd.join.to_i * 2).to_s.split('').inject(0) { |a,c| a + c.to_i }
  even = even.inject(0) { |a,c| a + c.to_i }
  (10 - ((odd + even) % 10)) % 10
end

.generate_mod11_check_digit_for(value, style) ⇒ Object



151
152
153
154
155
156
157
# File 'lib/barcode1dtools/msi.rb', line 151

def generate_mod11_check_digit_for(value, style)
  max = (style == 'ncr' ? 9 : 7)
  value = value.to_s
  weight = 1
  sum = value.split('').reverse.inject(0) { |a,c| weight = (weight == max ? 2 : weight + 1); a + weight * c.to_i }
  (11 - (sum % 11)) % 11
end

.split_payload_and_check_digits(value, options = {}) ⇒ Object



127
128
129
130
131
132
133
134
# File 'lib/barcode1dtools/msi.rb', line 127

def split_payload_and_check_digits(value, options = {})
  if options[:check_digit] == 'mod 1010' || options[:check_digit] == 'mod 1110'
    md = value.to_s.match(/\A(.*?)(..)\z/)
  else
    md = value.to_s.match(/\A(.*?)(.)\z/)
  end
  [md[1], md[2]]
end

.validate_check_digit_for(value, options = {}) ⇒ Object



122
123
124
125
# File 'lib/barcode1dtools/msi.rb', line 122

def validate_check_digit_for(value, options = {})
  payload, check_digits = split_payload_and_check_digits(value, options)
  self.generate_check_digit_for(payload, options) == check_digits
end

Instance Method Details

#barsObject

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



244
245
246
# File 'lib/barcode1dtools/msi.rb', line 244

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

#rleObject

returns a run-length-encoded string representation



239
240
241
# File 'lib/barcode1dtools/msi.rb', line 239

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

#widthObject

returns the total unit width of the bar code



249
250
251
# File 'lib/barcode1dtools/msi.rb', line 249

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”)



234
235
236
# File 'lib/barcode1dtools/msi.rb', line 234

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