dmtx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c16436599c8d67eaede6abf22d043f3154e7448d11c5aad7ea2d73a9d60a2f8a
4
+ data.tar.gz: 05a2a4585428d5a1f7dc653ea08e49e2c66bdd704a2791443524eee1196c4150
5
+ SHA512:
6
+ metadata.gz: 38aed0964ba6bc6e60d276b867bf7def43aef7cd7be1f950c08690634cda9e6f58cf70396d7399d42c983427ca5fb1ffc9629f81a5bd26b66f9690221d47e49b
7
+ data.tar.gz: ca534e74457c993ec07af6e88011f44d1a8ae7e1aba3901a7e8dbdb12952ae5d0d241935a94bf9f6de8a6b12052ef621539ae5fe6a74b3f66c56df9b74237661
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.1.0] - 2021-09-02
2
+
3
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Matthias Grosser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # dmtx
2
+ Pure Ruby Data Matrix generator
3
+
4
+ ## Installation
5
+
6
+ In your Gemfile:
7
+
8
+ ```ruby
9
+ gem 'dmtx'
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ dmtx = Dmtx::DataMatrix.new('Chunky Bacon')
16
+
17
+ puts dmtx.to_s
18
+
19
+ ██ ██ ██ ██ ██ ██ ██ ██
20
+ ██ ██████ ██ ██ ████
21
+ ████ ██ ██████████████ ████
22
+ ██ ████ ██ ██████ ██ ██
23
+ ██ ████ ██ ██ ██████
24
+ ██ ██████ ████████████ ████
25
+ ██ ██ ████ ██ ██
26
+ ██ ██ ██████ ██ ██
27
+ ██ ████ ████ ████████
28
+ ██ ████ ██ ████ ██ ████
29
+ ████ ██████████████
30
+ ██ ██ ████ ████ ██ ██ ████
31
+ ██ ████ ██ ██
32
+ ████████ ██ ██ ████ ██
33
+ ██ ██ ██████ ██████ ██
34
+ ████████████████████████████████
35
+
36
+ dmtx.width
37
+ => 16
38
+
39
+ dmtx.height
40
+ => 16
41
+
42
+ # Generate SVG
43
+ dmtx.to_svg
44
+ => "<svg xmlns= ..."
45
+
46
+ # SVG default options
47
+ dmtx.to_svg(dim: 256, pad: 2, bgcolor: nil, color: '#000')
48
+
49
+ # Generate PNG with given module pixel size
50
+ dmtx.to_png
51
+ => <ChunkyPNG::Image 160x160>
52
+
53
+ # raw PNG data
54
+ dmtx.to_png.to_s
55
+ => "\x89PNG..."
56
+
57
+ # PNG default options
58
+ dmtx.to_png(mod: 8, pad: 2, bgcolor: nil, color: '#000')
59
+
60
+ # Integer representation
61
+ dmtx.to_i
62
+ => 77194835539484717974890203635482091341863808501307723137647139603919014133759
63
+
64
+ # Binary representation
65
+ dmtx.to_i.to_s(2)
66
+ => "1010101010101010101110101000001111010111111101101001101011101001100110010100111010111011111100111001011001001000100010000111010110110001101111001011001001101011110000011111110010101101101010111001101001000000111100101000110110101110011100101111111111111111"
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,439 @@
1
+ # Translated to Ruby from datamatrix-svg
2
+ # https://github.com/datalog/datamatrix-svg
3
+
4
+ require 'builder'
5
+ require 'chunky_png'
6
+
7
+ module Dmtx
8
+ class DataMatrix
9
+ attr_reader :width, :height
10
+
11
+ C40 = [230,
12
+ 31, 0, 0,
13
+ 32, 9, 29,
14
+ 47, 1, 33,
15
+ 57, 9, 44,
16
+ 64, 1, 43,
17
+ 90, 9, 51,
18
+ 95, 1, 69,
19
+ 127, 2, 96,
20
+ 255, 1, 0]
21
+
22
+ TEXT = [239,
23
+ 31, 0, 0,
24
+ 32, 9, 29,
25
+ 47, 1, 33,
26
+ 57, 9, 44,
27
+ 64, 1, 43,
28
+ 90, 2, 64,
29
+ 95, 1, 69,
30
+ 122, 9, 83,
31
+ 127, 2, 96,
32
+ 255, 1, 0]
33
+
34
+ X12 = [238,
35
+ 12, 8, 0,
36
+ 13, 9, 13,
37
+ 31, 8, 0,
38
+ 32, 9, 29,
39
+ 41, 8, 0,
40
+ 42, 9, 41,
41
+ 47, 8, 0,
42
+ 57, 9, 44,
43
+ 64, 8, 0,
44
+ 90, 9, 51,
45
+ 255, 8, 0]
46
+
47
+ def initialize(msg, rect: false)
48
+ @m = []
49
+ @width = 0
50
+ @height = 0
51
+ encode(msg, rect)
52
+ end
53
+
54
+ def inspect
55
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}>"
56
+ end
57
+
58
+ def to_i
59
+ (0..(height - 1)).inject(0) { |i, y| (0..(width - 1)).inject(i) { |j, x| (j << 1) | (bit?(x,y) ? 1 : 0) } }
60
+ end
61
+
62
+ def to_s(pad: 2)
63
+ (0..(height - 1)).inject('') do |s, y|
64
+ (0..(width - 1)).inject(s) { |t, x| t << (bit?(x,y) ? '██' : ' ') } << "\n"
65
+ end
66
+ end
67
+
68
+ def to_svg(dim: 256, pad: 2, bgcolor: nil, color: '#000')
69
+ raise ArgumentError, 'illegal dimension' unless dim > 0
70
+ raise ArgumentError, 'illegal padding' unless pad >= 0
71
+ color ||= '#000'
72
+ path = ''
73
+ sx = width + pad * 2
74
+ sy = height + pad * 2
75
+ mx = [1, 0, 0, 1, pad, pad]
76
+ y = height
77
+ while y > 0
78
+ y -= 1
79
+ d = 0
80
+ x = width
81
+ while x > 0
82
+ x -= 1
83
+ path << "M#{x},#{y}h1v1h-1v-1z" if bit?(x,y)
84
+ end
85
+ end
86
+ builder = Builder::XmlMarkup.new
87
+ builder.tag! 'svg', xmlns: 'http://www.w3.org/2000/svg',
88
+ viewBox: [0, 0, sx, sy].join(' '),
89
+ width: dim * sx / sy,
90
+ height: dim,
91
+ fill: color,
92
+ 'shape-rendering' => 'crispEdges',
93
+ version: '1.1' do |svg|
94
+ svg.path fill: bgcolor, d: "M0,0v#{sy}h#{sx}V0H0Z" if bgcolor
95
+ svg.path transform: "matrix(#{mx.map(&:to_s).join(',')})", d: path
96
+ end
97
+ builder.target!
98
+ end
99
+
100
+ def to_png(mod: 8, pad: 2, bgcolor: nil, color: '#000')
101
+ raise ArgumentError, 'module size too small' unless mod > 0
102
+ raise ArgumentError, 'padding too small' unless pad >= 0
103
+ color = color ? ChunkyPNG::Color(color) : ChunkyPNG::Color('black')
104
+ bgcolor = bgcolor ? ChunkyPNG::Color(bgcolor) : ChunkyPNG::Color::TRANSPARENT
105
+ width_px = mod * (width + pad * 2)
106
+ height_px = mod * (height + pad * 2)
107
+ png = ChunkyPNG::Image.new(width_px, height_px, ChunkyPNG::Color::TRANSPARENT)
108
+ sx = sy = mod * pad
109
+ (0..(height - 1)).each do |y|
110
+ (0..(width - 1)).each do |x|
111
+ png.rect(sx + x * mod, sy + y * mod, sx + (x + 1) * mod, sy + (y + 1) * mod, color, color) if bit?(x,y)
112
+ end
113
+ end
114
+ png
115
+ end
116
+
117
+ private
118
+
119
+ def bit!(x, y)
120
+ @m[y] ||= []
121
+ @m[y][x] = 1
122
+ end
123
+
124
+ def bit?(x, y)
125
+ @m[y] && @m[y][x]
126
+ end
127
+
128
+ def ascii_encode(t)
129
+ bytes, result = t.bytes, []
130
+ while c = bytes.shift
131
+ if !bytes.empty? && c > 47 && c < 58 && bytes.first > 47 && bytes.first < 58
132
+ result << (c - 48) * 10 + bytes.shift + 82
133
+ elsif c > 127
134
+ result << 235
135
+ result << ((c - 127) & 255)
136
+ else
137
+ result << c + 1
138
+ end
139
+ end
140
+ result
141
+ end
142
+
143
+ def base_encode(t)
144
+ bytes, result = t.bytes, [231]
145
+ result << (37 + (bytes.size / 250) & 255) if bytes.size > 250
146
+ result << (bytes.size % 250 + 149 * (result.size + 1) % 255 + 1 & 255)
147
+ bytes.each { |c| result << (c + 149 * (result.size + 1) % 255 + 1 & 255) }
148
+ result
149
+ end
150
+
151
+ def edifact_encode(t)
152
+ bytes = t.bytes
153
+ return [] if bytes.any? { |c| c < 32 || c > 94 }
154
+ l = (bytes.size + 1) & -4
155
+ cw = 0
156
+ result = l > 0 ? [240] : []
157
+ (0..(l - 1)).each do |i|
158
+ ch = i < l - 1 ? bytes[i] : 31
159
+ cw = cw * 64 + (ch & 63)
160
+ if i & 3 == 3
161
+ result << (cw >> 16)
162
+ result << (cw >> 8 & 255)
163
+ result << (cw & 255)
164
+ cw = 0
165
+ end
166
+ end
167
+ return result if l > bytes.size
168
+ result.concat ascii_encode(bytes[(l == 0 ? 0 : l - 1)..-1].to_a.pack('C*'))
169
+ end
170
+
171
+ def text_encode(t, s)
172
+ cc = cw = 0
173
+ bytes, result = t.bytes, [s[0]]
174
+ l = bytes.size
175
+ push = lambda do |v|
176
+ cw = 40 * cw + v
177
+ cc += 1
178
+ if cc == 3
179
+ result << ((cw += 1) >> 8)
180
+ result << (cw & 255)
181
+ cc = cw = 0
182
+ end
183
+ end
184
+ i = 0
185
+ while i < l
186
+ break if 0 == cc && i == l - 1
187
+ ch = bytes[i]
188
+ if ch > 127 && 238 != result[0]
189
+ push.(1)
190
+ push.(30)
191
+ ch -= 128
192
+ end
193
+ j = 1
194
+ j += 3 while ch > s[j]
195
+ x = s[j + 1]
196
+ return [] if 8 == x || (9 == x && 0 == cc && i == l - 1)
197
+ break if x < 5 && cc == 2 && i == l - 1
198
+ push.(x) if x < 5
199
+ push.(ch - s[j + 2])
200
+ i += 1
201
+ end
202
+ push.(0) if 2 == cc && 238 != result[0]
203
+ result << 254
204
+ result.concat ascii_encode(bytes[(i - cc)..-1].to_a.pack('C*')) if cc > 0 || i < l
205
+ result
206
+ end
207
+
208
+ def encodings(msg)
209
+ { ascii: ascii_encode(msg),
210
+ c40: text_encode(msg, C40),
211
+ txt: text_encode(msg, TEXT),
212
+ x12: text_encode(msg, X12),
213
+ edifact: edifact_encode(msg),
214
+ base: base_encode(msg) }
215
+ end
216
+
217
+ def encode(text, rct)
218
+ enc = encodings(text).values.reject(&:empty?).min_by(&:size)
219
+ el = enc.size
220
+ nc = nr = 1
221
+ j = -1
222
+ b = 1
223
+ rs = []
224
+ rc = []
225
+ lg = []
226
+ ex = []
227
+ if rct && el < 50
228
+ k = [16, 7, 28, 11, 24, 14, 32, 18, 32, 24, 44, 28]
229
+ begin
230
+ w = k[j += 1]
231
+ h = 6 + (j & 12)
232
+ l = w * h / 8
233
+ end while l - k[j += 1] < el
234
+ nc = 2 if w > 25
235
+ else
236
+ w = h = 6
237
+ i = 2
238
+ k = [5, 7, 10, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 68, 84, 112, 144, 192, 224, 272, 336, 408, 496, 620]
239
+ begin
240
+ j += 1
241
+ return [0, 0] if j == k.size
242
+ i = 4 + i & 12 if w > 11 * i
243
+ w = h += i
244
+ l = (w * h) >> 3
245
+ end while l - k[j] < el
246
+ nr = nc = 2 * (w / 54) + 2 if w > 27
247
+ b = 2 * (l >> 9) + 2 if l > 255
248
+ end
249
+ s = k[j]
250
+ fw = w / nc
251
+ fh = h / nr
252
+ # first padding
253
+ if el < l - s
254
+ enc[el] = 129
255
+ el += 1
256
+ end
257
+ # more padding
258
+ while el < l - s
259
+ enc[el] = (((149 * (el += 1)) % 253) + 130) % 254 # WTF
260
+ end
261
+ s /= b
262
+ # log / exp table of Galois field
263
+ i, j = 0, 1
264
+ while i < 255
265
+ ex[i] = j
266
+ lg[j] = i
267
+ j += j
268
+ j ^= 301 if j > 255
269
+ i += 1
270
+ end
271
+ # RS generator polynomial
272
+ rs[s], i = 0, 1
273
+ while i <= s
274
+ j = s - i
275
+ rs[j] = 1
276
+ while j < s
277
+ rs[j] = rs[j + 1] ^ ex[(lg[rs[j]] + i) % 255]
278
+ j += 1
279
+ end
280
+ i += 1
281
+ end
282
+ # RS correction data for each block
283
+ c = 0
284
+ while c < b
285
+ i = 0
286
+ while i <= s
287
+ rc[i] = 0
288
+ i += 1
289
+ end
290
+ i = c
291
+ while i < el
292
+ j = 0
293
+ x = rc[0] ^ enc[i]
294
+ while j < s
295
+ rc[j] = rc[j + 1] ^ (x.nonzero? ? ex[(lg[rs[j]] + lg[x]) % 255] : 0)
296
+ j += 1
297
+ end
298
+ i += b
299
+ end
300
+ # interleaved correction data
301
+ i = 0
302
+ while i < s
303
+ enc[el + c + i * b] = rc[i]
304
+ i += 1
305
+ end
306
+ c += 1
307
+ end
308
+ # layout perimeter finder pattern
309
+ # horizontal
310
+ i = 0
311
+ while i < h + 2 * nr
312
+ j = 0
313
+ while j < w + 2 * nc
314
+ bit!(j, i + fh + 1)
315
+ bit!(j, i) if j & 1 == 0
316
+ j += 1
317
+ end
318
+ i += fh + 2
319
+ end
320
+ # vertical
321
+ i = 0
322
+ while i < w + 2 * nc
323
+ j = 0
324
+ while j < h
325
+ bit!(i, j + (j / fh) * 2 + 1)
326
+ bit!(i + fw + 1, j + (j / fh) * 2) if j & 1 == 1
327
+ j += 1
328
+ end
329
+ i += fw + 2
330
+ end
331
+ s, c, r = 2, 0, 4
332
+ b = [0, 0, -1, 0, -2, 0, 0, -1, -1, -1, -2, -1, -1, -2, -2, -2]
333
+ # diagonal steps
334
+ i = 0
335
+ while i < l
336
+ if r == h - 3 && c == -1
337
+ # corner A layout
338
+ k = [w, 6 - h,
339
+ w, 5 - h,
340
+ w, 4 - h,
341
+ w, 3 - h,
342
+ w - 1, 3 - h,
343
+ 3, 2,
344
+ 2, 2,
345
+ 1, 2]
346
+ elsif r == h + 1 && c == 1 && w & 7 == 0 && h & 7 == 6
347
+ # corner D layout
348
+ k = [w - 2, -h,
349
+ w - 3, -h,
350
+ w - 4, -h,
351
+ w - 2, -1 - h,
352
+ w - 3, -1 - h,
353
+ w - 4, -1 - h,
354
+ w - 2, -2,
355
+ -1, -2]
356
+ else
357
+ if r == 0 && c == w - 2 && (w & 3).nonzero?
358
+ # corner B: omit upper left
359
+ r -= s
360
+ c += s
361
+ next
362
+ end
363
+ if r < 0 || c >= w || r >= h || c < 0
364
+ # outside
365
+ s = -s
366
+ r += 2 + s / 2
367
+ c += 2 - s / 2
368
+ while r < 0 || c >= w || r >= h || c < 0
369
+ r -= s
370
+ c += s
371
+ end
372
+ end
373
+ if r == h - 2 && c == 0 && (w & 3).nonzero?
374
+ # corner B layout
375
+ k = [w - 1, 3 - h,
376
+ w - 1, 2 - h,
377
+ w - 2, 2 - h,
378
+ w - 3, 2 - h,
379
+ w - 4, 2 - h,
380
+ 0, 1,
381
+ 0, 0,
382
+ 0, -1]
383
+ elsif r == h - 2 && c == 0 && w & 7 == 4
384
+ # corner C layout
385
+ k = [w - 1, 5 - h,
386
+ w - 1, 4 - h,
387
+ w - 1, 3 - h,
388
+ w - 1, 2 - h,
389
+ w - 2, 2 - h,
390
+ 0, 1,
391
+ 0, 0,
392
+ 0, -1]
393
+ elsif r == 1 && c == w - 1 && w & 7 == 0 && h & 7 == 6
394
+ # omit corner D
395
+ r -= s
396
+ c += s
397
+ next
398
+ else
399
+ # nominal L-shape layout
400
+ k = b
401
+ end
402
+ end
403
+ # layout each bit
404
+ el = enc[i]
405
+ i += 1
406
+ j = 0
407
+ while el > 0
408
+ if (el & 1).nonzero?
409
+ x = c + k[j]
410
+ y = r + k[j + 1]
411
+ # wrap around
412
+ if x < 0
413
+ x += w
414
+ y += 4 - ((w + 4) & 7)
415
+ end
416
+ if y < 0
417
+ y += h
418
+ x += 4 - ((h + 4) & 7)
419
+ end
420
+ # region gap
421
+ bit!(x + 2 * (x / fw) + 1, y + 2 * (y / fh) + 1)
422
+ end
423
+ j += 2
424
+ el >>= 1
425
+ end
426
+ r -= s
427
+ c += s
428
+ end
429
+ # unfilled corner
430
+ i = w
431
+ while (i & 3).nonzero?
432
+ bit!(i, i)
433
+ i -= 1
434
+ end
435
+ @width = w + 2 * nc
436
+ @height = h + 2 * nr
437
+ end
438
+ end
439
+ end
@@ -0,0 +1,3 @@
1
+ module Dmtx
2
+ VERSION = '0.1.0'
3
+ end
data/lib/dmtx.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'dmtx/version'
2
+ require_relative 'dmtx/data_matrix'
3
+
4
+ module Dmtx
5
+ class Error < StandardError; end
6
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dmtx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: builder
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: chunky_png
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - mtgrosser@gmx.net
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - lib/dmtx.rb
53
+ - lib/dmtx/data_matrix.rb
54
+ - lib/dmtx/version.rb
55
+ homepage: https://github.com/mtgrosser/dmtx
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.4.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.3
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Pure Ruby Datamatrix Generator
78
+ test_files: []