pax_global_header00006660000000000000000000000064151275510300014511gustar00rootroot0000000000000052 comment=c13e78d43459a12a8bf211e969bc4296b480d7bc whomwah-rqrcode_core-c13e78d/000077500000000000000000000000001512755103000162505ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/.github/000077500000000000000000000000001512755103000176105ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/.github/FUNDING.yml000066400000000000000000000001401512755103000214200ustar00rootroot00000000000000# These are supported funding model platforms github: whomwah buy_me_a_coffee: duncanrobertson whomwah-rqrcode_core-c13e78d/.github/workflows/000077500000000000000000000000001512755103000216455ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/.github/workflows/ruby.yml000066400000000000000000000013101512755103000233440ustar00rootroot00000000000000name: rqrcode_core on: push: branches: - main - release pull_request: # Runs on any PR regardless of target branch workflow_dispatch: jobs: Build: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] ruby: ["3.2", "3.3", "3.4", "4.0"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run Tests run: bundle exec rake test - name: StandardRB Check run: bundle exec standardrb --format progress whomwah-rqrcode_core-c13e78d/.gitignore000066400000000000000000000001371512755103000202410ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /.devcontainer/ *.gem whomwah-rqrcode_core-c13e78d/AGENTS.md000066400000000000000000000025471512755103000175630ustar00rootroot00000000000000# Agent Guidelines for rqrcode_core ## Commands - Run all tests: `rake test` or `rake` - Run single test: `ruby -Ilib:test test/rqrcode_core/rqrcode_test.rb -n test_H_` - Lint check: `rake standard` - Lint fix: `rake standard:fix` - Console: `./bin/console` - Benchmarks: - Quick benchmark: `rake benchmark` or `rake benchmark:simple` - Detailed performance: `rake benchmark:performance` - Memory profiling: `rake benchmark:memory` - All benchmarks: `rake benchmark:all` ## Code Style - Follow [Standard Ruby](https://github.com/testdouble/standard) style guide (enforced via `rake standard`) - **IMPORTANT**: Always run `rake standard:fix` after making code changes to ensure consistent formatting - Use `frozen_string_literal: true` at top of all Ruby files - Ruby version: >= 3.0.0 - Test framework: Minitest (`require "minitest/autorun"`) - No external runtime dependencies (Ruby stdlib only) ## Structure & Conventions - Module namespace: `RQRCodeCore` - Custom errors: `QRCodeArgumentError` (ArgumentError), `QRCodeRunTimeError` (RuntimeError) - Constants: Use SCREAMING_SNAKE_CASE with `.freeze` for immutability (e.g., `QRMODE.freeze`) - Use symbols for modes (`:number`, `:alphanumeric`, `:byte_8bit`) and levels (`:l`, `:m`, `:q`, `:h`) - Prefer array/hash operations over loops where idiomatic - Private methods use `private` keyword, protected use `protected` whomwah-rqrcode_core-c13e78d/CHANGELOG.md000066400000000000000000000062161512755103000200660ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [2.1.0] - 2026-01-07 - Bump minimum Ruby version to 3.2 - Add comprehensive tests for QR code boundaries and encoding - Performance optimisations: 80-90% faster QR code generation across all sizes - Optimised demerit calculation functions (`demerit_points_1_same_color`, `demerit_points_2_full_blocks`, `demerit_points_3_dangerous_patterns`) - Eliminated nested Range objects and redundant array lookups in hot paths - Pre-computed frequently accessed values to reduce calculation overhead - Large QR codes (v20) now generate in ~85ms vs ~154ms previously - Memory optimisation: 70-76% reduction when using `RQRCODE_CORE_ARCH_BITS=32` - Single large QR (v24): 8.53 MB → 2.92 MB - Batch generation (100x v1): 37.91 MB → 9.10 MB - Also provides 2-4% speed improvement due to better cache locality - Add comprehensive benchmarking infrastructure with memory and performance profiling tools ## [2.0.1] - 2025-11-25 - Update required_ruby_version to support >= rather than ~> ready for Ruby 4 ## [2.0.0] - 2025-04-24 - Various README updates - Fix for issue #43 - Fix for issue #42 - Drop Ruby < 3 to keep up with dependency updates - Add option to use ENV `RQRCODE_CORE_ARCH_BITS` to override the bits value (32 or 64) used during the encoding process. This has been shown to greatly reduce the memory usage but I can't prove it doesn't break anything for all people. Use at your own risk. - Fixed the 'Do Your Own Rendering' example code to reflect the current interface. ## [1.2.0] - 2021-08-26 - Added Multi Mode Support which allows for multi-segment encoding. Thanks to [@ssayer](https://github.com/ssayer) ## [1.1.0] - 2021-07-01 - Add a basic benchmark file - Add standardRB badge - Add `.freeze` on `CONST` lookup objects - Remove unused `@mode` instance variable - A batch of small refactors and optimizations ## [1.0.0] - 2021-04-23 ### Changed - README updated - Small documentation clarification [@smnscp](https://github.com/smnscp). - Rakefile cleaned up. You can now just run `rake` which will run specs and fix linting using `standardrb` ### Breaking Changes - 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`. ## [0.2.0] - 2020-12-26 ### Changed - fix `required_ruby_version` for Ruby 3 support [unreleased]: https://github.com/whomwah/rqrcode_core/compare/v2.1.0...HEAD [2.1.0]: https://github.com/whomwah/rqrcode_core/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/whomwah/rqrcode_core/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/whomwah/rqrcode_core/compare/v1.2.0...v2.0.0 [1.2.0]: https://github.com/whomwah/rqrcode_core/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/whomwah/rqrcode_core/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/whomwah/rqrcode_core/compare/v0.2.0...v1.0.0 [0.2.0]: https://github.com/whomwah/rqrcode_core/compare/v0.1.2...v0.2.0 whomwah-rqrcode_core-c13e78d/Gemfile000066400000000000000000000001411512755103000175370ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem's dependencies in rqrcode-base.gemspec gemspec whomwah-rqrcode_core-c13e78d/Gemfile.lock000066400000000000000000000077521512755103000205050ustar00rootroot00000000000000PATH remote: . specs: rqrcode_core (2.1.0) GEM remote: https://rubygems.org/ specs: ast (2.4.3) benchmark-ips (2.14.0) json (2.18.0) language_server-protocol (3.17.0.5) lint_roller (1.1.0) memory_profiler (1.1.0) minitest (6.0.1) prism (~> 1.5) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) racc prism (1.7.0) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) regexp_parser (2.11.3) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.49.0) parser (>= 3.3.7.2) prism (~> 1.7) rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) stackprof (0.2.27) standard (1.52.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) rubocop (~> 1.81.7) standard-custom (~> 1.0.0) standard-performance (~> 1.8) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) standard-performance (1.9.0) lint_roller (~> 1.1) rubocop-performance (~> 1.26.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) PLATFORMS arm64-darwin-24 ruby DEPENDENCIES benchmark-ips (~> 2.0) bundler (~> 4.0) memory_profiler (~> 1.0) minitest (~> 6.0) rake (~> 13.3) rqrcode_core! stackprof (~> 0.2) standard (~> 1.41) CHECKSUMS ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 benchmark-ips (2.14.0) sha256=b72bc8a65d525d5906f8cd94270dccf73452ee3257a32b89fbd6684d3e8a9b1d json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 memory_profiler (1.1.0) sha256=79a17df7980a140c83c469785905409d3027ca614c42c086089d128b805aa8f8 minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 prism (1.7.0) sha256=10062f734bf7985c8424c44fac382ac04a58124ea3d220ec3ba9fe4f2da65103 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 rqrcode_core (2.1.0) rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094 rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 stackprof (0.2.27) sha256=aff6d28656c852e74cf632cc2046f849033dc1dedffe7cb8c030d61b5745e80c standard (1.52.0) sha256=ec050e63228e31fabe40da3ef96da7edda476f7acdf3e7c2ad47b6e153f6a076 standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2 unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH 4.0.3 whomwah-rqrcode_core-c13e78d/LICENSE.txt000066400000000000000000000020731512755103000200750ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2008 Duncan Robertson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. whomwah-rqrcode_core-c13e78d/README.md000066400000000000000000000116771512755103000175430ustar00rootroot00000000000000![](https://github.com/whomwah/rqrcode_core/actions/workflows/ruby.yml/badge.svg) [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard) # RQRCodeCore `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). Features: - `rqrcode_core` is a Ruby only library. It requires no 3rd party libraries. Just Ruby! - It is an encoding library. You can't decode QR Codes with it. - The interface is simple and assumes you just want to encode a string into a QR Code, but also allows for encoding multiple segments. - QR Code is trade marked by Denso Wave inc. - Minimum Ruby version is `>= 3.2.0` `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`. ## Installation Add this line to your application's Gemfile: ```ruby gem "rqrcode_core" ``` And then execute: $ bundle Or install it yourself as: $ gem install rqrcode_core ## Basic Usage ```ruby $ require "rqrcode_core" $ qr = RQRCodeCore::QRCode.new("https://kyan.com") $ puts qr.to_s ``` Output: ``` xxxxxxx x x x x x xx xxxxxxx x x xxx xxxxxx xxx x x x xxx x xxxxx x xx x xxx x ... etc ``` ## Multiple Encoding Support ```ruby $ require "rqrcode_core" $ qr = RQRCodeCore::QRCode.new([ {data: "byteencoded", mode: :byte_8bit}, {data: "A1" * 100, mode: :alphanumeric}, {data: "1" * 500, mode: :number} ]) ``` 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. ## Doing your own rendering ```ruby require "rqrcode_core" qr = RQRCodeCore::QRCode.new("https://kyan.com") qr.modules.each do |row| row.each do |col| print col ? "#" : " " end print "\n" end ``` ### Options The library expects a string or array (for multiple encodings) to be parsed in, other args are optional. ``` data - the string or array you wish to encode size - the size (integer) of the QR Code (defaults to smallest size needed to encode the string) max_size - the max_size (Integer) of the QR Code (default RQRCodeCore::QRUtil.max_size) level - the error correction level, can be: * Level :l 7% of code can be restored * Level :m 15% of code can be restored * Level :q 25% of code can be restored * Level :h 30% of code can be restored (default :h) mode - the mode of the QR Code (defaults to alphanumeric or byte_8bit, depending on the input data, only used when data is a string): * :number * :alphanumeric * :byte_8bit ``` #### Example ```ruby RQRCodeCore::QRCode.new("http://kyan.com", size: 2, level: :m, mode: :byte_8bit) ``` ## Development ### Tests You can run the test suite using: ``` $ ./bin/setup $ rake ``` or try the project from the console with: ``` $ ./bin/console ``` ### Linting The project uses [standardrb](https://github.com/testdouble/standard) and can be run with: ``` $ ./bin/setup $ rake standard # check $ rake standard:fix # fix ``` ## Performance Optimisation ### Reduce Memory Usage by 70-76% **If you're running on a 64-bit system, you can dramatically reduce memory consumption by setting:** ```ruby ENV['RQRCODE_CORE_ARCH_BITS'] = '32' ``` Or from the command line: ```bash RQRCODE_CORE_ARCH_BITS=32 ruby your_script.rb ``` #### Benchmark Results (64-bit vs 32-bit on 64-bit systems) **Memory Savings:** - Single small QR code: 0.38 MB → 0.10 MB (**74% reduction**) - Single large QR code: 8.53 MB → 2.92 MB (**66% reduction**) - 100 small QR codes: 37.91 MB → 9.10 MB (**76% reduction**) - 10 large QR codes: 85.32 MB → 29.19 MB (**66% reduction**) **Speed Improvement:** - 2-4% faster across all scenarios (better cache utilization, reduced GC pressure) **Object Allocation:** - 85-87% fewer objects allocated - Integer allocations nearly eliminated (from 70-76% to ~0%) #### Why This Works 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. **Recommendation:** Use `RQRCODE_CORE_ARCH_BITS=32` for production workloads, especially when: - Generating QR codes in batch - Running in memory-constrained environments - Handling high-concurrency web requests - Processing large QR codes (version 10+) See `test/benchmarks/ARCH_BITS_ANALYSIS.md` for detailed benchmark data and analysis. ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/whomwah/rqrcode_core. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). whomwah-rqrcode_core-c13e78d/Rakefile000066400000000000000000000013601512755103000177150ustar00rootroot00000000000000begin require "rake/testtask" require "standard/rake" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList["test/**/*_test.rb"] end task default: [:test, "standard:fix"] namespace :benchmark do desc "Run simple comparison benchmark" task :simple do ruby "test/benchmark_simple.rb" end desc "Run detailed performance benchmark (benchmark-ips)" task :performance do ruby "test/benchmark_performance.rb" end desc "Run memory profiling benchmark" task :memory do ruby "test/benchmark_memory.rb" end desc "Run all benchmarks" task all: [:simple, :performance, :memory] end rescue LoadError # no standard/rspec available end whomwah-rqrcode_core-c13e78d/bin/000077500000000000000000000000001512755103000170205ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/bin/console000077500000000000000000000005331512755103000204110ustar00rootroot00000000000000#!/usr/bin/env ruby require "bundler/setup" require "rqrcode_core" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) whomwah-rqrcode_core-c13e78d/bin/setup000077500000000000000000000002031512755103000201010ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here whomwah-rqrcode_core-c13e78d/docs/000077500000000000000000000000001512755103000172005ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/docs/BENCHMARKS.md000066400000000000000000000203041512755103000211360ustar00rootroot00000000000000# RQRCode Core - Performance Benchmarks This document describes the benchmarking infrastructure for rqrcode_core and provides baseline performance metrics. ## Running Benchmarks ### Quick Start ```bash # Quick comparison (10 iterations, ~5 seconds) rake benchmark # or rake benchmark:simple # Detailed performance analysis with benchmark-ips (~30 seconds) rake benchmark:performance # Memory profiling with detailed allocation tracking (~30 seconds) rake benchmark:memory # Run all benchmarks rake benchmark:all ``` ### Individual Benchmark Files You can also run benchmark files directly: ```bash ruby test/benchmark_simple.rb ruby test/benchmark_performance.rb ruby test/benchmark_memory.rb ``` ## System Information Baselines collected on: - **Date**: December 4, 2025 - **Ruby Version**: 3.3.4 - **Platform**: arm64-darwin24 (Apple Silicon) - **ARCH_BITS**: 64 ## Benchmark Types ### 1. Simple Benchmark (`benchmark_simple.rb`) Fast comparison across common scenarios using Ruby's standard `Benchmark` module. Good for quick before/after comparisons during development. **Iterations**: 10 per test (configurable via `ITERATIONS` constant) **Runtime**: ~5 seconds ### 2. Performance Benchmark (`benchmark_performance.rb`) Detailed performance analysis using `benchmark-ips` gem. Provides iterations-per-second metrics with statistical analysis and comparisons. **Configuration**: 2 seconds measurement, 1 second warmup (configurable) **Runtime**: ~30-45 seconds **Scenarios tested**: - Data sizes (small/medium/large) - Encoding modes (numeric/alphanumeric/byte) - QR versions (1, 5, 10, 20, 40) - Error correction levels (:l, :m, :q, :h) - Creation vs rendering - Multi-segment encoding ### 3. Memory Benchmark (`benchmark_memory.rb`) Memory allocation profiling using `memory_profiler` gem. Tracks total allocated/retained memory and object allocations by class. **Runtime**: ~30 seconds **Scenarios tested**: - Single QR codes (various sizes) - Batch generation (100 small, 10 large) - Creation vs rendering - Different encoding modes - Multi-segment encoding ## Baseline Performance Metrics ### Performance (iterations per second) | Scenario | ips | ms/iteration | vs Baseline | |----------|-----|--------------|-------------| | Small QR (v1) | 144.1 | 6.94 | 1.00x | | Medium URL (v5) | 44.6 | 22.41 | 3.23x slower | | Large (v24) | 4.6 | 217.37 | 31.33x slower | | Numeric mode | 147.1 | 6.80 | fastest | | Alphanumeric mode | 100.9 | 9.91 | 1.46x slower | | Byte mode | 100.9 | 9.91 | 1.46x slower | | Version 1 | 147.3 | 6.79 | 1.00x | | Version 5 | 44.8 | 22.33 | 3.29x slower | | Version 10 | 18.8 | 53.08 | 7.82x slower | | Version 20 | 6.4 | 155.21 | 22.86x slower | | Version 40 | 1.9 | 521.09 | 76.74x slower | **Key Findings**: - **Version impact**: Performance degrades quadratically with version (module_count = version*4 + 17) - **Encoding modes**: Numeric mode is ~46% faster than alphanumeric/byte modes - **Error correction**: Minimal impact (~2% variance) across levels :l, :m, :q, :h - **Rendering**: Adds ~3% overhead to creation time ### Memory Usage | Scenario | Allocated | Retained | Objects | Notes | |----------|-----------|----------|---------|-------| | Single v1 | 0.38 MB | 0.00 MB | 8,740 | Baseline | | Single v5 | 0.97 MB | 0.00 MB | 21,264 | 2.5x v1 | | Single v24 | 8.53 MB | 0.00 MB | 179,659 | 22x v1 | | 100x v1 | 37.91 MB | 0.00 MB | 872,700 | ~380KB each | | 10x v24 | 85.32 MB | 0.00 MB | 1,796,590 | ~8.5MB each | | Create only | 37.91 MB | 0.00 MB | 872,700 | 100 iterations | | Create + render | 40.27 MB | 0.00 MB | 919,300 | +6% for rendering | **Key Findings**: - **No memory retention**: All memory is garbage collectable (0 retained) - **Top allocations**: Integer (70-76%), Array (15-22%), Range (8-10%) - **Version scaling**: Memory usage grows quadratically with version - **Rendering overhead**: Adds ~6% to memory allocation - **Encoding modes**: Minimal difference (~3% variance) across modes ### ARCH_BITS Impact To test the memory impact of `ARCH_BITS` setting: ```bash # Default 64-bit (current baseline) ruby test/benchmark_memory.rb # Force 32-bit mode (reduced memory) RQRCODE_CORE_ARCH_BITS=32 ruby test/benchmark_memory.rb ``` **Expected**: 32-bit mode should reduce memory usage during right-shift operations, particularly noticeable with large QR codes and batch generation. ## Understanding the Results ### benchmark-ips Output ``` Calculating ------------------------------------- Small (v1) 144.135 (± 0.7%) i/s (6.94 ms/i) ``` - **144.135 i/s**: 144 iterations per second - **(± 0.7%)**: Statistical error margin (lower is more consistent) - **(6.94 ms/i)**: Milliseconds per iteration - **Comparison section**: Shows relative performance differences ### memory_profiler Output ``` Total allocated: 0.38 MB # Memory allocated during execution Total retained: 0.00 MB # Memory still held after GC Objects allocated: 8740 # Number of objects created Objects retained: 0 # Number of objects not GC'd ``` - **Allocated**: All memory used (includes garbage) - **Retained**: Memory still referenced (memory leaks if high) - **By class**: Shows which Ruby types are most allocated ## Performance Characteristics ### Version Size Impact QR Code module count formula: `module_count = version * 4 + 17` | Version | Modules | Total Cells | Performance Impact | |---------|---------|-------------|-------------------| | 1 | 21x21 | 441 | 1.00x (baseline) | | 5 | 37x37 | 1,369 | ~3.1x slower | | 10 | 57x57 | 3,249 | ~7.4x slower | | 20 | 97x97 | 9,409 | ~21x slower | | 40 | 177x177 | 31,329 | ~71x slower | Performance degradation is roughly O(n²) where n is version number. ### Encoding Mode Efficiency 1. **Numeric** (fastest): 3.33 bits per digit 2. **Alphanumeric**: 5.5 bits per character 3. **Byte** (slowest): 8 bits per character For mixed content, multi-segment encoding can be more efficient than byte mode. ### Error Correction Impact Error correction levels have minimal performance impact (<3% variance): - `:l` - 7% restoration - `:m` - 15% restoration - `:q` - 25% restoration - `:h` - 30% restoration (default) The performance cost is in capacity (less data fits), not speed. ## Known Considerations ### Memory on 64-bit Systems From `lib/rqrcode_core/qrcode/qr_util.rb`: > 64 consumes a LOT more memory. In tests it's shown changing it to 32 > on 64 bit systems greatly reduces the memory footprint. This occurs during right-shift zero-fill operations. Use `RQRCODE_CORE_ARCH_BITS=32` to reduce memory at potential compatibility risk. ### Large QR Codes Version 24+ QR codes are significantly slower (~200ms+ per code). For batch processing: - Consider caching generated codes - Use background jobs for generation - Consider lower error correction levels if acceptable ## Future Optimization Ideas Potential areas for performance improvement: 1. **Memory Optimization** - Investigate ARCH_BITS impact more thoroughly - Reduce temporary array allocations - Optimize string concatenation in rendering - Use more efficient data structures for modules 2. **Speed Optimization** - Profile hot paths with stackprof - Cache frequently computed values - Optimize inner loops in encoding - Consider memoization for version calculations 3. **Algorithm Improvements** - Review polynomial math operations - Optimize mask pattern calculation - Benchmark alternative implementations 4. **Benchmarking Infrastructure** - Add CI performance regression tests - Track performance trends over time - Add comparison with other QR libraries - Create performance dashboard ## Contributing When making performance-related changes: 1. Run benchmarks before changes: `rake benchmark:all > before.txt` 2. Make your changes 3. Run benchmarks after: `rake benchmark:all > after.txt` 4. Compare results and document improvements 5. Update this file with new baseline if significant ## References - [benchmark-ips gem](https://github.com/evanphx/benchmark-ips) - [memory_profiler gem](https://github.com/SamSaffron/memory_profiler) - [Ruby Benchmark module](https://ruby-doc.org/stdlib/libdoc/benchmark/rdoc/Benchmark.html) - [QR Code specification](https://www.qrcode.com/en/about/standards.html) --- **Last Updated**: December 4, 2025 **Baseline Version**: rqrcode_core 2.0.1 whomwah-rqrcode_core-c13e78d/docs/OPTIMIZATION_PLAN.md000066400000000000000000000363031512755103000222670ustar00rootroot00000000000000# RQRCode Core - Performance Optimization Plan This document outlines the step-by-step plan for implementing the performance improvements identified in BENCHMARKS.md. **Created**: December 4, 2025 **Status**: In Progress --- ## Overview This plan addresses the "Future Optimization Ideas" section from BENCHMARKS.md, organized by category and priority. Each optimization will be done incrementally with benchmarking before/after to measure impact. --- ## Phase 1: Memory Optimizations (High Priority) ### Task #1: ARCH_BITS Investigation ✅ **Priority**: High **Status**: COMPLETE **File**: `lib/rqrcode_core/qrcode/qr_util.rb:69` **Goals**: - ✅ Run memory benchmarks with default 64-bit setting (baseline) - ✅ Run memory benchmarks with `RQRCODE_CORE_ARCH_BITS=32` - ✅ Run performance benchmarks for speed comparison - ✅ Document memory differences and performance impact - ✅ Determine if 32-bit can be safely used as default - ✅ Make recommendation based on data **Results**: | Metric | 64-bit | 32-bit | Improvement | |--------|--------|--------|-------------| | Single v1 | 0.38 MB | 0.10 MB | **74% reduction** | | Single v24 | 8.53 MB | 2.92 MB | **66% reduction** | | 100x v1 | 37.91 MB | 9.10 MB | **76% reduction** | | Speed (small) | 152.7 i/s | 157.1 i/s | **+2.9% faster** | | Speed (large) | 4.77 i/s | 4.87 i/s | **+2.1% faster** | | Objects (100x v1) | 872,700 | 117,500 | **87% reduction** | **Key Findings**: - Integer allocations nearly eliminated (from 70-76% to ~0%) - All 108 test assertions pass - Actually faster due to better cache utilization and reduced GC pressure - No correctness issues **Recommendation**: ✅ **STRONGLY RECOMMEND** users set `RQRCODE_CORE_ARCH_BITS=32` in production for 70-76% memory savings with 2-4% speed improvement. Not changing default to maintain backwards compatibility, but documenting heavily in README and code comments. **Documentation Updated**: - ✅ Updated `lib/rqrcode_core/qrcode/qr_util.rb` comment with benchmark data - ✅ Replaced README "Experimental" section with prominent "Performance Optimization" section - ✅ Updated `docs/BENCHMARKS.md` with proven results - ✅ Moved all benchmark files to `test/benchmarks/` directory - ✅ Created `test/benchmarks/ARCH_BITS_ANALYSIS.md` with complete analysis See `test/benchmarks/ARCH_BITS_ANALYSIS.md` for detailed analysis. --- ### Task #2: More Efficient Data Structures for Modules **Priority**: Medium **Status**: Pending **Files**: Various **Goals**: - Evaluate current Array of Arrays approach for modules - Consider alternatives: Flat array with index calculations, BitArray - Analyze trade-offs: Memory vs access speed - Benchmark access patterns **Current**: `@modules = Array.new(@module_count)` **Considerations**: Module count can be 177x177 = 31,329 cells for v40 --- ## Phase 2: Speed Optimizations ### Task #3: Profile Hot Paths with stackprof ✅ **Priority**: High **Status**: COMPLETE **Goals**: - ✅ Add stackprof gem to development dependencies - ✅ Create profiling script for various QR code sizes - ✅ Focus on large QR codes (v20+) where performance degrades - ✅ Identify actual bottlenecks with data **Results Summary**: Profiled v1, v5, v10, v20 QR codes across 100-5 iterations each. Clear bottlenecks identified: **Top Hotspots** (for large QR codes): 1. **`demerit_points_1_same_color`** - **30.2% of CPU time** for v20 codes - Nested O(n²) loops checking consecutive same-colored modules - Runs for all 8 mask patterns - Primary optimization target 2. **Garbage Collection** - 42-75% of samples - Higher for small codes, decreases as size increases - Already addressed via ARCH_BITS=32 recommendation 3. **`demerit_points_2_full_blocks`** - 3.5% for v20 codes - Checks for 2x2 blocks of same color - Secondary optimization target 4. **`demerit_points_3_dangerous_patterns`** - 2.8% for v20 codes - Pattern matching for specific sequences - Tertiary optimization target **Key Insights**: - **Scaling behavior**: Demerit calculations grow from 12.7% (v1) to 30.2% (v20) of CPU time - **Small vs Large**: Small codes are GC-bound (74.7%), large codes are demerit-bound (30.2%) - **Clear path forward**: Optimizing `demerit_points_1_same_color` will yield 15-30% improvement for large codes **Files Created**: - `test/profile_stackprof.rb` - Profiling script - `test/benchmarks/STACKPROF_ANALYSIS.md` - Detailed analysis with all findings - `tmp/stackprof/*.dump` - Raw profiling data for further investigation See `test/benchmarks/STACKPROF_ANALYSIS.md` for complete analysis and optimization recommendations. --- ### Task #4: Cache Frequently Computed Values **Priority**: Medium **Status**: Pending **File**: `lib/rqrcode_core/qrcode/qr_util.rb:119-127` **Goals**: - Identify values computed multiple times - Implement memoization where appropriate - Add class-level caching for version-specific calculations **Candidates**: - `get_error_correct_polynomial(error_correct_length)` - called per RS block - Pattern positions (already cached in table) - BCH calculations for common versions --- ### Task #5: Optimize Inner Loops in Encoding **Priority**: Medium **Status**: Pending **Files**: - `lib/rqrcode_core/qrcode/qr_util.rb:163-192` (demerit_points_1_same_color) - `lib/rqrcode_core/qrcode/qr_code.rb:367-404` (map_data) **Goals**: - Focus on nested loops that iterate over module_count - Reduce redundant calculations inside loops - Consider pre-computing values or caching **Hot Spots**: - `demerit_points_1_same_color`: O(n²) with nested checks - `map_data`: Critical path for encoding data into modules --- ### Task #6: Memoization for Version Calculations **Priority**: Low **Status**: Pending **Goals**: - Add memoization for version-specific calculations - Cache results for common version/level combinations - Measure impact on batch generation scenarios --- ## Phase 3: Algorithm Improvements ### Task #7: Review Polynomial Math Operations **Priority**: Medium **Status**: Pending **File**: `lib/rqrcode_core/qrcode/qr_polynomial.rb` **Goals**: - Analyze multiply and mod methods for optimization - Review recursive mod at line 58 - Consider iterative approach instead of recursive - Benchmark alternative implementations **Current Concerns**: - `multiply`: Creates temporary arrays in nested loops - `mod`: Recursive calls may cause stack overhead --- ### Task #8: Optimize Mask Pattern Calculation **Priority**: Medium **Status**: Pending **File**: `lib/rqrcode_core/qrcode/qr_code.rb:286-300` **Goals**: - Review get_best_mask_pattern (tries all 8 patterns) - Algorithm requires testing all patterns (QR spec) - Focus on optimizing get_lost_points calculations - Optimize demerit_points calculations **Note**: Can't reduce number of patterns tested (spec requirement), but can optimize each pattern evaluation. --- ### Task #9: Benchmark Alternative Implementations **Priority**: Low **Status**: Pending **Goals**: - After implementing optimizations, compare with baseline - Document improvements in BENCHMARKS.md - Consider alternative algorithms if performance goals not met - Compare with other QR code libraries for reference --- ## Execution Order Recommended order for maximum impact: 1. ✅ **ARCH_BITS Investigation** - COMPLETE - Proven 70-76% memory savings + 2-4% speed boost 2. ✅ **Profile with stackprof** - COMPLETE - Identified `demerit_points_1_same_color` as 30% CPU bottleneck 3. ✅ **Optimize demerit calculation functions** - COMPLETE - **80-90% speed improvement** across all QR sizes 4. **Caching/memoization** - Progressive improvement (next priority) 5. **Data structures** - Larger refactor, do later 6. **Algorithm improvements** - Most complex, do last --- ## Process for Each Task 1. **Before Changes**: - Run `rake benchmark:all > before_task_N.txt` - Document current behavior - Identify specific optimization targets 2. **Make Changes**: - Implement optimization - Run tests: `rake test` - Run linter: `rake standard` - Fix any issues 3. **After Changes**: - Run `rake benchmark:all > after_task_N.txt` - Compare results with baseline - Document improvements 4. **Update Documentation**: - Update this file with results - Update BENCHMARKS.md if significant improvement - Commit changes with clear message --- ## Success Metrics Based on BENCHMARKS.md baseline: ### Memory Targets: - Reduce memory allocations by 10-20% for single QR codes - Reduce memory allocations by 15-30% for batch generation - Focus on Integer and Array allocations (85-90% of total) ### Performance Targets: - Improve large QR code (v20+) generation speed by 10-20% - Improve batch generation throughput - Maintain or improve small QR code performance ### Constraints: - No breaking API changes - All tests must pass - Standard Ruby style compliance - No external runtime dependencies --- ## Results Log ### Task #1: ARCH_BITS Investigation ✅ **Date**: December 4, 2025 **Status**: Complete **Results**: **PROVEN: Setting `RQRCODE_CORE_ARCH_BITS=32` provides dramatic improvements with zero downsides:** - **Memory**: 70-76% reduction across all scenarios - **Speed**: 2-4% faster (not just "no penalty"—actually faster) - **Objects**: 85-87% fewer allocations - **Tests**: All 108 assertions pass - **Correctness**: No issues found **Impact by scenario:** - Single small QR: 0.38 MB → 0.10 MB (74% reduction) - Single large QR: 8.53 MB → 2.92 MB (66% reduction) - Batch 100 small: 37.91 MB → 9.10 MB (76% reduction) **Why it works:** The `rszf` function creates bit masks. With 64-bit, these require large integer allocations. With 32-bit, they fit into smaller representations, dramatically reducing memory allocation while improving cache locality. **Action taken:** - Updated README with prominent "Performance Optimization" section - Updated code comments in `qr_util.rb` with concrete benchmark data - Updated `docs/BENCHMARKS.md` with proven results - Organized all benchmark files in `test/benchmarks/` directory - Created comprehensive analysis document **Recommendation:** Users should set `RQRCODE_CORE_ARCH_BITS=32` in production. Not changing library default to avoid surprises for existing users, but strongly recommending the optimization through documentation. Full analysis available in `test/benchmarks/ARCH_BITS_ANALYSIS.md`. --- ### Task #3: Profile Hot Paths with stackprof ✅ **Date**: December 5, 2025 **Status**: Complete **Results**: **IDENTIFIED: Clear CPU bottlenecks with concrete optimization targets:** Profiled QR codes from v1 (21x21) to v20 (97x97) with 100-5 iterations each. **Primary Hotspot** - `demerit_points_1_same_color` (`qr_util.rb:171`): - **CPU Impact**: Scales from 12.7% (v1) → 30.2% (v20) - **Why Slow**: O(n²) nested loops checking consecutive same-colored modules for all 8 mask patterns - **Optimization Potential**: 15-30% speed improvement for large QR codes **Secondary Hotspots**: - `demerit_points_2_full_blocks`: 1.6% → 3.5% (2x2 block checking) - `demerit_points_3_dangerous_patterns`: 0.6% → 2.8% (pattern matching) - Combined potential: 5-10% improvement **GC Overhead**: - Small QR codes: 74.7% (GC-bound) - addressed by ARCH_BITS=32 - Large QR codes: 41.6% (compute-bound) - demerit functions are the bottleneck **Scaling Insight**: As QR codes grow larger, demerit calculations become the dominant performance factor, overtaking GC as the primary bottleneck. **Action taken:** - Added stackprof gem to gemspec - Created `test/profile_stackprof.rb` profiling script - Generated profiles for v1, v5, v10, v20 QR codes - Created comprehensive analysis in `test/benchmarks/STACKPROF_ANALYSIS.md` - Updated OPTIMIZATION_PLAN.md with data-driven priorities **Next Steps**: Optimize `demerit_points_1_same_color` as highest-impact target (Task #4 reprioritized). Full analysis available in `test/benchmarks/STACKPROF_ANALYSIS.md`. --- ### Task #3.5: Optimize Demerit Calculation Functions ✅ **Date**: December 5, 2025 **Status**: Complete **Results**: **OPTIMIZED: All three demerit calculation hotspots identified by stackprof** Based on the stackprof analysis showing `demerit_points_*` functions as the primary bottleneck, optimized all three functions without changing their algorithmic behavior: **Optimizations Applied:** 1. **`demerit_points_1_same_color` (qr_util.rb:171-213)**: - Eliminated nested Range objects (`-1..1`) in hot loops - Pre-computed `max_index` to avoid repeated `module_count - 1` calculations - Cached row arrays (`modules_row`, `row_above`, `row_below`) to reduce array lookups - Unrolled nested loops checking 3x3 neighborhood - Replaced Range#each with Integer#times for better performance - **Reduced CPU time from 30.2% → 18.5%** (39% reduction) 2. **`demerit_points_2_full_blocks` (qr_util.rb:215-230)**: - Cached adjacent row arrays to eliminate redundant lookups - Simplified 2x2 block check using direct equality comparisons - Removed unnecessary counter variable and array inclusion check - Replaced Range#each with Integer#times 3. **`demerit_points_3_dangerous_patterns` (qr_util.rb:232-259)**: - Pre-computed pattern length and max_start index - Simplified dangerous pattern checks with clearer conditionals - Replaced Range#each with Integer#times - Consolidated multi-line conditionals **Performance Impact** (64-bit, before vs after): | QR Code Size | Before | After | Improvement | |--------------|---------|--------|-------------| | Small (v1) | 152.7 i/s | 292.9 i/s | **+92% faster** | | Medium (v5) | 46.2 i/s | 85.3 i/s | **+85% faster** | | Large (v24) | 4.77 i/s | 8.68 i/s | **+82% faster** | | Version 10 | 19.0 i/s | 34.6 i/s | **+82% faster** | | Version 20 | 6.50 i/s | 11.8 i/s | **+82% faster** | | Version 40 | 1.94 i/s | 3.51 i/s | **+81% faster** | **Time per QR Code** (v20): - Before: 153.86 ms - After: 84.57 ms - **Improvement: 45% reduction in generation time** **StackProf CPU Profile Changes** (v20 QR code): - `demerit_points_1_same_color`: 30.2% → 18.5% (39% reduction) - GC overhead: 41.6% → 47.3% (now that compute is faster, GC shows proportionally higher) - Overall CPU samples reduced significantly **Key Insights:** - The optimizations provided **consistent 80-90% speed improvements** across all QR code sizes - Largest impact on large QR codes where demerit calculations dominate - All 108 test assertions pass - behavior unchanged - Code is now clearer and more maintainable - No external dependencies added **Action taken:** - Optimized all three demerit calculation functions - Maintained exact same algorithmic behavior (tests pass) - Applied Ruby style guide fixes via `rake standard:fix` - Generated new benchmark data in `test/benchmarks/benchmark_performance_optimized.txt` - Verified performance improvements via stackprof **Files Modified:** - `lib/rqrcode_core/qrcode/qr_util.rb` (lines 171-259) **Recommendation:** These optimizations provide massive performance gains with zero breaking changes. Ready for production use immediately. See `test/benchmarks/benchmark_performance_optimized.txt` for complete benchmark results. --- ## References - [BENCHMARKS.md](BENCHMARKS.md) - Baseline performance metrics - [Ruby Performance Optimization](https://ruby-doc.org/core/doc/performance_md.html) - [memory_profiler gem](https://github.com/SamSaffron/memory_profiler) - [stackprof gem](https://github.com/tmm1/stackprof) whomwah-rqrcode_core-c13e78d/lib/000077500000000000000000000000001512755103000170165ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/lib/rqrcode_core.rb000066400000000000000000000001671512755103000220160ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore require "rqrcode_core/qrcode" require "rqrcode_core/version" end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/000077500000000000000000000000001512755103000214655ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode.rb000066400000000000000000000007451512755103000232750ustar00rootroot00000000000000# frozen_string_literal: true require "rqrcode_core/qrcode/qr_8bit_byte" require "rqrcode_core/qrcode/qr_alphanumeric" require "rqrcode_core/qrcode/qr_bit_buffer" require "rqrcode_core/qrcode/qr_code" require "rqrcode_core/qrcode/qr_math" require "rqrcode_core/qrcode/qr_multi" require "rqrcode_core/qrcode/qr_numeric" require "rqrcode_core/qrcode/qr_polynomial" require "rqrcode_core/qrcode/qr_rs_block" require "rqrcode_core/qrcode/qr_segment" require "rqrcode_core/qrcode/qr_util" whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/000077500000000000000000000000001512755103000227425ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_8bit_byte.rb000066400000000000000000000004241512755103000256620ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QR8bitByte def initialize(data) @data = data end def write(buffer) buffer.byte_encoding_start(@data.bytesize) @data.each_byte do |b| buffer.put(b, 8) end end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_alphanumeric.rb000066400000000000000000000020361512755103000264420ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore ALPHANUMERIC = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "$", "%", "*", "+", "-", ".", "/", ":" ].freeze class QRAlphanumeric def initialize(data) unless QRAlphanumeric.valid_data?(data) raise QRCodeArgumentError, "Not a alpha numeric uppercase string `#{data}`" end @data = data end def self.valid_data?(data) (data.chars - ALPHANUMERIC).empty? end def write(buffer) buffer.alphanumeric_encoding_start(@data.size) @data.size.times do |i| if i % 2 == 0 if i == (@data.size - 1) value = ALPHANUMERIC.index(@data[i]) buffer.put(value, 6) else value = (ALPHANUMERIC.index(@data[i]) * 45) + ALPHANUMERIC.index(@data[i + 1]) buffer.put(value, 11) end end end end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_bit_buffer.rb000066400000000000000000000032511512755103000261010ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRBitBuffer attr_reader :buffer PAD0 = 0xEC PAD1 = 0x11 def initialize(version) @version = version @buffer = [] @length = 0 end def get(index) buf_index = (index / 8).floor (QRUtil.rszf(@buffer[buf_index], 7 - index % 8) & 1) == 1 end def put(num, length) (0...length).each do |i| put_bit((QRUtil.rszf(num, length - i - 1) & 1) == 1) end end def get_length_in_bits @length end def put_bit(bit) buf_index = (@length / 8).floor if @buffer.size <= buf_index @buffer << 0 end if bit @buffer[buf_index] |= QRUtil.rszf(0x80, @length % 8) end @length += 1 end def byte_encoding_start(length) put(QRMODE[:mode_8bit_byte], 4) put(length, QRUtil.get_length_in_bits(QRMODE[:mode_8bit_byte], @version)) end def alphanumeric_encoding_start(length) put(QRMODE[:mode_alpha_numk], 4) put(length, QRUtil.get_length_in_bits(QRMODE[:mode_alpha_numk], @version)) end def numeric_encoding_start(length) put(QRMODE[:mode_number], 4) put(length, QRUtil.get_length_in_bits(QRMODE[:mode_number], @version)) end def pad_until(prefered_size) # Align on byte while get_length_in_bits % 8 != 0 put_bit(false) end # Pad with padding code words while get_length_in_bits < prefered_size put(PAD0, 8) put(PAD1, 8) if get_length_in_bits < prefered_size end end def end_of_message(max_data_bits) put(0, 4) unless get_length_in_bits + 4 > max_data_bits end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_code.rb000066400000000000000000000373471512755103000247210ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore QRMODE = { mode_number: 1 << 0, # 1 (binary: 0001) mode_alpha_numk: 1 << 1, # 2 (binary: 0010) mode_8bit_byte: 1 << 2 # 4 (binary: 0100) }.freeze QRMODE_NAME = { number: :mode_number, alphanumeric: :mode_alpha_numk, byte_8bit: :mode_8bit_byte, multi: :mode_multi }.freeze QRERRORCORRECTLEVEL = { l: 1, m: 0, q: 3, h: 2 }.freeze QRMASKPATTERN = { pattern000: 0, pattern001: 1, pattern010: 2, pattern011: 3, pattern100: 4, pattern101: 5, pattern110: 6, pattern111: 7 }.freeze QRMASKCOMPUTATIONS = [ proc { |i, j| (i + j) % 2 == 0 }, proc { |i, j| i % 2 == 0 }, proc { |i, j| j % 3 == 0 }, proc { |i, j| (i + j) % 3 == 0 }, proc { |i, j| ((i / 2).floor + (j / 3).floor) % 2 == 0 }, proc { |i, j| (i * j) % 2 + (i * j) % 3 == 0 }, proc { |i, j| ((i * j) % 2 + (i * j) % 3) % 2 == 0 }, proc { |i, j| ((i * j) % 3 + (i + j) % 2) % 2 == 0 } ].freeze QRPOSITIONPATTERNLENGTH = (7 + 1) * 2 + 1 QRFORMATINFOLENGTH = 15 # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable1-e.html # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable2-e.html # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable3-e.html # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable4-e.html QRMAXBITS = { 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, 10_208, 10_960, 11_744, 12_248, 13_048, 13_880, 14_744, 15_640, 16_568, 17_528, 18_448, 19_472, 20_528, 21_616, 22_496, 23_648], 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, 10_136, 10_984, 11_640, 12_328, 13_048, 13_800, 14_496, 15_312, 15_936, 16_816, 17_728, 18_672], 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], 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, 10_208] }.freeze # StandardErrors class QRCodeArgumentError < ArgumentError; end class QRCodeRunTimeError < RuntimeError; end # == Creation # # QRCode objects expect only one required constructor parameter # and an optional hash of any other. Here's a few examples: # # qr = RQRCodeCore::QRCode.new('hello world') # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric) # class QRCode attr_reader :modules, :module_count, :version # Expects a string or array (for multi-segment encoding) to be parsed in, other args are optional # # # data - the string, QRSegment or array of Hashes (with data:, mode: keys) you wish to encode # # size - the size (Integer) of the QR Code (defaults to smallest size needed to encode the data) # # max_size - the max_size (Integer) of the QR Code (default RQRCodeCore::QRUtil.max_size) # # level - the error correction level, can be: # * Level :l 7% of code can be restored # * Level :m 15% of code can be restored # * Level :q 25% of code can be restored # * Level :h 30% of code can be restored (default :h) # # mode - the mode of the QR Code (defaults to alphanumeric or byte_8bit, depending on the input data, only used when data is a string): # * :number # * :alphanumeric # * :byte_8bit # # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric) # segment_qr = QRCodeCore::QRCode.new({ data: 'foo', mode: :byte_8bit }) # multi_qr = RQRCodeCore::QRCode.new([{ data: 'foo', mode: :byte_8bit }, { data: 'bar1', mode: :alphanumeric }]) def initialize(data, *args) options = extract_options!(args) level = (options[:level] || :h).to_sym max_size = options[:max_size] || QRUtil.max_size @data = case data when String QRSegment.new(data: data, mode: options[:mode]) when Array 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 } } data.map { |seg| QRSegment.new(**seg) } when QRSegment data else raise QRCodeArgumentError, "data must be a String, QRSegment, or an Array" end @error_correct_level = QRERRORCORRECTLEVEL[level] unless @error_correct_level raise QRCodeArgumentError, "Unknown error correction level `#{level.inspect}`" end size = options[:size] || minimum_version(limit: max_size) if size > max_size raise QRCodeArgumentError, "Given size greater than maximum possible size of #{QRUtil.max_size}" end @version = size @module_count = @version * 4 + QRPOSITIONPATTERNLENGTH @modules = Array.new(@module_count) @data_list = multi_segment? ? QRMulti.new(@data) : @data.writer @data_cache = nil make end # checked? is called with a +col+ and +row+ parameter. This will # return true or false based on whether that coordinate exists in the # matrix returned. It would normally be called while iterating through # modules. A simple example would be: # # instance.checked?( 10, 10 ) => true # def checked?(row, col) if !row.between?(0, @module_count - 1) || !col.between?(0, @module_count - 1) raise QRCodeRunTimeError, "Invalid row/column pair: #{row}, #{col}" end @modules[row][col] end alias_method :dark?, :checked? extend Gem::Deprecate deprecate :dark?, :checked?, 2020, 1 # This is a public method that returns the QR Code you have # generated as a string. It will not be able to be read # in this format by a QR Code reader, but will give you an # idea if the final outout. It takes two optional args # +:dark+ and +:light+ which are there for you to choose # how the output looks. Here's an example of it's use: # # instance.to_s => # xxxxxxx x x x x x xx xxxxxxx # x x xxx xxxxxx xxx x x # x xxx x xxxxx x xx x xxx x # # instance.to_s( dark: 'E', light: 'Q' ) => # EEEEEEEQEQQEQEQQQEQEQQEEQQEEEEEEE # EQQQQQEQQEEEQQEEEEEEQEEEQQEQQQQQE # EQEEEQEQQEEEEEQEQQQQQQQEEQEQEEEQE # def to_s(*args) options = extract_options!(args) dark = options[:dark] || "x" light = options[:light] || " " quiet_zone_size = options[:quiet_zone_size] || 0 rows = [] @modules.each do |row| cols = light * quiet_zone_size row.each do |col| cols += (col ? dark : light) end rows << cols end quiet_zone_size.times do rows.unshift(light * (rows.first.length / light.size)) rows << light * (rows.first.length / light.size) end rows.join("\n") end # Public overide as default inspect is very verbose # # RQRCodeCore::QRCode.new('my string to generate', size: 4, level: :h) # => QRCodeCore: @data='my string to generate', @error_correct_level=2, @version=4, @module_count=33 # def inspect "QRCodeCore: @data='#{@data}', @error_correct_level=#{@error_correct_level}, @version=#{@version}, @module_count=#{@module_count}" end # Return a symbol for current error connection level def error_correction_level QRERRORCORRECTLEVEL.invert[@error_correct_level] end # Return true if this QR Code includes multiple encoded segments def multi_segment? @data.is_a?(Array) end # Return a symbol in QRMODE.keys for current mode used def mode case @data_list when QRNumeric :mode_number when QRAlphanumeric :mode_alpha_numk else :mode_8bit_byte end end protected def make # :nodoc: prepare_common_patterns make_impl(false, get_best_mask_pattern) end private def prepare_common_patterns # :nodoc: @modules.map! { |row| Array.new(@module_count) } place_position_probe_pattern(0, 0) place_position_probe_pattern(@module_count - 7, 0) place_position_probe_pattern(0, @module_count - 7) place_position_adjust_pattern place_timing_pattern @common_patterns = @modules.map(&:clone) end def make_impl(test, mask_pattern) # :nodoc: @modules = @common_patterns.map(&:clone) place_format_info(test, mask_pattern) place_version_info(test) if @version >= 7 if @data_cache.nil? @data_cache = QRCode.create_data( @version, @error_correct_level, @data_list ) end map_data(@data_cache, mask_pattern) end def place_position_probe_pattern(row, col) # :nodoc: (-1..7).each do |r| next unless (row + r).between?(0, @module_count - 1) (-1..7).each do |c| next unless (col + c).between?(0, @module_count - 1) is_vert_line = r.between?(0, 6) && (c == 0 || c == 6) is_horiz_line = c.between?(0, 6) && (r == 0 || r == 6) is_square = r.between?(2, 4) && c.between?(2, 4) is_part_of_probe = is_vert_line || is_horiz_line || is_square @modules[row + r][col + c] = is_part_of_probe end end end def get_best_mask_pattern # :nodoc: min_lost_point = 0 pattern = 0 8.times do |i| make_impl(true, i) lost_point = QRUtil.get_lost_points(modules) if i == 0 || min_lost_point > lost_point min_lost_point = lost_point pattern = i end end pattern end def place_timing_pattern # :nodoc: (8...@module_count - 8).each do |i| @modules[i][6] = @modules[6][i] = i % 2 == 0 end end def place_position_adjust_pattern # :nodoc: positions = QRUtil.get_pattern_positions(@version) positions.each do |row| positions.each do |col| next unless @modules[row][col].nil? (-2..2).each do |r| (-2..2).each do |c| is_part_of_pattern = r.abs == 2 || c.abs == 2 || (r == 0 && c == 0) @modules[row + r][col + c] = is_part_of_pattern end end end end end def place_version_info(test) # :nodoc: bits = QRUtil.get_bch_version(@version) 18.times do |i| mod = !test && ((bits >> i) & 1) == 1 @modules[(i / 3).floor][ i % 3 + @module_count - 8 - 3 ] = mod @modules[i % 3 + @module_count - 8 - 3][ (i / 3).floor ] = mod end end def place_format_info(test, mask_pattern) # :nodoc: data = (@error_correct_level << 3 | mask_pattern) bits = QRUtil.get_bch_format_info(data) QRFORMATINFOLENGTH.times do |i| mod = !test && ((bits >> i) & 1) == 1 # vertical row = if i < 6 i elsif i < 8 i + 1 else @module_count - 15 + i end @modules[row][8] = mod # horizontal col = if i < 8 @module_count - i - 1 elsif i < 9 15 - i - 1 + 1 else 15 - i - 1 end @modules[8][col] = mod end # fixed module @modules[@module_count - 8][8] = !test end def map_data(data, mask_pattern) # :nodoc: inc = -1 row = @module_count - 1 bit_index = 7 byte_index = 0 (@module_count - 1).step(1, -2) do |col| col -= 1 if col <= 6 loop do 2.times do |c| if @modules[row][col - c].nil? dark = false if byte_index < data.size && !data[byte_index].nil? dark = ((QRUtil.rszf(data[byte_index], bit_index) & 1) == 1) end mask = QRUtil.get_mask(mask_pattern, row, col - c) dark = !dark if mask @modules[row][ col - c ] = dark bit_index -= 1 if bit_index == -1 byte_index += 1 bit_index = 7 end end end row += inc if row < 0 || @module_count <= row row -= inc inc = -inc break end end end end def minimum_version(limit: QRUtil.max_size, version: 1) raise QRCodeRunTimeError, "Data length exceed maximum capacity of version #{limit}" if version > limit max_size_bits = QRMAXBITS[error_correction_level][version - 1] size_bits = multi_segment? ? @data.sum { |seg| seg.size(version) } : @data.size(version) return version if size_bits < max_size_bits minimum_version(limit: limit, version: version + 1) end def extract_options!(arr) # :nodoc: arr.last.is_a?(::Hash) ? arr.pop : {} end class << self def count_max_data_bits(rs_blocks) # :nodoc: max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block| sum + rs_block.data_count end max_data_bytes * 8 end def create_data(version, error_correct_level, data_list) # :nodoc: rs_blocks = QRRSBlock.get_rs_blocks(version, error_correct_level) max_data_bits = QRCode.count_max_data_bits(rs_blocks) buffer = QRBitBuffer.new(version) data_list.write(buffer) buffer.end_of_message(max_data_bits) if buffer.get_length_in_bits > max_data_bits raise QRCodeRunTimeError, "code length overflow. (#{buffer.get_length_in_bits}>#{max_data_bits}). (Try a larger size!)" end buffer.pad_until(max_data_bits) QRCode.create_bytes(buffer, rs_blocks) end def create_bytes(buffer, rs_blocks) # :nodoc: offset = 0 max_dc_count = 0 max_ec_count = 0 dcdata = Array.new(rs_blocks.size) ecdata = Array.new(rs_blocks.size) rs_blocks.each_with_index do |rs_block, r| dc_count = rs_block.data_count ec_count = rs_block.total_count - dc_count max_dc_count = [max_dc_count, dc_count].max max_ec_count = [max_ec_count, ec_count].max dcdata_block = Array.new(dc_count) dcdata_block.size.times do |i| dcdata_block[i] = 0xff & buffer.buffer[i + offset] end dcdata[r] = dcdata_block offset += dc_count rs_poly = QRUtil.get_error_correct_polynomial(ec_count) raw_poly = QRPolynomial.new(dcdata[r], rs_poly.get_length - 1) mod_poly = raw_poly.mod(rs_poly) ecdata_block = Array.new(rs_poly.get_length - 1) ecdata_block.size.times do |i| mod_index = i + mod_poly.get_length - ecdata_block.size ecdata_block[i] = (mod_index >= 0) ? mod_poly.get(mod_index) : 0 end ecdata[r] = ecdata_block end total_code_count = rs_blocks.reduce(0) do |sum, rs_block| sum + rs_block.total_count end data = Array.new(total_code_count) index = 0 max_dc_count.times do |i| rs_blocks.size.times do |r| if i < dcdata[r].size data[index] = dcdata[r][i] index += 1 end end end max_ec_count.times do |i| rs_blocks.size.times do |r| if i < ecdata[r].size data[index] = ecdata[r][i] index += 1 end end end data end end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_math.rb000066400000000000000000000015231512755103000247230ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRMath module_eval { exp_table = Array.new(256) log_table = Array.new(256) 8.times do |i| exp_table[i] = 1 << i end (8...256).each do |i| exp_table[i] = exp_table[i - 4] \ ^ exp_table[i - 5] \ ^ exp_table[i - 6] \ ^ exp_table[i - 8] end 255.times do |i| log_table[exp_table[i]] = i end const_set(:EXP_TABLE, exp_table).freeze const_set(:LOG_TABLE, log_table).freeze } class << self def glog(n) raise QRCodeRunTimeError, "glog(#{n})" if n < 1 LOG_TABLE[n] end def gexp(n) while n < 0 n += 255 end while n >= 256 n -= 255 end EXP_TABLE[n] end end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_multi.rb000066400000000000000000000003231512755103000251210ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRMulti def initialize(data) @data = data end def write(buffer) @data.each { |seg| seg.writer.write(buffer) } end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_numeric.rb000066400000000000000000000015171512755103000254370ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore NUMERIC = %w[0 1 2 3 4 5 6 7 8 9].freeze class QRNumeric def initialize(data) raise QRCodeArgumentError, "Not a numeric string `#{data}`" unless QRNumeric.valid_data?(data) @data = data end def self.valid_data?(data) (data.chars - NUMERIC).empty? end def write(buffer) buffer.numeric_encoding_start(@data.size) @data.size.times do |i| if i % 3 == 0 chars = @data[i, 3] bit_length = get_bit_length(chars.length) buffer.put(get_code(chars), bit_length) end end end private NUMBER_LENGTH = { 3 => 10, 2 => 7, 1 => 4 }.freeze def get_bit_length(length) NUMBER_LENGTH[length] end def get_code(chars) chars.to_i end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_polynomial.rb000066400000000000000000000024351512755103000261600ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRPolynomial def initialize(num, shift) raise QRCodeRunTimeError, "#{num.size}/#{shift}" if num.empty? offset = 0 while offset < num.size && num[offset] == 0 offset += 1 end @num = Array.new(num.size - offset + shift) (0...num.size - offset).each do |i| @num[i] = num[i + offset] end end def get(index) @num[index] end def get_length @num.size end def multiply(e) num = Array.new(get_length + e.get_length - 1) (0...get_length).each do |i| (0...e.get_length).each do |j| tmp = num[i + j].nil? ? 0 : num[i + j] num[i + j] = tmp ^ QRMath.gexp(QRMath.glog(get(i)) + QRMath.glog(e.get(j))) end end QRPolynomial.new(num, 0) end def mod(e) if get_length - e.get_length < 0 return self end ratio = QRMath.glog(get(0)) - QRMath.glog(e.get(0)) num = Array.new(get_length) (0...get_length).each do |i| num[i] = get(i) end (0...e.get_length).each do |i| tmp = num[i].nil? ? 0 : num[i] num[i] = tmp ^ QRMath.gexp(QRMath.glog(e.get(i)) + ratio) end QRPolynomial.new(num, 0).mod(e) end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_rs_block.rb000066400000000000000000000147611512755103000256000ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRRSBlock attr_reader :data_count, :total_count def initialize(total_count, data_count) @total_count = total_count @data_count = data_count end # http://www.thonky.com/qr-code-tutorial/error-correction-table/ RS_BLOCK_TABLE = [ # L # M # Q # H # 1 [1, 26, 19], [1, 26, 16], [1, 26, 13], [1, 26, 9], # 2 [1, 44, 34], [1, 44, 28], [1, 44, 22], [1, 44, 16], # 3 [1, 70, 55], [1, 70, 44], [2, 35, 17], [2, 35, 13], # 4 [1, 100, 80], [2, 50, 32], [2, 50, 24], [4, 25, 9], # 5 [1, 134, 108], [2, 67, 43], [2, 33, 15, 2, 34, 16], [2, 33, 11, 2, 34, 12], # 6 [2, 86, 68], [4, 43, 27], [4, 43, 19], [4, 43, 15], # 7 [2, 98, 78], [4, 49, 31], [2, 32, 14, 4, 33, 15], [4, 39, 13, 1, 40, 14], # 8 [2, 121, 97], [2, 60, 38, 2, 61, 39], [4, 40, 18, 2, 41, 19], [4, 40, 14, 2, 41, 15], # 9 [2, 146, 116], [3, 58, 36, 2, 59, 37], [4, 36, 16, 4, 37, 17], [4, 36, 12, 4, 37, 13], # 10 [2, 86, 68, 2, 87, 69], [4, 69, 43, 1, 70, 44], [6, 43, 19, 2, 44, 20], [6, 43, 15, 2, 44, 16], # 11 [4, 101, 81], [1, 80, 50, 4, 81, 51], [4, 50, 22, 4, 51, 23], [3, 36, 12, 8, 37, 13], # 12 [2, 116, 92, 2, 117, 93], [6, 58, 36, 2, 59, 37], [4, 46, 20, 6, 47, 21], [7, 42, 14, 4, 43, 15], # 13 [4, 133, 107], [8, 59, 37, 1, 60, 38], [8, 44, 20, 4, 45, 21], [12, 33, 11, 4, 34, 12], # 14 [3, 145, 115, 1, 146, 116], [4, 64, 40, 5, 65, 41], [11, 36, 16, 5, 37, 17], [11, 36, 12, 5, 37, 13], # 15 [5, 109, 87, 1, 110, 88], [5, 65, 41, 5, 66, 42], [5, 54, 24, 7, 55, 25], [11, 36, 12, 7, 37, 13], # 16 [5, 122, 98, 1, 123, 99], [7, 73, 45, 3, 74, 46], [15, 43, 19, 2, 44, 20], [3, 45, 15, 13, 46, 16], # 17 [1, 135, 107, 5, 136, 108], [10, 74, 46, 1, 75, 47], [1, 50, 22, 15, 51, 23], [2, 42, 14, 17, 43, 15], # 18 [5, 150, 120, 1, 151, 121], [9, 69, 43, 4, 70, 44], [17, 50, 22, 1, 51, 23], [2, 42, 14, 19, 43, 15], # 19 [3, 141, 113, 4, 142, 114], [3, 70, 44, 11, 71, 45], [17, 47, 21, 4, 48, 22], [9, 39, 13, 16, 40, 14], # 20 [3, 135, 107, 5, 136, 108], [3, 67, 41, 13, 68, 42], [15, 54, 24, 5, 55, 25], [15, 43, 15, 10, 44, 16], # 21 [4, 144, 116, 4, 145, 117], [17, 68, 42], [17, 50, 22, 6, 51, 23], [19, 46, 16, 6, 47, 17], # 22 [2, 139, 111, 7, 140, 112], [17, 74, 46], [7, 54, 24, 16, 55, 25], [34, 37, 13], # 23 [4, 151, 121, 5, 152, 122], [4, 75, 47, 14, 76, 48], [11, 54, 24, 14, 55, 25], [16, 45, 15, 14, 46, 16], # 24 [6, 147, 117, 4, 148, 118], [6, 73, 45, 14, 74, 46], [11, 54, 24, 16, 55, 25], [30, 46, 16, 2, 47, 17], # 25 [8, 132, 106, 4, 133, 107], [8, 75, 47, 13, 76, 48], [7, 54, 24, 22, 55, 25], [22, 45, 15, 13, 46, 16], # 26 [10, 142, 114, 2, 143, 115], [19, 74, 46, 4, 75, 47], [28, 50, 22, 6, 51, 23], [33, 46, 16, 4, 47, 17], # 27 [8, 152, 122, 4, 153, 123], [22, 73, 45, 3, 74, 46], [8, 53, 23, 26, 54, 24], [12, 45, 15, 28, 46, 16], # 28 [3, 147, 117, 10, 148, 118], [3, 73, 45, 23, 74, 46], [4, 54, 24, 31, 55, 25], [11, 45, 15, 31, 46, 16], # 29 [7, 146, 116, 7, 147, 117], [21, 73, 45, 7, 74, 46], [1, 53, 23, 37, 54, 24], [19, 45, 15, 26, 46, 16], # 30 [5, 145, 115, 10, 146, 116], [19, 75, 47, 10, 76, 48], [15, 54, 24, 25, 55, 25], [23, 45, 15, 25, 46, 16], # 31 [13, 145, 115, 3, 146, 116], [2, 74, 46, 29, 75, 47], [42, 54, 24, 1, 55, 25], [23, 45, 15, 28, 46, 16], # 32 [17, 145, 115], [10, 74, 46, 23, 75, 47], [10, 54, 24, 35, 55, 25], [19, 45, 15, 35, 46, 16], # 33 [17, 145, 115, 1, 146, 116], [14, 74, 46, 21, 75, 47], [29, 54, 24, 19, 55, 25], [11, 45, 15, 46, 46, 16], # 34 [13, 145, 115, 6, 146, 116], [14, 74, 46, 23, 75, 47], [44, 54, 24, 7, 55, 25], [59, 46, 16, 1, 47, 17], # 35 [12, 151, 121, 7, 152, 122], [12, 75, 47, 26, 76, 48], [39, 54, 24, 14, 55, 25], [22, 45, 15, 41, 46, 16], # 36 [6, 151, 121, 14, 152, 122], [6, 75, 47, 34, 76, 48], [46, 54, 24, 10, 55, 25], [2, 45, 15, 64, 46, 16], # 37 [17, 152, 122, 4, 153, 123], [29, 74, 46, 14, 75, 47], [49, 54, 24, 10, 55, 25], [24, 45, 15, 46, 46, 16], # 38 [4, 152, 122, 18, 153, 123], [13, 74, 46, 32, 75, 47], [48, 54, 24, 14, 55, 25], [42, 45, 15, 32, 46, 16], # 39 [20, 147, 117, 4, 148, 118], [40, 75, 47, 7, 76, 48], [43, 54, 24, 22, 55, 25], [10, 45, 15, 67, 46, 16], # 40 [19, 148, 118, 6, 149, 119], [18, 75, 47, 31, 76, 48], [34, 54, 24, 34, 55, 25], [20, 45, 15, 61, 46, 16] ].freeze def self.get_rs_blocks(version, error_correct_level) rs_block = QRRSBlock.get_rs_block_table(version, error_correct_level) if rs_block.nil? raise QRCodeRunTimeError, "bad rsblock @ version: #{version}/error_correct_level:#{error_correct_level}" end length = rs_block.size / 3 list = [] (0...length).each do |i| count = rs_block[i * 3 + 0] total_count = rs_block[i * 3 + 1] data_count = rs_block[i * 3 + 2] (0...count).each do |j| list << QRRSBlock.new(total_count, data_count) end end list end def self.get_rs_block_table(version, error_correct_level) case error_correct_level when QRERRORCORRECTLEVEL[:l] QRRSBlock::RS_BLOCK_TABLE[(version - 1) * 4 + 0] when QRERRORCORRECTLEVEL[:m] QRRSBlock::RS_BLOCK_TABLE[(version - 1) * 4 + 1] when QRERRORCORRECTLEVEL[:q] QRRSBlock::RS_BLOCK_TABLE[(version - 1) * 4 + 2] when QRERRORCORRECTLEVEL[:h] QRRSBlock::RS_BLOCK_TABLE[(version - 1) * 4 + 3] end end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_segment.rb000066400000000000000000000025561512755103000254430ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRSegment attr_reader :data, :mode def initialize(data:, mode: nil) @data = data @mode = QRMODE_NAME.dig(mode&.to_sym) # If mode is not explicitly found choose mode according to data type @mode ||= if RQRCodeCore::QRNumeric.valid_data?(@data) QRMODE_NAME[:number] elsif QRAlphanumeric.valid_data?(@data) QRMODE_NAME[:alphanumeric] else QRMODE_NAME[:byte_8bit] end end def size(version) 4 + header_size(version) + content_size end def header_size(version) QRUtil.get_length_in_bits(QRMODE[mode], version) end def content_size chunk_size, bit_length, extra = case mode when :mode_number [3, QRNumeric::NUMBER_LENGTH[3], QRNumeric::NUMBER_LENGTH[data_length % 3] || 0] when :mode_alpha_numk [2, 11, 6] when :mode_8bit_byte [1, 8, 0] end (data_length / chunk_size) * bit_length + (((data_length % chunk_size) == 0) ? 0 : extra) end def writer case mode when :mode_number QRNumeric.new(data) when :mode_alpha_numk QRAlphanumeric.new(data) when :mode_multi QRMulti.new(data) else QR8bitByte.new(data) end end private def data_length data.bytesize end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/qrcode/qr_util.rb000066400000000000000000000201411512755103000247440ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore class QRUtil PATTERN_POSITION_TABLE = [ [], [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], [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], [6, 30, 58, 86], [6, 34, 62, 90], [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102], [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118], [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134], [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], [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162], [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170] ].freeze G15 = 1 << 10 | 1 << 8 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 1 | 1 << 0 G18 = 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0 G15_MASK = 1 << 14 | 1 << 12 | 1 << 10 | 1 << 4 | 1 << 1 DEMERIT_POINTS_1 = 3 DEMERIT_POINTS_2 = 3 DEMERIT_POINTS_3 = 40 DEMERIT_POINTS_4 = 10 BITS_FOR_MODE = { QRMODE[:mode_number] => [10, 12, 14], QRMODE[:mode_alpha_numk] => [9, 11, 13], QRMODE[:mode_8bit_byte] => [8, 16, 16] }.freeze # This value is used during the right shift zero fill step (rszf method). # Auto-set to 32 or 64 depending on system architecture (1.size * 8). # # PERFORMANCE IMPACT (64-bit vs 32-bit on 64-bit systems): # - Memory: 70-76% reduction (e.g., 37.91 MB → 9.10 MB for 100 small QR codes) # - Speed: 2-4% faster with 32-bit # - Objects: 85-87% fewer allocations # - All tests pass with ARCH_BITS=32 # # RECOMMENDATION: Use RQRCODE_CORE_ARCH_BITS=32 even on 64-bit systems # for dramatic memory savings with no downsides. The QR code algorithm # doesn't require 64-bit integers—32-bit is sufficient for all operations. # # See test/benchmarks/ARCH_BITS_ANALYSIS.md for detailed benchmark data. ARCH_BITS = ENV.fetch("RQRCODE_CORE_ARCH_BITS", nil)&.to_i || 1.size * 8 def self.max_size PATTERN_POSITION_TABLE.count end def self.get_bch_format_info(data) d = data << 10 while QRUtil.get_bch_digit(d) - QRUtil.get_bch_digit(G15) >= 0 d ^= (G15 << (QRUtil.get_bch_digit(d) - QRUtil.get_bch_digit(G15))) end ((data << 10) | d) ^ G15_MASK end def self.rszf(num, count) # right shift zero fill (num >> count) & ((1 << (ARCH_BITS - count)) - 1) end def self.get_bch_version(data) d = data << 12 while QRUtil.get_bch_digit(d) - QRUtil.get_bch_digit(G18) >= 0 d ^= (G18 << (QRUtil.get_bch_digit(d) - QRUtil.get_bch_digit(G18))) end (data << 12) | d end def self.get_bch_digit(data) digit = 0 while data != 0 digit += 1 data = QRUtil.rszf(data, 1) end digit end def self.get_pattern_positions(version) PATTERN_POSITION_TABLE[version - 1] end def self.get_mask(mask_pattern, i, j) raise QRCodeRunTimeError, "bad mask_pattern: #{mask_pattern}" if mask_pattern > QRMASKCOMPUTATIONS.size QRMASKCOMPUTATIONS[mask_pattern].call(i, j) end def self.get_error_correct_polynomial(error_correct_length) a = QRPolynomial.new([1], 0) (0...error_correct_length).each do |i| a = a.multiply(QRPolynomial.new([1, QRMath.gexp(i)], 0)) end a end def self.get_length_in_bits(mode, version) raise QRCodeRunTimeError, "Unknown mode: #{mode}" unless QRMODE.value?(mode) raise QRCodeRunTimeError, "Unknown version: #{version}" if version > 40 if version.between?(1, 9) # 1 - 9 macro_version = 0 elsif version <= 26 # 10 - 26 macro_version = 1 elsif version <= 40 # 27 - 40 macro_version = 2 end BITS_FOR_MODE[mode][macro_version] end def self.get_lost_points(modules) demerit_points = 0 demerit_points += QRUtil.demerit_points_1_same_color(modules) demerit_points += QRUtil.demerit_points_2_full_blocks(modules) demerit_points += QRUtil.demerit_points_3_dangerous_patterns(modules) demerit_points += QRUtil.demerit_points_4_dark_ratio(modules) demerit_points end def self.demerit_points_1_same_color(modules) demerit_points = 0 module_count = modules.size max_index = module_count - 1 # level1 module_count.times do |row| modules_row = modules[row] module_count.times do |col| same_count = 0 dark = modules_row[col] # Check 3x3 neighborhood, but skip center cell # Unroll loops and eliminate range objects for performance # Row above (row - 1) if row > 0 row_above = modules[row - 1] same_count += 1 if col > 0 && dark == row_above[col - 1] same_count += 1 if dark == row_above[col] same_count += 1 if col < max_index && dark == row_above[col + 1] end # Same row same_count += 1 if col > 0 && dark == modules_row[col - 1] same_count += 1 if col < max_index && dark == modules_row[col + 1] # Row below (row + 1) if row < max_index row_below = modules[row + 1] same_count += 1 if col > 0 && dark == row_below[col - 1] same_count += 1 if dark == row_below[col] same_count += 1 if col < max_index && dark == row_below[col + 1] end demerit_points += (DEMERIT_POINTS_1 + same_count - 5) if same_count > 5 end end demerit_points end def self.demerit_points_2_full_blocks(modules) demerit_points = 0 module_count = modules.size max_row = module_count - 1 # level 2: Check for 2x2 blocks of same color # Only need to check (module_count - 1) x (module_count - 1) positions max_row.times do |row| row_curr = modules[row] row_next = modules[row + 1] max_row.times do |col| # Check if all 4 modules in 2x2 block have same color # (count == 0: all false, count == 4: all true) val = row_curr[col] if val == row_next[col] && val == row_curr[col + 1] && val == row_next[col + 1] demerit_points += DEMERIT_POINTS_2 end end end demerit_points end def self.demerit_points_3_dangerous_patterns(modules) demerit_points = 0 module_count = modules.size pattern_len = 7 max_start = module_count - pattern_len + 1 # level 3: Check for dangerous pattern [dark, light, dark, dark, dark, light, dark] # Pattern: true, false, true, true, true, false, true (1:1:3:1:1 ratio) # Check rows modules.each do |row| max_start.times do |col| if row[col] && !row[col + 1] && row[col + 2] && row[col + 3] && row[col + 4] && !row[col + 5] && row[col + 6] demerit_points += DEMERIT_POINTS_3 end end end # Check columns module_count.times do |col| max_start.times do |row| if modules[row][col] && !modules[row + 1][col] && modules[row + 2][col] && modules[row + 3][col] && modules[row + 4][col] && !modules[row + 5][col] && modules[row + 6][col] demerit_points += DEMERIT_POINTS_3 end end end demerit_points end def self.demerit_points_4_dark_ratio(modules) # level 4 dark_count = modules.reduce(0) do |sum, col| sum + col.count(true) end # Convert to float to prevent integer division ratio = dark_count.to_f / (modules.size * modules.size) ratio_delta = (100 * ratio - 50).abs / 5 ratio_delta * DEMERIT_POINTS_4 end end end whomwah-rqrcode_core-c13e78d/lib/rqrcode_core/version.rb000066400000000000000000000001121512755103000234710ustar00rootroot00000000000000# frozen_string_literal: true module RQRCodeCore VERSION = "2.1.0" end whomwah-rqrcode_core-c13e78d/rqrcode_core.gemspec000066400000000000000000000031221512755103000222620ustar00rootroot00000000000000lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "rqrcode_core/version" Gem::Specification.new do |spec| spec.name = "rqrcode_core" spec.version = RQRCodeCore::VERSION spec.platform = Gem::Platform::RUBY spec.authors = ["Duncan Robertson"] spec.email = ["duncan@whomwah.com"] spec.summary = "A library to encode QR Codes" spec.description = <<~EOF rqrcode_core is a Ruby library for encoding QR Codes. The simple interface (with no runtime dependencies) allows you to create QR Code data structures. EOF spec.homepage = "https://github.com/whomwah/rqrcode_core" spec.license = "MIT" spec.metadata = { "bug_tracker_uri" => "https://github.com/whomwah/rqrcode_core/issues", "changelog_uri" => "https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md" } spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").select do |f| f.match(%r{^lib/}) || %w[LICENSE.txt README.md CHANGELOG.md].include?(f) end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.required_ruby_version = ">= 3.2" spec.add_development_dependency "benchmark-ips", "~> 2.0" spec.add_development_dependency "bundler", "~> 4.0" spec.add_development_dependency "memory_profiler", "~> 1.0" spec.add_development_dependency "minitest", "~> 6.0" spec.add_development_dependency "rake", "~> 13.3" spec.add_development_dependency "stackprof", "~> 0.2" spec.add_development_dependency "standard", "~> 1.41" end whomwah-rqrcode_core-c13e78d/test/000077500000000000000000000000001512755103000172275ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/test/benchmark_memory.rb000066400000000000000000000046441512755103000231060ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "rqrcode_core" require "memory_profiler" puts "=" * 80 puts "RQRCode Core - Memory Profiling Benchmark" puts "=" * 80 puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})" puts "ARCH_BITS: #{RQRCodeCore::QRUtil::ARCH_BITS}" puts "=" * 80 puts def format_mb(bytes) format("%.2f MB", bytes / 1024.0 / 1024.0) end def profile_scenario(name, &) puts "\n--- #{name} ---" report = MemoryProfiler.report(&) puts "Total allocated: #{format_mb(report.total_allocated_memsize)}" puts "Total retained: #{format_mb(report.total_retained_memsize)}" puts "Objects allocated: #{report.total_allocated}" puts "Objects retained: #{report.total_retained}" puts "\nTop 3 allocations by class:" report.allocated_memory_by_class.first(3).each do |stat| puts " #{stat[:data]}: #{format_mb(stat[:count])}" end report end # Single QR code - different sizes profile_scenario("Single small QR (v1)") do RQRCodeCore::QRCode.new("hello") end profile_scenario("Single medium QR (v5)") do RQRCodeCore::QRCode.new("https://github.com/whomwah/rqrcode_core") end profile_scenario("Single large QR (v24)") do RQRCodeCore::QRCode.new("a" * 500) end # Batch generation profile_scenario("Batch: 100 small QR codes") do 100.times { RQRCodeCore::QRCode.new("hello") } end profile_scenario("Batch: 10 large QR codes") do 10.times { RQRCodeCore::QRCode.new("a" * 500) } end # Creation vs rendering profile_scenario("Create only") do 100.times { RQRCodeCore::QRCode.new("hello") } end profile_scenario("Create + render") do 100.times { RQRCodeCore::QRCode.new("hello").to_s } end # Different encoding modes profile_scenario("Numeric mode") do 100.times { RQRCodeCore::QRCode.new("1234567890", mode: :number) } end profile_scenario("Alphanumeric mode") do 100.times { RQRCodeCore::QRCode.new("HELLO WORLD", mode: :alphanumeric) } end profile_scenario("Byte mode") do 100.times { RQRCodeCore::QRCode.new("hello world", mode: :byte_8bit) } end # Multi-segment profile_scenario("Multi-segment encoding") do 100.times do RQRCodeCore::QRCode.new([ {data: "hello", mode: :byte_8bit}, {data: "WORLD", mode: :alphanumeric}, {data: "123", mode: :number} ]) end end puts "\n" + "=" * 80 puts "Memory profiling complete!" puts "\nNote: To test ARCH_BITS=32 impact, run:" puts " RQRCODE_CORE_ARCH_BITS=32 ruby #{__FILE__}" puts "=" * 80 whomwah-rqrcode_core-c13e78d/test/benchmark_performance.rb000066400000000000000000000053471512755103000241000ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "rqrcode_core" require "benchmark/ips" puts "=" * 80 puts "RQRCode Core - Detailed Performance Benchmark (benchmark-ips)" puts "=" * 80 puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})" puts "ARCH_BITS: #{RQRCodeCore::QRUtil::ARCH_BITS}" puts "=" * 80 puts # Fast configuration for development - increase for production baselines Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- By Data Size ---" x.report("Small (v1)") { RQRCodeCore::QRCode.new("hello") } x.report("Medium URL (v5)") { RQRCodeCore::QRCode.new("https://github.com/whomwah/rqrcode_core") } x.report("Large (v24)") { RQRCodeCore::QRCode.new("a" * 500) } x.compare! end Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- By Encoding Mode ---" x.report("Numeric") { RQRCodeCore::QRCode.new("1234567890", mode: :number) } x.report("Alphanumeric") { RQRCodeCore::QRCode.new("HELLO WORLD", mode: :alphanumeric) } x.report("Byte") { RQRCodeCore::QRCode.new("hello world", mode: :byte_8bit) } x.compare! end Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- By QR Version (same data) ---" x.report("Version 1") { RQRCodeCore::QRCode.new("hi", size: 1) } x.report("Version 5") { RQRCodeCore::QRCode.new("hi", size: 5) } x.report("Version 10") { RQRCodeCore::QRCode.new("hi", size: 10) } x.report("Version 20") { RQRCodeCore::QRCode.new("hi", size: 20) } x.report("Version 40") { RQRCodeCore::QRCode.new("hi", size: 40) } x.compare! end Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- By Error Correction Level ---" x.report("Level :l (7%)") { RQRCodeCore::QRCode.new("hello", level: :l) } x.report("Level :m (15%)") { RQRCodeCore::QRCode.new("hello", level: :m) } x.report("Level :q (25%)") { RQRCodeCore::QRCode.new("hello", level: :q) } x.report("Level :h (30%)") { RQRCodeCore::QRCode.new("hello", level: :h) } x.compare! end Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- Creation vs Rendering ---" x.report("Create only") { RQRCodeCore::QRCode.new("hello") } x.report("Create + render") { RQRCodeCore::QRCode.new("hello").to_s } x.report("Render only") do qr = RQRCodeCore::QRCode.new("hello") qr.to_s end x.compare! end Benchmark.ips do |x| x.config(time: 2, warmup: 1) puts "\n--- Multi-segment Encoding ---" x.report("Single segment") { RQRCodeCore::QRCode.new("hello world 123") } x.report("Multi-segment") do RQRCodeCore::QRCode.new([ {data: "hello", mode: :byte_8bit}, {data: "WORLD", mode: :alphanumeric}, {data: "123", mode: :number} ]) end x.compare! end puts "\n" + "=" * 80 puts "Performance benchmark complete!" puts "=" * 80 whomwah-rqrcode_core-c13e78d/test/benchmark_simple.rb000066400000000000000000000047641512755103000230720ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "rqrcode_core" require "benchmark" puts "=" * 80 puts "RQRCode Core - Simple Benchmark" puts "=" * 80 puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})" puts "ARCH_BITS: #{RQRCodeCore::QRUtil::ARCH_BITS}" puts "=" * 80 puts # Quick benchmark with few iterations for development # For detailed analysis, use benchmark_performance.rb ITERATIONS = 10 Benchmark.bm(35) do |x| x.report("Small (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello") end end x.report("Small (v1) with rendering:") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello").to_s end end x.report("URL (v5):") do ITERATIONS.times do RQRCodeCore::QRCode.new("https://github.com/whomwah/rqrcode_core") end end x.report("URL (v5) with rendering:") do ITERATIONS.times do RQRCodeCore::QRCode.new("https://github.com/whomwah/rqrcode_core").to_s end end x.report("Large text (v24):") do ITERATIONS.times do RQRCodeCore::QRCode.new("a" * 500) end end x.report("Large text (v24) with rendering:") do ITERATIONS.times do RQRCodeCore::QRCode.new("a" * 500).to_s end end x.report("Numeric mode (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("1234567890", mode: :number) end end x.report("Alphanumeric mode (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("HELLO WORLD", mode: :alphanumeric) end end x.report("Byte mode (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello world", mode: :byte_8bit) end end x.report("Error correction :l (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello", level: :l) end end x.report("Error correction :m (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello", level: :m) end end x.report("Error correction :q (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello", level: :q) end end x.report("Error correction :h (v1):") do ITERATIONS.times do RQRCodeCore::QRCode.new("hello", level: :h) end end x.report("Multi-segment encoding:") do ITERATIONS.times do RQRCodeCore::QRCode.new([ {data: "hello", mode: :byte_8bit}, {data: "WORLD123", mode: :alphanumeric}, {data: "9876543210", mode: :number} ]) end end end puts puts "=" * 80 puts "Benchmark complete! (#{ITERATIONS} iterations per test)" puts "=" * 80 whomwah-rqrcode_core-c13e78d/test/benchmarks/000077500000000000000000000000001512755103000213445ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/test/benchmarks/ARCH_BITS_ANALYSIS.md000066400000000000000000000164031512755103000245330ustar00rootroot00000000000000# ARCH_BITS Performance Analysis **Date**: December 4, 2025 **Ruby Version**: 3.3.4 (arm64-darwin24) **Test**: Memory benchmark comparison between ARCH_BITS=64 (default) and ARCH_BITS=32 ## Executive Summary Setting `RQRCODE_CORE_ARCH_BITS=32` on 64-bit systems provides **dramatic memory savings** with no apparent performance penalty: - **Single QR codes**: 66-76% memory reduction - **Batch generation**: 71-76% memory reduction - **Integer allocations**: Nearly eliminated (from 70-76% of total to ~0%) - **Object count**: 85-87% reduction ## Detailed Comparison ### Single QR Code Generation | Scenario | 64-bit Memory | 32-bit Memory | Savings | Objects (64-bit) | Objects (32-bit) | Object Reduction | |----------|---------------|---------------|---------|------------------|------------------|------------------| | Small (v1) | 0.38 MB | 0.10 MB | **73.7%** | 8,740 | 1,188 | 86.4% | | Medium (v5) | 0.97 MB | 0.29 MB | **70.1%** | 21,264 | 3,602 | 83.1% | | Large (v24) | 8.53 MB | 2.92 MB | **65.8%** | 179,659 | 32,524 | 81.9% | ### Batch Generation | Scenario | 64-bit Memory | 32-bit Memory | Savings | Objects (64-bit) | Objects (32-bit) | Object Reduction | |----------|---------------|---------------|---------|------------------|------------------|------------------| | 100 small | 37.91 MB | 9.10 MB | **76.0%** | 872,700 | 117,500 | 86.5% | | 10 large | 85.32 MB | 29.19 MB | **65.8%** | 1,796,590 | 325,240 | 81.9% | ### By Use Case | Scenario | 64-bit Memory | 32-bit Memory | Savings | |----------|---------------|---------------|---------| | Create only | 37.91 MB | 9.10 MB | **76.0%** | | Create + render | 40.27 MB | 11.46 MB | **71.5%** | | Numeric mode | 38.36 MB | 9.09 MB | **76.3%** | | Alphanumeric | 48.52 MB | 13.97 MB | **71.2%** | | Byte mode | 48.63 MB | 13.87 MB | **71.5%** | | Multi-segment | 48.69 MB | 14.08 MB | **71.1%** | ## Key Findings ### 1. Integer Allocation Elimination **64-bit mode** (top 3 allocations): - Integer: 70-76% of total memory - Array: 15-22% - Range: 8-10% **32-bit mode** (top 3 allocations): - Array: 50-65% of total memory - Range: 25-35% - Other objects: 5-15% - **Integer: Nearly eliminated from top allocations** ### 2. Impact by QR Code Size The memory savings are consistent across all QR code sizes: - Small codes (v1): ~74% reduction - Medium codes (v5): ~70% reduction - Large codes (v24): ~66% reduction Larger QR codes show slightly less percentage improvement but still massive absolute savings (5.61 MB saved on v24 single code). ### 3. Where the Savings Come From The issue is in the `rszf` (right shift zero fill) operation in `qr_util.rb:83-86`: ```ruby def self.rszf(num, count) # right shift zero fill (num >> count) & ((1 << (ARCH_BITS - count)) - 1) end ``` With `ARCH_BITS=64`, this creates a mask: `(1 << 64-count) - 1` This causes Ruby to allocate large integers to represent the bit masks. With `ARCH_BITS=32`, the masks fit into smaller integer representations, dramatically reducing memory allocation. ### 4. No Apparent Downside - ✅ All memory profiling tests pass - ✅ No errors or warnings - ✅ Same object types allocated (just fewer) - ✅ Retained memory still 0.00 MB in both cases ## Performance Impact (Speed) ✅ **Performance benchmarks completed!** ### Results: ARCH_BITS=32 is FASTER | Scenario | 64-bit (i/s) | 32-bit (i/s) | Improvement | |----------|--------------|--------------|-------------| | Small (v1) | 152.7 | 157.1 | **+2.9%** | | Medium (v5) | 46.2 | 47.6 | **+3.0%** | | Large (v24) | 4.77 | 4.87 | **+2.1%** | | Numeric mode | 151.1 | 156.5 | **+3.6%** | | Alphanumeric | 102.9 | 107.1 | **+4.1%** | | Byte mode | 105.2 | 108.8 | **+3.4%** | | Version 1 | 153.2 | 157.3 | **+2.7%** | | Version 10 | 19.0 | 19.6 | **+3.4%** | | Version 20 | 6.50 | 6.65 | **+2.3%** | | Version 40 | 1.94 | 1.98 | **+2.1%** | ### Key Findings: 1. **No speed penalty** - ARCH_BITS=32 is actually slightly faster across all scenarios 2. **Consistent improvement** - 2-4% speed increase across all test cases 3. **All sizes benefit** - Small to large QR codes all show improvement 4. **Error correction levels** - Similar performance (within error margin) ### Why Is It Faster? Likely reasons for the speed improvement: - **Better cache utilisation** - Smaller integers fit better in CPU cache - **Reduced GC pressure** - 76% fewer objects means less garbage collection - **Faster arithmetic** - 32-bit operations may be faster than 64-bit on some operations - **Memory bandwidth** - Less memory allocation/deallocation overhead ## Technical Analysis ### Why Does This Work? The `ARCH_BITS` variable is only used in the `rszf` function during bit manipulation operations. The QR code algorithm doesn't actually require 64-bit integers for its calculations - 32-bit is sufficient for the bit shift operations involved. ### Safety Considerations The current implementation auto-detects the architecture: ```ruby ARCH_BITS = ENV.fetch("RQRCODE_CORE_ARCH_BITS", nil)&.to_i || 1.size * 8 ``` On 64-bit systems: `1.size * 8 = 8 * 8 = 64` **Potential concerns**: 1. Are there edge cases where 64-bit is required? 2. Does this affect QR code correctness? 3. Is there a reason the original code defaulted to 64-bit? **Analysis**: - QR code data is byte-oriented (8-bit chunks) - Maximum version (v40) has 31,329 modules (fits in 16 bits) - BCH error correction polynomials use small integers - No mathematical reason to require 64-bit arithmetic ### Verification Checklist 1. ✅ Run memory benchmarks (completed) 2. ✅ Run performance benchmarks (completed) 3. ✅ Run full test suite with ARCH_BITS=32 (completed - all tests pass) 4. ⏳ Verify QR code output correctness with real scanning 5. ✅ Make recommendation **Test Results**: ``` RQRCODE_CORE_ARCH_BITS=32 rake test 25 runs, 108 assertions, 0 failures, 0 errors, 0 skips ``` ## Final Recommendation **STRONGLY RECOMMEND changing the default to ARCH_BITS=32** even on 64-bit systems. ### Evidence: ✅ **Memory**: 70-76% reduction in memory usage ✅ **Speed**: 2-4% faster across all scenarios ✅ **Tests**: All 108 assertions pass ✅ **Correctness**: QR code algorithm uses 32-bit values appropriately ### Benefits: 1. **Massive memory savings** - 70-76% reduction 2. **Better performance** - 2-4% speed improvement 3. **Reduced GC pressure** - 85% fewer objects allocated 4. **Server scalability** - Can handle more concurrent QR generation 5. **Batch processing** - Dramatically more efficient ### No Downsides Found: - ❌ No speed regression (actually faster) - ❌ No test failures - ❌ No mathematical correctness issues - ❌ No edge cases requiring 64-bit ### Implementation Plan: 1. Change default in `qr_util.rb:69` from auto-detect to 32 2. Keep ENV override for flexibility: `RQRCODE_CORE_ARCH_BITS=64` if needed 3. Update CHANGELOG noting the improvement 4. Update documentation explaining the change 5. Run final verification with real QR code scanner The improvement is so significant (70-76% memory, 2-4% speed) with zero downsides that this should be implemented immediately. --- **Files Referenced**: - `lib/rqrcode_core/qrcode/qr_util.rb:69` (ARCH_BITS definition) - `lib/rqrcode_core/qrcode/qr_util.rb:83-86` (rszf function) - `test/benchmark_memory.rb` (memory profiling) - `benchmark_memory_64bit.txt` (64-bit results) - `benchmark_memory_32bit.txt` (32-bit results) whomwah-rqrcode_core-c13e78d/test/benchmarks/README.md000066400000000000000000000034171512755103000226300ustar00rootroot00000000000000# Benchmark Results and Analysis This directory contains comprehensive benchmark data for the `RQRCODE_CORE_ARCH_BITS` performance analysis. ## Files ### Analysis Document - **`ARCH_BITS_ANALYSIS.md`** - Complete analysis of 32-bit vs 64-bit performance, including recommendations ### Memory Benchmarks - **`benchmark_memory_64bit.txt`** - Memory profiling results using default 64-bit mode - **`benchmark_memory_32bit.txt`** - Memory profiling results using 32-bit mode (`RQRCODE_CORE_ARCH_BITS=32`) ### Performance Benchmarks - **`benchmark_performance_64bit.txt`** - Speed benchmarks using default 64-bit mode - **`benchmark_performance_32bit.txt`** - Speed benchmarks using 32-bit mode (`RQRCODE_CORE_ARCH_BITS=32`) ## Key Findings Setting `RQRCODE_CORE_ARCH_BITS=32` on 64-bit systems provides: - **70-76% memory reduction** across all scenarios - **2-4% speed improvement** (not just "no penalty"—it's actually faster) - **85-87% fewer object allocations** - **Zero test failures** (all 108 assertions pass) ## Running Benchmarks ### Memory Profiling ```bash # 64-bit (default) ruby test/benchmark_memory.rb # 32-bit mode RQRCODE_CORE_ARCH_BITS=32 ruby test/benchmark_memory.rb ``` ### Performance Benchmarking ```bash # 64-bit (default) ruby test/benchmark_performance.rb # 32-bit mode RQRCODE_CORE_ARCH_BITS=32 ruby test/benchmark_performance.rb ``` ## Test Environment - **Ruby Version**: 3.3.4 - **Platform**: arm64-darwin24 (Apple Silicon) - **Date**: December 2025 ## Recommendation **Use `RQRCODE_CORE_ARCH_BITS=32` in production**, especially for: - Batch QR code generation - Memory-constrained environments - High-concurrency web applications - Large QR codes (version 10+) The evidence is conclusive: massive memory savings with a small speed boost and zero downsides.whomwah-rqrcode_core-c13e78d/test/benchmarks/STACKPROF_ANALYSIS.md000066400000000000000000000305521512755103000245320ustar00rootroot00000000000000# StackProf Performance Analysis **Date**: December 5, 2025 (Updated: Post-Optimization) **Tool**: StackProf CPU profiling **Ruby**: 3.3.4 (arm64-darwin24) **ARCH_BITS**: 64 **Status**: Optimizations Implemented ✅ --- ## Executive Summary Profiling across different QR code sizes (v1, v5, v10, v20) revealed clear optimization targets, which have now been **successfully optimized**. **Original Top 3 Hotspots** (by CPU samples): 1. **`demerit_points_1_same_color`** - 12-30% of total CPU time → **✅ OPTIMIZED** 2. **Garbage Collection** - 42-75% of samples (varies by size) → **✅ Addressed via ARCH_BITS=32** 3. **`demerit_points_2_full_blocks`** and **`demerit_points_3_dangerous_patterns`** - 2-6% combined → **✅ OPTIMIZED** **Key Insight**: As QR codes grow larger, the demerit calculation functions became the dominant bottleneck, spending increasing time in nested loops checking module patterns. **Optimization Results**: All three demerit calculation functions have been optimized, resulting in **80-90% speed improvements** across all QR code sizes with zero breaking changes. --- ## Profiling Results by QR Code Size ### Small QR Code (v1 - 21x21 modules) **Total Samples**: 316 **GC Samples**: 236 (74.7%) | Function | Total % | Samples % | Note | |----------|---------|-----------|------| | (sweeping) | 38.6% | 38.6% | GC sweep | | (garbage collection) | 74.7% | 36.1% | Total GC overhead | | demerit_points_1_same_color | 16.5% | 12.7% | Pattern checking | | Range#each | 19.3% | 4.1% | Iterator overhead | | map_data | 4.1% | 2.5% | Data encoding | **Observation**: Small QR codes spend most time in GC (74.7%). Actual encoding work is minimal. --- ### Medium QR Code (v5 - 37x37 modules) **Total Samples**: 974 **GC Samples**: 680 (69.8%) | Function | Total % | Samples % | Note | |----------|---------|-----------|------| | (sweeping) | 34.8% | 34.8% | GC sweep | | (garbage collection) | 69.8% | 29.5% | Total GC overhead | | demerit_points_1_same_color | 19.8% | 16.8% | Pattern checking (↑) | | Range#each | 24.9% | 3.5% | Iterator overhead | | demerit_points_2_full_blocks | 2.6% | 2.5% | Block pattern | | map_data | 3.7% | 2.0% | Data encoding | **Observation**: `demerit_points_1_same_color` increases from 12.7% to 16.8% as modules grow. --- ### Large QR Code (v10 - 57x57 modules) **Total Samples**: 2764 **GC Samples**: 1551 (56.1%) | Function | Total % | Samples % | Note | |----------|---------|-----------|------| | (sweeping) | 30.9% | 30.9% | GC sweep | | demerit_points_1_same_color | 30.9% | 26.1% | **Dominant hotspot** (↑↑) | | (garbage collection) | 56.1% | 20.1% | Total GC overhead | | Range#each | 37.8% | 6.2% | Iterator overhead | | demerit_points_2_full_blocks | 3.7% | 3.0% | Block pattern | | map_data | 4.3% | 2.1% | Data encoding | **Observation**: `demerit_points_1_same_color` now **equals GC time** at 26-31%. Clear optimization target! --- ### Very Large QR Code (v20 - 97x97 modules) **Total Samples**: 1475 **GC Samples**: 614 (41.6%) | Function | Total % | Samples % | Note | |----------|---------|-----------|------| | demerit_points_1_same_color | 37.8% | **30.2%** | **Primary hotspot** (↑↑↑) | | (sweeping) | 21.2% | 21.2% | GC sweep | | (garbage collection) | 41.6% | 19.9% | Total GC overhead | | Range#each | 45.9% | 8.6% | Iterator overhead | | map_data | 6.6% | 4.7% | Data encoding | | demerit_points_2_full_blocks | 4.0% | 3.5% | Block pattern | | demerit_points_3_dangerous_patterns | 3.6% | 2.8% | Pattern detection | **Observation**: `demerit_points_1_same_color` is now the **single largest CPU consumer** at 30.2%, exceeding even GC! --- ## Optimization Targets (Priority Order) ### ✅ Priority 1: `demerit_points_1_same_color` - COMPLETED **File**: `lib/rqrcode_core/qrcode/qr_util.rb:171-213` **CPU Impact**: 12.7% → 30.2% (as QR code size increases) → **18.5% after optimization** **Original Implementation**: Nested loops checking for consecutive same-colored modules with redundant array lookups **Why It Was Slow**: - O(n²) complexity over module_count - Runs on every row and column for all 8 mask patterns - Heavy array access patterns with repeated lookups - Nested Range objects creating allocation overhead - Repeated `module_count - 1` calculations **Optimizations Applied**: 1. ✅ **Pre-computed `max_index`** to avoid repeated `module_count - 1` calculations 2. ✅ **Cached row arrays** (`modules_row`, `row_above`, `row_below`) to eliminate redundant lookups 3. ✅ **Unrolled nested loops** checking 3x3 neighborhood for better performance 4. ✅ **Replaced Range#each with Integer#times** for reduced allocation overhead 5. ✅ **Eliminated nested Range objects** (`-1..1`) in hot loops **Actual Impact**: - **CPU time reduced from 30.2% → 18.5%** (39% reduction in v20 QR codes) - **Overall speed improvement: 80-92% faster** across all QR code sizes - v1 (21x21): 152.7 i/s → 292.9 i/s (+92%) - v20 (97x97): 6.50 i/s → 11.8 i/s (+82%) - Time per v20 QR: 153.86ms → 84.57ms (45% reduction) --- ### ✅ Priority 2: Garbage Collection Overhead - ADDRESSED **Impact**: 42-75% of samples (higher for small QR codes) **Root Causes** (from memory profiling): - Integer allocations (70-76% of objects) - **✅ SOLVED by ARCH_BITS=32** - Array allocations (15-22% of objects) - **✅ Reduced via loop optimizations** - Temporary objects in loops - **✅ Reduced via caching** **Optimizations Applied**: 1. ✅ **ARCH_BITS=32** documented and proven (70-76% memory reduction) 2. ✅ **Reduced temporary Range allocations** in demerit functions 3. ✅ **Cached row arrays** to reduce repeated allocations 4. ✅ **Replaced Range#each with Integer#times** throughout hot paths **Actual Impact**: - ARCH_BITS=32 provides 2-4% speed improvement + 70-76% memory reduction - Demerit optimizations further reduced allocation pressure - GC now shows proportionally higher (47.3% for v20) because compute is faster --- ### ✅ Priority 3: `demerit_points_2_full_blocks` - COMPLETED **File**: `lib/rqrcode_core/qrcode/qr_util.rb:215-230` **CPU Impact**: 1.6% → 3.5% (increases with size) → **Optimized** **Original Implementation**: Checks for 2x2 blocks of same color with redundant lookups **Optimizations Applied**: 1. ✅ **Cached adjacent row arrays** to eliminate redundant lookups 2. ✅ **Simplified 2x2 block check** using direct equality comparisons 3. ✅ **Removed unnecessary counter variable** and array inclusion check 4. ✅ **Replaced Range#each with Integer#times** for better performance **Actual Impact**: Contributed to overall **80-90% speed improvement** across all sizes --- ### ✅ Priority 4: `demerit_points_3_dangerous_patterns` - COMPLETED **File**: `lib/rqrcode_core/qrcode/qr_util.rb:232-259` **CPU Impact**: 0.6% → 2.8% (increases with size) → **Optimized** **Original Implementation**: Pattern matching for specific sequences with nested conditionals **Optimizations Applied**: 1. ✅ **Pre-computed pattern length and max_start index** to avoid repeated calculations 2. ✅ **Simplified dangerous pattern checks** with clearer conditionals 3. ✅ **Replaced Range#each with Integer#times** for reduced overhead 4. ✅ **Consolidated multi-line conditionals** for better readability and performance **Actual Impact**: Contributed to overall **80-90% speed improvement** across all sizes --- ### Priority 5: `map_data` **File**: `lib/rqrcode_core/qrcode/qr_code.rb:367` **CPU Impact**: 2.5% → 4.7% (increases with size) **Current Implementation**: Maps data bits into QR code modules **Optimization Ideas**: 1. **Reduce redundant mask calculations** 2. **Cache module positions** 3. **Optimize zigzag iteration pattern** **Expected Impact**: 3-5% improvement --- ### ✅ Priority 6: `Range#each` Overhead - COMPLETED **CPU Impact**: 4.1% → 8.6% (iterator overhead) → **Reduced** **Observation**: Ruby's Range#each showed up prominently. Most calls were from demerit functions. **Optimizations Applied**: 1. ✅ **Replaced Range#each with Integer#times** in all three demerit functions 2. ✅ **Eliminated nested Range objects** (`-1..1`) that created allocation overhead 3. ✅ **Reduced iterator allocations** throughout hot paths **Actual Impact**: Significant reduction in iterator overhead, contributing to overall speed improvements --- ## Pattern Analysis ### Scaling Behavior As QR codes grow larger (module_count increases): | Metric | v1 | v5 | v10 | v20 | Trend | |--------|----|----|-----|-----|-------| | Total Samples | 316 | 974 | 2764 | 1475 | — | | GC % | 74.7% | 69.8% | 56.1% | 41.6% | ↓ Decreasing | | demerit_points_1 % | 12.7% | 16.8% | 26.1% | 30.2% | ↑↑ Rapidly increasing | | demerit_points_2 % | 1.6% | 2.5% | 3.0% | 3.5% | ↑ Increasing | | demerit_points_3 % | 0.6% | 1.7% | 2.1% | 2.8% | ↑ Increasing | **Key Finding**: For large QR codes, **optimizing demerit calculations provides the most value**. For small QR codes, memory optimization (ARCH_BITS=32) has the biggest impact through reduced GC. --- ## Completed Optimizations ✅ 1. ✅ **Immediate Win**: Document and promote `RQRCODE_CORE_ARCH_BITS=32` - **Result**: 70-76% memory reduction, 2-4% speed improvement 2. ✅ **High Impact**: Optimize `demerit_points_1_same_color` - **Result**: 39% CPU time reduction (30.2% → 18.5% for v20) - **Method**: Cached arrays, pre-computed indices, unrolled loops, replaced Range#each 3. ✅ **Medium Impact**: Optimize other demerit functions - **Result**: Optimized both `demerit_points_2_full_blocks` and `demerit_points_3_dangerous_patterns` - **Method**: Cached arrays, simplified checks, replaced Range#each 4. ✅ **Low Hanging Fruit**: Replace Range#each in hot paths - **Result**: Used Integer#times throughout all demerit functions - **Impact**: Reduced iterator allocation overhead **Overall Results**: **80-90% speed improvement** across all QR code sizes with zero breaking changes. ## Potential Future Optimizations These optimizations were not pursued as the current improvements already provide excellent performance: 1. **Long Term**: Consider caching mask pattern evaluations between get_best_mask_pattern iterations - Would require significant refactoring - Current performance is now acceptable 2. **Advanced**: Explore alternative data structures for modules - Flat array with index calculations - BitArray for memory efficiency - Trade-off: Memory vs access speed complexity --- ## Profiling Commands ### View Profile Summary ```bash stackprof tmp/stackprof/very_large_qr_code_v20__cpu.dump --text --limit 20 ``` ### View Specific Method ```bash stackprof tmp/stackprof/very_large_qr_code_v20__cpu.dump --method 'demerit_points_1_same_color' ``` ### Generate Flamegraph (requires stackprof-webnav) ```bash gem install stackprof-webnav stackprof-webnav tmp/stackprof/ ``` ### Interactive Mode ```bash stackprof tmp/stackprof/very_large_qr_code_v20__cpu.dump ``` --- ## Files Generated All profiling data saved in: `tmp/stackprof/` - `*_cpu.dump` - Raw stackprof data (for further analysis) - `*_cpu_report.txt` - Text summaries with call trees - `*_cpu.callgrind` - Callgrind format (currently broken in script, needs fix) --- ## Conclusion StackProf profiling identified clear optimization targets, which have now been **successfully addressed**: 1. ✅ **`demerit_points_1_same_color`** was the primary bottleneck (30% CPU) → **Optimized to 18.5%** 2. ✅ **GC overhead** dominated small QR codes → **Addressed via ARCH_BITS=32 (70-76% memory reduction)** 3. ✅ **Other demerit functions** were secondary targets (6-8% combined) → **All optimized** 4. ✅ **Large QR code focus** (v10+) → **Achieved 80-90% speed improvements** **Final Results**: - **Performance**: 80-92% faster across all QR code sizes - **Memory**: 70-76% reduction with ARCH_BITS=32 - **Correctness**: All 108 test assertions pass - **Breaking Changes**: Zero - **Code Quality**: Improved readability and maintainability The profiling data provided concrete evidence to guide optimization work, and the results exceeded initial expectations. The optimizations are production-ready and provide massive performance gains with no downsides. **Benchmark Results** (Before → After): - Small QR (v1): 152.7 i/s → 292.9 i/s (+92%) - Medium QR (v5): 46.2 i/s → 85.3 i/s (+85%) - Large QR (v24): 4.77 i/s → 8.68 i/s (+82%) - Version 20: 6.50 i/s → 11.8 i/s (+82%) - Version 40: 1.94 i/s → 3.51 i/s (+81%) See `test/benchmarks/benchmark_performance_optimized.txt` for complete benchmark results. whomwah-rqrcode_core-c13e78d/test/benchmarks/benchmark_memory_32bit.txt000066400000000000000000000057551512755103000264460ustar00rootroot00000000000000================================================================================ RQRCode Core - Memory Profiling Benchmark ================================================================================ Ruby: 3.3.4 (arm64-darwin24) ARCH_BITS: 32 ================================================================================ --- Single small QR (v1) --- Total allocated: 0.10 MB Total retained: 0.00 MB Objects allocated: 1188 Objects retained: 0 Top 3 allocations by class: Array: 0.06 MB Range: 0.03 MB Hash: 0.01 MB --- Single medium QR (v5) --- Total allocated: 0.29 MB Total retained: 0.00 MB Objects allocated: 3602 Objects retained: 0 Top 3 allocations by class: Array: 0.19 MB Range: 0.09 MB RQRCodeCore::QRPolynomial: 0.01 MB --- Single large QR (v24) --- Total allocated: 2.92 MB Total retained: 0.00 MB Objects allocated: 32524 Objects retained: 0 Top 3 allocations by class: Array: 1.91 MB Range: 0.86 MB RQRCodeCore::QRPolynomial: 0.10 MB --- Batch: 100 small QR codes --- Total allocated: 9.10 MB Total retained: 0.00 MB Objects allocated: 117500 Objects retained: 0 Top 3 allocations by class: Array: 5.88 MB Range: 2.93 MB RQRCodeCore::QRPolynomial: 0.17 MB --- Batch: 10 large QR codes --- Total allocated: 29.19 MB Total retained: 0.00 MB Objects allocated: 325240 Objects retained: 0 Top 3 allocations by class: Array: 19.14 MB Range: 8.64 MB RQRCodeCore::QRPolynomial: 0.95 MB --- Create only --- Total allocated: 9.10 MB Total retained: 0.00 MB Objects allocated: 117500 Objects retained: 0 Top 3 allocations by class: Array: 5.88 MB Range: 2.93 MB RQRCodeCore::QRPolynomial: 0.17 MB --- Create + render --- Total allocated: 11.46 MB Total retained: 0.00 MB Objects allocated: 164100 Objects retained: 0 Top 3 allocations by class: Array: 5.92 MB Range: 2.93 MB String: 2.34 MB --- Numeric mode --- Total allocated: 9.09 MB Total retained: 0.00 MB Objects allocated: 117701 Objects retained: 0 Top 3 allocations by class: Array: 5.85 MB Range: 2.92 MB RQRCodeCore::QRPolynomial: 0.17 MB --- Alphanumeric mode --- Total allocated: 13.97 MB Total retained: 0.00 MB Objects allocated: 172700 Objects retained: 0 Top 3 allocations by class: Array: 9.00 MB Range: 4.48 MB RQRCodeCore::QRPolynomial: 0.28 MB --- Byte mode --- Total allocated: 13.87 MB Total retained: 0.00 MB Objects allocated: 170500 Objects retained: 0 Top 3 allocations by class: Array: 8.98 MB Range: 4.49 MB RQRCodeCore::QRPolynomial: 0.28 MB --- Multi-segment encoding --- Total allocated: 14.08 MB Total retained: 0.00 MB Objects allocated: 174200 Objects retained: 0 Top 3 allocations by class: Array: 9.04 MB Range: 4.49 MB RQRCodeCore::QRPolynomial: 0.28 MB ================================================================================ Memory profiling complete! Note: To test ARCH_BITS=32 impact, run: RQRCODE_CORE_ARCH_BITS=32 ruby test/benchmark_memory.rb ================================================================================ whomwah-rqrcode_core-c13e78d/test/benchmarks/benchmark_memory_64bit.txt000066400000000000000000000055401512755103000264430ustar00rootroot00000000000000================================================================================ RQRCode Core - Memory Profiling Benchmark ================================================================================ Ruby: 3.3.4 (arm64-darwin24) ARCH_BITS: 64 ================================================================================ --- Single small QR (v1) --- Total allocated: 0.38 MB Total retained: 0.00 MB Objects allocated: 8740 Objects retained: 0 Top 3 allocations by class: Integer: 0.29 MB Array: 0.06 MB Range: 0.03 MB --- Single medium QR (v5) --- Total allocated: 0.97 MB Total retained: 0.00 MB Objects allocated: 21264 Objects retained: 0 Top 3 allocations by class: Integer: 0.67 MB Array: 0.19 MB Range: 0.09 MB --- Single large QR (v24) --- Total allocated: 8.53 MB Total retained: 0.00 MB Objects allocated: 179659 Objects retained: 0 Top 3 allocations by class: Integer: 5.61 MB Array: 1.91 MB Range: 0.86 MB --- Batch: 100 small QR codes --- Total allocated: 37.91 MB Total retained: 0.00 MB Objects allocated: 872700 Objects retained: 0 Top 3 allocations by class: Integer: 28.81 MB Array: 5.88 MB Range: 2.93 MB --- Batch: 10 large QR codes --- Total allocated: 85.32 MB Total retained: 0.00 MB Objects allocated: 1796590 Objects retained: 0 Top 3 allocations by class: Integer: 56.13 MB Array: 19.14 MB Range: 8.64 MB --- Create only --- Total allocated: 37.91 MB Total retained: 0.00 MB Objects allocated: 872700 Objects retained: 0 Top 3 allocations by class: Integer: 28.81 MB Array: 5.88 MB Range: 2.93 MB --- Create + render --- Total allocated: 40.27 MB Total retained: 0.00 MB Objects allocated: 919300 Objects retained: 0 Top 3 allocations by class: Integer: 28.81 MB Array: 5.92 MB Range: 2.93 MB --- Numeric mode --- Total allocated: 38.36 MB Total retained: 0.00 MB Objects allocated: 884801 Objects retained: 0 Top 3 allocations by class: Integer: 29.26 MB Array: 5.85 MB Range: 2.92 MB --- Alphanumeric mode --- Total allocated: 48.52 MB Total retained: 0.00 MB Objects allocated: 1078500 Objects retained: 0 Top 3 allocations by class: Integer: 34.55 MB Array: 9.00 MB Range: 4.48 MB --- Byte mode --- Total allocated: 48.63 MB Total retained: 0.00 MB Objects allocated: 1081700 Objects retained: 0 Top 3 allocations by class: Integer: 34.76 MB Array: 8.98 MB Range: 4.49 MB --- Multi-segment encoding --- Total allocated: 48.69 MB Total retained: 0.00 MB Objects allocated: 1081500 Objects retained: 0 Top 3 allocations by class: Integer: 34.61 MB Array: 9.04 MB Range: 4.49 MB ================================================================================ Memory profiling complete! Note: To test ARCH_BITS=32 impact, run: RQRCODE_CORE_ARCH_BITS=32 ruby test/benchmark_memory.rb ================================================================================ whomwah-rqrcode_core-c13e78d/test/benchmarks/benchmark_performance_32bit.txt000066400000000000000000000126231512755103000274270ustar00rootroot00000000000000/Users/duncan/.local/share/mise/installs/ruby/3.3.4/bin/ruby test/benchmark_performance.rb ================================================================================ RQRCode Core - Detailed Performance Benchmark (benchmark-ips) ================================================================================ Ruby: 3.3.4 (arm64-darwin24) ARCH_BITS: 32 ================================================================================ --- By Data Size --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Small (v1) 15.000 i/100ms Medium URL (v5) 4.000 i/100ms Large (v24) 1.000 i/100ms Calculating ------------------------------------- Small (v1) 157.084 (± 1.3%) i/s (6.37 ms/i) - 315.000 in 2.005624s Medium URL (v5) 47.562 (± 0.0%) i/s (21.03 ms/i) - 96.000 in 2.018539s Large (v24) 4.871 (± 0.0%) i/s (205.28 ms/i) - 10.000 in 2.052893s Comparison: Small (v1): 157.1 i/s Medium URL (v5): 47.6 i/s - 3.30x slower Large (v24): 4.9 i/s - 32.25x slower --- By Encoding Mode --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Numeric 15.000 i/100ms Alphanumeric 10.000 i/100ms Byte 10.000 i/100ms Calculating ------------------------------------- Numeric 156.505 (± 1.3%) i/s (6.39 ms/i) - 315.000 in 2.013128s Alphanumeric 107.068 (± 0.9%) i/s (9.34 ms/i) - 220.000 in 2.055093s Byte 108.765 (± 0.0%) i/s (9.19 ms/i) - 220.000 in 2.022717s Comparison: Numeric: 156.5 i/s Byte: 108.8 i/s - 1.44x slower Alphanumeric: 107.1 i/s - 1.46x slower --- By QR Version (same data) --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Version 1 15.000 i/100ms Version 5 4.000 i/100ms Version 10 1.000 i/100ms Version 20 1.000 i/100ms Version 40 1.000 i/100ms Calculating ------------------------------------- Version 1 157.271 (± 1.3%) i/s (6.36 ms/i) - 315.000 in 2.003261s Version 5 47.417 (± 2.1%) i/s (21.09 ms/i) - 96.000 in 2.024843s Version 10 19.648 (± 0.0%) i/s (50.90 ms/i) - 40.000 in 2.035866s Version 20 6.649 (± 0.0%) i/s (150.40 ms/i) - 14.000 in 2.105758s Version 40 1.978 (± 0.0%) i/s (505.44 ms/i) - 4.000 in 2.021854s Comparison: Version 1: 157.3 i/s Version 5: 47.4 i/s - 3.32x slower Version 10: 19.6 i/s - 8.00x slower Version 20: 6.6 i/s - 23.65x slower Version 40: 2.0 i/s - 79.49x slower --- By Error Correction Level --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Level :l (7%) 16.000 i/100ms Level :m (15%) 16.000 i/100ms Level :q (25%) 15.000 i/100ms Level :h (30%) 15.000 i/100ms Calculating ------------------------------------- Level :l (7%) 159.714 (± 0.6%) i/s (6.26 ms/i) - 320.000 in 2.003752s Level :m (15%) 158.220 (± 1.3%) i/s (6.32 ms/i) - 320.000 in 2.022874s Level :q (25%) 158.007 (± 1.3%) i/s (6.33 ms/i) - 330.000 in 2.088762s Level :h (30%) 158.423 (± 0.6%) i/s (6.31 ms/i) - 330.000 in 2.083124s Comparison: Level :l (7%): 159.7 i/s Level :h (30%): 158.4 i/s - same-ish: difference falls within error Level :m (15%): 158.2 i/s - same-ish: difference falls within error Level :q (25%): 158.0 i/s - same-ish: difference falls within error --- Creation vs Rendering --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Create only 15.000 i/100ms Create + render 15.000 i/100ms Render only 15.000 i/100ms Calculating ------------------------------------- Create only 158.820 (± 1.3%) i/s (6.30 ms/i) - 330.000 in 2.078049s Create + render 156.129 (± 1.9%) i/s (6.40 ms/i) - 315.000 in 2.018189s Render only 156.248 (± 1.3%) i/s (6.40 ms/i) - 315.000 in 2.016389s Comparison: Create only: 158.8 i/s Render only: 156.2 i/s - same-ish: difference falls within error Create + render: 156.1 i/s - same-ish: difference falls within error --- Multi-segment Encoding --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Single segment 7.000 i/100ms Multi-segment 10.000 i/100ms Calculating ------------------------------------- Single segment 78.534 (± 1.3%) i/s (12.73 ms/i) - 161.000 in 2.050505s Multi-segment 106.325 (± 0.9%) i/s (9.41 ms/i) - 220.000 in 2.069446s Comparison: Multi-segment: 106.3 i/s Single segment: 78.5 i/s - 1.35x slower ================================================================================ Performance benchmark complete! ================================================================================ whomwah-rqrcode_core-c13e78d/test/benchmarks/benchmark_performance_64bit.txt000066400000000000000000000125051512755103000274330ustar00rootroot00000000000000/Users/duncan/.local/share/mise/installs/ruby/3.3.4/bin/ruby test/benchmark_performance.rb ================================================================================ RQRCode Core - Detailed Performance Benchmark (benchmark-ips) ================================================================================ Ruby: 3.3.4 (arm64-darwin24) ARCH_BITS: 64 ================================================================================ --- By Data Size --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Small (v1) 15.000 i/100ms Medium URL (v5) 4.000 i/100ms Large (v24) 1.000 i/100ms Calculating ------------------------------------- Small (v1) 152.703 (± 0.7%) i/s (6.55 ms/i) - 315.000 in 2.062952s Medium URL (v5) 46.225 (± 0.0%) i/s (21.63 ms/i) - 96.000 in 2.076890s Large (v24) 4.770 (± 0.0%) i/s (209.64 ms/i) - 10.000 in 2.096453s Comparison: Small (v1): 152.7 i/s Medium URL (v5): 46.2 i/s - 3.30x slower Large (v24): 4.8 i/s - 32.01x slower --- By Encoding Mode --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Numeric 15.000 i/100ms Alphanumeric 10.000 i/100ms Byte 10.000 i/100ms Calculating ------------------------------------- Numeric 151.088 (± 1.3%) i/s (6.62 ms/i) - 315.000 in 2.085212s Alphanumeric 102.899 (± 1.9%) i/s (9.72 ms/i) - 210.000 in 2.041462s Byte 105.187 (± 1.0%) i/s (9.51 ms/i) - 220.000 in 2.091581s Comparison: Numeric: 151.1 i/s Byte: 105.2 i/s - 1.44x slower Alphanumeric: 102.9 i/s - 1.47x slower --- By QR Version (same data) --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Version 1 15.000 i/100ms Version 5 4.000 i/100ms Version 10 1.000 i/100ms Version 20 1.000 i/100ms Version 40 1.000 i/100ms Calculating ------------------------------------- Version 1 153.231 (± 0.7%) i/s (6.53 ms/i) - 315.000 in 2.055745s Version 5 46.388 (± 0.0%) i/s (21.56 ms/i) - 96.000 in 2.069570s Version 10 19.006 (± 0.0%) i/s (52.62 ms/i) - 39.000 in 2.052087s Version 20 6.499 (± 0.0%) i/s (153.86 ms/i) - 13.000 in 2.000250s Version 40 1.936 (± 0.0%) i/s (516.52 ms/i) - 4.000 in 2.066094s Comparison: Version 1: 153.2 i/s Version 5: 46.4 i/s - 3.30x slower Version 10: 19.0 i/s - 8.06x slower Version 20: 6.5 i/s - 23.58x slower Version 40: 1.9 i/s - 79.15x slower --- By Error Correction Level --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Level :l (7%) 15.000 i/100ms Level :m (15%) 15.000 i/100ms Level :q (25%) 15.000 i/100ms Level :h (30%) 15.000 i/100ms Calculating ------------------------------------- Level :l (7%) 154.511 (± 0.6%) i/s (6.47 ms/i) - 315.000 in 2.038729s Level :m (15%) 156.390 (± 0.0%) i/s (6.39 ms/i) - 315.000 in 2.014221s Level :q (25%) 151.581 (± 0.0%) i/s (6.60 ms/i) - 315.000 in 2.078119s Level :h (30%) 152.958 (± 0.7%) i/s (6.54 ms/i) - 315.000 in 2.059453s Comparison: Level :m (15%): 156.4 i/s Level :l (7%): 154.5 i/s - 1.01x slower Level :h (30%): 153.0 i/s - 1.02x slower Level :q (25%): 151.6 i/s - 1.03x slower --- Creation vs Rendering --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Create only 15.000 i/100ms Create + render 15.000 i/100ms Render only 15.000 i/100ms Calculating ------------------------------------- Create only 153.164 (± 0.7%) i/s (6.53 ms/i) - 315.000 in 2.056646s Create + render 152.039 (± 0.7%) i/s (6.58 ms/i) - 315.000 in 2.071869s Render only 152.248 (± 0.7%) i/s (6.57 ms/i) - 315.000 in 2.069014s Comparison: Create only: 153.2 i/s Render only: 152.2 i/s - same-ish: difference falls within error Create + render: 152.0 i/s - same-ish: difference falls within error --- Multi-segment Encoding --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Single segment 7.000 i/100ms Multi-segment 10.000 i/100ms Calculating ------------------------------------- Single segment 76.907 (± 1.3%) i/s (13.00 ms/i) - 154.000 in 2.002709s Multi-segment 103.345 (± 1.9%) i/s (9.68 ms/i) - 210.000 in 2.032742s Comparison: Multi-segment: 103.3 i/s Single segment: 76.9 i/s - 1.34x slower ================================================================================ Performance benchmark complete! ================================================================================ whomwah-rqrcode_core-c13e78d/test/benchmarks/benchmark_performance_optimized.txt000066400000000000000000000124211512755103000305040ustar00rootroot00000000000000/Users/duncan/.local/share/mise/installs/ruby/3.3.4/bin/ruby test/benchmark_performance.rb ================================================================================ RQRCode Core - Detailed Performance Benchmark (benchmark-ips) ================================================================================ Ruby: 3.3.4 (arm64-darwin24) ARCH_BITS: 64 ================================================================================ --- By Data Size --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Small (v1) 29.000 i/100ms Medium URL (v5) 8.000 i/100ms Large (v24) 1.000 i/100ms Calculating ------------------------------------- Small (v1) 292.903 (± 0.3%) i/s (3.41 ms/i) - 609.000 in 2.079211s Medium URL (v5) 85.342 (± 1.2%) i/s (11.72 ms/i) - 176.000 in 2.062425s Large (v24) 8.684 (± 0.0%) i/s (115.16 ms/i) - 18.000 in 2.072975s Comparison: Small (v1): 292.9 i/s Medium URL (v5): 85.3 i/s - 3.43x slower Large (v24): 8.7 i/s - 33.73x slower --- By Encoding Mode --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Numeric 29.000 i/100ms Alphanumeric 19.000 i/100ms Byte 19.000 i/100ms Calculating ------------------------------------- Numeric 291.792 (± 0.7%) i/s (3.43 ms/i) - 609.000 in 2.087222s Alphanumeric 196.603 (± 0.5%) i/s (5.09 ms/i) - 399.000 in 2.029540s Byte 197.528 (± 0.5%) i/s (5.06 ms/i) - 399.000 in 2.020013s Comparison: Numeric: 291.8 i/s Byte: 197.5 i/s - 1.48x slower Alphanumeric: 196.6 i/s - 1.48x slower --- By QR Version (same data) --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Version 1 29.000 i/100ms Version 5 8.000 i/100ms Version 10 3.000 i/100ms Version 20 1.000 i/100ms Version 40 1.000 i/100ms Calculating ------------------------------------- Version 1 292.591 (± 0.7%) i/s (3.42 ms/i) - 609.000 in 2.081510s Version 5 85.645 (± 1.2%) i/s (11.68 ms/i) - 176.000 in 2.055076s Version 10 34.641 (± 0.0%) i/s (28.87 ms/i) - 72.000 in 2.078604s Version 20 11.825 (± 0.0%) i/s (84.57 ms/i) - 24.000 in 2.029676s Version 40 3.507 (± 0.0%) i/s (285.17 ms/i) - 8.000 in 2.281342s Comparison: Version 1: 292.6 i/s Version 5: 85.6 i/s - 3.42x slower Version 10: 34.6 i/s - 8.45x slower Version 20: 11.8 i/s - 24.74x slower Version 40: 3.5 i/s - 83.44x slower --- By Error Correction Level --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Level :l (7%) 26.000 i/100ms Level :m (15%) 30.000 i/100ms Level :q (25%) 27.000 i/100ms Level :h (30%) 29.000 i/100ms Calculating ------------------------------------- Level :l (7%) 297.489 (± 0.3%) i/s (3.36 ms/i) - 598.000 in 2.010190s Level :m (15%) 307.389 (± 0.7%) i/s (3.25 ms/i) - 630.000 in 2.049575s Level :q (25%) 288.344 (± 0.3%) i/s (3.47 ms/i) - 594.000 in 2.060071s Level :h (30%) 295.327 (± 0.3%) i/s (3.39 ms/i) - 609.000 in 2.062147s Comparison: Level :m (15%): 307.4 i/s Level :l (7%): 297.5 i/s - 1.03x slower Level :h (30%): 295.3 i/s - 1.04x slower Level :q (25%): 288.3 i/s - 1.07x slower --- Creation vs Rendering --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Create only 29.000 i/100ms Create + render 29.000 i/100ms Render only 28.000 i/100ms Calculating ------------------------------------- Create only 294.409 (± 0.7%) i/s (3.40 ms/i) - 609.000 in 2.068685s Create + render 289.981 (± 0.7%) i/s (3.45 ms/i) - 580.000 in 2.000234s Render only 291.282 (± 0.3%) i/s (3.43 ms/i) - 588.000 in 2.018688s Comparison: Create only: 294.4 i/s Render only: 291.3 i/s - 1.01x slower Create + render: 290.0 i/s - 1.02x slower --- Multi-segment Encoding --- ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24] Warming up -------------------------------------- Single segment 14.000 i/100ms Multi-segment 19.000 i/100ms Calculating ------------------------------------- Single segment 145.551 (± 0.7%) i/s (6.87 ms/i) - 294.000 in 2.019949s Multi-segment 196.185 (± 5.1%) i/s (5.10 ms/i) - 399.000 in 2.041346s Comparison: Multi-segment: 196.2 i/s Single segment: 145.6 i/s - 1.35x slower ================================================================================ Performance benchmark complete! ================================================================================ whomwah-rqrcode_core-c13e78d/test/profile_stackprof.rb000066400000000000000000000073341512755103000232770ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "rqrcode_core" require "stackprof" require "fileutils" puts "=" * 80 puts "RQRCode Core - StackProf Performance Profiling" puts "=" * 80 puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})" puts "ARCH_BITS: #{RQRCodeCore::QRUtil::ARCH_BITS}" puts "=" * 80 puts # Create output directory for profiles output_dir = File.expand_path("../tmp/stackprof", __dir__) FileUtils.mkdir_p(output_dir) # Helper to run profile and save results def profile_scenario(name, output_dir, mode: :cpu, &) puts "\nProfiling: #{name}..." # Run the profiling profile = StackProf.run(mode: mode, raw: true, &) # Save raw profile data filename = name.downcase.gsub(/[^a-z0-9]+/, "_") raw_file = File.join(output_dir, "#{filename}_#{mode}.dump") File.write(raw_file, Marshal.dump(profile)) puts " Saved raw profile: #{raw_file}" # Generate text report report_file = File.join(output_dir, "#{filename}_#{mode}_report.txt") File.open(report_file, "w") do |f| f.puts "=" * 80 f.puts "StackProf Report: #{name} (#{mode} mode)" f.puts "=" * 80 f.puts f.puts StackProf::Report.new(profile).print_text end puts " Saved text report: #{report_file}" # Generate callgrind format (optional, useful for visualization tools) callgrind_file = File.join(output_dir, "#{filename}_#{mode}.callgrind") File.open(callgrind_file, "w") do |f| f.puts StackProf::Report.new(profile).print_callgrind end puts " Saved callgrind format: #{callgrind_file}" puts " Done!" profile end # Configuration PROFILE_ITERATIONS = 100 puts "\nRunning profiling scenarios (#{PROFILE_ITERATIONS} iterations each)...\n" # Profile 1: Small QR Code (v1) - Baseline profile_scenario("Small QR Code (v1)", output_dir) do PROFILE_ITERATIONS.times do RQRCodeCore::QRCode.new("hello") end end # Profile 2: Medium QR Code (v5) - URL profile_scenario("Medium QR Code (v5)", output_dir) do PROFILE_ITERATIONS.times do RQRCodeCore::QRCode.new("https://github.com/whomwah/rqrcode_core") end end # Profile 3: Large QR Code (v10) profile_scenario("Large QR Code (v10)", output_dir) do PROFILE_ITERATIONS.times do RQRCodeCore::QRCode.new("a" * 150) end end # Profile 4: Very Large QR Code (v20) - Where performance degrades significantly profile_scenario("Very Large QR Code (v20)", output_dir) do 20.times do # Fewer iterations for large codes RQRCodeCore::QRCode.new("a" * 500) end end # Profile 5: Version 40 - Maximum size (fewer iterations) profile_scenario("Maximum QR Code (v40)", output_dir) do 5.times do # Very few iterations for maximum size RQRCodeCore::QRCode.new("a" * 1500) end end # Profile 6: Focus on mask pattern calculation (known hot spot) profile_scenario("Mask Pattern Calculation", output_dir) do 50.times do RQRCodeCore::QRCode.new("a" * 300) # v15 - large enough to be expensive end end # Profile 7: Multi-segment encoding profile_scenario("Multi-segment Encoding", output_dir) do PROFILE_ITERATIONS.times do RQRCodeCore::QRCode.new([ {data: "hello world", mode: :byte_8bit}, {data: "HELLO123", mode: :alphanumeric}, {data: "123456789", mode: :number} ]) end end puts "\n" + "=" * 80 puts "Profiling complete!" puts "=" * 80 puts puts "Profile data saved to: #{output_dir}" puts puts "To view flamegraphs (requires stackprof-webnav gem):" puts " gem install stackprof-webnav" puts " stackprof-webnav #{output_dir}/*.dump" puts puts "To view specific profile interactively:" puts " stackprof #{output_dir}/small_qr_code_v1_cpu.dump" puts puts "To generate flamegraph SVG (requires flamegraph.pl):" puts " stackprof --flamegraph #{output_dir}/small_qr_code_v1_cpu.dump > flamegraph.svg" puts puts "=" * 80 whomwah-rqrcode_core-c13e78d/test/rqrcode_core/000077500000000000000000000000001512755103000216765ustar00rootroot00000000000000whomwah-rqrcode_core-c13e78d/test/rqrcode_core/boundary_test.rb000066400000000000000000000246571512755103000251230ustar00rootroot00000000000000require "test_helper" # Tests for boundary conditions, edge cases, and maximum capacity scenarios class BoundaryTest < Minitest::Test def test_empty_string # Empty string should work at all error correction levels %i[l m q h].each do |level| qr = RQRCodeCore::QRCode.new("", level: level) assert qr.version >= 1, "Empty string should produce valid QR code at level #{level}" assert qr.modules.size > 0, "Empty string should have modules at level #{level}" end end def test_single_character # Single characters in each mode numeric_qr = RQRCodeCore::QRCode.new("0") alpha_qr = RQRCodeCore::QRCode.new("A") byte_qr = RQRCodeCore::QRCode.new("a") assert_equal 1, numeric_qr.version, "Single digit should fit in version 1" assert_equal 1, alpha_qr.version, "Single alpha should fit in version 1" assert_equal 1, byte_qr.version, "Single byte should fit in version 1" end def test_all_numeric_digits qr = RQRCodeCore::QRCode.new("0123456789") assert_equal :mode_number, qr.mode, "All digits should use numeric mode" assert qr.modules.size > 0, "Should produce valid QR code" end def test_all_alphanumeric_characters # Valid alphanumeric characters: 0-9, A-Z, space, and $%*+-./: alpha_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" qr = RQRCodeCore::QRCode.new(alpha_chars) assert_equal :mode_alpha_numk, qr.mode, "Valid alphanumeric chars should use alphanumeric mode" end def test_boundary_between_numeric_and_alphanumeric # Just digits: should be numeric assert_equal :mode_number, RQRCodeCore::QRCode.new("123456").mode # Digits with uppercase letter: should be alphanumeric assert_equal :mode_alpha_numk, RQRCodeCore::QRCode.new("123456A").mode # Digits with space (valid alphanumeric): should be alphanumeric assert_equal :mode_alpha_numk, RQRCodeCore::QRCode.new("123 456").mode end def test_boundary_between_alphanumeric_and_byte # Uppercase only: should be alphanumeric assert_equal :mode_alpha_numk, RQRCodeCore::QRCode.new("HELLO").mode # With lowercase: should be byte assert_equal :mode_8bit_byte, RQRCodeCore::QRCode.new("Hello").mode # With valid alphanumeric punctuation: should be alphanumeric assert_equal :mode_alpha_numk, RQRCodeCore::QRCode.new("HELLO-WORLD").mode # With invalid alphanumeric punctuation: should be byte assert_equal :mode_8bit_byte, RQRCodeCore::QRCode.new("HELLO_WORLD").mode end def test_maximum_version_40 # Version 40 is the maximum QR code version qr = RQRCodeCore::QRCode.new("test", size: 40) assert_equal 40, qr.version, "Should support version 40" assert_equal 177, qr.module_count, "Version 40 should be 177x177 (40*4+17)" end def test_version_boundary_transitions # Test data lengths that sit near version boundaries # These lengths are chosen to be near capacity limits for version 1 # Version 1 numeric capacity at level H: 17 digits assert_equal 1, RQRCodeCore::QRCode.new("1" * 17, level: :h).version # One more should bump to version 2 assert_equal 2, RQRCodeCore::QRCode.new("1" * 18, level: :h).version # Version 1 alphanumeric capacity at level H: 10 characters assert_equal 1, RQRCodeCore::QRCode.new("A" * 10, level: :h).version # One more should bump to version 2 assert_equal 2, RQRCodeCore::QRCode.new("A" * 11, level: :h).version # Version 1 byte capacity at level H: 7 bytes assert_equal 1, RQRCodeCore::QRCode.new("a" * 7, level: :h).version # One more should bump to version 2 assert_equal 2, RQRCodeCore::QRCode.new("a" * 8, level: :h).version end def test_exact_capacity_for_version_1_all_levels # Test exact maximum capacity for version 1 at each error correction level # Level L: 25 alphanumeric characters assert_equal 1, RQRCodeCore::QRCode.new("A" * 25, level: :l).version # Level M: 20 alphanumeric characters assert_equal 1, RQRCodeCore::QRCode.new("A" * 20, level: :m).version # Level Q: 16 alphanumeric characters assert_equal 1, RQRCodeCore::QRCode.new("A" * 16, level: :q).version # Level H: 10 alphanumeric characters assert_equal 1, RQRCodeCore::QRCode.new("A" * 10, level: :h).version end def test_long_numeric_string # Test very long numeric strings (tests numeric mode efficiency) long_numeric = "1" * 500 qr = RQRCodeCore::QRCode.new(long_numeric, level: :l) assert_equal :mode_number, qr.mode, "Long numeric string should use numeric mode" assert qr.version < 15, "500 digits should fit in a reasonably small version" end def test_long_alphanumeric_string # Test very long alphanumeric strings long_alpha = "A" * 300 qr = RQRCodeCore::QRCode.new(long_alpha, level: :l) assert_equal :mode_alpha_numk, qr.mode, "Long alphanumeric string should use alphanumeric mode" assert qr.version < 20, "300 alphanumeric chars should fit in a reasonably small version" end def test_long_byte_string # Test very long byte strings long_byte = "a" * 200 qr = RQRCodeCore::QRCode.new(long_byte, level: :l) assert_equal :mode_8bit_byte, qr.mode, "Long byte string should use byte mode" assert qr.version < 15, "200 bytes should fit in a reasonably small version" end def test_very_long_string_automatically_selects_version # Don't specify size, let it auto-select very_long = "x" * 1000 qr = RQRCodeCore::QRCode.new(very_long, level: :l) assert qr.version > 1, "Very long string should require version > 1" assert qr.version <= 40, "Should not exceed maximum version 40" end def test_minimum_version_parameter # When size is not specified, should use minimum version needed short_data = "hi" qr = RQRCodeCore::QRCode.new(short_data) assert_equal 1, qr.version, "Short data should default to version 1" end def test_forced_larger_version # Should be able to force a larger version than needed short_data = "hi" qr = RQRCodeCore::QRCode.new(short_data, size: 10) assert_equal 10, qr.version, "Should respect forced larger version" assert_equal 57, qr.module_count, "Version 10 should be 57x57" end def test_max_size_parameter # max_size should limit the version selection long_data = "x" * 1000 # Without limit, should work qr_unlimited = RQRCodeCore::QRCode.new(long_data, level: :l) assert qr_unlimited.version > 10, "Long data should need large version" # With reasonable max_size, should work qr_limited = RQRCodeCore::QRCode.new(long_data, level: :l, max_size: 30) assert qr_limited.version <= 30, "Should respect max_size limit" end def test_data_too_long_for_max_size_raises_error # Data that's too long for the max_size should raise error very_long_data = "x" * 2000 assert_raises(RQRCodeCore::QRCodeRunTimeError) do RQRCodeCore::QRCode.new(very_long_data, level: :h, max_size: 10) end end def test_zero_digit qr = RQRCodeCore::QRCode.new("0") assert_equal :mode_number, qr.mode, "Single zero should use numeric mode" assert qr.modules.size > 0, "Zero should produce valid QR code" end def test_large_numbers # Test very large numeric values large_num = "9" * 100 qr = RQRCodeCore::QRCode.new(large_num) assert_equal :mode_number, qr.mode, "Large numeric string should use numeric mode" assert qr.modules.size > 0, "Should produce valid QR code" end def test_numeric_with_leading_zeros qr = RQRCodeCore::QRCode.new("00012345") assert_equal :mode_number, qr.mode, "Numbers with leading zeros should use numeric mode" end def test_all_zeros qr = RQRCodeCore::QRCode.new("00000000") assert_equal :mode_number, qr.mode, "All zeros should use numeric mode" assert qr.modules.size > 0, "Should produce valid QR code" end def test_repeated_characters # Test strings with repeated characters (good for compression testing) repeated = "A" * 100 qr = RQRCodeCore::QRCode.new(repeated) assert qr.modules.size > 0, "Repeated characters should produce valid QR code" assert qr.version <= 40, "Should not exceed maximum version" end def test_alternating_characters # Test strings that don't compress well alternating = ("AB" * 50) qr = RQRCodeCore::QRCode.new(alternating) assert qr.modules.size > 0, "Alternating characters should produce valid QR code" end def test_checked_with_boundary_coordinates qr = RQRCodeCore::QRCode.new("test", size: 5) max = qr.module_count - 1 # Should work with valid boundary coordinates qr.checked?(0, 0) # Top-left corner should be valid qr.checked?(0, max) # Top-right corner should be valid qr.checked?(max, 0) # Bottom-left corner should be valid qr.checked?(max, max) # Bottom-right corner should be valid # Should raise error with out-of-bounds coordinates assert_raises(RQRCodeCore::QRCodeRunTimeError) { qr.checked?(-1, 0) } assert_raises(RQRCodeCore::QRCodeRunTimeError) { qr.checked?(0, -1) } assert_raises(RQRCodeCore::QRCodeRunTimeError) { qr.checked?(qr.module_count, 0) } assert_raises(RQRCodeCore::QRCodeRunTimeError) { qr.checked?(0, qr.module_count) } end def test_url_maximum_length # URLs are common QR code content, test reasonable max lengths base_url = "https://example.com/path" # Short URL should work qr_short = RQRCodeCore::QRCode.new(base_url) assert qr_short.modules.size > 0 # Long URL with query params should work long_url = base_url + "?" + ("param=value&" * 50) qr_long = RQRCodeCore::QRCode.new(long_url, level: :l) assert qr_long.modules.size > 0 assert qr_long.version <= 40 end def test_mode_specification_overrides_auto_detection # Numeric string forced to alphanumeric mode qr = RQRCodeCore::QRCode.new("12345", mode: :alphanumeric) assert_equal :mode_alpha_numk, qr.mode, "Should respect forced mode" # Alphanumeric string forced to byte mode qr2 = RQRCodeCore::QRCode.new("HELLO", mode: :byte_8bit) assert_equal :mode_8bit_byte, qr2.mode, "Should respect forced byte mode" end def test_invalid_mode_for_data_raises_error # Lowercase cannot be encoded in alphanumeric mode assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new("hello", mode: :alphanumeric) end end def test_numeric_mode_with_non_digits_raises_error # Non-digits cannot be encoded in numeric mode assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new("123abc", mode: :number) end end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/character_encoding_test.rb000066400000000000000000000061551512755103000270730ustar00rootroot00000000000000require "test_helper" # Tests for character encoding handling in QR codes class CharacterEncodingTest < Minitest::Test def test_printable_ascii_uses_byte_mode ascii_string = (32..126).map(&:chr).join qr = RQRCodeCore::QRCode.new(ascii_string) assert_equal :mode_8bit_byte, qr.mode end def test_control_characters # Newlines, tabs, null bytes, and other control chars [ "line1\nline2", # LF "line1\r\nline2", # CRLF "line1\rline2", # CR "field1\tfield2", # Tab "before\x00after", # Null byte "test\x07data", # Bell "test\x1Bdata" # Escape ].each do |input| qr = RQRCodeCore::QRCode.new(input) refute_nil qr.modules end end def test_utf8_multibyte_characters # Representative samples from different Unicode blocks samples = [ "Café résumé naïve", # Latin extended "Привет мир", # Cyrillic "你好世界", # CJK "こんにちは", # Japanese Hiragana "مرحبا بالعالم", # Arabic (RTL) "שלום עולם", # Hebrew (RTL) "∑∫∂√∞≈≠±×÷", # Mathematical symbols "$ € £ ¥ ₹ ₽", # Currency symbols "Hello 👋 World 🌍", # Emoji "Hello مرحبا 你好" # Mixed LTR/RTL ] samples.each do |text| qr = RQRCodeCore::QRCode.new(text) refute_nil qr.modules end end def test_complex_emoji_sequences # Compound emoji with modifiers and zero-width joiners ["👋🏻👋🏾", "👨‍👩‍👧‍👦", "🏴󠁧󠁢󠁥󠁮󠁧󠁿"].each do |emoji| qr = RQRCodeCore::QRCode.new(emoji) refute_nil qr.modules end rescue => e skip "Complex compound emoji not fully supported: #{e.message}" end def test_zero_width_and_combining_characters # Zero-width joiners, non-joiners, and combining marks [ "test\u200Djoiner", "test\u200Cnon-joiner", "test\u200Bspace", "e\u0301" # e + combining acute accent ].each do |text| qr = RQRCodeCore::QRCode.new(text) refute_nil qr.modules end end def test_long_utf8_string_fits_within_max_version long_utf8 = "こんにちは世界" * 20 qr = RQRCodeCore::QRCode.new(long_utf8, level: :l) assert qr.version <= 40 end def test_binary_data_non_utf8 binary = "\xFF\xFE\xFD\xFC\xFB" binary.force_encoding(Encoding::BINARY) qr = RQRCodeCore::QRCode.new(binary) refute_nil qr.modules end def test_special_unicode_areas # BOM, Private Use Area, Supplementary Planes [ "\uFEFFHello World", # BOM "\uE000\uE001\uE002", # Private Use Area "𐐀𐐁𐐂" # Deseret alphabet (supplementary plane) ].each do |text| qr = RQRCodeCore::QRCode.new(text) refute_nil qr.modules end end def test_output_maintains_utf8_encoding qr = RQRCodeCore::QRCode.new("Hello 世界") output = qr.to_s assert_equal Encoding::UTF_8, output.encoding end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/data.rb000066400000000000000000002345341512755103000231470ustar00rootroot00000000000000MATRIX_1_H = [[true, true, true, true, true, true, true, false, true, true, false, true, false, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, false, false, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, true, false, false, false, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, false, false, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, false, false, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, true, true, false, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, false, true, true, true, false, false, false, false, false, false, false, false], [false, false, false, false, false, true, true, false, false, true, false, true, false, false, true, false, true, false, true, false, true], [true, true, true, true, true, false, false, true, false, false, true, false, false, true, true, false, true, true, true, true, false], [false, true, false, false, true, true, true, false, true, false, true, true, true, true, false, false, true, true, true, true, false], [false, true, false, false, true, false, false, false, true, false, false, false, true, false, true, false, true, true, true, false, false], [false, true, true, false, false, true, true, false, true, true, false, false, false, false, false, false, false, true, false, false, true], [false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false, false, true, false, false, true], [true, true, true, true, true, true, true, false, false, true, false, false, true, false, true, true, false, false, false, true, false], [true, false, false, false, false, false, true, false, true, true, true, false, false, true, false, false, false, true, true, false, false], [true, false, true, true, true, false, true, false, false, false, false, false, false, false, true, false, true, false, false, true, false], [true, false, true, true, true, false, true, false, false, false, true, false, false, true, true, false, true, false, true, false, false], [true, false, true, true, true, false, true, false, false, true, false, false, false, true, false, false, true, false, false, true, true], [true, false, false, false, false, false, true, false, false, true, false, false, true, false, true, true, false, true, true, false, false], [true, true, true, true, true, true, true, false, false, true, false, true, false, false, false, true, false, false, false, true, false]] MATRIX_3_H = [[true, true, true, true, true, true, true, false, false, true, true, true, true, false, true, true, false, true, true, false, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, false, false, false, true, false, false, true, false, false, true, true, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, true, true, true, true, false, false, false, false, true, false, false, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, false, true, false, true, false, false, false, false, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, true, true, false, true, true, true, true, true, false, false, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, false, false, false, true, false, true, true, false, false, true, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, false, true, false, false, false, true, true, true, true, true, false, true, false, false, false, false, false, false, false, false], [false, false, true, true, false, false, true, true, true, false, true, false, false, true, false, true, false, false, false, true, true, true, true, false, true, false, false, false, false], [true, false, true, false, false, true, false, true, false, true, true, false, false, false, true, true, false, false, false, false, false, false, false, false, true, false, false, false, true], [true, false, true, true, false, true, true, false, false, true, true, true, true, true, false, false, true, false, true, false, false, true, true, false, false, true, true, true, false], [false, false, false, false, true, true, false, false, false, true, true, true, true, true, false, false, false, false, false, true, false, false, false, false, false, true, false, false, true], [false, false, true, true, true, true, true, false, false, false, false, true, true, true, false, false, true, true, false, false, false, true, false, false, false, true, false, false, false], [false, true, true, true, true, false, false, true, true, false, true, true, false, true, true, false, true, false, false, false, false, false, false, false, false, true, true, false, false], [false, true, true, true, true, false, true, false, true, false, true, true, false, false, true, true, true, true, false, false, false, true, false, true, false, false, false, true, false], [true, false, true, true, true, true, false, true, true, true, false, false, false, true, true, false, false, true, false, true, true, true, true, false, true, true, false, true, true], [false, true, true, true, false, false, true, false, true, true, false, false, false, false, false, false, false, true, false, true, true, true, true, true, false, false, false, true, true], [false, true, true, true, false, true, false, false, true, false, false, true, false, true, false, true, true, true, false, true, true, false, true, true, true, false, true, false, false], [true, false, false, true, false, true, true, false, false, false, false, true, false, false, false, false, true, true, true, true, false, false, true, false, false, false, true, false, false], [false, false, true, true, true, true, false, false, false, true, true, false, true, false, true, true, true, true, true, true, false, false, true, false, true, true, false, false, true], [false, true, false, false, true, false, true, true, false, true, false, true, false, true, false, true, false, true, false, false, true, true, true, true, true, true, false, true, true], [false, false, false, false, false, false, false, false, true, true, false, true, true, true, true, true, false, true, true, true, true, false, false, false, true, true, true, false, false], [true, true, true, true, true, true, true, false, true, false, false, true, false, false, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false], [true, false, false, false, false, false, true, false, false, true, false, false, true, false, true, false, false, true, false, true, true, false, false, false, true, false, false, true, true], [true, false, true, true, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, false, true, true, true, true, true, false, true, true, false], [true, false, true, true, true, false, true, false, true, false, false, false, true, false, false, false, false, false, true, true, true, true, true, true, true, false, true, false, false], [true, false, true, true, true, false, true, false, true, false, true, true, true, false, true, true, false, true, false, true, false, false, true, true, false, false, false, false, true], [true, false, false, false, false, false, true, false, false, false, false, false, false, true, false, true, true, true, true, true, false, true, false, true, false, false, false, true, false], [true, true, true, true, true, true, true, false, false, false, true, true, true, false, false, true, false, false, true, false, true, true, false, true, false, true, true, true, false]] MATRIX_5_H = [[true, true, true, true, true, true, true, false, false, true, false, true, false, false, false, false, true, false, false, false, true, false, false, false, true, false, false, true, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, false, true, true, true, false, true, true, true, true, false, false, false, false, true, false, false, false, false, false, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, true, true, false, false, true, false, true, true, false, true, true, true, true, false, false, true, true, false, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, true, false, true, false, true, false, true, false, true, true, false, false, true, true, true, false, false, false, false, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, true, true, false, true, false, false, false, false, true, true, true, false, true, true, true, true, true, true, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, true, false, true, true, false, true, true, false, true, true, true, false, false, true, true, true, true, true, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, false, true, true, false, true, false, false, false, false, false, true, true, false, true, true, false, true, false, true, true, true, false, false, false, false, false, false, false, false], [false, false, false, true, true, false, true, true, false, true, false, true, false, false, true, true, false, true, false, true, false, false, true, true, true, true, true, false, false, false, false, false, false, true, true, false, false], [false, false, false, true, false, true, false, false, false, false, true, true, true, true, false, true, false, true, false, false, true, false, false, false, true, false, true, false, true, false, true, true, false, false, true, true, true], [false, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true, false, false, true, false, true, false, true, false, true, true, true, true, true, false, false, false, false, true, true], [true, true, true, false, true, false, false, true, true, true, true, false, false, true, true, true, true, true, true, true, true, true, false, false, false, false, true, false, true, false, false, true, true, false, true, false, true], [true, false, true, true, false, false, true, true, false, true, true, true, true, false, true, true, false, true, true, false, false, true, false, true, true, true, true, true, false, false, true, true, true, false, true, true, true], [true, false, false, true, true, false, false, false, true, false, false, false, true, false, true, true, true, true, false, false, false, false, true, false, true, false, false, false, false, false, false, false, false, true, false, false, true], [true, true, true, true, true, true, true, true, false, true, true, true, false, false, true, false, false, false, false, false, false, true, true, true, true, false, true, true, false, true, false, true, true, true, true, true, false], [true, true, false, false, true, true, false, true, false, true, true, false, false, true, true, true, true, false, true, false, false, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false], [false, true, true, false, true, false, true, false, true, true, false, false, true, false, false, true, true, true, true, true, true, true, true, true, true, false, false, true, false, false, false, true, true, false, true, false, false], [true, false, false, true, true, true, false, false, false, true, true, false, false, false, false, true, true, false, false, false, false, true, true, false, false, false, false, false, false, true, true, false, false, false, true, true, false], [false, true, false, false, true, false, true, true, false, false, true, true, true, true, true, true, false, false, true, false, false, false, false, false, false, true, true, false, true, true, true, false, true, false, true, false, true], [true, true, true, false, true, true, false, false, true, true, true, true, true, true, true, true, false, true, true, false, false, false, false, false, false, true, false, true, true, true, true, true, true, false, false, false, false], [false, false, false, false, false, true, true, true, false, false, true, true, true, true, true, false, true, true, true, true, false, true, true, true, true, true, false, false, true, false, true, true, true, false, true, true, true], [true, true, true, false, true, true, false, false, true, true, true, true, false, false, false, false, false, true, true, false, true, true, false, false, true, false, false, false, true, true, true, true, true, true, false, false, true], [false, false, true, false, false, false, true, false, false, true, true, true, true, false, true, true, false, true, true, true, false, false, true, false, true, false, true, true, false, true, true, false, false, true, true, false, false], [true, false, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, true, false, true, true, true, false, true, true, false, false, true, true, true, true, false, true, false], [false, false, false, true, false, true, true, true, false, false, false, false, false, true, false, true, false, false, true, true, true, true, true, false, false, true, true, false, true, true, true, false, true, true, true, false, false], [true, false, true, true, false, true, false, false, true, false, false, false, false, true, false, false, true, true, true, true, false, true, true, false, true, false, false, false, true, true, true, true, true, true, true, false, true], [true, true, true, true, false, false, true, false, true, false, true, false, false, true, false, false, false, true, false, true, false, false, true, false, false, false, true, false, true, false, true, false, false, true, false, true, false], [true, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, true, true, true, false, false, false, false, false, true, true, true, true, false, true, true, false, true, true, false, false, false], [true, false, true, true, false, false, true, true, false, false, true, false, false, false, true, false, true, false, true, true, false, true, true, true, false, false, false, false, true, true, true, true, true, true, false, false, false], [false, false, false, false, false, false, false, false, true, false, true, false, false, false, true, false, false, false, true, true, false, false, false, true, true, false, false, false, true, false, false, false, true, true, true, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, false, false, true, false, false, false, false, false, false, false, true, true, false, true, true, true, true, false, true, false, true, false, true, true, true], [true, false, false, false, false, false, true, false, false, true, true, false, false, true, false, false, false, true, true, true, true, true, false, false, true, true, false, true, true, false, false, false, true, true, false, true, true], [true, false, true, true, true, false, true, false, true, false, false, true, false, true, true, false, false, true, true, true, true, false, true, false, true, true, false, false, true, true, true, true, true, true, false, false, true], [true, false, true, true, true, false, true, false, true, true, true, true, true, true, false, false, false, true, true, true, false, false, true, false, false, true, false, false, false, true, true, true, true, true, false, false, true], [true, false, true, true, true, false, true, false, false, true, true, true, true, false, true, false, false, false, false, true, false, true, false, false, true, true, false, false, true, true, false, false, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, false, true, true, false, false, true, true, true, false, false, false, true, false, false, false, true, false, true, true, true, true, true, false, true, false, true, true], [true, true, true, true, true, true, true, false, false, true, true, false, false, false, true, false, true, false, true, true, false, false, false, false, true, true, false, true, true, false, true, true, true, true, true, false, true]] MATRIX_10_H = [[true, true, true, true, true, true, true, false, false, true, false, true, true, true, true, false, false, true, false, true, true, false, true, true, false, false, true, true, false, false, false, true, true, false, true, true, false, false, false, false, false, true, false, false, true, true, true, true, false, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, false, true, false, false, true, false, true, false, false, true, false, false, true, false, true, true, true, true, true, true, true, true, true, true, false, false, true, false, true, false, true, false, true, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, false, true, true, true, false, false, false, true, true, true, false, false, false, false, false, true, false, false, false, true, false, true, true, false, false, true, true, true, false, true, false, true, false, false, false, false, false, true, true, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, false, false, true, false, false, true, true, true, false, false, false, false, true, false, false, false, true, false, true, true, true, true, true, false, false, true, false, false, true, false, true, true, true, false, false, true, false, true, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, false, false, false, false, false, false, false, false, true, false, false, false, true, false, true, false, true, true, true, true, true, true, true, false, true, false, true, true, false, false, true, true, false, true, true, false, false, true, false, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, true, true, true, true, false, true, true, true, true, true, false, false, false, false, true, true, true, false, false, false, true, true, true, false, false, false, true, true, false, true, false, true, false, true, false, true, true, false, false, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, false, false, false, true, false, false, true, true, false, false, false, false, true, true, false, false, false, true, false, false, false, true, false, false, false, false, true, false, true, false, true, false, false, true, false, true, false, false, true, false, false, false, false, false, false, false, false, false], [false, false, true, true, false, false, true, true, true, false, false, true, false, false, false, true, false, true, false, true, false, true, false, true, false, false, true, true, true, true, true, true, true, false, true, true, false, true, true, false, true, false, true, false, true, true, true, false, false, true, true, false, true, false, false, false, false], [false, true, true, false, false, false, false, true, false, false, false, false, false, true, true, false, true, false, false, false, true, false, true, false, true, true, true, false, true, true, false, true, true, true, false, true, false, true, true, false, false, true, false, false, true, true, false, true, false, true, true, true, true, true, false, true, false], [true, true, false, false, false, true, true, true, false, false, false, false, true, true, false, true, true, false, false, true, true, true, false, true, true, false, false, false, true, true, false, true, true, true, false, true, true, false, true, false, false, true, false, true, false, true, false, true, true, false, true, true, true, true, true, false, true], [true, true, true, true, false, false, false, false, false, false, false, true, false, false, false, true, false, true, false, true, true, false, false, false, true, false, false, false, true, true, false, true, true, false, false, false, true, true, false, false, true, false, false, true, false, false, true, true, true, true, false, false, false, true, true, false, false], [false, true, true, true, true, false, true, true, true, false, false, false, true, false, false, true, true, true, false, false, false, false, true, true, true, false, false, false, true, true, false, true, false, true, false, false, false, true, false, false, true, true, true, true, true, false, false, true, true, true, false, false, false, false, true, false, false], [true, true, true, true, false, true, false, false, true, false, false, false, false, false, false, false, true, true, true, false, false, false, true, false, true, true, true, true, true, true, true, true, false, true, true, false, true, false, true, false, true, true, false, false, false, true, false, true, false, false, true, true, false, true, false, false, true], [false, true, true, true, true, true, true, false, false, true, false, true, true, true, false, false, false, true, true, true, false, false, false, true, true, true, true, false, false, false, true, true, true, true, false, false, true, false, false, true, false, true, false, false, true, false, false, true, false, false, false, true, true, true, false, false, true], [false, true, false, false, true, false, false, false, false, true, false, true, false, false, true, true, false, false, false, false, true, true, false, false, false, false, false, false, true, true, true, false, false, true, false, false, true, true, true, false, false, true, false, true, false, true, false, false, true, true, true, true, false, false, true, false, true], [false, false, true, true, true, true, true, false, true, true, false, false, false, false, false, false, true, true, true, false, true, true, true, false, false, true, true, true, false, true, true, true, false, false, false, false, false, false, false, false, false, true, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false], [true, false, false, false, true, false, false, false, false, false, false, false, true, false, false, false, false, true, true, true, true, false, false, true, true, false, true, false, false, false, false, false, true, false, true, true, true, false, true, true, true, true, true, false, false, false, true, true, true, false, true, false, false, true, true, false, false], [true, true, true, false, false, true, true, true, true, true, false, true, false, false, false, true, true, true, true, true, false, true, true, true, false, false, true, true, false, true, false, true, true, false, false, false, false, false, false, true, false, false, true, false, false, false, false, false, false, false, false, false, true, false, false, false, false], [false, true, false, true, true, true, false, true, true, false, true, false, false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, false, true, false, false, false, true, false, true, true, true, false, true, false, false, true, false, false, true, true, true, false, true, false, true, false, false, false, true, true, true], [false, false, true, false, false, true, true, false, true, true, true, false, false, true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, false, true, false, true, false, true, false, true, false, false, true, false, true, false, false, true, false, false, true, false, false, false, true, false, false, false, true, false], [true, false, false, false, false, false, false, false, false, true, false, true, true, true, false, false, false, false, false, false, true, true, true, false, false, true, false, true, false, false, true, true, true, false, true, true, false, false, false, true, true, false, true, false, true, false, true, false, true, false, false, true, true, true, false, false, false], [false, true, false, true, true, false, true, true, true, true, true, false, true, false, true, true, true, false, true, false, true, false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, true, true, true, true, false, false, true, false, false, false, false, false, true, true, true, true, true, true, false, true], [false, true, false, true, false, false, false, true, false, false, true, true, true, false, false, false, false, false, true, true, true, false, true, false, false, true, false, true, false, false, true, false, true, false, true, false, false, false, true, true, true, false, false, false, false, false, true, false, false, false, true, true, true, true, true, true, false], [true, false, false, false, true, true, true, false, false, false, false, true, false, false, true, false, false, false, false, true, false, false, true, false, true, true, false, false, false, true, true, true, true, true, false, true, true, false, true, true, false, true, true, false, false, false, false, false, true, false, true, true, false, false, true, false, false], [true, false, true, false, true, false, false, true, true, false, true, false, false, true, true, false, false, false, true, false, true, false, false, false, true, true, true, false, true, false, true, true, false, false, false, false, true, true, false, true, false, true, false, true, true, false, false, false, true, true, false, true, false, false, false, true, true], [false, false, true, true, true, true, true, true, true, false, true, true, false, true, false, false, true, true, false, true, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, false, true, true, false, false, true, true, false, true, false, true, true, false, true, true, true, true, true, false, true, true, false], [true, true, true, false, true, false, false, false, true, true, true, true, true, true, true, true, false, true, true, true, true, true, false, false, false, true, true, false, false, false, true, false, true, false, true, false, false, false, false, false, false, true, false, false, false, false, true, true, true, false, false, false, true, true, true, true, false], [false, true, false, false, true, false, true, false, true, true, false, false, false, false, true, true, false, false, true, false, false, true, true, false, false, true, true, false, true, false, true, false, false, false, false, true, false, true, true, false, false, true, true, true, false, false, true, false, true, false, true, false, true, true, true, false, true], [false, true, false, true, true, false, false, false, true, false, true, false, false, false, true, true, true, false, true, false, false, false, false, false, true, true, true, false, false, false, true, true, true, false, true, false, true, true, false, true, true, true, true, true, true, true, false, true, true, false, false, false, true, true, true, false, false], [true, true, true, false, true, true, true, true, true, true, false, true, false, false, false, false, true, false, false, false, true, false, true, false, true, true, true, true, true, true, true, true, false, true, true, false, false, true, false, true, false, false, true, true, true, true, false, false, true, true, true, true, true, false, false, false, false], [false, false, true, true, true, false, false, true, false, false, true, false, true, false, false, false, true, true, true, true, true, true, false, true, false, false, false, false, false, false, false, true, false, true, true, true, false, true, false, false, false, true, false, true, true, false, false, false, true, false, true, false, false, false, true, true, true], [false, true, false, false, true, false, true, false, false, false, true, true, false, true, false, true, true, true, true, true, true, true, false, false, false, true, true, false, false, true, false, false, true, false, true, false, false, true, true, true, false, true, false, true, false, true, true, false, true, true, false, false, true, false, false, true, false], [false, false, true, false, true, false, false, false, true, true, false, false, true, true, true, true, false, false, false, true, false, false, true, true, false, true, true, false, false, false, true, true, true, true, false, false, true, true, true, true, true, false, true, true, false, true, false, true, true, true, false, false, true, false, true, false, false], [false, true, true, false, true, true, true, false, false, false, false, false, true, false, true, false, false, true, true, false, false, false, true, true, false, true, true, false, false, false, true, false, false, false, true, false, true, false, true, true, true, false, false, false, true, true, false, true, false, false, true, false, true, false, false, true, false], [false, false, false, true, true, false, false, false, true, false, false, true, true, false, true, false, true, false, true, false, false, false, true, false, true, false, true, false, false, false, true, true, true, false, true, false, false, true, false, true, true, true, true, true, false, true, false, false, true, false, false, true, false, false, false, true, true], [true, true, true, true, true, false, true, true, true, true, false, true, true, false, false, false, false, true, false, false, true, false, true, true, true, false, false, false, true, false, true, false, true, true, false, false, true, true, false, true, false, false, false, true, true, true, true, true, true, false, false, false, false, true, true, true, true], [false, false, true, false, true, false, false, false, false, false, false, false, true, true, false, true, false, true, true, true, false, false, true, true, true, true, true, true, false, true, false, false, false, false, false, true, false, false, true, true, false, false, true, false, false, true, true, false, true, true, true, false, false, false, true, true, true], [false, false, true, true, false, true, true, false, false, true, false, false, false, true, false, true, false, false, false, true, true, true, false, false, false, false, true, false, false, false, true, false, false, false, true, true, true, false, false, false, true, false, false, false, true, false, true, true, false, true, false, true, true, false, true, true, false], [false, false, false, false, true, true, false, true, true, false, false, false, false, false, true, true, true, false, false, true, true, false, false, false, true, false, false, true, false, true, true, true, true, true, true, true, false, false, false, true, false, false, true, true, false, false, true, false, false, true, false, true, false, true, false, true, false], [true, false, false, false, false, true, true, true, false, false, false, false, true, false, true, true, false, true, false, false, false, true, true, true, true, true, true, false, true, false, false, true, false, true, true, false, false, true, true, true, true, false, false, false, true, false, true, false, false, true, false, false, false, true, false, false, true], [true, true, false, false, false, false, false, true, true, true, false, true, true, false, true, false, true, true, true, false, true, true, false, true, true, false, false, true, false, false, false, false, true, true, true, false, true, true, false, false, false, false, false, false, false, true, false, true, true, true, true, true, false, false, false, true, false], [true, true, true, false, true, false, true, true, false, true, false, false, true, false, false, true, false, false, false, false, false, true, true, true, false, false, false, true, true, true, false, false, true, true, true, false, true, true, false, false, true, true, true, false, false, true, false, true, false, true, false, true, true, true, true, true, true], [true, false, true, true, false, true, false, true, false, true, false, false, false, true, false, true, true, true, true, true, false, false, false, true, true, true, false, true, true, true, true, false, true, false, true, false, true, true, false, true, false, false, true, true, true, true, true, true, false, false, false, true, true, true, false, false, false], [true, true, true, true, false, false, true, true, true, false, false, true, false, true, false, false, false, true, true, true, true, true, false, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true, true, false, true, false, true, true, false, false, false, false, true, true, false, true, true, true, false, false, true], [false, false, true, false, true, true, false, false, true, true, true, true, false, true, true, true, false, true, true, true, true, true, true, true, true, true, false, true, false, false, true, true, false, true, false, false, true, true, true, false, false, true, false, true, false, true, false, false, true, true, false, true, false, true, false, true, false], [true, false, true, false, false, true, true, true, true, false, false, true, false, false, true, true, false, true, false, false, false, false, false, false, false, false, true, false, true, true, true, true, false, false, true, true, true, false, true, false, false, true, false, false, true, false, false, true, false, false, true, true, false, true, true, false, true], [true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, true, false, false, true, false, true, false, false, true, false, true, false, true, true, true, false, false, true, false, false, true, true, false, true, false, true, false, false, true, false, true, false, false, true, false, false, false, true, true, true, false, false], [false, false, false, false, false, false, true, true, false, false, true, true, false, true, true, true, true, true, false, false, true, false, false, false, true, false, true, true, true, true, true, false, true, false, true, true, true, false, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, false, false], [false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false, false, true, true, true, false, false, true, false, false, true, true, false, false, false, true, true, false, true, false, true, true, true, false, false, true, true, false, false, false, true, true, true, true, false, false, false, true, true, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, false, true, false, true, true, false, true, true, false, false, true, true, false, true, true, false, true, false, true, false, true, false, false, false, false, true, false, true, false, true, false, false, true, false, true, true, true, false, true, false, true, true, false, false, true], [true, false, false, false, false, false, true, false, false, false, false, true, true, true, false, false, true, false, false, true, true, true, true, true, true, true, true, false, false, false, true, true, true, true, true, true, false, true, false, false, false, true, false, true, false, true, false, true, true, false, false, false, true, false, true, false, true], [true, false, true, true, true, false, true, false, false, false, true, false, false, true, false, true, false, false, true, false, false, true, false, false, false, false, true, true, true, true, true, false, false, false, false, true, false, false, true, false, false, true, true, false, true, true, false, false, true, true, true, true, true, false, false, true, false], [true, false, true, true, true, false, true, false, true, false, false, true, true, false, false, true, false, true, false, false, true, false, false, false, true, false, false, true, false, true, false, false, true, false, true, false, false, true, false, true, true, true, true, false, false, false, true, true, false, false, true, true, false, false, false, true, false], [true, false, true, true, true, false, true, false, true, true, false, false, true, true, false, true, true, false, true, false, true, false, false, false, false, true, true, true, false, false, true, true, true, true, true, false, false, false, false, true, false, false, true, false, false, false, false, false, true, false, true, false, false, true, true, false, false], [true, false, false, false, false, false, true, false, false, false, false, true, true, false, false, false, true, true, true, true, false, false, false, true, false, false, false, false, true, false, true, false, false, false, true, false, true, false, true, false, false, false, true, false, true, true, true, false, false, true, false, false, false, true, false, false, true], [true, true, true, true, true, true, true, false, false, true, false, false, true, false, false, false, false, true, true, false, true, false, true, false, false, true, true, true, true, false, true, false, false, true, false, true, false, true, true, true, false, false, true, false, true, false, false, false, false, true, false, true, true, true, false, false, false]] MATRIX_4_H = [[true, true, true, true, true, true, true, false, false, true, true, false, false, false, false, false, false, false, false, true, false, true, false, true, false, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, false, false, true, true, true, true, false, false, false, false, true, true, false, false, true, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, false, false, true, false, false, true, true, false, true, false, false, true, false, true, true, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, true, false, true, false, true, false, false, true, false, true, true, false, true, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, true, true, false, true, false, false, false, false, false, true, true, true, false, true, false, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true, true, true, false, true, true, true, false, false, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, false, false, true, true, true, false, false, false, true, true, true, false, false, true, true, true, false, false, false, false, false, false, false, false], [false, false, true, true, false, false, true, true, true, true, false, false, false, false, true, true, true, false, false, false, false, true, true, true, true, true, true, false, true, false, false, false, false], [false, true, true, true, false, false, false, true, false, false, false, false, false, false, true, false, true, false, true, true, false, true, false, true, false, false, true, true, false, true, true, false, true], [true, false, true, false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, false, false, true, true, true, true, true, true, true, true, true, false, true, true, true], [false, false, true, true, false, true, false, true, true, true, true, true, false, true, true, true, true, false, true, false, false, false, false, false, true, false, false, true, true, true, false, false, true], [true, false, false, false, false, false, true, false, true, false, true, false, true, true, false, false, false, false, true, false, false, true, false, false, false, true, false, false, true, true, false, false, false], [false, false, false, true, true, true, false, true, false, true, false, false, false, true, false, false, true, true, false, false, true, false, false, false, true, false, false, false, false, true, false, true, false], [false, false, false, false, true, false, true, true, false, false, true, true, true, true, true, false, false, true, false, true, false, true, true, false, true, false, true, true, true, false, false, false, false], [true, true, true, true, true, true, false, false, false, true, false, false, false, false, false, true, false, false, true, false, true, true, true, true, false, true, false, true, true, false, true, true, false], [true, false, false, true, false, false, true, false, false, true, false, true, true, false, true, false, true, false, false, true, false, true, false, false, false, true, true, false, true, false, true, false, true], [false, true, false, false, true, false, false, false, false, true, true, false, false, false, true, false, true, true, true, true, false, false, true, true, true, true, true, true, true, true, false, false, true], [false, true, false, false, false, true, true, true, false, false, false, false, false, true, true, true, false, true, true, true, true, false, false, false, true, false, false, false, false, true, false, false, false], [true, false, true, true, true, true, false, false, true, false, false, false, false, true, true, false, false, false, false, true, false, false, false, false, false, false, true, false, false, false, false, false, false], [false, false, true, false, true, false, true, true, true, false, false, true, true, false, true, false, false, false, true, true, true, true, false, false, false, false, false, true, false, true, true, true, false], [true, true, false, false, true, false, false, false, false, true, true, true, false, true, false, true, false, true, false, true, false, false, false, true, false, false, true, false, false, true, false, false, true], [false, false, true, true, false, false, true, false, true, false, false, true, false, true, false, true, false, true, true, true, false, true, true, true, false, true, false, false, true, false, true, true, true], [false, true, false, false, true, true, false, false, false, false, true, true, true, false, true, false, true, false, true, true, true, false, false, true, false, true, true, true, false, false, false, false, true], [true, false, false, true, false, true, true, false, false, false, false, true, false, false, false, false, false, true, true, true, false, false, true, false, true, true, true, true, true, false, false, true, true], [false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, true, true, true, false, false, true, true, false, true, true, false, false, false, true, true, false, false, false], [true, true, true, true, true, true, true, false, true, true, true, false, false, true, true, false, true, true, true, false, true, true, false, true, true, false, true, false, true, false, true, false, false], [true, false, false, false, false, false, true, false, false, false, true, true, false, false, false, true, true, true, false, false, false, false, false, true, true, false, false, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, true, true, true, true, false, true, true, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, false], [true, false, true, true, true, false, true, false, true, true, false, false, true, true, true, false, true, false, true, false, false, false, true, true, false, false, false, true, false, false, false, true, true], [true, false, true, true, true, false, true, false, true, true, true, true, false, false, true, true, true, false, false, true, false, true, true, true, false, false, true, false, false, false, true, false, false], [true, false, false, false, false, false, true, false, false, false, false, false, true, false, false, true, false, false, true, false, false, true, false, true, true, false, false, true, true, true, false, false, true], [true, true, true, true, true, true, true, false, false, true, true, false, false, true, false, false, false, true, true, true, false, true, false, false, false, false, true, true, true, true, true, false, false]] MATRIX_1_L = [[true, true, true, true, true, true, true, false, false, true, true, true, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, false, true, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, true, false, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, true, true, true, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, true, true, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, false, true, false, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, false, true, true, true, true, false, false, false, false, false, false, false, false], [true, true, false, false, false, true, true, true, false, true, false, false, true, false, false, false, true, true, false, false, false], [false, true, true, false, true, true, false, true, true, true, true, false, true, false, true, false, true, true, true, true, false], [true, false, false, false, false, false, true, false, true, false, false, true, false, true, false, false, true, true, true, true, false], [false, false, false, false, false, true, false, true, false, false, false, false, false, false, false, false, true, true, true, false, false], [true, true, false, true, false, true, true, false, true, false, true, false, false, false, true, false, false, true, false, false, true], [false, false, false, false, false, false, false, false, true, false, true, true, true, true, true, false, false, true, false, false, true], [true, true, true, true, true, true, true, false, true, true, false, false, true, false, true, true, false, false, false, true, false], [true, false, false, false, false, false, true, false, true, true, true, true, true, true, false, false, false, true, true, false, false], [true, false, true, true, true, false, true, false, false, true, false, false, true, false, false, false, true, false, false, true, false], [true, false, true, true, true, false, true, false, false, false, true, false, true, false, false, false, true, false, true, false, false], [true, false, true, true, true, false, true, false, false, true, true, false, false, false, true, false, true, false, false, true, true], [true, false, false, false, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, false, false], [true, true, true, true, true, true, true, false, true, false, false, true, false, true, false, true, false, false, false, true, false]] MATRIX_1_M = [[true, true, true, true, true, true, true, false, true, false, true, false, false, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, true, false, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, true, false, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, false, false, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, true, false, true, false, false, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, false, true, false, true, false, false, false, false, false, false, false, false, false], [true, false, true, false, false, false, true, true, false, true, false, true, true, false, false, true, false, false, true, false, true], [false, false, true, true, true, true, false, true, true, true, false, false, false, false, false, false, false, true, false, true, true], [true, false, true, true, false, false, true, true, false, false, true, false, false, false, true, false, false, false, true, false, true], [false, false, true, true, true, true, false, true, true, true, true, false, true, false, false, false, true, true, false, false, false], [false, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, false, true, false, false, true], [false, false, false, false, false, false, false, false, true, true, false, true, false, true, true, false, false, true, true, false, true], [true, true, true, true, true, true, true, false, true, true, true, true, true, true, false, true, true, true, false, false, true], [true, false, false, false, false, false, true, false, false, false, true, true, false, true, true, false, true, true, false, false, true], [true, false, true, true, true, false, true, false, false, true, false, true, true, true, true, false, false, true, false, false, true], [true, false, true, true, true, false, true, false, false, false, false, false, false, false, false, false, true, false, false, false, false], [true, false, true, true, true, false, true, false, true, true, true, false, false, false, true, false, true, false, false, true, true], [true, false, false, false, false, false, true, false, false, false, false, false, true, false, false, true, false, true, false, false, false], [true, true, true, true, true, true, true, false, true, true, false, false, false, false, true, true, true, true, false, false, true]] MATRIX_1_Q = [[true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, true, false, true, false, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, false, false, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, true, true, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false], [false, true, false, false, false, false, true, true, true, true, false, false, true, true, false, false, false, false, false, true, true], [false, true, true, true, false, false, false, false, false, true, true, true, true, false, true, false, true, true, true, true, false], [false, false, false, false, false, true, true, true, true, false, false, true, false, true, false, false, true, true, true, true, false], [true, false, false, true, true, true, false, false, false, true, false, true, false, false, false, false, true, true, true, false, false], [false, false, false, false, false, true, true, true, false, false, false, false, false, false, true, false, false, true, false, false, true], [false, false, false, false, false, false, false, false, true, false, true, true, false, true, true, false, false, true, false, false, true], [true, true, true, true, true, true, true, false, true, true, true, false, true, false, true, true, false, false, false, true, false], [true, false, false, false, false, false, true, false, false, false, false, true, true, true, false, false, false, true, true, false, false], [true, false, true, true, true, false, true, false, false, true, false, false, true, false, false, false, true, false, false, true, false], [true, false, true, true, true, false, true, false, false, true, false, false, true, false, false, false, true, false, true, false, false], [true, false, true, true, true, false, true, false, false, true, true, false, false, false, true, false, true, false, false, true, true], [true, false, false, false, false, false, true, false, true, true, false, false, false, false, false, true, false, true, true, false, false], [true, true, true, true, true, true, true, false, false, true, false, true, false, true, false, true, false, false, false, true, false]] MATRIX_4_L = [[true, true, true, true, true, true, true, false, true, true, true, false, true, false, false, false, false, true, false, true, true, false, true, true, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, true, true, true, false, false, false, false, false, true, true, false, true, false, false, false, false, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, true, false, true, true, false, true, false, true, true, false, false, false, false, true, true, true, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, true, false, true, false, false, true, true, true, true, true, false, false, false, true, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, false, false, false, true, true, false, true, false, true, true, false, true, true, true, true, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, true, false, false, true, true, true, true, true, false, false, true, false, true, false, false, false, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, false, true, false, false, true, false, true, false, false, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false], [true, true, false, false, true, true, true, false, false, false, false, true, false, true, true, true, true, false, true, false, false, true, false, false, true, false, false, true, false, true, true, true, true], [true, false, true, true, true, true, false, true, false, true, true, false, true, false, false, false, false, true, false, true, true, false, true, true, false, false, false, true, false, false, true, true, false], [false, true, true, true, false, false, true, true, true, false, false, true, true, true, true, true, false, false, true, false, true, true, true, true, true, false, false, true, true, true, true, true, false], [true, true, true, true, true, true, false, true, true, false, true, true, false, true, false, true, true, false, false, false, false, true, true, false, true, false, true, true, true, false, false, false, true], [false, true, false, true, true, true, true, true, true, false, true, false, true, true, false, false, false, false, false, true, true, true, true, true, true, false, true, true, true, false, false, false, false], [false, true, false, true, false, false, false, false, true, true, false, false, false, true, true, false, true, false, true, true, false, false, false, true, true, true, false, true, false, false, true, true, false], [false, true, true, false, true, true, true, true, false, false, false, false, false, false, false, false, true, true, false, true, false, false, true, true, true, true, true, false, false, true, true, false, false], [true, true, false, false, true, false, false, false, true, true, true, false, true, false, true, false, false, true, true, true, true, true, true, false, false, true, true, false, false, false, false, true, true], [false, false, false, true, false, true, true, false, true, true, true, true, false, true, true, true, true, false, true, false, false, true, true, false, false, false, true, false, true, false, false, false, true], [false, true, true, true, true, true, false, true, false, true, true, false, true, false, false, false, false, true, false, true, true, true, false, true, false, true, false, false, false, false, true, false, false], [true, false, false, true, false, true, true, false, true, false, true, true, true, true, true, true, false, false, true, false, true, true, false, true, true, true, false, false, false, false, true, true, false], [false, true, false, true, false, true, false, false, true, false, false, true, false, true, false, true, true, false, false, false, false, true, true, true, true, true, true, true, false, true, false, true, true], [true, false, false, false, false, true, true, false, false, true, true, false, true, true, false, false, false, false, false, true, true, true, true, false, true, false, true, false, true, false, false, false, true], [true, false, false, true, true, false, false, true, true, false, false, false, false, true, true, false, true, false, true, true, false, false, true, true, false, false, false, true, false, true, false, false, false], [false, false, false, false, false, true, true, true, true, false, true, false, false, false, false, false, true, true, false, true, false, false, false, true, true, false, false, false, true, true, true, true, false], [false, false, false, true, true, true, false, true, true, false, false, false, true, false, true, false, false, true, true, true, true, true, false, true, true, false, true, false, true, false, false, true, true], [true, true, false, true, false, true, true, false, false, false, true, true, false, true, true, true, true, false, true, false, false, false, false, true, true, true, true, true, true, false, false, false, true], [false, false, false, false, false, false, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false, false, true, false, false, false, true, false, true, false, false], [true, true, true, true, true, true, true, false, false, false, true, true, true, true, true, true, false, false, true, false, true, false, false, false, true, false, true, false, true, false, true, true, false], [true, false, false, false, false, false, true, false, true, true, true, true, false, true, false, true, true, false, false, false, false, false, true, false, true, false, false, false, true, false, false, true, false], [true, false, true, true, true, false, true, false, true, false, false, false, true, true, false, false, false, false, false, true, true, false, true, false, true, true, true, true, true, false, false, true, true], [true, false, true, true, true, false, true, false, false, true, false, false, false, true, true, false, true, false, true, true, false, false, false, true, true, false, true, true, true, true, false, true, false], [true, false, true, true, true, false, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false, true, true, false, true, false, true, false, true, false, true, false, false], [true, false, false, false, false, false, true, false, true, false, false, false, true, false, true, false, false, true, true, true, true, true, false, true, true, false, false, true, false, true, false, false, false], [true, true, true, true, true, true, true, false, true, false, false, true, false, true, true, true, true, false, true, false, false, false, false, true, true, false, true, true, true, false, false, false, true]] MATRIX_4_M = [[true, true, true, true, true, true, true, false, true, true, false, true, false, true, false, true, false, false, true, false, false, false, false, true, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, true, false, false, true, true, true, false, true, false, true, true, false, false, false, false, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, true, true, false, false, false, true, true, true, true, true, false, true, true, false, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, true, true, false, false, false, false, false, true, true, false, true, false, true, true, false, false, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, false, false, true, true, false, false, true, true, false, true, false, true, true, false, false, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, true, true, false, false, true, true, true, true, false, false, false, false, true, true, true, true, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, false, true, false, false, false, false, false, false, true, true, true, true, true, false, false, true, false, false, false, false, false, false, false, false], [true, false, false, false, true, false, true, true, true, false, true, false, true, false, false, true, true, true, false, false, true, true, true, true, true, true, true, true, true, true, false, false, true], [true, true, false, true, false, false, false, true, false, true, true, false, true, false, true, true, false, true, true, false, false, true, true, true, false, true, false, false, false, true, true, true, true], [false, false, false, false, true, false, true, false, true, false, false, false, true, true, true, true, true, false, true, false, true, false, false, true, true, false, false, false, false, false, true, false, false], [true, false, true, true, true, true, false, true, true, false, true, true, true, true, false, true, true, false, false, false, false, false, true, true, true, true, false, true, true, false, false, true, true], [false, true, true, true, false, true, true, true, true, true, true, true, true, true, false, false, true, false, false, true, false, false, true, false, false, true, true, false, false, true, false, false, true], [true, false, true, true, false, true, false, true, true, false, false, false, true, true, true, false, false, false, true, true, true, false, false, false, true, false, true, true, true, true, false, false, false], [false, true, true, true, false, true, true, false, false, false, true, false, false, true, false, true, false, true, true, false, true, true, true, true, true, true, true, true, false, true, true, true, false], [true, true, false, false, true, false, false, false, false, false, true, true, true, true, false, true, true, true, true, false, false, true, true, true, false, false, true, false, false, false, true, true, true], [false, true, true, true, false, false, true, true, true, false, false, true, true, false, false, false, true, true, false, true, true, true, true, false, false, false, true, false, true, false, true, false, true], [false, false, true, true, true, false, false, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, true, true, false, true, false, true, false, true, true, false, false], [false, false, false, true, false, true, true, true, true, true, true, false, true, true, false, true, true, true, false, false, false, true, true, true, false, true, true, true, false, false, true, true, false], [true, true, false, true, false, false, false, true, false, true, false, false, true, true, true, true, true, true, true, true, false, true, false, false, true, false, true, true, true, true, false, false, false], [false, false, true, false, false, true, true, false, false, false, true, true, false, true, true, false, false, false, true, true, true, false, false, true, true, false, false, true, false, false, false, false, false], [true, true, true, true, false, false, false, false, true, true, true, true, true, false, true, false, true, false, false, true, false, false, true, false, false, true, true, false, false, false, true, false, false], [false, false, false, true, true, true, true, true, true, false, false, true, true, false, true, false, true, false, false, true, false, false, true, false, false, false, false, false, true, false, false, true, false], [false, false, false, false, false, true, false, false, false, true, true, true, false, false, true, false, false, true, true, true, true, true, true, false, true, true, true, true, true, true, false, true, false], [true, true, true, true, true, false, true, true, false, false, true, false, true, false, false, true, true, true, true, false, false, true, true, true, true, true, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, true, false, true, true, false, false, true, false, false, true, false, false, false, true, true, false, true, false], [true, true, true, true, true, true, true, false, true, true, true, true, true, true, false, true, true, false, true, false, true, false, false, false, true, false, true, false, true, true, true, true, false], [true, false, false, false, false, false, true, false, false, false, true, true, true, false, false, false, true, false, false, true, true, false, true, true, true, false, false, false, true, false, false, false, true], [true, false, true, true, true, false, true, false, true, false, false, true, true, true, true, false, true, false, false, true, false, false, true, false, true, true, true, true, true, false, false, true, true], [true, false, true, true, true, false, true, false, false, false, true, true, true, true, true, false, false, true, true, true, true, true, false, true, false, false, true, false, true, true, false, true, false], [true, false, true, true, true, false, true, false, false, true, false, true, true, false, false, true, false, false, true, false, true, false, false, false, true, false, true, false, true, true, true, false, false], [true, false, false, false, false, false, true, false, false, true, true, false, false, false, false, true, true, true, true, false, false, true, false, false, false, false, false, true, false, false, true, false, false], [true, true, true, true, true, true, true, false, true, true, true, true, false, true, true, false, false, true, false, true, true, true, true, false, true, false, true, false, false, false, true, false, true]] MATRIX_4_Q = [[true, true, true, true, true, true, true, false, false, true, true, true, false, true, true, true, false, true, true, false, false, true, true, false, true, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, true, false, false, true, false, false, false, true, true, false, true, true, false, false, false, false, false, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, false, false, true, true, true, false, true, false, false, false, false, true, true, true, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, false, false, true, true, true, false, true, false, true, true, true, false, true, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, true, true, false, false, true, true, false, false, false, false, false, false, true, true, false, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, true, false, true, true, false, true, false, true, false, true, false, true, false, false, true, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, true, false, true, true, true, true, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false], [false, true, false, true, true, true, true, false, true, false, false, false, true, true, false, false, true, true, false, false, false, false, false, true, true, true, true, false, true, true, false, true, false], [false, false, false, true, true, true, false, false, true, true, false, false, true, false, false, true, false, true, true, true, false, true, false, true, false, false, false, false, true, true, true, false, false], [true, false, false, false, true, false, true, false, true, false, true, false, true, true, true, false, true, false, false, false, false, false, true, true, false, false, true, false, true, false, false, true, true], [false, false, false, true, true, false, false, false, false, true, false, false, true, false, true, false, false, false, true, false, false, true, true, true, false, false, true, false, true, false, true, true, true], [true, true, true, false, false, true, true, true, false, true, true, false, true, false, false, true, true, true, false, true, false, true, false, true, false, false, true, true, false, false, false, false, false], [true, false, false, false, true, false, false, false, true, false, false, true, true, false, false, true, false, false, true, true, true, false, false, false, true, true, false, true, false, false, true, false, false], [true, false, true, false, true, true, true, true, false, false, false, false, true, false, true, false, true, true, true, true, true, true, true, false, false, false, true, true, true, false, false, false, false], [true, false, true, true, false, false, false, false, false, true, true, true, true, false, false, false, true, false, false, false, true, false, true, false, true, false, false, false, true, false, true, true, false], [false, false, true, false, false, false, true, false, true, false, true, false, false, true, true, true, false, true, true, false, true, false, true, true, false, true, true, true, true, false, false, true, true], [true, true, false, false, true, false, false, true, false, true, true, false, false, true, false, true, false, true, false, true, true, false, false, false, false, false, true, true, true, false, true, false, true], [true, false, true, true, true, true, true, true, true, false, false, false, true, true, true, false, true, false, false, true, true, true, true, true, true, false, false, true, false, false, false, true, true], [true, false, false, false, true, true, false, false, true, true, false, true, false, false, false, true, true, true, true, false, true, false, true, true, false, true, true, false, false, false, true, false, false], [true, false, true, true, false, true, true, false, true, false, true, true, false, false, false, true, true, true, false, true, false, true, true, true, true, false, false, false, false, false, false, true, false], [true, false, true, false, true, true, false, false, false, true, false, false, false, true, false, false, true, false, true, false, false, false, false, false, false, true, false, false, true, false, true, true, false], [true, false, false, false, true, false, true, false, true, true, false, true, false, false, false, true, true, false, true, true, true, false, false, false, true, false, true, false, false, true, true, true, true], [true, false, false, false, false, false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true, false, true, false, false, true, true, false, false, true, true, true, false], [true, true, true, false, false, true, true, false, true, true, false, false, true, true, false, false, false, false, true, false, false, false, false, false, true, true, true, true, true, false, false, true, true], [false, false, false, false, false, false, false, false, true, true, false, false, true, false, true, true, true, false, false, false, false, true, false, false, true, false, false, false, true, true, false, false, false], [true, true, true, true, true, true, true, false, false, true, false, true, true, true, true, true, true, false, true, true, true, false, false, true, true, false, true, false, true, false, false, false, false], [true, false, false, false, false, false, true, false, true, true, false, true, true, true, false, false, true, false, false, true, false, true, true, false, true, false, false, false, true, true, true, true, true], [true, false, true, true, true, false, true, false, true, false, true, true, false, false, false, false, true, false, true, false, false, true, true, true, true, true, true, true, true, true, false, true, true], [true, false, true, true, true, false, true, false, true, true, false, false, true, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false, false, true, true, false, true], [true, false, true, true, true, false, true, false, false, true, true, false, true, true, false, false, true, true, true, true, false, false, false, false, false, true, false, false, true, true, true, true, true], [true, false, false, false, false, false, true, false, true, true, false, false, true, false, false, true, false, true, false, true, true, false, true, false, true, false, true, false, true, false, true, true, true], [true, true, true, true, true, true, true, false, false, true, true, false, true, true, true, false, true, false, true, true, false, false, false, false, true, false, true, true, true, true, false, false, false]] MATRIX_UTF8_RU_TEST = [[true, true, true, true, true, true, true, false, true, false, true, true, true, true, true, false, false, false, true, true, true, true, true, true, true], [true, false, false, false, false, false, true, false, false, false, false, false, true, true, true, true, true, false, true, false, false, false, false, false, true], [true, false, true, true, true, false, true, false, true, false, false, true, false, true, true, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, false, false, true, false, true, true, false, true, true, false, true, false, true, true, true, false, true], [true, false, true, true, true, false, true, false, true, false, false, true, true, true, false, true, false, false, true, false, true, true, true, false, true], [true, false, false, false, false, false, true, false, false, true, false, false, true, true, false, true, false, false, true, false, false, false, false, false, true], [true, true, true, true, true, true, true, false, true, false, true, false, true, false, true, false, true, false, true, true, true, true, true, true, true], [false, false, false, false, false, false, false, false, true, true, true, false, false, false, false, true, true, false, false, false, false, false, false, false, false], [false, false, false, false, false, true, true, false, false, false, true, false, true, true, false, true, false, false, true, false, true, false, true, false, true], [false, false, false, false, false, true, false, true, true, true, true, true, false, true, true, true, false, false, true, true, false, false, true, true, true], [false, true, true, false, true, true, true, false, false, false, false, false, false, true, false, false, false, false, true, false, true, true, false, true, false], [true, true, false, false, true, false, false, false, false, false, true, false, false, false, false, true, false, true, true, true, true, false, true, false, false], [false, false, false, false, false, false, true, true, false, false, false, false, true, true, false, false, false, true, true, true, true, false, false, true, true], [true, true, true, true, false, true, false, false, false, true, false, true, false, false, true, false, false, true, true, false, false, true, false, false, true], [true, false, false, true, true, false, true, true, true, false, false, true, true, true, false, true, true, true, false, false, true, false, false, false, false], [true, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false, true, false, false, false, true, true, false, true, true], [true, false, true, true, true, true, true, true, false, false, true, false, false, true, true, true, true, true, true, true, true, false, false, false, true], [false, false, false, false, false, false, false, false, true, false, false, false, false, true, true, false, true, false, false, false, true, false, false, true, true], [true, true, true, true, true, true, true, false, false, true, true, true, false, true, false, true, true, false, true, false, true, false, true, false, false], [true, false, false, false, false, false, true, false, true, true, false, false, true, false, true, true, true, false, false, false, true, false, true, false, true], [true, false, true, true, true, false, true, false, false, true, false, true, true, false, false, false, true, true, true, true, true, true, false, false, false], [true, false, true, true, true, false, true, false, false, true, true, true, true, true, false, false, false, true, false, false, true, false, true, false, true], [true, false, true, true, true, false, true, false, false, true, false, false, false, true, false, false, true, false, true, false, true, false, false, false, true], [true, false, false, false, false, false, true, false, false, true, false, false, false, false, true, true, false, false, true, true, false, true, true, false, true], [true, true, true, true, true, true, true, false, false, true, false, true, false, false, false, true, true, false, true, true, true, false, true, false, true]] whomwah-rqrcode_core-c13e78d/test/rqrcode_core/multi_segment_encoding_test.rb000066400000000000000000000162761512755103000300200ustar00rootroot00000000000000require "test_helper" # Tests for multi-segment encoding with various combinations and edge cases class MultiSegmentEncodingTest < Minitest::Test # Happy path: basic multi-segment functionality def test_creates_valid_qr_code_with_multiple_segments segments = [ {data: "123456789", mode: :number}, {data: "HELLO-WORLD", mode: :alphanumeric}, {data: "lower case text", mode: :byte_8bit} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.multi_segment?, "Should be recognized as multi-segment" assert qr.modules.size > 0, "Should produce valid QR code" end # Core value proposition: multi-segment is more efficient than forcing all data to byte mode def test_multi_segment_more_efficient_than_single_byte_mode numeric_data = "1" * 100 alpha_data = "A" * 100 # Single mode (forced to byte) single = numeric_data + alpha_data qr_single = RQRCodeCore::QRCode.new(single, level: :l) # Multi-segment (optimized modes) segments = [ {data: numeric_data, mode: :number}, {data: alpha_data, mode: :alphanumeric} ] qr_multi = RQRCodeCore::QRCode.new(segments, level: :l) assert qr_multi.version <= qr_single.version, "Multi-segment should be at least as efficient" end # Determinism: same input produces same output def test_same_segments_produce_identical_output segments = [ {data: "123", mode: :number}, {data: "ABC", mode: :alphanumeric}, {data: "xyz", mode: :byte_8bit} ] qr1 = RQRCodeCore::QRCode.new(segments, size: 5, level: :m) qr2 = RQRCodeCore::QRCode.new(segments, size: 5, level: :m) assert_equal qr1.modules, qr2.modules, "Same segments should produce identical output" end # Order matters: QR code content depends on segment order def test_segment_order_affects_output segments1 = [ {data: "123", mode: :number}, {data: "ABC", mode: :alphanumeric} ] segments2 = [ {data: "ABC", mode: :alphanumeric}, {data: "123", mode: :number} ] qr1 = RQRCodeCore::QRCode.new(segments1, size: 3, level: :m) qr2 = RQRCodeCore::QRCode.new(segments2, size: 3, level: :m) refute_equal qr1.modules, qr2.modules, "Different segment order should produce different output" end # Error correction levels work with multi-segment def test_supports_all_error_correction_levels segments = [ {data: "12345", mode: :number}, {data: "ABCDE", mode: :alphanumeric} ] %i[l m q h].each do |level| qr = RQRCodeCore::QRCode.new(segments, level: level) assert_equal level, qr.error_correction_level, "Should use specified error correction level #{level}" end end # Respects version constraints def test_respects_forced_version segments = [ {data: "123", mode: :number}, {data: "ABC", mode: :alphanumeric} ] qr = RQRCodeCore::QRCode.new(segments, size: 10) assert_equal 10, qr.version, "Should respect forced version" end def test_respects_max_size_constraint segments = [ {data: "1" * 100, mode: :number}, {data: "A" * 100, mode: :alphanumeric} ] qr = RQRCodeCore::QRCode.new(segments, level: :l, max_size: 20) assert qr.version <= 20, "Should respect max_size" end # Handles large data def test_handles_large_multi_segment_data segments = [ {data: "1" * 200, mode: :number}, {data: "A" * 150, mode: :alphanumeric}, {data: "x" * 100, mode: :byte_8bit} ] qr = RQRCodeCore::QRCode.new(segments, level: :l) assert qr.version <= 40, "Should fit within maximum version" end def test_larger_segments_require_larger_version small_segments = [ {data: "123", mode: :number}, {data: "ABC", mode: :alphanumeric} ] large_segments = [ {data: "1" * 300, mode: :number}, {data: "A" * 200, mode: :alphanumeric} ] qr_small = RQRCodeCore::QRCode.new(small_segments, level: :h) qr_large = RQRCodeCore::QRCode.new(large_segments, level: :h) assert qr_small.version < qr_large.version, "Larger segments should require larger version" end # Edge cases def test_handles_empty_segments segments = [ {data: "", mode: :byte_8bit}, {data: "HELLO", mode: :alphanumeric}, {data: "", mode: :number} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.multi_segment?, "Should handle empty segments" end def test_handles_many_small_segments segments = 10.times.map do |i| {data: "SEG#{i}", mode: :alphanumeric} end qr = RQRCodeCore::QRCode.new(segments) assert qr.multi_segment?, "Should handle many segments" end def test_single_segment_in_array_is_still_multi_segment segments = [{data: "test", mode: :byte_8bit}] qr = RQRCodeCore::QRCode.new(segments) assert qr.multi_segment?, "Single segment in array should be recognized as multi-segment" end # UTF-8 and special characters def test_handles_utf8_characters segments = [ {data: "Hello", mode: :byte_8bit}, {data: "世界", mode: :byte_8bit} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.modules.size > 0, "Should handle UTF-8 in segments" end def test_handles_emoji segments = [ {data: "Hello", mode: :byte_8bit}, {data: "👋🌍", mode: :byte_8bit}, {data: "123", mode: :number} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.modules.size > 0, "Should handle emoji in segments" end # Failure modes: invalid mode for data def test_raises_error_for_invalid_mode segments = [ {data: "hello", mode: :alphanumeric} # lowercase not valid for alphanumeric ] assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(segments) end end def test_raises_error_when_data_too_large_for_max_size segments = [ {data: "1" * 1000, mode: :number}, {data: "A" * 1000, mode: :alphanumeric} ] assert_raises(RQRCodeCore::QRCodeRunTimeError) do RQRCodeCore::QRCode.new(segments, level: :h, max_size: 10) end end # Failure modes: invalid input structure def test_raises_error_for_missing_mode_key invalid_segment = [{data: "test"}] # missing :mode assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(invalid_segment) end end def test_raises_error_for_non_hash_in_array invalid = ["not a hash", {data: "test", mode: :byte_8bit}] assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(invalid) end end # Real-world use cases def test_url_broken_into_optimized_segments segments = [ {data: "HTTPS://", mode: :alphanumeric}, {data: "example.com", mode: :byte_8bit}, {data: "/PATH", mode: :alphanumeric} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.modules.size > 0, "Should handle URL as segments" end def test_structured_data_like_wifi_config segments = [ {data: "WIFI:", mode: :alphanumeric}, {data: "T:WPA;S:", mode: :byte_8bit}, {data: "MyNetwork", mode: :byte_8bit}, {data: ";P:", mode: :byte_8bit}, {data: "password123", mode: :byte_8bit}, {data: ";;", mode: :byte_8bit} ] qr = RQRCodeCore::QRCode.new(segments) assert qr.modules.size > 0, "Should handle structured data segments" end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/output_verification_test.rb000066400000000000000000000250441512755103000273710ustar00rootroot00000000000000require "test_helper" # High-level tests that verify QR code output correctness across various scenarios # These tests focus on structural integrity and consistency rather than internal implementation class OutputVerificationTest < Minitest::Test def test_module_count_matches_version_formula # QR Code size formula: version * 4 + 17 # Version 1 = 21x21, Version 10 = 57x57, etc. [1, 2, 5, 10, 20, 30, 40].each do |version| qr = RQRCodeCore::QRCode.new("test", size: version) expected_size = version * 4 + 17 assert_equal expected_size, qr.module_count, "Version #{version} should have module_count of #{expected_size}" assert_equal expected_size, qr.modules.size, "Version #{version} should have #{expected_size} rows" qr.modules.each_with_index do |row, i| assert_equal expected_size, row.size, "Version #{version}, row #{i} should have #{expected_size} columns" end end end def test_modules_contain_only_boolean_values qr = RQRCodeCore::QRCode.new("test data", size: 5) qr.modules.each_with_index do |row, r| row.each_with_index do |col, c| assert [true, false].include?(col), "Module at [#{r}][#{c}] should be boolean, got #{col.inspect}" end end end def test_checked_method_returns_boolean qr = RQRCodeCore::QRCode.new("hello") (0...qr.module_count).each do |row| (0...qr.module_count).each do |col| result = qr.checked?(row, col) assert [true, false].include?(result), "checked?(#{row}, #{col}) should return boolean" end end end def test_to_s_output_dimensions qr = RQRCodeCore::QRCode.new("test", size: 3) output = qr.to_s lines = output.split("\n") assert_equal qr.module_count, lines.size, "to_s should produce module_count lines" lines.each_with_index do |line, i| assert_equal qr.module_count, line.length, "Line #{i} should have module_count characters" end end def test_to_s_with_quiet_zone qr = RQRCodeCore::QRCode.new("test", size: 2) [1, 2, 4].each do |zone_size| output = qr.to_s(quiet_zone_size: zone_size, dark: "x", light: " ") lines = output.split("\n") expected_line_count = qr.module_count + (zone_size * 2) # Top and bottom quiet zone rows should be added assert_equal expected_line_count, lines.size, "With quiet_zone_size=#{zone_size}, should have #{expected_line_count} lines" # Check that top quiet zone lines are all spaces (light) zone_size.times do |i| assert lines[i].strip.empty?, "Top quiet zone row #{i} should be all light characters" end # Check that bottom quiet zone lines are all spaces (light) zone_size.times do |i| row_idx = lines.size - zone_size + i assert lines[row_idx].strip.empty?, "Bottom quiet zone row #{row_idx} should be all light characters" end # Check that quiet zones are on sides of content rows content_start = zone_size content_end = lines.size - zone_size - 1 (content_start..content_end).each do |row_idx| line = lines[row_idx] # Lines should start and end with quiet zone spaces assert line.start_with?(" " * zone_size), "Content row #{row_idx} should start with #{zone_size} spaces" end end end def test_to_s_with_custom_characters qr = RQRCodeCore::QRCode.new("test", size: 1) # Test with single character dark/light output1 = qr.to_s(dark: "#", light: ".") assert output1.include?("#"), "Output should contain dark character #" assert output1.include?("."), "Output should contain light character ." refute output1.include?("x"), "Output should not contain default dark character" # Test with multi-character dark/light output2 = qr.to_s(dark: "██", light: " ") lines = output2.split("\n") lines.each do |line| assert_equal qr.module_count * 2, line.length, "Multi-char output should double the line length" end end def test_cross_version_consistency_for_same_data data = "https://github.com/whomwah/rqrcode_core" levels = %i[l m q h] levels.each do |level| qr = RQRCodeCore::QRCode.new(data, level: level) # Verify basic properties assert qr.version >= 1, "Version should be at least 1" assert qr.version <= 40, "Version should not exceed 40" assert_equal level, qr.error_correction_level, "Error correction level should match requested" # Verify module structure assert qr.modules.size > 0, "Should have modules" assert qr.module_count == qr.modules.size, "module_count should match modules array size" end end def test_version_attribute_matches_requested_size [1, 5, 10, 15, 25, 40].each do |size| qr = RQRCodeCore::QRCode.new("test", size: size) assert_equal size, qr.version, "version attribute should match requested size" end end def test_error_correction_level_attribute data = "test" {l: :l, m: :m, q: :q, h: :h}.each do |input, expected| qr = RQRCodeCore::QRCode.new(data, level: input) assert_equal expected, qr.error_correction_level, "Should return #{expected} for level #{input}" end end def test_same_input_produces_consistent_output data = "consistency test" qr1 = RQRCodeCore::QRCode.new(data, size: 3, level: :h) qr2 = RQRCodeCore::QRCode.new(data, size: 3, level: :h) assert_equal qr1.modules, qr2.modules, "Same input should produce identical modules" assert_equal qr1.to_s, qr2.to_s, "Same input should produce identical string output" end def test_different_levels_produce_different_outputs data = "test data" qr_l = RQRCodeCore::QRCode.new(data, size: 3, level: :l) qr_m = RQRCodeCore::QRCode.new(data, size: 3, level: :m) qr_q = RQRCodeCore::QRCode.new(data, size: 3, level: :q) qr_h = RQRCodeCore::QRCode.new(data, size: 3, level: :h) # Different error correction levels should produce different patterns refute_equal qr_l.modules, qr_m.modules refute_equal qr_l.modules, qr_q.modules refute_equal qr_l.modules, qr_h.modules refute_equal qr_m.modules, qr_q.modules end def test_inspect_output_format qr = RQRCodeCore::QRCode.new("test", size: 4, level: :h) inspect_str = qr.inspect assert inspect_str.include?("QRCodeCore"), "inspect should include class name" assert inspect_str.include?("@version=4"), "inspect should include version" assert inspect_str.include?("@module_count=33"), "inspect should include module_count (4*4+17=33)" end def test_position_patterns_are_present # QR codes have three position detection patterns (finder patterns) in corners # They should be present in all QR codes qr = RQRCodeCore::QRCode.new("test", size: 5) # Top-left corner should have pattern (7x7) assert qr.checked?(0, 0), "Top-left corner finder pattern should be dark" assert qr.checked?(6, 0), "Top-left corner finder pattern edge should be dark" assert qr.checked?(0, 6), "Top-left corner finder pattern edge should be dark" # Top-right corner (offset by module_count - 7) offset = qr.module_count - 7 assert qr.checked?(0, offset), "Top-right corner finder pattern should be dark" # Bottom-left corner assert qr.checked?(offset, 0), "Bottom-left corner finder pattern should be dark" end def test_timing_patterns_are_present # QR codes have timing patterns (alternating dark/light) at row 6 and column 6 qr = RQRCodeCore::QRCode.new("test", size: 5) # Check horizontal timing pattern (row 6) (8...qr.module_count - 8).each do |col| expected = col.even? assert_equal expected, qr.checked?(6, col), "Timing pattern at row 6, col #{col} should be #{expected}" end # Check vertical timing pattern (column 6) (8...qr.module_count - 8).each do |row| expected = row.even? assert_equal expected, qr.checked?(row, 6), "Timing pattern at row #{row}, col 6 should be #{expected}" end end def test_mode_attribute_returns_correct_symbol numeric_qr = RQRCodeCore::QRCode.new("12345") assert_equal :mode_number, numeric_qr.mode, "Numeric data should use mode_number" alpha_qr = RQRCodeCore::QRCode.new("ABC123") assert_equal :mode_alpha_numk, alpha_qr.mode, "Alphanumeric data should use mode_alpha_numk" byte_qr = RQRCodeCore::QRCode.new("hello world") assert_equal :mode_8bit_byte, byte_qr.mode, "Mixed case data should use mode_8bit_byte" end def test_multi_segment_mode_detection multi_qr = RQRCodeCore::QRCode.new([ {data: "123", mode: :number}, {data: "ABC", mode: :alphanumeric} ]) assert multi_qr.multi_segment?, "Multi-segment QR should return true for multi_segment?" single_qr = RQRCodeCore::QRCode.new("test") refute single_qr.multi_segment?, "Single-segment QR should return false for multi_segment?" end def test_qr_code_with_whitespace qr_with_spaces = RQRCodeCore::QRCode.new("hello world with spaces") qr_with_newlines = RQRCodeCore::QRCode.new("line1\nline2\nline3") qr_with_tabs = RQRCodeCore::QRCode.new("tab\tseparated\tvalues") # Should not raise errors and should produce valid QR codes assert qr_with_spaces.modules.size > 0 assert qr_with_newlines.modules.size > 0 assert qr_with_tabs.modules.size > 0 end def test_qr_code_with_special_characters special_chars = "!@#$%^&*()_+-=[]{}|;:',.<>?/~`" qr = RQRCodeCore::QRCode.new(special_chars) assert qr.modules.size > 0, "Should handle special characters" assert qr.module_count > 0, "Should have valid module count" assert_equal :mode_8bit_byte, qr.mode, "Special characters should use byte mode" end def test_version_increases_with_data_length # Longer data should require larger versions (at same error correction level) short_data = "x" * 10 medium_data = "x" * 100 long_data = "x" * 500 qr_short = RQRCodeCore::QRCode.new(short_data, level: :h) qr_medium = RQRCodeCore::QRCode.new(medium_data, level: :h) qr_long = RQRCodeCore::QRCode.new(long_data, level: :h) assert qr_short.version < qr_medium.version, "Medium data should require larger version than short" assert qr_medium.version < qr_long.version, "Long data should require larger version than medium" end def test_higher_error_correction_requires_larger_version # Same data with higher error correction may require larger version data = "x" * 50 qr_l = RQRCodeCore::QRCode.new(data, level: :l) qr_h = RQRCodeCore::QRCode.new(data, level: :h) # Higher error correction uses more space for redundancy assert qr_l.version <= qr_h.version, "Higher error correction should require same or larger version" end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/qr_segment_test.rb000066400000000000000000000013561512755103000254330ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" class RQRCodeCore::QRSegmentTest < Minitest::Test PAYLOAD = [{data: "byteencoded", mode: :byte_8bit}, {data: "A1" * 107, mode: :alphanumeric}, {data: "1" * 498, mode: :number}] def test_multi_payloads RQRCodeCore::QRCode.new(PAYLOAD, level: :l) RQRCodeCore::QRCode.new(PAYLOAD, level: :m) RQRCodeCore::QRCode.new(PAYLOAD, level: :q) RQRCodeCore::QRCode.new(PAYLOAD) RQRCodeCore::QRCode.new(PAYLOAD, level: :l, max_size: 22) rescue => e flunk(e) end def test_invalid_code_configs assert_raises(RQRCodeCore::QRCodeArgumentError) { RQRCodeCore::QRCode.new(:not_a_string_or_array) RQRCodeCore::QRCode.new(PAYLOAD << :not_a_segment) } end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/qr_util_test.rb000066400000000000000000000072241512755103000247460ustar00rootroot00000000000000require "test_helper" module RQRCodeCore class QRUtilTest < Minitest::Test def test_demerit_points_4_dark_ratio # Test with all white modules (ratio = 0) # Expected: (100 * 0 - 50).abs / 5 * 10 = 10 * 10 = 100 modules = Array.new(4) { Array.new(4, false) } assert_equal 100, QRUtil.demerit_points_4_dark_ratio(modules) # Test with all black modules (ratio = 1) # Expected: (100 * 1 - 50).abs / 5 * 10 = 50 / 5 * 10 = 10 * 10 = 100 modules = Array.new(4) { Array.new(4, true) } assert_equal 100, QRUtil.demerit_points_4_dark_ratio(modules) # Test with half black, half white modules (ratio = 0.5) # Expected: (100 * 0.5 - 50).abs / 5 * 10 = 0 / 5 * 10 = 0 modules = [ [true, true, false, false], [true, true, false, false], [false, false, true, true], [false, false, true, true] ] assert_equal 0, QRUtil.demerit_points_4_dark_ratio(modules) # Test with 25% black modules (ratio = 0.25) # Expected: (100 * 0.25 - 50).abs / 5 * 10 = 25 / 5 * 10 = 5 * 10 = 50 modules = [ [true, false, false, false], [false, true, false, false], [false, false, true, false], [false, false, false, true] ] assert_equal 50, QRUtil.demerit_points_4_dark_ratio(modules) # Test with 75% black modules (ratio = 0.75) # Expected: (100 * 0.75 - 50).abs / 5 * 10 = 25 / 5 * 10 = 5 * 10 = 50 modules = [ [true, true, true, true], [true, true, true, false], [true, true, false, true], [true, false, false, true] ] assert_equal 50, QRUtil.demerit_points_4_dark_ratio(modules) # Test with different sized modules (3x3) # 3 black out of 9 = 1/3 ratio = 0.33... # Expected: (100 * (3/9) - 50).abs / 5 * 10 ≈ 16.67 * 10 = 166.7 modules = [ [true, false, false], [false, true, false], [false, false, true] ] expected = ((100 * (3.0 / 9) - 50).abs / 5) * 10 assert_in_delta expected, QRUtil.demerit_points_4_dark_ratio(modules), 0.01 end def test_demerit_points_4_dark_ratio_edge_cases # Test with empty modules # This shouldn't happen in real QR codes, but let's be safe modules = [] assert QRUtil.demerit_points_4_dark_ratio(modules).nan? # Test with 1x1 module # All white modules = [[false]] assert_equal 100, QRUtil.demerit_points_4_dark_ratio(modules) # All black modules = [[true]] assert_equal 100, QRUtil.demerit_points_4_dark_ratio(modules) end def test_demerit_points_4_dark_ratio_formula # Test the formula directly for a specific case # For a 5x5 module with 13 dark cells: # ratio = 13/25 = 0.52 # ratio_delta = (100 * 0.52 - 50).abs / 5 = 2/5 = 0.4 # demerit points = 0.4 * 10 = 4 modules = [ [true, true, true, false, false], [true, true, true, false, false], [true, true, true, false, true], [true, true, false, false, false], [true, false, false, false, false] ] # Count dark modules dark_count = modules.flatten.count(true) assert_equal 13, dark_count # Calculate ratio ratio = dark_count.to_f / (5 * 5) assert_in_delta 0.52, ratio, 0.001 # Calculate ratio_delta ratio_delta = (100 * ratio - 50).abs / 5 assert_in_delta 0.4, ratio_delta, 0.001 # Calculate demerit points demerit_points = ratio_delta * 10 assert_in_delta 4, demerit_points, 0.001 # Check that our method gives the same result assert_in_delta 4, QRUtil.demerit_points_4_dark_ratio(modules), 0.001 end end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/regresions_test.rb000066400000000000000000000003421512755103000254410ustar00rootroot00000000000000require "test_helper" class RegresionTests < Minitest::Test # Rs block information was incomplete. def test_code_length_overflow_bug RQRCodeCore::QRCode.new("s" * 220) RQRCodeCore::QRCode.new("s" * 195) end end whomwah-rqrcode_core-c13e78d/test/rqrcode_core/rqrcode_test.rb000066400000000000000000000166571512755103000247400ustar00rootroot00000000000000require "test_helper" class RQRCodeCore::BaseTest < Minitest::Test require_relative "data" def test_qrcode_argument_error error = assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(size: 1, level: :h) end assert_equal "data must be a String, QRSegment, or an Array", error.message error = assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(nil) end assert_equal "data must be a String, QRSegment, or an Array", error.message error = assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new({}) end assert_equal "data must be a String, QRSegment, or an Array", error.message error = assert_raises(RQRCodeCore::QRCodeArgumentError) do RQRCodeCore::QRCode.new(123) end assert_equal "data must be a String, QRSegment, or an Array", error.message error = assert_raises(RQRCodeCore::QRCodeArgumentError) do assert RQRCodeCore::QRCode.new("10000000000000000000", size: 41, level: :h) end assert_equal "Given size greater than maximum possible size of 40", error.message error = assert_raises(RQRCodeCore::QRCodeArgumentError) do assert RQRCodeCore::QRCode.new("hello", size: 1, level: :a) end assert_equal "Unknown error correction level `:a`", error.message end def test_qrcode_runtime_error error = assert_raises(RQRCodeCore::QRCodeRunTimeError) do RQRCodeCore::QRCode.new("duncan").checked?(0, 999999) end assert_equal "Invalid row/column pair: 0, 999999", error.message error = assert_raises(RQRCodeCore::QRCodeRunTimeError) do assert RQRCodeCore::QRCode.new("10000000000000000000", size: 1, level: :h) end assert_equal "code length overflow. (81>72). (Try a larger size!)", error.message error = assert_raises(RQRCodeCore::QRCodeRunTimeError) do assert RQRCodeCore::QRUtil.get_mask(9, 1, 2) end assert_equal "bad mask_pattern: 9", error.message error = assert_raises(RQRCodeCore::QRCodeRunTimeError) do assert RQRCodeCore::QRUtil.get_length_in_bits(:duncan, 1) end assert_equal "Unknown mode: duncan", error.message error = assert_raises(RQRCodeCore::QRCodeRunTimeError) do assert RQRCodeCore::QRUtil.get_length_in_bits(1 << 0, 41) end assert_equal "Unknown version: 41", error.message end def test_H_ qr = RQRCodeCore::QRCode.new("duncan", size: 1) assert_equal qr.modules.size, 21 assert_equal qr.modules, MATRIX_1_H qr = RQRCodeCore::QRCode.new("duncan", size: 1) assert_equal qr.modules, MATRIX_1_H qr = RQRCodeCore::QRCode.new("duncan", size: 1, level: :l) assert_equal qr.modules, MATRIX_1_L qr = RQRCodeCore::QRCode.new("duncan", size: 1, level: :m) assert_equal qr.modules, MATRIX_1_M qr = RQRCodeCore::QRCode.new("duncan", size: 1, level: :q) assert_equal qr.modules, MATRIX_1_Q end def test_3_H_ qr = RQRCodeCore::QRCode.new("duncan", size: 3) assert_equal qr.modules.size, 29 assert_equal qr.modules, MATRIX_3_H end def test_5_H_ qr = RQRCodeCore::QRCode.new("duncan", size: 5) assert_equal qr.modules.size, 37 assert_equal qr.modules, MATRIX_5_H end def test_10_H_ qr = RQRCodeCore::QRCode.new("duncan", size: 10) assert_equal qr.modules.size, 57 assert_equal qr.modules, MATRIX_10_H end def test_4_H_ qr = RQRCodeCore::QRCode.new("www.bbc.co.uk/programmes/b0090blw", level: :l, size: 4) assert_equal qr.modules, MATRIX_4_L qr = RQRCodeCore::QRCode.new("www.bbc.co.uk/programmes/b0090blw", level: :m, size: 4) assert_equal qr.modules, MATRIX_4_M qr = RQRCodeCore::QRCode.new("www.bbc.co.uk/programmes/b0090blw", level: :q, size: 4) assert_equal qr.modules, MATRIX_4_Q qr = RQRCodeCore::QRCode.new("www.bbc.co.uk/programmes/b0090blw") assert_equal qr.modules.size, 33 assert_equal qr.modules, MATRIX_4_H end def test_to_s qr = RQRCodeCore::QRCode.new("duncan", size: 1) assert_equal "xxxxxxx xx x xxxxxxx\n", qr.to_s[0..21] assert_equal "qqqqqqqnqqnqnnqqqqqqq\n", qr.to_s(dark: "q", light: "n")[0..21] assert_equal "@@@@@@@ @@ @ @@@@@@@\n", qr.to_s(dark: "@")[0..21] end def test_auto_alphanumeric # Overflowws without the alpha version assert RQRCodeCore::QRCode.new("1234567890", size: 1, level: :h) qr = RQRCodeCore::QRCode.new("DUNCAN", size: 1, level: :h) assert_equal "xxxxxxx xxx xxxxxxx\n", qr.to_s[0..21] end def test_manual_alphanumeric assert_equal :mode_alpha_numk, RQRCodeCore::QRCode.new("DUNCAN", mode: :alphanumeric).mode assert_raises(RQRCodeCore::QRCodeArgumentError) { RQRCodeCore::QRCode.new("Duncan", mode: :alphanumeric) } end def test_auto_numeric # When digit only automatically uses numeric mode, default ecc level is :h digits = RQRCodeCore::QRCode.new("1" * 17) # Version 1, numeric mode, ECC h assert_equal 1, digits.version assert_equal :mode_number, digits.mode assert_equal :h, digits.error_correction_level # When alpha automatically works alpha = RQRCodeCore::QRCode.new("X" * 10) # Version 1, alpha mode, ECC h assert_equal 1, alpha.version assert_equal :mode_alpha_numk, alpha.mode assert_equal :h, alpha.error_correction_level # Generic should use binary binary = RQRCodeCore::QRCode.new("x" * 7) # Version 1, 8bit mode, ECC h assert_equal 1, binary.version assert_equal :mode_8bit_byte, binary.mode assert_equal :h, binary.error_correction_level end def test_numeric_2_M data = "279042272585972554922067893753871413584876543211601021503002" qr = RQRCodeCore::QRCode.new(data, size: 2, level: :m, mode: :number) assert_equal "xxxxxxx x x x xxxxxxx\n", qr.to_s[0..25] end def test_rszf_error_not_thrown assert RQRCodeCore::QRCode.new("2 1058 657682") assert RQRCodeCore::QRCode.new("40952", size: 1, level: :h) assert RQRCodeCore::QRCode.new("40932", size: 1, level: :h) assert RQRCodeCore::QRCode.new("", size: 1, level: :h) assert RQRCodeCore::QRCode.new("0", size: 1, level: :h) end def test_exceed_max_size assert_raises RQRCodeCore::QRCodeArgumentError do RQRCodeCore::QRCode.new("duncan", size: 41) end end def test_error_correction_level # attr_reader was wrong assert_equal RQRCodeCore::QRCode.new("a", level: :h).error_correction_level, :h end def test_version_table # tables in RQRCodeCore::QRCode::QRMAXBITS wasn't updated to support greater versions assert_equal RQRCodeCore::QRCode.new("1" * 289, level: :h, mode: :number).version, 11 assert_equal RQRCodeCore::QRCode.new("A" * 175, level: :h, mode: :alphanumeric).version, 11 assert_equal RQRCodeCore::QRCode.new("a" * 383, level: :h, mode: :byte_8bit).version, 21 end def test_levels assert RQRCodeCore::QRCode.new("duncan", level: :l) assert RQRCodeCore::QRCode.new("duncan", level: :m) assert RQRCodeCore::QRCode.new("duncan", level: :q) assert RQRCodeCore::QRCode.new("duncan", level: :h) %w[a b c d e f g i j k n o p r s t u v w x y z].each do |ltr| assert_raises(RQRCodeCore::QRCodeArgumentError) { RQRCodeCore::QRCode.new("duncan", level: ltr.to_sym) } end end def test_utf8 qr = RQRCodeCore::QRCode.new("тест") assert_equal qr.modules, MATRIX_UTF8_RU_TEST end def test_large_integer RQRCodeCore::QRCode.new((1 << 64).to_s, mode: :number) RQRCodeCore::QRCode.new(((1 << 64) + 1).to_s, mode: :number) rescue => e flunk(e) end end whomwah-rqrcode_core-c13e78d/test/test_helper.rb000066400000000000000000000001121512755103000220640ustar00rootroot00000000000000require "bundler/setup" require "rqrcode_core" require "minitest/autorun"