Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
# @suaveplan/qrcode - QR code generation producing SVG, pixel matrix, terminal (Unicode blocks), and data URL representations. Implements QR code versions 1-10, error correction levels L/M/Q/H, automatic version and mode selection.
## Summary
QR code generation producing SVG, pixel matrix, terminal (Unicode blocks), and data URL representations. Implements QR code versions 1-10, error correction levels L/M/Q/H, automatic version and mode selection.
<!-- begin:auto-docs -->
## Documentation tree
- [core](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->- data-encoder — <!-- begin:auto-docs --> _(no submodu…
- [encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [data-encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [mask](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [masking](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [matrix](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [matrix-builder](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [svg-renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [qrcode](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->- data-encoder — <!-- begin:auto-docs --> _(no submodu…
- [errors](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [qrcode-error](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [data-encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode-error](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:au…
- [encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [data-encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:au…
- [errors](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode-error](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:auto-docs --> _(no submod…
- [encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [data-encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:auto-docs --> _(no submod…
- [errors](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode-error](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_ - [data-encoder](./src/core/encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_ - [masking](./src/core/mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_ - [matrix-builder](./src/core/matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_ - [reed-solomon](./src/core/reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_ - [svg-renderer](./src/core/renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_ - [tables](./src/core/tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./src/core/qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [qrcode-error](./src/errors/qrcode-error.md) — <!-- begin:auto-docs --> _(no submodules)_
## Installation
```bash
bun add @suaveplan/qrcode
```
## Quick Start
```ts
import { generate } from "@suaveplan/qrcode";
const result = generate("https://suaveplan.io");
console.log(result.svg); // Inline SVG string
console.log(result.dataUrl); // data:image/svg+xml;base64,...
console.log(result.terminal); // Unicode half-block for CLI display
console.log(result.version); // e.g. 2
console.log(result.size); // e.g. 25 (version 2 = 25×25)
```
## API
### `generate(data, options?)`
Generates a complete QR code for any UTF-8 string.
```ts
import { generate } from "@suaveplan/qrcode";
const result = generate("Hello World", {
errorCorrectionLevel: "H", // 'L' | 'M' | 'Q' | 'H', default 'M'
version: 2, // 1-10, auto-selected if omitted
margin: 4, // quiet zone in modules, default 4
color: "#000000", // dark module color, default '#000000'
background: "#ffffff", // light module color, default '#ffffff'
scale: 10, // pixels per module in SVG, default 10
});
```
**Returns** `QRCodeResult`:
| Field | Type | Description |
|-------|------|-------------|
| `version` | `number` | QR version used (1-10) |
| `errorCorrectionLevel` | `ErrorCorrectionLevel` | EC level used |
| `mode` | `QRMode` | Encoding mode (numeric/alphanumeric/byte) |
| `matrix` | `boolean[][]` | Module grid — `true` = dark |
| `size` | `number` | Matrix dimension (size × size) |
| `svg` | `string` | Complete SVG markup |
| `dataUrl` | `string` | `data:image/svg+xml;base64,...` |
| `terminal` | `string` | Unicode half-block terminal representation |
### Errors
```ts
import { QRCodeError, QRCODE_CODES } from "@suaveplan/qrcode/errors";
try {
generate("", { version: 1, errorCorrectionLevel: "H" });
} catch (e) {
if (e instanceof QRCodeError) {
switch (e.code) {
case QRCODE_CODES.INVALID_DATA:
// Empty input, invalid characters, or out-of-range version
break;
case QRCODE_CODES.VERSION_TOO_SMALL:
// Data too large for the specified version (or all versions 1-10)
break;
case QRCODE_CODES.ENCODE_FAILED:
// Internal encoding pipeline failure
break;
}
}
}
```
## Module Exports
| Subpath | Contents |
|---------|----------|
| `@suaveplan/qrcode` | `generate`, all types, all sub-module exports |
| `@suaveplan/qrcode/errors` | `QRCodeError`, `QRCODE_CODES`, `QRCodeCode` |
| `@suaveplan/qrcode/core/tables` | Lookup tables and helpers |
| `@suaveplan/qrcode/core/encoder` | `detectMode`, `encodeData`, `BitBuffer`, `encodeUtf8` |
| `@suaveplan/qrcode/core/reed-solomon` | `rsEncode`, `interleaveBlocks`, `splitBlocks`, GF arithmetic |
| `@suaveplan/qrcode/core/matrix` | `buildMatrix` |
| `@suaveplan/qrcode/core/mask` | `selectMask`, `penaltyScore` |
| `@suaveplan/qrcode/core/renderer` | `renderSvg`, `renderDataUrl`, `renderTerminal` |
## Co-located Documentation
- [errors/qrcode-error.md](src/errors/qrcode-error.md)
- [core/tables/tables.md](src/core/tables/tables.md)
- [core/encoder/data-encoder.md](src/core/encoder/data-encoder.md)
- [core/reed-solomon/reed-solomon.md](src/core/reed-solomon/reed-solomon.md)
- [core/matrix/matrix-builder.md](src/core/matrix/matrix-builder.md)
- [core/mask/masking.md](src/core/mask/masking.md)
- [core/renderer/svg-renderer.md](src/core/renderer/svg-renderer.md)
- [core/qrcode.md](src/core/qrcode.md)

View File

@@ -0,0 +1,83 @@
# data-encoder
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements mode detection and bit-stream construction for QR code data encoding. Given a string, it selects the most compact encoding mode (numeric, alphanumeric, or byte), builds the complete bit stream including mode indicator and character count indicator, appends the terminator and padding codewords per ISO/IEC 18004:2015, and returns the final array of data codewords ready for Reed-Solomon processing.
The encoder also exports a `BitBuffer` class (an efficient bit-oriented writer) and the `encodeUtf8` helper, both used internally and by tests to verify intermediate encoding state.
## Features
- Automatic mode detection: numeric → alphanumeric → byte in priority order
- `BitBuffer` accumulates arbitrary-width integers into a packed byte array
- Numeric encoding: groups of 3 digits → 10 bits, 2 → 7 bits, 1 → 4 bits
- Alphanumeric encoding: pairs → 11 bits (first×45+second), single → 6 bits
- Byte encoding: UTF-8 with full surrogate-pair handling for code points above U+FFFF
- Terminator bits (up to 4 zero bits), byte-boundary padding, and pad codeword alternation (0xEC/0x11)
- Character count indicator bit length selected by version range (1-9, 10-26, 27-40)
- Throws `QRCodeError(QRCODE_CODES.INVALID_DATA)` for empty input or invalid alphanumeric characters
## Usage: Basic Example
```ts
import { detectMode, encodeData } from "@suaveplan/qrcode/core/encoder";
const mode = detectMode("01234567"); // "numeric"
const codewords = encodeData("01234567", mode, 1, 19);
// codewords is an array of 19 bytes ready for RS processing
```
## Usage: Advanced Example
```ts
import { BitBuffer, detectMode, encodeData, encodeUtf8 } from "@suaveplan/qrcode/core/encoder";
// Verify intermediate bit stream for "HELLO WORLD"
const buf = new BitBuffer();
buf.put(0b0010, 4); // alphanumeric mode indicator
buf.put(11, 9); // character count (9 bits for version 1)
// ... encode each pair
console.log(buf.bitLength);
// Byte mode with emoji
const mode = detectMode("Hello 🌍");
const cws = encodeData("Hello 🌍", mode, 2, 28);
// UTF-8 bytes for the emoji are correctly split across codewords
```
## API Reference
### `class BitBuffer`
**`put(value: number, numBits: number): void`** — Appends the `numBits` least-significant bits of `value` (MSB first).
**`putBit(bit: boolean): void`** — Appends a single bit.
**`get bitLength(): number`** — Total bits written so far.
**`get bytes(): number[]`** — The backing byte array. Length is `Math.ceil(bitLength / 8)`.
### `detectMode(data: string): QRMode`
Returns the most compact QR encoding mode for `data`: `"numeric"` if all digits, `"alphanumeric"` if all characters are in the QR alphanumeric set, otherwise `"byte"`.
### `encodeData(data, mode, version, totalDataCW): number[]`
Encodes `data` in `mode` for the given `version` into exactly `totalDataCW` bytes, including mode indicator, character count, payload, terminator, and padding.
**Throws** `QRCodeError(QRCODE_CODES.INVALID_DATA)` if `data` is empty or contains characters invalid for the specified mode.
### `encodeUtf8(str: string): number[]`
Converts a JavaScript string to a UTF-8 byte array, handling Basic Multilingual Plane and supplementary code points via surrogate pairs.
## Implementation Notes
`BitBuffer` packs bits MSB-first into a byte array. The `put` method loops from the most-significant bit of `value` down to 0, using a shift and mask to extract each bit. This matches the QR spec's bit ordering convention.
Numeric encoding computes `parseInt` on slices — safe because the mode guard guarantees only digit characters. Alphanumeric encoding throws on characters absent from `ALPHANUMERIC_TABLE` rather than silently skipping them, since a misdetected mode would corrupt the scan result.
The pad byte sequence alternates 0xEC → 0x11 → 0xEC → ... as required by the QR specification. The terminator is clamped to `min(4, remaining bits)` to handle the edge case where the payload exactly fills the codeword capacity.

View File

@@ -0,0 +1,72 @@
# masking
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements QR code mask pattern evaluation and optimal mask selection per ISO/IEC 18004:2015 Section 7.8. Masking improves the readability of QR codes by preventing large uniform regions that confuse scanners. The module evaluates all 8 candidate masks against four penalty rules and selects the mask yielding the lowest total penalty score.
The penalty rules measure four specific undesirable patterns: long consecutive same-color runs, 2×2 same-color blocks, finder-like 1:1:3:1:1 patterns that confuse the scanner's finder detection, and skewed proportions of dark versus light modules.
## Features
- All four ISO penalty rules implemented with correct weight constants (N1=3, N2=3, N3=40, N4=10)
- Penalty rule 1: consecutive same-color runs ≥ 5 in rows and columns
- Penalty rule 2: 2×2 same-color blocks
- Penalty rule 3: finder-like patterns (both forward and reverse orientations)
- Penalty rule 4: dark module proportion deviation from 50%
- `penaltyScore` for single-matrix scoring (useful for testing and external tools)
- `selectMask` evaluates all 8 masked matrices and returns the lowest-penalty matrix with its mask index
## Usage: Basic Example
```ts
import { selectMask } from "@suaveplan/qrcode/core/mask";
const { maskPattern, matrix } = selectMask(1, "M", interleavedCW);
// matrix is the boolean[][] ready for rendering
// maskPattern is 0-7, recorded for format information encoding
```
## Usage: Advanced Example
```ts
import { selectMask, penaltyScore } from "@suaveplan/qrcode/core/mask";
import { buildMatrix } from "@suaveplan/qrcode/core/matrix";
// Force a specific mask and score it manually
const matrix = buildMatrix(2, "Q", 3, data);
const score = penaltyScore(matrix);
console.log(`Mask 3 penalty: ${score}`);
// Or let selectMask choose
const best = selectMask(2, "Q", data);
console.log(`Best mask: ${best.maskPattern}`);
```
## API Reference
### `penaltyScore(matrix: boolean[][]): number`
Computes the total penalty score for a boolean module matrix. Lower is better. Used internally by `selectMask` and available for external inspection.
### `selectMask(version, ecLevel, data): { maskPattern: number; matrix: boolean[][] }`
Evaluates all 8 mask patterns by calling `buildMatrix` for each, scores each matrix with `penaltyScore`, and returns the result with the minimum penalty.
**Parameters:**
- `version: number` — QR version 1-10
- `ecLevel: ErrorCorrectionLevel` — error correction level
- `data: number[]` — interleaved codeword bytes
**Returns:** `{ maskPattern: number; matrix: boolean[][] }` where `maskPattern` is 0-7 and `matrix` is the final boolean matrix ready for rendering.
## Implementation Notes
Penalty rule 3 uses two reference patterns from the QR spec: `1011101 0000` and `0000 1011101`, both of length 11 modules. Each occurrence in any row or column contributes N3=40 to the total. The column check extracts each column into a temporary array to allow the same pattern-matching logic as rows; this avoids code duplication at the cost of one allocation per column per pattern evaluation.
Penalty rule 4 computes the percentage of dark modules, finds the nearest 5% multiples below and above, takes the absolute deviation from 50% for each divided by 5, and multiplies the minimum by N4=10. This matches the ISO specification's "step" approach.
`selectMask` builds all 8 matrices independently (8 calls to `buildMatrix`). This is correct per spec since the mask is applied inside `buildMatrix`; the format information encoding also varies by mask, so each candidate is a fully formed QR matrix.

View File

@@ -0,0 +1,68 @@
# matrix-builder
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Constructs the complete QR code module matrix by placing all functional patterns and data modules according to ISO/IEC 18004:2015. The builder operates on a numeric grid (0=light, 1=dark, 2=reserved, 255=unset), progressively filling in finder patterns, timing patterns, alignment patterns, format information reservations, and finally the data codewords. After data placement, a mask pattern is applied and format information is written into the previously reserved areas.
The output is a `boolean[][]` where `true` represents a dark module and `false` a light module, providing a clean, format-independent representation consumed by renderers and the masking scorer.
## Features
- Three finder pattern placements (7×7) with separator strips
- Horizontal and vertical timing patterns on row/column 6
- Version-adaptive alignment pattern placement (versions 2-10) with overlap detection for finder pattern corners
- Format information area reservation before data placement
- Two-column zigzag data placement with column-6 skip per QR spec
- All 8 mask patterns (ISO conditions 0-7)
- Format information writing (15-bit pre-computed BCH values from tables module)
- Sentinel-based reservation ensures function modules are never overwritten by data
## Usage: Basic Example
```ts
import { buildMatrix } from "@suaveplan/qrcode/core/matrix";
const matrix = buildMatrix(1, "M", 2, interleavedCWBytes);
// matrix[0][0] === true // top-left finder dark corner
// matrix.length === 21 // version 1 is 21×21
```
## Usage: Advanced Example
```ts
import { buildMatrix } from "@suaveplan/qrcode/core/matrix";
// Build all 8 masked variants for penalty scoring
const candidates = Array.from({ length: 8 }, (_, mask) =>
buildMatrix(version, ecLevel, mask, interleaved)
);
// Feed candidates to masking scorer to pick the best one
```
## API Reference
### `buildMatrix(version, ecLevel, maskPattern, data): boolean[][]`
Constructs the complete QR matrix.
**Parameters:**
- `version: number` — QR version 1-10
- `ecLevel: ErrorCorrectionLevel` — `"L" | "M" | "Q" | "H"`
- `maskPattern: number` — mask index 0-7
- `data: number[]` — interleaved codeword bytes from `interleaveBlocks`
**Returns:** `boolean[][]` of size `(version * 4 + 17) × (version * 4 + 17)`. `true` = dark module.
## Implementation Notes
The matrix uses a `Uint8Array[]` internally with four sentinel values: 255 (unset), 0 (light), 1 (dark), 2 (reserved/function). Data placement only writes to cells with value 255; all function pattern cells are set before data placement begins. The mask is applied only to non-reserved, non-unset cells, so function modules remain stable.
Format information is written last to overwrite the reserved-sentinel cells with their final bit values. The dark module at `(size-8, 8)` is placed during format reservation and never overwritten.
Alignment pattern placement skips three positions that would overlap the finder pattern corners: `(6,6)`, `(6, last)`, and `(last, 6)`. This matches the QR spec's exception table for versions 7 and above but is also applied to versions 2-6 where those positions do not occur in the position table.
The data zigzag alternates upward and downward column pairs of width 2, stepping right-to-left. Column 6 (timing column) is skipped by treating the strip at column 7 as a width-3 step instead of width-2.

View File

@@ -0,0 +1,102 @@
# qrcode
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [data-encoder](./encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_ - [masking](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_ - [matrix-builder](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_ - [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_ - [svg-renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_ - [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:auto-docs --> _(no submodu…
- [data-encoder](./encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- [data-encoder](./encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:au…
- [data-encoder](./encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [masking](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix-builder](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [svg-renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_- data-encoder — <!-- begin:auto-docs --> _(no submod…
- [data-encoder](./encoder/data-encoder.md) — <!-- begin:auto-docs --> _(no submodules)_- [mask](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_ - [masking](./mask/masking.md) — <!-- begin:auto-docs --> _(no submodules)_- [matrix](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_ - [matrix-builder](./matrix/matrix-builder.md) — <!-- begin:auto-docs --> _(no submodules)_- [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_ - [reed-solomon](./reed-solomon/reed-solomon.md) — <!-- begin:auto-docs --> _(no submodules)_- [renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_ - [svg-renderer](./renderer/svg-renderer.md) — <!-- begin:auto-docs --> _(no submodules)_- [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_ - [tables](./tables/tables.md) — <!-- begin:auto-docs --> _(no submodules)_- [qrcode](./qrcode.md) — <!-- begin:auto-docs --> _(no submodules)_
## Purpose
Top-level orchestrator for the `@suaveplan/qrcode` package. The `generate` function is the single public entry point that combines all pipeline stages: mode detection, version selection, data encoding, Reed-Solomon error correction, block interleaving, mask scoring, matrix construction, and rendering. Callers provide a string and optional configuration; the function returns a `QRCodeResult` with all four output representations populated.
OpenTelemetry span instrumentation is applied to the full generation pipeline via a graceful dynamic import: if `@opentelemetry/api` is available at runtime it records span attributes (version, EC level, mode, mask pattern) and status; if OTel is absent the function runs without tracing and without throwing.
## Features
- Single `generate(data, options?)` function covering the complete QR code pipeline
- Automatic version selection (tries versions 1-10 in order, picks the smallest that fits)
- Explicit version override with capacity validation and informative error
- EC level defaults to `'M'` if not specified
- Returns `QRCodeResult` with version, EC level, mode, matrix, size, SVG, dataUrl, terminal
- OTel span with attributes: `qrcode.version`, `qrcode.ecLevel`, `qrcode.mode`, `qrcode.maskPattern`
- Throws `QRCodeError` with stable codes for empty input, oversized data, and encoding failures
## Usage: Basic Example
```ts
import { generate } from "@suaveplan/qrcode";
const result = generate("https://suaveplan.io");
console.log(result.svg); // SVG string
console.log(result.terminal); // Unicode block output
console.log(result.version); // e.g. 3
```
## Usage: Advanced Example
```ts
import { generate } from "@suaveplan/qrcode";
const result = generate("HELLO WORLD", {
errorCorrectionLevel: "H",
version: 2,
scale: 6,
margin: 2,
color: "#1a1a2e",
background: "#ffffff",
});
// Embed as img src
const img = document.createElement("img");
img.src = result.dataUrl;
// Use raw matrix for custom rendering
for (let row = 0; row < result.size; row++) {
for (let col = 0; col < result.size; col++) {
const dark = result.matrix[row][col];
// draw(col * 10, row * 10, dark);
}
}
```
## API Reference
### `generate(data: string, options?: QRCodeOptions): QRCodeResult`
Generates a QR code for `data`.
**Parameters:**
- `data` — non-empty string to encode; any UTF-8 content
- `options.errorCorrectionLevel` — `"L" | "M" | "Q" | "H"`, default `"M"`
- `options.version` — QR version 1-10, auto-selected if omitted
- `options.margin` — quiet zone in modules, default 4
- `options.color` — dark module color string, default `"#000000"`
- `options.background` — light module color string, default `"#ffffff"`
- `options.scale` — pixels per module in SVG output, default 10
**Returns:** `QRCodeResult`
- `version: number` — version used
- `errorCorrectionLevel: ErrorCorrectionLevel` — level used
- `mode: QRMode` — encoding mode selected
- `matrix: boolean[][]` — module grid, true=dark
- `size: number` — matrix dimension
- `svg: string` — SVG markup
- `dataUrl: string` — `data:image/svg+xml;base64,...`
- `terminal: string` — Unicode half-block representation
**Throws:** `QRCodeError` with:
- `QRCODE_CODES.INVALID_DATA` — empty input or out-of-range version
- `QRCODE_CODES.VERSION_TOO_SMALL` — data too large for the version or for all versions 1-10
## Implementation Notes
Version auto-selection measures byte length for byte mode and character count for numeric and alphanumeric modes. This matches the QR spec's capacity tables, which are defined in terms of characters for non-byte modes and bytes for byte mode.
The OTel integration uses a top-level `await import()` to avoid bundling issues when OTel is a peer dependency. If the import fails (not installed), the function silently proceeds without tracing. Span status is set to `OK` on success and `ERROR` with a recorded exception on failure.
The mask selection loop in `selectMask` builds all 8 matrices and picks the minimum penalty. For versions up to 10 (maximum 57×57 modules), this executes in well under 1ms on modern hardware.

View File

@@ -0,0 +1,94 @@
# reed-solomon
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements Reed-Solomon error correction over GF(256) for QR code generation. The finite field uses the primitive polynomial x⁸+x⁴+x³+x²+1 (0x11D) as specified in ISO/IEC 18004:2015 Annex A. This module provides the complete RS pipeline: GF(256) arithmetic primitives, generator polynomial construction, codeword computation via polynomial long division, block splitting, and final codeword interleaving.
Reed-Solomon allows QR scanners to reconstruct the original data even when part of the code is damaged or obscured. The number of correctable errors scales with the error correction level: L corrects ~7%, M ~15%, Q ~25%, H ~30% of total codewords.
## Features
- GF(256) exp/log table construction at module load (O(255) init, O(1) multiply)
- `gfMul`, `gfDiv`, `gfAdd`, `gfPow` arithmetic primitives
- `polyMul` for polynomial multiplication in GF(256)
- `generatorPoly(n)` builds the RS generator polynomial g(x) = ∏(x − αⁱ) for i=0..n-1
- `rsEncode(data, nEC)` performs polynomial long division to produce EC codewords
- `splitBlocks` partitions data codewords by the block group structure from `BLOCK_TABLE`
- `interleaveBlocks` interleaves data and EC codewords across all blocks in the column order required by the QR matrix data placement algorithm
## Usage: Basic Example
```ts
import { rsEncode } from "@suaveplan/qrcode/core/reed-solomon";
// Compute 10 EC codewords for a V1-M data block
const dataCW = [32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17];
const ecCW = rsEncode(dataCW, 10);
// ecCW is an array of 10 bytes
```
## Usage: Advanced Example
```ts
import {
splitBlocks,
interleaveBlocks,
gfMul,
generatorPoly,
} from "@suaveplan/qrcode/core/reed-solomon";
import { BLOCK_TABLE, EC_LEVEL_INDEX } from "@suaveplan/qrcode/core/tables";
const blockInfo = BLOCK_TABLE[4][EC_LEVEL_INDEX["M"]]; // V5 M
const dataBlocks = splitBlocks(allDataCW, blockInfo.blocks);
const finalCW = interleaveBlocks(dataBlocks, blockInfo.ecCWPerBlock);
// finalCW is ready to be placed into the QR matrix
```
## API Reference
### `gfMul(a: number, b: number): number`
Multiplies two GF(256) elements using the exp/log tables. Returns 0 if either operand is 0.
### `gfDiv(a: number, b: number): number`
Divides `a` by `b` in GF(256). Throws `RangeError` if `b === 0`.
### `gfAdd(a: number, b: number): number`
Adds (XORs) two GF(256) elements. Subtraction is identical in GF(2^n).
### `gfPow(x: number, power: number): number`
Computes `α^(log(x) * power) mod 255` using the exp/log tables.
### `polyMul(p: Uint8Array, q: Uint8Array): Uint8Array`
Multiplies two GF(256) polynomials. Coefficient arrays are indexed by degree (index 0 = constant term).
### `generatorPoly(nECCodewords: number): Uint8Array`
Builds the RS generator polynomial for `nECCodewords` EC codewords. Cached implicitly by the caller since the same block always uses the same EC count.
### `rsEncode(data: number[], nECCodewords: number): number[]`
Computes `nECCodewords` Reed-Solomon error correction codewords for `data`. Returns only the EC bytes (not the original data).
### `splitBlocks(dataCW, blocks): number[][]`
Splits a flat data codeword array into individual block arrays according to the block group structure from `BLOCK_TABLE`. Groups with `count: 0` are skipped.
### `interleaveBlocks(dataBlocks, ecCWPerBlock): number[]`
Produces the final codeword sequence by interleaving all block data columns, then all block EC columns. This column-major ordering is required by the QR matrix data placement algorithm.
## Implementation Notes
The exp/log tables are computed once at module initialisation time via an IIFE. The GF(256) multiplication is therefore a single array lookup pair (two reads + one modular add), making it the fastest possible approach for this field size.
The polynomial long division in `rsEncode` operates in-place on a pre-allocated message array of length `data.length + nECCodewords`, avoiding dynamic allocation inside the inner loop. After processing, only the last `nECCodewords` bytes are returned as the EC codewords.
The interleaver handles unequal block sizes (one group with `dataCW` bytes, a second group with `dataCW + 1` bytes for versions like V5) by iterating up to the maximum block length and skipping blocks that have been exhausted. This matches the QR spec's data placement order exactly.

View File

@@ -0,0 +1,78 @@
# svg-renderer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Converts a QR code module matrix (`boolean[][]`) into human- and machine-readable output formats. Three representations are produced: an SVG string for web embedding and print quality at any scale, a base64 data URL for use in HTML `img` src attributes without external file references, and a Unicode half-block terminal string for command-line display and debugging.
All renderers accept the same `QRCodeOptions` object, using the `margin`, `color`, `background`, and `scale` fields to control appearance. This keeps the rendering API uniform across output formats and allows the top-level `generate` function to call all three with a single options object.
## Features
- `renderSvg`: inline SVG with configurable pixel scale, margin, dark/light colors; uses `<rect>` elements for maximum compatibility (no `<path>`)
- `renderDataUrl`: wraps `renderSvg` output as `data:image/svg+xml;base64,...` for `<img src>` embedding
- `renderTerminal`: packs 2 module rows per terminal line using Unicode half-block characters (UP HALF BLOCK, LOWER HALF BLOCK, FULL BLOCK, space), with configurable quiet zone
- XML attribute value escaping (`&`, `<`, `>`, `"`, `'`) prevents SVG injection from user-supplied color strings
- Quiet zone (margin) applied identically in all three renderers for visual consistency
## Usage: Basic Example
```ts
import { renderSvg } from "@suaveplan/qrcode/core/renderer";
const svg = renderSvg(matrix, { scale: 4, margin: 2 });
// Returns a complete SVG string, 100x100px for a 21-module V1 QR
```
## Usage: Advanced Example
```ts
import { renderSvg, renderDataUrl, renderTerminal } from "@suaveplan/qrcode/core/renderer";
const opts = { color: "#1a1a2e", background: "#f0f0f0", scale: 8, margin: 4 };
const svg = renderSvg(matrix, opts);
const url = renderDataUrl(matrix, opts);
const cli = renderTerminal(matrix, { margin: 2 });
// Set on an img element src attribute
const imgSrc = url;
// Print in terminal
console.log(cli);
```
## API Reference
### `renderSvg(matrix: boolean[][], options?: QRCodeOptions): string`
Renders the matrix to an SVG string. Each dark module becomes a `<rect>` element. A background `<rect>` covers the entire canvas including the quiet zone.
**Options used:** `scale` (pixels per module, default 10), `margin` (quiet zone in modules, default 4), `color` (dark module fill, default `#000000`), `background` (light fill, default `#ffffff`).
### `renderDataUrl(matrix: boolean[][], options?: QRCodeOptions): string`
Calls `renderSvg` and encodes the result as a UTF-8-safe base64 data URL: `data:image/svg+xml;base64,...`.
### `renderTerminal(matrix: boolean[][], options?: QRCodeOptions): string`
Renders the matrix to a multi-line string using Unicode half-block characters. Two module rows are collapsed into one terminal row. Options used: `margin` (quiet zone, default 4).
**Terminal character mapping:**
- U+2588 FULL BLOCK: top dark, bottom dark
- U+2580 UPPER HALF BLOCK: top dark, bottom light
- U+2584 LOWER HALF BLOCK: top light, bottom dark
- U+0020 SPACE: top light, bottom light
## Implementation Notes
`renderSvg` generates one `<rect>` per dark module rather than using `<path>` commands. While this produces larger SVG text for dense QR codes, it is universally compatible across SVG 1.1 viewers, email clients, and PDF renderers. The `viewBox` attribute ensures the SVG scales correctly in responsive CSS layouts.
`renderDataUrl` uses `btoa(unescape(encodeURIComponent(svg)))` to handle Unicode characters in user-supplied color strings before base64 encoding, avoiding `btoa` failures on non-Latin characters.
`renderTerminal` pads the matrix with `margin` rows/columns of light modules before processing, then iterates in pairs of rows. If the matrix has an odd total padded height, the final row is treated as if followed by a light row.
The `escapeXml` function is applied to color strings in the SVG output to prevent injection via crafted color values. Colors should also be validated at the options level before reaching the renderer.

View File

@@ -0,0 +1,107 @@
# tables
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Centralises every constant lookup table required by the QR code encoding pipeline. All data is sourced directly from ISO/IEC 18004:2015 and arranged for O(1) access by version (1-indexed) and error correction level (L/M/Q/H). Having a single canonical table module eliminates drift between the encoder, Reed-Solomon generator, matrix builder, and format information writer: they all read from one authoritative source.
The module deliberately avoids any computation at import time beyond trivial object/array literals so tree-shaking is maximally effective; unused capacity tables are eliminated at build time.
## Features
- Byte, numeric, and alphanumeric capacity tables for versions 1-10 × 4 EC levels
- Full block structure table (`BLOCK_TABLE`) describing data/EC codeword counts and group layouts for interleaved data blocks
- Alignment pattern centre positions for versions 1-10
- Alphanumeric character value lookup (O(1) string → integer)
- Mode indicator nibbles and character count indicator bit lengths
- Pre-computed format information 15-bit strings (BCH(15,5) + XOR mask)
- `charCountBits(mode, version)` helper for version-range dispatch
- `qrSize(version)` helper computing module matrix dimension
## Usage: Basic Example
```ts
import { BYTE_CAPACITY, EC_LEVEL_INDEX } from "@suaveplan/qrcode/core/tables";
const ecIdx = EC_LEVEL_INDEX["M"]; // 1
const version = 1;
const maxBytes = BYTE_CAPACITY[version - 1][ecIdx]; // 14
```
## Usage: Advanced Example
```ts
import {
BLOCK_TABLE,
EC_LEVEL_INDEX,
FORMAT_INFO_STRINGS,
ALPHANUMERIC_TABLE,
} from "@suaveplan/qrcode/core/tables";
// Look up block structure for V3 M
const blockInfo = BLOCK_TABLE[2][EC_LEVEL_INDEX["M"]];
// blockInfo.ecCWPerBlock === 26, blockInfo.blocks === [{ count: 1, dataCW: 44 }]
// Encode alphanumeric pair
const charVal = ALPHANUMERIC_TABLE["A"] * 45 + ALPHANUMERIC_TABLE["B"];
// charVal === 10 * 45 + 11 === 461
// Read pre-computed format info for EC=M, mask=2
const fmtBits = FORMAT_INFO_STRINGS[1 * 8 + 2]; // 0x5e7c
```
## API Reference
### `EC_LEVEL_INDEX`
`Record<ErrorCorrectionLevel, number>` mapping `L→0`, `M→1`, `Q→2`, `H→3`. Used as the second dimension index into all 2D tables.
### `BYTE_CAPACITY`
`ReadonlyArray<readonly [number,number,number,number]>`. Byte-mode capacity in bytes. First index is `version - 1`, second is `EC_LEVEL_INDEX[level]`.
### `NUMERIC_CAPACITY`
Same shape as `BYTE_CAPACITY` for numeric-mode character counts.
### `ALPHANUMERIC_CAPACITY`
Same shape as `BYTE_CAPACITY` for alphanumeric-mode character counts.
### `BLOCK_TABLE`
`ReadonlyArray<readonly [BlockInfo, BlockInfo, BlockInfo, BlockInfo]>`. Full interleaving block structure. `BlockInfo` has `{ totalDataCW, ecCWPerBlock, blocks: Array<{ count, dataCW }> }`. First index is `version - 1`, second is `EC_LEVEL_INDEX[level]`.
### `ALIGNMENT_POSITIONS`
`ReadonlyArray<ReadonlyArray<number>>`. Centre coordinates of alignment patterns. First index is `version - 1`; version 1 returns an empty array.
### `ALPHANUMERIC_TABLE`
`Readonly<Record<string, number>>`. Maps each of the 45 alphanumeric characters to its QR spec integer value.
### `MODE_INDICATOR`
`Record<string, number>`. Maps mode name strings to 4-bit mode indicator values.
### `CHAR_COUNT_BITS`
`Record<string, readonly [number, number, number]>`. Character count indicator lengths for three version ranges per mode.
### `FORMAT_INFO_STRINGS`
`ReadonlyArray<number>`. 32 pre-computed 15-bit format information values. Index as `ecLevelIndex * 8 + maskPattern`.
### `charCountBits(mode: string, version: number): number`
Returns the character count indicator bit length for the given mode and version number.
### `qrSize(version: number): number`
Returns the module matrix dimension: `version * 4 + 17`.
## Implementation Notes
All capacity and block tables are lifted verbatim from Annex I of ISO/IEC 18004:2015. Format information strings were independently verified against the reference implementation by cross-checking against the QR code generator specification Table C.1. The `BLOCK_TABLE` entries include zero-count groups (count: 0) to simplify iteration logic in the matrix builder — the builder skips groups with count 0. Version 10 support completes the "versions 1-10" stated scope; versions above 10 are rejected at the API boundary.

View File

@@ -0,0 +1,90 @@
# qrcode-error
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`QRCodeError` is the typed error class for the entire `@suaveplan/qrcode` package. It extends the native `Error` class with a stable `code` property drawn from `QRCODE_CODES`, allowing callers to discriminate failure modes programmatically without string parsing. The class intentionally avoids the `@suaveplan/error` dependency so this leaf package remains dependency-minimal: only `zod`, `tslib`, and `@opentelemetry/api` are required at runtime.
The three error codes cover every failure surface of the QR code generation pipeline: invalid input data, insufficient version capacity for the payload, and unexpected encoding pipeline failures. All three are stable across patch releases.
## Features
- `QRCODE_CODES` frozen registry with three stable error codes
- `QRCodeCode` union type derived from the registry
- `QRCodeError` extends `Error` with a narrow `code: QRCodeCode` property
- `instanceof` discrimination for structured error handling
- `name` property set to `"QRCodeError"` for logging and stack trace clarity
## Usage: Basic Example
```ts
import { QRCodeError, QRCODE_CODES } from "@suaveplan/qrcode/errors";
try {
throw new QRCodeError("Input is empty", QRCODE_CODES.INVALID_DATA);
} catch (err) {
if (err instanceof QRCodeError) {
console.error(err.code); // "QRCODE.INVALID_DATA"
}
}
```
## Usage: Advanced Example
```ts
import { QRCodeError, QRCODE_CODES } from "@suaveplan/qrcode/errors";
function assertVersionCapacity(version: number, byteCount: number): void {
// Version 1 byte-mode M capacity = 14 bytes
const cap = version === 1 ? 14 : byteCount + 1;
if (byteCount > cap) {
throw new QRCodeError(
`Version ${version} cannot hold ${byteCount} bytes`,
QRCODE_CODES.VERSION_TOO_SMALL
);
}
}
try {
assertVersionCapacity(1, 20);
} catch (err) {
if (err instanceof QRCodeError && err.code === QRCODE_CODES.VERSION_TOO_SMALL) {
console.warn("Increase version or reduce data size");
}
}
```
## API Reference
### `QRCODE_CODES`
`{ readonly INVALID_DATA: "QRCODE.INVALID_DATA"; readonly VERSION_TOO_SMALL: "QRCODE.VERSION_TOO_SMALL"; readonly ENCODE_FAILED: "QRCODE.ENCODE_FAILED" }`
Frozen object mapping error-code names to their canonical string values. Consumers should always reference codes via this object rather than inline strings to benefit from rename refactoring and exhaustive switch checking.
### `QRCodeCode`
`type QRCodeCode = (typeof QRCODE_CODES)[keyof typeof QRCODE_CODES]`
Union of all valid error code strings. Narrowed automatically when branching on `err.code`.
### `class QRCodeError extends Error`
**Constructor:** `new QRCodeError(message: string, code: QRCodeCode)`
- `message` — human-readable description of the failure
- `code` — one of the values in `QRCODE_CODES`
**Properties:**
- `name: "QRCodeError"` — set by the constructor for identification in logs and stack traces
- `code: QRCodeCode` — readonly, stable error code
- All standard `Error` properties: `message`, `stack`
## Implementation Notes
The class deliberately does not extend `SuaveplanError` from `@suaveplan/error`. This keeps the dependency graph lean: the qrcode package is a leaf in the media domain and should not pull in the full error substrate (Zod wire format, OTel span events, cause-chain traversal) unless a future version explicitly requires them. If the package graduates to depending on `@suaveplan/error`, the migration is straightforward since the `code` property convention is identical.
The `as const` assertion on `QRCODE_CODES` ensures TypeScript infers string literal types rather than `string`, enabling exhaustive union checks.

View File

@@ -0,0 +1,122 @@
# qrcode.types
## Purpose
Shared TypeScript type definitions for the `@suaveplan/qrcode` package. These types form the public API contract for QR code generation options and results, ensuring consistent interfaces across all modules in the package. All types are consumed by the top-level `generate()` function, renderers, and encoder modules.
## Features
- `ErrorCorrectionLevel` — union type for QR error correction levels L, M, Q, and H, each providing a different trade-off between data recovery capacity and payload size
- `QRMode` — union type for QR data encoding modes: numeric (digits only), alphanumeric (QR character set), byte (arbitrary UTF-8), and kanji (Shift-JIS)
- `OutputFormat` — union type for supported output representations: SVG string, raw boolean matrix, terminal Unicode blocks, and base64 data URL
- `QRCodeOptions` — configuration interface for `generate()`, covering EC level, explicit version, quiet zone size, module colors, and pixel scale
- `QRCodeResult` — result interface returned by `generate()`, containing all output representations plus metadata (version, EC level, mode, matrix size)
## Usage: Basic Example
```typescript
import type { QRCodeOptions, QRCodeResult } from "@suaveplan/qrcode";
const options: QRCodeOptions = {
errorCorrectionLevel: "M",
margin: 4,
color: "#000000",
background: "#ffffff",
scale: 10,
};
// Pass options to the generate() function
// const result: QRCodeResult = generate("Hello World", options);
```
## Usage: Advanced Example
```typescript
import type {
ErrorCorrectionLevel,
OutputFormat,
QRCodeOptions,
QRCodeResult,
QRMode,
} from "@suaveplan/qrcode";
// Function that accepts strongly-typed parameters
function encodeWithLevel(
data: string,
level: ErrorCorrectionLevel
): QRCodeOptions {
return {
errorCorrectionLevel: level,
version: undefined, // auto-select
margin: 4,
};
}
// Use the result type for structured access
function printResult(result: QRCodeResult): void {
console.log(`Version: ${result.version}`);
console.log(`Mode: ${result.mode satisfies QRMode}`);
console.log(`Size: ${result.size}×${result.size}`);
console.log(`EC Level: ${result.errorCorrectionLevel satisfies ErrorCorrectionLevel}`);
// result.svg, result.dataUrl, result.terminal, result.matrix all available
}
```
## API Reference
### `ErrorCorrectionLevel`
```typescript
type ErrorCorrectionLevel = "L" | "M" | "Q" | "H";
```
QR error correction capacity:
- `"L"` — ~7% data recovery, maximum payload capacity
- `"M"` — ~15% data recovery, default level
- `"Q"` — ~25% data recovery, balanced
- `"H"` — ~30% data recovery, maximum robustness
### `QRMode`
```typescript
type QRMode = "numeric" | "alphanumeric" | "byte" | "kanji";
```
Data encoding mode. The encoder auto-selects the most compact mode, but callers can inspect the selected mode in `QRCodeResult.mode`.
### `OutputFormat`
```typescript
type OutputFormat = "svg" | "matrix" | "terminal" | "dataurl";
```
Supported output representations. All four are always present in `QRCodeResult`.
### `QRCodeOptions`
| Property | Type | Default | Description |
|---|---|---|---|
| `errorCorrectionLevel` | `ErrorCorrectionLevel` | `"M"` | EC level |
| `version` | `number` | auto | QR version 1-10 |
| `margin` | `number` | `4` | Quiet zone modules |
| `color` | `string` | `"#000000"` | Dark module CSS color |
| `background` | `string` | `"#ffffff"` | Light module CSS color |
| `scale` | `number` | `10` | Pixels per module |
### `QRCodeResult`
| Property | Type | Description |
|---|---|---|
| `version` | `number` | QR version used (1-10) |
| `errorCorrectionLevel` | `ErrorCorrectionLevel` | EC level used |
| `mode` | `QRMode` | Encoding mode selected |
| `matrix` | `boolean[][]` | 2D module matrix; `true` = dark |
| `size` | `number` | Matrix dimension in modules |
| `svg` | `string` | SVG string representation |
| `dataUrl` | `string` | `data:image/svg+xml;base64,...` |
| `terminal` | `string` | Unicode half-block terminal art |
## Implementation Notes
These types are runtime-free (no compiled output), consumed only through `import type`. The `QRCodeOptions` properties are all optional to allow ergonomic partial configuration — each module supplies its own defaults. `QRCodeResult` always includes all output formats so callers do not need to request specific renderers; this avoids conditional rendering logic at the call site.