qrcode 0.0.1 → 0.1.1

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.
@@ -0,0 +1,436 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2008-2025, by Duncan Robertson.
5
+ # Copyright, 2011, by Daniel Schierbeck.
6
+ # Copyright, 2011, by Gioele Barabucci.
7
+ # Copyright, 2012-2015, by Björn Blomqvist.
8
+ # Copyright, 2012, by xn.
9
+ # Copyright, 2013, by Yauhen Kharuzhy.
10
+ # Copyright, 2014, by Sean Doig.
11
+ # Copyright, 2015, by Tonči Damjanić.
12
+ # Copyright, 2015-2016, by Bjorn Blomqvist.
13
+ # Copyright, 2015-2016, by Fabio Napoleoni.
14
+ # Copyright, 2020, by Nathaniel Roman.
15
+ # Copyright, 2021, by Simon Schrape.
16
+ # Copyright, 2021, by Sam Sayer.
17
+ # Copyright, 2025, by Samuel Williams.
18
+
19
+ require_relative "constants"
20
+ require_relative "math"
21
+ require_relative "polynomial"
22
+ require_relative "util"
23
+ require_relative "bit_buffer"
24
+ require_relative "error_correction_block"
25
+ require_relative "segment"
26
+
27
+ module QRCode
28
+ module Encoder
29
+ # == Creation
30
+ #
31
+ # QRCode objects expect only one required constructor parameter
32
+ # and an optional hash of any other. Here's a few examples:
33
+ #
34
+ # qr = QRCode::Encoder::Code.new('hello world')
35
+ # qr = QRCode::Encoder::Code.new('hello world', size: 1, level: :m, mode: :alphanumeric)
36
+ #
37
+ class Code
38
+ attr_reader :modules, :module_count, :version, :segments
39
+
40
+ # Alias for module_count - the width/height of the QR code square
41
+ alias_method :size, :module_count
42
+
43
+ # Factory method to build QR code from data
44
+ # @parameter data [String, Array] The data to encode
45
+ # @parameter level [Symbol] Error correction level (:l, :m, :q, :h)
46
+ # @parameter mode [Symbol] Encoding mode (:auto, :number, :alphanumeric, :byte_8bit)
47
+ # @parameter size [Integer] QR code version (auto-detected if not specified)
48
+ # @parameter max_size [Integer] Maximum allowed version
49
+ def self.build(data, level: :h, mode: :auto, size: nil, max_size: nil)
50
+ segments = Segment.build(data, mode: mode)
51
+ new(segments, level: level, size: size, max_size: max_size)
52
+ end
53
+
54
+ # Simple constructor that takes an array of segments
55
+ # @parameter segments [Array<Segment>] Array of segments to encode
56
+ # @parameter level [Symbol] Error correction level (:l, :m, :q, :h)
57
+ # @parameter size [Integer] QR code version (auto-detected if not specified)
58
+ # @parameter max_size [Integer] Maximum allowed version
59
+ def initialize(segments, level: :h, size: nil, max_size: nil)
60
+ @segments = Array(segments)
61
+ @error_correction_level = ERROR_CORRECTION_LEVEL[level]
62
+
63
+ unless @error_correction_level
64
+ raise ArgumentError, "Unknown error correction level `#{level.inspect}`"
65
+ end
66
+
67
+ max_size ||= Encoder::Util.max_size
68
+ calculated_size = size || minimum_version(limit: max_size)
69
+
70
+ if calculated_size > max_size
71
+ raise ArgumentError, "Given size greater than maximum possible size of #{max_size}"
72
+ end
73
+
74
+ @version = calculated_size
75
+ @module_count = @version * 4 + POSITION_PATTERN_LENGTH
76
+ @modules = Array.new(@module_count)
77
+ @data_cache = nil
78
+ make
79
+ end
80
+
81
+ # <tt>checked?</tt> is called with a +col+ and +row+ parameter. This will
82
+ # return true or false based on whether that coordinate exists in the
83
+ # matrix returned. It would normally be called while iterating through
84
+ # <tt>modules</tt>. A simple example would be:
85
+ #
86
+ # instance.checked?( 10, 10 ) => true
87
+ #
88
+ def checked?(row, col)
89
+ if !row.between?(0, @module_count - 1) || !col.between?(0, @module_count - 1)
90
+ raise RuntimeError, "Invalid row/column pair: #{row}, #{col}"
91
+ end
92
+ @modules[row][col]
93
+ end
94
+
95
+ # This is a public method that returns the QR Code you have
96
+ # generated as a string. It will not be able to be read
97
+ # in this format by a QR Code reader, but will give you an
98
+ # idea if the final outout. It takes two optional args
99
+ # +:dark+ and +:light+ which are there for you to choose
100
+ # how the output looks. Here's an example of it's use:
101
+ #
102
+ # instance.to_s =>
103
+ # xxxxxxx x x x x x xx xxxxxxx
104
+ # x x xxx xxxxxx xxx x x
105
+ # x xxx x xxxxx x xx x xxx x
106
+ #
107
+ # instance.to_s( dark: 'E', light: 'Q' ) =>
108
+ # EEEEEEEQEQQEQEQQQEQEQQEEQQEEEEEEE
109
+ # EQQQQQEQQEEEQQEEEEEEQEEEQQEQQQQQE
110
+ # EQEEEQEQQEEEEEQEQQQQQQQEEQEQEEEQE
111
+ #
112
+ def to_s(*args)
113
+ options = extract_options!(args)
114
+ dark = options[:dark] || "x"
115
+ light = options[:light] || " "
116
+ quiet_zone_size = options[:quiet_zone_size] || 0
117
+
118
+ rows = []
119
+
120
+ @modules.each do |row|
121
+ cols = light * quiet_zone_size
122
+ row.each do |col|
123
+ cols += (col ? dark : light)
124
+ end
125
+ rows << cols
126
+ end
127
+
128
+ quiet_zone_size.times do
129
+ rows.unshift(light * (rows.first.length / light.size))
130
+ rows << light * (rows.first.length / light.size)
131
+ end
132
+ rows.join("\n")
133
+ end
134
+
135
+ # Public overide as default inspect is very verbose
136
+ #
137
+ # QRCode::Encoder::Code.new('my string to generate', size: 4, level: :h)
138
+ # => QRCodeCore: @data='my string to generate', @error_correction_level=2, @version=4, @module_count=33
139
+ #
140
+ def inspect
141
+ "QRCodeCore: @segments=#{@segments.size} segments, @error_correction_level=#{@error_correction_level}, @version=#{@version}, @module_count=#{@module_count}"
142
+ end
143
+
144
+ # Return a symbol for current error connection level
145
+ def error_correction_level
146
+ ERROR_CORRECTION_LEVEL.invert[@error_correction_level]
147
+ end
148
+
149
+ # Return true if this QR Code includes multiple encoded segments
150
+ def multi_segment?
151
+ @segments.size > 1
152
+ end
153
+
154
+ # Return the primary mode used (first segment's mode)
155
+ def mode
156
+ @segments.first&.mode || :mode_8bit_byte
157
+ end
158
+
159
+ protected
160
+
161
+ def make # :nodoc:
162
+ prepare_common_patterns
163
+ make_impl(false, get_best_mask_pattern)
164
+ end
165
+
166
+ private
167
+
168
+ def prepare_common_patterns # :nodoc:
169
+ @modules.map! {|row| Array.new(@module_count)}
170
+
171
+ place_position_probe_pattern(0, 0)
172
+ place_position_probe_pattern(@module_count - 7, 0)
173
+ place_position_probe_pattern(0, @module_count - 7)
174
+ place_position_adjust_pattern
175
+ place_timing_pattern
176
+
177
+ @common_patterns = @modules.map(&:clone)
178
+ end
179
+
180
+ def make_impl(test, mask_pattern) # :nodoc:
181
+ @modules = @common_patterns.map(&:clone)
182
+
183
+ place_format_info(test, mask_pattern)
184
+ place_version_info(test) if @version >= 7
185
+
186
+ if @data_cache.nil?
187
+ @data_cache = Code.create_data(
188
+ @version, @error_correction_level, @segments
189
+ )
190
+ end
191
+
192
+ map_data(@data_cache, mask_pattern)
193
+ end
194
+
195
+ def place_position_probe_pattern(row, col) # :nodoc:
196
+ (-1..7).each do |r|
197
+ next unless (row + r).between?(0, @module_count - 1)
198
+
199
+ (-1..7).each do |c|
200
+ next unless (col + c).between?(0, @module_count - 1)
201
+
202
+ is_vert_line = r.between?(0, 6) && (c == 0 || c == 6)
203
+ is_horiz_line = c.between?(0, 6) && (r == 0 || r == 6)
204
+ is_square = r.between?(2, 4) && c.between?(2, 4)
205
+
206
+ is_part_of_probe = is_vert_line || is_horiz_line || is_square
207
+ @modules[row + r][col + c] = is_part_of_probe
208
+ end
209
+ end
210
+ end
211
+
212
+ def get_best_mask_pattern # :nodoc:
213
+ min_lost_point = 0
214
+ pattern = 0
215
+
216
+ 8.times do |i|
217
+ make_impl(true, i)
218
+ lost_point = Encoder::Util.get_lost_points(modules)
219
+
220
+ if i == 0 || min_lost_point > lost_point
221
+ min_lost_point = lost_point
222
+ pattern = i
223
+ end
224
+ end
225
+ pattern
226
+ end
227
+
228
+ def place_timing_pattern # :nodoc:
229
+ (8...@module_count - 8).each do |i|
230
+ @modules[i][6] = @modules[6][i] = i % 2 == 0
231
+ end
232
+ end
233
+
234
+ def place_position_adjust_pattern # :nodoc:
235
+ positions = Encoder::Util.get_pattern_positions(@version)
236
+
237
+ positions.each do |row|
238
+ positions.each do |col|
239
+ next unless @modules[row][col].nil?
240
+
241
+ (-2..2).each do |r|
242
+ (-2..2).each do |c|
243
+ is_part_of_pattern = r.abs == 2 || c.abs == 2 || (r == 0 && c == 0)
244
+ @modules[row + r][col + c] = is_part_of_pattern
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ def place_version_info(test) # :nodoc:
252
+ bits = Encoder::Util.get_bch_version(@version)
253
+
254
+ 18.times do |i|
255
+ mod = !test && ((bits >> i) & 1) == 1
256
+ @modules[(i / 3).floor][ i % 3 + @module_count - 8 - 3 ] = mod
257
+ @modules[i % 3 + @module_count - 8 - 3][ (i / 3).floor ] = mod
258
+ end
259
+ end
260
+
261
+ def place_format_info(test, mask_pattern) # :nodoc:
262
+ data = (@error_correction_level << 3 | mask_pattern)
263
+ bits = Encoder::Util.get_bch_format_info(data)
264
+
265
+ FORMAT_INFO_LENGTH.times do |i|
266
+ mod = !test && ((bits >> i) & 1) == 1
267
+
268
+ # vertical
269
+ row = if i < 6
270
+ i
271
+ elsif i < 8
272
+ i + 1
273
+ else
274
+ @module_count - 15 + i
275
+ end
276
+ @modules[row][8] = mod
277
+
278
+ # horizontal
279
+ col = if i < 8
280
+ @module_count - i - 1
281
+ elsif i < 9
282
+ 15 - i - 1 + 1
283
+ else
284
+ 15 - i - 1
285
+ end
286
+ @modules[8][col] = mod
287
+ end
288
+
289
+ # fixed module
290
+ @modules[@module_count - 8][8] = !test
291
+ end
292
+
293
+ def map_data(data, mask_pattern) # :nodoc:
294
+ inc = -1
295
+ row = @module_count - 1
296
+ bit_index = 7
297
+ byte_index = 0
298
+
299
+ (@module_count - 1).step(1, -2) do |col|
300
+ col -= 1 if col <= 6
301
+
302
+ loop do
303
+ 2.times do |c|
304
+ if @modules[row][col - c].nil?
305
+ dark = false
306
+ if byte_index < data.size && !data[byte_index].nil?
307
+ dark = ((Encoder::Util.rszf(data[byte_index], bit_index) & 1) == 1)
308
+ end
309
+ mask = Encoder::Util.get_mask(mask_pattern, row, col - c)
310
+ dark = !dark if mask
311
+ @modules[row][ col - c ] = dark
312
+ bit_index -= 1
313
+
314
+ if bit_index == -1
315
+ byte_index += 1
316
+ bit_index = 7
317
+ end
318
+ end
319
+ end
320
+
321
+ row += inc
322
+
323
+ if row < 0 || @module_count <= row
324
+ row -= inc
325
+ inc = -inc
326
+ break
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ def minimum_version(limit: Encoder::Util.max_size, version: 1)
333
+ raise RuntimeError, "Data length exceed maximum capacity of version #{limit}" if version > limit
334
+
335
+ max_size_bits = MAX_BITS[error_correction_level][version - 1]
336
+
337
+ size_bits = @segments.sum {|segment| segment.bit_size(version)}
338
+
339
+ return version if size_bits < max_size_bits
340
+
341
+ minimum_version(limit: limit, version: version + 1)
342
+ end
343
+
344
+ def extract_options!(arr) # :nodoc:
345
+ arr.last.is_a?(::Hash) ? arr.pop : {}
346
+ end
347
+
348
+ class << self
349
+ def count_max_data_bits(rs_blocks) # :nodoc:
350
+ max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block|
351
+ sum + rs_block.data_count
352
+ end
353
+
354
+ max_data_bytes * 8
355
+ end
356
+
357
+ def create_data(version, error_correction_level, segments) # :nodoc:
358
+ rs_blocks = Encoder::ErrorCorrectionBlock.for(version, error_correction_level)
359
+ max_data_bits = Code.count_max_data_bits(rs_blocks)
360
+ buffer = Encoder::BitBuffer.new(version)
361
+
362
+ segments.each {|segment| segment.write(buffer)}
363
+ buffer.end_of_message(max_data_bits)
364
+
365
+ if buffer.get_length_in_bits > max_data_bits
366
+ raise RuntimeError, "code length overflow. (#{buffer.get_length_in_bits}>#{max_data_bits}). (Try a larger size!)"
367
+ end
368
+
369
+ buffer.pad_until(max_data_bits)
370
+
371
+ Code.create_bytes(buffer, rs_blocks)
372
+ end
373
+
374
+ def create_bytes(buffer, rs_blocks) # :nodoc:
375
+ offset = 0
376
+ max_dc_count = 0
377
+ max_ec_count = 0
378
+ dcdata = Array.new(rs_blocks.size)
379
+ ecdata = Array.new(rs_blocks.size)
380
+
381
+ rs_blocks.each_with_index do |rs_block, r|
382
+ dc_count = rs_block.data_count
383
+ ec_count = rs_block.total_count - dc_count
384
+ max_dc_count = [max_dc_count, dc_count].max
385
+ max_ec_count = [max_ec_count, ec_count].max
386
+
387
+ dcdata_block = Array.new(dc_count)
388
+ dcdata_block.size.times do |i|
389
+ dcdata_block[i] = 0xff & buffer.buffer[i + offset]
390
+ end
391
+ dcdata[r] = dcdata_block
392
+
393
+ offset += dc_count
394
+ rs_poly = Encoder::Util.get_error_correct_polynomial(ec_count)
395
+ raw_poly = Encoder::Polynomial.new(dcdata[r], rs_poly.get_length - 1)
396
+ mod_poly = raw_poly.mod(rs_poly)
397
+
398
+ ecdata_block = Array.new(rs_poly.get_length - 1)
399
+ ecdata_block.size.times do |i|
400
+ mod_index = i + mod_poly.get_length - ecdata_block.size
401
+ ecdata_block[i] = (mod_index >= 0) ? mod_poly.get(mod_index) : 0
402
+ end
403
+ ecdata[r] = ecdata_block
404
+ end
405
+
406
+ total_code_count = rs_blocks.reduce(0) do |sum, rs_block|
407
+ sum + rs_block.total_count
408
+ end
409
+
410
+ data = Array.new(total_code_count)
411
+ index = 0
412
+
413
+ max_dc_count.times do |i|
414
+ rs_blocks.size.times do |r|
415
+ if i < dcdata[r].size
416
+ data[index] = dcdata[r][i]
417
+ index += 1
418
+ end
419
+ end
420
+ end
421
+
422
+ max_ec_count.times do |i|
423
+ rs_blocks.size.times do |r|
424
+ if i < ecdata[r].size
425
+ data[index] = ecdata[r][i]
426
+ index += 1
427
+ end
428
+ end
429
+ end
430
+
431
+ data
432
+ end
433
+ end
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ module QRCode
7
+ module Encoder
8
+ MODE = {
9
+ mode_number: 1 << 0, # 1 (binary: 0001)
10
+ mode_alpha_numk: 1 << 1, # 2 (binary: 0010)
11
+ mode_8bit_byte: 1 << 2 # 4 (binary: 0100)
12
+ }.freeze
13
+
14
+ MODE_NAME = {
15
+ number: :mode_number,
16
+ alphanumeric: :mode_alpha_numk,
17
+ byte_8bit: :mode_8bit_byte,
18
+ multi: :mode_multi
19
+ }.freeze
20
+
21
+ # Error correction levels as defined in ISO/IEC 18004 QR Code specification
22
+ # These exact numeric values are encoded in the format information of every QR code
23
+ # and are standardized across all QR code implementations worldwide.
24
+ #
25
+ # The seemingly random order (1,0,3,2) represents the official 2-bit binary encoding:
26
+ # L = 01 binary = 1 decimal (~7% error correction)
27
+ # M = 00 binary = 0 decimal (~15% error correction)
28
+ # Q = 11 binary = 3 decimal (~25% error correction)
29
+ # H = 10 binary = 2 decimal (~30% error correction)
30
+ #
31
+ # These values MUST NOT be changed as they are part of the global QR Code standard.
32
+ ERROR_CORRECTION_LEVEL = {
33
+ l: 1, # Low - ~7% of codewords can be restored
34
+ m: 0, # Medium - ~15% of codewords can be restored
35
+ q: 3, # Quartile - ~25% of codewords can be restored
36
+ h: 2 # High - ~30% of codewords can be restored
37
+ }.freeze
38
+
39
+ MASK_PATTERN = {
40
+ pattern000: 0,
41
+ pattern001: 1,
42
+ pattern010: 2,
43
+ pattern011: 3,
44
+ pattern100: 4,
45
+ pattern101: 5,
46
+ pattern110: 6,
47
+ pattern111: 7
48
+ }.freeze
49
+
50
+ MASK_COMPUTATIONS = [
51
+ proc {|i, j| (i + j) % 2 == 0},
52
+ proc {|i, j| i % 2 == 0},
53
+ proc {|i, j| j % 3 == 0},
54
+ proc {|i, j| (i + j) % 3 == 0},
55
+ proc {|i, j| ((i / 2) + (j / 3)) % 2 == 0},
56
+ proc {|i, j| ((i * j) % 2) + ((i * j) % 3) == 0},
57
+ proc {|i, j| (((i * j) % 2) + ((i * j) % 3)) % 2 == 0},
58
+ proc {|i, j| (((i * j) % 3) + ((i + j) % 2)) % 2 == 0}
59
+ ].freeze
60
+
61
+ POSITION_PATTERN_LENGTH = (7 + 1) * 2 + 1
62
+ FORMAT_INFO_LENGTH = 15
63
+
64
+ # max bits by version
65
+ # version 1: 26 x 26 = 676
66
+ # version 40: 177 x 177 = 31329
67
+ # 31329 / 8 = 3916 bytes
68
+ MAX_BITS = {
69
+ l: [152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888, 7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880, 14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648],
70
+ m: [128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352, 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984, 11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672],
71
+ q: [104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880, 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328],
72
+ h: [72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080, 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208]
73
+ }.freeze
74
+ end
75
+ end