rqrcode_core 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.
@@ -0,0 +1,579 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RQRCodeCore
4
+ QRMODE = {
5
+ :mode_number => 1 << 0,
6
+ :mode_alpha_numk => 1 << 1,
7
+ :mode_8bit_byte => 1 << 2,
8
+ }
9
+
10
+ QRMODE_NAME = {
11
+ :number => :mode_number,
12
+ :alphanumeric => :mode_alpha_numk,
13
+ :byte_8bit => :mode_8bit_byte
14
+ }
15
+
16
+ QRERRORCORRECTLEVEL = {
17
+ :l => 1,
18
+ :m => 0,
19
+ :q => 3,
20
+ :h => 2
21
+ }
22
+
23
+ QRMASKPATTERN = {
24
+ :pattern000 => 0,
25
+ :pattern001 => 1,
26
+ :pattern010 => 2,
27
+ :pattern011 => 3,
28
+ :pattern100 => 4,
29
+ :pattern101 => 5,
30
+ :pattern110 => 6,
31
+ :pattern111 => 7
32
+ }
33
+
34
+ QRMASKCOMPUTATIONS = [
35
+ Proc.new { |i,j| (i + j) % 2 == 0 },
36
+ Proc.new { |i,j| i % 2 == 0 },
37
+ Proc.new { |i,j| j % 3 == 0 },
38
+ Proc.new { |i,j| (i + j) % 3 == 0 },
39
+ Proc.new { |i,j| ((i / 2).floor + (j / 3).floor) % 2 == 0 },
40
+ Proc.new { |i,j| (i * j) % 2 + (i * j) % 3 == 0 },
41
+ Proc.new { |i,j| ((i * j) % 2 + (i * j) % 3) % 2 == 0 },
42
+ Proc.new { |i,j| ((i * j) % 3 + (i + j) % 2) % 2 == 0 },
43
+ ]
44
+
45
+ QRPOSITIONPATTERNLENGTH = (7 + 1) * 2 + 1
46
+ QRFORMATINFOLENGTH = 15
47
+
48
+ #http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable1-e.html
49
+ #http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable2-e.html
50
+ #http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable3-e.html
51
+ #http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable4-e.html
52
+ # Each array contains levels max chars from level 1 to level 40
53
+ QRMAXDIGITS = {
54
+ l: {
55
+ mode_number: [
56
+ 41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772,
57
+ 883, 1022, 1101, 1250, 1408, 1548, 1725, 1903, 2061,
58
+ 2232, 2409, 2620, 2812, 3057, 3283, 3514, 3669, 3909, 4158,
59
+ 4417, 4686, 4965, 5253, 5529, 5836, 6153, 6479, 6743, 7089
60
+ ],
61
+ mode_alpha_numk: [
62
+ 25, 47, 77, 114, 154, 195, 224, 279, 335, 395,
63
+ 468, 535, 619, 667, 758, 854, 938, 1046, 1153, 1249,
64
+ 1352, 1460, 1588, 1704, 1853, 1990, 2132, 2223, 2369, 2520,
65
+ 2677, 2840, 3009, 3183, 3351, 3537, 3729, 3927, 4087, 4296
66
+ ],
67
+ mode_8bit_byte: [
68
+ 17, 32, 53, 78, 106, 134, 154, 192, 230, 271,
69
+ 321, 367, 425, 458, 520, 586, 644, 718, 792, 858,
70
+ 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732,
71
+ 1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953
72
+ ],
73
+ },
74
+ m: {
75
+ mode_number: [
76
+ 34, 63, 101, 149, 202, 255, 293, 365, 432, 513,
77
+ 604, 691, 796, 871, 991, 1082, 1212, 1346, 1500, 1600,
78
+ 1708, 1872, 2059, 2188, 2395, 2544, 2701, 2857, 3035, 3289,
79
+ 3486, 3693, 3909, 4134, 4343, 4588, 4775, 5039, 5313, 5596,
80
+ ],
81
+ mode_alpha_numk: [
82
+ 20, 38, 61, 90, 122, 154, 178, 221, 262, 311,
83
+ 366, 419, 483, 528, 600, 656, 734, 816, 909, 970,
84
+ 1035, 1134, 1248, 1326, 1451, 1542, 1637, 1732, 1839, 1994,
85
+ 2113, 2238, 2369, 2506, 2632, 2780, 2894, 3054, 3220, 3391
86
+ ],
87
+ mode_8bit_byte: [
88
+ 14, 26, 42, 62, 84, 106, 122, 152, 180, 213,
89
+ 251, 287, 331, 362, 412, 450, 504, 560, 624, 666,
90
+ 711, 779, 857, 911, 997, 1059, 1125, 1190, 1264, 1370,
91
+ 1452, 1538, 1628, 1722, 1809, 1911, 1989, 2099, 2213, 2331
92
+ ],
93
+ },
94
+ q: {
95
+ mode_number: [
96
+ 27, 48, 77, 111, 144, 178, 207, 259, 312, 364,
97
+ 427, 489, 580, 621, 703, 775, 876, 948, 1063, 1159,
98
+ 1224, 1358, 1468, 1588, 1718, 1804, 1933, 2085, 2181, 2358,
99
+ 2473, 2670, 2805, 2949, 3081, 3244, 3417, 3599, 3791, 3993
100
+ ],
101
+ mode_alpha_numk: [
102
+ 16, 29, 47, 67, 87, 108, 125, 157, 189, 221,
103
+ 259, 296, 352, 376, 426, 470, 531, 574, 644, 702,
104
+ 742, 823, 890, 963, 1041, 1094, 1172, 1263, 1322, 1429,
105
+ 1499, 1618, 1700, 1787, 1867, 1966, 2071, 2181, 2298, 2420
106
+ ],
107
+ mode_8bit_byte: [
108
+ 11, 20, 32, 46, 60, 74, 86, 108, 130, 151,
109
+ 177, 203, 241, 258, 292, 22, 364, 394, 442, 482,
110
+ 509, 565, 611, 661, 715, 751, 805, 868, 908, 982,
111
+ 1030, 1112, 1168, 1228, 1283, 1351, 1423, 1499, 1579, 1663
112
+ ],
113
+ },
114
+ h: {
115
+ mode_number: [
116
+ 17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331, 374, 427, 468, 530, 602, 674,
117
+ 331, 374, 427, 468, 530, 602, 674, 746, 813, 919,
118
+ 969, 1056, 1108, 1228, 1286, 1425, 1501, 1581, 1677, 1782,
119
+ 1897, 2022, 2157, 2301, 2361, 2524, 2625, 2735, 2927, 3057
120
+
121
+ ],
122
+ mode_alpha_numk: [
123
+ 10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200,
124
+ 200, 227, 259, 283, 321, 365, 408, 452, 493, 557,
125
+ 587, 640, 672, 744, 779, 864, 910, 958, 1016, 1080,
126
+ 1150, 1226, 1307, 1394, 1431, 1530, 1591, 1658, 1774, 1852
127
+ ],
128
+ mode_8bit_byte: [
129
+ 7, 14, 24, 34, 44, 58, 64, 84, 98, 119,
130
+ 137, 155, 177, 194, 220, 250, 280, 310, 338, 382,
131
+ 403, 439, 461, 511, 535, 593, 625, 658, 698, 742,
132
+ 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273
133
+ ],
134
+ },
135
+ }
136
+
137
+ # StandardErrors
138
+
139
+ class QRCodeArgumentError < ArgumentError; end
140
+ class QRCodeRunTimeError < RuntimeError; end
141
+
142
+ # == Creation
143
+ #
144
+ # QRCode objects expect only one required constructor parameter
145
+ # and an optional hash of any other. Here's a few examples:
146
+ #
147
+ # qr = RQRCodeCore::QRCode.new('hello world')
148
+ # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric)
149
+ #
150
+
151
+ class QRCode
152
+ attr_reader :modules, :module_count, :version
153
+
154
+ # Expects a string to be parsed in, other args are optional
155
+ #
156
+ # # string - the string you wish to encode
157
+ # # size - the size of the qrcode (default 4)
158
+ # # level - the error correction level, can be:
159
+ # * Level :l 7% of code can be restored
160
+ # * Level :m 15% of code can be restored
161
+ # * Level :q 25% of code can be restored
162
+ # * Level :h 30% of code can be restored (default :h)
163
+ # # mode - the mode of the qrcode (defaults to alphanumeric or byte_8bit, depending on the input data):
164
+ # * :number
165
+ # * :alphanumeric
166
+ # * :byte_8bit
167
+ # * :kanji
168
+ #
169
+ # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric)
170
+ #
171
+
172
+ def initialize( string, *args )
173
+ if !string.is_a? String
174
+ raise QRCodeArgumentError, "The passed data is #{string.class}, not String"
175
+ end
176
+
177
+ options = args.extract_options!
178
+ level = (options[:level] || :h).to_sym
179
+
180
+ if !QRERRORCORRECTLEVEL.has_key?(level)
181
+ raise QRCodeArgumentError, "Unknown error correction level `#{level.inspect}`"
182
+ end
183
+
184
+ @data = string
185
+
186
+ mode = QRMODE_NAME[(options[:mode] || '').to_sym]
187
+ # If mode is not explicitely given choose mode according to data type
188
+ mode ||= case
189
+ when RQRCodeCore::QRNumeric.valid_data?(@data)
190
+ QRMODE_NAME[:number]
191
+ when QRAlphanumeric.valid_data?(@data)
192
+ QRMODE_NAME[:alphanumeric]
193
+ else
194
+ QRMODE_NAME[:byte_8bit]
195
+ end
196
+
197
+ max_size_array = QRMAXDIGITS[level][mode]
198
+ size = options[:size] || smallest_size_for(string, max_size_array)
199
+
200
+ if size > QRUtil.max_size
201
+ raise QRCodeArgumentError, "Given size greater than maximum possible size of #{QRUtil.max_size}"
202
+ end
203
+
204
+ @error_correct_level = QRERRORCORRECTLEVEL[level]
205
+ @version = size
206
+ @module_count = @version * 4 + QRPOSITIONPATTERNLENGTH
207
+ @modules = Array.new( @module_count )
208
+ @data_list =
209
+ case mode
210
+ when :mode_number
211
+ QRNumeric.new( @data )
212
+ when :mode_alpha_numk
213
+ QRAlphanumeric.new( @data )
214
+ else
215
+ QR8bitByte.new( @data )
216
+ end
217
+
218
+ @data_cache = nil
219
+ self.make
220
+ end
221
+
222
+ # <tt>checked?</tt> is called with a +col+ and +row+ parameter. This will
223
+ # return true or false based on whether that coordinate exists in the
224
+ # matrix returned. It would normally be called while iterating through
225
+ # <tt>modules</tt>. A simple example would be:
226
+ #
227
+ # instance.checked?( 10, 10 ) => true
228
+ #
229
+
230
+ def checked?( row, col )
231
+ if !row.between?(0, @module_count - 1) || !col.between?(0, @module_count - 1)
232
+ raise QRCodeRunTimeError, "Invalid row/column pair: #{row}, #{col}"
233
+ end
234
+ @modules[row][col]
235
+ end
236
+
237
+ # This is a public method that returns the QR Code you have
238
+ # generated as a string. It will not be able to be read
239
+ # in this format by a QR Code reader, but will give you an
240
+ # idea if the final outout. It takes two optional args
241
+ # +:true+ and +:false+ which are there for you to choose
242
+ # how the output looks. Here's an example of it's use:
243
+ #
244
+ # instance.to_s =>
245
+ # xxxxxxx x x x x x xx xxxxxxx
246
+ # x x xxx xxxxxx xxx x x
247
+ # x xxx x xxxxx x xx x xxx x
248
+ #
249
+ # instance.to_s( dark: 'E', light: 'Q' ) =>
250
+ # EEEEEEEQEQQEQEQQQEQEQQEEQQEEEEEEE
251
+ # EQQQQQEQQEEEQQEEEEEEQEEEQQEQQQQQE
252
+ # EQEEEQEQQEEEEEQEQQQQQQQEEQEQEEEQE
253
+ #
254
+
255
+ def to_s( *args )
256
+ options = args.extract_options!
257
+ dark = options[:dark] || options[:true] || 'x'
258
+ light = options[:light] || options[:false] || ' '
259
+ quiet_zone_size = options[:quiet_zone_size] || 0
260
+
261
+ rows = []
262
+
263
+ @modules.each do |row|
264
+ cols = light * quiet_zone_size
265
+ row.each do |col|
266
+ cols += (col ? dark : light)
267
+ end
268
+ rows << cols
269
+ end
270
+
271
+ quiet_zone_size.times do
272
+ rows.unshift(light * (rows.first.length / light.size))
273
+ rows << light * (rows.first.length / light.size)
274
+ end
275
+ rows.join("\n")
276
+ end
277
+
278
+ # Public overide as default inspect is very verbose
279
+ #
280
+ # RQRCodeCore::QRCode.new('my string to generate', size: 4, level: :h)
281
+ # => QRCodeCore: @data='my string to generate', @error_correct_level=2, @version=4, @module_count=33
282
+ #
283
+
284
+ def inspect
285
+ "QRCodeCore: @data='#{@data}', @error_correct_level=#{@error_correct_level}, @version=#{@version}, @module_count=#{@module_count}"
286
+ end
287
+
288
+ # Return a symbol for current error connection level
289
+ def error_correction_level
290
+ QRERRORCORRECTLEVEL.invert[@error_correct_level]
291
+ end
292
+
293
+ # Return a symbol in QRMODE.keys for current mode used
294
+ def mode
295
+ case @data_list
296
+ when QRNumeric
297
+ :mode_number
298
+ when QRAlphanumeric
299
+ :mode_alpha_numk
300
+ else
301
+ :mode_8bit_byte
302
+ end
303
+ end
304
+
305
+ protected
306
+
307
+ def make #:nodoc:
308
+ prepare_common_patterns
309
+ make_impl( false, get_best_mask_pattern )
310
+ end
311
+
312
+ private
313
+
314
+ def prepare_common_patterns
315
+ @modules.map! { |row| Array.new(@module_count) }
316
+
317
+ place_position_probe_pattern(0, 0)
318
+ place_position_probe_pattern(@module_count - 7, 0)
319
+ place_position_probe_pattern(0, @module_count - 7)
320
+ place_position_adjust_pattern
321
+ place_timing_pattern
322
+
323
+ @common_patterns = @modules.map(&:clone)
324
+ end
325
+
326
+ def make_impl( test, mask_pattern ) #:nodoc:
327
+ @modules = @common_patterns.map(&:clone)
328
+
329
+ place_format_info(test, mask_pattern)
330
+ place_version_info(test) if @version >= 7
331
+
332
+ if @data_cache.nil?
333
+ @data_cache = QRCode.create_data(
334
+ @version, @error_correct_level, @data_list
335
+ )
336
+ end
337
+
338
+ map_data( @data_cache, mask_pattern )
339
+ end
340
+
341
+
342
+ def place_position_probe_pattern( row, col ) #:nodoc:
343
+ (-1..7).each do |r|
344
+ next if !(row + r).between?(0, @module_count - 1)
345
+
346
+ (-1..7).each do |c|
347
+ next if !(col + c).between?(0, @module_count - 1)
348
+
349
+ is_vert_line = (r.between?(0, 6) && (c == 0 || c == 6))
350
+ is_horiz_line = (c.between?(0, 6) && (r == 0 || r == 6))
351
+ is_square = r.between?(2,4) && c.between?(2, 4)
352
+
353
+ is_part_of_probe = is_vert_line || is_horiz_line || is_square
354
+ @modules[row + r][col + c] = is_part_of_probe
355
+ end
356
+ end
357
+ end
358
+
359
+
360
+ def get_best_mask_pattern #:nodoc:
361
+ min_lost_point = 0
362
+ pattern = 0
363
+
364
+ ( 0...8 ).each do |i|
365
+ make_impl( true, i )
366
+ lost_point = QRUtil.get_lost_points(self.modules)
367
+
368
+ if i == 0 || min_lost_point > lost_point
369
+ min_lost_point = lost_point
370
+ pattern = i
371
+ end
372
+ end
373
+ pattern
374
+ end
375
+
376
+
377
+ def place_timing_pattern #:nodoc:
378
+ ( 8...@module_count - 8 ).each do |i|
379
+ @modules[i][6] = @modules[6][i] = i % 2 == 0
380
+ end
381
+ end
382
+
383
+
384
+ def place_position_adjust_pattern #:nodoc:
385
+ positions = QRUtil.get_pattern_positions(@version)
386
+
387
+ positions.each do |row|
388
+ positions.each do |col|
389
+ next unless @modules[row][col].nil?
390
+
391
+ ( -2..2 ).each do |r|
392
+ ( -2..2 ).each do |c|
393
+ is_part_of_pattern = (r.abs == 2 || c.abs == 2 || ( r == 0 && c == 0 ))
394
+ @modules[row + r][col + c] = is_part_of_pattern
395
+ end
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+
402
+ def place_version_info(test) #:nodoc:
403
+ bits = QRUtil.get_bch_version(@version)
404
+
405
+ ( 0...18 ).each do |i|
406
+ mod = ( !test && ( (bits >> i) & 1) == 1 )
407
+ @modules[ (i / 3).floor ][ i % 3 + @module_count - 8 - 3 ] = mod
408
+ @modules[ i % 3 + @module_count - 8 - 3 ][ (i / 3).floor ] = mod
409
+ end
410
+ end
411
+
412
+
413
+ def place_format_info(test, mask_pattern) #:nodoc:
414
+ data = (@error_correct_level << 3 | mask_pattern)
415
+ bits = QRUtil.get_bch_format_info(data)
416
+
417
+ QRFORMATINFOLENGTH.times do |i|
418
+ mod = (!test && ( (bits >> i) & 1) == 1)
419
+
420
+ # vertical
421
+ if i < 6
422
+ row = i
423
+ elsif i < 8
424
+ row = i + 1
425
+ else
426
+ row = @module_count - 15 + i
427
+ end
428
+ @modules[row][8] = mod
429
+
430
+ # horizontal
431
+ if i < 8
432
+ col = @module_count - i - 1
433
+ elsif i < 9
434
+ col = 15 - i - 1 + 1
435
+ else
436
+ col = 15 - i - 1
437
+ end
438
+ @modules[8][col] = mod
439
+ end
440
+
441
+ # fixed module
442
+ @modules[ @module_count - 8 ][8] = !test
443
+ end
444
+
445
+
446
+ def map_data( data, mask_pattern ) #:nodoc:
447
+ inc = -1
448
+ row = @module_count - 1
449
+ bit_index = 7
450
+ byte_index = 0
451
+
452
+ ( @module_count - 1 ).step( 1, -2 ) do |col|
453
+ col = col - 1 if col <= 6
454
+
455
+ while true do
456
+ ( 0...2 ).each do |c|
457
+
458
+ if @modules[row][ col - c ].nil?
459
+ dark = false
460
+ if byte_index < data.size && !data[byte_index].nil?
461
+ dark = (( (data[byte_index]).rszf( bit_index ) & 1) == 1 )
462
+ end
463
+ mask = QRUtil.get_mask( mask_pattern, row, col - c )
464
+ dark = !dark if mask
465
+ @modules[row][ col - c ] = dark
466
+ bit_index -= 1
467
+
468
+ if bit_index == -1
469
+ byte_index += 1
470
+ bit_index = 7
471
+ end
472
+ end
473
+ end
474
+
475
+ row += inc
476
+
477
+ if row < 0 || @module_count <= row
478
+ row -= inc
479
+ inc = -inc
480
+ break
481
+ end
482
+ end
483
+ end
484
+ end
485
+
486
+ def smallest_size_for(string, max_size_array) #:nodoc:
487
+ l = string.bytesize
488
+ ver = max_size_array.index{|i| i >= l}
489
+ raise QRCodeRunTimeError,"code length overflow. (#{l} digits > any version capacity)" unless ver
490
+ ver + 1
491
+ end
492
+
493
+ def QRCode.count_max_data_bits(rs_blocks)
494
+ max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block|
495
+ sum + rs_block.data_count
496
+ end
497
+
498
+ return max_data_bytes * 8
499
+ end
500
+
501
+ def QRCode.create_data(version, error_correct_level, data_list) #:nodoc:
502
+ rs_blocks = QRRSBlock.get_rs_blocks(version, error_correct_level)
503
+ max_data_bits = QRCode.count_max_data_bits(rs_blocks)
504
+ buffer = QRBitBuffer.new(version)
505
+
506
+ data_list.write(buffer)
507
+ buffer.end_of_message(max_data_bits)
508
+
509
+ if buffer.get_length_in_bits > max_data_bits
510
+ raise QRCodeRunTimeError, "code length overflow. (#{buffer.get_length_in_bits}>#{max_data_bits}). (Try a larger size!)"
511
+ end
512
+
513
+ buffer.pad_until(max_data_bits)
514
+
515
+ QRCode.create_bytes( buffer, rs_blocks )
516
+ end
517
+
518
+
519
+ def QRCode.create_bytes( buffer, rs_blocks ) #:nodoc:
520
+ offset = 0
521
+ max_dc_count = 0
522
+ max_ec_count = 0
523
+ dcdata = Array.new( rs_blocks.size )
524
+ ecdata = Array.new( rs_blocks.size )
525
+
526
+ rs_blocks.each_with_index do |rs_block, r|
527
+ dc_count = rs_block.data_count
528
+ ec_count = rs_block.total_count - dc_count
529
+ max_dc_count = [ max_dc_count, dc_count ].max
530
+ max_ec_count = [ max_ec_count, ec_count ].max
531
+
532
+ dcdata_block = Array.new(dc_count)
533
+ dcdata_block.size.times do |i|
534
+ dcdata_block[i] = 0xff & buffer.buffer[ i + offset ]
535
+ end
536
+ dcdata[r] = dcdata_block
537
+
538
+ offset = offset + dc_count
539
+ rs_poly = QRUtil.get_error_correct_polynomial( ec_count )
540
+ raw_poly = QRPolynomial.new( dcdata[r], rs_poly.get_length - 1 )
541
+ mod_poly = raw_poly.mod( rs_poly )
542
+
543
+ ecdata_block = Array.new(rs_poly.get_length - 1)
544
+ ecdata_block.size.times do |i|
545
+ mod_index = i + mod_poly.get_length - ecdata_block.size
546
+ ecdata_block[i] = mod_index >= 0 ? mod_poly.get( mod_index ) : 0
547
+ end
548
+ ecdata[r] = ecdata_block
549
+ end
550
+
551
+ total_code_count = rs_blocks.reduce(0) do |sum, rs_block|
552
+ sum + rs_block.total_count
553
+ end
554
+
555
+ data = Array.new( total_code_count )
556
+ index = 0
557
+
558
+ max_dc_count.times do |i|
559
+ rs_blocks.size.times do |r|
560
+ if i < dcdata[r].size
561
+ data[index] = dcdata[r][i]
562
+ index += 1
563
+ end
564
+ end
565
+ end
566
+
567
+ max_ec_count.times do |i|
568
+ rs_blocks.size.times do |r|
569
+ if i < ecdata[r].size
570
+ data[index] = ecdata[r][i]
571
+ index += 1
572
+ end
573
+ end
574
+ end
575
+
576
+ data
577
+ end
578
+ end
579
+ end