rqrcode_core 1.2.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea3856aba6c1bc5602b3f7fa4126187fe26c47fdba56447b709871f96f432ff5
4
- data.tar.gz: eab7166c0e00478bcdb12ab6a550a9a9b595401bf5f6c0271c6bac95b722564f
3
+ metadata.gz: 8ae36b5e8654fbcb84247c8c60287e33f2104efa558e5eef0cd085fed1e51394
4
+ data.tar.gz: 462b3b44d61bde0d1a9e50eb617a8bd06f61297450339399f6db6377c0b9f61e
5
5
  SHA512:
6
- metadata.gz: b9d1977c291eaed204644f09d3900b2ea18bde93e1c242628bdf1c4cd30573c046328e3cdbfe294c6ae27370f5963cdfa8454f1f2e6af7bf185e32cd0fefe592
7
- data.tar.gz: 56d9c31b3c056a47ffccc2848f43e0e44d3b3a984f0ef4c332505fae31dadda0ca6adac5ad0566443dbf46a86a2b81b419806cf71a9da6880d51eacd583cbb34
6
+ metadata.gz: ca381a1dab19720731900ee4610053959fce0d2ddaa5132df2bda496d7b1d3a183e4413343d3ca48cd64b383aba15516200557ea1ae4af1cbe947e66822623f7
7
+ data.tar.gz: 75f36956b5f61f6762be2cbdd63409fd3d7b9df243cab6b8d25c7e8e64241ad148d496a386e446637518a872f9e21ca563b9df1d51819140f99bb9767fbcfc57
data/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ - Add comprehensive tests for QR code boundaries and encoding
11
+ - Performance optimisations: 80-90% faster QR code generation across all sizes
12
+ - Optimised demerit calculation functions (`demerit_points_1_same_color`, `demerit_points_2_full_blocks`, `demerit_points_3_dangerous_patterns`)
13
+ - Eliminated nested Range objects and redundant array lookups in hot paths
14
+ - Pre-computed frequently accessed values to reduce calculation overhead
15
+ - Large QR codes (v20) now generate in ~85ms vs ~154ms previously
16
+ - Memory optimisation: 70-76% reduction when using `RQRCODE_CORE_ARCH_BITS=32`
17
+ - Single large QR (v24): 8.53 MB → 2.92 MB
18
+ - Batch generation (100x v1): 37.91 MB → 9.10 MB
19
+ - Also provides 2-4% speed improvement due to better cache locality
20
+ - Add comprehensive benchmarking infrastructure with memory and performance profiling tools
21
+
22
+ ## [2.0.1] - 2025-11-25
23
+
24
+ - Update required_ruby_version to support >= rather than ~> ready for Ruby 4
25
+
26
+ ## [2.0.0] - 2025-04-24
27
+
28
+ - Various README updates
29
+ - Fix for issue #43
30
+ - Fix for issue #42
31
+ - Drop Ruby < 3 to keep up with dependency updates
32
+ - Add option to use ENV `RQRCODE_CORE_ARCH_BITS` to override the bits value (32 or 64) used during the encoding process.
33
+ This has been shown to greatly reduce the memory usage but I can't prove it doesn't break anything for all people.
34
+ Use at your own risk.
35
+ - Fixed the 'Do Your Own Rendering' example code to reflect the current interface.
36
+
10
37
  ## [1.2.0] - 2021-08-26
11
38
 
12
39
  - Added Multi Mode Support which allows for multi-segment encoding. Thanks to [@ssayer](https://github.com/ssayer)
@@ -29,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
29
56
 
30
57
  ### Breaking Changes
31
58
 
32
- - Very niche but a breaking change never the less. The `to_s` method *no longer* accepts the `:true` and `:false` arguments, but prefers `:dark` and `:light`.
59
+ - Very niche but a breaking change never the less. The `to_s` method _no longer_ accepts the `:true` and `:false` arguments, but prefers `:dark` and `:light`.
33
60
 
34
61
  ## [0.2.0] - 2020-12-26
35
62
 
@@ -37,7 +64,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
37
64
 
38
65
  - fix `required_ruby_version` for Ruby 3 support
39
66
 
40
- [unreleased]: https://github.com/whomwah/rqrcode_core/compare/v1.2.0...HEAD
67
+ [unreleased]: https://github.com/whomwah/rqrcode_core/compare/v2.0.1...HEAD
68
+ [2.0.1]: https://github.com/whomwah/rqrcode_core/compare/v2.0.0...v2.0.1
69
+ [2.0.0]: https://github.com/whomwah/rqrcode_core/compare/v1.2.0...v2.0.0
41
70
  [1.2.0]: https://github.com/whomwah/rqrcode_core/compare/v1.1.0...v1.2.0
42
71
  [1.1.0]: https://github.com/whomwah/rqrcode_core/compare/v1.0.0...v1.1.0
43
72
  [1.0.0]: https://github.com/whomwah/rqrcode_core/compare/v0.2.0...v1.0.0
data/README.md CHANGED
@@ -3,14 +3,15 @@
3
3
 
4
4
  # RQRCodeCore
5
5
 
6
- `rqrcode_core` is a library for encoding QR Codes in pure Ruby. It has a simple interface with all the standard qrcode options. It was originally adapted in 2008 from a Javascript library by [Kazuhiko Arase](https://github.com/kazuhikoarase).
6
+ `rqrcode_core` is a library for encoding QR Codes in pure Ruby. It has a simple interface with all the standard QR Code options. It was originally adapted in 2008 from a Javascript library by [Kazuhiko Arase](https://github.com/kazuhikoarase).
7
7
 
8
8
  Features:
9
9
 
10
- * `rqrcode_core` is a Ruby only library. It requires no native libraries. Just Ruby!
11
- * It is an encoding library. You can't decode QR Codes with it.
12
- * The interface is simple and assumes you just want to encode a string into a QR Code, but also allows for encoding multiple segments.
13
- * QR Code is trademarked by Denso Wave inc.
10
+ - `rqrcode_core` is a Ruby only library. It requires no 3rd party libraries. Just Ruby!
11
+ - It is an encoding library. You can't decode QR Codes with it.
12
+ - The interface is simple and assumes you just want to encode a string into a QR Code, but also allows for encoding multiple segments.
13
+ - QR Code is trade marked by Denso Wave inc.
14
+ - Minimum Ruby version is `>= 3.2.0`
14
15
 
15
16
  `rqrcode_core` is the basis of the popular `rqrcode` gem [https://github.com/whomwah/rqrcode]. This gem allows you to generate different renderings of your QR Code, including `png`, `svg` and `ansi`.
16
17
 
@@ -51,7 +52,11 @@ x xxx x xxxxx x xx x xxx x
51
52
 
52
53
  ```ruby
53
54
  $ require "rqrcode_core"
54
- $ qr = RQRCodeCore::QRCode.new([{data: "byteencoded", mode: :byte_8bit}, {data: "A1" * 100, mode: :alphanumeric}, {data: "1" * 500, mode: :number}])
55
+ $ qr = RQRCodeCore::QRCode.new([
56
+ {data: "byteencoded", mode: :byte_8bit},
57
+ {data: "A1" * 100, mode: :alphanumeric},
58
+ {data: "1" * 500, mode: :number}
59
+ ])
55
60
  ```
56
61
 
57
62
  This will create a QR Code with byte encoded, alphanumeric and number segments. Any combination of encodings/segments will work provided it fits within size limits.
@@ -62,7 +67,7 @@ This will create a QR Code with byte encoded, alphanumeric and number segments.
62
67
  require "rqrcode_core"
63
68
 
64
69
  qr = RQRCodeCore::QRCode.new("https://kyan.com")
65
- qr.rows.each do |row|
70
+ qr.modules.each do |row|
66
71
  row.each do |col|
67
72
  print col ? "#" : " "
68
73
  end
@@ -92,13 +97,12 @@ mode - the mode of the QR Code (defaults to alphanumeric or byte_8bit, depending
92
97
  * :number
93
98
  * :alphanumeric
94
99
  * :byte_8bit
95
- * :kanji
96
100
  ```
97
101
 
98
102
  #### Example
99
103
 
100
104
  ```ruby
101
- RQRCodeCore::QRCode.new("http://kyan.com", size: 1, level: :m, mode: :alphanumeric)
105
+ RQRCodeCore::QRCode.new("http://kyan.com", size: 2, level: :m, mode: :byte_8bit)
102
106
  ```
103
107
 
104
108
  ## Development
@@ -128,6 +132,49 @@ $ rake standard # check
128
132
  $ rake standard:fix # fix
129
133
  ```
130
134
 
135
+ ## Performance Optimisation
136
+
137
+ ### Reduce Memory Usage by 70-76%
138
+
139
+ **If you're running on a 64-bit system, you can dramatically reduce memory consumption by setting:**
140
+
141
+ ```ruby
142
+ ENV['RQRCODE_CORE_ARCH_BITS'] = '32'
143
+ ```
144
+
145
+ Or from the command line:
146
+
147
+ ```bash
148
+ RQRCODE_CORE_ARCH_BITS=32 ruby your_script.rb
149
+ ```
150
+
151
+ #### Benchmark Results (64-bit vs 32-bit on 64-bit systems)
152
+
153
+ **Memory Savings:**
154
+ - Single small QR code: 0.38 MB → 0.10 MB (**74% reduction**)
155
+ - Single large QR code: 8.53 MB → 2.92 MB (**66% reduction**)
156
+ - 100 small QR codes: 37.91 MB → 9.10 MB (**76% reduction**)
157
+ - 10 large QR codes: 85.32 MB → 29.19 MB (**66% reduction**)
158
+
159
+ **Speed Improvement:**
160
+ - 2-4% faster across all scenarios (better cache utilization, reduced GC pressure)
161
+
162
+ **Object Allocation:**
163
+ - 85-87% fewer objects allocated
164
+ - Integer allocations nearly eliminated (from 70-76% to ~0%)
165
+
166
+ #### Why This Works
167
+
168
+ The QR code algorithm doesn't require 64-bit integers for its bit manipulation operations—32-bit is sufficient for all calculations. By default, Ruby on 64-bit systems uses 64-bit integers, which causes unnecessary memory allocation during the internal "right shift zero fill" operations.
169
+
170
+ **Recommendation:** Use `RQRCODE_CORE_ARCH_BITS=32` for production workloads, especially when:
171
+ - Generating QR codes in batch
172
+ - Running in memory-constrained environments
173
+ - Handling high-concurrency web requests
174
+ - Processing large QR codes (version 10+)
175
+
176
+ See `test/benchmarks/ARCH_BITS_ANALYSIS.md` for detailed benchmark data and analysis.
177
+
131
178
  ## Contributing
132
179
 
133
180
  Bug reports and pull requests are welcome on GitHub at https://github.com/whomwah/rqrcode_core.
@@ -15,12 +15,12 @@ module RQRCodeCore
15
15
 
16
16
  def get(index)
17
17
  buf_index = (index / 8).floor
18
- ((QRUtil.rszf(@buffer[buf_index], 7 - index % 8)) & 1) == 1
18
+ (QRUtil.rszf(@buffer[buf_index], 7 - index % 8) & 1) == 1
19
19
  end
20
20
 
21
21
  def put(num, length)
22
22
  (0...length).each do |i|
23
- put_bit(((QRUtil.rszf(num, length - i - 1)) & 1) == 1)
23
+ put_bit((QRUtil.rszf(num, length - i - 1) & 1) == 1)
24
24
  end
25
25
  end
26
26
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  module RQRCodeCore
4
4
  QRMODE = {
5
- mode_number: 1 << 0,
6
- mode_alpha_numk: 1 << 1,
7
- mode_8bit_byte: 1 << 2
5
+ mode_number: 1 << 0, # 1 (binary: 0001)
6
+ mode_alpha_numk: 1 << 1, # 2 (binary: 0010)
7
+ mode_8bit_byte: 1 << 2 # 4 (binary: 0100)
8
8
  }.freeze
9
9
 
10
10
  QRMODE_NAME = {
@@ -97,7 +97,6 @@ module RQRCodeCore
97
97
  # * :number
98
98
  # * :alphanumeric
99
99
  # * :byte_8bit
100
- # * :kanji
101
100
  #
102
101
  # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric)
103
102
  # segment_qr = QRCodeCore::QRCode.new({ data: 'foo', mode: :byte_8bit })
@@ -156,6 +155,7 @@ module RQRCodeCore
156
155
  end
157
156
  alias_method :dark?, :checked?
158
157
  extend Gem::Deprecate
158
+
159
159
  deprecate :dark?, :checked?, 2020, 1
160
160
 
161
161
  # This is a public method that returns the QR Code you have
@@ -233,14 +233,14 @@ module RQRCodeCore
233
233
 
234
234
  protected
235
235
 
236
- def make #:nodoc:
236
+ def make # :nodoc:
237
237
  prepare_common_patterns
238
238
  make_impl(false, get_best_mask_pattern)
239
239
  end
240
240
 
241
241
  private
242
242
 
243
- def prepare_common_patterns #:nodoc:
243
+ def prepare_common_patterns # :nodoc:
244
244
  @modules.map! { |row| Array.new(@module_count) }
245
245
 
246
246
  place_position_probe_pattern(0, 0)
@@ -252,7 +252,7 @@ module RQRCodeCore
252
252
  @common_patterns = @modules.map(&:clone)
253
253
  end
254
254
 
255
- def make_impl(test, mask_pattern) #:nodoc:
255
+ def make_impl(test, mask_pattern) # :nodoc:
256
256
  @modules = @common_patterns.map(&:clone)
257
257
 
258
258
  place_format_info(test, mask_pattern)
@@ -267,15 +267,15 @@ module RQRCodeCore
267
267
  map_data(@data_cache, mask_pattern)
268
268
  end
269
269
 
270
- def place_position_probe_pattern(row, col) #:nodoc:
270
+ def place_position_probe_pattern(row, col) # :nodoc:
271
271
  (-1..7).each do |r|
272
272
  next unless (row + r).between?(0, @module_count - 1)
273
273
 
274
274
  (-1..7).each do |c|
275
275
  next unless (col + c).between?(0, @module_count - 1)
276
276
 
277
- is_vert_line = (r.between?(0, 6) && (c == 0 || c == 6))
278
- is_horiz_line = (c.between?(0, 6) && (r == 0 || r == 6))
277
+ is_vert_line = r.between?(0, 6) && (c == 0 || c == 6)
278
+ is_horiz_line = c.between?(0, 6) && (r == 0 || r == 6)
279
279
  is_square = r.between?(2, 4) && c.between?(2, 4)
280
280
 
281
281
  is_part_of_probe = is_vert_line || is_horiz_line || is_square
@@ -284,11 +284,11 @@ module RQRCodeCore
284
284
  end
285
285
  end
286
286
 
287
- def get_best_mask_pattern #:nodoc:
287
+ def get_best_mask_pattern # :nodoc:
288
288
  min_lost_point = 0
289
289
  pattern = 0
290
290
 
291
- (0...8).each do |i|
291
+ 8.times do |i|
292
292
  make_impl(true, i)
293
293
  lost_point = QRUtil.get_lost_points(modules)
294
294
 
@@ -300,13 +300,13 @@ module RQRCodeCore
300
300
  pattern
301
301
  end
302
302
 
303
- def place_timing_pattern #:nodoc:
303
+ def place_timing_pattern # :nodoc:
304
304
  (8...@module_count - 8).each do |i|
305
305
  @modules[i][6] = @modules[6][i] = i % 2 == 0
306
306
  end
307
307
  end
308
308
 
309
- def place_position_adjust_pattern #:nodoc:
309
+ def place_position_adjust_pattern # :nodoc:
310
310
  positions = QRUtil.get_pattern_positions(@version)
311
311
 
312
312
  positions.each do |row|
@@ -315,7 +315,7 @@ module RQRCodeCore
315
315
 
316
316
  (-2..2).each do |r|
317
317
  (-2..2).each do |c|
318
- is_part_of_pattern = (r.abs == 2 || c.abs == 2 || (r == 0 && c == 0))
318
+ is_part_of_pattern = r.abs == 2 || c.abs == 2 || (r == 0 && c == 0)
319
319
  @modules[row + r][col + c] = is_part_of_pattern
320
320
  end
321
321
  end
@@ -323,22 +323,22 @@ module RQRCodeCore
323
323
  end
324
324
  end
325
325
 
326
- def place_version_info(test) #:nodoc:
326
+ def place_version_info(test) # :nodoc:
327
327
  bits = QRUtil.get_bch_version(@version)
328
328
 
329
- (0...18).each do |i|
330
- mod = (!test && ((bits >> i) & 1) == 1)
329
+ 18.times do |i|
330
+ mod = !test && ((bits >> i) & 1) == 1
331
331
  @modules[(i / 3).floor][ i % 3 + @module_count - 8 - 3 ] = mod
332
332
  @modules[i % 3 + @module_count - 8 - 3][ (i / 3).floor ] = mod
333
333
  end
334
334
  end
335
335
 
336
- def place_format_info(test, mask_pattern) #:nodoc:
336
+ def place_format_info(test, mask_pattern) # :nodoc:
337
337
  data = (@error_correct_level << 3 | mask_pattern)
338
338
  bits = QRUtil.get_bch_format_info(data)
339
339
 
340
340
  QRFORMATINFOLENGTH.times do |i|
341
- mod = (!test && ((bits >> i) & 1) == 1)
341
+ mod = !test && ((bits >> i) & 1) == 1
342
342
 
343
343
  # vertical
344
344
  row = if i < 6
@@ -365,7 +365,7 @@ module RQRCodeCore
365
365
  @modules[@module_count - 8][8] = !test
366
366
  end
367
367
 
368
- def map_data(data, mask_pattern) #:nodoc:
368
+ def map_data(data, mask_pattern) # :nodoc:
369
369
  inc = -1
370
370
  row = @module_count - 1
371
371
  bit_index = 7
@@ -375,7 +375,7 @@ module RQRCodeCore
375
375
  col -= 1 if col <= 6
376
376
 
377
377
  loop do
378
- (0...2).each do |c|
378
+ 2.times do |c|
379
379
  if @modules[row][col - c].nil?
380
380
  dark = false
381
381
  if byte_index < data.size && !data[byte_index].nil?
@@ -416,12 +416,12 @@ module RQRCodeCore
416
416
  minimum_version(limit: limit, version: version + 1)
417
417
  end
418
418
 
419
- def extract_options!(arr) #:nodoc:
419
+ def extract_options!(arr) # :nodoc:
420
420
  arr.last.is_a?(::Hash) ? arr.pop : {}
421
421
  end
422
422
 
423
423
  class << self
424
- def count_max_data_bits(rs_blocks) #:nodoc:
424
+ def count_max_data_bits(rs_blocks) # :nodoc:
425
425
  max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block|
426
426
  sum + rs_block.data_count
427
427
  end
@@ -429,7 +429,7 @@ module RQRCodeCore
429
429
  max_data_bytes * 8
430
430
  end
431
431
 
432
- def create_data(version, error_correct_level, data_list) #:nodoc:
432
+ def create_data(version, error_correct_level, data_list) # :nodoc:
433
433
  rs_blocks = QRRSBlock.get_rs_blocks(version, error_correct_level)
434
434
  max_data_bits = QRCode.count_max_data_bits(rs_blocks)
435
435
  buffer = QRBitBuffer.new(version)
@@ -446,7 +446,7 @@ module RQRCodeCore
446
446
  QRCode.create_bytes(buffer, rs_blocks)
447
447
  end
448
448
 
449
- def create_bytes(buffer, rs_blocks) #:nodoc:
449
+ def create_bytes(buffer, rs_blocks) # :nodoc:
450
450
  offset = 0
451
451
  max_dc_count = 0
452
452
  max_ec_count = 0
@@ -473,7 +473,7 @@ module RQRCodeCore
473
473
  ecdata_block = Array.new(rs_poly.get_length - 1)
474
474
  ecdata_block.size.times do |i|
475
475
  mod_index = i + mod_poly.get_length - ecdata_block.size
476
- ecdata_block[i] = mod_index >= 0 ? mod_poly.get(mod_index) : 0
476
+ ecdata_block[i] = (mod_index >= 0) ? mod_poly.get(mod_index) : 0
477
477
  end
478
478
  ecdata[r] = ecdata_block
479
479
  end
@@ -6,7 +6,7 @@ module RQRCodeCore
6
6
  exp_table = Array.new(256)
7
7
  log_table = Array.new(256)
8
8
 
9
- (0...8).each do |i|
9
+ 8.times do |i|
10
10
  exp_table[i] = 1 << i
11
11
  end
12
12
 
@@ -17,7 +17,7 @@ module RQRCodeCore
17
17
  ^ exp_table[i - 8]
18
18
  end
19
19
 
20
- (0...255).each do |i|
20
+ 255.times do |i|
21
21
  log_table[exp_table[i]] = i
22
22
  end
23
23
 
@@ -8,7 +8,7 @@ module RQRCodeCore
8
8
  @data = data
9
9
  @mode = QRMODE_NAME.dig(mode&.to_sym)
10
10
 
11
- # If mode is not explicitely found choose mode according to data type
11
+ # If mode is not explicitly found choose mode according to data type
12
12
  @mode ||= if RQRCodeCore::QRNumeric.valid_data?(@data)
13
13
  QRMODE_NAME[:number]
14
14
  elsif QRAlphanumeric.valid_data?(@data)
@@ -36,7 +36,7 @@ module RQRCodeCore
36
36
  [1, 8, 0]
37
37
  end
38
38
 
39
- (data_length / chunk_size) * bit_length + ((data_length % chunk_size) == 0 ? 0 : extra)
39
+ (data_length / chunk_size) * bit_length + (((data_length % chunk_size) == 0) ? 0 : extra)
40
40
  end
41
41
 
42
42
  def writer
@@ -57,10 +57,25 @@ module RQRCodeCore
57
57
  BITS_FOR_MODE = {
58
58
  QRMODE[:mode_number] => [10, 12, 14],
59
59
  QRMODE[:mode_alpha_numk] => [9, 11, 13],
60
- QRMODE[:mode_8bit_byte] => [8, 16, 16],
61
- QRMODE[:mode_kanji] => [8, 10, 12]
60
+ QRMODE[:mode_8bit_byte] => [8, 16, 16]
62
61
  }.freeze
63
62
 
63
+ # This value is used during the right shift zero fill step (rszf method).
64
+ # Auto-set to 32 or 64 depending on system architecture (1.size * 8).
65
+ #
66
+ # PERFORMANCE IMPACT (64-bit vs 32-bit on 64-bit systems):
67
+ # - Memory: 70-76% reduction (e.g., 37.91 MB → 9.10 MB for 100 small QR codes)
68
+ # - Speed: 2-4% faster with 32-bit
69
+ # - Objects: 85-87% fewer allocations
70
+ # - All tests pass with ARCH_BITS=32
71
+ #
72
+ # RECOMMENDATION: Use RQRCODE_CORE_ARCH_BITS=32 even on 64-bit systems
73
+ # for dramatic memory savings with no downsides. The QR code algorithm
74
+ # doesn't require 64-bit integers—32-bit is sufficient for all operations.
75
+ #
76
+ # See test/benchmarks/ARCH_BITS_ANALYSIS.md for detailed benchmark data.
77
+ ARCH_BITS = ENV.fetch("RQRCODE_CORE_ARCH_BITS", nil)&.to_i || 1.size * 8
78
+
64
79
  def self.max_size
65
80
  PATTERN_POSITION_TABLE.count
66
81
  end
@@ -74,8 +89,8 @@ module RQRCodeCore
74
89
  end
75
90
 
76
91
  def self.rszf(num, count)
77
- # zero fill right shift
78
- (num >> count) & ((2**((num.size * 8) - count)) - 1)
92
+ # right shift zero fill
93
+ (num >> count) & ((1 << (ARCH_BITS - count)) - 1)
79
94
  end
80
95
 
81
96
  def self.get_bch_version(data)
@@ -102,9 +117,7 @@ module RQRCodeCore
102
117
  end
103
118
 
104
119
  def self.get_mask(mask_pattern, i, j)
105
- if mask_pattern > QRMASKCOMPUTATIONS.size
106
- raise QRCodeRunTimeError, "bad mask_pattern: #{mask_pattern}"
107
- end
120
+ raise QRCodeRunTimeError, "bad mask_pattern: #{mask_pattern}" if mask_pattern > QRMASKCOMPUTATIONS.size
108
121
 
109
122
  QRMASKCOMPUTATIONS[mask_pattern].call(i, j)
110
123
  end
@@ -120,13 +133,9 @@ module RQRCodeCore
120
133
  end
121
134
 
122
135
  def self.get_length_in_bits(mode, version)
123
- if !QRMODE.value?(mode)
124
- raise QRCodeRunTimeError, "Unknown mode: #{mode}"
125
- end
136
+ raise QRCodeRunTimeError, "Unknown mode: #{mode}" unless QRMODE.value?(mode)
126
137
 
127
- if version > 40
128
- raise QRCodeRunTimeError, "Unknown version: #{version}"
129
- end
138
+ raise QRCodeRunTimeError, "Unknown version: #{version}" if version > 40
130
139
 
131
140
  if version.between?(1, 9)
132
141
  # 1 - 9
@@ -156,28 +165,40 @@ module RQRCodeCore
156
165
  def self.demerit_points_1_same_color(modules)
157
166
  demerit_points = 0
158
167
  module_count = modules.size
168
+ max_index = module_count - 1
159
169
 
160
170
  # level1
161
- (0...module_count).each do |row|
162
- (0...module_count).each do |col|
171
+ module_count.times do |row|
172
+ modules_row = modules[row]
173
+
174
+ module_count.times do |col|
163
175
  same_count = 0
164
- dark = modules[row][col]
165
-
166
- (-1..1).each do |r|
167
- next if row + r < 0 || module_count <= row + r
168
-
169
- (-1..1).each do |c|
170
- next if col + c < 0 || module_count <= col + c
171
- next if r == 0 && c == 0
172
- if dark == modules[row + r][col + c]
173
- same_count += 1
174
- end
175
- end
176
+ dark = modules_row[col]
177
+
178
+ # Check 3x3 neighborhood, but skip center cell
179
+ # Unroll loops and eliminate range objects for performance
180
+
181
+ # Row above (row - 1)
182
+ if row > 0
183
+ row_above = modules[row - 1]
184
+ same_count += 1 if col > 0 && dark == row_above[col - 1]
185
+ same_count += 1 if dark == row_above[col]
186
+ same_count += 1 if col < max_index && dark == row_above[col + 1]
176
187
  end
177
188
 
178
- if same_count > 5
179
- demerit_points += (DEMERIT_POINTS_1 + same_count - 5)
189
+ # Same row
190
+ same_count += 1 if col > 0 && dark == modules_row[col - 1]
191
+ same_count += 1 if col < max_index && dark == modules_row[col + 1]
192
+
193
+ # Row below (row + 1)
194
+ if row < max_index
195
+ row_below = modules[row + 1]
196
+ same_count += 1 if col > 0 && dark == row_below[col - 1]
197
+ same_count += 1 if dark == row_below[col]
198
+ same_count += 1 if col < max_index && dark == row_below[col + 1]
180
199
  end
200
+
201
+ demerit_points += (DEMERIT_POINTS_1 + same_count - 5) if same_count > 5
181
202
  end
182
203
  end
183
204
 
@@ -187,16 +208,19 @@ module RQRCodeCore
187
208
  def self.demerit_points_2_full_blocks(modules)
188
209
  demerit_points = 0
189
210
  module_count = modules.size
190
-
191
- # level 2
192
- (0...(module_count - 1)).each do |row|
193
- (0...(module_count - 1)).each do |col|
194
- count = 0
195
- count += 1 if modules[row][col]
196
- count += 1 if modules[row + 1][col]
197
- count += 1 if modules[row][col + 1]
198
- count += 1 if modules[row + 1][col + 1]
199
- if count == 0 || count == 4
211
+ max_row = module_count - 1
212
+
213
+ # level 2: Check for 2x2 blocks of same color
214
+ # Only need to check (module_count - 1) x (module_count - 1) positions
215
+ max_row.times do |row|
216
+ row_curr = modules[row]
217
+ row_next = modules[row + 1]
218
+
219
+ max_row.times do |col|
220
+ # Check if all 4 modules in 2x2 block have same color
221
+ # (count == 0: all false, count == 4: all true)
222
+ val = row_curr[col]
223
+ if val == row_next[col] && val == row_curr[col + 1] && val == row_next[col + 1]
200
224
  demerit_points += DEMERIT_POINTS_2
201
225
  end
202
226
  end
@@ -208,31 +232,27 @@ module RQRCodeCore
208
232
  def self.demerit_points_3_dangerous_patterns(modules)
209
233
  demerit_points = 0
210
234
  module_count = modules.size
235
+ pattern_len = 7
236
+ max_start = module_count - pattern_len + 1
237
+
238
+ # level 3: Check for dangerous pattern [dark, light, dark, dark, dark, light, dark]
239
+ # Pattern: true, false, true, true, true, false, true (1:1:3:1:1 ratio)
211
240
 
212
- # level 3
241
+ # Check rows
213
242
  modules.each do |row|
214
- (module_count - 6).times do |col_idx|
215
- if row[col_idx] &&
216
- !row[col_idx + 1] &&
217
- row[col_idx + 2] &&
218
- row[col_idx + 3] &&
219
- row[col_idx + 4] &&
220
- !row[col_idx + 5] &&
221
- row[col_idx + 6]
243
+ max_start.times do |col|
244
+ if row[col] && !row[col + 1] && row[col + 2] &&
245
+ row[col + 3] && row[col + 4] && !row[col + 5] && row[col + 6]
222
246
  demerit_points += DEMERIT_POINTS_3
223
247
  end
224
248
  end
225
249
  end
226
250
 
227
- (0...module_count).each do |col|
228
- (0...(module_count - 6)).each do |row|
229
- if modules[row][col] &&
230
- !modules[row + 1][col] &&
231
- modules[row + 2][col] &&
232
- modules[row + 3][col] &&
233
- modules[row + 4][col] &&
234
- !modules[row + 5][col] &&
235
- modules[row + 6][col]
251
+ # Check columns
252
+ module_count.times do |col|
253
+ max_start.times do |row|
254
+ if modules[row][col] && !modules[row + 1][col] && modules[row + 2][col] &&
255
+ modules[row + 3][col] && modules[row + 4][col] && !modules[row + 5][col] && modules[row + 6][col]
236
256
  demerit_points += DEMERIT_POINTS_3
237
257
  end
238
258
  end
@@ -247,7 +267,8 @@ module RQRCodeCore
247
267
  sum + col.count(true)
248
268
  end
249
269
 
250
- ratio = dark_count / (modules.size * modules.size)
270
+ # Convert to float to prevent integer division
271
+ ratio = dark_count.to_f / (modules.size * modules.size)
251
272
  ratio_delta = (100 * ratio - 50).abs / 5
252
273
 
253
274
  ratio_delta * DEMERIT_POINTS_4
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RQRCodeCore
4
- VERSION = "1.2.0"
4
+ VERSION = "2.1.0"
5
5
  end
metadata CHANGED
@@ -1,17 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rqrcode_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duncan Robertson
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2021-08-26 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: bundler
13
+ name: benchmark-ips
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
@@ -25,47 +24,89 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '2.0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: rake
27
+ name: bundler
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: '13.0'
32
+ version: '4.0'
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: '13.0'
39
+ version: '4.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: memory_profiler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: minitest
43
56
  requirement: !ruby/object:Gem::Requirement
44
57
  requirements:
45
58
  - - "~>"
46
59
  - !ruby/object:Gem::Version
47
- version: '5.0'
60
+ version: '6.0'
48
61
  type: :development
49
62
  prerelease: false
50
63
  version_requirements: !ruby/object:Gem::Requirement
51
64
  requirements:
52
65
  - - "~>"
53
66
  - !ruby/object:Gem::Version
54
- version: '5.0'
67
+ version: '6.0'
55
68
  - !ruby/object:Gem::Dependency
56
- name: standardrb
69
+ name: rake
57
70
  requirement: !ruby/object:Gem::Requirement
58
71
  requirements:
59
72
  - - "~>"
60
73
  - !ruby/object:Gem::Version
61
- version: '1.0'
74
+ version: '13.3'
62
75
  type: :development
63
76
  prerelease: false
64
77
  version_requirements: !ruby/object:Gem::Requirement
65
78
  requirements:
66
79
  - - "~>"
67
80
  - !ruby/object:Gem::Version
68
- version: '1.0'
81
+ version: '13.3'
82
+ - !ruby/object:Gem::Dependency
83
+ name: stackprof
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.2'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.2'
96
+ - !ruby/object:Gem::Dependency
97
+ name: standard
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.41'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.41'
69
110
  description: |
70
111
  rqrcode_core is a Ruby library for encoding QR Codes. The simple
71
112
  interface (with no runtime dependencies) allows you to create QR Code data structures.
@@ -75,16 +116,9 @@ executables: []
75
116
  extensions: []
76
117
  extra_rdoc_files: []
77
118
  files:
78
- - ".github/workflows/ruby.yml"
79
- - ".gitignore"
80
119
  - CHANGELOG.md
81
- - Gemfile
82
- - Gemfile.lock
83
120
  - LICENSE.txt
84
121
  - README.md
85
- - Rakefile
86
- - bin/console
87
- - bin/setup
88
122
  - lib/rqrcode_core.rb
89
123
  - lib/rqrcode_core/qrcode.rb
90
124
  - lib/rqrcode_core/qrcode/qr_8bit_byte.rb
@@ -99,12 +133,12 @@ files:
99
133
  - lib/rqrcode_core/qrcode/qr_segment.rb
100
134
  - lib/rqrcode_core/qrcode/qr_util.rb
101
135
  - lib/rqrcode_core/version.rb
102
- - rqrcode_core.gemspec
103
136
  homepage: https://github.com/whomwah/rqrcode_core
104
137
  licenses:
105
138
  - MIT
106
- metadata: {}
107
- post_install_message:
139
+ metadata:
140
+ bug_tracker_uri: https://github.com/whomwah/rqrcode_core/issues
141
+ changelog_uri: https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md
108
142
  rdoc_options: []
109
143
  require_paths:
110
144
  - lib
@@ -112,15 +146,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
146
  requirements:
113
147
  - - ">="
114
148
  - !ruby/object:Gem::Version
115
- version: '2.3'
149
+ version: '3.2'
116
150
  required_rubygems_version: !ruby/object:Gem::Requirement
117
151
  requirements:
118
152
  - - ">="
119
153
  - !ruby/object:Gem::Version
120
154
  version: '0'
121
155
  requirements: []
122
- rubygems_version: 3.2.22
123
- signing_key:
156
+ rubygems_version: 4.0.3
124
157
  specification_version: 4
125
158
  summary: A library to encode QR Codes
126
159
  test_files: []
@@ -1,29 +0,0 @@
1
- name: rqrcode_core
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- - release/*
8
- pull_request:
9
- branches:
10
- - master
11
-
12
- jobs:
13
- Build:
14
- strategy:
15
- fail-fast: false
16
- matrix:
17
- os: [ubuntu-latest, macos-latest]
18
- ruby: [2.5, 2.6, 2.7, 3.0]
19
- runs-on: ${{ matrix.os }}
20
- steps:
21
- - uses: actions/checkout@v1
22
- - uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby }}
25
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
26
- - name: Run Tests
27
- run: bundle exec rake test
28
- - name: StandardRB Check
29
- run: bundle exec standardrb --format progress
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- /.devcontainer/
10
- *.gem
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in rqrcode-base.gemspec
4
- gemspec
data/Gemfile.lock DELETED
@@ -1,51 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- rqrcode_core (1.2.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.2)
10
- minitest (5.14.4)
11
- parallel (1.20.1)
12
- parser (3.0.2.0)
13
- ast (~> 2.4.1)
14
- rainbow (3.0.0)
15
- rake (13.0.6)
16
- regexp_parser (2.1.1)
17
- rexml (3.2.5)
18
- rubocop (1.18.4)
19
- parallel (~> 1.10)
20
- parser (>= 3.0.0.0)
21
- rainbow (>= 2.2.2, < 4.0)
22
- regexp_parser (>= 1.8, < 3.0)
23
- rexml
24
- rubocop-ast (>= 1.8.0, < 2.0)
25
- ruby-progressbar (~> 1.7)
26
- unicode-display_width (>= 1.4.0, < 3.0)
27
- rubocop-ast (1.9.1)
28
- parser (>= 3.0.1.1)
29
- rubocop-performance (1.11.4)
30
- rubocop (>= 1.7.0, < 2.0)
31
- rubocop-ast (>= 0.4.0)
32
- ruby-progressbar (1.11.0)
33
- standard (1.1.7)
34
- rubocop (= 1.18.4)
35
- rubocop-performance (= 1.11.4)
36
- standardrb (1.0.0)
37
- standard
38
- unicode-display_width (2.0.0)
39
-
40
- PLATFORMS
41
- ruby
42
-
43
- DEPENDENCIES
44
- bundler (~> 2.0)
45
- minitest (~> 5.0)
46
- rake (~> 13.0)
47
- rqrcode_core!
48
- standardrb (~> 1.0)
49
-
50
- BUNDLED WITH
51
- 2.2.22
data/Rakefile DELETED
@@ -1,19 +0,0 @@
1
- begin
2
- require "rake/testtask"
3
- require "standard/rake"
4
-
5
- Rake::TestTask.new(:test) do |t|
6
- t.libs << "test"
7
- t.libs << "lib"
8
- t.test_files = FileList["test/**/*_test.rb"]
9
- end
10
-
11
- task default: [:test, "standard:fix"]
12
-
13
- desc "Run a simple benchmark (x1000)"
14
- task :benchmark do
15
- ruby "test/benchmark.rb"
16
- end
17
- rescue LoadError
18
- # no standard/rspec available
19
- end
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "rqrcode_core"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/rqrcode_core.gemspec DELETED
@@ -1,32 +0,0 @@
1
- lib = File.expand_path("../lib", __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "rqrcode_core/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "rqrcode_core"
7
- spec.version = RQRCodeCore::VERSION
8
- spec.platform = Gem::Platform::RUBY
9
- spec.authors = ["Duncan Robertson"]
10
- spec.email = ["duncan@whomwah.com"]
11
-
12
- spec.summary = "A library to encode QR Codes"
13
- spec.description = <<~EOF
14
- rqrcode_core is a Ruby library for encoding QR Codes. The simple
15
- interface (with no runtime dependencies) allows you to create QR Code data structures.
16
- EOF
17
- spec.homepage = "https://github.com/whomwah/rqrcode_core"
18
- spec.license = "MIT"
19
-
20
- spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
- end
23
- spec.bindir = "exe"
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
26
-
27
- spec.required_ruby_version = ">= 2.3"
28
- spec.add_development_dependency "bundler", "~> 2.0"
29
- spec.add_development_dependency "rake", "~> 13.0"
30
- spec.add_development_dependency "minitest", "~> 5.0"
31
- spec.add_development_dependency "standardrb", "~> 1.0"
32
- end