pax_global_header00006660000000000000000000000064151300036740014511gustar00rootroot0000000000000052 comment=f0d65004f2b5ba595fd47b36104fc1a549c07def rqrcode-3.2.0/000077500000000000000000000000001513000367400131525ustar00rootroot00000000000000rqrcode-3.2.0/.github/000077500000000000000000000000001513000367400145125ustar00rootroot00000000000000rqrcode-3.2.0/.github/FUNDING.yml000066400000000000000000000001401513000367400163220ustar00rootroot00000000000000# These are supported funding model platforms github: whomwah buy_me_a_coffee: duncanrobertson rqrcode-3.2.0/.github/workflows/000077500000000000000000000000001513000367400165475ustar00rootroot00000000000000rqrcode-3.2.0/.github/workflows/ruby.yml000066400000000000000000000014161513000367400202550ustar00rootroot00000000000000name: rqrcode on: push: branches: - main - release pull_request: # Runs on any PR regardless of target branch 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 for Ruby ${{ matrix.ruby }} on ${{ matrix.os }} run: bundle exec rake spec - name: StandardRB check for Ruby ${{ matrix.ruby }} on ${{ matrix.os }} run: bundle exec standardrb --format progress rqrcode-3.2.0/.gitignore000066400000000000000000000002401513000367400151360ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /.devcontainer/ .rvmrc *.sublime-project *.sublime-workspace *.gem /benchmark/results/ rqrcode-3.2.0/.rspec000066400000000000000000000000361513000367400142660ustar00rootroot00000000000000--color --require spec_helper rqrcode-3.2.0/.standard.yml000066400000000000000000000000221513000367400155450ustar00rootroot00000000000000ruby_version: 2.7 rqrcode-3.2.0/AGENTS.md000066400000000000000000000036241513000367400144620ustar00rootroot00000000000000# RQRCode Development Guide for AI Agents ## Build/Test/Lint Commands - `bundle install` - Install dependencies - `rake` - Run all specs and auto-fix linting (default task) - `rake spec` - Run all specs - `bundle exec rspec spec/path/to/file_spec.rb` - Run a single test file - `bundle exec rspec spec/path/to/file_spec.rb:42` - Run single test at line 42 - `rake standard` - Check code style - `rake standard:fix` - Auto-fix code style issues - `./bin/console` - Launch interactive console ## Code Style (Standard RB) - Follow [Standard Ruby](https://github.com/testdouble/standard) style guide (enforced via `standard` gem) - Ruby version: >= 3.0.0 - Always use `# frozen_string_literal: true` at the top of all Ruby files - Use double quotes for strings - Use snake_case for variables/methods, SCREAMING_SNAKE_CASE for constants - 2-space indentation - No trailing whitespace - Module structure: `lib/rqrcode/` for implementation, `spec/rqrcode/` for tests ## File Structure & Naming - Implementation: `lib/rqrcode/.rb` or `lib/rqrcode//.rb` - Tests: `spec/rqrcode/_spec.rb` (must end with `_spec.rb`) - Export modules live in `lib/rqrcode/export/` (e.g., `svg.rb`, `png.rb`, `ansi.rb`) ## Testing - Use RSpec for all tests - Test files require `spec_helper` at the top - Use `describe` blocks for grouping related tests - Use `it` blocks for individual test cases - Tests should verify behavior, not implementation details ## Dependencies - Core QR generation: `rqrcode_core` gem (do not modify, separate project) - PNG rendering: `chunky_png` gem - This gem focuses on rendering QR codes from `rqrcode_core` data structures ## Commit Messages Use the Semantic Commit Message style: - **type**: The type of change (see below) - **scope**: Optional, the area of the codebase affected (e.g., `auth`, `api`, `ui`) - **subject**: A brief description in imperative mood, lowercase, no full stop rqrcode-3.2.0/CHANGELOG.md000066400000000000000000000107011513000367400147620ustar00rootroot00000000000000# 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] ## [3.2.0] - 2025-06-12 ### Added - Comprehensive benchmarking suite in `benchmark/` directory for measuring performance and memory usage across all export formats (SVG, PNG, HTML, ANSI) - `benchmark_helper.rb` providing shared utilities for IPS, memory, and stack profiling - Rake tasks for running benchmarks individually or all at once - `benchmark/README.md` explaining usage, metrics, and interpretation of results - `AGENTS.md` as a development guide for AI agents ### Changed - **SVG rendering**: Improved by **+130%** (from 184 i/s to 424 i/s) with **71% memory reduction** - **HTML rendering**: Now the fastest export format at **1,876 i/s** (rendering-only benchmark) - **Memory efficiency**: HTML now uses **6x less memory** than SVG (previously 22x) - Updated minimum Ruby version requirement to >= 3.2.0 - Updated GitHub workflow Ruby matrix to test only supported versions (3.2, 3.3, 3.4, 4.0) - Updated `README.md` with benchmark documentation and contribution guidelines ## [3.1.1] - 2025-11-25 - Update required_ruby_version to support >= rather than ~> ready for Ruby 4 ## [3.1.0] - 2025-04-28 - Added support for `offset_x` and `offset_y` options in the `as_svg` method for independent x and y padding around QR codes [#153] ## [3.0.0] - 2025-04-24 - Drop support for Ruby <3.0 in order to keep up with dev dependencies. - **Breaking Change**: The `rqrcode_core` gem has been updated to version 2.0.0, which includes breaking changes. Please refer to the [rqrcode_core changelog](https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md) ## [2.2.0] - 2023-06-17 ### Changed - Allow all ChunkyPNG::Color options to be passed into `fill` and `color` on `as_png` [#135] - Add 3.2 to CI [@petergoldstein](https://github.com/petergoldstein) [#133] - Development dependency upgrades. Minimum Ruby change [#130] - README updates ## [2.1.2] - 2022-07-26 ### Changed - Remove setup script as it just calls bundle install [#128] - Change inline styles to the fill property to allow for strict CSP style-src directive [#127] ## [2.1.1] - 2022-02-11 ### Added - Added in a handler for when color arguments are passed in as symbols e.g `color: :yellow`. This also allows for the use of the `:currentColor` keyword. [#122] ## [2.1.0] - 2021-08-26 ### Changed - Sync Gemfile.lock with `rqrcode_core.1.2.0` [Adds Multimode Support](https://github.com/whomwah/rqrcode_core#multiple-encoding-support) ### Added - Add badge for Standard linting ### Changed - Corrected method name referred to in CHANGELOG. ## [2.0.0] - 2021-05-06 ### Added - A new `use_path:` option on `.as_svg`. This uses a `` node to greatly reduce the final SVG size. [#108] - A new `viewbox:` option on `.as_svg`. Replaces the `svg.width` and `svg.height` attribute with `svg.viewBox` to allow CSS scaling. [#112] - A new `svg_attributes:` option on `.as_svg`. Allows you to pass in custom SVG attributes to be used in the `` tag. [#113] ### Changed - README updated - Rakefile cleaned up. You can now just run `rake` which will run specs and fix linting using `standardrb` - Small documentation clarification [@smnscp](https://github.com/smnscp) - Bump `rqrcode_core` to `~> 1.0` ### Breaking Change - The dependency `rqrcode_core-1.0.0` has a tiny breaking change to the `to_s` public method. https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md#breaking-changes ## [1.2.0] - 2020-12-26 ### Changed - README updated - bump dependencies - fix `required_ruby_version` for Ruby 3 support [unreleased]: https://github.com/whomwah/rqrcode/compare/v3.2.0...HEAD [3.2.0]: https://github.com/whomwah/rqrcode/compare/v3.1.1...v3.2.0 [3.1.1]: https://github.com/whomwah/rqrcode/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/whomwah/rqrcode/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/whomwah/rqrcode/compare/v2.2.0...v3.0.0 [2.2.0]: https://github.com/whomwah/rqrcode/compare/v2.1.2...v2.2.0 [2.1.2]: https://github.com/whomwah/rqrcode/compare/v2.1.1...v2.1.2 [2.1.1]: https://github.com/whomwah/rqrcode/compare/v2.1.0...v2.1.1 [2.1.0]: https://github.com/whomwah/rqrcode/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/whomwah/rqrcode/compare/v1.2.0...v2.0.0 [1.2.0]: https://github.com/whomwah/rqrcode/compare/v1.1.1...v1.2.0 ���������������������������������������������������������������rqrcode-3.2.0/Gemfile�������������������������������������������������������������������������������0000664�0000000�0000000�00000000220�15130003674�0014437�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������source "https://rubygems.org" # Specify your gem's dependencies in rqrcode-base.gemspec gemspec # gem "rqrcode_core", path: "../rqrcode_core" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/Gemfile.lock��������������������������������������������������������������������������0000664�0000000�0000000�00000012103�15130003674�0015371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PATH remote: . specs: rqrcode (3.2.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) GEM remote: https://rubygems.org/ specs: ast (2.4.3) benchmark-ips (2.14.0) chunky_png (1.4.0) diff-lcs (1.6.2) json (2.18.0) language_server-protocol (3.17.0.5) lint_roller (1.1.0) memory_profiler (1.1.0) 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) rqrcode_core (2.1.0) rspec (3.13.2) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) rspec-core (3.13.6) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) 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) rake (~> 13.0) rqrcode! rspec (~> 3.5) stackprof (~> 0.2) standard (~> 1.41) CHECKSUMS ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 benchmark-ips (2.14.0) sha256=b72bc8a65d525d5906f8cd94270dccf73452ee3257a32b89fbd6684d3e8a9b1d chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 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 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 (3.2.0) rqrcode_core (2.1.0) sha256=f303b85df89c1b8fc5ee8dc19808c9dc4330e6329b660d99d4a8cbb36ca13051 rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/LICENSE.txt���������������������������������������������������������������������������0000664�0000000�0000000�00000002073�15130003674�0014777�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The 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. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/README.md�����������������������������������������������������������������������������0000664�0000000�0000000�00000023133�15130003674�0014433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# RQRCode ![](https://github.com/whomwah/rqrcode/actions/workflows/ruby.yml/badge.svg) [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard) [RQRCode](https://github.com/whomwah/rqrcode) is a library for creating and rendering QR codes into various formats. It has a simple interface with all the standard QR code options. It was adapted from the Javascript library by Kazuhiko Arase. - QR code is trademarked by Denso Wave inc - Minimum Ruby version is `>= 3.2.0` ## Installing Add this line to your application's `Gemfile`: ```ruby gem "rqrcode", "~> 3.0" ``` or install manually: ```ruby gem install rqrcode ``` ## Basic usage example ```ruby require "rqrcode" qr = RQRCode::QRCode.new("https://kyan.com") puts qr.to_s # to_s( dark: "x", light: " " ) # defaults xxxxxxx xxxxxxx xxx xxxxxxx x x x xxx xx x x x xxx x xx x x xx x xxx x x xxx x xx xx xx x xxx x x xxx x x x xxx x xxx x x x xxx x xx x x x x ... ``` Easy, but unlikely to be readable. For this you will need to use one of the many [rendering options](#render-types) below. ### Advanced Options These are the various QR code generation options provided by the underlying [rqrcode_core](https://github.com/whomwah/rqrcode_core). You may actually only need this library if you don't need the various rendering options `rqrcode` provides, but just need the data structure. ``` 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 ``` Example ```ruby simple_qrcode = RQRCodeCore::QRCode.new("https://kyan.com", size: 2, level: :m, mode: :byte_8bit) segment_qrcode = RQRCodeCore::QRCode.new([{ data: "foo", mode: :byte_8bit }]) multi_qrcode = RQRCodeCore::QRCode.new([ { data: 'foo', mode: :byte_8bit }, { data: 'BAR1', mode: :alphanumeric } ]) ``` ## Render types You probably want to output your QR code in a specific format. We make this easy by providing a bunch of formats to choose from below, each with their own set of options: ### `as_svg` The SVG renderer will produce a stand-alone SVG as a `String` ``` Options: offset - Padding around the QR Code in pixels (default 0) offset_x - X Padding around the QR Code in pixels (default offset) offset_y - Y Padding around the QR Code in pixels (default offset) fill - Background color e.g "ffffff" or :white or :currentColor (default none) color - Foreground color e.g "000" or :black or :currentColor (default "000") module_size - The Pixel size of each module (defaults 11) shape_rendering - SVG Attribute: auto | optimizeSpeed | crispEdges | geometricPrecision (defaults crispEdges) standalone - Whether to make this a full SVG file, or only an svg to embed in other svg (default true) use_path - Use to render SVG rather than to significantly reduce size. This will become the default in future versions. (default false) viewbox - Replace the `svg.width` and `svg.height` attribute with `svg.viewBox` to allow CSS scaling (default false) svg_attributes - A optional hash of custom attributes. Existing attributes will remain. (default {}) ``` Example ```ruby require "rqrcode" qrcode = RQRCode::QRCode.new("http://github.com/") # NOTE: showing with default options specified explicitly svg = qrcode.as_svg( color: "000", shape_rendering: "crispEdges", module_size: 11, standalone: true, use_path: true ) ``` ![QR code with github url](./images/github-qrcode.svg) ### `as_png` The will produce a PNG using the [ChunkyPNG gem](https://github.com/wvanbergen/chunky_png). The result will be a `ChunkyPNG::Image` instance. ``` Options: fill - Background , defaults to 'white'. Use [] for multi args color - Foreground , defaults to 'black'. Use [] for multi args When option :file is supplied you can use the following ChunkyPNG constraints: color_mode - The color mode to use. Use one of the ChunkyPNG::COLOR_* constants. (defaults to 'ChunkyPNG::COLOR_GRAYSCALE') bit_depth - The bit depth to use. This option is only used for indexed images. (defaults to 1 bit) interlace - Whether to use interlacing (true or false). (defaults to ChunkyPNG default) compression - The compression level for Zlib. This can be a value between 0 and 9, or a Zlib constant like Zlib::BEST_COMPRESSION (defaults to ChunkyPNG default) There are two sizing algorithms. * Original that can result in blurry and hard to scan images * Google's Chart API inspired sizing that resizes the module size to fit within the given image size. The Google one will be used when no options are given or when the new size option is used. *Google Sizing* size - Total size of PNG in pixels. The module size is calculated so it fits. (defaults to 120) border_modules - Width of white border around the modules. (defaults to 4). -- DONT USE border_modules OPTION UNLESS YOU KNOW ABOUT THE QUIET ZONE NEEDS OF QR CODES -- *Original Sizing* module_px_size - Image size, in pixels. border - Border thickness, in pixels It first creates an image where 1px = 1 module, then resizes. Defaults to 120x120 pixels, customizable by option. ``` Example ```ruby require "rqrcode" qrcode = RQRCode::QRCode.new("http://github.com/") # NOTE: showing with default options specified explicitly png = qrcode.as_png( bit_depth: 1, border_modules: 4, color_mode: ChunkyPNG::COLOR_GRAYSCALE, color: "black", file: nil, fill: "white", module_px_size: 6, resize_exactly_to: false, resize_gte_to: false, size: 120 ) IO.binwrite("/tmp/github-qrcode.png", png.to_s) ``` ![QR code with github url](./images/github-qrcode.png) ### `as_ansi` The ANSI renderer will produce as a string with ANSI color codes. ``` Options: light - Foreground ANSI code (default "\033[47m") dark - Background ANSI code (default "\033[40m") fill_character - The written character (default ' ') quiet_zone_size - Padding around the edge (default 4) ``` Example ```ruby require "rqrcode" qrcode = RQRCode::QRCode.new("http://github.com/") # NOTE: showing with default options specified explicitly svg = qrcode.as_ansi( light: "\033[47m", dark: "\033[40m", fill_character: " ", quiet_zone_size: 4 ) ``` ![QR code with github url](./images/ansi-screen-shot.png) ## API Documentation [http://www.rubydoc.info/gems/rqrcode](http://www.rubydoc.info/gems/rqrcode) ## Tests You can run the test suite using: ``` $ bundle install $ rake # runs specs and standard:fix $ rake spec # just runs the specs ``` or try the lib from the console with: ``` $ ./bin/console ``` ## Linting The project uses [standardrb](https://github.com/testdouble/standard) and can be used with: ``` $ bundle install $ rake standard # checks $ rake standard:fix # fixes ``` ## Benchmarks RQRCode includes comprehensive performance benchmarks for tracking export format performance over time. See the [benchmark README](benchmark/README.md) for details on running benchmarks, interpreting results, and current performance baselines. ## Contributing I am not currently accepting any new renderers as the current `as_png`, `as_svg` and `as_ansi` work for most cases. If you need something different from what's available, the [`rqrcode_core`](https://github.com/whomwah/rqrcode_core) gem gives you access to all the QR Code information you will need so makes it simple to generate your own. The motivation for the above is because the rendering side of this gem takes up the most time. It seems that many people want a slightly different version of a QR Code so supporting all the variations would be hard. The easiest way is to empower people to create their own versions which they can manage and share. This is what `rqrcode_core` does. Any contribution PR's will be greatly accepted. It's important that they are well tested and backwards compatible. - Fork the project - Send a pull request - Don't touch the .gemspec, I'll do that when I release a new version Thanks D. ## Authors Original RQRCode author: Duncan Robertson A massive thanks to [all the contributors of the library over the years](https://github.com/whomwah/rqrcode/graphs/contributors). It wouldn't exist if it wasn't for you all. Oh, and thanks to my bosses at https://kyan.com for giving me time to maintain this project. ## Resources - wikipedia:: http://en.wikipedia.org/wiki/QR_Code - Denso-Wave website:: http://www.denso-wave.com/qrcode/index-e.html - kaywa:: http://qrcode.kaywa.com ## Copyright MIT License (http://www.opensource.org/licenses/mit-license.html) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/Rakefile������������������������������������������������������������������������������0000664�0000000�0000000�00000001605�15130003674�0014621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������begin require "standard/rake" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task default: [:spec, "standard:fix"] rescue LoadError # no standard/rspec available end # Benchmark tasks namespace :benchmark do desc "Run all benchmarks" task :all do %w[svg png html ansi format_comparison].each do |format| Rake::Task["benchmark:#{format}"].invoke end end desc "Run SVG export benchmarks" task :svg do ruby "benchmark/svg_export.rb" end desc "Run PNG export benchmarks" task :png do ruby "benchmark/png_export.rb" end desc "Run HTML export benchmarks" task :html do ruby "benchmark/html_export.rb" end desc "Run ANSI export benchmarks" task :ansi do ruby "benchmark/ansi_export.rb" end desc "Run format comparison benchmarks" task :format_comparison do ruby "benchmark/format_comparison.rb" end end ���������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/_config.yml���������������������������������������������������������������������������0000664�0000000�0000000�00000000031�15130003674�0015273�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������theme: jekyll-theme-slate�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0015104�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/README.md�������������������������������������������������������������������0000664�0000000�0000000�00000024501�15130003674�0016365�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# RQRCode Benchmarks This directory contains benchmarks for tracking RQRCode performance over time, measuring both **end-to-end workflows** (generation + export) and **rendering-only** performance. ## Quick Start ```bash # Install dependencies bundle install # Run specific format benchmarks rake benchmark:svg rake benchmark:png rake benchmark:html rake benchmark:ansi rake benchmark:format_comparison # Run all benchmarks (takes ~1-2 minutes) rake benchmark:all ``` ## Results Storage **All benchmark results are automatically saved to `benchmark/results/` as JSON files.** Each run creates timestamped files with the format: - `ips__YYYYMMDD_HHMMSS.json` - Performance data (iterations/sec, comparisons) - `memory__YYYYMMDD_HHMMSS.json` - Memory allocation data This allows you to: - Track performance changes over time - Compare before/after optimisation results - Share baseline results with the team - Generate summary reports comparing different runs **Note:** The `results/` directory is gitignored - results are local to your machine. ## Benchmark Types All benchmarks run **two modes**: ### 1. End-to-end (PRIMARY METRIC) Measures the complete user workflow: `RQRCode::QRCode.new(data).as_svg` - **What it shows**: Total time users experience, including rqrcode_core generation - **Why it matters**: This is what users actually do - reflects real-world performance - **When to use**: Track overall improvements, compare formats for actual usage - **File naming**: `ips_e2e_*_YYYYMMDD_HHMMSS.json` ### 2. Rendering-only (DIAGNOSTIC METRIC) Measures only export performance using pre-generated QR codes - **What it shows**: Export format performance in isolation - **Why it matters**: Helps identify which export method needs optimisation - **When to use**: When optimising export code, isolating rendering bottlenecks - **File naming**: `ips_*_YYYYMMDD_HHMMSS.json` **Key Insight**: End-to-end benchmarks often show QR generation is the bottleneck (all formats perform similarly), while rendering-only benchmarks reveal differences between export formats (SVG is ~4x slower than HTML due to algorithmic complexity). ## Available Benchmarks Each benchmark tests 3 QR code sizes (small, medium, large) with realistic data and runs both end-to-end and rendering-only modes: ### Format Comparison (`benchmark/format_comparison.rb`) Compares all export formats head-to-head using a medium-sized QR code. - **End-to-end metric**: Which format is fastest for users? (Usually ~same due to generation overhead) - **Rendering-only metric**: Which export format is most efficient? ### SVG Export (`benchmark/svg_export.rb`) Tests SVG path mode (most common use case) across different QR sizes. - **End-to-end metric**: Total time for generation + SVG export - **Rendering-only metric**: SVG rendering performance in isolation ### PNG Export (`benchmark/png_export.rb`) Tests PNG with default sizing (most common use case) across different QR sizes. - **End-to-end metric**: Total time for generation + PNG export - **Rendering-only metric**: PNG rendering performance in isolation ### HTML Export (`benchmark/html_export.rb`) Tests HTML table export across different QR sizes. - **End-to-end metric**: Total time for generation + HTML export - **Rendering-only metric**: HTML rendering performance in isolation ### ANSI Export (`benchmark/ansi_export.rb`) Tests ANSI terminal output across different QR sizes. - **End-to-end metric**: Total time for generation + ANSI export - **Rendering-only metric**: ANSI rendering performance in isolation ## Understanding the JSON Output ### End-to-end Results Files named `ips_e2e_*` contain generation + export times: ```json { "label": "All Export Formats (end-to-end)", "timestamp": "2025-12-17T21:46:51+00:00", "ruby_version": "3.3.4", "results": { "svg": { "iterations_per_second": 16.71, "standard_deviation": 0.00, "samples": 84, "comparison": 1.09 }, "ansi": { "iterations_per_second": 18.13, "standard_deviation": 5.50, "samples": 91, "comparison": 1.0 } } } ``` ### Rendering-only Results Files named `ips_*` (without `e2e`) contain export-only times: ```json { "label": "All Export Formats", "timestamp": "2025-12-09T19:49:35+00:00", "ruby_version": "3.3.4", "results": { "svg": { "iterations_per_second": 186.71, "standard_deviation": 0.54, "samples": 18, "comparison": 7.28 }, "ansi": { "iterations_per_second": 1359.48, "standard_deviation": 0.22, "samples": 135, "comparison": 1.0 } } } ``` **Common fields:** - `iterations_per_second`: Higher is better (more iterations completed per second) - `standard_deviation`: Lower is more consistent (percent variation) - `comparison`: Multiplier vs fastest (1.0x = fastest, higher = slower) - `samples`: Number of iterations run for measurement ## Test Data Benchmarks use 3 representative QR code sizes: - **small**: GitHub URL (~40 chars, typical use case) - **medium**: Lorem ipsum sentence (~100 chars) - **large**: 500 characters (stress test) ## Interpreting Results ### What to track over time: 1. **End-to-end i/s**: Are users getting faster overall? (Includes rqrcode_core improvements) 2. **Rendering-only i/s**: Are export methods getting more efficient? 3. **Relative comparisons**: How do formats compare to each other in both modes? 4. **Memory allocations**: Are we creating fewer objects? ### Interpreting Performance Changes **When end-to-end improves but rendering-only doesn't:** - Improvements came from rqrcode_core (QR generation algorithm) - Export methods haven't changed **When rendering-only improves but end-to-end shows modest gains:** - Export methods got faster, but generation time dominates - For small improvements, generation overhead masks rendering gains **When both improve proportionally:** - Changes benefited the whole pipeline ## Latest Benchmark Results **Last Updated: 2026-01-08 14:09:06 UTC** **Ruby Version: 3.3.4** **Platform: Apple M-series** **rqrcode_core version: 2.0.1** ### Quick Reference Baselines #### End-to-end (Generation + Export) - Medium QR Code *User-facing performance - what matters for real-world usage* | Format | Iterations/sec | Std Dev | Samples | Slowdown vs Fastest | |--------|----------------|---------|---------|---------------------| | HTML | 34.1 | 2.90% | 171 | 1.00x (baseline) | | ANSI | 34.1 | 0.00% | 171 | 1.00x (same-ish) | | PNG | 33.6 | 0.00% | 171 | 1.01x (same-ish) | | SVG | 32.2 | 0.00% | 162 | 1.06x (same-ish) | **Key Insight**: All formats now perform very similarly (~32-34 i/s) because QR generation dominates the time. SVG optimisations brought it in line with other formats for end-to-end usage. #### Rendering-only (Export Performance) - Medium QR Code *Diagnostic metric - shows export efficiency in isolation* | Format | Iterations/sec | Std Dev | Samples | Slowdown vs Fastest | |--------|----------------|---------|---------|---------------------| | HTML | 1,876 | 0.70% | 9,464 | 1.00x (baseline) | | ANSI | 1,310 | 6.20% | 6,615 | 1.43x | | PNG | 840 | 4.90% | 4,214 | 2.23x | | SVG | 424 | 1.70% | 2,150 | 4.42x | **Key Insight**: SVG rendering improved from 184 i/s to 424 i/s (+130%) after optimisations. The gap vs HTML reduced from 10x to 4.4x. Remaining gap is due to algorithmic complexity (edge detection + path tracing vs simple iteration). ### Performance by QR Code Size *Note: Higher iterations/sec is better; lower std dev is better; lower slowdown is better* #### SVG Export (End-to-end) | Size | Iterations/sec | Std Dev | Slowdown vs Small | |--------|----------------|---------|-------------------| | Small | 102.7 | 1.00% | 1.00x (baseline) | | Medium | 32.2 | 0.00% | 3.19x | | Large | 10.9 | 0.00% | 9.41x | #### PNG Export (End-to-end) | Size | Iterations/sec | Std Dev | Slowdown vs Small | |--------|----------------|---------|-------------------| | Small | 102.5 | 1.00% | 1.00x (baseline) | | Medium | 33.6 | 0.00% | 3.05x | | Large | 11.4 | 0.00% | 9.00x | #### HTML Export (End-to-end) | Size | Iterations/sec | Std Dev | Slowdown vs Small | |--------|----------------|---------|-------------------| | Small | 109.2 | 3.70% | 1.00x (baseline) | | Medium | 34.2 | 0.00% | 3.19x | | Large | 11.6 | 0.00% | 9.40x | #### ANSI Export (End-to-end) | Size | Iterations/sec | Std Dev | Slowdown vs Small | |--------|----------------|---------|-------------------| | Small | 108.6 | 0.90% | 1.00x (baseline) | | Medium | 33.9 | 0.00% | 3.21x | | Large | 11.5 | 0.00% | 9.42x | ### Memory Allocations *Note: Lower is better for both metrics* | Format | Total Objects Allocated | Total Memory (MB) | |--------|-------------------------|-------------------| | HTML | 451 | 18.0 | | PNG | 357,676 | 23.1 | | SVG | 2,157,951 | 113.8 | **Key Insights:** - **End-to-end**: All formats now perform similarly (~32-34 i/s) - SVG optimisations closed the gap - **Rendering-only**: HTML is fastest (1,876 i/s), SVG improved significantly (424 i/s, was 184 i/s) - **SVG improvements**: +130% rendering speed, 71% memory reduction after 2026-01-08 optimisations - **Optimisation priority**: Improvements to rqrcode_core have biggest impact on user experience - **Format choice**: For high-volume rendering, HTML/ANSI are still faster but SVG is now competitive - All formats show 3-10x performance degradation as QR size increases - Memory usage: HTML uses 6x less memory than SVG (was 22x before optimisation) ## Notes - **Primary metric**: End-to-end results reflect real user experience - **Secondary metric**: Rendering-only helps diagnose where to optimise - Focus on relative comparisons, not absolute numbers - Results vary by system (CPU, Ruby version, rqrcode_core version) - Run benchmarks before and after making changes to measure impact - Full suite runs in ~2-3 minutes (doubled due to two modes per benchmark) - When rqrcode_core updates, expect end-to-end improvements even without changes to this gem �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/ansi_export.rb��������������������������������������������������������������0000664�0000000�0000000�00000002157�15130003674�0017771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "benchmark_helper" BenchmarkHelper.section "ANSI Export Benchmarks" # PRIMARY: End-to-end benchmark (generation + export) BenchmarkHelper.run_ips_e2e("ANSI Export") do |x, qr_data| x.report("ansi_small") { RQRCode::QRCode.new(qr_data[:small]).as_ansi } x.report("ansi_medium") { RQRCode::QRCode.new(qr_data[:medium]).as_ansi } x.report("ansi_large") { RQRCode::QRCode.new(qr_data[:large]).as_ansi } x.compare! end # DIAGNOSTIC: Rendering-only benchmark (isolates export performance) BenchmarkHelper.run_ips("ANSI Export") do |x, qrcodes| x.report("ansi_small") { qrcodes[:small].as_ansi } x.report("ansi_medium") { qrcodes[:medium].as_ansi } x.report("ansi_large") { qrcodes[:large].as_ansi } x.compare! end # Memory Profile - ANSI Export BenchmarkHelper.run_memory("ANSI Export") do |qrcodes| 50.times do qrcodes[:small].as_ansi qrcodes[:medium].as_ansi qrcodes[:large].as_ansi end end puts "\n✓ ANSI benchmarks complete" puts " - End-to-end: Full user workflow (generation + export)" puts " - Rendering-only: Export performance in isolation" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/benchmark_helper.rb���������������������������������������������������������0000664�0000000�0000000�00000014136�15130003674�0020727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require "bundler/setup" require "rqrcode" require "benchmark/ips" require "memory_profiler" require "stackprof" require "json" require "fileutils" require "time" module BenchmarkHelper # Test data of varying sizes QR_DATA = { tiny: "Hi", small: "https://github.com/whomwah/rqrcode", medium: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", large: "A" * 500, xlarge: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 20 }.freeze # Create QR codes for benchmarking (rendering-only benchmarks) def self.qrcodes @qrcodes ||= QR_DATA.transform_values { |data| RQRCode::QRCode.new(data) } end # Get raw QR data for end-to-end benchmarks (generation + rendering) def self.qr_data QR_DATA end # Get results directory def self.results_dir @results_dir ||= File.join(__dir__, "results") end # Ensure results directory exists def self.ensure_results_dir FileUtils.mkdir_p(results_dir) unless Dir.exist?(results_dir) end # Get timestamp for filename def self.timestamp @timestamp ||= Time.now.strftime("%Y%m%d_%H%M%S") end # Save results to JSON def self.save_results(name, data) ensure_results_dir filename = File.join(results_dir, "#{name}_#{timestamp}.json") File.write(filename, JSON.pretty_generate(data)) puts "\n💾 Results saved to: #{filename}" end # Run IPS benchmark (rendering-only, uses pre-generated QR codes) def self.run_ips(label, warmup: 2, time: 5, &block) puts "\n" + "=" * 80 puts "IPS Benchmark: #{label} (rendering-only)" puts "=" * 80 results = {} report = Benchmark.ips do |x| x.config(warmup: warmup, time: time) block.call(x, qrcodes) x.compare! end # Extract actual metrics from the report report.entries.each do |entry| results[entry.label] = { iterations_per_second: entry.stats.central_tendency.round(2), standard_deviation: entry.stats.error_percentage.round(2), samples: entry.measurement_cycle } end # Calculate comparison ratios (fastest = 1.0x) if results.any? fastest_ips = results.values.map { |r| r[:iterations_per_second] }.max results.each do |_label, data| data[:comparison] = (fastest_ips / data[:iterations_per_second]).round(2) end end # Save results with actual metrics save_results( "ips_#{label.downcase.gsub(/\s+/, "_")}", { label: label, timestamp: Time.now.iso8601, ruby_version: RUBY_VERSION, results: results } ) report end # Run IPS benchmark for end-to-end workflow (generation + rendering) def self.run_ips_e2e(label, warmup: 2, time: 5, &block) puts "\n" + "=" * 80 puts "IPS Benchmark: #{label} (end-to-end: generation + rendering)" puts "=" * 80 results = {} report = Benchmark.ips do |x| x.config(warmup: warmup, time: time) block.call(x, qr_data) x.compare! end # Extract actual metrics from the report report.entries.each do |entry| results[entry.label] = { iterations_per_second: entry.stats.central_tendency.round(2), standard_deviation: entry.stats.error_percentage.round(2), samples: entry.measurement_cycle } end # Calculate comparison ratios (fastest = 1.0x) if results.any? fastest_ips = results.values.map { |r| r[:iterations_per_second] }.max results.each do |_label, data| data[:comparison] = (fastest_ips / data[:iterations_per_second]).round(2) end end # Save results with actual metrics save_results( "ips_e2e_#{label.downcase.gsub(/\s+/, "_")}", { label: "#{label} (end-to-end)", timestamp: Time.now.iso8601, ruby_version: RUBY_VERSION, results: results } ) report end # Run memory profiler def self.run_memory(label, &block) puts "\n" + "=" * 80 puts "Memory Profile: #{label}" puts "=" * 80 report = MemoryProfiler.report do block.call(qrcodes) end report.pretty_print(scale_bytes: true, normalize_paths: true) # Save memory results memory_data = { label: label, timestamp: Time.now.iso8601, ruby_version: RUBY_VERSION, total_allocated_memsize: report.total_allocated_memsize, total_retained_memsize: report.total_retained_memsize, total_allocated: report.total_allocated, total_retained: report.total_retained } save_results("memory_#{label.downcase.gsub(/\s+/, "_")}", memory_data) report end # Run stack profiler def self.run_stackprof(label, mode: :cpu, &block) puts "\n" + "=" * 80 puts "Stack Profile: #{label} (#{mode} mode)" puts "=" * 80 profile = StackProf.run(mode: mode, interval: 1000) do block.call(qrcodes) end StackProf::Report.new(profile).print_text(limit: 20) # Save stackprof results stackprof_data = { label: label, timestamp: Time.now.iso8601, ruby_version: RUBY_VERSION, mode: mode, samples: profile[:samples], frames: profile[:frames].map do |_frame_id, frame_data| { name: frame_data[:name], total_samples: frame_data[:samples], file: frame_data[:file], line: frame_data[:line] } end.sort_by { |f| -f[:total_samples] }.first(20) } save_results("stackprof_#{label.downcase.gsub(/\s+/, "_")}", stackprof_data) profile end # Convenience method to run all profiling types def self.profile_all(label, &block) run_ips(label, &block) run_memory(label, &block) run_stackprof(label, &block) end # Helper to print section header def self.section(title) puts "\n\n" puts "#" * 80 puts "# #{title}" puts "#" * 80 puts "Timestamp: #{timestamp}" puts "Ruby Version: #{RUBY_VERSION}" end # Helper to format bytes def self.format_bytes(bytes) if bytes < 1024 "#{bytes} B" elsif bytes < 1024 * 1024 "#{(bytes / 1024.0).round(2)} KB" else "#{(bytes / (1024.0 * 1024)).round(2)} MB" end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/format_comparison.rb��������������������������������������������������������0000664�0000000�0000000�00000002034�15130003674�0021152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "benchmark_helper" BenchmarkHelper.section "Format Comparison Benchmarks" # PRIMARY: End-to-end benchmark (generation + export) - what users actually do BenchmarkHelper.run_ips_e2e("All Export Formats") do |x, qr_data| data = qr_data[:medium] x.report("svg") { RQRCode::QRCode.new(data).as_svg(use_path: true) } x.report("png") { RQRCode::QRCode.new(data).as_png } x.report("html") { RQRCode::QRCode.new(data).as_html } x.report("ansi") { RQRCode::QRCode.new(data).as_ansi } x.compare! end # DIAGNOSTIC: Rendering-only benchmark (isolates export performance) BenchmarkHelper.run_ips("All Export Formats") do |x, qrcodes| qr = qrcodes[:medium] x.report("svg") { qr.as_svg(use_path: true) } x.report("png") { qr.as_png } x.report("html") { qr.as_html } x.report("ansi") { qr.as_ansi } x.compare! end puts "\n✓ Format comparison benchmarks complete" puts " - End-to-end: Full user workflow (generation + export)" puts " - Rendering-only: Export performance in isolation" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/html_export.rb��������������������������������������������������������������0000664�0000000�0000000�00000002157�15130003674�0020003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "benchmark_helper" BenchmarkHelper.section "HTML Export Benchmarks" # PRIMARY: End-to-end benchmark (generation + export) BenchmarkHelper.run_ips_e2e("HTML Export") do |x, qr_data| x.report("html_small") { RQRCode::QRCode.new(qr_data[:small]).as_html } x.report("html_medium") { RQRCode::QRCode.new(qr_data[:medium]).as_html } x.report("html_large") { RQRCode::QRCode.new(qr_data[:large]).as_html } x.compare! end # DIAGNOSTIC: Rendering-only benchmark (isolates export performance) BenchmarkHelper.run_ips("HTML Export") do |x, qrcodes| x.report("html_small") { qrcodes[:small].as_html } x.report("html_medium") { qrcodes[:medium].as_html } x.report("html_large") { qrcodes[:large].as_html } x.compare! end # Memory Profile - HTML Export BenchmarkHelper.run_memory("HTML Export") do |qrcodes| 50.times do qrcodes[:small].as_html qrcodes[:medium].as_html qrcodes[:large].as_html end end puts "\n✓ HTML benchmarks complete" puts " - End-to-end: Full user workflow (generation + export)" puts " - Rendering-only: Export performance in isolation" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/png_export.rb���������������������������������������������������������������0000664�0000000�0000000�00000002132�15130003674�0017614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "benchmark_helper" BenchmarkHelper.section "PNG Export Benchmarks" # PRIMARY: End-to-end benchmark (generation + export) BenchmarkHelper.run_ips_e2e("PNG Export") do |x, qr_data| x.report("png_small") { RQRCode::QRCode.new(qr_data[:small]).as_png } x.report("png_medium") { RQRCode::QRCode.new(qr_data[:medium]).as_png } x.report("png_large") { RQRCode::QRCode.new(qr_data[:large]).as_png } x.compare! end # DIAGNOSTIC: Rendering-only benchmark (isolates export performance) BenchmarkHelper.run_ips("PNG Export") do |x, qrcodes| x.report("png_small") { qrcodes[:small].as_png } x.report("png_medium") { qrcodes[:medium].as_png } x.report("png_large") { qrcodes[:large].as_png } x.compare! end # Memory Profile - PNG Export BenchmarkHelper.run_memory("PNG Export") do |qrcodes| 25.times do qrcodes[:small].as_png qrcodes[:medium].as_png qrcodes[:large].as_png end end puts "\n✓ PNG benchmarks complete" puts " - End-to-end: Full user workflow (generation + export)" puts " - Rendering-only: Export performance in isolation" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/benchmark/svg_export.rb���������������������������������������������������������������0000664�0000000�0000000�00000002352�15130003674�0017633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "benchmark_helper" BenchmarkHelper.section "SVG Export Benchmarks" # PRIMARY: End-to-end benchmark (generation + export) BenchmarkHelper.run_ips_e2e("SVG Export") do |x, qr_data| x.report("svg_small") { RQRCode::QRCode.new(qr_data[:small]).as_svg(use_path: true) } x.report("svg_medium") { RQRCode::QRCode.new(qr_data[:medium]).as_svg(use_path: true) } x.report("svg_large") { RQRCode::QRCode.new(qr_data[:large]).as_svg(use_path: true) } x.compare! end # DIAGNOSTIC: Rendering-only benchmark (isolates export performance) BenchmarkHelper.run_ips("SVG Export") do |x, qrcodes| x.report("svg_small") { qrcodes[:small].as_svg(use_path: true) } x.report("svg_medium") { qrcodes[:medium].as_svg(use_path: true) } x.report("svg_large") { qrcodes[:large].as_svg(use_path: true) } x.compare! end # Memory Profile - SVG Export BenchmarkHelper.run_memory("SVG Export") do |qrcodes| 50.times do qrcodes[:small].as_svg(use_path: true) qrcodes[:medium].as_svg(use_path: true) qrcodes[:large].as_svg(use_path: true) end end puts "\n✓ SVG benchmarks complete" puts " - End-to-end: Full user workflow (generation + export)" puts " - Rendering-only: Export performance in isolation" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/bin/����������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0013722�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/bin/console���������������������������������������������������������������������������0000775�0000000�0000000�00000000526�15130003674�0015315�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env ruby require "bundler/setup" require "rqrcode" # 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__) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/images/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0014417�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/images/ansi-screen-shot.png�����������������������������������������������������������0000664�0000000�0000000�00000245616�15130003674�0020325�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR�����#���n���sRGB���� pHYs�� �� ���YiTXtXML:com.adobe.xmp����� 1 L'Y��@�IDATxifY߹<\w.LۦHZ |@: 'XtE 6DB7|IH(Bc_wTsթSg>׻󞪺WW{LYϚ^{biyy֭^Uoy\Oν w4S3SO}iO~ԗiv4p/5\R?T= ձc'c+K׫՛k_--TNSjuz555YmllU3N599YMMOW{^5V511YM>1QW{:}۩ݨ;Քj^^!ȴxmVU⿧ {Ĕ>Z�3dUmV'NU+W}4)yvww4vwvU)eSfݝjW<̾hQqD#В"[RZȹX-<:C8DI4`'.J#Qn|R/UP]70UA}wBӔQW/|ԳS8`p]:2 ^#Ri6J H&q8.٘ufY-l7(CK\`$|o9V-!YS6jdo|[տ:~x5 zU3$̔*iz7c'NV kׅ=Q,/Uo(jss]8#}}c#!Б,/T.]jfyںe32kaQOImi_Ʋ rS WǰATa�/jw]u23յ՛zB)FaV,la #fW?f\tB%*# tWy0Bva@2ƋVnK}:aQ}ZA 6:F[":oG>M|).++ ɈtbV<ۼ,<cVڋlԠ7aM/D M b]W8VX~aF!gvx#;eq"CMiʛ&Yv/diRR8_.W^ H JkbkJ?a_5MjTn޼U8%Z h4b5}�M *oBl գidgOg޽GÚD-j�ܤ: KcXf.#q`¿y/7y*߸ w*twTuT.bDqfuҥjkSSl҈q!08?�Htb4�z+WG4$)mi&S"׮y]=mڠ� 1N!C٠(kj5: V'^#IU�x0MW׮وYaS(XV5e )ViS)&�ɟr";#Y-Og72?sA]:rvlG6Ւ<h(=T [ŵʏ аc�h_R skMu4Y#رig-~^4j0% 1(WrdLll >n3>'�e!g gs-fPȦt;jJ\0H:"?:W!{@(Z yBCӑ't<ǁ[CQC50KR6s]ҴӱfmWJ/,U995Wm177XwL5㮙yVsZ/iy٘ A t~6܈ ?#;hf9z�]t9OHZ8Ơ2dp9:|"g504-t"[,Ej{t(J ]A!O˷3Q剙H@#4HT:!)aXQڄvϪvɓeƴOp9B;svY`uN\Sծv//-V3?uTlhl$4LA`taJ+ʉဋY�Hr,`@۠p Lma׸I'6'|[,_Ykmj$46F}ՠcTWݖpݗN|c(~g #z"cƌ r9Qͣ+O ]޸O*a,g?l)t,qp)iZ>@f >8t(Ψp; L/ [” /ӿ~q4 v7t~|S[7Ԑ +J~juv5EuF5;ׯ /nkw+>#�a8ðה7NyǁIkO_YG's;k),?WozuIGp4 =A yV>gpo 77 {7u95(SXlx1P;"7{C?hthp=ewZ׏M8f4#;P0%Mt8,ePXGhpG mN(Hm+zэfFfȨ-FĢK�GhKb>33[jN878f8{!p Gwq?a*Da"Dxdm4%cv"jw�0w؛H=QyG%R*ِꎱóԯu :(ٚkQd}uA913E;͋9rEi|< e+nwtRÄa2$T }3>RddleNtjtr"?uQOՕ'?d&з 9 o-r7z)1I(H:pG ѤEFͼd &hHF"Y:u2aIK:(Gp_sħ4ʲ8n<%}ٕIpMYF-ã` ȕG{Ԗ4&S jJH;!6`ܮN77.Cg̵,oWs8n];+\k: q^&$z@m%2$,C}-eˆT2TnTz spIs8K:K2\nC Fe'\Mxqh~74Kܒ>ě; fHuyƉ]n`7F^C [jpTi>oY2\P*K>H'<>vS=h 3P _;* ;u'#zTS*pd\d8Ң㊄k:gӈx_򻮕7C#i MY0Fc _E4ܐEdCF1A'Rfzl@>�x*f-D&J:zF " c=; y9˲Kie=I!.t:;-c5Sƍ _N% (]. Р:i{)Dê<?Bx[a9L9e7,jV[L`0BlxNQr &rɒ|KG G9g#Nr_S-8AhC֬|NÑQbڲ e9Ӹ~MP)?=^j96ڄH>ԇ Uvj٭0m%]e`y .!b:Ӎ>BS4^`h(exW;_ SӺ8 [j(+10ڭ Ȩ,@aNK0poف<,Jtuq%W\*ߘʊyϞ=[-. .w/V}J\,N6f4u-?Zt~إa NRIq~K3?QbvlQ``M|pf�OSvzJn0ɨ-N>"l.YJ9R\R/vL(2 kpf ^T\M0 6|KچQȖwd`")j~!W-,Ӊ'.e78FJSr,^Iyѧ범[L#9ՅiP"\I'S[·e)K{0>-AĈ>UJR҂gf(Z4 ]LcTgwrƚ~kǎ-T~]0T冮H `:=RҘ^4< ΀[`7D :~ФgNOсFf|KR2#:$u4p~^ܗ^,[*Ch4u?5*8.Q[Մ!#РLK>h4[ Y#UմJC$!%A:Qǁ9smW˴2}dSƷ^QQGѹWѢ.5BMr˖龦+ˋ5k>@ 5rH)*FMpBU W`˳fVKz!˄m5d{Bcר{L2\_cܸ~Oh4::B<,i62KtEnPPݺ;ϋ嗗/_7xcOykl�=(5r~@Jpz18x]<38ϰMV!P^CunjexL#Y G-W _dwOb~[ i]n){-6.^hrIr/^`NNHa M� ^5Dq JFvFm5Zˀ= 4^U>yqipx>_ƖcmMmi&zEdpSZ< v.3:5ʋLG't#ߦMوFRpWqA3S3$tOz.aCaL7cCى=3vG(ixLDЉև!W]॰{w˕i|K!StqNHMi��1i|0 [BSV S3=o He0axC2,[)G(BKXPFQsIQQW'eAd\yz!`SMo 8ajyg !G(c-AACZqv2,W̸\R+d*> KC V1`pA?ol&{,-u鱳d8#e#%<R膛vt`0(4(hњFtԅO)ECaQj�'*JC{TSQƗѹ`iHFM#ﱿﺁS lM+$O逇8hj'}F,7rC2qoަ*N nkvvH~?ڙ?\{f_h({4kX 28D0/t]K>H~83҃N=B=p#Y8V\x( IzwGzH ]ub4ԄN@N>̘]yHKg}NKK! {> )Uz\׾Z[q+7y9vt>!rXѐgv7"e;jBIt &|D18OV׳dvkL#h ~YI8رjjA%;U|%|fIlܴ͊*j]}3`r>Q-;7oV]_я~x}̳{1 mPjcGG}z&.Ge1?_οH+|S? 9'yW 3jP8r=GLN_/տQM $Z^3ϝo(oF׼6>ޱ8]hs ZgW[Q w7f7)f4Y4M~Z{Ƚl~-iJ`?].LbpWҋX<]o$<̂N(VH*.w=̩hγuSԓs窛h.s;Y}ҳzZۜf٬k@:ܻfl!!E]c2|5BKp> %>|zqIg0ĉ?E}+_Jg5qJ)e& .eI ?|y KY_uyկjxlr޿,=Ƴҵ'y՜Y 8)5ٹf7h?Es=X\'vK19sڪͪ]u24K{}Z*zMƬxЉz_u Ϋ-G7M9BːfLS9!m_Qyb4ql!g)89"yK/l?M o}Ѽ[vA.:k+0jx|ԾF&ma\NL8WXQz[4nnqˁU2S36)(nɠ8 ;ikW.;67;阎 r씾_o o)J>W蚫#Ϩpu=/.9bJ͸meTW^3)=^:h@ʍM[ߤ;Npe8,4r> &IR4LN8ptlUlJ65Rn6 7K_<'COp|z]/lX8%;<$s̎H"{Fn_w .yJx+/le^dGkܮ7Ty^R#t{u9΁ Z><٬䘕xRs[_qp�tpZ7|:yJ2|cH4ځx Z}gx>ׄ+ >k5wfSMUMm):p f9qZGCs86{L=H;=583#8'hQ kaJnɩsnMeWCDBAQ 6ETuE#7GDv]1Q#E.xN8?OWz}t蠸4`T/t+A-/'$e#U$ uxW—FG!vBojg@幧`uyBk^/]̍. #,x-Gc Hԋ Vy>�ã$3ZJhBX=m@9]x/KFSڤrǏY�O8sS-Fǃ<چN…1è#709#zgFÚ'ڪ4cXEqAg ‹%dXYgf%A҉QVt8̗ WFfסR^s[L ڗ6&mѡ0aR1A$t<[4rLh9D-ϧmx(Kz٦>W.>ܶf:[ la̬~>\'?(8`^ѹbGqB5c?3^Q9 GLv3{ ԓ(p}.: !h.Wb;M \"^`]C?ӱtЮ#<;>84XDc۟ɟeGc:+_h^[>/SOE6uQJ :@_rS.#O;@�'I@N:Ft+?4\fG8w8dazZJkOHَeQǵ 2Qh|Q6@ǟxױNe4òuЭf+lA7\ =\V8A EE_i$ș:. %g/B:%шn00 v(n(2mk~١lh|~7I'”Xoow<,g64�̏?^Zӧ^CѝҹߍƎe΍ؘɩӤc ,]h9-ZS(-2IoRk/ׁqK?Gt p) za#4̅v/e|3ӼZ^/+._ֲe*R?ufj8(iwjoM/yaYT8{<QE^DzdCdP ;>@KtÖw}a@ơm:wwy 5AK7㸒0wWPi\ �ɺvc-^**tĻ _yօ틗˸ntcᄳoK>#QP_ZW!Kze5~)u�Ȭo My1ghTp] ]|.~wt}#^!7l($<~ ;Rt60|~,o2+}K 9JY^s.z4 c%*j8^WtvI뚕iݣy]y%dW'Ϗw`|p0Q8Iu2b_iSGvqS?~W hT?+Q8ZmkGHtEZA Y܉0f͗[c3/Wl/"4Cᇽ0�iSdfH޿!iH끝Y6mйV%2gM."q?{\bLAN;ı)7+7V?iU/gwf>qBNi&m޲Ջqx+w6[ښdLׇ7r{i]kD8*-8i0t`K%Ë BXlc'+ "ߪ3XQ< 8i4pF[^řO-_IAJ2c6 cqkЇ>a1>c??m w[o6CЯ,A}8 +^tY3/V+GLOoP%~,@6<0<Ƭk`)G9nfyxiGnЉetYB;阦G۱y<>ӺIi`$WLABf z > XV*'Y18vI95ӓgO>cvJ(e? 7R7H)-|-6`YG:0Տ؏5ǒT>S7c0Lg]'8`dG'cW>U^u(Q~\ںJ=MG{/ޮG>NCTw+Gı,^뿰5%:F.CAn״nD$\DR$<48]Fܹ| ŴOX9F2*; q_2ـJ=f0"v2Z4dO|,is|saJ_u<fkU7Mg{oyLȥ9>J?}k}}ܹQ8.W7'$l;K.xYxʹK\ix:=zsedb!^1HSa7=.8X!>Ft�퀤aS6b(ghatNpq�:3w9@SYPxMH#ߑ݅ Tï' &Lhqۤv}q8+MkqUD>fȊ1VRHt!G|Ep ԃtoKWqoAu_ֱ|}xy=ԯZ>a#Nf\~|i8xx\( u#\K`8:&/#sBh+`0b&aC6')/2coz^9GRLqtxw!AD= y7.�$[ݸrak?+_xneْnbM}SwC68¢a k(g\ &F# N`&t+Ty㢀9U\4vxn_}y*̢V_J�5ȃ%7PMÅ G:auDQ] O5_?E zA.X#NIK2\..tM /W?x_օ+ObN`F":uUZt{'YlyUx9efv>>quʥ|#D77hl4+qYv΂ , FQ6Dϛ*�xuxK rwPl4BNj~*Kd0\IH>B.ebx)k&qMx: z%,q>U7L+LZtOEwQN1}섂,9M[ !JT v%i')]'%q]T\(\.̷%ݶ.R%*ܼ.XYڍCX5vJ WZ}|\4$Nť7t= ɓ'bt- *ޣV}FzM5\bx`2]}v4'| _6wӽzj(8 ] fK%7nlꩲcq= 5~uu-]zzT'O/.j誣I}U եC8VRܾ̳V~mo̳Tܗ]\ӺEj(U<:C:ʱ҇?ǕVI `90sDd__l7)9FX �ZY5 H44QGyh5FkY@BO:pNvF_S a.Pdv Gg$ vU>Ǒ {eIWAKi'cROdwA17< x{D)nL9c %[hϞ*̂FG5t c *uƍ(v^q=+|`&01ìCjbzcJ$n{aj3*)/ fKkxB›noqf=%S:I͐V>SR;蠲Q^Wtsd!!ǍS3oF>͸b^=ғsߔ*-5奜8I 5n Jݷ0 j ZVƻ\yPh?0 bqpA ^JL˝GCS E8zD 8uѼB7FL[ʲ«)WPZ7# |*B 0i]uy-GmÏw66^I;VWתzZUkÊLxF%Ӫޯ+j2,.zz5c\=Ĕ A3hTW#kj \Ef(ƀ1LOhѐN *g{ӲΠBDcaڽ-9NI}RٸRCrMaiED_KQWc5}gxWtϜnUg i]=t|΄;ܘ9p0lgDm3gCoC+qiA)4 XgLhǧ^4d2d`52woSc<|K}ZpN_<:̯~zQ]0^UavoMMզ5Rk4^FIs4^K>=mve|u5/k8Oxq8hPq; ) dHtov)lr(FؔM5fq#W@3DZÛrRvjy>'kUYG([Eg`?9er#<2,G)u ~r�M%x RJl.ć8;;!Q)!i 0-wE]zZU@i4S휞J< aiͨy}9uBͦA~>Q(珟¢r!/:TiC8gYÓFtreȤq勨d!U68LGg^03!.[Y) �N:Sh ̙42S!blGǾ�|/t|+ėS**@e'Ca 2*H2ӘGgIƒ<NA7[UnFFׅ؁GF OHc+F:Q4oh ɟ5yM=#}M y:a$8cr4o4l䐬!']o\hsZ}-B2vNSrlEowJ;FڕLKE6>t_ـ7V&~| h6Bm Sj>ʹ/}0uM*U2-Xd׶h|IfJY|S5QNa$ėźvFLٳM#[d|h◪O~߭~'~z^sugDbR;F`styO=[['N#{2:: 1:_ɆC?CoEQ=p%c~:u&*|6W #-94dBBB MFBc7$B)*g ߚC# A'2u~<)~᠇A<:a\6y�FW3za@L0^S��@�IDAT=qT!z%5%3pI|=e5(�<8:Sjooq*5:CX/-`ffW1'+bqyZ%]sqp0);ڷaZhj&^:u lP3wx&](< _IQcuZ,8b:MPg>.駟xFʀi@݄Oo> <W̋ 3JsM(8FFa7 IzM Im.3zl+IsI-Lw$ϸK2B>X#dЍ( C8h>i2B=ј#7 egoQQ;mo՚0^\DlXa(tA3c@ vKZ#@-gvLԲv kWzQ0K,/p�%lt48/p,Y7ԁ u8N!5Pe?uf;Ѡw\-lzb/fSS!aKWI&4̆̋?N~X:n?A Fb.h(oA YrL7+9wZA;Lw#-2<_8d.qƕā(W+^: ?VLgy$.[ћ1>5*%օGUI}&8 \=z<.Fĭ`NHΆtۊMI${46G78S4=e>1z7A]y|gb~]b/N'o1^ ,CfL%N$b]>3ZOLݺ syA_5j:il:IkbTAI4vS"rEc L#IW^7,]]?@Ѕ3�0C꧟,Q9׺JSBw>Ї!' �E8ǏGzI)CQ.aңL*xkǢ]kEMJB4Tq ܥNAh t£d;'Hr1//loQA֩[Ŧb@E+L>6 ~8Ns᱌~X׾55lΝ�;\VIǑ3 mO_E*xҐ}Ŕg}66-IYip 8"z*NG] I>RLKQ.lB"9u=p4pw2) ]vG]EV<f"8LJ`2eӓ0rѫE=3.\]vsݚ.e(q SOC8%ly2) *u2`dh0\)ʂpʌjB; 9nϪM2?N+OI8% ٌ;ߩ4~v$qS~Kh#n^8(z\0 zw"Z a\RhQ )wXG_t\&zwj:$̹oIs @x\Cֳ#׺Waܬm4(AS5y}KAXw1}#/Ɖ\Q(5oЇhS?l*#k}?pҷ AN`ix@_u'u[LK?ݛync~&"zWI;7ے2n}kcaQrM(~ETcHQ9�H1]-`Uǿy,hao+'I "0xm@3M㡢{Hhu v6S |+ xz߆"|9*Q!Q@ zAS`w EF9b2 C?hnЗ{ /h/i@=+ S83ʛ7J?3 ׿(1ٳq"!Rv8@tytsz̙/Tl㮏+QײsH-42 N^(\hZS LRL!}V0vN: !2tCCၷ0rF/~ }IevEG~z[ҜC}sk뺏}cz%'0t:::.? }<_z?VNP.x }q\mCKD%?'!7eKn-<.vI1CIS3&|G/a a0*Fwtl[ xV뙐Qbک5i" q9c:XZ9=8 5<32#Pʚ2yFeݵ(8T-?FFm̆ǗyD ޼QGӥ# |b>pt sz Ӌ 5u|IzaqנGLEY}/ͷ6<^pj;jטb2Gx,Ot6}+IS9Q %,aPDwMiz/_VelϨ4ҕQ~ З0p�uN%Avy<,~P'N7-7끗JN7-;^f~ %A8e^^V0I2cE%E8Ha9\E4F<:͠S7;n C5(opz~]^EtkqYt?nB/7{'`xC ٮ޼NUt>=c=3BQ8p5 a L;ڰdtJt4GAlL c:\.ݶ؄A&Ϧc,3|÷Cp(/oC6JLaA]y*;O.n8",M0eE.c{`dӷ<0]AM"io?̧֌3ZvNeLjgE2TP0HW4u{X�,q_fiϮ~F .&6>M eh`\ u(?78s[R>同Ȇ}c iЦL/R<Þrԇcx9>>oqϼdx>yȳ-R? QepR 1smz.3D_E+?g=+yy C%~,揌ѡKh}|ï^rs/�Lc^jCIV.P֗@8y|>QbUUvЫ (zL c7è VaH^(h|?\;w.&218:~'8ZWԀjlG#<ˆo%\}?O1ښXÙ}S[s}׭l@3$}-(GkJGɿ }۹W%L./lM/;u!O'`Hr )BJ \QZ孳\p[>ǓH5OAFhy$1zk3,,*ʚlR䎔tK[8prWf|<�s`D1>tu;+XCY;*4| mÙ_GїZ1G\Ō!ߑZFiyLX.nƱd!?eՕWIZYٺ)m R?%2 l$aH?A,KKgtvwy10 ҈[a9Y'ݟC_agJ]iů-6וefYK=w#ˋ.w|]wTNWijp+^/؊W#apˆ b/2jKe%Q9\W"F {3! ni0x6&R|L 0Df|8*>.<5)#87M*Cy,^8>D7ECWi,Hˣ77 ѩU˭N@X{OFq?\-5+A| (i]o9><(!s<ʔ<;d<*IҩK^Wچ.'E\ uX%#=D8N܊ȼ[$8WD4LVenַͩgG>Ro ǐW")Oa3?3Z5ǟ1ڣ~WFk|LGhq1F"OC_4S$KN:Bqi8yg%#e7MqY$?pN*ԣw-g3Qz'>^n_<V^’8atIG3.{EgɆVçh`&`՟fZUYT$Jc3PZY [w*uO/'|c+\C<-O+77ɖg9r1V> 1'M�G~ylE 5k$ׅ i3-m[^}lryZMp ?L3}-G#\ ,#n]LYqm:Z}MNLwg:ǣҿWt ]DmL\uŏĵAD ?HF`s`kz>aatOi&(F* o ?;8~0)[(`w'v.褔#\ y]KXV껪.70lJH68MB8H`WRw}.:[Ps0'%G4YͶ1 x9 Cg%GPpYHkwB~ʙ|秌 9`:^wqK8ʅ8߂NSF)aѰ͜Nc|&TC,ef >AV;"(j ~c Ĕ8v =xOg‡%�: XW͡'~yt`<޳-A.eLN|r˗P2=HG$Gzî\9*5ʛ�5j@c'|pkptlmV:&=Yvh|ʧ ~H?^l OE:l7R>GmBf0|MHJ%Rxy磾-?vB[:xLj:qDlbލkd 1BvaW2@S.vwH/tHruc;,$ ıP6OwEK " <3[-/o_P<2Yy Ͽ4UޢG:p/V촢Cmo[c.3 mo}.K|j6jdPEaT}h)[##yZ~Z/ǮsavZ^f~K8P??;p^O/y]xI!W>̹IZHYo~g>=|y(aD_);8&3ߨ կyM o £GT>irUftRr:B؀G:*i BvX_(eX| BGGCZU]шrLfȧkԴJU/i4[~EoI]=#:X [>;;ӕc5}Z|45>'7oUsSԈ#SUGCJ Îskb8-2Ƙ{�,hy+qUL\2QQҍAF X>Gqf§ϑ A-5ޚla8uH!cFB[-Sg| eǘ>2J x(3?= 9#rfy|,6ǜ u ^M|o/qK> wՕ,y+U̶E ;=o95&UME`;AP>5NfOf=ufyQ,qLyF^f OEfJ)qQ0`=r!`bxp@'|+�[:܅Y1N2%Чs/K|($Mq0T`l$i>Ğ?X7> 6epqL֕ @'%2 AOa4z N)wew\q=xIm"hꊓTձPgT:5=N☞VX.S}#b~Im\ʤrV0(񨸨viuN};-Vo7-w6/9s=9K:, /1_6S]WKȺvF˼G/�:=}xcgM_evϟA إd`tԩƚ8凒ɳ8sY%щB'wa;@ɋ 6qK(S04.j�x҉^0D'K5qial*Y.^7o'v˜[]t락&ffb�奟S Lc޼zF~dzjFtnM2cG6&vC£2qTM 3gT?s?#+>p^TC<{-=%K (r/n62`8 t|OZ˿ћ]c,#?Ra87"cz|*  :UՎeMz 'Z7ulJ~D!0 _xBV|a|;ܹWIdˑ0BG[:Rh1Y}Íz]kdFX"اpPh@[bj)kK81M3ztH>w6J/"껜5K'zTF.+cv9^4e:Kh &B04M=]V@>CƏ㔸d5T 5!20Ű0<}xemF̻ p‹Z}t%+\$v,ˠl@맴PudMeЃtRyXC*eф ^KapAGQvFVyiPQ^ހCȐӖ `>vHatc:c7KP3Xѡ^-1Y d;yw޻x/E^ΣtľMͯ->ԇ~.-1X891o*qa%E?Ƴ/K4〱]W&2$5Mb}t[b 0Mu ~DSm8H݂9n4qԊ엿2&!anC<ݐߥcnG9~.4[q9GֻE[ 5C7L/"cyGOxB=b)GhyFx8ãGbǜ'ø\.o60sz#ɸ8*L`/iGpg/3e4$8J7 9rv4ݼC]{SwT]Z{h]4tw4gR$)E1~S]]p>vqXtZxsV>dTb=8V CGa*v͵eWrv]6t =8 zK݅>WK*TVtv/id>d9/#ϑo K$B =ZX6;;WbSߩCo{r$MoPƵNa (W|}{bDb2 1z[,^AX=mՋ~~!<VH\]l 8 o}+Ƈ=Ŕʵӡ )?s|Y^@yw76T!/,@t܌ `_yz!&Vrx χհ~P)Plejh8BOA5ǴŕmKcjcgh59T]r kʃﱅ1zӂ xAv΍(?Tm\󞽹y㵯}]̗j_\\>?s}(s- !}g Lpc\I3peڌ|)eO#c!/ȑ C*myzL+ታt|\:cWWkw'S긞HЯH_P3BH%a�@2'޼rB(:MA8a8Pc<1U6Alyj{]jl$z)]gv3F=ʊ`[;1&OƮ"lУ/DCXApF&@Ϝ:Y-\dqO9"PfF+仧r5Q_.;hgK-!6a0(fH#FKsZ ;w`5BaAs\Mb4B \ţ6~p�s O|W�M\]F.^1^5[ |;w.i2JsNo74✖R,p+lDa![X44?'ei*J]j1c XV˝l٣wj $`ǤFi[fIEO^{4?k qYuDI8?8-uGLL7ح' TAgKDr cn?T~PVOClW h,n}[lR#4 /qeʈmYֈ݊j{J{2aSӅ3|yuW\{sMRPCi+:hEՋ^\hzL;BE)33(洰'묢i0]mp@48䅱л/h n`C-4"XGQ,p_vP8qJq 16$o-rѐ7| )G{a*5gfi%pxM2.`;g1WJpO>.^ys Sec˼Cwƕb1(3e,/O}G8>? :U;Dtq T s%2BpŪϙ8 瘝z hj80C%a\yBDkA4gFŽBxc p~N^F5'%u'5;vݙOvFle<-[~ ~@7y@7Ϻ ^[[ǵ\>s뜷/x NJi3-pO2Zs@Cw^NrCW֋ vt)OhGCZO=�rNJ|D/Pt60T]PͺsnPIcN 6YױV8½&~;OD~h8rk>/Pt02t?taE/'g|JsZd>b_a+,Ϭ!-ֆ'/(E/)qN 79vR'4W%1G!1 x"./|}U?L50&Oٳ/Ӵgӏ9ER{id\D%.j$P{~vM=Z`#P0zXT*z~~h\g7Bs?7זNCCH̎9 Ex`S^=dL1% +)'Ŝšy͘|ZƓOOMhx2 /V 55!o3xWjnT^eR"kxHC+A`A@9Ȃ^rH_8.5ӛ0#71ګFC{2,% @9*KB8`L54o3Sbx/9 ȟ8[ 1p\Oyy/xJ|~/ %G?V^ Z9逦:̧8o7_9={�hN|w2q$5~f{j4$7{j>a: ?9)DKwoq�LO]T/ԃ3/c!i008Ji7#(Ѧ[V@c@¥\x}9]# m&;F7)Yji,GQ&A,iMfRk~vܞ8Қ) hC`1 nH8ӪQ5(x١8+R5w}-]`nR^}7ҿGLJ:fY,qLFG!$HĻ@" <?- ׬ `-=p:3k̲믯DCtlṺW sռ0 �``Jƚ|ج\Yu_÷znQapn?�O, Í) 3;7F(ss>}¼:�#<4vI\6}ϼuw~0��@�IDAT zX԰HCN/Pt %`Dsnpy~g/hU\[L3|t|zLڈ[Ȗi,98S,{φa^Cљ[ *S!,`b_f1*[z"ꑅQ'|CK ᥐjшQam�l>=t2r"a(|飹MX_�@tr&! MyK%j+AXO03`{#,iB@޻x}5a|V36,B~./l(V 7b^cݸl.jG53.M IƵ3mttIɰ״ǥ|iBF%F_~UDPBDe-edpPGy.zX,Q"42kNqZ:<_|PSdSCo;^[-F#|:)'|@i Ώrup=4Fs+r -1I8 pFډ'c8$&{# rzAKxXF-W۪<`h,EqK=†^l#}lB}kハJuZaK=jC^!qƋa߂>$$7|42.P#w쵞/iXEr?&1q<%?፧YBtß[ 7*lM<_/K8=W~ez3ln`t^JX?+ q}G�5T;%?i ~ҕϼwl!آZB^(96,H pg]ahwQ@ZUVۗטx(Sh<-Y:B圃[8|UGF֓'S BbMá1ĠTZ = [T* UT[VvY}\t$nJ@@_1_oK\oLmG?p1-KR6KP{e_rU{.޳%9waQ>{Mxr )-W9PbpKn=J9+Cvs#V"pf?[os]__ZgVvF!vC ߼H4b642eQs`<@v[ ^FP48BiȜ&nzq>3' }t[g'I`|e4m2LϘ:S.?GnM)NBZ]w>qdo(*q@ 7=G>݀qyMNI\ć^ ` Zm G) eu'x'.B+'2q4!�eS  u%?7pFVFr +{+|qǨ1mE<R~NhgjN|˓'[uYD%Mu[pm≢Ni,V@W=�qi}~dm($BA�I`Ɠy_PC%ɸ=<

2Cx' \?suҡQ%0_jne)WI/]L1FYJvhHT&*|q[}MfB;zr1^nJ01*;w_Jk;245JH\+?q(E7%#2?iMO/~2 1}7az$Cb"?]2A/':pZ2ǭ3 ~B7|ueW]ׁx#7L7}y ?4mpK(Kng;_&nB_2<\E1~dz|Y"3?BZ!i5dax0 ZWaȣw`ބۊ"=ȏ`8 ]Hs~dKs(`xǐ Vx@t9{3y؏.$BzS'2ttW,N׽xICYqi?_IӖyW;CU+L*MHg_CpP~0`f$]<=qq)Xn[-4vq]I{R*U iSaGIN!b-JbLV&^WynvC U=kF2)i- *J[񿈻IEzʹ2[`qY1Dw\a"FJӃxl('$I F V,.f ]v42o,y(rD}BJSx>^B@*dWM$i~)䗼JB" D%?n L|,7Q/+ A#ϔ@n^;4Y/8OFiXy"wn|3WN,{e}|=K6e!|(z7߰AQ7(5Qoz枾.",L'>wC(ZԦ?sjyg sGC@3 ӠJzxC\}tRp︩aK[ ؓ~8Ky 0zyDžveM%{ .qF@a<#;L'Gn@&,p9?Gl@4Ws(imo煼94z}Ef _^F)! axg|oi+]2xw-LvĆh23AdcTWED[MNȨ'ֿ5,e`dc\߰;]KK#3q'zg|oQۯ̐_`׶~SOnSt K0 ]<5Av(p߾+hN2ЍjvC:t",_aɼ ^1*/t\?GE~Ys]NwMk67_+F2 [=R٩B"|)Zۋ-!%ɤːsOCCaH(`wI}%*?eqbX1sWz䙫*;poL*d<xqat 1P沦8]c:'=%ng< BxQ TCtnӣ~lx)JYjd+k~g9J ӔpWÐrNn%fU pVFm.z=T1\ALk TpItR-Lę!\Bى({P0ޗt"L匋0&=WQ-iP :!J'M[3_<}B8*wQ^ep~4 dk. QX Tza ?n3\GU 5_7oV 0ĵ Ym(\]UxjA#5tdE WQVeCx,d3Uq}aߨ>;~Z;NUVU)}W}NRFiP~Ik|׫ :gHq(ըNl}G}c+nr`5=6J } g_q({Eá%iM79y,$4jύFy}o=bT^ A'#^;I:zbz%{B-+$Li(=1mzm| kUk9iwدe& =`'~P H tL!4J[YWջi\sTʧL8YRavMӧ-5`n HQR=d*6Z ;$%B |u躡x. d{Ozvz9¿,Zy&$Yx1ba#^CkIpy}md?LU~|`TvݬKon#F>4ICWy5 4QΦ'5}@'gzgDgH4( a“09|US|p<z:+JWu ^8 M^=sqa yE8aDR !@y5!ف0:b^.ppy;e8t˰|s9_ g].8XzJa!o:7l+PAgzG0x8 2Z@(yo^]j(XP"VKZ+0'^qpظF)@\t s9]& ;j^ nLf$g l.(Fk&U=xe$˕Fty]a|rJl:cvỸa@sapAFp3vr{;y+s/>^C}e0|.SEA9+~ťc#o~4pc;7w8 3t yK:?W2f}|لVsKl1j3GK'FkfDΩT A:wɝ6}4_˹s:v2cUeZ((3c#`1OhJ1JVذɍaLtGnjf|Z n4 LGPF # wZpz[FoN9##YHK!!]<A}D=h^L w!tקގZ a@ϊ3Jy^pc͎Ezk 1^^VB›yeǀU~4El(Q_;7u/ىQE<)ˋle#(QPbFQ6x8t;B_ăev*@ =t^ Kօ >ci} P/1-01oկ~uu%k,ΖdGCXC2 'o5F5#Ճ_0(u y5| ~0QR)ѣSѸwXW[wx1}f<-1SraEf |bȔ n4MkgNׂ?9WNS=OO5"ޣJ:&)ƈ1UQ2QFU'v#Zd(aB| bVy =ʂiXQ~܀cD!.&DJ #W(І;p@Z24ң<{H}ȠLēQԄGp|(ω?sO9vsO܀v|mQpmrdsǣq8l #{dr2u 8wy=vJ9j+rym7+mPIzzlU jmTLP]/Ž/gdRz(7HHQ ',8h8go}p]# Q,0JausP-^QA09d6? { J=p%ڹ.1gfoнҗ; ?,q~s52{úЋe9Kt,NP趩h% q4oiQ4 -έх')+j -ZA}ddtq'զys25ZNʧY|JbdDz3F3q]zg)qG txvZŶ g[67{xs,C 7s=y-A` ni<蝰xvJYc_+cOddžrђ^%V=꯭]D@GϜ99PijpH 1B`p32",t BMF rVFiOaˠe DO=Ӛjft]Kn4$_ڨ.*C9F#2"֭/*0LeVr8^Wp7褴(vmO]ƍw#uT!VrR<ƙN2#ɣ"x!-I=:p,ͧ/|J(Ě,(9c>#'e?-TCa2bO"DJCT ˉ|p?9 4]|g"^0딁J`4x 7?2 vF;݈GYjo_8("-'Cb$;/ua/y*QF4B?qri.^j~߈fк- ꌲA:dC `eG\>"K{nU^kA͎\^24yYJx^SMdi9]I21R(G"%<S#Ih`H>w[ϼpo{|ɿ}퍴`>P]LO8؜gN@PaP󲰳krnIѴhmKBQHX $aۿ۞~V>&hQb1L4QfZo']~0zi|~qbn{S4V >Eۇ]p#|"<7`WgA:q4.ijI4"Ըp:tu_vkܧ1:be$B)6X}m`7xf'*-xQ 9̻MgH"%ejpݾqj06d)諲BK]uHҠb*әB:{*~M=D5 ~T~U}]rD+fep/Rl g.`DNd"\wuaCFRh6xꆪFq\K<>lލ@:qMezL_.М4DZj7e9#ִ|o)"X7{ ?:5 cƲ cykq""1.p( AAL(+B@A80`x]!;8]wA tMZJ\?'I'>s%r^Qq\|H]”{HO{+iQ~(=8LE/w%\$9]ɫ>N䲩;h3OGYƄhGquL6PM)␔fE+8Ɩ=gΫ׷mM.6sNêe͗ZhEɩp-Ȳ>ĚxlB g_3ss Fn3]'oӗЗt ߥ`+Wo_.S_Buw>w_gq;҂$Z }˪(eƐU/g(p 5+дgeeewbֹg%l%atmqҫFb20O=".p4- ޔ_ ýB3J>w?6.p\^}_KW:Aheu)æw;)CmeZ+GL/tWw˲=;sBљcSDE+G"A4fJ^oWnEN@P[JG\409IHMˇ&=B%+Qr#6iIWl`$};p(+Ke!G>R^OðF a}moSnw_XOUp_5=aT97tS׼!7ke{]`8<{>> `XnHvq ,zGI8Q3~z=xwW壂![4JL܏r͞=sx=*tб xy\C89t?g>$y[mYtW^.=WnӤGkeE*a!qϻ%``p^J(ױa[-0,N&Z~-U!1Uɞ9SY, dAk6h!sYswWS$+=]ӡ98(i4?i"nwӛѐt.q-=G9jSU49UrCOtq+PI9iz < t,?@2u|mJR:vJ*:L^uV3k'$.X? )qWV;1lT0d&L-E0?/0Tv!/WӐiq+bcffD(]}*F5/ Ѝt[ S֗hePȻ-c-6@N2uPw#=?.;/w﯄q_ ݅ +fPՍ7\jz*!pL;k~% eF3.錗glH 0/i=pqΫiLa-T˗ O ߽7N~ȃӁwK7ߝ e~wgӃ+% ύJ#C(>4>_ƽe)?+_e"h&`$P&x7n.[om}ѐ4ـJs`h?3M ¡ ?7y |'4~+(gBd8$|ğ \/]:[ [/)/Q]7<ɧV}׋=ǵ8~+[.o;+Y6M ߽'ݕvyml^YATd$({[R2M0@OZ! }ZYrQgqE 5vOWTT5.Fb^&ؖb-a(?yڿwzON)hcŎ i92>=r? n._\sM^sbcGMZp[CyK)h()o``,j C%.([E絢' lHďgϊrG_;>ܔ!2sOS2MyOz4L2~ҸP|~ d k^ȃ?tl2B(D(=)2Zgg浘3&PCG>ğ9dk^.{iL;N:~^Eu-ƈ{W8xo)psHNYE B;FG$}W[pAk<x P~{7ޫõo}[Gz ?#u&kPRpdOEDG1W/-jx :Q{77ECu@#'l\gP V(W -o+g/Z"`"Pt PNUz?C{w:4:2x~nMZ 4AɅ``B0(akePiz+i>#a҄8rRWuդzN8S74IxQD,U!_aFu^##FpX[i}ɓʘ7߆5X Z(C'UEEDKkjey7 gV i{,z:6,+:QƪW@+8*FYXx;QN9i(Nr.qA+8yss{&F+UnBry)x@^aG/ʈiP*9oV޿%o\_y,h:tA %(?rѠP_[K<,=|xȜ\5r"ڭ0mXG?q|C4,:؃jXΑP:.(9_XF+lk7a9mӄf#Q D!°UZǎ1W@ nQ8Ep7BR1a..M}{|kI8 k S`z5wUt!~(<4V^+_pTJ%aJ@' 4봦[x[74#~q^yR(_7C~<n^*3\@IDAT<)}4qpC[.0 𮄋ȈkY3Dr5q:k?Jˁ E;yRDTATjs,[NW ^uUs'~[gwh(WpMF_j8p@+|O{5`;<:^לS1,q IvG.Go3 Wb~r4Tj+7AymJߏ>F H;ComuQx?rZ 6 3le苴4&e%;2}_d\y V%1<2a o} a54aaEѷۮNE-[WmD/aw0~&+]/z􂉡YTZYݺӰk^4 =_N\1Ď6q[c0j =)v(_\S˪6z|zn`ۈ{GFe /. 5JK#Q~:2VSA}]fo4v148:Kyc3Oe86d@;4HCc̹ 5;G*`2~Z$,o|4`q?K8x.o'e۬GN4rSvZޛϙH1s^k]e_N:$y^*AH|4U ¡IxHAv:`^%'JL]G&3mvI9wû b%@ Rn*04!$^QΤEDҢKx}b-g 6xKx,UX1NM$1= X] 9p#F2QGQA84)SDtqxe6)F ;EOR!. *ށ(yПčsT 71J1甾T vo@ ׸<Whr]AHF#0Rr$7=-:x8W(D0/aΑ ҬOmc1L 6ZF4m0_l p1=ryN>(F,w}w^k7rNRqRP{-*^Lf<M5zIŎp'?y1.p!xJ ɇ5?QR#NX )l:k}_ɩ8t ቓo$^:x@jcS4bq-VB0d^^G-`1  ZSn>QUI2!|! ? [pf*-E)$,QPc1R?Olg?;0$80l4P7u1AhP SwVp+3U}ZTJ']ݿ_Kuʹr<;?ܙ4Cj)C'5;h8*|#QWfd[5-U01\mO0 'MzX%]gmeu< s,F㷦-I/MhAs 7bCQrz/`zA%^]cR)y?F%4 u"~,=NAa~Uw06 6FZQ6ޓ4rQvZ d(>'昫ҝ< ’VǟR}m; D̟{/h&zqay+*)5'cE K'}ֽӽhF{ ŐrvMj^ývQ-t^[ U?On[ ́*^d_В.AO5@1 w j,ƒ9i &U3-SZ1I )?_(h{R%P9XHA\(B E=wCB.(9% UFO蕙LO#JxA X,pKRGdhax$֤.pd| T55ЋOHstgS aM3p%S+/cd:U#2<8bTZV A'p6}ӏiL&A-5-^.VW_}u ϸR0 B׽!([ 8vJ9CIpУrlpd'^tRϙsT\W0p"ͲOfج {PuP euQʿo*x':4,{ /.TE-zi) aP"j+:ěFLw$\xu2cAc7eK-97x~LA'h(YQiTQ=S7=L&pvew>)SS3s—AijI͟Ví@#V6T}1R 4bdbyN-#3nOF<1#`7PxX4(##څ}Uu}=wѡSH%ŋe ¨'CYLLpIaOY_EW/{!/$ܛ\Ȼ+7 GL~qLE ׸+qXm GD23 (V%OO C DE!줋8!&jR" a) lw-/]QPBhp&|ߍ7a =L4fHtrxi=@^*,%BPa=Э;+'$M4> Ún~Wx36K:oXJc@e?i J%p|ηl׀q=M.c7~@Wo(OHd3ys"XuP:)0@;:"8d'<[C}xr Nuu>=I$?E-Ѳd 0D4O^1$^d.epQ=3Cхa;/ 7JKeΡŧ- &XF !+80%Ze6V>Kի[@&QP6"eP~ƽ锞*aNVd201*8X<{\sgy6"oZnnK{T} o;z^sNSc}sM44-JzBQW#v}37TJsk{ O<0O4x, I@|dnPzɏtI+~YХM[o )3!~Sާ?a;Kx{kooFC@ȉu腿_L}9 ʴQe2ٍR?ˆ:$HW]uJi='8yB٨"t5 ]RJ֥Yl"CDKx9eCE4;Hb;=?D1Z5 j$AugC _tŸ՞^k,2ZN:F!#{"2>HcEZ D~UgɠW§;%YԴ8 ļ+ȏ.a*G i,Ǟ.8oꉕ縌YOkBL[d4\ }߮:2>hgbRnSƴπk|Xٌm=y44xHCM3 b%iMO<!ll#d ?=dcNmNK|<sY\WrqC^^٦ zԳ?ض([!mPn>4PzBĐH|^ >E`%8FiEPE'bɊ\(#remȽ& 0VAJ+r1 IzUF% ì7B 2Qkg\JufZv,F&⓵c01tzz\#;%Żk@+Nj9 j 4~tj4L.~@Z`KdvcXK42k(H|lb _jb4KLn0QRZV8b(Kc^Ÿ?ƥwD{%objƈSNpaa ~0RVPgF>kV OС .O˂r<8u.y[:xC!'8sm%NdC [$|` ANE{G5Fh`HvGEG/zExsxC?< XF.z1=R &#HϢ-T# | 9 9T7okz%R~#EPF^xBJ]O颡 Id+KXc˗OdEyMG@pyC..L#exʆ{y!IC JF! hJIaC(#-x2) ]wݩEr5Mi=xSOE%R7L*\3 '|=6[x iyE)]њ'"Aܳ%X=OC|{C?=^ħ‚ |,۪#GBI][݃ D!4 m^sNn"g qal Q>~'[D#+!`EqE}q%+(W9ց{Ub2(F঑c_?T>0=0baTAA5wܛv,ڪjXC@΃/dz^B#6ʭ_ ).?{QACw EkHZ R cE;7 oa|v"Eo+!+uspIY1FB7n$!1bmq4`4Ny h*VmqpUoG7̿"0lGc{v]}p_%Ód2FڝT3|OΟ'5 >\!8[8Q%KK:)]1NmY)WAMNw8}:KR(3a306vk3r)Xs>][FݔViIT|P8(˒\d< g.wPey6R;r>|8Nek+P#HVR?WsIW HsO0[DNI%˲VD9ԫ{ [:epVߞ] fvp&2yߖY?lt_6eS T/sf[2A6Sm;WV(s%> \M~|7 - kׇt=}q6{rSᛢp#_h8SE=p;/\K'Q蚩5wc_分=P Gek9Hu[N\ 1$Q/$B sxRn38F B-֤p /3|q5;{Pu|8B`/N'KL >+19 <,+r#WG[|S'rg1:J2? ғ 3F|ZB#eyuVǷY_k?ӺbdWn[giFCwk]?=-YWeu|LSO5Wuk] 0tn?]} n#?%]m9UpmLyLLiy&YۭNug(2"jA427Խqc !+ץH]Jj]Nnw "8{o5op ݣx~^a.OٕUbWk1uT[6[4}\VW0 ?3>O|xwꜲL: ϷySݮDvj=nNX^hh`iULG\J(8q|OWvt OoE՘PkeFf-i>V+<|0|b[m{oigÁ|oE2Pgzq6s4W]_9l}ަס;鷎U<︝v|x}_Z麬5^yߴgs?vO~yw*&9yPlq–^ʂ!5k~[>Xl`:!r"gZix7һ2tYn6q.:Ak3_VQ*}~Liqs$J-wQ $Udԓu,efUFDڏ朁' Ys_׊vt*׎=z@vOߋ#U9`T/A?z[gBlbDzJX6ɧӮF8do].5 4M$p ʍ$zȾ\bt{o!yU|7M +2 SE$Rͽ'Sd،n,&@h3Z/h@}X~sX([T$⹞6|{يȶ-4ҭt$q . R5qԡqB#3O+iR0![m큨l< | =kͲ `!*7fD`]J(4 #zfɄ{6S"FNdiuV4jV9?aצ)^K ްE 8;n"/-0^-{6G{zgFMۅocmy=Sve_ޅlbk炌TsRRл!7QVҒVH3 E;<<ؚ̒SZHciPJm2`[fq  ?Tҁ"ʠ-D׍0CS±eR(q!0uI\k|N1ϷF: '4ѻγ~8ѭʋp)Úyv[g.L)Y#2_ε-o?*Lʜ_fRt#{9apq%hq$c8M&ՀRo{ nNYR4W+tfu@K&"dzKG7$J!+q˶d3kև!+zʵns$VY,fQ֞,ڷ?fƀa݅C7ʏd^g0 {#r'#pdE$C[~#%V$<be]Oc]l{{J@dNTeh=ʤ:AjwkO֊%L)-A-RһJln8FFD# /wg69|_Vm ўV̞??Xݵ](o͏,]da.~K1EċrsrQP7C.-'cBh7:bn{vwѿo8{Wi@3-N1^sNcQ)st o9}Wu"-=zp{bϑ k1h"wgcf7R.*^:h;MűQyf;$k界HO{5ܯWFڂ}B8VrfHΑMNq=9lzL;w 0#A?n}+t1ym8gw2{w3'@1؂Rw'I_z9/8-ff.vu.s r~ u6ØvH)86TyI(I-9J[=)h| eo}yjkg*a] D}֪ h>p~vbt#.S9e6<1ӟB_Hsvܟ#`JdWU?Zi)Z٬4rMLw?Ϙ3/>G-a8[iVh:~f'M13~X;ݪ9G|J{f??zoļ7Kv[0۟j {S0bԹčꮀ.#9gx\ )Ht,bKilGFգcrw=N~&9t9DV~3D?.ZTZn_"H lEęaN<4L"{v~K 0Dc!HYp|ik~3: qԑ}fΧLcJ@Z|\y#6#2Er̆̏Pϕ&p!.7B`Jئ#}|j|t*?z#i֕GoeKC9ZFl]&71klTij `=w[{_+&~(怐é#~[ɵtXx)$PZwwd˜2 Uޤ[:?W'e]fqF"ޡp$Hz8{g8S~e W_+mo5\żgi&Q&]AF(V*at#j!+5u !_1׈߽jgJm& ^^ ȑK)Ǔc[& jmlY?!Gfg_l1:G(Cmf7pOD'z4؇/_"ĝߵ"-=f80;m#/dH=  o邂_L#Ex[hemLVx<یԟg̏VEj0TDH_}?0{|KnԼʼ Sio`^fR8+śo2u"Co3Ms1y-d"}HjYk?WIz L#ZKWxtÄ9 wg~*$d Tƛ;N#f,"[B4Nڡ?v_HFPҭ@IDAT5ẓ1 iQ nF\F:y{s3:xWItfa=:b9yIWSfs-'V^ xqIwzqzF t\`&$V'Y\SS1S57v3Q޵y"D[>ǩe;Sז l_e?iF۬~}4LϤ|D*q Oײ}zڌ#wwڦ: 4#s~+=/+Qff:ҀPFt^mڵcXŊIa^i خ)&ꆃƒF@#Vk@D Ȝ>ΞAYK,44 K[[a̩a DsxweeAG oG*[YeGY޻ [ߊ9lwNFjt9wC!jLOIcV:F^\@,#cU8!t]HgT+aW(ԇDz# 4u84+<&rP-Tf~6 s9|w#6oܷێm7f/E-Lt% OگhvL$Hbrvmg&Ty]Wc ap0^QzAXCUnJg @j%icD% +0TyN;0ֻ9+|R.[o+0_52H\ tc09T[\ _OeTm)34FStWh,mHԷ;5z.QOh,ig]a BB QAdΜpЩgǝ31m2Sɲ;, )e: oJ&GaV); Y4LFFC1jy`cw|T:tp !nUZcJ]vr_j!0>[&&k՝g}ML][h䑇z:js-T_Wommx [ea6fq#[.ɵ1Yoim@sbAphڬW ুp_>\?kdΣ}*=?D`S4צv=R7 ^g({zYΫse|;//Q'|p=#x3"QB{Li*{'#9@m~0fzmw '0FF6FʴяӪ37_mZ1E9S&/nuwxD^v>R{TV^P =ׯm15@8[-ۡ H`\(P-]n Tj*z?")׊G"/Li>!4xq-Z"PRRä<05T9q jط{t\-P $C7+xZaR!lKDF>"< tEWZss04X&ђ+_zNY=EҫgDHnjѿᆦ.+Vou=:1Lv d\#̢AOа3 7htC?3Β' SӒhJL+q IOpŒE('r<2A *QF&yUg[`ldt|lQ5H}eOPܾ<~ JWK08w>ڡLwgϏ nDBR;XcmĺF] oQg7k-2&:EF#!ash  mDͩשG4 !"iL)w/5L~G.5?M'AXOg;:5|3 8%0 ,# ;?E0y롣'xa~a 9>BĪv+Nv;63uS _ |3B^p;RGJ'֨wPkV@ohz%,=5t&~zpfg];v?_fI *7-4,F:a:kM=:;.bK kO‹|(η>lc ?nluؙ9qR`s9%//>x" K}gGd?gܚ;?Z,͈^[^e/>Ofx";zgg'ffג0!(E)i.6zW7 xQ4Q3bhYt0,.ü(%ޜ ?OqwB{JZ0fox<{Jlyg28}Ng p,n2#[i''gK灬 ,L/<OR?_Բk#>gk*it<&ZObmM%A& ?Ĕ1/9qҐ!vz%sG!\W+j*>-l$)3=4>4Lu`ԧ"qgXMW׈c "p.sgyϾ:vKcWiF@߭ܛvl/X^!ze)tv1f8<:{SC'É|OFӠ`7Ȅp)߅K;oC`ni}A SNgԓiFKt?sJ˹9ݔ+ $ {?e/`S*+VV豿_"=cB{׫dwC`k! |_#q T؎9$nghQRھ΢7JL8 0tuwˎ9`_xL!N` [vWgz͋zn 3P[v#lspsrs20C \1S;Ӭ<\w}|F=o7̒sb',.!<`n4 V#R^ۗ, ̢X ^+ew]8hyj3s|[+jrm5ۯ=(NHh:CL/kFLy6x5Sφ4jbt 76=|"NWy?vhd] "fa05FPP 4P?psYQUU?2I]]sMC]mFDx'~ u3mnzH,aR@\wg0BSNlsw?j}S.W~&f r^(RѰ޾M:q46a~@Ν{J}'u=uz9|mc03ћhDlwa #e;t!; e⧵k?ՉA{a睅Sʷr22_z"vZivLd۩=Y!fίWE,퀎Fa|&#Gc=NX٣9^Ly(_p2MS>=ԻJ4c#&†$| ;ct[0gpFY;2!XμVoE`n*OuR1u^eaJ_F:ӻM4!1&f" !cñFֻ>#{z`'eρ`2ۃS^zYgI羽Cfdb4QĻ=7鍥#jwdFG(-Ow=Tm"Goxp{atx:2wEI^^0)0%^ſ׼sf_zs+A (8Ԣʔ CȓkDx`~8 {za}%HZ#͞6,<:wcDGj0_!+Z'ggP< ! a ⾘IDO_[b61BXza^k(cd>JН@iSǜM,;j0Xs|<iaՃ  _^wݳi׍k` Xfl1P3sϳIT+lH!0?GLR)D/L[:#?{f7*IA7 ԋɏ2*QWzPhZ q"<Cj7:Fº5Ǽ3Q8="Vш]oϽwf:T!(̟!ƏdlLe5ba ˙{kl]iqpQ]Ԝ'CN3@pRk4(LK7<"TOeZA/g(sbF5="^B׸'p`bC cI+IVF$ϏǴ\h[_C4i.n@܎u1)',~ F!-V`YNzy-Lی<5'876exa73L0`Q6%b@375uTgy |0i"byuQSV}^V7qa;e_}_G xwϾ wywێi<]6a]&{ hjp<ڧs[v<Ҹ+6wW휨|(bG-VW_vz0m&[W:3U'o-Z#eF=k讶!2E3m 聘!SԼϢ(̩ o{LÚf cҢ@C"?u?GXe^kj^;}Rjs=%!3skk < ,]. yB*Q~{xz;4< ?E!L+Lo Rrp@;J qyH&ܴy4 4v\4M/0Vem֛Ȉ.B`…"<{Q?x m^36]ɽw"bZIz(sTE{#nҶV9_O;|~C]g"p34ޞ{%z8q29[:Z15m5]7_9H xŊAJR`uziRHk #qNE(X$ɼyX3];;cdBOhi24^/`,cy9"bUQZPi7LJ!׼DEr Z˼fB;~Dü!gEdגi@BT`4 t-npf!8\R>'FBA2ldpwZi8~ۂl?@A'mdJ?p'>.Cu/U}U6&3ϖ"uQjX{N,|3%O͔wj6/ #NwQ ix/{gEc.<&ϰӥX[DM5A=oC ˷UԹ3bBTϽ6Ȼ_u~ezHm59m'B•A;g?h͹C9 2tq?&0ٷV kv!VqTQ ,0߫,s;+Oa_|%cnL/ߏ^v7WLayMf!Rt9R^Kan̎ᮤ^B=N"cYSzW7C.ȢB$|'fo~ۓo0W^ eF,/Ulu qvk,HuIdߑ IN{Kг@U]H#l QRO8A37nܰ2jWɪdΑv<5G_mP1M[㝚X`쮬^[2Ya8v'bxc@"28V)Ϫ{kY-.楤۱=x1u@Yphhy-mch]T!F31ݹۦ!#iCz浕k,Z;nOHMV8K‹}c)xzb]w%Sd8H8`/hت1p2;ۻf?z]̋,^D9RWyZȅk@20 >Ȫ4'~fo{[kȽ?B苿:YEFp%8 #ZUoŽiCN[ #_>wv6|칧Zkə}{_ +&5ɻ?gk~7`o( |A@.DnW4/5{V ̌%l'}~M/ʂ6>z_7gJM<|x ceivNLv ~NB^T˽yĤ2]5fNni.g@$*.|67hE%N]77A|qUH+7OޏwD gb&2lePlz'Vs^3ML$o;'8[͔jymu$e氦4[k\/̡.JM^kñCA*m?"BRzUT旕1ԯD < ,`0bϥ\4ֺs+Nrzv*`=m=S2!$X )823Q ;a+3vb$v锤2D'#mQqx0jbźGKPqbiw;?la^$.8 _85qڼ }p sY5k Tq7n&['g^%`os$XcJ'4Lp b g26[a{k6`pwLŏo{(@|)иSVq٬a/G6IN7g(iU\ ^D\r'u_eY e'㧰zs=*B@"*EX_<}HxcሠM!(3<3S>k)LO[1R)|%gp=$]%#XtJRXTYY+36ȼf2/^ڜѻ@U㛡{ukׂFkkULzHz/d j~MX/Zf> =ZGZWh>]NЕ̵ɲWa^^{^f. G3oݻE6F JXÜi*Oż2.Ֆ]R6 աap]#y2ߚt+FYpNz^]2W𵺭OVrtEg4~Ri?<% ޼7:>vMkEf^;RyjjUB Yא.G׆ym9dƢ4Q V416c\O hCX=(9{6Ahmlh(aB5Im[lbmes*eNQ4 sImhw L0e"2\6A$481-x0S Lt"=ڥf_7_bq~ĵAHY1_-nB[dyD<0O٘iO,Ccka> )5D0 >>sxGN=C+hD-bOY_V??{[<.Cg+g?C?BD\7O?nWV72&Bu; -}kQ=E ?335J.gVS>-ko-.^^w-Mpkǭ;OOkǩpN,{(ȗ7K@z+??,+ eYLgZ`vMZ" 0Լ3+F//%"ImQ iV,/ vz^SaڼF{QsXKmq衲 ,,ldGmW*@E:@J# ozSbC!bhb 0 -V֏rP+XP/J w:V[+gOeqŁ v/r:=̗Fb::z?C[;([Fr1;6zeN\+C@NRͽ!.TɁ1m1|$B: t}mru#kGf0kt%s#|.ܾY ؞:_}aFH=esnYZL4FWh5Mr_~j8afQ)k]v|q[ݕ\P+"ƁFrss(!0e4q+ymԌ;ЎX<"˲ 3\l"@ 7LhFT>YβQ9LBb4/m9D,q#7>长9z ޵-?|nT+TMh Ab!p| QΝ_30gL  ٠c}غ)_2u(7RZgOM_0T{ʨ!LfFX0ȻoF'k )Ɖ| ((3zR_8ՀZorg:Q~a`n ܱ|B+gaGp&0_lգo^U޼W6p 8K f'!aQx=E@lXUPUB5hAe~ψq,_#E~qW[[W[ha[4"ͼ}+ou3=D-SO+?4{\4?y7Goŝ2H;]fv~˚A㞠Sn\L :eGtBW=ii:E||i<_!-ֺ.<".w]?w<4Z#x?.V#6Q݇8sgE҉Y(_w ~(1i;'*wҬU\_c=͇`e%Ђl2w?Y3̅{gV.ZÙ/;]CyCsmQ@{L13az,xc^$_m^6m_A9QoS2e7Weu_ +eom:46۴AB@>E'@>B7q;,`SP.i G_0]B;bnL6s5+4⻛!gdl5Ķ bߕ Z0MvC~ ? Yѓe|~?v)W[Bѳc\tVOvu|0m؜.|i,TxΪYv̎oJ6<_qXy2#!IJ=g~k̰hcYaE)̷ћYJ|yO T1 \W (zjY:BCh썵9~6:~bWv9tf1ZHMZQi_1k4R.Ҟ/g`w!g!9sI5*GymzĐ;LaF3&A00^(-= Q~m:⥡Fɶ=w!od)dWvTݶpo 2ÍlD^m8=y\?HcHQfJ8cB+˧(9OܐMRk\=˱!z"u !\4Go(!笱+lL,a+W|Lύ; 1ϙasw.3jZSjrG# lj}ʬtNM8#A}1Ƶ>+k;/x ^h)y+p6Z]i+Bg?RE,A@1_ :a׋=w*2@6ߍ}p|)Fg0NۢaQ_z2YwڑDF ޽~),(Hj݊T+Q!ȟɟ^鎍.q.xӛ%eYq5kΝ~qvٛ itGG]K\1m<1>^fg.mn;;Mټޝ6|Ƅlӄ "PJ "<ņe~(t͞뭝a#O&ʭIFxF8޵}۳O_.ʽт/'\:{5<f_o6UO?3OFɈ+Mv3 B GI$E9W3\vP,I<#Q@iW^z^TLPqa/ˎ_=]uOrq6Sp5#3Na(^wCSP|.)/\(yk C'^FCslʩ3qL c\{䝸Zn³ϔ`0,}gBcibۗg>\<ҚgY 1vO$T~.홳9fȡs<]f{z̄ «!69o@Z0SI̱{nn̵섧ф^6~w? u]k1ȖńG.8L'gfom+;pŋf8h#h퇖69W2BF=:;/yrE(MDxvj@uxkxOI4̲S'2 _4"rPmZ08h=f뭛CGajBO,RW;`#4[3^xn5,nn"Y:TƴwQOl飱Cdc)@q62{Ӱ??n '0\⁖w}CnUaRa^s|WYv/% H5DlrIF*oU["jOcCZB {ۑ .S$$UdDASllU;xTj=0?OW{'kTo"bJrww=2^a ʂ.ły=7Yi7q?=x8rV9_N.]'H' r]Gw?uw)\(Gtu lJSStЂm$Og0-W]Fûi<ֻCW[/;|]N_1ꭻ4b&f"$(ĩ1дGH$ e}+y}7]vZ^07CunWʰD;1FwAo;Qm^z)x?0)wMA6YAyY܆UoQ;_eE[S4ĕcԄ,at0ػ!#yI~;Sx(\NNԡY}o[𧽌Z^[[h~h!'i+FA\i$tt[X3ū2\eկ&cvNRFp-'F {?=?X rI `B-|6 &? r?"WPi~ٰbӄ9}71PlA!5*G얧tJͳ+ieHrsh4։/`pU^Ƒn+f5MI#$ZK #+:MLƲ\ZkD[G2ҳ%ݟ2F"ix׈Rn/ΔPη :O>zx]+:: ,0,Dɫ;CP;eZt;xS\_#'Z$c hz] {v UHڑ$ϦWQ!G6zj ]9,[HoݾS.]mP{+$2dIPA@ܕ+: 6d)U]9#mPy IDAT 7!X)Xv3zԺasFn( 녲P/!myL fdCYnCs%096p,R33Q0Z7_V/x2 5׳u;x?a?9}IP[ ;1cNgJb -ӥi./ǀ2CSj[qڿmHu_[ 0I w&KjOfnw¾#X;n ڬ$3(1 p*3sZN4G G^vAZC Jmd̈BŨ`941oٞB{h2يjO-ܞ<,,&O< vo8 1ZOmPNl|4 FyJ ;#.{۳ #6Zrp</ږnR7;Ɏyps3y-oX p_ uTq#_um` L7"0ovh߷[ƞo8NuxՂܶw Kof.fu/3˺r+EvZ ~4y_I 3mФy]B^*szR6 Ն`K҆QƱ 7c=¤v9-2e'X֭_@G3/۽j%$/5#!\:ѺQ]`ToG[?Uݸ\Wы"y@ͨ3sRN-d T5tS՛(ev;A`=WoNHa}'M0^EkӨ#d w0v"1T_km2*Ωvr5 3@탆'Nj apCN.p7HkI \% IyZٗ|_y[z4>O'pXc&s a]4>`@S"WbD .L4 \ # |tpGXcG+a&&BNFp%)IoVe%CTkq/ͼ73>ڿ n4iKj֨,x߀c0'Pt.]HpVELvdD9lyڢ0&md0 FTC\VMY^(Aݏ|3vBԯϜ́ސU4gSQ7G( 9:{\gxŽρ X/e!O~'Ĺ,Xz秘(n<DO17Py'~7AKSkGf>U]9 FU<53rAT= ]ho8<̞5LLVi4Pxoa` Q<Dn7km7km7km7km7km7&;xn*&k-p0c̕Md,p)Fg{6~Uƽa{𭬉8- laÇZ(^q0wKýf}>;ώn2җ_mO|m&߭8[kǀ-lAX_xvỗr4 rqrcode-3.2.0/lib/000077500000000000000000000000001513000367400137205ustar00rootroot00000000000000rqrcode-3.2.0/lib/rqrcode.rb000066400000000000000000000002351513000367400157040ustar00rootroot00000000000000# frozen_string_literal: true module RQRCode require "rqrcode_core" require "rqrcode/qrcode" require "rqrcode/export" require "rqrcode/version" end rqrcode-3.2.0/lib/rqrcode/000077500000000000000000000000001513000367400153575ustar00rootroot00000000000000rqrcode-3.2.0/lib/rqrcode/export.rb000066400000000000000000000002251513000367400172240ustar00rootroot00000000000000# frozen_string_literal: true require "rqrcode/export/ansi" require "rqrcode/export/html" require "rqrcode/export/png" require "rqrcode/export/svg" rqrcode-3.2.0/lib/rqrcode/export/000077500000000000000000000000001513000367400167005ustar00rootroot00000000000000rqrcode-3.2.0/lib/rqrcode/export/ansi.rb000066400000000000000000000037211513000367400201620ustar00rootroot00000000000000# frozen_string_literal: true module RQRCode module Export module ANSI # # Returns a string of the QR code as # characters writen with ANSI background set. # # Options: # light: Foreground ("\033[47m") # dark: Background ANSI code. ("\033[40m") # fill_character: The written character. (' ') # quiet_zone_size: (4) # def as_ansi(options = {}) options = { light: "\033[47m", dark: "\033[40m", fill_character: " ", quiet_zone_size: 4 }.merge(options) normal = "\033[m\n" light = options.fetch(:light) dark = options.fetch(:dark) fill_character = options.fetch(:fill_character) quiet_zone_size = options.fetch(:quiet_zone_size) output = [] @qrcode.modules.each_index do |c| # start row with quiet zone row = light + fill_character * quiet_zone_size previous_dark = false @qrcode.modules.each_index do |r| if @qrcode.checked?(c, r) if previous_dark != true row << dark previous_dark = true end elsif previous_dark != false # light row << light previous_dark = false end row << fill_character end # add quiet zone if previous_dark != false row << light end row << fill_character * quiet_zone_size # always end with reset and newline row << normal output << row end # count the row width so we can add quiet zone rows width = output.first.scan(fill_character).length quiet_row = light + fill_character * width + normal quiet_rows = quiet_row * quiet_zone_size quiet_rows + output.join + quiet_rows end end end end RQRCode::QRCode.send :include, RQRCode::Export::ANSI rqrcode-3.2.0/lib/rqrcode/export/html.rb000066400000000000000000000015741513000367400202000ustar00rootroot00000000000000# frozen_string_literal: true module RQRCode module Export module HTML TABLE_OPEN = "" TABLE_CLOSE = "
" TR_OPEN = "" TR_CLOSE = "" TD_BLACK = '' TD_WHITE = '' def as_html qr = @qrcode module_count = qr.module_count estimated_size = (module_count * module_count * 26) + (module_count * 9) + 15 result = String.new(capacity: estimated_size) result << TABLE_OPEN module_count.times do |row_index| result << TR_OPEN module_count.times do |col_index| result << (qr.checked?(row_index, col_index) ? TD_BLACK : TD_WHITE) end result << TR_CLOSE end result << TABLE_CLOSE result end end end end RQRCode::QRCode.include RQRCode::Export::HTML rqrcode-3.2.0/lib/rqrcode/export/png.rb000066400000000000000000000115541513000367400200170ustar00rootroot00000000000000# frozen_string_literal: true require "chunky_png" # This class creates PNG files. module RQRCode module Export module PNG # Render the PNG from the QR Code. # # Options: # fill - Background ChunkyPNG::Color, defaults to 'white'. # color - Foreground ChunkyPNG::Color, defaults to 'black'. # # When option :file is supplied you can use the following ChunkyPNG constraints # color_mode - The color mode to use. Use one of the ChunkyPNG::COLOR_* constants. # (defaults to 'ChunkyPNG::COLOR_GRAYSCALE') # bit_depth - The bit depth to use. This option is only used for indexed images. # (defaults to 1 bit) # interlace - Whether to use interlacing (true or false). # (defaults to ChunkyPNG default) # compression - The compression level for Zlib. This can be a value between 0 and 9, or a # Zlib constant like Zlib::BEST_COMPRESSION # (defaults to ChunkyPNG default) # # There are two sizing algorithms. # # - Original that can result in blurry and hard to scan images # - Google's Chart API inspired sizing that resizes the module size to fit within the given image size. # # The Googleis one will be used when no options are given or when the new size option is used. # # *Google* # size - Total size of PNG in pixels. The module size is calculated so it fits. # (defaults to 120) # border_modules - Width of white border around in modules. # (defaults to 4). # # -- DONT USE border_modules OPTION UNLESS YOU KNOW ABOUT THE QUIET ZONE NEEDS OF QR CODES -- # # *Original* # module_px_size - Image size, in pixels. # border - Border thickness, in pixels # # It first creates an image where 1px = 1 module, then resizes. # Defaults to 120x120 pixels, customizable by option. # def as_png(options = {}) default_img_options = { bit_depth: 1, border_modules: 4, color_mode: ChunkyPNG::COLOR_GRAYSCALE, color: "black", file: false, fill: "white", module_px_size: 6, resize_exactly_to: false, resize_gte_to: false, size: 120 } googleis = options.length == 0 || !options[:size].nil? options = default_img_options.merge(options) # reverse_merge fill = ChunkyPNG::Color(*(options[:fill].is_a?(Array) ? options[:fill] : [options[:fill]])) color = ChunkyPNG::Color(*(options[:color].is_a?(Array) ? options[:color] : [options[:color]])) output_file = options[:file] module_px_size = nil border_px = nil png = nil if googleis total_image_size = options[:size] border_modules = options[:border_modules] module_px_size = (total_image_size.to_f / (@qrcode.module_count + 2 * border_modules).to_f).floor.to_i img_size = module_px_size * @qrcode.module_count remaining = total_image_size - img_size border_px = (remaining / 2.0).floor.to_i png = ChunkyPNG::Image.new(total_image_size, total_image_size, fill) else border = options[:border_modules] total_border = border * 2 module_px_size = if options[:resize_gte_to] (options[:resize_gte_to].to_f / (@qrcode.module_count + total_border).to_f).ceil.to_i else options[:module_px_size] end border_px = border * module_px_size total_border_px = border_px * 2 resize_to = options[:resize_exactly_to] img_size = module_px_size * @qrcode.module_count total_img_size = img_size + total_border_px png = ChunkyPNG::Image.new(total_img_size, total_img_size, fill) end @qrcode.modules.each_index do |x| @qrcode.modules.each_index do |y| if @qrcode.checked?(x, y) (0...module_px_size).each do |i| (0...module_px_size).each do |j| png[(y * module_px_size) + border_px + j, (x * module_px_size) + border_px + i] = color end end end end end if !googleis && resize_to png = png.resize(resize_to, resize_to) end if output_file constraints = { color_mode: options[:color_mode], bit_depth: options[:bit_depth] } constraints[:interlace] = options[:interlace] if options.has_key?(:interlace) constraints[:compression] = options[:compression] if options.has_key?(:compression) png.save(output_file, constraints) end png end end end end RQRCode::QRCode.send :include, RQRCode::Export::PNG rqrcode-3.2.0/lib/rqrcode/export/svg.rb000066400000000000000000000241301513000367400200240ustar00rootroot00000000000000# frozen_string_literal: true # This class creates a SVG files. # Initial code from: https://github.com/samvincent/rqrcode-rails3 module RQRCode module Export module SVG class BaseOutputSVG attr_reader :result def initialize(qrcode) @qrcode = qrcode @result = [] end end class Path < BaseOutputSVG # Direction constants for edge representation # Edges stored as [start_x, start_y, direction] arrays instead of Struct DIR_UP = 0 DIR_DOWN = 1 DIR_LEFT = 2 DIR_RIGHT = 3 # Pre-computed end coordinate deltas: [dx, dy] for each direction DIR_DELTAS = [ [0, -1], # UP [0, 1], # DOWN [-1, 0], # LEFT [1, 0] # RIGHT ].freeze # SVG path commands indexed by direction constant DIR_PATH_COMMANDS = ["v-", "v", "h-", "h"].freeze def build(module_size, options = {}) color = options[:color] offset_x = options[:offset_x].to_i offset_y = options[:offset_y].to_i modules_array = @qrcode.modules module_count = modules_array.length matrix_size = module_count + 1 # Edge matrix stores arrays of [x, y, direction] tuples edge_matrix = Array.new(matrix_size) { Array.new(matrix_size) } edge_count = 0 # Process horizontal edges (between vertically adjacent cells) (module_count + 1).times do |row_index| module_count.times do |col_index| above = row_index > 0 && modules_array[row_index - 1][col_index] below = row_index < module_count && modules_array[row_index][col_index] if above && !below # Edge going left at (col+1, row) x = col_index + 1 y = row_index (edge_matrix[y][x] ||= []) << [x, y, DIR_LEFT] edge_count += 1 elsif !above && below # Edge going right at (col, row) x = col_index y = row_index (edge_matrix[y][x] ||= []) << [x, y, DIR_RIGHT] edge_count += 1 end end end # Process vertical edges (between horizontally adjacent cells) module_count.times do |row_index| (module_count + 1).times do |col_index| left = col_index > 0 && modules_array[row_index][col_index - 1] right = col_index < module_count && modules_array[row_index][col_index] if left && !right # Edge going down at (col, row) x = col_index y = row_index (edge_matrix[y][x] ||= []) << [x, y, DIR_DOWN] edge_count += 1 elsif !left && right # Edge going up at (col, row+1) x = col_index y = row_index + 1 (edge_matrix[y][x] ||= []) << [x, y, DIR_UP] edge_count += 1 end end end path_parts = [] # Track search position to avoid re-scanning from beginning search_y = 0 search_x = 0 while edge_count > 0 # Find next non-empty cell, starting from last position start_edge = nil found_y = search_y found_x = search_x # Continue from where we left off (search_y...matrix_size).each do |y| start_col = (y == search_y) ? search_x : 0 (start_col...matrix_size).each do |x| cell = edge_matrix[y][x] next unless cell && !cell.empty? start_edge = cell.first found_y = y found_x = x break end break if start_edge end # Update search position for next iteration search_y = found_y search_x = found_x # Build path string directly without intermediate edge_loop array path_str = String.new(capacity: 64) path_str << "M" << start_edge[0].to_s << " " << start_edge[1].to_s current_edge = start_edge current_dir = nil current_count = 0 while current_edge ex, ey, edir = current_edge # Remove edge from matrix cell = edge_matrix[ey][ex] cell.delete(current_edge) edge_matrix[ey][ex] = nil if cell.empty? edge_count -= 1 # Accumulate consecutive edges in same direction if edir == current_dir current_count += 1 else # Flush previous direction path_str << DIR_PATH_COMMANDS[current_dir] << current_count.to_s if current_dir current_dir = edir current_count = 1 end # Find next edge at end coordinates delta = DIR_DELTAS[edir] next_x = ex + delta[0] next_y = ey + delta[1] next_cell = edge_matrix[next_y]&.[](next_x) current_edge = next_cell&.first end # Don't output the last direction segment - close path instead path_str << "z" path_parts << path_str end @result << %{} end end class Rect < BaseOutputSVG def build(module_size, options = {}) # Extract values from options color = options[:color] offset_x = options[:offset_x].to_i offset_y = options[:offset_y].to_i @qrcode.modules.each_index do |c| @qrcode.modules.each_index do |r| next unless @qrcode.checked?(c, r) x = r * module_size + offset_x y = c * module_size + offset_y @result << %() end end end end DEFAULT_SVG_ATTRIBUTES = [ %(version="1.1"), %(xmlns="http://www.w3.org/2000/svg"), %(xmlns:xlink="http://www.w3.org/1999/xlink"), %(xmlns:ev="http://www.w3.org/2001/xml-events") ] SVG_PATH_COMMANDS = { move: "M", up: "v-", down: "v", left: "h-", right: "h", close: "z" } # # Render the SVG from the Qrcode. # # Options: # offset - Padding around the QR Code in pixels # (default 0) # offset_x - X Padding around the QR Code in pixels # (default offset) # offset_y - Y Padding around the QR Code in pixels # (default offset) # fill - Background color e.g "ffffff" # (default none) # color - Foreground color e.g "000" # (default "000") # module_size - The Pixel size of each module # (defaults 11) # shape_rendering - SVG Attribute: auto | optimizeSpeed | crispEdges | geometricPrecision # (defaults crispEdges) # standalone - Whether to make this a full SVG file, or only an svg to embed in other svg # (default true) # use_path - Use to render SVG rather than to significantly reduce size # and quality. This will become the default in future versions. # (default false) # viewbox - replace `width` and `height` in with a viewBox, allows CSS scaling # (default false) # svg_attributes - A optional hash of custom attributes. Existing attributes will remain. # (default {}) # def as_svg(options = {}) fill = options[:fill] use_path = options[:use_path] offset = options[:offset].to_i offset_x = options.key?(:offset_x) ? options[:offset_x].to_i : offset offset_y = options.key?(:offset_y) ? options[:offset_y].to_i : offset color = options[:color] || "000" shape_rendering = options[:shape_rendering] || "crispEdges" module_size = options[:module_size] || 11 standalone = options[:standalone].nil? || options[:standalone] viewbox = options[:viewbox].nil? ? false : options[:viewbox] svg_attributes = options[:svg_attributes] || {} # height and width dependent on offset and QR complexity width = (@qrcode.module_count * module_size) + (2 * offset_x) height = (@qrcode.module_count * module_size) + (2 * offset_y) dimension = [width, height].max # use dimensions differently if we are using a viewBox dimensions_attr = viewbox ? %(viewBox="0 0 #{width} #{height}") : %(width="#{width}" height="#{height}") svg_tag_attributes = (DEFAULT_SVG_ATTRIBUTES + [ dimensions_attr, %(shape-rendering="#{shape_rendering}") ] + svg_attributes.map { |k, v| %(#{k}="#{v}") }).join(" ") xml_tag = %() open_tag = %() close_tag = "" # Prefix hexadecimal colors unless using a named color (symbol) color = "##{color}" unless color.is_a?(Symbol) output_tag = (use_path ? Path : Rect).new(@qrcode) output_tag.build(module_size, offset_x: offset_x, offset_y: offset_y, color: color) if fill # Prefix hexadecimal colors unless using a named color (symbol) fill = "##{fill}" unless fill.is_a?(Symbol) output_tag.result.unshift %() end if standalone output_tag.result.unshift(xml_tag, open_tag) output_tag.result << close_tag end output_tag.result.join end end end end RQRCode::QRCode.include RQRCode::Export::SVG ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/lib/rqrcode/qrcode.rb�����������������������������������������������������������������0000664�0000000�0000000�00000000077�15130003674�0017165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require "rqrcode/qrcode/qrcode" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/lib/rqrcode/qrcode/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0016634�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/lib/rqrcode/qrcode/qrcode.rb����������������������������������������������������������0000664�0000000�0000000�00000000521�15130003674�0020434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require "forwardable" module RQRCode # :nodoc: class QRCode extend Forwardable def_delegators :@qrcode, :to_s def_delegators :@qrcode, :modules # deprecated attr_reader :qrcode def initialize(string, *args) @qrcode = RQRCodeCore::QRCode.new(string, *args) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/lib/rqrcode/version.rb����������������������������������������������������������������0000664�0000000�0000000�00000000106�15130003674�0017366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true module RQRCode VERSION = "3.2.0" end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/rqrcode.gemspec�����������������������������������������������������������������������0000664�0000000�0000000�00000003224�15130003674�0016157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "rqrcode/version" Gem::Specification.new do |spec| spec.name = "rqrcode" spec.version = RQRCode::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 is a library for encoding QR Codes. The simple interface allows you to create QR Code data structures and then render them in the way you choose. EOF spec.homepage = "https://github.com/whomwah/rqrcode" spec.license = "MIT" spec.metadata = { "bug_tracker_uri" => "https://github.com/whomwah/rqrcode/issues", "changelog_uri" => "https://github.com/whomwah/rqrcode/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_dependency "chunky_png", "~> 1.0" spec.add_dependency "rqrcode_core", "~> 2.0" 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 "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.5" spec.add_development_dependency "stackprof", "~> 0.2" spec.add_development_dependency "standard", "~> 1.41" end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/spec/���������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0014104�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/spec/rqrcode/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15130003674�0015543�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������rqrcode-3.2.0/spec/rqrcode/data.rb������������������������������������������������������������������0000664�0000000�0000000�00000160165�15130003674�0017012�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AS_ANSI = <<~ANSI.chomp \e[47m \e[m\n\e[47m \e[m\n\e[47m \e[m\n\e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[40m \e[47m \e[m\n\e[47m \e[m\n\e[47m \e[m\n\e[47m \e[m\n\e[47m \e[m\n ANSI AS_SVG = <<~SVG.chomp SVG AS_SVG1 = <<~SVG.chomp SVG AS_SVG2 = <<~SVG.chomp SVG AS_SVG3 = <<~SVG.chomp SVG AS_SVG4 = <<~SVG.chomp SVG AS_SVG5 = <<~SVG.chomp SVG AS_SVG6 = <<~SVG.chomp SVG AS_SVG7 = <<~SVG.chomp SVG AS_SVG8 = <<~SVG.chomp SVG AS_SVG9 = <<~SVG.chomp SVG AS_SVG10 = <<~SVG.chomp SVG AS_BASIC = <<~STR XXXXXXXOOOXOXOOXXXOOOOXXXXXXX XOOOOOXOOXXXXXOOXOXOOOXOOOOOX XOXXXOXOOOOXOXOOOOOXOOXOXXXOX XOXXXOXOOXXXOOXOXXXOOOXOXXXOX XOXXXOXOXXXOOXOOXOOXOOXOXXXOX XOOOOOXOOOXXOOXXOXXXXOXOOOOOX XXXXXXXOXOXOXOXOXOXOXOXXXXXXX OOOOOOOOXXXOXXOXXOOOXOOOOOOOO OOXXOOXXXOOXOXOXOOXXOXXOXOOOO XXOOXXOOXOXXXOOOOOXOXXOXOXXOX XOXOOXXXXXOXXOOXXOXXXXXXXXOXO OOOOOOOXXOOOXOOOOXXOXXOXXOOOX XXXOXXXOXOOXOXOOOXOXOXOOOOXXX XOXXXXOOXOOOXXOXXOOXOOXXOXXOX XOOXXXXXOOXOOOOOOXXXXOXOXXOXX OOOOOOOXXXOXXOXOXOXOOOOOOXOOX XOOOXOXOXXXXXOXOOXXXOOOOOXOOO OOOXOXOXOOOOOOOXOOXXOXXOOOXOO XOXXOOXOXXOOXXOXOXOOXOXXXXOOO OOXXXXOXOXXXXXXOXOXOXXXXOOXXX OXOXXOXXOXXXOOOXXXOXXXXXXXOXO OOOOOOOOXOXOXOOXOOXXXOOOXXOXX XXXXXXXOXOXOOOOXXOOXXOXOXOXXO XOOOOOXOOOXOOXOXXOXOXOOOXOOOO XOXXXOXOOXOOOXXXOOOOXXXXXXXOX XOXXXOXOXXXXOXOOXXXXOXOOXXOOO XOXXXOXOXOOOXOOXOOOXXOOOOXXOX XOOOOOXOOXXOXXOOOOOOOOXOXXXXO XXXXXXXOOOXXXXOOOOXXOOXXOOXXO STR AS_HTML = <<~HTML.chomp
HTML rqrcode-3.2.0/spec/rqrcode/export_ansi_spec.rb000066400000000000000000000004231513000367400214340ustar00rootroot00000000000000require "spec_helper" require "rqrcode/data" describe "Export::ANSI" do it "must respond_to ansi" do expect(RQRCode::QRCode.new("x")).to respond_to(:as_ansi) end it "must export to ansi" do expect(RQRCode::QRCode.new("ansi").as_ansi).to eq(AS_ANSI) end end rqrcode-3.2.0/spec/rqrcode/export_html_spec.rb000066400000000000000000000003771513000367400214560ustar00rootroot00000000000000require "spec_helper" describe "Export::HTML" do it "must respond_to html" do expect(RQRCode::QRCode.new("html")).to respond_to(:as_html) end it "must export to html" do expect(RQRCode::QRCode.new("html").as_html).to eq(AS_HTML) end end rqrcode-3.2.0/spec/rqrcode/export_png_spec.rb000066400000000000000000000124411513000367400212710ustar00rootroot00000000000000require "spec_helper" describe "Export::PNG" do let(:mockImage) { double(:mock_image) } before :each do allow(mockImage).to receive(:[]=) end it "must respond_to png" do expect(RQRCode::QRCode.new("x")).to respond_to(:as_png) end it "must export to png file" do expect(RQRCode::QRCode.new("png").as_png).to be_instance_of(ChunkyPNG::Image) end it "must export a png using the correct defaults" do expect(ChunkyPNG::Image).to receive(:new) .once .with(120, 120, 4294967295) .and_return(mockImage) expect(mockImage).not_to receive(:save) RQRCode::QRCode.new("png").as_png end context "with various color inputs" do before :each do allow(ChunkyPNG).to receive(:Color).and_call_original expect(mockImage).to receive(:save) .once .with("some/path", {bit_depth: 1, color_mode: 0}) end it "should handle the defaults" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path" ) expect(ChunkyPNG).to have_received(:Color).with("black").once expect(ChunkyPNG).to have_received(:Color).with("white").once end it "should handle a blue 'color'" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path", color: "blue" ) expect(ChunkyPNG).to have_received(:Color).with("blue").once expect(ChunkyPNG).to have_received(:Color).with("white").once end it "should handle a #FC0000 'color'" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path", color: "#FC0000" ) expect(ChunkyPNG).to have_received(:Color).with("#FC0000").once expect(ChunkyPNG).to have_received(:Color).with("white").once end it "should handle an rgb 'color'" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path", color: [0, 0, 0] ) expect(ChunkyPNG).to have_received(:Color).with(0, 0, 0).once expect(ChunkyPNG).to have_received(:Color).with("white").once end it "should handle a green 'fill'" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 8388863) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path", fill: "green" ) expect(ChunkyPNG).to have_received(:Color).with("black").once expect(ChunkyPNG).to have_received(:Color).with("green").once end it "should handle an rgb 'fill'" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) RQRCode::QRCode.new("png").as_png( file: "some/path", fill: [255, 255, 255] ) expect(ChunkyPNG).to have_received(:Color).with("black").once expect(ChunkyPNG).to have_received(:Color).with(255, 255, 255).once end end it "should not handle nonsense color " do expect { RQRCode::QRCode.new("png").as_png( file: "some/path", color: "madeupcolor" ) }.to raise_error(ArgumentError, "Unknown color name madeupcolor!") end it "should not handle nonsense fill " do expect { RQRCode::QRCode.new("png").as_png( file: "some/path", fill: "madeupcolor" ) }.to raise_error(ArgumentError, "Unknown color name madeupcolor!") end context "with file save and constaints" do it "should export using the correct defaults" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) expect(mockImage).to receive(:save) .once .with("some/path", {bit_depth: 1, color_mode: 0}) RQRCode::QRCode.new("png").as_png( file: "some/path" ) end it "should export using custom constraints" do expect(ChunkyPNG::Image).to receive(:new) .once .with(174, 174, 4294967295) .and_return(mockImage) expect(mockImage).to receive(:save) .once .with("some/path", { bit_depth: 8, color_mode: 2, interlace: true, compression: 5 }) RQRCode::QRCode.new("png").as_png( color_mode: ChunkyPNG::COLOR_TRUECOLOR, bit_depth: 8, file: "some/path", color: "red", interlace: true, compression: 5 ) end it "should save" do qrcode = RQRCode::QRCode.new("http://github.com/") png = qrcode.as_png( bit_depth: 1, border_modules: 4, color_mode: ChunkyPNG::COLOR_GRAYSCALE, color: "black", file: nil, fill: "white", module_px_size: 6, resize_exactly_to: false, resize_gte_to: false, size: 120 ) IO.binwrite("/tmp/github-qrcode.png", png.to_s) expect(IO.binread("/tmp/github-qrcode.png")).to eq png.to_s end end end rqrcode-3.2.0/spec/rqrcode/export_svg_spec.rb000066400000000000000000000223761513000367400213140ustar00rootroot00000000000000require "spec_helper" require "rqrcode/data" describe "Export::SVG" do it "must respond_to svg" do expect(RQRCode::QRCode.new("qrcode")).to respond_to(:as_svg) end context "with use_rect (default) option" do it "must export to svg" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg).to eq(AS_SVG) end end context "with use_path option" do it "must export to svg" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true )).to eq(AS_SVG1) end end context "with various options" do it "must export to svg" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( module_size: 10, use_path: true, offset: 40, color: "ff0000", fill: "ffcc00" )).to eq(AS_SVG2) end end context "standalone false" do it "will not include the " do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( standalone: false, use_path: true )).to eq(AS_SVG3) end end context "viewbox true" do it "will use the viewBox attr" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( viewbox: true, use_path: true )).to eq(AS_SVG4) end end context "svg_attributes" do it "renders `svg_attributes` when provided " do expect(RQRCode::QRCode.new("qrcode").as_svg( use_path: true, svg_attributes: { id: "myUniqueId", class: "myClass" } )).to eq(AS_SVG5) end end context "with color name (symbol)" do it "does not include # prefix for color" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true, color: :red, fill: :yellow )).to eq(AS_SVG6) end end context "with offset_x and offset_y options" do it "applies different x and y offsets with use_path for smaller output" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true, offset_x: 20, offset_y: 30 )).to eq(AS_SVG7) end it "uses offset as fallback when specific offsets aren't provided" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true, offset: 25 )).to eq(AS_SVG8) end end context "with fill and offset options combined" do it "combines fill with global offset" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true, fill: "ccffcc", offset: 10 )).to eq(AS_SVG9) end it "combines fill with specific offsets" do expect(RQRCode::QRCode.new("https://kyan.com").as_svg( use_path: true, fill: "ccffcc", offset_x: 15, offset_y: 20 )).to eq(AS_SVG10) end end context "with shape_rendering option" do it "uses default crispEdges when not specified" do svg = RQRCode::QRCode.new("test").as_svg expect(svg).to include('shape-rendering="crispEdges"') end it "applies custom shape_rendering value" do svg = RQRCode::QRCode.new("test").as_svg(shape_rendering: "geometricPrecision") expect(svg).to include('shape-rendering="geometricPrecision"') end it "supports optimizeSpeed shape_rendering" do svg = RQRCode::QRCode.new("test").as_svg(shape_rendering: "optimizeSpeed") expect(svg).to include('shape-rendering="optimizeSpeed"') end end context "SVG element structure" do it "generates rect elements when use_path is false (default)" do svg = RQRCode::QRCode.new("test").as_svg expect(svg).to include("') end it "includes required SVG namespace attributes" do svg = RQRCode::QRCode.new("test").as_svg expect(svg).to include('xmlns="http://www.w3.org/2000/svg"') expect(svg).to include('xmlns:xlink="http://www.w3.org/1999/xlink"') expect(svg).to include('version="1.1"') end it "closes SVG tag properly" do svg = RQRCode::QRCode.new("test").as_svg expect(svg).to end_with("") end end context "dimension calculations" do let(:qr) { RQRCode::QRCode.new("test") } let(:module_count) { qr.instance_variable_get(:@qrcode).module_count } it "calculates dimensions based on module_count and default module_size" do svg = qr.as_svg expected_size = module_count * 11 # default module_size is 11 expect(svg).to include(%(width="#{expected_size}" height="#{expected_size}")) end it "calculates dimensions with custom module_size" do svg = qr.as_svg(module_size: 5) expected_size = module_count * 5 expect(svg).to include(%(width="#{expected_size}" height="#{expected_size}")) end it "includes offsets in dimension calculations" do svg = qr.as_svg(module_size: 10, offset: 20) expected_size = (module_count * 10) + (2 * 20) expect(svg).to include(%(width="#{expected_size}" height="#{expected_size}")) end it "calculates asymmetric dimensions with different x and y offsets" do svg = qr.as_svg(module_size: 10, offset_x: 15, offset_y: 25) expected_width = (module_count * 10) + (2 * 15) expected_height = (module_count * 10) + (2 * 25) expect(svg).to include(%(width="#{expected_width}" height="#{expected_height}")) end end context "color handling" do it "prefixes hex color with # for color option" do svg = RQRCode::QRCode.new("test").as_svg(use_path: true, color: "ff0000") expect(svg).to include('fill="#ff0000"') end it "prefixes hex color with # for fill option" do svg = RQRCode::QRCode.new("test").as_svg(use_path: true, fill: "00ff00") expect(svg).to include('fill="#00ff00"') end it "does not prefix symbol colors" do svg = RQRCode::QRCode.new("test").as_svg(use_path: true, color: :blue) expect(svg).to include('fill="blue"') expect(svg).not_to include('fill="#blue"') end it "uses default color 000 when not specified" do svg = RQRCode::QRCode.new("test").as_svg(use_path: true) expect(svg).to include('fill="#000"') end it "supports 3-character hex codes" do svg = RQRCode::QRCode.new("test").as_svg(use_path: true, color: "f00") expect(svg).to include('fill="#f00"') end end context "viewbox with use_rect" do it "uses viewBox attribute instead of width/height" do qr = RQRCode::QRCode.new("test") svg = qr.as_svg(viewbox: true) # use_rect is default module_count = qr.instance_variable_get(:@qrcode).module_count expected_size = module_count * 11 expect(svg).to include(%(viewBox="0 0 #{expected_size} #{expected_size}")) expect(svg).not_to include(%(width="#{expected_size}")) expect(svg).not_to include(%(height="#{expected_size}")) end end context "fill background with use_rect" do it "adds background rect before module rects" do svg = RQRCode::QRCode.new("test").as_svg(fill: "ffffff") # Background rect should be first rect after svg open tag expect(svg).to match(/]*>]*fill="#ffffff"/) end end context "standalone false with use_rect" do it "outputs only rect elements without XML declaration or SVG wrapper" do svg = RQRCode::QRCode.new("test").as_svg(standalone: false) expect(svg).not_to include("") expect(svg).to include(" "qr-code", :class => "qr-image", "data-content" => "test", :role => "img" } ) expect(svg).to include('id="qr-code"') expect(svg).to include('class="qr-image"') expect(svg).to include('data-content="test"') expect(svg).to include('role="img"') end end context "with different QR code complexities" do it "handles short content" do svg = RQRCode::QRCode.new("a").as_svg(use_path: true) expect(svg).to include("") end it "handles URL content" do svg = RQRCode::QRCode.new("https://example.com/path?query=value").as_svg(use_path: true) expect(svg).to include("") end it "handles content with special characters" do svg = RQRCode::QRCode.new("Hello, World! @#$%").as_svg(use_path: true) expect(svg).to include("") end end context "path output compactness" do it "generates smaller output with use_path than use_rect for same content" do qr = RQRCode::QRCode.new("https://example.com") svg_rect = qr.as_svg(use_path: false) svg_path = qr.as_svg(use_path: true) expect(svg_path.length).to be < svg_rect.length end end end rqrcode-3.2.0/spec/rqrcode/qrqcode_spec.rb000066400000000000000000000025511513000367400205430ustar00rootroot00000000000000require "spec_helper" require "rqrcode/data" describe "RQRCode" do it "must provide a custom to_s" do qr = RQRCode::QRCode.new("http://kyan.com", size: 3) expect(qr.to_s[0..50]).to eq("xxxxxxx x x xxx xxxxxxx\nx x xxxxx x x ") expect(qr.to_s(dark: "q", light: "n")[0..36]).to eq("qqqqqqqnnnqnqnnqqqnnnnqqqqqqq\nqnnnnnq") expect(qr.to_s(dark: "@")[0..21]).to eq("@@@@@@@ @ @ @@@ ") end it "must expose the core qrcode" do expect(RQRCode::QRCode.new("svg").qrcode).to be_instance_of(RQRCodeCore::QRCode) end it "should do a basic render" do qr = RQRCode::QRCode.new("http://kyan.com") str = "" qr.qrcode.modules.each do |row| row.each do |col| str << (col ? "X" : "O") end str << "\n" end expect(str).to eq(AS_BASIC) end it 'should do a basic render using old "modules" interface' do qr = RQRCode::QRCode.new("http://kyan.com") str = "" qr.modules.each do |row| row.each do |col| str << (col ? "X" : "O") end str << "\n" end expect(str).to eq(AS_BASIC) end it "should pass options to rqrcode_core" do options = { size: 5, mode: :alphanumeric } qr = RQRCode::QRCode.new("QRCODE", options) expect(qr.qrcode.mode).to eq(:mode_alpha_numk) expect(qr.qrcode.version).to eq(options[:size]) end end rqrcode-3.2.0/spec/spec_helper.rb000066400000000000000000000117751513000367400167350ustar00rootroot00000000000000# This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause # this file to always be loaded, without a need to explicitly require it in any # files. require "bundler/setup" require "rqrcode" # # Given that it is always loaded, you are encouraged to keep this file as # light-weight as possible. Requiring heavyweight dependencies from this file # will add to the boot time of your test suite on EVERY test run, even for an # individual file that may not need all of that loaded. Instead, consider making # a separate helper file that requires the additional dependencies and performs # the additional setup, and require it from the spec files that actually need # it. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # This option will default to `:apply_to_host_groups` in RSpec 4 (and will # have no way to turn it off -- the option exists only for backwards # compatibility in RSpec 3). It causes shared context metadata to be # inherited by the metadata hash of host groups and examples, rather than # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. # # This allows you to limit a spec run to individual examples or groups # # you care about by tagging them with `:focus` metadata. When nothing # # is tagged with `:focus`, all examples get run. RSpec also provides # # aliases for `it`, `describe`, and `context` that include `:focus` # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. # config.filter_run_when_matching :focus # # # Allows RSpec to persist some state between runs in order to support # # the `--only-failures` and `--next-failure` CLI options. We recommend # # you configure your source control system to ignore this file. # config.example_status_persistence_file_path = "spec/examples.txt" # # # Limits the available syntax to the non-monkey patched syntax that is # # recommended. For more details, see: # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode # config.disable_monkey_patching! # # # This setting enables warnings. It's recommended, but in some cases may # # be too noisy due to issues in dependencies. # config.warnings = true # # # Many RSpec users commonly either run the entire suite or an individual # # file, and it's useful to allow more verbose output when running an # # individual spec file. # if config.files_to_run.one? # # Use the documentation formatter for detailed output, # # unless a formatter has already been configured # # (e.g. via a command-line flag). # config.default_formatter = "doc" # end # # # Print the 10 slowest examples and example groups at the # # end of the spec run, to help surface which specs are running # # particularly slow. # config.profile_examples = 10 # # # Run specs in random order to surface order dependencies. If you find an # # order dependency and want to debug it, you can fix the order by providing # # the seed, which is printed after each run. # # --seed 1234 # config.order = :random # # # Seed global randomization in this process using the `--seed` CLI option. # # Setting this allows you to use `--seed` to deterministically reproduce # # test failures related to randomization by passing the same `--seed` value # # as the one that triggered the failure. # Kernel.srand config.seed end