qr 0.4.2 → 0.5.0

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
@@ -6,7 +6,7 @@ Minimal 0-dep QR code generator & reader.
6
6
  - 🏞️ Encoding (generating) supports ASCII, term, gif and svg codes
7
7
  - 📷 Decoding (reading) supports camera feed input, files and non-browser environments
8
8
  - 🔍 Extensive tests ensure correctness: 100MB+ of vectors
9
- - 🪶 35KB for encoding + decoding, 18KB for encoding (1000 lines of code)
9
+ - 🪶 14KB (gzipped) for encoding + decoding, 7KB for encoding
10
10
 
11
11
  Check out:
12
12
 
@@ -43,7 +43,7 @@ A standalone file [qr.js](https://github.com/paulmillr/qr/releases) is also avai
43
43
  ```ts
44
44
  import encodeQR from 'qr';
45
45
 
46
- // import decodeQR from 'qr/decode';
46
+ // import decodeQR from 'qr/decode.js';
47
47
  // See separate README section for decoding.
48
48
 
49
49
  const txt = 'Hello world';
@@ -100,7 +100,7 @@ Decoding raw bitmap is still possible.
100
100
 
101
101
  ```js
102
102
  import encodeQR from 'qr';
103
- import decodeQR from 'qr/decode';
103
+ import decodeQR from 'qr/decode.js';
104
104
  import { Bitmap } from 'qr';
105
105
 
106
106
  // Scale so it would be 100x100 instead of 25x25
@@ -136,7 +136,7 @@ function decodeWithExternal() {
136
136
  }
137
137
 
138
138
  // c) draw gif/svg to browser DOM canvas
139
- import { svgToPng } from 'qr/dom';
139
+ import { svgToPng } from 'qr/dom.js';
140
140
  const png = svgToPng(encodeQR('Hello world', 'svg'), 512, 512);
141
141
  ```
142
142
 
@@ -196,7 +196,7 @@ Check out `dom.ts` for browser-related camera code that would make your apps sim
196
196
  ## Using with Kotlin
197
197
 
198
198
  ```kotlin
199
- @JsModule("@paulmillr/qr")
199
+ @JsModule("qr")
200
200
  @JsNonModule
201
201
  external object Qr {
202
202
  @JsName("default")
@@ -238,23 +238,22 @@ Benchmarks measured with Apple M2 on MacOS 13 with node.js 19.
238
238
 
239
239
  ```
240
240
  ======== encode/ascii ========
241
- encode/paulmillr-qr x 1,794 ops/sec @ 557μs/op
242
- encode/qrcode-generator x 3,128 ops/sec @ 319μs/op ± 1.12% (min: 293μs, max: 3ms)
243
- encode/nuintun x 1,872 ops/sec @ 533μs/op
241
+ encode/paulmillr x 2,995 ops/sec @ 333μs/op
242
+ encode/qrcode-generator x 6,029 ops/sec @ 165μs/op ± 1.39% (min: 142μs, max: 2ms)
243
+ encode/nuintun x 3,647 ops/sec @ 274μs/op
244
244
  ======== encode/gif ========
245
- encode/paulmillr-qr x 1,771 ops/sec @ 564μs/op
246
- encode/qrcode-generator x 1,773 ops/sec @ 563μs/op
247
- encode/nuintun x 1,883 ops/sec @ 530μs/op
245
+ encode/paulmillr x 2,967 ops/sec @ 337μs/op
246
+ encode/qrcode-generator x 3,486 ops/sec @ 286μs/op
247
+ encode/nuintun x 3,643 ops/sec @ 274μs/op
248
248
  ======== encode: big ========
249
- encode/paulmillr-qr x 87 ops/sec @ 11ms/op
250
- encode/qrcode-generator x 124 ops/sec @ 8ms/op
251
- encode/nuintun x 143 ops/sec @ 6ms/op
249
+ encode/paulmillr x 156 ops/sec @ 6ms/op
250
+ encode/qrcode-generator x 200 ops/sec @ 4ms/op
251
+ encode/nuintun x 223 ops/sec @ 4ms/op
252
252
  ======== decode ========
253
- decode/paulmillr-qr x 96 ops/sec @ 10ms/op ± 1.39% (min: 9ms, max: 32ms)
254
- decode/jsqr x 34 ops/sec @ 28ms/op
255
- decode/nuintun x 35 ops/sec @ 28ms/op
256
- decode/instascan x 79 ops/sec @ 12ms/op ± 6.73% (min: 9ms, max: 223ms)
257
- ======== Decoding quality ========
253
+ decode/paulmillr x 154 ops/sec @ 6ms/op ± 1.29% (min: 6ms, max: 18ms)
254
+ decode/jsqr x 52 ops/sec @ 18ms/op
255
+ decode/nuintun x 51 ops/sec @ 19ms/op
256
+ decode/instascan x 158 ops/sec @ 6ms/op ± 9.06% (min: 3ms, max: 144ms)======== Decoding quality ========
258
257
  blurred(45): paulmillr-qr=12 (26.66%) jsqr=13 (28.88%) nuintun=13 (28.88%) instascan=11 (24.44%)
259
258
  ```
260
259
 
package/decode.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /*!
3
2
  Copyright (c) 2023 Paul Miller (paulmillr.com)
4
3
  The library paulmillr-qr is dual-licensed under the Apache 2.0 OR MIT license.
@@ -23,11 +22,8 @@ limitations under the License.
23
22
 
24
23
  ```
25
24
  */
26
- Object.defineProperty(exports, "__esModule", { value: true });
27
- exports._tests = void 0;
28
- exports.decodeQR = decodeQR;
29
- const index_ts_1 = require("./index.js");
30
- const { best, bin, drawTemplate, fillArr, info, interleave, validateVersion, zigzag } = index_ts_1.utils;
25
+ import { Bitmap, utils } from "./index.js";
26
+ const { best, bin, drawTemplate, fillArr, info, interleave, validateVersion, zigzag } = utils;
31
27
  // Constants
32
28
  const MAX_BITS_ERROR = 3; // Up to 3 bit errors in version/format
33
29
  const GRAYSCALE_BLOCK_SIZE = 8;
@@ -115,7 +111,7 @@ function toBitmap(img) {
115
111
  blocks[bWidth * y + x] = int(average);
116
112
  }
117
113
  }
118
- const matrix = new index_ts_1.Bitmap({ width: img.width, height: img.height });
114
+ const matrix = new Bitmap({ width: img.width, height: img.height });
119
115
  for (let y = 0; y < bHeight; y++) {
120
116
  const yPos = cap(y * block, 0, maxY);
121
117
  const top = cap(y, 2, bHeight - 3);
@@ -660,7 +656,7 @@ function transform(b, size, from, to) {
660
656
  ];
661
657
  const sToQ = squareToQuadrilateral(from);
662
658
  const transform = sToQ.map((i) => i.map((_, qx) => i.reduce((acc, v, j) => acc + v * qToS[j][qx], 0)));
663
- const res = new index_ts_1.Bitmap(size);
659
+ const res = new Bitmap(size);
664
660
  const points = fillArr(2 * size, 0);
665
661
  const pointsLength = points.length;
666
662
  for (let y = 0; y < size; y++) {
@@ -886,7 +882,7 @@ function cropToSquare(img) {
886
882
  }
887
883
  return { offset, img: { height: squareSize, width: squareSize, data: croppedData } };
888
884
  }
889
- function decodeQR(img, opts = {}) {
885
+ export function decodeQR(img, opts = {}) {
890
886
  for (const field of ['height', 'width']) {
891
887
  if (!Number.isSafeInteger(img[field]) || img[field] <= 0)
892
888
  throw new Error(`invalid img.${field}=${img[field]} (${typeof img[field]})`);
@@ -919,9 +915,9 @@ function decodeQR(img, opts = {}) {
919
915
  opts.imageOnResult(bits.toImage());
920
916
  return res;
921
917
  }
922
- exports.default = decodeQR;
918
+ export default decodeQR;
923
919
  // Unsafe API utils, exported only for tests
924
- exports._tests = {
920
+ export const _tests = {
925
921
  toBitmap,
926
922
  decodeBitmap,
927
923
  findFinder,
package/dom.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /*!
3
2
  Copyright (c) 2023 Paul Miller (paulmillr.com)
4
3
  The library paulmillr-qr is dual-licensed under the Apache 2.0 OR MIT license.
@@ -23,19 +22,13 @@ limitations under the License.
23
22
  * The code is fragile: it is easy to make subtle errors, which will break decoding.
24
23
  * @module
25
24
  */
26
- Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.QRCanvas = exports.getSize = void 0;
28
- exports.frontalCamera = frontalCamera;
29
- exports.frameLoop = frameLoop;
30
- exports.svgToPng = svgToPng;
31
- const decode_ts_1 = require("./decode.js");
32
- const getSize = (elm) => {
25
+ import decodeQR, {} from "./decode.js";
26
+ export const getSize = (elm) => {
33
27
  const css = getComputedStyle(elm);
34
28
  const width = Math.floor(+css.width.split('px')[0]);
35
29
  const height = Math.floor(+css.height.split('px')[0]);
36
30
  return { width, height };
37
31
  };
38
- exports.getSize = getSize;
39
32
  const setCanvasSize = (canvas, height, width) => {
40
33
  // NOTE: setting canvas.width even to same size will clear & redraw it (flickering)
41
34
  if (canvas.height !== height)
@@ -55,9 +48,14 @@ const clearCanvas = ({ canvas, context }) => {
55
48
  /**
56
49
  * Handles canvases for QR code decoding
57
50
  */
58
- class QRCanvas {
51
+ export class QRCanvas {
52
+ opts;
53
+ lastDetect = 0;
54
+ main;
55
+ overlay;
56
+ bitmap;
57
+ resultQR;
59
58
  constructor({ overlay, bitmap, resultQR } = {}, opts = {}) {
60
- this.lastDetect = 0;
61
59
  this.opts = {
62
60
  resultBlockSize: 8,
63
61
  overlayMainColor: 'green',
@@ -171,7 +169,7 @@ class QRCanvas {
171
169
  if (this.resultQR)
172
170
  options.imageOnResult = (img) => this.drawResultQr(img);
173
171
  try {
174
- const res = (0, decode_ts_1.default)(data, options);
172
+ const res = decodeQR(data, options);
175
173
  this.lastDetect = Date.now();
176
174
  return res;
177
175
  }
@@ -191,8 +189,9 @@ class QRCanvas {
191
189
  clearCanvas(this.resultQR);
192
190
  }
193
191
  }
194
- exports.QRCanvas = QRCanvas;
195
192
  class QRCamera {
193
+ stream;
194
+ player;
196
195
  constructor(stream, player) {
197
196
  this.stream = stream;
198
197
  this.player = player;
@@ -236,7 +235,7 @@ class QRCamera {
236
235
  const { player } = this;
237
236
  if (fullSize)
238
237
  return canvas.drawImage(player, player.videoHeight, player.videoWidth);
239
- const size = (0, exports.getSize)(player);
238
+ const size = getSize(player);
240
239
  return canvas.drawImage(player, size.height, size.width);
241
240
  }
242
241
  stop() {
@@ -254,7 +253,7 @@ class QRCamera {
254
253
  * await camera.setDevice(devices[0].deviceId); // Change camera
255
254
  * const res = camera.readFrame(canvas);
256
255
  */
257
- async function frontalCamera(player) {
256
+ export async function frontalCamera(player) {
258
257
  const stream = await navigator.mediaDevices.getUserMedia({
259
258
  video: {
260
259
  // Ask for screen resolution
@@ -274,7 +273,7 @@ async function frontalCamera(player) {
274
273
  * const cancel = frameLoop((ns) => console.log(ns));
275
274
  * cancel();
276
275
  */
277
- function frameLoop(cb) {
276
+ export function frameLoop(cb) {
278
277
  let handle = undefined;
279
278
  function loop(ts) {
280
279
  cb(ts);
@@ -288,7 +287,7 @@ function frameLoop(cb) {
288
287
  handle = undefined;
289
288
  };
290
289
  }
291
- function svgToPng(svgData, width, height) {
290
+ export function svgToPng(svgData, width, height) {
292
291
  return new Promise((resolve, reject) => {
293
292
  if (!(Number.isSafeInteger(width) &&
294
293
  Number.isSafeInteger(height) &&
package/index.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /*!
3
2
  Copyright (c) 2023 Paul Miller (paulmillr.com)
4
3
  The library paulmillr-qr is dual-licensed under the Apache 2.0 OR MIT license.
@@ -15,24 +14,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
14
  See the License for the specific language governing permissions and
16
15
  limitations under the License.
17
16
  */
18
- Object.defineProperty(exports, "__esModule", { value: true });
19
- exports._tests = exports.utils = exports.Encoding = exports.ECMode = exports.Bitmap = void 0;
20
- exports.utf8ToBytes = utf8ToBytes;
21
- exports.encodeQR = encodeQR;
22
17
  /**
23
18
  * Methods for encoding (generating) QR code patterns.
24
19
  * Check out decode.ts for decoding (reading).
25
20
  * @module
26
21
  * @example
27
22
  ```js
28
- import encodeQR from '@paulmillr/qr';
23
+ import encodeQR from 'qr';
29
24
  const txt = 'Hello world';
30
25
  const ascii = encodeQR(txt, 'ascii'); // Not all fonts are supported
31
26
  const terminalFriendly = encodeQR(txt, 'term'); // 2x larger, all fonts are OK
32
27
  const gifBytes = encodeQR(txt, 'gif'); // Uncompressed GIF
33
28
  const svgElement = encodeQR(txt, 'svg'); // SVG vector image element
34
29
  const array = encodeQR(txt, 'raw'); // 2d array for canvas or other libs
35
- // import decodeQR from '@paulmillr/qr/decode';
30
+ // import decodeQR from 'qr/decode.js';
36
31
  ```
37
32
  */
38
33
  // We do not use newline escape code directly in strings because it's not parser-friendly
@@ -125,7 +120,7 @@ function alphabet(alphabet) {
125
120
  },
126
121
  };
127
122
  }
128
- class Bitmap {
123
+ export class Bitmap {
129
124
  static size(size, limit) {
130
125
  if (typeof size === 'number')
131
126
  size = { height: size, width: size };
@@ -168,6 +163,9 @@ class Bitmap {
168
163
  width = 0;
169
164
  return new Bitmap({ height, width }, data);
170
165
  }
166
+ data;
167
+ height;
168
+ width;
171
169
  constructor(size, data) {
172
170
  const { height, width } = Bitmap.size(size);
173
171
  this.data = data || Array.from({ length: height }, () => fillArr(width, undefined));
@@ -385,13 +383,12 @@ class Bitmap {
385
383
  return { height, width, data };
386
384
  }
387
385
  }
388
- exports.Bitmap = Bitmap;
389
386
  // End of utils
390
387
  // Runtime type-checking
391
388
  /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30% */
392
- exports.ECMode = ['low', 'medium', 'quartile', 'high'];
389
+ export const ECMode = ['low', 'medium', 'quartile', 'high'];
393
390
  /** QR Code encoding */
394
- exports.Encoding = ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'];
391
+ export const Encoding = ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'];
395
392
  // Various constants & tables
396
393
  // prettier-ignore
397
394
  const BYTES = [
@@ -886,7 +883,7 @@ function detectType(str) {
886
883
  /**
887
884
  * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
888
885
  */
889
- function utf8ToBytes(str) {
886
+ export function utf8ToBytes(str) {
890
887
  if (typeof str !== 'string')
891
888
  throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
892
889
  return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
@@ -1036,12 +1033,12 @@ function drawQRBest(ver, ecc, data, maskIdx) {
1036
1033
  return drawQR(ver, ecc, data, maskIdx);
1037
1034
  }
1038
1035
  function validateECC(ec) {
1039
- if (!exports.ECMode.includes(ec))
1040
- throw new Error(`Invalid error correction mode=${ec}. Expected: ${exports.ECMode}`);
1036
+ if (!ECMode.includes(ec))
1037
+ throw new Error(`Invalid error correction mode=${ec}. Expected: ${ECMode}`);
1041
1038
  }
1042
1039
  function validateEncoding(enc) {
1043
- if (!exports.Encoding.includes(enc))
1044
- throw new Error(`Encoding: invalid mode=${enc}. Expected: ${exports.Encoding}`);
1040
+ if (!Encoding.includes(enc))
1041
+ throw new Error(`Encoding: invalid mode=${enc}. Expected: ${Encoding}`);
1045
1042
  if (enc === 'kanji' || enc === 'eci')
1046
1043
  throw new Error(`Encoding: ${enc} is not supported (yet?).`);
1047
1044
  }
@@ -1049,7 +1046,7 @@ function validateMask(mask) {
1049
1046
  if (![0, 1, 2, 3, 4, 5, 6, 7].includes(mask) || !PATTERNS[mask])
1050
1047
  throw new Error(`Invalid mask=${mask}. Expected number [0..7]`);
1051
1048
  }
1052
- function encodeQR(text, output = 'raw', opts = {}) {
1049
+ export function encodeQR(text, output = 'raw', opts = {}) {
1053
1050
  const ecc = opts.ecc !== undefined ? opts.ecc : 'medium';
1054
1051
  validateECC(ecc);
1055
1052
  const encoding = opts.encoding !== undefined ? opts.encoding : detectType(text);
@@ -1099,8 +1096,8 @@ function encodeQR(text, output = 'raw', opts = {}) {
1099
1096
  else
1100
1097
  throw new Error(`Unknown output: ${output}`);
1101
1098
  }
1102
- exports.default = encodeQR;
1103
- exports.utils = {
1099
+ export default encodeQR;
1100
+ export const utils = {
1104
1101
  best,
1105
1102
  bin,
1106
1103
  drawTemplate,
@@ -1111,7 +1108,7 @@ exports.utils = {
1111
1108
  zigzag,
1112
1109
  };
1113
1110
  // Unsafe API utils, exported only for tests
1114
- exports._tests = {
1111
+ export const _tests = {
1115
1112
  Bitmap,
1116
1113
  info,
1117
1114
  detectType,
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "qr",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Minimal 0-dep QR code generator & reader. Supports ascii, term, gif and svg formats",
5
5
  "files": [
6
- "esm",
7
6
  "index.js",
8
7
  "index.d.ts",
9
8
  "index.d.ts.map",
@@ -16,17 +15,16 @@
16
15
  "dom.d.ts",
17
16
  "dom.d.ts.map",
18
17
  "dom.ts",
18
+ "src",
19
19
  "LICENSE",
20
20
  "LICENSE-MIT"
21
21
  ],
22
- "main": "index.js",
23
- "module": "index.js",
24
- "types": "index.d.ts",
25
22
  "sideEffects": false,
26
23
  "devDependencies": {
27
- "@paulmillr/jsbt": "0.3.3",
24
+ "@paulmillr/jsbt": "0.4.1",
25
+ "@types/node": "22.15.23",
28
26
  "micro-bmark": "0.4.1",
29
- "micro-should": "0.5.2",
27
+ "micro-should": "0.5.3",
30
28
  "prettier": "3.5.3",
31
29
  "typescript": "5.8.3"
32
30
  },
@@ -37,28 +35,23 @@
37
35
  "type": "git",
38
36
  "url": "git+https://github.com/paulmillr/qr.git"
39
37
  },
38
+ "engines": {
39
+ "node": ">= 20.19.0"
40
+ },
41
+ "type": "module",
42
+ "main": "index.js",
43
+ "module": "index.js",
44
+ "types": "index.d.ts",
40
45
  "scripts": {
41
- "build": "tsc && tsc -p tsconfig.cjs.json",
46
+ "build": "tsc",
42
47
  "build:release": "npx jsbt esbuild test/build",
48
+ "bench": "cd test/benchmark && npm ci && node index.ts",
43
49
  "lint": "prettier --check src",
44
50
  "format": "prettier --write src",
45
- "test": "cd test; npm ci; node index.js",
46
- "test:bun": "cd test; bun install; bun index.js",
47
- "test:deno": "cd test; npm install; deno --allow-env --allow-read index.js"
48
- },
49
- "exports": {
50
- ".": {
51
- "import": "./esm/index.js",
52
- "require": "./index.js"
53
- },
54
- "./decode.js": {
55
- "import": "./esm/decode.js",
56
- "require": "./decode.js"
57
- },
58
- "./dom.js": {
59
- "import": "./esm/dom.js",
60
- "require": "./dom.js"
61
- }
51
+ "test": "cd test; npm ci; node --experimental-strip-types --no-warnings index.ts",
52
+ "test:bun": "cd test; bun install; bun index.ts",
53
+ "test:deno": "cd test; npm install; deno --allow-env --allow-read index.ts",
54
+ "test:node20": "cd test; npx tsc; cd compiled/test; npm install; node index.js"
62
55
  },
63
56
  "keywords": [
64
57
  "qr",