quadqr-js 1.2.0 → 1.4.2
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/FORMAT.md +37 -9
- package/README.md +98 -48
- package/SPECIFICATION.md +30 -10
- package/bin/quadqr.js +6 -2
- package/dist/esm/benchmark.js +71 -15
- package/dist/esm/brotli.js +2210 -0
- package/dist/esm/deflate.js +381 -0
- package/dist/esm/quadqr.js +1927 -343
- package/dist/esm/security.js +19 -0
- package/dist/esm/vision.js +660 -44
- package/dist/quadqr.js +5292 -489
- package/dist/quadqr.min.js +4987 -468
- package/docs/API.md +49 -12
- package/docs/BROWSER_CDN.md +9 -5
- package/docs/CLI.md +19 -2
- package/docs/COMPRESSION.md +170 -0
- package/docs/GETTING_STARTED.md +12 -2
- package/docs/HIGH_DENSITY_MODE.md +100 -0
- package/docs/NODE.md +22 -0
- package/docs/README.md +8 -5
- package/docs/RELIABILITY_LAB.md +64 -0
- package/docs/TRIANGLE16.md +98 -0
- package/docs/WASM.md +1 -1
- package/package.json +11 -10
- package/types/benchmark.d.ts +0 -8
- package/types/index.d.ts +0 -375
- package/types/node.d.ts +0 -11
package/dist/esm/benchmark.js
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
decodeMatrix,
|
|
13
13
|
getVersionInfo,
|
|
14
14
|
compressPayload,
|
|
15
|
+
compressDeflatePayload,
|
|
16
|
+
compressBrotliPayload,
|
|
15
17
|
MAX_VERSION
|
|
16
18
|
} from "./quadqr.js";
|
|
17
19
|
|
|
@@ -30,6 +32,16 @@ function normalizeEcc(ecc = "M") {
|
|
|
30
32
|
return value;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
function resolveHighDensity(options = {}) {
|
|
36
|
+
if (typeof options.highDensity === "boolean") return options.highDensity;
|
|
37
|
+
// Backward compatibility for the first experimental Triangle16 branch.
|
|
38
|
+
return options.cellEncoding === "triangle16";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function encodingForHighDensity(highDensity) {
|
|
42
|
+
return highDensity ? "triangle16" : "rgbw";
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
function nowMs() {
|
|
34
46
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
35
47
|
return performance.now();
|
|
@@ -76,9 +88,11 @@ export function getStandardQrByteCapacity(version, ecc = "M") {
|
|
|
76
88
|
return STANDARD_QR_BYTE_CAPACITY[normalizeEcc(ecc)][version - 1];
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
export function compareCapacity(version, ecc = "M") {
|
|
91
|
+
export function compareCapacity(version, ecc = "M", options = {}) {
|
|
80
92
|
const level = normalizeEcc(ecc);
|
|
81
|
-
const
|
|
93
|
+
const highDensity = resolveHighDensity(options);
|
|
94
|
+
const cellEncoding = encodingForHighDensity(highDensity);
|
|
95
|
+
const quadqr = getVersionInfo(version, { ecc: level, highDensity });
|
|
82
96
|
const standardQrBytes = getStandardQrByteCapacity(version, level);
|
|
83
97
|
const quadqrBytes = quadqr.capacityBytes;
|
|
84
98
|
const differenceBytes = quadqrBytes - standardQrBytes;
|
|
@@ -94,6 +108,8 @@ export function compareCapacity(version, ecc = "M") {
|
|
|
94
108
|
differenceBytes,
|
|
95
109
|
ratio,
|
|
96
110
|
gainPercent,
|
|
111
|
+
highDensity,
|
|
112
|
+
cellEncoding: quadqr.cellEncoding,
|
|
97
113
|
quadqrBitsPerDataCell: quadqr.bitsPerDataCell,
|
|
98
114
|
quadqrPayloadEfficiencyPercent: quadqr.theoreticalBits > 0
|
|
99
115
|
? (quadqrBytes * 8 / quadqr.theoreticalBits) * 100
|
|
@@ -108,42 +124,72 @@ export function compareCapacity(version, ecc = "M") {
|
|
|
108
124
|
/** Calculate the smallest QuadQR and standard QR versions for a payload size. */
|
|
109
125
|
export function calculateCapacityPlan(options = {}) {
|
|
110
126
|
const ecc = normalizeEcc(options.ecc ?? "M");
|
|
127
|
+
const highDensity = resolveHighDensity(options);
|
|
128
|
+
const cellEncoding = encodingForHighDensity(highDensity);
|
|
111
129
|
let sourceBytes;
|
|
112
130
|
const hasConcretePayload = options.payload instanceof Uint8Array || typeof options.payload === "string";
|
|
113
131
|
if (options.payload instanceof Uint8Array) sourceBytes = options.payload;
|
|
114
132
|
else if (typeof options.payload === "string") sourceBytes = new TextEncoder().encode(options.payload);
|
|
115
133
|
else sourceBytes = new Uint8Array(Math.max(0, Math.floor(options.payloadBytes ?? 0)));
|
|
116
134
|
|
|
117
|
-
const requestedCompression = options.compression ?? "none";
|
|
135
|
+
const requestedCompression = String(options.compression ?? "none").toLowerCase();
|
|
136
|
+
if (!["none", "auto", "smart", "lz", "deflate", "brotli"].includes(requestedCompression)) {
|
|
137
|
+
throw new Error("compression must be none, auto, smart, lz, deflate, or brotli.");
|
|
138
|
+
}
|
|
118
139
|
const signed = Boolean(options.signed);
|
|
119
140
|
const keyIdBytes = options.keyId ? new TextEncoder().encode(String(options.keyId)).length : 0;
|
|
120
141
|
const envelopeHeaderBytes = 16;
|
|
121
142
|
const signingBytes = signed ? 64 + keyIdBytes + (options.embedPublicKey ? 32 : 0) : 0;
|
|
122
143
|
let compression = requestedCompression;
|
|
144
|
+
let compressionLevel = null;
|
|
123
145
|
let storedBytes = sourceBytes.length;
|
|
124
146
|
let compressed = false;
|
|
125
147
|
|
|
126
148
|
if (requestedCompression !== "none" && hasConcretePayload) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
const candidates = [];
|
|
150
|
+
const explicitLevel = options.compressionLevel;
|
|
151
|
+
if (requestedCompression === "auto" || requestedCompression === "smart" || requestedCompression === "lz") {
|
|
152
|
+
const level = requestedCompression === "lz" ? (explicitLevel ?? options.lzLevel ?? 6) : 6;
|
|
153
|
+
candidates.push({ compression: "lz", bytes: compressPayload(sourceBytes, { level }).length, level });
|
|
154
|
+
}
|
|
155
|
+
if (requestedCompression === "auto") {
|
|
156
|
+
candidates.push({ compression: "deflate", bytes: compressDeflatePayload(sourceBytes, { level: 6 }).length, level: 6 });
|
|
157
|
+
candidates.push({ compression: "brotli", bytes: compressBrotliPayload(sourceBytes, { quality: 6 }).length, level: 6 });
|
|
158
|
+
} else if (requestedCompression === "smart") {
|
|
159
|
+
for (const level of [6, 8, 9]) candidates.push({ compression: "deflate", bytes: compressDeflatePayload(sourceBytes, { level }).length, level });
|
|
160
|
+
for (const level of [6, 9, 11]) candidates.push({ compression: "brotli", bytes: compressBrotliPayload(sourceBytes, { quality: level }).length, level });
|
|
161
|
+
} else if (requestedCompression === "deflate") {
|
|
162
|
+
const level = explicitLevel ?? options.deflateLevel ?? 6;
|
|
163
|
+
candidates.push({ compression: "deflate", bytes: compressDeflatePayload(sourceBytes, { level }).length, level });
|
|
164
|
+
} else if (requestedCompression === "brotli") {
|
|
165
|
+
const level = explicitLevel ?? options.brotliQuality ?? 11;
|
|
166
|
+
candidates.push({ compression: "brotli", bytes: compressBrotliPayload(sourceBytes, { quality: level }).length, level });
|
|
167
|
+
}
|
|
168
|
+
if (!candidates.length) throw new Error("compression must be none, auto, smart, lz, deflate, or brotli.");
|
|
169
|
+
candidates.sort((a, b) => a.bytes - b.bytes);
|
|
170
|
+
const best = candidates[0];
|
|
171
|
+
const compressionOverhead = signed ? 0 : envelopeHeaderBytes;
|
|
172
|
+
if (!["auto", "smart"].includes(requestedCompression) || best.bytes + compressionOverhead < sourceBytes.length) {
|
|
173
|
+
storedBytes = best.bytes;
|
|
130
174
|
compressed = true;
|
|
131
|
-
compression =
|
|
175
|
+
compression = best.compression;
|
|
176
|
+
compressionLevel = best.level ?? null;
|
|
132
177
|
} else {
|
|
133
178
|
compression = "none";
|
|
179
|
+
compressionLevel = null;
|
|
134
180
|
}
|
|
135
181
|
} else if (requestedCompression !== "none" && !hasConcretePayload) {
|
|
136
182
|
compression = "unknown";
|
|
137
183
|
}
|
|
138
184
|
|
|
139
|
-
const needsEnvelope = signed || compressed || requestedCompression === "lz";
|
|
185
|
+
const needsEnvelope = signed || compressed || requestedCompression === "lz" || requestedCompression === "deflate" || requestedCompression === "brotli";
|
|
140
186
|
const encodedBytes = storedBytes + (needsEnvelope ? envelopeHeaderBytes + signingBytes : 0);
|
|
141
187
|
const extensionOverheadBytes = encodedBytes - storedBytes;
|
|
142
188
|
|
|
143
189
|
let quadqrVersion = null;
|
|
144
190
|
let quadqrInfo = null;
|
|
145
191
|
for (let version = 1; version <= MAX_VERSION; version++) {
|
|
146
|
-
const info = getVersionInfo(version, { ecc });
|
|
192
|
+
const info = getVersionInfo(version, { ecc, highDensity });
|
|
147
193
|
if (encodedBytes <= info.capacityBytes) {
|
|
148
194
|
quadqrVersion = version;
|
|
149
195
|
quadqrInfo = info;
|
|
@@ -162,11 +208,14 @@ export function calculateCapacityPlan(options = {}) {
|
|
|
162
208
|
|
|
163
209
|
return {
|
|
164
210
|
ecc,
|
|
211
|
+
highDensity,
|
|
212
|
+
cellEncoding,
|
|
165
213
|
sourceBytes: sourceBytes.length,
|
|
166
214
|
encodedBytes,
|
|
167
215
|
storedBytes,
|
|
168
216
|
extensionOverheadBytes,
|
|
169
217
|
compression,
|
|
218
|
+
compressionLevel,
|
|
170
219
|
compressed,
|
|
171
220
|
signed,
|
|
172
221
|
quadqrVersion,
|
|
@@ -185,12 +234,15 @@ export function calculateCapacityPlan(options = {}) {
|
|
|
185
234
|
|
|
186
235
|
export function buildCapacityComparison(options = {}) {
|
|
187
236
|
const ecc = normalizeEcc(options.ecc ?? "M");
|
|
237
|
+
const highDensity = resolveHighDensity(options);
|
|
188
238
|
const versions = options.versions ?? Array.from({ length: MAX_VERSION }, (_, i) => i + 1);
|
|
189
|
-
return versions.map((version) => compareCapacity(version, ecc));
|
|
239
|
+
return versions.map((version) => compareCapacity(version, ecc, { highDensity }));
|
|
190
240
|
}
|
|
191
241
|
|
|
192
242
|
export function benchmarkCodec(options = {}) {
|
|
193
243
|
const ecc = normalizeEcc(options.ecc ?? "M");
|
|
244
|
+
const highDensity = resolveHighDensity(options);
|
|
245
|
+
const cellEncoding = encodingForHighDensity(highDensity);
|
|
194
246
|
const iterations = Math.max(1, Math.floor(options.iterations ?? 30));
|
|
195
247
|
const warmup = Math.max(0, Math.floor(options.warmup ?? Math.min(5, iterations)));
|
|
196
248
|
const requestedSizes = options.payloadSizes ?? [24, 32, 128, 512, 1024, 2048];
|
|
@@ -202,14 +254,14 @@ export function benchmarkCodec(options = {}) {
|
|
|
202
254
|
|
|
203
255
|
let probe;
|
|
204
256
|
try {
|
|
205
|
-
probe = encodeBytes(payload, { ecc });
|
|
257
|
+
probe = encodeBytes(payload, { ecc, highDensity });
|
|
206
258
|
} catch (error) {
|
|
207
259
|
results.push({ payloadBytes, skipped: true, reason: error.message });
|
|
208
260
|
continue;
|
|
209
261
|
}
|
|
210
262
|
|
|
211
263
|
for (let i = 0; i < warmup; i++) {
|
|
212
|
-
const encoded = encodeBytes(payload, { ecc, version: probe.version });
|
|
264
|
+
const encoded = encodeBytes(payload, { ecc, highDensity, version: probe.version });
|
|
213
265
|
decodeMatrix(encoded.matrix);
|
|
214
266
|
}
|
|
215
267
|
|
|
@@ -219,7 +271,7 @@ export function benchmarkCodec(options = {}) {
|
|
|
219
271
|
|
|
220
272
|
for (let i = 0; i < iterations; i++) {
|
|
221
273
|
let start = nowMs();
|
|
222
|
-
encoded = encodeBytes(payload, { ecc, version: probe.version });
|
|
274
|
+
encoded = encodeBytes(payload, { ecc, highDensity, version: probe.version });
|
|
223
275
|
encodeSamples.push(nowMs() - start);
|
|
224
276
|
|
|
225
277
|
start = nowMs();
|
|
@@ -231,7 +283,7 @@ export function benchmarkCodec(options = {}) {
|
|
|
231
283
|
}
|
|
232
284
|
}
|
|
233
285
|
|
|
234
|
-
const versionInfo = getVersionInfo(encoded.version, { ecc });
|
|
286
|
+
const versionInfo = getVersionInfo(encoded.version, { ecc, highDensity });
|
|
235
287
|
results.push({
|
|
236
288
|
payloadBytes,
|
|
237
289
|
skipped: false,
|
|
@@ -250,6 +302,8 @@ export function benchmarkCodec(options = {}) {
|
|
|
250
302
|
return {
|
|
251
303
|
format: "QuadQR",
|
|
252
304
|
ecc,
|
|
305
|
+
highDensity,
|
|
306
|
+
cellEncoding,
|
|
253
307
|
iterations,
|
|
254
308
|
warmup,
|
|
255
309
|
generatedAt: new Date().toISOString(),
|
|
@@ -259,11 +313,13 @@ export function benchmarkCodec(options = {}) {
|
|
|
259
313
|
|
|
260
314
|
export function benchmarkReport(options = {}) {
|
|
261
315
|
const ecc = normalizeEcc(options.ecc ?? "M");
|
|
316
|
+
const highDensity = resolveHighDensity(options);
|
|
262
317
|
const versions = options.versions ?? [1, 2, 5, 10, 20, 30, 40];
|
|
263
318
|
return {
|
|
264
|
-
capacity: buildCapacityComparison({ ecc, versions }),
|
|
319
|
+
capacity: buildCapacityComparison({ ecc, versions, highDensity }),
|
|
265
320
|
performance: benchmarkCodec({
|
|
266
321
|
ecc,
|
|
322
|
+
highDensity,
|
|
267
323
|
iterations: options.iterations ?? 30,
|
|
268
324
|
warmup: options.warmup,
|
|
269
325
|
payloadSizes: options.payloadSizes
|