qr_code_maker 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: 883092da5fc7735a8babef3fe6bb57b630147a3ed7c4ce7c21e3e095b829c9e8
4
+ data.tar.gz: 26869583a0e07a791bd552a2675000e5b33cf00c4217e08e82cca4208f7bc196
5
+ SHA512:
6
+ metadata.gz: 44d9ea7bd6708381e7445a180956273309269b50d5b9ff54ce8c01736c8c48de030fb4ff6a3a154ad195f3c27d73ca8a488d0d6ccf58c861fe5e2a316b90d9da
7
+ data.tar.gz: 2b9e34abdbae056cf89512be3ebbb9216832db62af0a9d2f9b063bc019e63e864607cf02ea287c10ec968c08052cec14ce97b6eca3be1757d91dd08b80a53e53
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # qr_code_maker
2
+
3
+ _A simple, zero-dependency, standard-library-only QR code generator for Ruby._
4
+
5
+ `qr_code_maker` is written in pure Ruby, with no external gem dependencies. It supports the full QR Code specification (Versions 1-40, ECC levels L, M, Q, H) and generates vector SVG strings or terminal ASCII art.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'qr_code_maker'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```bash
24
+ $ gem install qr_code_maker
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### High-Level API (Quickest)
30
+
31
+ You can generate QR code SVG strings or ASCII prints directly from a URL or text string:
32
+
33
+ ```ruby
34
+ require 'qr_code_maker'
35
+
36
+ # Generate a raw SVG string
37
+ svg = QrCodeMaker.generate_svg("https://github.com")
38
+
39
+ # Generate an ASCII terminal representation
40
+ ascii = QrCodeMaker.generate_ascii("https://github.com")
41
+ puts ascii
42
+ ```
43
+
44
+ ### Object-Oriented API
45
+
46
+ If you need more control, you can instantiate a `QRCode` object:
47
+
48
+ ```ruby
49
+ require 'qr_code_maker'
50
+
51
+ # Create the QR Code object with custom Error Correction Level (:l, :m, :q, or :h)
52
+ qr = QrCodeMaker::QRCode.new("https://github.com", level: :m)
53
+
54
+ # Access metadata
55
+ puts qr.version # The auto-chosen QR version (1-40)
56
+ puts qr.module_count # Dimension of the grid (width/height)
57
+ puts qr.checked?(0, 0) # Check if the module at (row, col) is dark (true) or light (false)
58
+
59
+ # Render to SVG with options
60
+ svg = qr.as_svg(
61
+ color: "black", # Foreground color of the modules
62
+ bg_color: "white", # Background color (set to nil or false for transparent)
63
+ module_size: 10, # Size of each module in pixels
64
+ margin: 4 # Number of modules for quiet zone margin
65
+ )
66
+ ```
67
+
68
+ ## Running Tests
69
+
70
+ Run the automated test suite with:
71
+
72
+ ```bash
73
+ ruby -Ilib -Itest test/test_qr_code_maker.rb
74
+ ```
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,1031 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QrCodeMaker
4
+ QRMODE = {
5
+ mode_number: 1 << 0, # 1
6
+ mode_alpha_numk: 1 << 1, # 2
7
+ mode_8bit_byte: 1 << 2 # 4
8
+ }.freeze
9
+
10
+ QRMODE_NAME = {
11
+ number: :mode_number,
12
+ alphanumeric: :mode_alpha_numk,
13
+ byte_8bit: :mode_8bit_byte,
14
+ multi: :mode_multi
15
+ }.freeze
16
+
17
+ QRERRORCORRECTLEVEL = {
18
+ l: 1,
19
+ m: 0,
20
+ q: 3,
21
+ h: 2
22
+ }.freeze
23
+
24
+ QRMASKPATTERN = {
25
+ pattern000: 0,
26
+ pattern001: 1,
27
+ pattern010: 2,
28
+ pattern011: 3,
29
+ pattern100: 4,
30
+ pattern101: 5,
31
+ pattern110: 6,
32
+ pattern111: 7
33
+ }.freeze
34
+
35
+ QRMASKCOMPUTATIONS = [
36
+ proc { |i, j| (i + j) % 2 == 0 },
37
+ proc { |i, j| i % 2 == 0 },
38
+ proc { |i, j| j % 3 == 0 },
39
+ proc { |i, j| (i + j) % 3 == 0 },
40
+ proc { |i, j| ((i / 2).floor + (j / 3).floor) % 2 == 0 },
41
+ proc { |i, j| (i * j) % 2 + (i * j) % 3 == 0 },
42
+ proc { |i, j| ((i * j) % 2 + (i * j) % 3) % 2 == 0 },
43
+ proc { |i, j| ((i * j) % 3 + (i + j) % 2) % 2 == 0 }
44
+ ].freeze
45
+
46
+ QRPOSITIONPATTERNLENGTH = (7 + 1) * 2 + 1
47
+ QRFORMATINFOLENGTH = 15
48
+
49
+ QRMAXBITS = {
50
+ l: [152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184,
51
+ 4712, 5176, 5768, 6360, 6888, 7456, 8048, 8752, 9392, 10_208, 10_960, 11_744, 12_248,
52
+ 13_048, 13_880, 14_744, 15_640, 16_568, 17_528, 18_448, 19_472, 20_528, 21_616, 22_496, 23_648],
53
+ m: [128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624,
54
+ 4056, 4504, 5016, 5352, 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10_136, 10_984,
55
+ 11_640, 12_328, 13_048, 13_800, 14_496, 15_312, 15_936, 16_816, 17_728, 18_672],
56
+ q: [104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936,
57
+ 3176, 3560, 3880, 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368,
58
+ 9848, 10288, 10832, 11408, 12016, 12656, 13328],
59
+ h: [72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784,
60
+ 2024, 2264, 2504, 2728, 3080, 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960,
61
+ 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10_208]
62
+ }.freeze
63
+
64
+ class QRCodeArgumentError < ArgumentError; end
65
+ class QRCodeRunTimeError < RuntimeError; end
66
+
67
+ class QRMath
68
+ module_eval {
69
+ exp_table = Array.new(256)
70
+ log_table = Array.new(256)
71
+
72
+ 8.times do |i|
73
+ exp_table[i] = 1 << i
74
+ end
75
+
76
+ (8...256).each do |i|
77
+ exp_table[i] = exp_table[i - 4] \
78
+ ^ exp_table[i - 5] \
79
+ ^ exp_table[i - 6] \
80
+ ^ exp_table[i - 8]
81
+ end
82
+
83
+ 255.times do |i|
84
+ log_table[exp_table[i]] = i
85
+ end
86
+
87
+ const_set(:EXP_TABLE, exp_table).freeze
88
+ const_set(:LOG_TABLE, log_table).freeze
89
+ }
90
+
91
+ class << self
92
+ def glog(n)
93
+ raise QRCodeRunTimeError, "glog(#{n})" if n < 1
94
+ LOG_TABLE[n]
95
+ end
96
+
97
+ def gexp(n)
98
+ while n < 0
99
+ n += 255
100
+ end
101
+
102
+ while n >= 256
103
+ n -= 255
104
+ end
105
+
106
+ EXP_TABLE[n]
107
+ end
108
+ end
109
+ end
110
+
111
+ class QRPolynomial
112
+ def initialize(num, shift)
113
+ raise QRCodeRunTimeError, "#{num.size}/#{shift}" if num.empty?
114
+ offset = 0
115
+
116
+ while offset < num.size && num[offset] == 0
117
+ offset += 1
118
+ end
119
+
120
+ @num = Array.new(num.size - offset + shift)
121
+
122
+ (0...num.size - offset).each do |i|
123
+ @num[i] = num[i + offset]
124
+ end
125
+ end
126
+
127
+ def get(index)
128
+ @num[index]
129
+ end
130
+
131
+ def get_length
132
+ @num.size
133
+ end
134
+
135
+ def multiply(e)
136
+ num = Array.new(get_length + e.get_length - 1)
137
+
138
+ (0...get_length).each do |i|
139
+ (0...e.get_length).each do |j|
140
+ tmp = num[i + j].nil? ? 0 : num[i + j]
141
+ num[i + j] = tmp ^ QRMath.gexp(QRMath.glog(get(i)) + QRMath.glog(e.get(j)))
142
+ end
143
+ end
144
+
145
+ QRPolynomial.new(num, 0)
146
+ end
147
+
148
+ def mod(e)
149
+ if get_length - e.get_length < 0
150
+ return self
151
+ end
152
+
153
+ ratio = QRMath.glog(get(0)) - QRMath.glog(e.get(0))
154
+ num = Array.new(get_length)
155
+
156
+ (0...get_length).each do |i|
157
+ num[i] = get(i)
158
+ end
159
+
160
+ (0...e.get_length).each do |i|
161
+ tmp = num[i].nil? ? 0 : num[i]
162
+ num[i] = tmp ^ QRMath.gexp(QRMath.glog(e.get(i)) + ratio)
163
+ end
164
+
165
+ QRPolynomial.new(num, 0).mod(e)
166
+ end
167
+ end
168
+
169
+ class QRRSBlock
170
+ attr_reader :data_count, :total_count
171
+
172
+ def initialize(total_count, data_count)
173
+ @total_count = total_count
174
+ @data_count = data_count
175
+ end
176
+
177
+ RS_BLOCK_TABLE = [
178
+ [1, 26, 19], [1, 26, 16], [1, 26, 13], [1, 26, 9],
179
+ [1, 44, 34], [1, 44, 28], [1, 44, 22], [1, 44, 16],
180
+ [1, 70, 55], [1, 70, 44], [2, 35, 17], [2, 35, 13],
181
+ [1, 100, 80], [2, 50, 32], [2, 50, 24], [4, 25, 9],
182
+ [1, 134, 108], [2, 67, 43], [2, 33, 15, 2, 34, 16], [2, 33, 11, 2, 34, 12],
183
+ [2, 86, 68], [4, 43, 27], [4, 43, 19], [4, 43, 15],
184
+ [2, 98, 78], [4, 49, 31], [2, 32, 14, 4, 33, 15], [4, 39, 13, 1, 40, 14],
185
+ [2, 121, 97], [2, 60, 38, 2, 61, 39], [4, 40, 18, 2, 41, 19], [4, 40, 14, 2, 41, 15],
186
+ [2, 146, 116], [3, 58, 36, 2, 59, 37], [4, 36, 16, 4, 37, 17], [4, 36, 12, 4, 37, 13],
187
+ [2, 86, 68, 2, 87, 69], [4, 69, 43, 1, 70, 44], [6, 43, 19, 2, 44, 20], [6, 43, 15, 2, 44, 16],
188
+ [4, 101, 81], [1, 80, 50, 4, 81, 51], [4, 50, 22, 4, 51, 23], [3, 36, 12, 8, 37, 13],
189
+ [2, 116, 92, 2, 117, 93], [6, 58, 36, 2, 59, 37], [4, 46, 20, 6, 47, 21], [7, 42, 14, 4, 43, 15],
190
+ [4, 133, 107], [8, 59, 37, 1, 60, 38], [8, 44, 20, 4, 45, 21], [12, 33, 11, 4, 34, 12],
191
+ [3, 145, 115, 1, 146, 116], [4, 64, 40, 5, 65, 41], [11, 36, 16, 5, 37, 17], [11, 36, 12, 5, 37, 13],
192
+ [5, 109, 87, 1, 110, 88], [5, 65, 41, 5, 66, 42], [5, 54, 24, 7, 55, 25], [11, 36, 12, 7, 37, 13],
193
+ [5, 122, 98, 1, 123, 99], [7, 73, 45, 3, 74, 46], [15, 43, 19, 2, 44, 20], [3, 45, 15, 13, 46, 16],
194
+ [1, 135, 107, 5, 136, 108], [10, 74, 46, 1, 75, 47], [1, 50, 22, 15, 51, 23], [2, 42, 14, 17, 43, 15],
195
+ [5, 150, 120, 1, 151, 121], [9, 69, 43, 4, 70, 44], [17, 50, 22, 1, 51, 23], [2, 42, 14, 19, 43, 15],
196
+ [3, 141, 113, 4, 142, 114], [3, 70, 44, 11, 71, 45], [17, 47, 21, 4, 48, 22], [9, 39, 13, 16, 40, 14],
197
+ [3, 135, 107, 5, 136, 108], [3, 67, 41, 13, 68, 42], [15, 54, 24, 5, 55, 25], [15, 43, 15, 10, 44, 16],
198
+ [4, 144, 116, 4, 145, 117], [17, 68, 42], [17, 50, 22, 6, 51, 23], [19, 46, 16, 6, 47, 17],
199
+ [2, 139, 111, 7, 140, 112], [17, 74, 46], [7, 54, 24, 16, 55, 25], [34, 37, 13],
200
+ [4, 151, 121, 5, 152, 122], [4, 75, 47, 14, 76, 48], [11, 54, 24, 14, 55, 25], [16, 45, 15, 14, 46, 16],
201
+ [6, 147, 117, 4, 148, 118], [6, 73, 45, 14, 74, 46], [11, 54, 24, 16, 55, 25], [30, 46, 16, 2, 47, 17],
202
+ [8, 132, 106, 4, 133, 107], [8, 75, 47, 13, 76, 48], [7, 54, 24, 22, 55, 25], [22, 45, 15, 13, 46, 16],
203
+ [10, 142, 114, 2, 143, 115], [19, 74, 46, 4, 75, 47], [28, 50, 22, 6, 51, 23], [33, 46, 16, 4, 47, 17],
204
+ [8, 152, 122, 4, 153, 123], [22, 73, 45, 3, 74, 46], [8, 53, 23, 26, 54, 24], [12, 45, 15, 28, 46, 16],
205
+ [3, 147, 117, 10, 148, 118], [3, 73, 45, 23, 74, 46], [4, 54, 24, 31, 55, 25], [11, 45, 15, 31, 46, 16],
206
+ [7, 146, 116, 7, 147, 117], [21, 73, 45, 7, 74, 46], [1, 53, 23, 37, 54, 24], [19, 45, 15, 26, 46, 16],
207
+ [5, 145, 115, 10, 146, 116], [19, 75, 47, 10, 76, 48], [15, 54, 24, 25, 55, 25], [23, 45, 15, 25, 46, 16],
208
+ [13, 145, 115, 3, 146, 116], [2, 74, 46, 29, 75, 47], [42, 54, 24, 1, 55, 25], [23, 45, 15, 28, 46, 16],
209
+ [17, 145, 115], [10, 74, 46, 23, 75, 47], [10, 54, 24, 35, 55, 25], [19, 45, 15, 35, 46, 16],
210
+ [17, 145, 115, 1, 146, 116], [14, 74, 46, 21, 75, 47], [29, 54, 24, 19, 55, 25], [11, 45, 15, 46, 46, 16],
211
+ [13, 145, 115, 6, 146, 116], [14, 74, 46, 23, 75, 47], [44, 54, 24, 7, 55, 25], [59, 46, 16, 1, 47, 17],
212
+ [12, 151, 121, 7, 152, 122], [12, 75, 47, 26, 76, 48], [39, 54, 24, 14, 55, 25], [22, 45, 15, 41, 46, 16],
213
+ [6, 151, 121, 14, 152, 122], [6, 75, 47, 34, 76, 48], [46, 54, 24, 10, 55, 25], [2, 45, 15, 64, 46, 16],
214
+ [17, 152, 122, 4, 153, 123], [29, 74, 46, 14, 75, 47], [49, 54, 24, 10, 55, 25], [24, 45, 15, 46, 46, 16],
215
+ [4, 152, 122, 18, 153, 123], [13, 74, 46, 32, 75, 47], [48, 54, 24, 14, 55, 25], [42, 45, 15, 32, 46, 16],
216
+ [20, 147, 117, 4, 148, 118], [40, 75, 47, 7, 76, 48], [43, 54, 24, 22, 55, 25], [10, 45, 15, 67, 46, 16],
217
+ [19, 148, 118, 6, 149, 119], [18, 75, 47, 31, 76, 48], [34, 54, 24, 34, 55, 25], [20, 45, 15, 61, 46, 16]
218
+ ].freeze
219
+
220
+ def self.get_rs_blocks(version, error_correct_level)
221
+ rs_block = get_rs_block_table(version, error_correct_level)
222
+ raise QRCodeRunTimeError, "bad rsblock @ version: #{version}/error_correct_level:#{error_correct_level}" if rs_block.nil?
223
+
224
+ length = rs_block.size / 3
225
+ list = []
226
+
227
+ (0...length).each do |i|
228
+ count = rs_block[i * 3 + 0]
229
+ total_count = rs_block[i * 3 + 1]
230
+ data_count = rs_block[i * 3 + 2]
231
+
232
+ (0...count).each do
233
+ list << QRRSBlock.new(total_count, data_count)
234
+ end
235
+ end
236
+ list
237
+ end
238
+
239
+ def self.get_rs_block_table(version, error_correct_level)
240
+ case error_correct_level
241
+ when QRERRORCORRECTLEVEL[:l]
242
+ RS_BLOCK_TABLE[(version - 1) * 4 + 0]
243
+ when QRERRORCORRECTLEVEL[:m]
244
+ RS_BLOCK_TABLE[(version - 1) * 4 + 1]
245
+ when QRERRORCORRECTLEVEL[:q]
246
+ RS_BLOCK_TABLE[(version - 1) * 4 + 2]
247
+ when QRERRORCORRECTLEVEL[:h]
248
+ RS_BLOCK_TABLE[(version - 1) * 4 + 3]
249
+ end
250
+ end
251
+ end
252
+
253
+ class QRUtil
254
+ PATTERN_POSITION_TABLE = [
255
+ [],
256
+ [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50], [6, 30, 54],
257
+ [6, 32, 58], [6, 34, 62], [6, 26, 46, 66], [6, 26, 48, 70], [6, 26, 50, 74], [6, 30, 54, 78], [6, 30, 56, 82],
258
+ [6, 30, 58, 86], [6, 34, 62, 90], [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102],
259
+ [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118],
260
+ [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134],
261
+ [6, 34, 60, 86, 112, 138], [6, 30, 58, 86, 114, 142], [6, 34, 62, 90, 118, 146], [6, 30, 54, 78, 102, 126, 150],
262
+ [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162],
263
+ [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170]
264
+ ].freeze
265
+
266
+ G15 = 1 << 10 | 1 << 8 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 1 | 1 << 0
267
+ G18 = 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0
268
+ G15_MASK = 1 << 14 | 1 << 12 | 1 << 10 | 1 << 4 | 1 << 1
269
+
270
+ DEMERIT_POINTS_1 = 3
271
+ DEMERIT_POINTS_2 = 3
272
+ DEMERIT_POINTS_3 = 40
273
+ DEMERIT_POINTS_4 = 10
274
+
275
+ BITS_FOR_MODE = {
276
+ QRMODE[:mode_number] => [10, 12, 14],
277
+ QRMODE[:mode_alpha_numk] => [9, 11, 13],
278
+ QRMODE[:mode_8bit_byte] => [8, 16, 16]
279
+ }.freeze
280
+
281
+ ARCH_BITS = ENV.fetch("RQRCODE_CORE_ARCH_BITS", nil)&.to_i || 1.size * 8
282
+
283
+ def self.max_size
284
+ PATTERN_POSITION_TABLE.count
285
+ end
286
+
287
+ def self.get_bch_format_info(data)
288
+ d = data << 10
289
+ while get_bch_digit(d) - get_bch_digit(G15) >= 0
290
+ d ^= (G15 << (get_bch_digit(d) - get_bch_digit(G15)))
291
+ end
292
+ ((data << 10) | d) ^ G15_MASK
293
+ end
294
+
295
+ def self.rszf(num, count)
296
+ (num >> count) & ((1 << (ARCH_BITS - count)) - 1)
297
+ end
298
+
299
+ def self.get_bch_version(data)
300
+ d = data << 12
301
+ while get_bch_digit(d) - get_bch_digit(G18) >= 0
302
+ d ^= (G18 << (get_bch_digit(d) - get_bch_digit(G18)))
303
+ end
304
+ (data << 12) | d
305
+ end
306
+
307
+ def self.get_bch_digit(data)
308
+ digit = 0
309
+ while data != 0
310
+ digit += 1
311
+ data = rszf(data, 1)
312
+ end
313
+ digit
314
+ end
315
+
316
+ def self.get_pattern_positions(version)
317
+ PATTERN_POSITION_TABLE[version - 1]
318
+ end
319
+
320
+ def self.get_mask(mask_pattern, i, j)
321
+ raise QRCodeRunTimeError, "bad mask_pattern: #{mask_pattern}" if mask_pattern > QRMASKCOMPUTATIONS.size
322
+ QRMASKCOMPUTATIONS[mask_pattern].call(i, j)
323
+ end
324
+
325
+ def self.get_error_correct_polynomial(error_correct_length)
326
+ a = QRPolynomial.new([1], 0)
327
+ (0...error_correct_length).each do |i|
328
+ a = a.multiply(QRPolynomial.new([1, QRMath.gexp(i)], 0))
329
+ end
330
+ a
331
+ end
332
+
333
+ def self.get_length_in_bits(mode, version)
334
+ raise QRCodeRunTimeError, "Unknown mode: #{mode}" unless QRMODE.value?(mode)
335
+ raise QRCodeRunTimeError, "Unknown version: #{version}" if version > 40
336
+
337
+ if version.between?(1, 9)
338
+ macro_version = 0
339
+ elsif version <= 26
340
+ macro_version = 1
341
+ elsif version <= 40
342
+ macro_version = 2
343
+ end
344
+
345
+ BITS_FOR_MODE[mode][macro_version]
346
+ end
347
+
348
+ def self.get_lost_points(modules)
349
+ demerit_points = 0
350
+ demerit_points += demerit_points_1_same_color(modules)
351
+ demerit_points += demerit_points_2_full_blocks(modules)
352
+ demerit_points += demerit_points_3_dangerous_patterns(modules)
353
+ demerit_points += demerit_points_4_dark_ratio(modules)
354
+ demerit_points
355
+ end
356
+
357
+ def self.demerit_points_1_same_color(modules)
358
+ demerit_points = 0
359
+ module_count = modules.size
360
+ max_index = module_count - 1
361
+
362
+ module_count.times do |row|
363
+ modules_row = modules[row]
364
+ module_count.times do |col|
365
+ same_count = 0
366
+ dark = modules_row[col]
367
+
368
+ if row > 0
369
+ row_above = modules[row - 1]
370
+ same_count += 1 if col > 0 && dark == row_above[col - 1]
371
+ same_count += 1 if dark == row_above[col]
372
+ same_count += 1 if col < max_index && dark == row_above[col + 1]
373
+ end
374
+
375
+ same_count += 1 if col > 0 && dark == modules_row[col - 1]
376
+ same_count += 1 if col < max_index && dark == modules_row[col + 1]
377
+
378
+ if row < max_index
379
+ row_below = modules[row + 1]
380
+ same_count += 1 if col > 0 && dark == row_below[col - 1]
381
+ same_count += 1 if dark == row_below[col]
382
+ same_count += 1 if col < max_index && dark == row_below[col + 1]
383
+ end
384
+
385
+ demerit_points += (DEMERIT_POINTS_1 + same_count - 5) if same_count > 5
386
+ end
387
+ end
388
+ demerit_points
389
+ end
390
+
391
+ def self.demerit_points_2_full_blocks(modules)
392
+ demerit_points = 0
393
+ module_count = modules.size
394
+ max_row = module_count - 1
395
+
396
+ max_row.times do |row|
397
+ row_curr = modules[row]
398
+ row_next = modules[row + 1]
399
+
400
+ max_row.times do |col|
401
+ val = row_curr[col]
402
+ if val == row_next[col] && val == row_curr[col + 1] && val == row_next[col + 1]
403
+ demerit_points += DEMERIT_POINTS_2
404
+ end
405
+ end
406
+ end
407
+ demerit_points
408
+ end
409
+
410
+ def self.demerit_points_3_dangerous_patterns(modules)
411
+ demerit_points = 0
412
+ module_count = modules.size
413
+ pattern_len = 7
414
+ max_start = module_count - pattern_len + 1
415
+
416
+ modules.each do |row|
417
+ max_start.times do |col|
418
+ if row[col] && !row[col + 1] && row[col + 2] &&
419
+ row[col + 3] && row[col + 4] && !row[col + 5] && row[col + 6]
420
+ demerit_points += DEMERIT_POINTS_3
421
+ end
422
+ end
423
+ end
424
+
425
+ module_count.times do |col|
426
+ max_start.times do |row|
427
+ if modules[row][col] && !modules[row + 1][col] && modules[row + 2][col] &&
428
+ modules[row + 3][col] && modules[row + 4][col] && !modules[row + 5][col] && modules[row + 6][col]
429
+ demerit_points += DEMERIT_POINTS_3
430
+ end
431
+ end
432
+ end
433
+ demerit_points
434
+ end
435
+
436
+ def self.demerit_points_4_dark_ratio(modules)
437
+ dark_count = modules.reduce(0) do |sum, col|
438
+ sum + col.count(true)
439
+ end
440
+ ratio = dark_count.to_f / (modules.size * modules.size)
441
+ ratio_delta = (100 * ratio - 50).abs / 5
442
+ ratio_delta * DEMERIT_POINTS_4
443
+ end
444
+ end
445
+
446
+ class QRBitBuffer
447
+ attr_reader :buffer
448
+
449
+ PAD0 = 0xEC
450
+ PAD1 = 0x11
451
+
452
+ def initialize(version)
453
+ @version = version
454
+ @buffer = []
455
+ @length = 0
456
+ end
457
+
458
+ def get(index)
459
+ buf_index = (index / 8).floor
460
+ (QRUtil.rszf(@buffer[buf_index], 7 - index % 8) & 1) == 1
461
+ end
462
+
463
+ def put(num, length)
464
+ (0...length).each do |i|
465
+ put_bit((QRUtil.rszf(num, length - i - 1) & 1) == 1)
466
+ end
467
+ end
468
+
469
+ def get_length_in_bits
470
+ @length
471
+ end
472
+
473
+ def put_bit(bit)
474
+ buf_index = (@length / 8).floor
475
+ if @buffer.size <= buf_index
476
+ @buffer << 0
477
+ end
478
+
479
+ if bit
480
+ @buffer[buf_index] |= QRUtil.rszf(0x80, @length % 8)
481
+ end
482
+
483
+ @length += 1
484
+ end
485
+
486
+ def byte_encoding_start(length)
487
+ put(QRMODE[:mode_8bit_byte], 4)
488
+ put(length, QRUtil.get_length_in_bits(QRMODE[:mode_8bit_byte], @version))
489
+ end
490
+
491
+ def alphanumeric_encoding_start(length)
492
+ put(QRMODE[:mode_alpha_numk], 4)
493
+ put(length, QRUtil.get_length_in_bits(QRMODE[:mode_alpha_numk], @version))
494
+ end
495
+
496
+ def numeric_encoding_start(length)
497
+ put(QRMODE[:mode_number], 4)
498
+ put(length, QRUtil.get_length_in_bits(QRMODE[:mode_number], @version))
499
+ end
500
+
501
+ def pad_until(prefered_size)
502
+ while get_length_in_bits % 8 != 0
503
+ put_bit(false)
504
+ end
505
+
506
+ while get_length_in_bits < prefered_size
507
+ put(PAD0, 8)
508
+ put(PAD1, 8) if get_length_in_bits < prefered_size
509
+ end
510
+ end
511
+
512
+ def end_of_message(max_data_bits)
513
+ put(0, 4) unless get_length_in_bits + 4 > max_data_bits
514
+ end
515
+ end
516
+
517
+ NUMERIC = %w[0 1 2 3 4 5 6 7 8 9].freeze
518
+
519
+ class QRNumeric
520
+ def initialize(data)
521
+ raise QRCodeArgumentError, "Not a numeric string `#{data}`" unless QRNumeric.valid_data?(data)
522
+ @data = data
523
+ end
524
+
525
+ def self.valid_data?(data)
526
+ (data.chars - NUMERIC).empty?
527
+ end
528
+
529
+ def write(buffer)
530
+ buffer.numeric_encoding_start(@data.size)
531
+ @data.size.times do |i|
532
+ if i % 3 == 0
533
+ chars = @data[i, 3]
534
+ bit_length = get_bit_length(chars.length)
535
+ buffer.put(chars.to_i, bit_length)
536
+ end
537
+ end
538
+ end
539
+
540
+ private
541
+
542
+ NUMBER_LENGTH = {
543
+ 3 => 10,
544
+ 2 => 7,
545
+ 1 => 4
546
+ }.freeze
547
+
548
+ def get_bit_length(length)
549
+ NUMBER_LENGTH[length]
550
+ end
551
+ end
552
+
553
+ ALPHANUMERIC = [
554
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
555
+ "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "$",
556
+ "%", "*", "+", "-", ".", "/", ":"
557
+ ].freeze
558
+
559
+ class QRAlphanumeric
560
+ def initialize(data)
561
+ raise QRCodeArgumentError, "Not a alpha numeric uppercase string `#{data}`" unless QRAlphanumeric.valid_data?(data)
562
+ @data = data
563
+ end
564
+
565
+ def self.valid_data?(data)
566
+ (data.chars - ALPHANUMERIC).empty?
567
+ end
568
+
569
+ def write(buffer)
570
+ buffer.alphanumeric_encoding_start(@data.size)
571
+ @data.size.times do |i|
572
+ if i % 2 == 0
573
+ if i == (@data.size - 1)
574
+ value = ALPHANUMERIC.index(@data[i])
575
+ buffer.put(value, 6)
576
+ else
577
+ value = (ALPHANUMERIC.index(@data[i]) * 45) + ALPHANUMERIC.index(@data[i + 1])
578
+ buffer.put(value, 11)
579
+ end
580
+ end
581
+ end
582
+ end
583
+ end
584
+
585
+ class QR8bitByte
586
+ def initialize(data)
587
+ @data = data
588
+ end
589
+
590
+ def write(buffer)
591
+ buffer.byte_encoding_start(@data.bytesize)
592
+ @data.each_byte do |b|
593
+ buffer.put(b, 8)
594
+ end
595
+ end
596
+ end
597
+
598
+ class QRMulti
599
+ def initialize(data)
600
+ @data = data
601
+ end
602
+
603
+ def write(buffer)
604
+ @data.each { |seg| seg.writer.write(buffer) }
605
+ end
606
+ end
607
+
608
+ class QRSegment
609
+ attr_reader :data, :mode
610
+
611
+ def initialize(data:, mode: nil)
612
+ @data = data
613
+ @mode = QRMODE_NAME.dig(mode&.to_sym)
614
+
615
+ @mode ||= if QRNumeric.valid_data?(@data)
616
+ QRMODE_NAME[:number]
617
+ elsif QRAlphanumeric.valid_data?(@data)
618
+ QRMODE_NAME[:alphanumeric]
619
+ else
620
+ QRMODE_NAME[:byte_8bit]
621
+ end
622
+ end
623
+
624
+ def size(version)
625
+ 4 + header_size(version) + content_size
626
+ end
627
+
628
+ def header_size(version)
629
+ QRUtil.get_length_in_bits(QRMODE[mode], version)
630
+ end
631
+
632
+ def content_size
633
+ chunk_size, bit_length, extra = case mode
634
+ when :mode_number
635
+ [3, QRNumeric.const_get(:NUMBER_LENGTH)[3], QRNumeric.const_get(:NUMBER_LENGTH)[data_length % 3] || 0]
636
+ when :mode_alpha_numk
637
+ [2, 11, 6]
638
+ when :mode_8bit_byte
639
+ [1, 8, 0]
640
+ end
641
+
642
+ (data_length / chunk_size) * bit_length + (((data_length % chunk_size) == 0) ? 0 : extra)
643
+ end
644
+
645
+ def writer
646
+ case mode
647
+ when :mode_number
648
+ QRNumeric.new(data)
649
+ when :mode_alpha_numk
650
+ QRAlphanumeric.new(data)
651
+ when :mode_multi
652
+ QRMulti.new(data)
653
+ else
654
+ QR8bitByte.new(data)
655
+ end
656
+ end
657
+
658
+ private
659
+
660
+ def data_length
661
+ data.bytesize
662
+ end
663
+ end
664
+
665
+ class QRCode
666
+ attr_reader :modules, :module_count, :version
667
+
668
+ def initialize(data, *args)
669
+ options = extract_options!(args)
670
+
671
+ level = (options[:level] || :h).to_sym
672
+ max_size = options[:max_size] || QRUtil.max_size
673
+
674
+ @data = case data
675
+ when String
676
+ QRSegment.new(data: data, mode: options[:mode])
677
+ when Array
678
+ raise QRCodeArgumentError, "Array must contain Hashes with :data and :mode keys" unless data.all? { |seg| seg.is_a?(Hash) && %i[data mode].all? { |s| seg.key? s } }
679
+ data.map { |seg| QRSegment.new(**seg) }
680
+ when QRSegment
681
+ data
682
+ else
683
+ raise QRCodeArgumentError, "data must be a String, QRSegment, or an Array"
684
+ end
685
+ @error_correct_level = QRERRORCORRECTLEVEL[level]
686
+
687
+ unless @error_correct_level
688
+ raise QRCodeArgumentError, "Unknown error correction level `#{level.inspect}`"
689
+ end
690
+
691
+ size = options[:size] || minimum_version(limit: max_size)
692
+
693
+ if size > max_size
694
+ raise QRCodeArgumentError, "Given size greater than maximum possible size of #{QRUtil.max_size}"
695
+ end
696
+
697
+ @version = size
698
+ @module_count = @version * 4 + QRPOSITIONPATTERNLENGTH
699
+ @modules = Array.new(@module_count)
700
+ @data_list = multi_segment? ? QRMulti.new(@data) : @data.writer
701
+ @data_cache = nil
702
+ make
703
+ end
704
+
705
+ def checked?(row, col)
706
+ if !row.between?(0, @module_count - 1) || !col.between?(0, @module_count - 1)
707
+ raise QRCodeRunTimeError, "Invalid row/column pair: #{row}, #{col}"
708
+ end
709
+ @modules[row][col]
710
+ end
711
+ alias_method :dark?, :checked?
712
+
713
+ def to_s(*args)
714
+ options = extract_options!(args)
715
+ dark = options[:dark] || "x"
716
+ light = options[:light] || " "
717
+ quiet_zone_size = options[:quiet_zone_size] || 0
718
+
719
+ rows = []
720
+ @modules.each do |row|
721
+ cols = light * quiet_zone_size
722
+ row.each do |col|
723
+ cols += (col ? dark : light)
724
+ end
725
+ rows << cols
726
+ end
727
+
728
+ quiet_zone_size.times do
729
+ rows.unshift(light * (rows.first.length / light.size))
730
+ rows << light * (rows.first.length / light.size)
731
+ end
732
+ rows.join("\n")
733
+ end
734
+
735
+ def inspect
736
+ "QRCodeMaker: @data='#{@data}', @error_correct_level=#{@error_correct_level}, @version=#{@version}, @module_count=#{@module_count}"
737
+ end
738
+
739
+ def error_correction_level
740
+ QRERRORCORRECTLEVEL.invert[@error_correct_level]
741
+ end
742
+
743
+ def multi_segment?
744
+ @data.is_a?(Array)
745
+ end
746
+
747
+ def mode
748
+ case @data_list
749
+ when QRNumeric
750
+ :mode_number
751
+ when QRAlphanumeric
752
+ :mode_alpha_numk
753
+ else
754
+ :mode_8bit_byte
755
+ end
756
+ end
757
+
758
+ protected
759
+
760
+ def make
761
+ prepare_common_patterns
762
+ make_impl(false, get_best_mask_pattern)
763
+ end
764
+
765
+ private
766
+
767
+ def prepare_common_patterns
768
+ @modules.map! { Array.new(@module_count) }
769
+
770
+ place_position_probe_pattern(0, 0)
771
+ place_position_probe_pattern(@module_count - 7, 0)
772
+ place_position_probe_pattern(0, @module_count - 7)
773
+ place_position_adjust_pattern
774
+ place_timing_pattern
775
+
776
+ @common_patterns = @modules.map(&:clone)
777
+ end
778
+
779
+ def make_impl(test, mask_pattern)
780
+ @modules = @common_patterns.map(&:clone)
781
+
782
+ place_format_info(test, mask_pattern)
783
+ place_version_info(test) if @version >= 7
784
+
785
+ if @data_cache.nil?
786
+ @data_cache = QRCode.create_data(
787
+ @version, @error_correct_level, @data_list
788
+ )
789
+ end
790
+
791
+ map_data(@data_cache, mask_pattern)
792
+ end
793
+
794
+ def place_position_probe_pattern(row, col)
795
+ (-1..7).each do |r|
796
+ next unless (row + r).between?(0, @module_count - 1)
797
+
798
+ (-1..7).each do |c|
799
+ next unless (col + c).between?(0, @module_count - 1)
800
+
801
+ is_vert_line = r.between?(0, 6) && (c == 0 || c == 6)
802
+ is_horiz_line = c.between?(0, 6) && (r == 0 || r == 6)
803
+ is_square = r.between?(2, 4) && c.between?(2, 4)
804
+
805
+ is_part_of_probe = is_vert_line || is_horiz_line || is_square
806
+ @modules[row + r][col + c] = is_part_of_probe
807
+ end
808
+ end
809
+ end
810
+
811
+ def get_best_mask_pattern
812
+ min_lost_point = 0
813
+ pattern = 0
814
+
815
+ 8.times do |i|
816
+ make_impl(true, i)
817
+ lost_point = QRUtil.get_lost_points(modules)
818
+
819
+ if i == 0 || min_lost_point > lost_point
820
+ min_lost_point = lost_point
821
+ pattern = i
822
+ end
823
+ end
824
+ pattern
825
+ end
826
+
827
+ def place_timing_pattern
828
+ (8...@module_count - 8).each do |i|
829
+ @modules[i][6] = @modules[6][i] = i % 2 == 0
830
+ end
831
+ end
832
+
833
+ def place_position_adjust_pattern
834
+ positions = QRUtil.get_pattern_positions(@version)
835
+
836
+ positions.each do |row|
837
+ positions.each do |col|
838
+ next unless @modules[row][col].nil?
839
+
840
+ (-2..2).each do |r|
841
+ (-2..2).each do |c|
842
+ is_part_of_pattern = r.abs == 2 || c.abs == 2 || (r == 0 && c == 0)
843
+ @modules[row + r][col + c] = is_part_of_pattern
844
+ end
845
+ end
846
+ end
847
+ end
848
+ end
849
+
850
+ def place_version_info(test)
851
+ bits = QRUtil.get_bch_version(@version)
852
+
853
+ 18.times do |i|
854
+ mod = !test && ((bits >> i) & 1) == 1
855
+ @modules[(i / 3).floor][ i % 3 + @module_count - 8 - 3 ] = mod
856
+ @modules[i % 3 + @module_count - 8 - 3][ (i / 3).floor ] = mod
857
+ end
858
+ end
859
+
860
+ def place_format_info(test, mask_pattern)
861
+ data = (@error_correct_level << 3 | mask_pattern)
862
+ bits = QRUtil.get_bch_format_info(data)
863
+
864
+ QRFORMATINFOLENGTH.times do |i|
865
+ mod = !test && ((bits >> i) & 1) == 1
866
+
867
+ # vertical
868
+ row = if i < 6
869
+ i
870
+ elsif i < 8
871
+ i + 1
872
+ else
873
+ @module_count - 15 + i
874
+ end
875
+ @modules[row][8] = mod
876
+
877
+ # horizontal
878
+ col = if i < 8
879
+ @module_count - i - 1
880
+ elsif i < 9
881
+ 15 - i - 1 + 1
882
+ else
883
+ 15 - i - 1
884
+ end
885
+ @modules[8][col] = mod
886
+ end
887
+
888
+ # fixed module
889
+ @modules[@module_count - 8][8] = !test
890
+ end
891
+
892
+ def map_data(data, mask_pattern)
893
+ inc = -1
894
+ row = @module_count - 1
895
+ bit_index = 7
896
+ byte_index = 0
897
+
898
+ (@module_count - 1).step(1, -2) do |col|
899
+ col -= 1 if col <= 6
900
+
901
+ loop do
902
+ 2.times do |c|
903
+ if @modules[row][col - c].nil?
904
+ dark = false
905
+ if byte_index < data.size && !data[byte_index].nil?
906
+ dark = ((QRUtil.rszf(data[byte_index], bit_index) & 1) == 1)
907
+ end
908
+ mask = QRUtil.get_mask(mask_pattern, row, col - c)
909
+ dark = !dark if mask
910
+ @modules[row][ col - c ] = dark
911
+ bit_index -= 1
912
+
913
+ if bit_index == -1
914
+ byte_index += 1
915
+ bit_index = 7
916
+ end
917
+ end
918
+ end
919
+
920
+ row += inc
921
+
922
+ if row < 0 || @module_count <= row
923
+ row -= inc
924
+ inc = -inc
925
+ break
926
+ end
927
+ end
928
+ end
929
+ end
930
+
931
+ def minimum_version(limit: QRUtil.max_size, version: 1)
932
+ raise QRCodeRunTimeError, "Data length exceed maximum capacity of version #{limit}" if version > limit
933
+
934
+ max_size_bits = QRMAXBITS[error_correction_level][version - 1]
935
+ size_bits = multi_segment? ? @data.sum { |seg| seg.size(version) } : @data.size(version)
936
+
937
+ return version if size_bits < max_size_bits
938
+
939
+ minimum_version(limit: limit, version: version + 1)
940
+ end
941
+
942
+ def extract_options!(arr)
943
+ arr.last.is_a?(::Hash) ? arr.pop : {}
944
+ end
945
+
946
+ class << self
947
+ def count_max_data_bits(rs_blocks)
948
+ max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block|
949
+ sum + rs_block.data_count
950
+ end
951
+ max_data_bytes * 8
952
+ end
953
+
954
+ def create_data(version, error_correct_level, data_list)
955
+ rs_blocks = QRRSBlock.get_rs_blocks(version, error_correct_level)
956
+ max_data_bits = count_max_data_bits(rs_blocks)
957
+ buffer = QRBitBuffer.new(version)
958
+
959
+ data_list.write(buffer)
960
+ buffer.end_of_message(max_data_bits)
961
+
962
+ if buffer.get_length_in_bits > max_data_bits
963
+ raise QRCodeRunTimeError, "code length overflow. (#{buffer.get_length_in_bits}>#{max_data_bits}). (Try a larger size!)"
964
+ end
965
+
966
+ buffer.pad_until(max_data_bits)
967
+ create_bytes(buffer, rs_blocks)
968
+ end
969
+
970
+ def create_bytes(buffer, rs_blocks)
971
+ offset = 0
972
+ max_dc_count = 0
973
+ max_ec_count = 0
974
+ dcdata = Array.new(rs_blocks.size)
975
+ ecdata = Array.new(rs_blocks.size)
976
+
977
+ rs_blocks.each_with_index do |rs_block, r|
978
+ dc_count = rs_block.data_count
979
+ ec_count = rs_block.total_count - dc_count
980
+ max_dc_count = [max_dc_count, dc_count].max
981
+ max_ec_count = [max_ec_count, ec_count].max
982
+
983
+ dcdata_block = Array.new(dc_count)
984
+ dcdata_block.size.times do |i|
985
+ dcdata_block[i] = 0xff & buffer.buffer[i + offset]
986
+ end
987
+ dcdata[r] = dcdata_block
988
+
989
+ offset += dc_count
990
+ rs_poly = QRUtil.get_error_correct_polynomial(ec_count)
991
+ raw_poly = QRPolynomial.new(dcdata[r], rs_poly.get_length - 1)
992
+ mod_poly = raw_poly.mod(rs_poly)
993
+
994
+ ecdata_block = Array.new(rs_poly.get_length - 1)
995
+ ecdata_block.size.times do |i|
996
+ mod_index = i + mod_poly.get_length - ecdata_block.size
997
+ ecdata_block[i] = (mod_index >= 0) ? mod_poly.get(mod_index) : 0
998
+ end
999
+ ecdata[r] = ecdata_block
1000
+ end
1001
+
1002
+ total_code_count = rs_blocks.reduce(0) do |sum, rs_block|
1003
+ sum + rs_block.total_count
1004
+ end
1005
+
1006
+ data = Array.new(total_code_count)
1007
+ index = 0
1008
+
1009
+ max_dc_count.times do |i|
1010
+ rs_blocks.size.times do |r|
1011
+ if i < dcdata[r].size
1012
+ data[index] = dcdata[r][i]
1013
+ index += 1
1014
+ end
1015
+ end
1016
+ end
1017
+
1018
+ max_ec_count.times do |i|
1019
+ rs_blocks.size.times do |r|
1020
+ if i < ecdata[r].size
1021
+ data[index] = ecdata[r][i]
1022
+ index += 1
1023
+ end
1024
+ end
1025
+ end
1026
+
1027
+ data
1028
+ end
1029
+ end
1030
+ end
1031
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QrCodeMaker
4
+ class QRCode
5
+ def as_svg(options = {})
6
+ color = options[:color] || "black"
7
+ bg_color = options[:bg_color]
8
+ bg_color = "white" if bg_color.nil?
9
+
10
+ module_size = options[:module_size] || 10
11
+ margin = options[:margin] || 4
12
+
13
+ view_box_size = @module_count + 2 * margin
14
+ total_size = view_box_size * module_size
15
+
16
+ path_data = +""
17
+ @modules.each_with_index do |row, r|
18
+ row.each_with_index do |col, c|
19
+ if col
20
+ path_data << "M#{c + margin},#{r + margin}h1v1h-1z"
21
+ end
22
+ end
23
+ end
24
+
25
+ svg = []
26
+ svg << %(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{view_box_size} #{view_box_size}" width="#{total_size}" height="#{total_size}">)
27
+ if bg_color && bg_color != "transparent" && bg_color != false
28
+ svg << %( <rect width="100%" height="100%" fill="#{bg_color}"/>)
29
+ end
30
+ svg << %( <path d="#{path_data}" fill="#{color}" shape-rendering="crispEdges"/>)
31
+ svg << %(</svg>)
32
+ svg.join("\n")
33
+ end
34
+
35
+ def as_ascii(options = {})
36
+ dark = options[:dark] || "██"
37
+ light = options[:light] || " "
38
+ margin = options[:margin] || 2
39
+
40
+ rows = []
41
+ margin.times { rows << (light * (@module_count + 2 * margin)) }
42
+
43
+ @modules.each do |row|
44
+ row_str = light * margin
45
+ row.each do |col|
46
+ row_str << (col ? dark : light)
47
+ end
48
+ row_str << (light * margin)
49
+ rows << row_str
50
+ end
51
+
52
+ margin.times { rows << (light * (@module_count + 2 * margin)) }
53
+ rows.join("\n")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "qr_code_maker/encoder"
4
+ require_relative "qr_code_maker/renderer"
5
+
6
+ module QrCodeMaker
7
+ # High-level helper to generate SVG in a single call
8
+ def self.generate_svg(data, options = {})
9
+ QRCode.new(data, options).as_svg(options)
10
+ end
11
+
12
+ # High-level helper to generate ASCII in a single call
13
+ def self.generate_ascii(data, options = {})
14
+ QRCode.new(data, options).as_ascii(options)
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qr_code_maker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - swlkr
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Generate QR codes in SVG and ASCII format with a super simple API, relying
13
+ only on standard Ruby.
14
+ email:
15
+ - from@swlkr.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/qr_code_maker.rb
22
+ - lib/qr_code_maker/encoder.rb
23
+ - lib/qr_code_maker/renderer.rb
24
+ homepage: https://github.com/swlkr/qr_code_generator
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.5.0
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.7.2
43
+ specification_version: 4
44
+ summary: A zero-dependency standard-library-only QR code maker.
45
+ test_files: []