Skip to content

Deterministic, zero-dependency QR Code Model 2 encoder for TS/JS. Outputs a binary module matrix and metadata only (no rendering)

License

Notifications You must be signed in to change notification settings

gokerDEV/qr-core

Repository files navigation

qr-core

npm JSR license buy me a coffee github sponsor

Deterministic, zero-dependency QR Code Model 2 encoder for TypeScript/JavaScript. Outputs a binary module matrix and metadata only (no rendering).

RFC: See RFC.md for the normative specification.

Highlights

  • Deterministic output: Same input + same options ⇒ identical version, mask, and matrix.
  • Strict typing: TypeScript 5.x, strict mode.
  • RFC-aligned: Numeric, alphanumeric, and byte modes; versions 1–40; ECC L/M/Q/H.
  • Read‑only matrix: Consumers cannot mutate the QR matrix.
  • Zero dependencies: Small, portable, runtime-agnostic.

Installation

npm install qr-core

Quick Start

import { encode } from "qr-core";

const qr = encode("HELLO WORLD");
console.log(qr.version, qr.mask, qr.size);
console.log(qr.matrix.get(0, 0));

Usage

import { encode, encodeSafe } from "qr-core";

// Basic
const qr = encode("https://example.com");

// Advanced options
const qr2 = encode("https://example.com", {
  ecc: "H",          // L | M | Q | H
  version: 5,         // 1..40 or "auto"
  mask: "auto",      // 0..7 or "auto"
  mode: "auto",      // numeric | alphanumeric | byte | auto
  charset: "utf-8",  // utf-8 | iso-8859-1
  quietZone: 4,       // metadata only
  strict: true
});

// Non-throwing variant
const result = encodeSafe("1234", { mode: "numeric" });
if (result.ok) {
  console.log(result.value.version);
} else {
  console.error(result.error.code, result.error.message);
}

API

encode(input, options?) => QrCode

  • input: string | Uint8Array
  • options: EncodeOptions (see below)
  • throws QrException on invalid input/options when strict=true

encodeSafe(input, options?) => Result<QrCode, QrError>

Non-throwing wrapper that returns { ok: true, value } or { ok: false, error }.

Types

export type EccLevel = "L" | "M" | "Q" | "H";
export type QrMode = "auto" | "byte" | "alphanumeric" | "numeric";
export type MaskId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;

export interface EncodeOptions {
  ecc?: EccLevel;                   // default: "M"
  version?: number | "auto";       // default: "auto"
  mask?: MaskId | "auto";          // default: "auto"
  mode?: QrMode;                    // default: "auto"
  charset?: "utf-8" | "iso-8859-1";// default: "utf-8"
  quietZone?: number;               // default: 4 (metadata only)
  strict?: boolean;                 // default: true
}

export interface BitMatrix {
  readonly size: number;
  get(x: number, y: number): 0 | 1;
  toPacked?(): Uint8Array;          // row-major, MSB-first, row padded to byte
  toRows?(): ReadonlyArray<Uint8Array>;
}

export interface QrCode {
  version: number;
  ecc: EccLevel;
  mask: MaskId;
  size: number;
  matrix: BitMatrix;
  meta?: QrMeta;
}

Matrix Packing Format

If toPacked() is available, it returns row-major, MSB‑first bits. Each row is padded to the next byte boundary with zero bits. Coordinates use (0,0) as the top‑left module.

Benchmarks

Run npm run bench to reproduce qr-core numbers. Run npm run compare to compare qr-core vs Nayuki (this builds a separate compare bundle via tsconfig.compare.json).

You can tune runtime for quick/slow passes:

BENCH_RUNS=3 BENCH_ITERS=200 BENCH_BATCH=5 BENCH_WARMUP=50 npm run bench

Compare scenario details:

  • Short text: input "HELLO WORLD", ecc M, auto version/mask.
  • URL (medium): input https://example.com/search?q=${"A".repeat(480)}, ecc M, auto version/mask.
  • vCard-like: multi-line vCard-ish text, ecc Q, auto version/mask.
  • Large payload: input "A".repeat(3000), ecc L, auto version/mask.

Methodology

The benchmark harness runs a warmup phase, followed by BENCH_RUNS measurement cycles. In each cycle, the task is executed BENCH_ITERS * BENCH_BATCH times. To minimize noise, garbage collection is forced (if exposed) before each cycle. We report:

  • p50 (median): The median time per operation across runs.
  • p95: The 95th percentile time per operation, indicating tail latency.

Bench (qr-core only)

Runtime Short text p50 / p95 (ms) URL (medium) p50 / p95 (ms) Large payload p50 / p95 (ms)
Node v23.11.0 0.046 / 0.051 1.193 / 1.205 3.897 / 3.899
Bun v1.3.8 0.040 / 0.053 1.154 / 1.171 3.736 / 3.736
Deno v2.6.7 0.041 / 0.046 1.179 / 1.213 3.866 / 3.876

Compare (qr-core vs Nayuki)

Runtime Short text (qr-core / Nayuki p50) URL (medium) (qr-core / Nayuki p50) vCard-like (qr-core / Nayuki p50) Large payload (qr-core / Nayuki p50)
Node v23.11.0 0.041 / 0.211 1.181 / 4.034 0.485 / 1.566 3.908 / 12.070
Bun v1.3.8 0.040 / 0.190 1.155 / 3.900 0.467 / 1.317 3.753 / 11.902
Deno v2.6.7 0.040 / 0.205 1.204 / 3.957 0.489 / 1.530 3.898 / 11.952

Benchmarks captured on 2026‑02‑01 (darwin arm64) with BENCH_RUNS=3 BENCH_ITERS=200 BENCH_BATCH=5 BENCH_WARMUP=50. Goal from RFC: version‑40 near‑capacity with auto‑mask < 5ms — met.

Contributing

Contributions are welcome. If you plan a larger change, please open an issue first to align on scope.

Development

npm install
npm test

Status & Roadmap

  • v1 scope: numeric, alphanumeric, byte; versions 1–40; ECC L/M/Q/H; auto version/mask; deterministic output.
  • Non‑goals in v1: rendering, Kanji, ECI, Structured Append, Micro QR.
  • Review notes:
    • Golden tests currently validate only version/size; matrix hash/bit equality is still missing.
    • V7+ version info bit placement is only indirectly validated (reservation checks), not byte‑level correctness.
    • Segmentation cost estimation uses V1 character count sizes; V10+ may choose larger versions than necessary.
    • Zigzag placement has no explicit “all bits consumed” assertion, so silent data loss is possible if reserved areas are wrong.

Guidelines

  • Keep output deterministic across runtimes.
  • Prefer small, well‑scoped PRs with tests.
  • Preserve strict typing and zero‑dependency policy.

License

MIT

About

Deterministic, zero-dependency QR Code Model 2 encoder for TS/JS. Outputs a binary module matrix and metadata only (no rendering)

Resources

License

Stars

Watchers

Forks

Packages

No packages published