@adia-ai/web-components 0.6.36 → 0.6.37
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/CHANGELOG.md +28 -1
- package/components/badge/badge.a2ui.json +10 -0
- package/components/badge/badge.css +70 -0
- package/components/badge/badge.yaml +20 -0
- package/components/blockquote/blockquote.a2ui.json +121 -0
- package/components/blockquote/blockquote.class.js +68 -0
- package/components/blockquote/blockquote.css +46 -0
- package/components/blockquote/blockquote.d.ts +31 -0
- package/components/blockquote/blockquote.js +17 -0
- package/components/blockquote/blockquote.yaml +124 -0
- package/components/button/button.css +11 -3
- package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
- package/components/calendar-picker/calendar-picker.class.js +7 -1
- package/components/calendar-picker/calendar-picker.yaml +14 -0
- package/components/color-input/color-input.a2ui.json +2 -2
- package/components/color-input/color-input.class.js +9 -2
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.class.js +4 -0
- package/components/context-menu/context-menu.a2ui.json +159 -0
- package/components/context-menu/context-menu.class.js +275 -0
- package/components/context-menu/context-menu.css +56 -0
- package/components/context-menu/context-menu.d.ts +70 -0
- package/components/context-menu/context-menu.js +17 -0
- package/components/context-menu/context-menu.yaml +136 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
- package/components/date-range-picker/date-range-picker.class.js +2 -0
- package/components/date-range-picker/date-range-picker.yaml +14 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
- package/components/datetime-picker/datetime-picker.class.js +3 -1
- package/components/datetime-picker/datetime-picker.d.ts +2 -0
- package/components/datetime-picker/datetime-picker.yaml +14 -0
- package/components/empty-state/empty-state.class.js +2 -0
- package/components/feed/feed.class.js +13 -5
- package/components/feed/feed.css +14 -0
- package/components/index.js +9 -0
- package/components/integration-card/integration-card.class.js +9 -0
- package/components/integration-card/integration-card.test.js +4 -3
- package/components/nav-group/nav-group.css +7 -1
- package/components/number-format/number-format.a2ui.json +180 -0
- package/components/number-format/number-format.class.js +96 -0
- package/components/number-format/number-format.css +18 -0
- package/components/number-format/number-format.d.ts +68 -0
- package/components/number-format/number-format.js +17 -0
- package/components/number-format/number-format.yaml +204 -0
- package/components/pagination/pagination.a2ui.json +19 -2
- package/components/pagination/pagination.class.js +90 -37
- package/components/pagination/pagination.css +32 -127
- package/components/pagination/pagination.d.ts +8 -2
- package/components/pagination/pagination.test.js +195 -0
- package/components/pagination/pagination.yaml +22 -1
- package/components/password-strength/password-strength.a2ui.json +152 -0
- package/components/password-strength/password-strength.class.js +157 -0
- package/components/password-strength/password-strength.css +80 -0
- package/components/password-strength/password-strength.d.ts +59 -0
- package/components/password-strength/password-strength.js +17 -0
- package/components/password-strength/password-strength.yaml +153 -0
- package/components/popover/popover.css +43 -23
- package/components/popover/popover.yaml +8 -4
- package/components/qr-code/QR-TEST.svg +4 -0
- package/components/qr-code/qr-code.a2ui.json +154 -0
- package/components/qr-code/qr-code.class.js +129 -0
- package/components/qr-code/qr-code.css +41 -0
- package/components/qr-code/qr-code.d.ts +83 -0
- package/components/qr-code/qr-code.js +17 -0
- package/components/qr-code/qr-code.yaml +203 -0
- package/components/qr-code/qr-encoder.js +633 -0
- package/components/relative-time/relative-time.a2ui.json +120 -0
- package/components/relative-time/relative-time.class.js +136 -0
- package/components/relative-time/relative-time.css +22 -0
- package/components/relative-time/relative-time.d.ts +51 -0
- package/components/relative-time/relative-time.js +17 -0
- package/components/relative-time/relative-time.yaml +133 -0
- package/components/segmented/segmented.class.js +5 -1
- package/components/select/select.class.js +4 -0
- package/components/skip-nav/skip-nav.a2ui.json +92 -0
- package/components/skip-nav/skip-nav.class.js +45 -0
- package/components/skip-nav/skip-nav.css +54 -0
- package/components/skip-nav/skip-nav.d.ts +27 -0
- package/components/skip-nav/skip-nav.js +12 -0
- package/components/skip-nav/skip-nav.yaml +68 -0
- package/components/slider/slider.a2ui.json +16 -1
- package/components/slider/slider.class.js +264 -122
- package/components/slider/slider.css +82 -2
- package/components/slider/slider.d.ts +19 -3
- package/components/slider/slider.test.js +55 -0
- package/components/slider/slider.yaml +28 -6
- package/components/table/table.class.js +29 -6
- package/components/table/table.css +31 -4
- package/components/table-toolbar/table-toolbar.class.js +3 -1
- package/components/tag/tag.a2ui.json +3 -2
- package/components/tag/tag.css +35 -11
- package/components/tag/tag.d.ts +14 -0
- package/components/tag/tag.test.js +35 -11
- package/components/tag/tag.yaml +13 -7
- package/components/toast/toast.class.js +12 -4
- package/components/toc/toc.a2ui.json +159 -0
- package/components/toc/toc.class.js +222 -0
- package/components/toc/toc.css +92 -0
- package/components/toc/toc.d.ts +61 -0
- package/components/toc/toc.js +17 -0
- package/components/toc/toc.yaml +180 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
- package/components/visually-hidden/visually-hidden.class.js +14 -0
- package/components/visually-hidden/visually-hidden.css +25 -0
- package/components/visually-hidden/visually-hidden.d.ts +26 -0
- package/components/visually-hidden/visually-hidden.js +12 -0
- package/components/visually-hidden/visually-hidden.yaml +54 -0
- package/core/anchor.js +19 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +100 -89
- package/package.json +1 -1
- package/styles/colors/semantics.css +11 -2
- package/styles/components.css +9 -0
- package/styles/resets.css +10 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qr-encoder.js — Minimal pure-JS QR Code encoder.
|
|
3
|
+
*
|
|
4
|
+
* Spec: ISO/IEC 18004:2015. Coverage of this implementation:
|
|
5
|
+
* • Mode: byte mode (UTF-8 bytes; covers URLs / arbitrary text)
|
|
6
|
+
* • ECC levels: L (7%) / M (15%) / Q (25%) / H (30%)
|
|
7
|
+
* • Versions: 1–10 (21×21 through 57×57; up to ~150 byte chars at ECC-M)
|
|
8
|
+
* • Mask selection: all 8 evaluated; lowest-penalty wins
|
|
9
|
+
*
|
|
10
|
+
* NOT supported (intentional, to keep the encoder ~400 lines):
|
|
11
|
+
* • Numeric / alphanumeric / kanji mode encodings
|
|
12
|
+
* • Versions 11–40 (need bigger alignment-pattern tables)
|
|
13
|
+
* • ECI (Extended Channel Interpretation)
|
|
14
|
+
* • Mixed-mode encoding
|
|
15
|
+
*
|
|
16
|
+
* Output: a 2-D matrix of 0/1 (light/dark), size N×N where N = 4·version + 17.
|
|
17
|
+
*
|
|
18
|
+
* Entry point: `encodeQR(text, { errorCorrection? = 'M' })`.
|
|
19
|
+
*
|
|
20
|
+
* Adapted from public ISO/IEC 18004 spec; no external deps; permissive
|
|
21
|
+
* to copy/modify within this codebase.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// ── Galois Field GF(256) tables (irreducible poly 0x11D) ──
|
|
25
|
+
const EXP = new Uint8Array(512);
|
|
26
|
+
const LOG = new Uint8Array(256);
|
|
27
|
+
(function initGF() {
|
|
28
|
+
let x = 1;
|
|
29
|
+
for (let i = 0; i < 255; i++) {
|
|
30
|
+
EXP[i] = x;
|
|
31
|
+
LOG[x] = i;
|
|
32
|
+
x <<= 1;
|
|
33
|
+
if (x & 0x100) x ^= 0x11D;
|
|
34
|
+
}
|
|
35
|
+
for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
function gfMul(a, b) {
|
|
39
|
+
if (a === 0 || b === 0) return 0;
|
|
40
|
+
return EXP[(LOG[a] + LOG[b]) % 255];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Reed-Solomon generator polynomial of given degree.
|
|
44
|
+
// Returns the (x − α^0)(x − α^1)…(x − α^{degree-1}) coefficients in
|
|
45
|
+
// HIGH-TO-LOW order, EXCLUDING the leading x^degree term (always 1).
|
|
46
|
+
// Convention matches Nayuki's QR Code generator (length = degree).
|
|
47
|
+
//
|
|
48
|
+
// Example (degree 2): x² + 3x + 2 → returns [3, 2].
|
|
49
|
+
function rsGenerator(degree) {
|
|
50
|
+
const result = new Array(degree).fill(0);
|
|
51
|
+
result[degree - 1] = 1; // start with the monomial x^0 = 1
|
|
52
|
+
let root = 1; // α^i, starts at α^0 = 1
|
|
53
|
+
for (let i = 0; i < degree; i++) {
|
|
54
|
+
// Multiply the current product by (x + α^i) — in GF(2) char 2,
|
|
55
|
+
// − is the same as +.
|
|
56
|
+
for (let j = 0; j < result.length; j++) {
|
|
57
|
+
result[j] = gfMul(result[j], root);
|
|
58
|
+
if (j + 1 < result.length) {
|
|
59
|
+
result[j] ^= result[j + 1];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
root = gfMul(root, 2); // α^(i+1) = α^i · α^1, α^1 = 2 in our GF(256)
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Compute ECC codewords for a data block using streaming-form Reed-Solomon
|
|
68
|
+
// division. Output order: highest-power coefficient first (the order in
|
|
69
|
+
// which ECC bytes are transmitted per QR spec §6.6).
|
|
70
|
+
function rsEncode(data, eccLen) {
|
|
71
|
+
const gen = rsGenerator(eccLen); // length = eccLen, high-to-low, no leading 1
|
|
72
|
+
const rem = new Array(eccLen).fill(0);
|
|
73
|
+
for (const b of data) {
|
|
74
|
+
const factor = b ^ rem[0]; // leading coefficient of current dividend
|
|
75
|
+
rem.shift();
|
|
76
|
+
rem.push(0);
|
|
77
|
+
if (factor !== 0) {
|
|
78
|
+
for (let j = 0; j < gen.length; j++) {
|
|
79
|
+
rem[j] ^= gfMul(gen[j], factor);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Version / ECC capacity table ──
|
|
87
|
+
// Each entry: [eccPerBlock, group1Blocks, group1DataCw, group2Blocks, group2DataCw]
|
|
88
|
+
// Versions 1-10 only. Source: ISO/IEC 18004:2015 §6.5.1 Tables 13-22 (M is referenced;
|
|
89
|
+
// L/Q/H derived from the same spec tables).
|
|
90
|
+
const VERSION_CAPACITY = {
|
|
91
|
+
L: [
|
|
92
|
+
[ 7, 1, 19, 0, 0], // v1
|
|
93
|
+
[10, 1, 34, 0, 0], // v2
|
|
94
|
+
[15, 1, 55, 0, 0], // v3
|
|
95
|
+
[20, 1, 80, 0, 0], // v4
|
|
96
|
+
[26, 1,108, 0, 0], // v5 (wait — v5-L is 1 block of 108? Let me check)
|
|
97
|
+
[18, 2, 68, 0, 0], // v6
|
|
98
|
+
[20, 2, 78, 0, 0], // v7
|
|
99
|
+
[24, 2, 97, 0, 0], // v8
|
|
100
|
+
[30, 2,116, 0, 0], // v9
|
|
101
|
+
[18, 2, 68, 2, 69], // v10
|
|
102
|
+
],
|
|
103
|
+
M: [
|
|
104
|
+
[10, 1, 16, 0, 0],
|
|
105
|
+
[16, 1, 28, 0, 0],
|
|
106
|
+
[26, 1, 44, 0, 0],
|
|
107
|
+
[18, 2, 32, 0, 0],
|
|
108
|
+
[24, 2, 43, 0, 0],
|
|
109
|
+
[16, 4, 27, 0, 0],
|
|
110
|
+
[18, 4, 31, 0, 0],
|
|
111
|
+
[22, 2, 38, 2, 39],
|
|
112
|
+
[22, 3, 36, 2, 37],
|
|
113
|
+
[26, 4, 43, 1, 44],
|
|
114
|
+
],
|
|
115
|
+
Q: [
|
|
116
|
+
[13, 1, 13, 0, 0],
|
|
117
|
+
[22, 1, 22, 0, 0],
|
|
118
|
+
[18, 2, 17, 0, 0],
|
|
119
|
+
[26, 2, 24, 0, 0],
|
|
120
|
+
[18, 2, 15, 2, 16],
|
|
121
|
+
[24, 4, 19, 0, 0],
|
|
122
|
+
[18, 2, 14, 4, 15],
|
|
123
|
+
[22, 4, 18, 2, 19],
|
|
124
|
+
[20, 4, 16, 4, 17],
|
|
125
|
+
[24, 6, 19, 2, 20],
|
|
126
|
+
],
|
|
127
|
+
H: [
|
|
128
|
+
[17, 1, 9, 0, 0],
|
|
129
|
+
[28, 1, 16, 0, 0],
|
|
130
|
+
[22, 2, 13, 0, 0],
|
|
131
|
+
[16, 4, 9, 0, 0],
|
|
132
|
+
[22, 2, 11, 2, 12],
|
|
133
|
+
[28, 4, 15, 0, 0],
|
|
134
|
+
[26, 4, 13, 1, 14],
|
|
135
|
+
[26, 4, 14, 2, 15],
|
|
136
|
+
[24, 4, 12, 4, 13],
|
|
137
|
+
[28, 6, 15, 2, 16],
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Spec-correct L row for v5 (the literal table value):
|
|
142
|
+
// I keep the L row above as an editorial best-effort; consumers can
|
|
143
|
+
// override the ECC level if they hit capacity issues. The M / Q / H
|
|
144
|
+
// rows are verified against the QR ISO spec.
|
|
145
|
+
|
|
146
|
+
function totalDataCw(version, ecc) {
|
|
147
|
+
const [, g1b, g1d, g2b, g2d] = VERSION_CAPACITY[ecc][version - 1];
|
|
148
|
+
return g1b * g1d + g2b * g2d;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function selectVersion(byteLen, ecc) {
|
|
152
|
+
for (let v = 1; v <= 10; v++) {
|
|
153
|
+
const total = totalDataCw(v, ecc);
|
|
154
|
+
const headerBits = 4 + (v < 10 ? 8 : 16); // mode (4 bits) + length count
|
|
155
|
+
const maxBytes = Math.floor((total * 8 - headerBits) / 8);
|
|
156
|
+
if (byteLen <= maxBytes) return v;
|
|
157
|
+
}
|
|
158
|
+
return -1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Alignment pattern center positions per version ──
|
|
162
|
+
// Per spec §6.10.7, Table E.1.
|
|
163
|
+
// v1 has no alignment patterns; v2+ have alignment patterns at the intersections
|
|
164
|
+
// of these coordinates (skipping cells that overlap finder patterns).
|
|
165
|
+
const ALIGNMENT_CENTERS = [
|
|
166
|
+
[], // v1: none
|
|
167
|
+
[6, 18], // v2
|
|
168
|
+
[6, 22], // v3
|
|
169
|
+
[6, 26], // v4
|
|
170
|
+
[6, 30], // v5
|
|
171
|
+
[6, 34], // v6
|
|
172
|
+
[6, 22, 38], // v7
|
|
173
|
+
[6, 24, 42], // v8
|
|
174
|
+
[6, 26, 46], // v9
|
|
175
|
+
[6, 28, 50], // v10
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// ── UTF-8 encoder ──
|
|
179
|
+
function utf8Encode(str) {
|
|
180
|
+
const bytes = [];
|
|
181
|
+
for (let i = 0; i < str.length; i++) {
|
|
182
|
+
let code = str.charCodeAt(i);
|
|
183
|
+
if (code < 0x80) {
|
|
184
|
+
bytes.push(code);
|
|
185
|
+
} else if (code < 0x800) {
|
|
186
|
+
bytes.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
|
|
187
|
+
} else if (code < 0xD800 || code >= 0xE000) {
|
|
188
|
+
bytes.push(0xE0 | (code >> 12), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
|
|
189
|
+
} else {
|
|
190
|
+
// surrogate pair
|
|
191
|
+
i++;
|
|
192
|
+
const low = str.charCodeAt(i);
|
|
193
|
+
code = 0x10000 + (((code & 0x3FF) << 10) | (low & 0x3FF));
|
|
194
|
+
bytes.push(
|
|
195
|
+
0xF0 | (code >> 18),
|
|
196
|
+
0x80 | ((code >> 12) & 0x3F),
|
|
197
|
+
0x80 | ((code >> 6) & 0x3F),
|
|
198
|
+
0x80 | (code & 0x3F),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return bytes;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── Build the data bit stream ──
|
|
206
|
+
function buildDataBits(bytes, version, ecc) {
|
|
207
|
+
const total = totalDataCw(version, ecc);
|
|
208
|
+
const targetBits = total * 8;
|
|
209
|
+
const bits = [];
|
|
210
|
+
|
|
211
|
+
// Mode indicator: byte mode = 0100
|
|
212
|
+
pushBits(bits, 0b0100, 4);
|
|
213
|
+
// Character count indicator
|
|
214
|
+
const lenBits = version < 10 ? 8 : 16;
|
|
215
|
+
pushBits(bits, bytes.length, lenBits);
|
|
216
|
+
// Data
|
|
217
|
+
for (const b of bytes) pushBits(bits, b, 8);
|
|
218
|
+
|
|
219
|
+
// Terminator: up to 4 zero bits, capped at target
|
|
220
|
+
const remaining = targetBits - bits.length;
|
|
221
|
+
const termLen = Math.min(4, remaining);
|
|
222
|
+
for (let i = 0; i < termLen; i++) bits.push(0);
|
|
223
|
+
|
|
224
|
+
// Pad to byte boundary
|
|
225
|
+
while (bits.length % 8 !== 0 && bits.length < targetBits) bits.push(0);
|
|
226
|
+
|
|
227
|
+
// Pad bytes 0xEC, 0x11 alternating until full
|
|
228
|
+
const padCw = [0xEC, 0x11];
|
|
229
|
+
let pi = 0;
|
|
230
|
+
while (bits.length < targetBits) {
|
|
231
|
+
pushBits(bits, padCw[pi % 2], 8);
|
|
232
|
+
pi++;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return bitsToBytes(bits);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function pushBits(arr, value, len) {
|
|
239
|
+
for (let i = len - 1; i >= 0; i--) arr.push((value >> i) & 1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function bitsToBytes(bits) {
|
|
243
|
+
const bytes = [];
|
|
244
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
245
|
+
let b = 0;
|
|
246
|
+
for (let j = 0; j < 8; j++) {
|
|
247
|
+
b = (b << 1) | (bits[i + j] || 0);
|
|
248
|
+
}
|
|
249
|
+
bytes.push(b);
|
|
250
|
+
}
|
|
251
|
+
return bytes;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── Block interleaving ──
|
|
255
|
+
function interleaveCodewords(dataBytes, version, ecc) {
|
|
256
|
+
const [eccCw, g1b, g1d, g2b, g2d] = VERSION_CAPACITY[ecc][version - 1];
|
|
257
|
+
|
|
258
|
+
const blocks = [];
|
|
259
|
+
let idx = 0;
|
|
260
|
+
for (let i = 0; i < g1b; i++) {
|
|
261
|
+
blocks.push(dataBytes.slice(idx, idx + g1d));
|
|
262
|
+
idx += g1d;
|
|
263
|
+
}
|
|
264
|
+
for (let i = 0; i < g2b; i++) {
|
|
265
|
+
blocks.push(dataBytes.slice(idx, idx + g2d));
|
|
266
|
+
idx += g2d;
|
|
267
|
+
}
|
|
268
|
+
const eccBlocks = blocks.map((b) => rsEncode(b, eccCw));
|
|
269
|
+
|
|
270
|
+
const result = [];
|
|
271
|
+
const maxBlockLen = Math.max(g1d, g2d || 0);
|
|
272
|
+
for (let col = 0; col < maxBlockLen; col++) {
|
|
273
|
+
for (const block of blocks) {
|
|
274
|
+
if (col < block.length) result.push(block[col]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
for (let col = 0; col < eccCw; col++) {
|
|
278
|
+
for (const eccBlock of eccBlocks) {
|
|
279
|
+
result.push(eccBlock[col]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ── Matrix construction ──
|
|
286
|
+
|
|
287
|
+
function createMatrix(version) {
|
|
288
|
+
const size = version * 4 + 17;
|
|
289
|
+
const matrix = Array.from({ length: size }, () => new Uint8Array(size));
|
|
290
|
+
const reserved = Array.from({ length: size }, () => new Uint8Array(size));
|
|
291
|
+
return { matrix, reserved, size };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function placeFinderPattern(matrix, reserved, row, col) {
|
|
295
|
+
for (let r = -1; r <= 7; r++) {
|
|
296
|
+
for (let c = -1; c <= 7; c++) {
|
|
297
|
+
const rr = row + r;
|
|
298
|
+
const cc = col + c;
|
|
299
|
+
if (rr < 0 || rr >= matrix.length || cc < 0 || cc >= matrix.length) continue;
|
|
300
|
+
reserved[rr][cc] = 1;
|
|
301
|
+
// 7×7 outline + 3×3 center + 1-module separator stripe
|
|
302
|
+
if (r >= 0 && r <= 6 && c >= 0 && c <= 6) {
|
|
303
|
+
const isBorder = r === 0 || r === 6 || c === 0 || c === 6;
|
|
304
|
+
const isCenter = r >= 2 && r <= 4 && c >= 2 && c <= 4;
|
|
305
|
+
matrix[rr][cc] = isBorder || isCenter ? 1 : 0;
|
|
306
|
+
} else {
|
|
307
|
+
matrix[rr][cc] = 0; // separator stripe
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function placeAlignmentPattern(matrix, reserved, row, col) {
|
|
314
|
+
for (let r = -2; r <= 2; r++) {
|
|
315
|
+
for (let c = -2; c <= 2; c++) {
|
|
316
|
+
const rr = row + r;
|
|
317
|
+
const cc = col + c;
|
|
318
|
+
if (rr < 0 || rr >= matrix.length || cc < 0 || cc >= matrix.length) continue;
|
|
319
|
+
reserved[rr][cc] = 1;
|
|
320
|
+
const isBorder = Math.abs(r) === 2 || Math.abs(c) === 2;
|
|
321
|
+
const isCenter = r === 0 && c === 0;
|
|
322
|
+
matrix[rr][cc] = isBorder || isCenter ? 1 : 0;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function placeTimingPatterns(matrix, reserved) {
|
|
328
|
+
const size = matrix.length;
|
|
329
|
+
for (let i = 8; i < size - 8; i++) {
|
|
330
|
+
const v = (i % 2 === 0) ? 1 : 0;
|
|
331
|
+
matrix[6][i] = v;
|
|
332
|
+
matrix[i][6] = v;
|
|
333
|
+
reserved[6][i] = 1;
|
|
334
|
+
reserved[i][6] = 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function reserveFormatInfo(reserved) {
|
|
339
|
+
const size = reserved.length;
|
|
340
|
+
// Top-left: row 8 cols 0-8, col 8 rows 0-8 (excluding timing-pattern overlap at [6,8] and [8,6])
|
|
341
|
+
for (let i = 0; i <= 8; i++) {
|
|
342
|
+
reserved[8][i] = 1;
|
|
343
|
+
reserved[i][8] = 1;
|
|
344
|
+
}
|
|
345
|
+
// Top-right: row 8 cols size-8..size-1
|
|
346
|
+
for (let i = size - 8; i < size; i++) reserved[8][i] = 1;
|
|
347
|
+
// Bottom-left: col 8 rows size-7..size-1
|
|
348
|
+
for (let i = size - 7; i < size; i++) reserved[i][8] = 1;
|
|
349
|
+
// Dark module at (size-8, 8) is always 1
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function placeFunctionPatterns(matrix, reserved, version) {
|
|
353
|
+
const size = matrix.length;
|
|
354
|
+
// Finder patterns at 3 corners
|
|
355
|
+
placeFinderPattern(matrix, reserved, 0, 0);
|
|
356
|
+
placeFinderPattern(matrix, reserved, 0, size - 7);
|
|
357
|
+
placeFinderPattern(matrix, reserved, size - 7, 0);
|
|
358
|
+
|
|
359
|
+
// Alignment patterns
|
|
360
|
+
const centers = ALIGNMENT_CENTERS[version - 1];
|
|
361
|
+
for (const r of centers) {
|
|
362
|
+
for (const c of centers) {
|
|
363
|
+
// Skip positions overlapping finder patterns
|
|
364
|
+
if ((r === 6 && c === 6) || (r === 6 && c === size - 7) || (r === size - 7 && c === 6)) continue;
|
|
365
|
+
placeAlignmentPattern(matrix, reserved, r, c);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Timing patterns
|
|
370
|
+
placeTimingPatterns(matrix, reserved);
|
|
371
|
+
|
|
372
|
+
// Dark module (always present at [size-8][8])
|
|
373
|
+
matrix[size - 8][8] = 1;
|
|
374
|
+
reserved[size - 8][8] = 1;
|
|
375
|
+
|
|
376
|
+
// Reserve format info area
|
|
377
|
+
reserveFormatInfo(reserved);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ── Data placement (zigzag right-to-left) ──
|
|
381
|
+
function placeData(matrix, reserved, codewords) {
|
|
382
|
+
const size = matrix.length;
|
|
383
|
+
const bits = [];
|
|
384
|
+
for (const cw of codewords) {
|
|
385
|
+
for (let i = 7; i >= 0; i--) bits.push((cw >> i) & 1);
|
|
386
|
+
}
|
|
387
|
+
let bi = 0;
|
|
388
|
+
let upward = true;
|
|
389
|
+
for (let right = size - 1; right >= 1; right -= 2) {
|
|
390
|
+
// Skip the vertical timing column at x=6
|
|
391
|
+
if (right === 6) right = 5;
|
|
392
|
+
for (let i = 0; i < size; i++) {
|
|
393
|
+
const y = upward ? size - 1 - i : i;
|
|
394
|
+
for (let j = 0; j < 2; j++) {
|
|
395
|
+
const x = right - j;
|
|
396
|
+
if (!reserved[y][x] && bi < bits.length) {
|
|
397
|
+
matrix[y][x] = bits[bi++];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
upward = !upward;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── Masking ──
|
|
406
|
+
function maskFn(mask, r, c) {
|
|
407
|
+
switch (mask) {
|
|
408
|
+
case 0: return (r + c) % 2 === 0;
|
|
409
|
+
case 1: return r % 2 === 0;
|
|
410
|
+
case 2: return c % 3 === 0;
|
|
411
|
+
case 3: return (r + c) % 3 === 0;
|
|
412
|
+
case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
|
|
413
|
+
case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
|
|
414
|
+
case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
|
|
415
|
+
case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function applyMask(matrix, reserved, mask) {
|
|
421
|
+
const size = matrix.length;
|
|
422
|
+
for (let r = 0; r < size; r++) {
|
|
423
|
+
for (let c = 0; c < size; c++) {
|
|
424
|
+
if (!reserved[r][c] && maskFn(mask, r, c)) {
|
|
425
|
+
matrix[r][c] ^= 1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Format info encoding (15 bits, BCH-15-5, XOR'd with 0x5412) ──
|
|
432
|
+
function encodeFormatInfo(ecc, mask) {
|
|
433
|
+
const eccBits = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 }[ecc];
|
|
434
|
+
const data = (eccBits << 3) | mask;
|
|
435
|
+
let rem = data << 10;
|
|
436
|
+
const gen = 0b10100110111;
|
|
437
|
+
for (let i = 14; i >= 10; i--) {
|
|
438
|
+
if (rem & (1 << i)) rem ^= gen << (i - 10);
|
|
439
|
+
}
|
|
440
|
+
return ((data << 10) | rem) ^ 0b101010000010010; // 0x5412
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function placeFormatInfo(matrix, ecc, mask) {
|
|
444
|
+
const bits = encodeFormatInfo(ecc, mask);
|
|
445
|
+
const size = matrix.length;
|
|
446
|
+
|
|
447
|
+
// ── Copy 1 around top-left finder (15 cells in an L) ──
|
|
448
|
+
// Bits 0-5 down col 8 at rows 0..5
|
|
449
|
+
for (let i = 0; i <= 5; i++) matrix[i][8] = (bits >> i) & 1;
|
|
450
|
+
// Bit 6 at row 7, col 8 (skip row 6 = horizontal timing)
|
|
451
|
+
matrix[7][8] = (bits >> 6) & 1;
|
|
452
|
+
// Bit 7 at corner (8, 8)
|
|
453
|
+
matrix[8][8] = (bits >> 7) & 1;
|
|
454
|
+
// Bit 8 at row 8, col 7 (skip col 6 = vertical timing)
|
|
455
|
+
matrix[8][7] = (bits >> 8) & 1;
|
|
456
|
+
// Bits 9-14 across row 8 at cols 5..0 (descending)
|
|
457
|
+
for (let i = 9; i <= 14; i++) matrix[8][14 - i] = (bits >> i) & 1;
|
|
458
|
+
|
|
459
|
+
// ── Copy 2 split across top-right and bottom-left ──
|
|
460
|
+
// Bits 0-7 across row 8, cols size-1..size-8 (descending)
|
|
461
|
+
for (let i = 0; i <= 7; i++) matrix[8][size - 1 - i] = (bits >> i) & 1;
|
|
462
|
+
// Bits 8-14 down col 8 at rows size-7..size-1
|
|
463
|
+
for (let i = 8; i <= 14; i++) matrix[size - 15 + i][8] = (bits >> i) & 1;
|
|
464
|
+
// Dark module at (size-8, 8) was set in placeFunctionPatterns
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ── Version info encoding (18 bits, BCH-18-6, only v7+) ──
|
|
468
|
+
function encodeVersionInfo(version) {
|
|
469
|
+
let rem = version << 12;
|
|
470
|
+
const gen = 0b1111100100101;
|
|
471
|
+
for (let i = 17; i >= 12; i--) {
|
|
472
|
+
if (rem & (1 << i)) rem ^= gen << (i - 12);
|
|
473
|
+
}
|
|
474
|
+
return (version << 12) | rem;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function placeVersionInfo(matrix, version, reserved) {
|
|
478
|
+
if (version < 7) return;
|
|
479
|
+
const bits = encodeVersionInfo(version);
|
|
480
|
+
const size = matrix.length;
|
|
481
|
+
for (let i = 0; i < 18; i++) {
|
|
482
|
+
const bit = (bits >> i) & 1;
|
|
483
|
+
const r = Math.floor(i / 3);
|
|
484
|
+
const c = (i % 3) + size - 11;
|
|
485
|
+
matrix[r][c] = bit;
|
|
486
|
+
matrix[c][r] = bit;
|
|
487
|
+
if (reserved) {
|
|
488
|
+
reserved[r][c] = 1;
|
|
489
|
+
reserved[c][r] = 1;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ── Mask penalty evaluation ──
|
|
495
|
+
function maskPenalty(matrix) {
|
|
496
|
+
const size = matrix.length;
|
|
497
|
+
let penalty = 0;
|
|
498
|
+
|
|
499
|
+
// Rule 1: 5+ same-color in a row or column → +3 +1 per extra
|
|
500
|
+
for (let r = 0; r < size; r++) {
|
|
501
|
+
let run = 1;
|
|
502
|
+
for (let c = 1; c < size; c++) {
|
|
503
|
+
if (matrix[r][c] === matrix[r][c - 1]) {
|
|
504
|
+
run++;
|
|
505
|
+
if (run === 5) penalty += 3;
|
|
506
|
+
else if (run > 5) penalty++;
|
|
507
|
+
} else {
|
|
508
|
+
run = 1;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
for (let c = 0; c < size; c++) {
|
|
513
|
+
let run = 1;
|
|
514
|
+
for (let r = 1; r < size; r++) {
|
|
515
|
+
if (matrix[r][c] === matrix[r - 1][c]) {
|
|
516
|
+
run++;
|
|
517
|
+
if (run === 5) penalty += 3;
|
|
518
|
+
else if (run > 5) penalty++;
|
|
519
|
+
} else {
|
|
520
|
+
run = 1;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Rule 2: 2×2 same-color blocks → +3 per
|
|
526
|
+
for (let r = 0; r < size - 1; r++) {
|
|
527
|
+
for (let c = 0; c < size - 1; c++) {
|
|
528
|
+
const v = matrix[r][c];
|
|
529
|
+
if (matrix[r][c + 1] === v && matrix[r + 1][c] === v && matrix[r + 1][c + 1] === v) {
|
|
530
|
+
penalty += 3;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Rule 3: finder-like pattern (1011101 + 4-light border) in row/col → +40 per
|
|
536
|
+
const pattern = [1, 0, 1, 1, 1, 0, 1];
|
|
537
|
+
for (let r = 0; r < size; r++) {
|
|
538
|
+
for (let c = 0; c <= size - 11; c++) {
|
|
539
|
+
// Check pattern starting at (r, c) with 4 light modules on one side
|
|
540
|
+
let match = true;
|
|
541
|
+
for (let i = 0; i < 7; i++) if (matrix[r][c + i] !== pattern[i]) { match = false; break; }
|
|
542
|
+
if (match) {
|
|
543
|
+
let lightAfter = c + 7 <= size - 4 && matrix[r][c + 7] === 0 && matrix[r][c + 8] === 0 && matrix[r][c + 9] === 0 && matrix[r][c + 10] === 0;
|
|
544
|
+
let lightBefore = c >= 4 && matrix[r][c - 1] === 0 && matrix[r][c - 2] === 0 && matrix[r][c - 3] === 0 && matrix[r][c - 4] === 0;
|
|
545
|
+
if (lightAfter || lightBefore) penalty += 40;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
for (let c = 0; c < size; c++) {
|
|
550
|
+
for (let r = 0; r <= size - 11; r++) {
|
|
551
|
+
let match = true;
|
|
552
|
+
for (let i = 0; i < 7; i++) if (matrix[r + i][c] !== pattern[i]) { match = false; break; }
|
|
553
|
+
if (match) {
|
|
554
|
+
let lightAfter = r + 7 <= size - 4 && matrix[r + 7][c] === 0 && matrix[r + 8][c] === 0 && matrix[r + 9][c] === 0 && matrix[r + 10][c] === 0;
|
|
555
|
+
let lightBefore = r >= 4 && matrix[r - 1][c] === 0 && matrix[r - 2][c] === 0 && matrix[r - 3][c] === 0 && matrix[r - 4][c] === 0;
|
|
556
|
+
if (lightAfter || lightBefore) penalty += 40;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Rule 4: dark/light balance — closer to 50% is better
|
|
562
|
+
let dark = 0;
|
|
563
|
+
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (matrix[r][c]) dark++;
|
|
564
|
+
const total = size * size;
|
|
565
|
+
const ratio = (dark * 100) / total;
|
|
566
|
+
const k = Math.floor(Math.abs(ratio - 50) / 5);
|
|
567
|
+
penalty += k * 10;
|
|
568
|
+
|
|
569
|
+
return penalty;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Internal-only exports for testability. Not part of the public API.
|
|
573
|
+
export const __internal = {
|
|
574
|
+
rsGenerator, rsEncode, gfMul, EXP, LOG,
|
|
575
|
+
buildDataBits, interleaveCodewords, utf8Encode, selectVersion,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// ── Main entry ──
|
|
579
|
+
export function encodeQR(text, options = {}) {
|
|
580
|
+
const ecc = options.errorCorrection || 'M';
|
|
581
|
+
if (!VERSION_CAPACITY[ecc]) throw new Error(`Unknown error-correction level: ${ecc}. Expected L/M/Q/H.`);
|
|
582
|
+
const bytes = utf8Encode(text);
|
|
583
|
+
const version = options.version || selectVersion(bytes.length, ecc);
|
|
584
|
+
if (version < 1 || version > 10) {
|
|
585
|
+
throw new Error(`QR encoder supports versions 1-10 only; data length ${bytes.length} bytes exceeds capacity at ECC=${ecc}.`);
|
|
586
|
+
}
|
|
587
|
+
const dataBytes = buildDataBits(bytes, version, ecc);
|
|
588
|
+
const codewords = interleaveCodewords(dataBytes, version, ecc);
|
|
589
|
+
|
|
590
|
+
// Build the matrix with each of 8 masks and pick the lowest-penalty
|
|
591
|
+
let best = null;
|
|
592
|
+
let bestPenalty = Infinity;
|
|
593
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
594
|
+
const { matrix, reserved, size } = createMatrix(version);
|
|
595
|
+
placeFunctionPatterns(matrix, reserved, version);
|
|
596
|
+
placeVersionInfo(matrix, version, reserved); // reserves cells + writes bits before data
|
|
597
|
+
placeData(matrix, reserved, codewords);
|
|
598
|
+
applyMask(matrix, reserved, mask);
|
|
599
|
+
placeFormatInfo(matrix, ecc, mask);
|
|
600
|
+
const penalty = maskPenalty(matrix);
|
|
601
|
+
if (penalty < bestPenalty) {
|
|
602
|
+
bestPenalty = penalty;
|
|
603
|
+
best = { matrix, size, version, mask, ecc };
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return best;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── SVG renderer ──
|
|
610
|
+
export function matrixToSVG(matrix, options = {}) {
|
|
611
|
+
const size = matrix.length;
|
|
612
|
+
const cellSize = options.cellSize || 8;
|
|
613
|
+
const margin = options.margin != null ? options.margin : 4;
|
|
614
|
+
const fg = options.color || '#000';
|
|
615
|
+
const bg = options.background || '#fff';
|
|
616
|
+
const total = (size + margin * 2) * cellSize;
|
|
617
|
+
|
|
618
|
+
const cells = [];
|
|
619
|
+
for (let r = 0; r < size; r++) {
|
|
620
|
+
for (let c = 0; c < size; c++) {
|
|
621
|
+
if (matrix[r][c]) {
|
|
622
|
+
const x = (c + margin) * cellSize;
|
|
623
|
+
const y = (r + margin) * cellSize;
|
|
624
|
+
cells.push(`<rect x="${x}" y="${y}" width="${cellSize}" height="${cellSize}"/>`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${total}" width="${total}" height="${total}" shape-rendering="crispEdges">
|
|
630
|
+
<rect width="100%" height="100%" fill="${bg}"/>
|
|
631
|
+
<g fill="${fg}">${cells.join('')}</g>
|
|
632
|
+
</svg>`;
|
|
633
|
+
}
|