qrcode 0.0.1 → 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 +7 -0
- data/.github/copilot-instructions.md +20 -0
- data/.github/workflows/documentation-coverage.yaml +24 -0
- data/.github/workflows/documentation.yaml +57 -0
- data/.github/workflows/rubocop.yaml +21 -0
- data/.github/workflows/test-coverage.yaml +58 -0
- data/.github/workflows/test-external.yaml +33 -0
- data/.github/workflows/test.yaml +47 -0
- data/fixtures/qrcode/sample_data.rb +31 -0
- data/lib/qrcode/encoder/bit_buffer.rb +90 -0
- data/lib/qrcode/encoder/code.rb +436 -0
- data/lib/qrcode/encoder/constants.rb +75 -0
- data/lib/qrcode/encoder/error_correction_block.rb +318 -0
- data/lib/qrcode/encoder/math.rb +50 -0
- data/lib/qrcode/encoder/polynomial.rb +69 -0
- data/lib/qrcode/encoder/segment.rb +190 -0
- data/lib/qrcode/encoder/util.rb +269 -0
- data/lib/qrcode/encoder.rb +13 -0
- data/lib/qrcode/output/svg.rb +46 -0
- data/lib/qrcode/output/text.rb +62 -0
- data/lib/qrcode/version.rb +13 -2
- data/lib/qrcode.rb +23 -3
- data/license.md +54 -0
- data/readme.md +60 -0
- data/releases.md +14 -0
- metadata +71 -62
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/README.md +0 -29
- data/Rakefile +0 -1
- data/qrcode.gemspec +0 -35
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008-2021, by Duncan Robertson.
|
5
|
+
# Copyright, 2011, by Gioele Barabucci.
|
6
|
+
# Copyright, 2015, by Björn Blomqvist.
|
7
|
+
# Copyright, 2025, by Samuel Williams.
|
8
|
+
|
9
|
+
require_relative "constants"
|
10
|
+
|
11
|
+
module QRCode
|
12
|
+
module Encoder
|
13
|
+
class ErrorCorrectionBlock
|
14
|
+
attr_reader :data_count, :total_count
|
15
|
+
|
16
|
+
def initialize(total_count, data_count)
|
17
|
+
@total_count = total_count
|
18
|
+
@data_count = data_count
|
19
|
+
end
|
20
|
+
|
21
|
+
# http://www.thonky.com/qr-code-tutorial/error-correction-table/
|
22
|
+
# Table of [block_count, total_codewords, data_codewords] for each version and error level
|
23
|
+
TABLE = [
|
24
|
+
# The order within each group is:
|
25
|
+
# L
|
26
|
+
# M
|
27
|
+
# Q
|
28
|
+
# H
|
29
|
+
|
30
|
+
# 1
|
31
|
+
[1, 26, 19],
|
32
|
+
[1, 26, 16],
|
33
|
+
[1, 26, 13],
|
34
|
+
[1, 26, 9],
|
35
|
+
|
36
|
+
# 2
|
37
|
+
[1, 44, 34],
|
38
|
+
[1, 44, 28],
|
39
|
+
[1, 44, 22],
|
40
|
+
[1, 44, 16],
|
41
|
+
|
42
|
+
# 3
|
43
|
+
[1, 70, 55],
|
44
|
+
[1, 70, 44],
|
45
|
+
[2, 35, 17],
|
46
|
+
[2, 35, 13],
|
47
|
+
|
48
|
+
# 4
|
49
|
+
[1, 100, 80],
|
50
|
+
[2, 50, 32],
|
51
|
+
[2, 50, 24],
|
52
|
+
[4, 25, 9],
|
53
|
+
|
54
|
+
# 5
|
55
|
+
[1, 134, 108],
|
56
|
+
[2, 67, 43],
|
57
|
+
[2, 33, 15, 2, 34, 16],
|
58
|
+
[2, 33, 11, 2, 34, 12],
|
59
|
+
|
60
|
+
# 6
|
61
|
+
[2, 86, 68],
|
62
|
+
[4, 43, 27],
|
63
|
+
[4, 43, 19],
|
64
|
+
[4, 43, 15],
|
65
|
+
|
66
|
+
# 7
|
67
|
+
[2, 98, 78],
|
68
|
+
[4, 49, 31],
|
69
|
+
[2, 32, 14, 4, 33, 15],
|
70
|
+
[4, 39, 13, 1, 40, 14],
|
71
|
+
|
72
|
+
# 8
|
73
|
+
[2, 121, 97],
|
74
|
+
[2, 60, 38, 2, 61, 39],
|
75
|
+
[4, 40, 18, 2, 41, 19],
|
76
|
+
[4, 40, 14, 2, 41, 15],
|
77
|
+
|
78
|
+
# 9
|
79
|
+
[2, 146, 116],
|
80
|
+
[3, 58, 36, 2, 59, 37],
|
81
|
+
[4, 36, 16, 4, 37, 17],
|
82
|
+
[4, 36, 12, 4, 37, 13],
|
83
|
+
|
84
|
+
# 10
|
85
|
+
[2, 86, 68, 2, 87, 69],
|
86
|
+
[4, 69, 43, 1, 70, 44],
|
87
|
+
[6, 43, 19, 2, 44, 20],
|
88
|
+
[6, 43, 15, 2, 44, 16],
|
89
|
+
|
90
|
+
# 11
|
91
|
+
[4, 101, 81],
|
92
|
+
[1, 80, 50, 4, 81, 51],
|
93
|
+
[4, 50, 22, 4, 51, 23],
|
94
|
+
[3, 36, 12, 8, 37, 13],
|
95
|
+
|
96
|
+
# 12
|
97
|
+
[2, 116, 92, 2, 117, 93],
|
98
|
+
[6, 58, 36, 2, 59, 37],
|
99
|
+
[4, 46, 20, 6, 47, 21],
|
100
|
+
[7, 42, 14, 4, 43, 15],
|
101
|
+
|
102
|
+
# 13
|
103
|
+
[4, 133, 107],
|
104
|
+
[8, 59, 37, 1, 60, 38],
|
105
|
+
[8, 44, 20, 4, 45, 21],
|
106
|
+
[12, 33, 11, 4, 34, 12],
|
107
|
+
|
108
|
+
# 14
|
109
|
+
[3, 145, 115, 1, 146, 116],
|
110
|
+
[4, 64, 40, 5, 65, 41],
|
111
|
+
[11, 36, 16, 5, 37, 17],
|
112
|
+
[11, 36, 12, 5, 37, 13],
|
113
|
+
|
114
|
+
# 15
|
115
|
+
[5, 109, 87, 1, 110, 88],
|
116
|
+
[5, 65, 41, 5, 66, 42],
|
117
|
+
[5, 54, 24, 7, 55, 25],
|
118
|
+
[11, 36, 12, 7, 37, 13],
|
119
|
+
|
120
|
+
# 16
|
121
|
+
[5, 122, 98, 1, 123, 99],
|
122
|
+
[7, 73, 45, 3, 74, 46],
|
123
|
+
[15, 43, 19, 2, 44, 20],
|
124
|
+
[3, 45, 15, 13, 46, 16],
|
125
|
+
|
126
|
+
# 17
|
127
|
+
[1, 135, 107, 5, 136, 108],
|
128
|
+
[10, 74, 46, 1, 75, 47],
|
129
|
+
[1, 50, 22, 15, 51, 23],
|
130
|
+
[2, 42, 14, 17, 43, 15],
|
131
|
+
|
132
|
+
# 18
|
133
|
+
[5, 150, 120, 1, 151, 121],
|
134
|
+
[9, 69, 43, 4, 70, 44],
|
135
|
+
[17, 50, 22, 1, 51, 23],
|
136
|
+
[2, 42, 14, 19, 43, 15],
|
137
|
+
|
138
|
+
# 19
|
139
|
+
[3, 141, 113, 4, 142, 114],
|
140
|
+
[3, 70, 44, 11, 71, 45],
|
141
|
+
[17, 47, 21, 4, 48, 22],
|
142
|
+
[9, 39, 13, 16, 40, 14],
|
143
|
+
|
144
|
+
# 20
|
145
|
+
[3, 135, 107, 5, 136, 108],
|
146
|
+
[3, 67, 41, 13, 68, 42],
|
147
|
+
[15, 54, 24, 5, 55, 25],
|
148
|
+
[15, 43, 15, 10, 44, 16],
|
149
|
+
|
150
|
+
# 21
|
151
|
+
[4, 144, 116, 4, 145, 117],
|
152
|
+
[17, 68, 42],
|
153
|
+
[17, 50, 22, 6, 51, 23],
|
154
|
+
[19, 46, 16, 6, 47, 17],
|
155
|
+
|
156
|
+
# 22
|
157
|
+
[2, 139, 111, 7, 140, 112],
|
158
|
+
[17, 74, 46],
|
159
|
+
[7, 54, 24, 16, 55, 25],
|
160
|
+
[34, 37, 13],
|
161
|
+
|
162
|
+
# 23
|
163
|
+
[4, 151, 121, 5, 152, 122],
|
164
|
+
[4, 75, 47, 14, 76, 48],
|
165
|
+
[11, 54, 24, 14, 55, 25],
|
166
|
+
[16, 45, 15, 14, 46, 16],
|
167
|
+
|
168
|
+
# 24
|
169
|
+
[6, 147, 117, 4, 148, 118],
|
170
|
+
[6, 73, 45, 14, 74, 46],
|
171
|
+
[11, 54, 24, 16, 55, 25],
|
172
|
+
[30, 46, 16, 2, 47, 17],
|
173
|
+
|
174
|
+
# 25
|
175
|
+
[8, 132, 106, 4, 133, 107],
|
176
|
+
[8, 75, 47, 13, 76, 48],
|
177
|
+
[7, 54, 24, 22, 55, 25],
|
178
|
+
[22, 45, 15, 13, 46, 16],
|
179
|
+
|
180
|
+
# 26
|
181
|
+
[10, 142, 114, 2, 143, 115],
|
182
|
+
[19, 74, 46, 4, 75, 47],
|
183
|
+
[28, 50, 22, 6, 51, 23],
|
184
|
+
[33, 46, 16, 4, 47, 17],
|
185
|
+
|
186
|
+
# 27
|
187
|
+
[8, 152, 122, 4, 153, 123],
|
188
|
+
[22, 73, 45, 3, 74, 46],
|
189
|
+
[8, 53, 23, 26, 54, 24],
|
190
|
+
[12, 45, 15, 28, 46, 16],
|
191
|
+
|
192
|
+
# 28
|
193
|
+
[3, 147, 117, 10, 148, 118],
|
194
|
+
[3, 73, 45, 23, 74, 46],
|
195
|
+
[4, 54, 24, 31, 55, 25],
|
196
|
+
[11, 45, 15, 31, 46, 16],
|
197
|
+
|
198
|
+
# 29
|
199
|
+
[7, 146, 116, 7, 147, 117],
|
200
|
+
[21, 73, 45, 7, 74, 46],
|
201
|
+
[1, 53, 23, 37, 54, 24],
|
202
|
+
[19, 45, 15, 26, 46, 16],
|
203
|
+
|
204
|
+
# 30
|
205
|
+
[5, 145, 115, 10, 146, 116],
|
206
|
+
[19, 75, 47, 10, 76, 48],
|
207
|
+
[15, 54, 24, 25, 55, 25],
|
208
|
+
[23, 45, 15, 25, 46, 16],
|
209
|
+
|
210
|
+
# 31
|
211
|
+
[13, 145, 115, 3, 146, 116],
|
212
|
+
[2, 74, 46, 29, 75, 47],
|
213
|
+
[42, 54, 24, 1, 55, 25],
|
214
|
+
[23, 45, 15, 28, 46, 16],
|
215
|
+
|
216
|
+
# 32
|
217
|
+
[17, 145, 115],
|
218
|
+
[10, 74, 46, 23, 75, 47],
|
219
|
+
[10, 54, 24, 35, 55, 25],
|
220
|
+
[19, 45, 15, 35, 46, 16],
|
221
|
+
|
222
|
+
# 33
|
223
|
+
[17, 145, 115, 1, 146, 116],
|
224
|
+
[14, 74, 46, 21, 75, 47],
|
225
|
+
[29, 54, 24, 19, 55, 25],
|
226
|
+
[11, 45, 15, 46, 46, 16],
|
227
|
+
|
228
|
+
# 34
|
229
|
+
[13, 145, 115, 6, 146, 116],
|
230
|
+
[14, 74, 46, 23, 75, 47],
|
231
|
+
[44, 54, 24, 7, 55, 25],
|
232
|
+
[59, 46, 16, 1, 47, 17],
|
233
|
+
|
234
|
+
# 35
|
235
|
+
[12, 151, 121, 7, 152, 122],
|
236
|
+
[12, 75, 47, 26, 76, 48],
|
237
|
+
[39, 54, 24, 14, 55, 25],
|
238
|
+
[22, 45, 15, 41, 46, 16],
|
239
|
+
|
240
|
+
# 36
|
241
|
+
[6, 151, 121, 14, 152, 122],
|
242
|
+
[6, 75, 47, 34, 76, 48],
|
243
|
+
[46, 54, 24, 10, 55, 25],
|
244
|
+
[2, 45, 15, 64, 46, 16],
|
245
|
+
|
246
|
+
# 37
|
247
|
+
[17, 152, 122, 4, 153, 123],
|
248
|
+
[29, 74, 46, 14, 75, 47],
|
249
|
+
[49, 54, 24, 10, 55, 25],
|
250
|
+
[24, 45, 15, 46, 46, 16],
|
251
|
+
|
252
|
+
# 38
|
253
|
+
[4, 152, 122, 18, 153, 123],
|
254
|
+
[13, 74, 46, 32, 75, 47],
|
255
|
+
[48, 54, 24, 14, 55, 25],
|
256
|
+
[42, 45, 15, 32, 46, 16],
|
257
|
+
|
258
|
+
# 39
|
259
|
+
[20, 147, 117, 4, 148, 118],
|
260
|
+
[40, 75, 47, 7, 76, 48],
|
261
|
+
[43, 54, 24, 22, 55, 25],
|
262
|
+
[10, 45, 15, 67, 46, 16],
|
263
|
+
|
264
|
+
# 40
|
265
|
+
[19, 148, 118, 6, 149, 119],
|
266
|
+
[18, 75, 47, 31, 76, 48],
|
267
|
+
[34, 54, 24, 34, 55, 25],
|
268
|
+
[20, 45, 15, 61, 46, 16]
|
269
|
+
].freeze
|
270
|
+
|
271
|
+
# Create error correction blocks for given version and level
|
272
|
+
# @parameter version [Integer] QR code version (1-40)
|
273
|
+
# @parameter level [Integer] Error correction level
|
274
|
+
# @return [Array<ErrorCorrectionBlock>] Array of error correction blocks
|
275
|
+
def self.for(version, level)
|
276
|
+
entry = table_entry_for(version, level)
|
277
|
+
|
278
|
+
if entry.nil?
|
279
|
+
raise RuntimeError, "Invalid error correction configuration: version=#{version}, level=#{level}"
|
280
|
+
end
|
281
|
+
|
282
|
+
blocks = []
|
283
|
+
groups = entry.size / 3
|
284
|
+
|
285
|
+
(0...groups).each do |i|
|
286
|
+
count = entry[i * 3 + 0]
|
287
|
+
total_count = entry[i * 3 + 1]
|
288
|
+
data_count = entry[i * 3 + 2]
|
289
|
+
|
290
|
+
count.times do
|
291
|
+
blocks << ErrorCorrectionBlock.new(total_count, data_count)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
blocks
|
296
|
+
end
|
297
|
+
|
298
|
+
# Get table entry for specific version and error correction level
|
299
|
+
# @parameter version [Integer] QR code version (1-40)
|
300
|
+
# @parameter level [Integer] Error correction level
|
301
|
+
# @return [Array] Table entry [block_count, total_codewords, data_codewords, ...]
|
302
|
+
def self.table_entry_for(version, level)
|
303
|
+
return nil unless version.between?(1, 40)
|
304
|
+
|
305
|
+
case level
|
306
|
+
when ERROR_CORRECTION_LEVEL[:l]
|
307
|
+
TABLE[(version - 1) * 4 + 0]
|
308
|
+
when ERROR_CORRECTION_LEVEL[:m]
|
309
|
+
TABLE[(version - 1) * 4 + 1]
|
310
|
+
when ERROR_CORRECTION_LEVEL[:q]
|
311
|
+
TABLE[(version - 1) * 4 + 2]
|
312
|
+
when ERROR_CORRECTION_LEVEL[:h]
|
313
|
+
TABLE[(version - 1) * 4 + 3]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008-2025, by Duncan Robertson.
|
5
|
+
# Copyright, 2025, by Samuel Williams.
|
6
|
+
|
7
|
+
module QRCode
|
8
|
+
module Encoder
|
9
|
+
class Math
|
10
|
+
module_eval do
|
11
|
+
exp_table = Array.new(256)
|
12
|
+
log_table = Array.new(256)
|
13
|
+
|
14
|
+
8.times do |i|
|
15
|
+
exp_table[i] = 1 << i
|
16
|
+
end
|
17
|
+
|
18
|
+
(8...256).each do |i|
|
19
|
+
exp_table[i] = exp_table[i - 4] ^ exp_table[i - 5] ^ exp_table[i - 6] ^ exp_table[i - 8]
|
20
|
+
end
|
21
|
+
|
22
|
+
255.times do |i|
|
23
|
+
log_table[exp_table[i]] = i
|
24
|
+
end
|
25
|
+
|
26
|
+
const_set(:EXP_TABLE, exp_table).freeze
|
27
|
+
const_set(:LOG_TABLE, log_table).freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def glog(n)
|
32
|
+
raise RuntimeError, "glog(#{n})" if n < 1
|
33
|
+
LOG_TABLE[n]
|
34
|
+
end
|
35
|
+
|
36
|
+
def gexp(n)
|
37
|
+
while n < 0
|
38
|
+
n += 255
|
39
|
+
end
|
40
|
+
|
41
|
+
while n >= 256
|
42
|
+
n -= 255
|
43
|
+
end
|
44
|
+
|
45
|
+
EXP_TABLE[n]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008-2021, by Duncan Robertson.
|
5
|
+
# Copyright, 2025, by Samuel Williams.
|
6
|
+
|
7
|
+
require_relative "math"
|
8
|
+
|
9
|
+
module QRCode
|
10
|
+
module Encoder
|
11
|
+
class Polynomial
|
12
|
+
def initialize(num, shift)
|
13
|
+
raise RuntimeError, "#{num.size}/#{shift}" if num.empty?
|
14
|
+
offset = 0
|
15
|
+
|
16
|
+
while offset < num.size && num[offset] == 0
|
17
|
+
offset += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
@num = Array.new(num.size - offset + shift)
|
21
|
+
|
22
|
+
(0...num.size - offset).each do |i|
|
23
|
+
@num[i] = num[i + offset]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(index)
|
28
|
+
@num[index]
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_length
|
32
|
+
@num.size
|
33
|
+
end
|
34
|
+
|
35
|
+
def multiply(e)
|
36
|
+
num = Array.new(get_length + e.get_length - 1)
|
37
|
+
|
38
|
+
(0...get_length).each do |i|
|
39
|
+
(0...e.get_length).each do |j|
|
40
|
+
tmp = num[i + j].nil? ? 0 : num[i + j]
|
41
|
+
num[i + j] = tmp ^ Encoder::Math.gexp(Encoder::Math.glog(get(i)) + Encoder::Math.glog(e.get(j)))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Encoder::Polynomial.new(num, 0)
|
46
|
+
end
|
47
|
+
|
48
|
+
def mod(e)
|
49
|
+
if get_length - e.get_length < 0
|
50
|
+
return self
|
51
|
+
end
|
52
|
+
|
53
|
+
ratio = Encoder::Math.glog(get(0)) - Encoder::Math.glog(e.get(0))
|
54
|
+
num = Array.new(get_length)
|
55
|
+
|
56
|
+
(0...get_length).each do |i|
|
57
|
+
num[i] = get(i)
|
58
|
+
end
|
59
|
+
|
60
|
+
(0...e.get_length).each do |i|
|
61
|
+
tmp = num[i].nil? ? 0 : num[i]
|
62
|
+
num[i] = tmp ^ Encoder::Math.gexp(Encoder::Math.glog(e.get(i)) + ratio)
|
63
|
+
end
|
64
|
+
|
65
|
+
Encoder::Polynomial.new(num, 0).mod(e)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2021, by Sam Sayer.
|
5
|
+
# Copyright, 2021-2025, by Duncan Robertson.
|
6
|
+
# Copyright, 2025, by Samuel Williams.
|
7
|
+
|
8
|
+
require_relative "constants"
|
9
|
+
require_relative "util"
|
10
|
+
|
11
|
+
module QRCode
|
12
|
+
module Encoder
|
13
|
+
# Base segment class - defaults to binary (8-bit byte) encoding
|
14
|
+
class Segment
|
15
|
+
attr_reader :data
|
16
|
+
|
17
|
+
def initialize(data)
|
18
|
+
@data = data.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def mode
|
22
|
+
:mode_8bit_byte
|
23
|
+
end
|
24
|
+
|
25
|
+
def bit_size(version)
|
26
|
+
4 + header_size(version) + content_size
|
27
|
+
end
|
28
|
+
|
29
|
+
def header_size(version)
|
30
|
+
Encoder::Util.get_length_in_bits(MODE[mode], version)
|
31
|
+
end
|
32
|
+
|
33
|
+
def content_size
|
34
|
+
@data.bytesize * 8
|
35
|
+
end
|
36
|
+
|
37
|
+
def write(buffer)
|
38
|
+
buffer.byte_encoding_start(@data.bytesize)
|
39
|
+
|
40
|
+
@data.each_byte do |b|
|
41
|
+
buffer.put(b, 8)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Factory method to build segments from various data types
|
46
|
+
# @parameter data [String, Array, Segment] The data to encode
|
47
|
+
# @parameter mode [Symbol] Encoding mode (:auto, :number, :alphanumeric, :byte_8bit)
|
48
|
+
# @return [Array<Segment>] Array of segments
|
49
|
+
def self.build(data, mode: :auto)
|
50
|
+
case data
|
51
|
+
when String
|
52
|
+
[create_segment_for_data(data, mode)]
|
53
|
+
when Array
|
54
|
+
data.map do |item|
|
55
|
+
case item
|
56
|
+
when Hash
|
57
|
+
create_segment_for_data(item[:data], item[:mode] || :auto)
|
58
|
+
when String
|
59
|
+
create_segment_for_data(item, mode)
|
60
|
+
when Segment
|
61
|
+
item
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Array elements must be Strings, Hashes with :data key, or Segments"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
when Segment
|
67
|
+
[data]
|
68
|
+
else
|
69
|
+
raise ArgumentError, "data must be a String, Segment, or Array"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private_class_method def self.create_segment_for_data(data, mode)
|
74
|
+
case mode
|
75
|
+
when :auto
|
76
|
+
detect_optimal_segment_class(data).new(data)
|
77
|
+
when :number, :numeric
|
78
|
+
NumericSegment.new(data)
|
79
|
+
when :alphanumeric, :alpha_numk
|
80
|
+
AlphanumericSegment.new(data)
|
81
|
+
when :byte_8bit, :binary
|
82
|
+
Segment.new(data)
|
83
|
+
else
|
84
|
+
raise ArgumentError, "Unknown mode: #{mode}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private_class_method def self.detect_optimal_segment_class(data)
|
89
|
+
if NumericSegment.valid_data?(data)
|
90
|
+
NumericSegment
|
91
|
+
elsif AlphanumericSegment.valid_data?(data)
|
92
|
+
AlphanumericSegment
|
93
|
+
else
|
94
|
+
Segment
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Numeric segment - optimized for numeric data (0-9)
|
100
|
+
class NumericSegment < Segment
|
101
|
+
def initialize(data)
|
102
|
+
data_str = data.to_s
|
103
|
+
unless self.class.valid_data?(data_str)
|
104
|
+
raise ArgumentError, "Not a numeric string `#{data_str}`"
|
105
|
+
end
|
106
|
+
super(data_str)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.valid_data?(data)
|
110
|
+
data.to_s.match?(/\A\d+\z/)
|
111
|
+
end
|
112
|
+
|
113
|
+
def mode
|
114
|
+
:mode_number
|
115
|
+
end
|
116
|
+
|
117
|
+
def content_size
|
118
|
+
data_length = @data.length
|
119
|
+
case data_length % 3
|
120
|
+
when 0
|
121
|
+
(data_length / 3) * 10
|
122
|
+
when 1
|
123
|
+
((data_length / 3) * 10) + 4
|
124
|
+
when 2
|
125
|
+
((data_length / 3) * 10) + 7
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def write(buffer)
|
130
|
+
buffer.numeric_encoding_start(@data.size)
|
131
|
+
|
132
|
+
@data.scan(/\d{1,3}/).each do |group|
|
133
|
+
case group.length
|
134
|
+
when 3
|
135
|
+
buffer.put(group.to_i, 10)
|
136
|
+
when 2
|
137
|
+
buffer.put(group.to_i, 7)
|
138
|
+
when 1
|
139
|
+
buffer.put(group.to_i, 4)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Alphanumeric segment - optimized for alphanumeric data
|
146
|
+
class AlphanumericSegment < Segment
|
147
|
+
ALPHANUMERIC = [
|
148
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
|
149
|
+
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "$",
|
150
|
+
"%", "*", "+", "-", ".", "/", ":"
|
151
|
+
].freeze
|
152
|
+
|
153
|
+
def initialize(data)
|
154
|
+
data_str = data.to_s
|
155
|
+
unless self.class.valid_data?(data_str)
|
156
|
+
raise ArgumentError, "Not an alphanumeric string `#{data_str}`"
|
157
|
+
end
|
158
|
+
super(data_str)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.valid_data?(data)
|
162
|
+
(data.to_s.chars - ALPHANUMERIC).empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
def mode
|
166
|
+
:mode_alpha_numk
|
167
|
+
end
|
168
|
+
|
169
|
+
def content_size
|
170
|
+
(@data.size / 2.0).ceil * 11
|
171
|
+
end
|
172
|
+
|
173
|
+
def write(buffer)
|
174
|
+
buffer.alphanumeric_encoding_start(@data.size)
|
175
|
+
|
176
|
+
@data.size.times do |i|
|
177
|
+
if i % 2 == 0
|
178
|
+
if i == (@data.size - 1)
|
179
|
+
value = ALPHANUMERIC.index(@data[i])
|
180
|
+
buffer.put(value, 6)
|
181
|
+
else
|
182
|
+
value = (ALPHANUMERIC.index(@data[i]) * 45) + ALPHANUMERIC.index(@data[i + 1])
|
183
|
+
buffer.put(value, 11)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|