Class: Barcode1DTools::Code128
- 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.
Be sure to use Latin-1 (ISO-8859-1) in Ruby 1.9+ for any strings that use high characters. They will be encoded and decoded properly.
Example
val = "This is a test"
bc = Barcode1DTools::Code128.new(val)
# Pass an array if you wish to use FNC characters
val = [:fnc_1, "42184037211"]
bc = Barcode1DTools::Code128.new(val)
pattern = bc.
rle_pattern = bc.rle
width = bc.width
It is possible to pass a raw value to be encoded by using the option :raw_value => true when creating the object. This must include the start character, string (each character being between 0 and 105 inclusive), the checksum character, and the stop character. No checks are performed on the string.
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.
Formats
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 =
Helps with 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 =
Guard/start patterns
- START_A_RLE =
- START_B_RLE =
- START_C_RLE =
- LOW_ASCII_LABELS =
These are the standard labels for the low-ascii (0-31) characters. They aren’t actually used here, but are included as they are useful for debugging.
[ '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
-
.can_encode?(value) ⇒ Boolean
Returns true if Code128 can encode the value.
-
.code128_to_latin1(str, options = {}) ⇒ Object
Convert a code128 encoded string to an ASCII/Latin-1 representation.
-
.decode(str, options = {}) ⇒ Object
Decode a string in rle format.
-
.generate_check_digit_for(value) ⇒ Object
Generates the check digit for the encoded string.
-
.latin1_to_code128(str, options = {}) ⇒ Object
Pass an array or string for encoding.
-
.parse_code128(str) ⇒ Object
Given a raw encoded value, this returns match data - 1: start character 2: payload 3: check digit 4: stop character.
-
.split_payload_and_check_digit(value) ⇒ Object
Splits the payload (raw) and check digit.
-
.validate_check_digit_for(value) ⇒ Object
Validates the check digit given an encoded value.
Instance Method Summary collapse
-
#bars ⇒ Object
Returns 1s and 0s (for “black” and “white”).
-
#initialize(value, options = {}) ⇒ Code128
constructor
Create a new Code128 object given a value.
-
#rle ⇒ Object
Returns a run-length-encoded string representation.
-
#width ⇒ Object
Returns the total unit width of the bar code.
-
#wn ⇒ Object
Code 128 is variable bar width, no w/n string.
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
Create a new Code128 object given a value. Options are :line_character, :space_character, and :raw_value.
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 |
# File 'lib/barcode1dtools/code128.rb', line 483 def initialize(value, = {}) @options = DEFAULT_OPTIONS.merge() 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
Returns true if Code128 can encode the value. Code128 can encode anything, so this is always true.
162 163 164 |
# File 'lib/barcode1dtools/code128.rb', line 162 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 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.
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 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/barcode1dtools/code128.rb', line 206 def code128_to_latin1(str, = {}) 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 [: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 && ![:no_latin1] if shift_latin1 in_high_latin1 = !in_high_latin1 shift_latin1 = false else shift_latin1 = true end elsif ![:raw_array] ret.push this_item end ret.push(this_item) if [: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 [: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 [: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.
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/barcode1dtools/code128.rb', line 431 def decode(str, = {}) 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 = (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*'), ) Code128.new(decoded_string, ) end |
.generate_check_digit_for(value) ⇒ Object
Generates the check digit for the encoded string. Code 128 creates the check digit based on the encoded value, so it’s of no use outside of this module. Because the same payload may be represented in more than one way, the check digit can’t be determined from the original payload value.
171 172 173 174 175 176 |
# File 'lib/barcode1dtools/code128.rb', line 171 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.
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 400 401 402 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 |
# File 'lib/barcode1dtools/code128.rb', line 301 def latin1_to_code128(str, = {}) 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
Given a raw encoded value, this returns match data - 1: start character 2: payload 3: check digit 4: stop character.
193 194 195 |
# File 'lib/barcode1dtools/code128.rb', line 193 def parse_code128(str) str.match(/\A([\x67-\x69])([\x00-\x66]*?)(?:([\x00-\x66])(\x6a))?\z/) end |
.split_payload_and_check_digit(value) ⇒ Object
Splits the payload (raw) and check digit.
185 186 187 188 |
# File 'lib/barcode1dtools/code128.rb', line 185 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
Validates the check digit given an encoded value.
179 180 181 182 |
# File 'lib/barcode1dtools/code128.rb', line 179 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
#bars ⇒ Object
Returns 1s and 0s (for “black” and “white”)
531 532 533 |
# File 'lib/barcode1dtools/code128.rb', line 531 def @bars ||= self.class.(self.rle, @options) end |
#rle ⇒ Object
Returns a run-length-encoded string representation
526 527 528 |
# File 'lib/barcode1dtools/code128.rb', line 526 def rle @rle ||= gen_rle(@encoded_string, @options) end |
#width ⇒ Object
Returns the total unit width of the bar code
536 537 538 |
# File 'lib/barcode1dtools/code128.rb', line 536 def width @width ||= rle.split('').inject(0) { |a,c| a + c.to_i } end |
#wn ⇒ Object
Code 128 is variable bar width, no w/n string
521 522 523 |
# File 'lib/barcode1dtools/code128.rb', line 521 def wn raise NotImplementedError end |