diff --git a/CHANGELOG.md b/CHANGELOG.md index cba6941..d80bb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.2.0] - 2024-02-11 + +- Support GS1 DataMatrix + ## [0.1.1] - 2021-09-02 - Fix PNG bgcolor diff --git a/Gemfile b/Gemfile index 0d3310a..f56fc3d 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,4 @@ gemspec gem 'rake', '~> 13.0' gem 'minitest', '~> 5.0' +gem 'debug' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..02d1d41 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,40 @@ +PATH + remote: . + specs: + dmtx (0.2.0) + builder + chunky_png + +GEM + remote: https://rubygems.org/ + specs: + builder (3.2.4) + chunky_png (1.4.0) + debug (1.9.1) + irb (~> 1.10) + reline (>= 0.3.8) + io-console (0.7.2) + irb (1.11.2) + rdoc + reline (>= 0.4.2) + minitest (5.22.2) + psych (5.1.2) + stringio + rake (13.1.0) + rdoc (6.6.2) + psych (>= 4.0.0) + reline (0.4.2) + io-console (~> 0.5) + stringio (3.1.0) + +PLATFORMS + ruby + +DEPENDENCIES + debug + dmtx! + minitest (~> 5.0) + rake (~> 13.0) + +BUNDLED WITH + 2.1.4 diff --git a/README.md b/README.md index 5364fbb..63c6d75 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,13 @@ dmtx.width dmtx.height => 16 +dmtx.encoding +=> :ascii + +dmtx.encoded_message +=> [68, 105, 118, 111, 108, 122, 33, 67, 98, 100, 112, 111, + 62, 103, 49, 93, 99, 53, 117, 202, 250, 186, 232, 14] + # Generate SVG dmtx.to_svg => " "1010101010101010101110101000001111010111111101101001101011101001100110010100111010111011111100111001011001001000100010000111010110110001101111001011001001101011110000011111110010101101101010111001101001000000111100101000110110101110011100101111111111111111" + +# Choose encoding +Dmtx::DataMatrix.new('Chunky Bacon', encoding: :txt) + +# GS1 DataMatrix +Dmtx::DataMatrix.new("\x1d01095011010209171719050810ABCD1234\x1d2110", encoding: :gs1) diff --git a/lib/dmtx.rb b/lib/dmtx.rb index e533f26..0c8c50b 100644 --- a/lib/dmtx.rb +++ b/lib/dmtx.rb @@ -1,7 +1,7 @@ require_relative 'dmtx/version' require_relative 'dmtx/data_matrix' -require_relative 'dmtx/gs1_data_matrix' module Dmtx class Error < StandardError; end + class EncodingError < Error; end end diff --git a/lib/dmtx/data_matrix.rb b/lib/dmtx/data_matrix.rb index c3d8992..103d6cc 100644 --- a/lib/dmtx/data_matrix.rb +++ b/lib/dmtx/data_matrix.rb @@ -6,7 +6,7 @@ module Dmtx class DataMatrix - attr_reader :width, :height + attr_reader :width, :height, :encoded_message, :encoding C40 = [230, 31, 0, 0, @@ -43,16 +43,26 @@ class DataMatrix 64, 8, 0, 90, 9, 51, 255, 8, 0] - - def initialize(msg, rect: false) + + # See https://www.gs1.org/standards/gs1-datamatrix-guideline/25 + FNC1 = 29 + FNC1_CODEWORD = 232 + + DEFAULT_ENCODINGS = %i[ascii c40 txt x12 edifact base gs1].freeze + ENCODINGS = (%i[gs1] + DEFAULT_ENCODINGS).freeze + + def initialize(msg, rect: false, encoding: nil) @m = [] @width = 0 @height = 0 - encode(msg, rect) + raise ArgumentError, "illegal encoding #{encoding.inspect}" if encoding && !ENCODINGS.include?(encoding) + @encoding, @encoded_message = encode_message(msg, encoding) + raise EncodingError, "illegal payload" unless @encoded_message + encode(@encoded_message, rect) end def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}>" + "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}@#{encoding}>" end def to_i @@ -76,7 +86,6 @@ def to_svg(dim: 256, pad: 2, bgcolor: nil, color: '#000') y = height while y > 0 y -= 1 - d = 0 x = width while x > 0 x -= 1 @@ -139,6 +148,23 @@ def ascii_encode(t) end result end + + def gs1_encode(t) + bytes, result = t.bytes, [] + while c = bytes.shift + if !bytes.empty? && c > 47 && c < 58 && bytes.first > 47 && bytes.first < 58 + result << (c - 48) * 10 + bytes.shift + 82 + elsif c > 127 + result << 235 + result << ((c - 127) & 255) + elsif c == FNC1 + result << FNC1_CODEWORD + else + result << c + 1 + end + end + result + end def base_encode(t) bytes, result = t.bytes, [231] @@ -204,18 +230,27 @@ def text_encode(t, s) result.concat ascii_encode(bytes[(i - cc)..-1].to_a.pack('C*')) if cc > 0 || i < l result end - - def encodings(msg) - { ascii: ascii_encode(msg), - c40: text_encode(msg, C40), - txt: text_encode(msg, TEXT), - x12: text_encode(msg, X12), - edifact: edifact_encode(msg), - base: base_encode(msg) } + + def c40_encode(t) + text_encode(t, C40) + end + + def txt_encode(t) + text_encode(t, TEXT) + end + + def x12_encode(t) + text_encode(t, X12) + end + + def encode_message(msg, encoding) + (encoding ? [encoding] : DEFAULT_ENCODINGS) + .map { |name| [name, send("#{name}_encode", msg)] } + .reject { |_, encoded| encoded.empty? } + .min_by { |_, encoded| encoded.size } end - def encode(text, rct) - enc = encodings(text).values.reject(&:empty?).min_by(&:size) + def encode(enc, rct) el = enc.size nc = nr = 1 j = -1 diff --git a/lib/dmtx/gs1_data_matrix.rb b/lib/dmtx/gs1_data_matrix.rb deleted file mode 100644 index 1f830cb..0000000 --- a/lib/dmtx/gs1_data_matrix.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Dmtx - # See https://www.gs1.org/standards/gs1-datamatrix-guideline/25 - class GS1DataMatrix < DataMatrix - FNC1 = "\u001D".freeze # ASCII 29, INFORMATION SEPARATOR THREE, group separator - FNC1_CODEWORD = 232 - - def encodings(msg) - { asci: ascii_encode(msg) } - end - - def ascii_encode(t) - bytes, result = t.bytes, [] - while c = bytes.shift - if !bytes.empty? && c > 47 && c < 58 && bytes.first > 47 && bytes.first < 58 - result << (c - 48) * 10 + bytes.shift + 82 - elsif c > 127 - result << 235 - result << ((c - 127) & 255) - elsif c == FNC1.bytes.first - result << FNC1_CODEWORD - else - result << c + 1 - end - end - result - end - end -end diff --git a/lib/dmtx/version.rb b/lib/dmtx/version.rb index 0a25727..86366d0 100644 --- a/lib/dmtx/version.rb +++ b/lib/dmtx/version.rb @@ -1,3 +1,3 @@ module Dmtx - VERSION = '0.1.1' + VERSION = '0.2.0' end diff --git a/test/dmtx_test.rb b/test/dmtx_test.rb index 0dccc5c..ef0e709 100644 --- a/test/dmtx_test.rb +++ b/test/dmtx_test.rb @@ -33,9 +33,9 @@ def test_generate_data_matrix_svg def test_gs1_data_matrix_encodings # See example 2 at https://www.gs1.org/standards/gs1-datamatrix-guideline/25#2-Encoding-data+2-3-Human-readable-interpretation-(HRI) - data = "#{Dmtx::GS1DataMatrix::FNC1}01095011010209171719050810ABCD1234#{Dmtx::GS1DataMatrix::FNC1}2110" - encodings = Dmtx::GS1DataMatrix.new(data).encodings(data) - assert_equal %i[asci], encodings.keys - assert_equal 232, encodings[:asci].first + data = "#{Dmtx::DataMatrix::FNC1.chr}01095011010209171719050810ABCD1234#{Dmtx::DataMatrix::FNC1.chr}2110" + dmtx = Dmtx::DataMatrix.new(data, encoding: :gs1) + assert_equal :gs1, dmtx.encoding + assert_equal 232, dmtx.encoded_message.first end end