dmtx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []