qr2term-rs
A stupidly simple Rust crate that renders QR codes directly in the terminal using color-inverted Unicode half-blocks.
Repository Health
Technical Analysis
qr2term is a small, focused Rust library for printing scannable QR codes straight to a terminal. It wraps the qrcode crate for the actual barcode encoding and crossterm for cross-platform terminal styling, and adds a rendering technique that packs two QR pixel rows into a single character cell using color-inverted ”▄” glyphs — avoiding the vertical gaps that plain block characters produce on many terminal fonts.
The public API is deliberately minimal: print_qr() writes an ANSI-colored code straight to stdout, and generate_qr_string() returns the same output as a String for custom handling. Internally, a generic Matrix<T> type handles pixel storage and quiet-zone padding, keeping the QR-generation, matrix, and rendering concerns cleanly separated across three small modules. It’s a good fit for CLI tools, setup wizards, and any terminal-only workflow that needs to hand a URL, token, or WiFi credential to a phone.
What You Get
- Two convenience entry points -
print_qr()writes an ANSI-colored QR code straight to stdout,generate_qr_string()returns the same rendering as aStringfor custom output handling - Compact terminal rendering - a technique that packs two QR code pixels into each terminal character using color-inverted half-block Unicode glyphs, avoiding the render gaps other approaches produce
- A reusable
Matrix<T>type - a generic 2D pixel grid withsize()andsurround()(quiet-zone padding) that can be composed into custom rendering pipelines - Typed error propagation -
QrErrorre-exported from theqrcodecrate so encoding failures (e.g. data too long for the QR spec) surface asResultvalues, not panics - A minimal dependency surface - built on just
crossterm(styling) andqrcode(encoding), both with default features disabled to keep the footprint small
Common Use Cases
- Printing a URL or login link as a scannable QR code directly in a CLI tool’s terminal output
- Generating WiFi credential QR codes (the bundled
example-wifi.rsbuilds aWIFI:S:...;T:...;P:...;;payload) for quick phone pairing - Piping arbitrary text or tokens from another command into a terminal QR code, as shown in the
example-read.rsexample - Embedding QR output inside larger terminal UIs or setup wizards that need to hand a value (API key, pairing code, deep link) to a mobile device
Under The Hood
Architecture
The crate is a thin, cleanly layered module set. lib.rs exposes print_qr() and generate_qr_string(), both delegating to qr::Qr::from() (a wrapper around the qrcode crate’s QrCode) to produce a Matrix<Color>, then to render::Renderer to turn that matrix into terminal output. matrix.rs owns a generic square Matrix<T> with sizing and quiet-zone-padding logic, render.rs owns terminal-specific presentation (pairing rows into color-inverted half-block characters), and a small util.rs provides an integer-square-root helper shared by matrix sizing and render row-pairing math. There’s no dependency-injection layer — composition happens through plain generics (Renderer::render<W: Write>) and Default. Because qr.rs and render.rs both assume a square, row-major pixel layout from Matrix<T>, any change to that core abstraction would ripple through both.
Tech Stack
Rust edition 2021 (MSRV 1.67.1), published to crates.io with docs on docs.rs. Two runtime dependencies, both with default features disabled to minimize footprint: crossterm 0.28 (cross-platform terminal styling/coloring, with the windows feature explicitly enabled) and qrcode 0.14 (QR code encoding). One dev-dependency, regex 1, used only in render.rs tests to strip ANSI escape codes when measuring rendered output width and height. CI runs on GitLab (.gitlab-ci.yml) rather than GitHub Actions — the .github directory holds only issue templates and funding metadata.
Code Quality
Both matrix.rs and render.rs carry #[cfg(test)] unit tests (padding/surround behavior, size-mismatch panics, and a parametrized width/height check across several matrix sizes), and qr.rs has an oversized-input #[should_panic] test. Error handling is mostly typed and explicit — QrError propagates through Result-returning public functions via ? — though print_stdout() and generate_qr_string() call .expect() on I/O writes, treating terminal write failures as unrecoverable rather than propagated, and qr.rs carries an explicit // TODO: error handle here! acknowledging one spot that isn’t fully handled. Naming is clear and idiomatic throughout, with doc comments on all public items.
What Makes It Unique
The crate’s one genuinely distinctive technical choice is documented directly in render.rs: rather than the naive approach of stacking full-block and half-block glyphs (which renders with a visible gap on many terminal fonts), it uses a single ”▄” character with inverted foreground/background coloring to represent every combination of two stacked QR pixels, eliminating the gap artifact. The author credits this rendering approach to an earlier project (qair) rather than claiming it as original. Otherwise the crate is intentionally minimal — a small, well-scoped wrapper composing qrcode and crossterm rather than reimplementing QR encoding or terminal styling itself.