@forgesworn/qr 0.0.0-seed.0 → 0.0.0-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,112 +1,3 @@
1
- # @forgesworn/qr
1
+ # Temporary Holding Version
2
2
 
3
- A QR code encoder and decoder for browsers, workers and Node, with no
4
- dependencies. Versions 1-40, all four error correction levels, numeric,
5
- alphanumeric and byte modes, optional UTF-8 ECI, and SVG, canvas and
6
- terminal output. The decoder reads a code from camera or screenshot pixels.
7
-
8
- The tests compare 114 symbols module for module with an independent encoder
9
- (ZXing, as a test-time oracle), covering every version and mask, and have an
10
- independent decoder (jsQR) read back random payloads and every renderer's
11
- output. Neither ships in the package.
12
- [SPEC.md](SPEC.md) defines the behaviour exactly, so ports in other
13
- languages can produce identical output.
14
-
15
- The decoder tests draw codes through synthetic camera effects (rotation,
16
- perspective, small modules, blur, noise, uneven light, inversion and
17
- mirroring) and require it to read at least as many of those frames as jsQR
18
- does. They are synthetic; real phone photographs are not yet part of the
19
- test set.
20
-
21
- ## Install
22
-
23
- ```bash
24
- npm install @forgesworn/qr
25
- ```
26
-
27
- ESM only, Node.js 22.13 or later, or any modern browser.
28
-
29
- ## Use
30
-
31
- ```ts
32
- import { drawCanvas, encode, isDark, toSvg, toTerminal } from '@forgesworn/qr'
33
-
34
- const qr = encode('https://example.com/', { ecc: 'Q' })
35
- qr.version // 2
36
- qr.size // 25 modules per side, quiet zone not included
37
- isDark(qr, 0, 0) // true: the top-left finder
38
-
39
- const svg = toSvg(qr, { margin: 4, dark: '#111', light: '#fff', title: 'Link to example.com' })
40
-
41
- drawCanvas(document.querySelector('canvas')!, qr, { width: 320 }) // or an OffscreenCanvas
42
- console.log(toTerminal(qr)) // half-block text for a dark terminal; { ink: 'dark' } for a light one
43
- ```
44
-
45
- `encode(data, options)` takes a string (encoded as UTF-8) or a
46
- `Uint8Array`. Options:
47
-
48
- | Option | Default | |
49
- |---|---|---|
50
- | `ecc` | `'M'` | `'L'`, `'M'`, `'Q'` or `'H'` |
51
- | `version` | smallest that fits | 1-40; throws `QrCapacityError` if the data does not fit |
52
- | `minVersion` | 1 | lower bound when choosing a version |
53
- | `mask` | lowest penalty | 0-7 |
54
- | `mode` | most compact | `'numeric'`, `'alphanumeric'` or `'byte'` |
55
- | `eci` | `false` | in byte mode, mark the data as UTF-8 |
56
-
57
- The result's `modules` is a row-major `Uint8Array` of `size * size`
58
- entries, 1 for dark, with no quiet zone.
59
-
60
- Renderers, each with `margin` (quiet zone in modules, default 4):
61
-
62
- - `toSvg(qr, { dark, light, title })`: an SVG string, one path, `light: null` for no background.
63
- - `drawCanvas(canvas, qr, { width, dark, light })`: sizes the canvas to `width` pixels
64
- square (default 8 per module) with module edges on whole pixels.
65
- - `toTerminal(qr, { ink })`: two rows per line in half blocks; `ink: 'light'` (default)
66
- for dark terminals, `'dark'` for light ones.
67
-
68
- ### Reading
69
-
70
- ```ts
71
- import { decode } from '@forgesworn/qr'
72
-
73
- const frame = ctx.getImageData(0, 0, width, height) // RGBA, as a canvas gives
74
- const found = decode(frame)
75
- found?.text // the payload as text: UTF-8 when it is valid UTF-8, else Latin-1, unless an ECI says
76
- found?.bytes // the raw bytes
77
- found?.location // picture coordinates of the three finders
78
- ```
79
-
80
- `decode(image, options)` takes `{ data, width, height }` with RGBA or
81
- greyscale bytes and returns `undefined` when it finds no readable code.
82
- Options: `rowStride` (pixels a row, for padded camera planes), `channels`
83
- (1 or 4; worked out from the data length by default) and `inversion`
84
- (`'both'` by default, or `'normal'` or `'inverted'`). The result also says
85
- whether the code was `mirrored` or `inverted`, and its `version`, `ecc` and
86
- `mask`. Kanji segments are kept as bytes and show as U+FFFD in `text`. A frame with no code in it costs more than one with a code; on a
87
- 1280x720 frame of pure noise, expect a few hundred milliseconds on a
88
- laptop, so throttle camera loops.
89
-
90
- `decodeModules(modules, size)` reads a module grid you already have.
91
-
92
- ## Development
93
-
94
- ```bash
95
- npm install
96
- npm run check # typecheck and tests
97
- npm run build # compile to dist/
98
- npm run generate-vectors # regenerate the ZXing vectors (needs a JDK and network once)
99
- ```
100
-
101
- ## Support
102
-
103
- For issues and feature requests, see [GitHub Issues](https://github.com/forgesworn/qr/issues).
104
-
105
- If you find this library useful, consider sending a tip:
106
-
107
- - **Lightning:** `profusemeat89@walletofsatoshi.com`
108
- - **Nostr zaps:** `npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`
109
-
110
- ## Licence
111
-
112
- MIT
3
+ This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
package/package.json CHANGED
@@ -1,68 +1,6 @@
1
1
  {
2
2
  "name": "@forgesworn/qr",
3
- "version": "0.0.0-seed.0",
4
- "description": "QR code encoder and decoder with no dependencies: versions 1-40, all error correction levels, SVG output, camera-frame reading, checked against independent test vectors.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "default": "./dist/index.js"
12
- },
13
- "./package.json": "./package.json"
14
- },
15
- "files": [
16
- "dist",
17
- "LICENSE",
18
- "README.md",
19
- "SPEC.md",
20
- "llms.txt",
21
- "CHANGELOG.md"
22
- ],
23
- "scripts": {
24
- "build": "tsc -p tsconfig.json",
25
- "test": "vitest run",
26
- "typecheck": "tsc -p tsconfig.typecheck.json",
27
- "generate-vectors": "sh scripts/generate-vectors.sh",
28
- "check": "npm run typecheck && npm test",
29
- "prepare": "npm run build",
30
- "prepublishOnly": "npm run check"
31
- },
32
- "keywords": [
33
- "qr",
34
- "qrcode",
35
- "qr-code",
36
- "encoder",
37
- "svg",
38
- "zero-dependency"
39
- ],
40
- "author": "forgesworn",
41
- "funding": {
42
- "type": "lightning",
43
- "url": "lightning:profusemeat89@walletofsatoshi.com"
44
- },
45
- "license": "MIT",
46
- "sideEffects": false,
47
- "engines": {
48
- "node": ">=22.13"
49
- },
50
- "devDependencies": {
51
- "@types/node": "^22",
52
- "jsqr": "1.4.0",
53
- "typescript": "^5.6.0",
54
- "vitest": "^4.1.11"
55
- },
56
- "publishConfig": {
57
- "access": "public",
58
- "provenance": true
59
- },
60
- "homepage": "https://github.com/forgesworn/qr#readme",
61
- "bugs": {
62
- "url": "https://github.com/forgesworn/qr/issues"
63
- },
64
- "repository": {
65
- "type": "git",
66
- "url": "git+https://github.com/forgesworn/qr.git"
67
- }
68
- }
3
+ "version": "0.0.0-stage",
4
+ "stub": true,
5
+ "description": "Temporary package placeholder for staged publishing"
6
+ }
package/CHANGELOG.md DELETED
@@ -1,14 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.1.0
4
-
5
- - First release: encoder (versions 1-40, levels L/M/Q/H, numeric,
6
- alphanumeric and byte modes, UTF-8 ECI) with SVG, canvas and terminal
7
- renderers.
8
- - Decoder: RGBA or greyscale frames, local-threshold binarisation, finder
9
- and alignment search, perspective sampling, Reed-Solomon correction,
10
- inverted and mirrored codes, numeric, alphanumeric, byte, kanji (as
11
- bytes), ECI, structured append and FNC1 segments. `decodeModules` for a
12
- module grid.
13
- - Encode vectors from ZXing core 3.5.3; jsQR round-trip tests; decoder
14
- tests on synthetic distorted frames with jsQR as the bar.
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 TheCryptoDonkey
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/SPEC.md DELETED
@@ -1,136 +0,0 @@
1
- # Specification
2
-
3
- This is what every implementation of `@forgesworn/qr` (TypeScript now,
4
- Kotlin and Rust later) must do, so that the same input gives the same
5
- symbol byte for byte. `vectors/encode-vectors.json` is the test of it.
6
- Section numbers refer to ISO/IEC 18004:2015.
7
-
8
- ## Input
9
-
10
- - Text is encoded as UTF-8. Lone surrogates become U+FFFD.
11
- - Bytes are encoded as given, always in byte mode.
12
-
13
- ## Mode
14
-
15
- One segment per symbol. Unless the caller names a mode, text whose every
16
- character is a digit is numeric; text whose every character is in
17
- `0-9 A-Z space $ % * + - . / :` is alphanumeric; anything else, and the
18
- empty string, is byte. Kanji mode and mixed segments are not produced.
19
-
20
- With `eci: true`, a byte-mode symbol starts with the ECI designator
21
- `0111` and assignment 26 (UTF-8) as 8 bits (section 7.4.2). Numeric and
22
- alphanumeric symbols never carry one.
23
-
24
- ## Version and level
25
-
26
- Level M unless the caller names one. The version is the smallest, from
27
- `minVersion` (default 1), whose data capacity holds the header (ECI if any,
28
- 4-bit mode, character count in the width for that version band:
29
- 1-9, 10-26, 27-40) and the data, and whose count field can hold the
30
- character count. A requested version that cannot hold the data is an
31
- error, never a silent upgrade.
32
-
33
- ## Bit stream and error correction
34
-
35
- Terminator of up to four zero bits, zero bits to a byte boundary, then
36
- pad bytes `0xEC`, `0x11` alternately (section 7.4.10). Codewords split into
37
- blocks as Table 9 gives (group 2 blocks hold one more data codeword);
38
- Reed-Solomon over GF(256) with polynomial `0x11D`, generator roots
39
- `α^0 .. α^(n-1)`. Data codewords are interleaved by index across blocks,
40
- then error correction codewords the same way.
41
-
42
- ## Matrix
43
-
44
- Finder patterns with separators, the dark module at (8, size - 8),
45
- alignment patterns at every pair of Annex E centres except those on a
46
- finder, timing patterns on row and column 6, then format and version
47
- areas reserved. Data fills two-module columns from the bottom right,
48
- upward first, skipping column 6 and every reserved module; remainder
49
- bits are light before masking.
50
-
51
- Format information is `(level bits << 3 | mask)` with level bits L=01,
52
- M=00, Q=11, H=10, BCH(15,5) with generator `0x537`, XOR `0x5412`. Bit
53
- `i` (least significant first) goes at the i-th of (8,0) (8,1) (8,2) (8,3)
54
- (8,4) (8,5) (8,7) (8,8) (7,8) (5,8) (4,8) (3,8) (2,8) (1,8) (0,8), as
55
- (x, y), and again at (size - 1 - i, 8) for `i < 8` or (8, size - 15 + i)
56
- otherwise. Version information (version 7 and up) is BCH(18,6) with
57
- generator `0x1F25`; bit `k` (least significant first) goes at
58
- (k / 3, size - 11 + k % 3) and its transpose.
59
-
60
- ## Mask
61
-
62
- Unless the caller names one, every mask 0-7 is applied to the finished
63
- symbol (format and version information included) and scored with the four
64
- penalty rules of section 7.8.3: N1 = 3 per run of five or more same-colour
65
- modules plus one per module beyond five, rows and columns; N2 = 3 per 2x2
66
- same-colour block, overlapping; N3 = 40 per `1011101` with four light
67
- modules before or after, rows and columns; N4 = 10 per whole 5 per cent
68
- that dark modules deviate from half, rounded down. The lowest score wins;
69
- ties go to the lower mask.
70
-
71
- One choice the standard leaves open: for N3, four light modules that would
72
- lie outside the symbol do not count (the quiet zone is not scored). ZXing
73
- makes the same choice, so masks chosen here match it.
74
-
75
- ## Output
76
-
77
- A square of `17 + 4 * version` modules, row-major, 1 for dark, with no
78
- quiet zone. Renderers add the quiet zone; the default is four modules.
79
-
80
- Renderers are not covered by the vectors and need not match byte for byte
81
- across ports, but must keep these properties: the quiet zone is light;
82
- a canvas or bitmap renderer puts module edges on whole pixels (edge of
83
- module `i` at `round(i * width / side)`); a terminal renderer packs two
84
- module rows per line with U+2580 (upper half), U+2584 (lower half), U+2588
85
- (full) and space, inking light modules by default.
86
-
87
- ## Vectors
88
-
89
- `vectors/encode-vectors.json` holds module matrices produced by ZXing core
90
- 3.5.3 (`scripts/generate-vectors.sh`). They cover every version at full
91
- capacity, every mask, every level, all three modes, both count-length band
92
- edges, the empty string and UTF-8 with ECI. ZXing is a development-time
93
- oracle only and is not part of the package.
94
-
95
- ## Decoder
96
-
97
- The decoder is not bound by the vectors in the same way: ports may find
98
- codes differently, but must return the same payload for any symbol they
99
- read. The TypeScript pipeline, for reference:
100
-
101
- 1. Luminance: RGBA to grey as `(77 R + 150 G + 29 B) >> 8`, with alpha
102
- blended onto white, so a code drawn on a transparent canvas reads.
103
- 2. Binarisation: 8x8 blocks; each block's threshold is the mean of the
104
- block averages in the 5x5 blocks around it; a block with a range of 24
105
- or less borrows its neighbours' level. Pictures under 40 pixels a side
106
- use one global threshold.
107
- 3. Finders: row scans for 1:1:3:1:1 runs, each checked down the column,
108
- back across the row and along a diagonal, with a strict and then a looser
109
- ratio tolerance. Centres within a module of each other merge.
110
- 4. Triples: three finders of similar module size forming a near right
111
- isosceles triangle; the cross product puts the top-right finder on the
112
- right.
113
- 5. Size and alignment: size from finder distances over module size,
114
- rounded to `1 (mod 4)`. From version 2, alignment candidates are found
115
- near the corner the finders imply, and ranked by how well the 5x5 pattern
116
- around each matches along the code's axes; the parallelogram corner is
117
- the last resort. Sizes four and eight modules either side are retried.
118
- 6. Sampling: a perspective map from module centres to the picture, then
119
- format and version information, unmasking and Reed-Solomon correction
120
- (section 7.5 and annex B). A failed read is retried transposed, for
121
- mirrored codes, and the whole search is repeated on the inverted
122
- picture.
123
- 7. Segments: numeric, alphanumeric, byte, kanji (kept as Shift_JIS bytes),
124
- ECI, structured append (header skipped) and FNC1 (indicators skipped).
125
- Byte text is UTF-8 under ECI 26 or when it is valid UTF-8, and Latin-1
126
- otherwise or under ECI 1 or 3.
127
-
128
- ## Decode fixtures
129
-
130
- `vectors/decode-frames/` holds 36 synthetic frames as 8-bit greyscale PNG,
131
- and `vectors/decode-frames.json` gives each one's exact text, the drawing
132
- parameters, and whether this decoder and jsQR read it when the frames were
133
- made. A port must read every frame marked `"typescript": true`. The frames
134
- are drawn by `test/fixtures.test.ts` (`QR_WRITE_FIXTURES=1` regenerates
135
- them); every payload in them is made up. Real photographs will join them as
136
- a separate set.
@@ -1,26 +0,0 @@
1
- /** A greyscale picture: one byte per pixel, 0 black to 255 white. */
2
- export interface Luminance {
3
- readonly data: Uint8Array;
4
- readonly width: number;
5
- readonly height: number;
6
- }
7
- /** A black-and-white picture: 1 for dark. */
8
- export interface BitImage {
9
- readonly data: Uint8Array;
10
- readonly width: number;
11
- readonly height: number;
12
- }
13
- /**
14
- * Greyscale from RGBA (4 bytes a pixel) or grey (1 byte a pixel) data, with
15
- * an optional row stride in pixels for padded camera planes. Transparent
16
- * pixels count as white, so a code drawn on a transparent canvas still reads.
17
- */
18
- export declare function toLuminance(data: Uint8Array | Uint8ClampedArray, width: number, height: number, channels: 1 | 4, rowStride?: number): Luminance;
19
- /**
20
- * Local-threshold binarisation: each 8x8 block's threshold is the mean of the
21
- * block averages in the 5x5 blocks around it, and a flat block borrows its
22
- * neighbours' threshold, so shadows and gradients across the frame do not
23
- * swallow the code. Pictures under 40 pixels a side use one global threshold.
24
- */
25
- export declare function binarise(image: Luminance): BitImage;
26
- export declare function invert(image: BitImage): BitImage;
package/dist/binarise.js DELETED
@@ -1,115 +0,0 @@
1
- /**
2
- * Greyscale from RGBA (4 bytes a pixel) or grey (1 byte a pixel) data, with
3
- * an optional row stride in pixels for padded camera planes. Transparent
4
- * pixels count as white, so a code drawn on a transparent canvas still reads.
5
- */
6
- export function toLuminance(data, width, height, channels, rowStride = width) {
7
- const out = new Uint8Array(width * height);
8
- for (let y = 0; y < height; y++) {
9
- for (let x = 0; x < width; x++) {
10
- const i = (y * rowStride + x) * channels;
11
- if (channels === 1) {
12
- out[y * width + x] = data[i];
13
- }
14
- else {
15
- const a = data[i + 3];
16
- const grey = (data[i] * 77 + data[i + 1] * 150 + data[i + 2] * 29) >> 8;
17
- out[y * width + x] = (grey * a + 255 * (255 - a)) / 255;
18
- }
19
- }
20
- }
21
- return { data: out, width, height };
22
- }
23
- const BLOCK = 8;
24
- const MIN_DYNAMIC_RANGE = 24;
25
- /**
26
- * Local-threshold binarisation: each 8x8 block's threshold is the mean of the
27
- * block averages in the 5x5 blocks around it, and a flat block borrows its
28
- * neighbours' threshold, so shadows and gradients across the frame do not
29
- * swallow the code. Pictures under 40 pixels a side use one global threshold.
30
- */
31
- export function binarise(image) {
32
- const { data, width, height } = image;
33
- const out = new Uint8Array(width * height);
34
- if (width < 40 || height < 40) {
35
- let sum = 0;
36
- let min = 255;
37
- let max = 0;
38
- for (const v of data) {
39
- sum += v;
40
- if (v < min)
41
- min = v;
42
- if (v > max)
43
- max = v;
44
- }
45
- const threshold = max - min < MIN_DYNAMIC_RANGE ? -1 : (min + max) / 2;
46
- for (let i = 0; i < data.length; i++)
47
- out[i] = data[i] <= threshold ? 1 : 0;
48
- return { data: out, width, height };
49
- }
50
- const across = Math.ceil(width / BLOCK);
51
- const down = Math.ceil(height / BLOCK);
52
- const levels = new Float64Array(across * down);
53
- for (let by = 0; by < down; by++) {
54
- const top = Math.min(by * BLOCK, height - BLOCK);
55
- for (let bx = 0; bx < across; bx++) {
56
- const left = Math.min(bx * BLOCK, width - BLOCK);
57
- let sum = 0;
58
- let min = 255;
59
- let max = 0;
60
- for (let y = top; y < top + BLOCK; y++) {
61
- for (let x = left; x < left + BLOCK; x++) {
62
- const v = data[y * width + x];
63
- sum += v;
64
- if (v < min)
65
- min = v;
66
- if (v > max)
67
- max = v;
68
- }
69
- }
70
- let level = sum / (BLOCK * BLOCK);
71
- if (max - min <= MIN_DYNAMIC_RANGE) {
72
- // A flat block: assume it is background unless its neighbours say otherwise.
73
- level = min / 2;
74
- if (bx > 0 && by > 0) {
75
- const around = (levels[(by - 1) * across + bx] + 2 * levels[by * across + bx - 1] + levels[(by - 1) * across + bx - 1]) / 4;
76
- if (min < around)
77
- level = around;
78
- }
79
- }
80
- levels[by * across + bx] = level;
81
- }
82
- }
83
- for (let by = 0; by < down; by++) {
84
- const top = Math.min(by * BLOCK, height - BLOCK);
85
- const cy = Math.min(Math.max(by, 2), down - 3);
86
- for (let bx = 0; bx < across; bx++) {
87
- const left = Math.min(bx * BLOCK, width - BLOCK);
88
- const cx = Math.min(Math.max(bx, 2), across - 3);
89
- let sum = 0;
90
- let n = 0;
91
- for (let dy = -2; dy <= 2; dy++) {
92
- for (let dx = -2; dx <= 2; dx++) {
93
- const yy = cy + dy;
94
- const xx = cx + dx;
95
- if (yy < 0 || xx < 0 || yy >= down || xx >= across)
96
- continue;
97
- sum += levels[yy * across + xx];
98
- n++;
99
- }
100
- }
101
- const threshold = sum / n;
102
- for (let y = top; y < top + BLOCK; y++) {
103
- for (let x = left; x < left + BLOCK; x++)
104
- out[y * width + x] = data[y * width + x] <= threshold ? 1 : 0;
105
- }
106
- }
107
- }
108
- return { data: out, width, height };
109
- }
110
- export function invert(image) {
111
- const out = new Uint8Array(image.data.length);
112
- for (let i = 0; i < out.length; i++)
113
- out[i] = image.data[i] ^ 1;
114
- return { data: out, width: image.width, height: image.height };
115
- }
package/dist/canvas.d.ts DELETED
@@ -1,29 +0,0 @@
1
- import { type QrCode } from './encode.js';
2
- /** The slice of a 2D context the renderer uses, so it needs no DOM types. */
3
- export interface Canvas2dLike {
4
- fillStyle: unknown;
5
- fillRect(x: number, y: number, width: number, height: number): void;
6
- }
7
- /** An `HTMLCanvasElement`, an `OffscreenCanvas`, or anything shaped like one. */
8
- export interface CanvasLike {
9
- width: number;
10
- height: number;
11
- getContext(type: '2d'): Canvas2dLike | null;
12
- }
13
- export interface CanvasOptions {
14
- /** Width and height of the canvas in pixels. Default 8 pixels per module. */
15
- width?: number;
16
- /** Quiet zone in modules on every side. Default 4, the standard's minimum. */
17
- margin?: number;
18
- /** Colour of dark modules. Default `'#000'`. */
19
- dark?: string;
20
- /** Background colour, or `null` to leave the canvas transparent. Default `'#fff'`. */
21
- light?: string | null;
22
- }
23
- /**
24
- * Draws a code onto a canvas, resizing it to `width` square and replacing
25
- * whatever it held. Module edges fall on whole pixels, so nothing is
26
- * anti-aliased; when `width` is not a multiple of the module count, modules
27
- * differ in size by at most one pixel.
28
- */
29
- export declare function drawCanvas(canvas: CanvasLike, qr: QrCode, options?: CanvasOptions): void;
package/dist/canvas.js DELETED
@@ -1,44 +0,0 @@
1
- /**
2
- * Draws a code onto a canvas, resizing it to `width` square and replacing
3
- * whatever it held. Module edges fall on whole pixels, so nothing is
4
- * anti-aliased; when `width` is not a multiple of the module count, modules
5
- * differ in size by at most one pixel.
6
- */
7
- export function drawCanvas(canvas, qr, options = {}) {
8
- const margin = options.margin ?? 4;
9
- if (!Number.isInteger(margin) || margin < 0)
10
- throw new RangeError('Margin must be a whole number of modules');
11
- const side = qr.size + margin * 2;
12
- const width = options.width ?? side * 8;
13
- if (!Number.isInteger(width) || width < side) {
14
- throw new RangeError(`Width must be a whole number of pixels, at least ${side} for this code`);
15
- }
16
- canvas.width = width;
17
- canvas.height = width;
18
- const context = canvas.getContext('2d');
19
- if (context === null)
20
- throw new Error('The canvas has no 2D context');
21
- const light = options.light === undefined ? '#fff' : options.light;
22
- const edge = (module) => Math.round((module * width) / side);
23
- if (light !== null) {
24
- context.fillStyle = light;
25
- context.fillRect(0, 0, width, width);
26
- }
27
- context.fillStyle = options.dark ?? '#000';
28
- for (let y = 0; y < qr.size; y++) {
29
- const top = edge(y + margin);
30
- const height = edge(y + margin + 1) - top;
31
- let x = 0;
32
- while (x < qr.size) {
33
- if (qr.modules[y * qr.size + x] !== 1) {
34
- x++;
35
- continue;
36
- }
37
- const start = x;
38
- while (x < qr.size && qr.modules[y * qr.size + x] === 1)
39
- x++;
40
- const left = edge(start + margin);
41
- context.fillRect(left, top, edge(x + margin) - left, height);
42
- }
43
- }
44
- }
package/dist/decode.d.ts DELETED
@@ -1,42 +0,0 @@
1
- import { type Point } from './locate.js';
2
- import { type Payload } from './symbol.js';
3
- /** Pixels to read: RGBA (4 bytes a pixel, as ImageData gives) or greyscale (1 byte a pixel). */
4
- export interface ImageLike {
5
- readonly data: Uint8Array | Uint8ClampedArray;
6
- readonly width: number;
7
- readonly height: number;
8
- }
9
- export interface DecodeOptions {
10
- /** Pixels from the start of one row to the next, for padded camera planes. Default: width. */
11
- readonly rowStride?: number;
12
- /** Bytes a pixel. Default: worked out from the data length. */
13
- readonly channels?: 1 | 4;
14
- /** Which way round to look for dark modules. Default 'both': dark on light first, then light on dark. */
15
- readonly inversion?: 'normal' | 'inverted' | 'both';
16
- }
17
- /** A symbol found in a picture. */
18
- export interface DecodeResult extends Payload {
19
- /** The symbol was mirrored (read transposed). */
20
- readonly mirrored: boolean;
21
- /** The symbol was light on dark. */
22
- readonly inverted: boolean;
23
- /** Picture coordinates of the finder pattern centres, and of the alignment pattern if one was used. */
24
- readonly location: {
25
- readonly topLeft: Point;
26
- readonly topRight: Point;
27
- readonly bottomLeft: Point;
28
- readonly alignment: Point | undefined;
29
- };
30
- }
31
- /**
32
- * Finds and reads one QR code in a picture. Returns undefined when no
33
- * readable code is found.
34
- */
35
- export declare function decode(image: ImageLike, options?: DecodeOptions): DecodeResult | undefined;
36
- /**
37
- * Reads a symbol from its module grid, `size` by `size`, row by row, truthy
38
- * for dark, with no quiet zone. Mirrored grids read too.
39
- */
40
- export declare function decodeModules(modules: ArrayLike<number | boolean>, size: number): (Payload & {
41
- readonly mirrored: boolean;
42
- }) | undefined;