rqrcode.cr
rqrcode.cr
A 1:1 Crystal port of whitesmith/rqrcode 2.2.0 (+ the core of rqrcode_core 1.2.0) — QR code encoding and rendering to HTML/ANSI/SVG/PNG. Suite: 83 examples, 0 failures; the oracle replays matrices, errors and render output recorded from the real gems, 1:1.
Installation
dependencies:
rqrcode_cr:
github: OrelSokolov/rqrcode.cr
shards install
No external dependencies (the PNG renderer replaces chunky_png, see below).
Usage
require "rqrcode_cr"
qr = RQRCode::QRCode.new("https://example.com")
qr.as_html
qr.as_ansi(dark: "█", light: " ")
qr.as_svg(module_size: 6, use_path: true)
qr.as_png(size: 240, border_modules: 4) # => PNG blob
qr.checked?(0, 0)
qr.modules
# multi-segment input
core = RQRCodeCore::QRCode.new([
{data: "123", mode: :number},
{data: "abc", mode: :byte_8bit},
])
Feature comparison vs rqrcode 2.2.0 / rqrcode_core 1.2.0
Encoding (rqrcode_core)
| Feature | Original (rqrcode_core) | Port (rqrcode.cr) | Status |
|---|---|---|---|
QRCode.new(data, level:, size:, max_size:, mode:) — String, QRSegment or segment array |
✅ | Same | ✅ |
Modes :number / :alphanumeric / :byte_8bit (auto-detect) |
✅ | Same | ✅ |
Error-correction levels :l/:m/:q/:h (default :h), versions 1–40, auto version selection |
✅ | Same | ✅ |
| Multi-segment UTF-8 input | ✅ | Same | ✅ |
checked?(row, col) (+ deprecated dark?), modules, module_count, version, mode, error_correction_level, multi_segment?, inspect, to_s(dark:, light:, quiet_zone_size:) |
✅ | Same | ✅ |
Errors: QRCodeArgumentError (< ArgumentError), QRCodeRunTimeError — same messages |
✅ | Same | ✅ |
Non-string scalar data (CORE.new(123)) |
raises QRCodeArgumentError |
Compile-time error in Crystal (case excluded from the oracle) | ❌ (equivalent: compile-time check) |
Rendering (rqrcode)
| Feature | Original | Port | Status |
|---|---|---|---|
as_html |
✅ | Same | ✅ |
as_ansi(light:, dark:, fill_character:, quiet_zone_size:) |
✅ | Same | ✅ |
as_svg(fill:, color:, offset:, module_size:, shape_rendering:, standalone:, use_path:, viewbox:, svg_attributes:) — rect and path variants |
✅ | Same | ✅ |
as_png(size:, border_modules:, module_px_size:, border:, resize_exactly_to:, resize_gte_to:, fill:, color:, file:) — both sizing algorithms (exactly / gte) with exact pixel geometry |
✅ | Same | 🔶 |
PNG colors "black"/"white", hex 3/6, [r,g,b]/[r,g,b,a] |
✅ | Same | ✅ |
| PNG color_mode/bit_depth/interlace/compression options | ✅ | Accepted but ignored | 🔶 |
Quirks preserved intentionally (oracle-confirmed)
as_png(..., border:)is effectively ignored (border_moduleswins).- Integer division in
demerit_points_4. get_maskcompares>instead of>=.
Deviations & Crystal-specific notes
- Options are named arguments instead of Ruby trailing-hash (
extract_options!→def to_s(dark = "x", ...));as_pngkeeps**optionsbecause the "passed or not" distinction selects the sizing algorithm. Int#between?is a monkeypatch (Ruby builtin, absent from the Crystal stdlib).- The unreachable
:mode_multibranch ofQRSegmentin the writer is omitted.
PNG without chunky_png
RQRCode::Export::PNG::Image is a minimal chunky_png replacement: an RGBA canvas with new/[]=/save/to_s/resize/width/height. Resize is an exact port of chunky_png's resample_nearest_neighbor (Bresenham error accumulation, not floor scaling). The encoder writes 8-bit grayscale (chunky_png writes 1-bit — files are larger but valid, verified with the ChunkyPNG decoder). CRC32 is a built-in table (not exported by the Crystal stdlib).
Testing
ruby oracle/cases.rb > oracle/expected.yml # re-record against the real gems
crystal spec # 83 examples
spec/oracle_spec.cr— 57 cases recorded from the real gems: matrices (v1–v40, all L/M/Q/H levels, all modes, multi-segment, UTF-8), errors (class + message), HTML/ANSI/SVG (rect + path)/PNG (pixel geometry of both sizing algorithms + resize).spec/rqrcode_spec.cr— 26 unit tests on the API (QRMath, QRUtil, RS blocks, QRSegment, buffer, renderers), ported from README/API scenarios (the originals ship no test files in the gem package).
rqrcode.cr
- 0
- 0
- 0
- 1
- 0
- 2 days ago
- October 12, 2026
Mon, 12 Oct 2026 05:23:58 GMT