qreator 9.7.2 → 9.9.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.
Files changed (49) hide show
  1. package/README.md +59 -1
  2. package/lib/browser/pdf.umd.js +297 -68
  3. package/lib/browser/pdf.umd.js.map +1 -1
  4. package/lib/browser/png.d.ts +3 -1
  5. package/lib/browser/png.umd.js +257 -43
  6. package/lib/browser/png.umd.js.map +1 -1
  7. package/lib/browser/png_browser.d.ts +3 -1
  8. package/lib/browser/react.umd.js +291 -86
  9. package/lib/browser/react.umd.js.map +1 -1
  10. package/lib/browser/svg.d.ts +3 -1
  11. package/lib/browser/svg.umd.js +251 -46
  12. package/lib/browser/svg.umd.js.map +1 -1
  13. package/lib/browser/typing/types.d.ts +13 -0
  14. package/lib/browser/utils.d.ts +32 -2
  15. package/lib/errorcode.js +12 -16
  16. package/lib/errorcode.js.map +1 -1
  17. package/lib/matrix.js +11 -3
  18. package/lib/matrix.js.map +1 -1
  19. package/lib/pdf.js +82 -23
  20. package/lib/pdf.js.map +1 -1
  21. package/lib/png.d.ts +3 -1
  22. package/lib/png.js +19 -5
  23. package/lib/png.js.map +1 -1
  24. package/lib/png_browser.d.ts +3 -1
  25. package/lib/png_browser.js +60 -10
  26. package/lib/png_browser.js.map +1 -1
  27. package/lib/qr-base.js +6 -4
  28. package/lib/qr-base.js.map +1 -1
  29. package/lib/svg.d.ts +3 -1
  30. package/lib/svg.js +54 -12
  31. package/lib/svg.js.map +1 -1
  32. package/lib/tests/test.js +101 -1
  33. package/lib/tests/test.js.map +1 -1
  34. package/lib/typing/types.d.ts +13 -0
  35. package/lib/utils.d.ts +32 -2
  36. package/lib/utils.js +169 -11
  37. package/lib/utils.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/bitMatrix.ts +1 -0
  40. package/src/errorcode.ts +10 -17
  41. package/src/matrix.ts +13 -3
  42. package/src/pdf.ts +105 -23
  43. package/src/png.ts +26 -5
  44. package/src/png_browser.ts +70 -10
  45. package/src/qr-base.ts +6 -4
  46. package/src/svg.ts +69 -12
  47. package/src/tests/test.ts +101 -1
  48. package/src/typing/types.ts +93 -0
  49. package/src/utils.ts +220 -12
package/README.md CHANGED
@@ -4,7 +4,20 @@
4
4
 
5
5
  QR Code generator for browser and node.js with tree shaking and logo support
6
6
 
7
- ![image](https://github.com/Short-io/qreator/assets/75169/02b84738-56f2-44d8-8d11-f40e263302ed)
7
+ <table>
8
+ <tr>
9
+ <td align="center"><img src="showcase/classic.png" width="180" /><br /><b>Classic</b></td>
10
+ <td align="center"><img src="showcase/rounded.png" width="180" /><br /><b>Rounded</b></td>
11
+ <td align="center"><img src="showcase/drops.png" width="180" /><br /><b>Custom finders</b></td>
12
+ <td align="center"><img src="showcase/logo.png" width="180" /><br /><b>Logo overlay</b></td>
13
+ </tr>
14
+ <tr>
15
+ <td align="center"><img src="showcase/label-below.png" width="180" /><br /><b>Label: below</b></td>
16
+ <td align="center"><img src="showcase/label-pill.png" width="180" /><br /><b>Label: pill</b></td>
17
+ <td align="center"><img src="showcase/label-box.png" width="180" /><br /><b>Label: box</b></td>
18
+ <td align="center"><img src="showcase/branded.png" width="180" /><br /><b>Branded</b></td>
19
+ </tr>
20
+ </table>
8
21
 
9
22
  ## Overview
10
23
 
@@ -14,6 +27,10 @@ QR Code generator for browser and node.js with tree shaking and logo support
14
27
  - supports color customization
15
28
  - supports logos
16
29
  - supports border-radius
30
+ - supports corner mode (merged rounded corners)
31
+ - supports finder pattern customization (shape + color)
32
+ - supports text labels (below, pill, box styles)
33
+ - optional React component
17
34
  - tree shaking support
18
35
  - browser / node.js
19
36
 
@@ -47,6 +64,37 @@ const pngBuffer = await getPNG("I love QR", {
47
64
  });
48
65
  ```
49
66
 
67
+ ### Label
68
+
69
+ ```javascript
70
+ const png = await getPNG("https://example.com", {
71
+ labelText: "SCAN ME",
72
+ labelStyle: "pill", // "below", "pill", or "box"
73
+ });
74
+ ```
75
+
76
+ ### Finder pattern customization
77
+
78
+ ```javascript
79
+ const svgString = await getSVG("I love QR", {
80
+ finderOuterShape: "drop",
81
+ finderInnerShape: "circle",
82
+ finderColor: "#ff0000",
83
+ borderRadius: 2,
84
+ cornerMode: "merge",
85
+ });
86
+ ```
87
+
88
+ ### React
89
+
90
+ ```jsx
91
+ import { QR } from "qreator/lib/react";
92
+
93
+ function App() {
94
+ return <QR text="https://example.com" labelText="SCAN ME" labelStyle="pill" />;
95
+ }
96
+ ```
97
+
50
98
  [More examples](./examples)
51
99
 
52
100
  ### Syntax
@@ -75,7 +123,17 @@ const pngBuffer = await getPNG("I love QR", {
75
123
  | `color` | module color in rgba or hex format | number | `#000000` - `#000000` | `#000000`<br />(black with 100% opacity) |
76
124
  | `bgColor` | background color in rgba or hex format | number | `#000000` - `#FFFFFF` | `#FFFFFF`<br />(white with 100% opacity) |
77
125
  | `borderRadius` | border-radius (in pixels) | number | 0 - `size / 2` | `0` |
126
+ | `cornerMode` | how corners are rendered when borderRadius > 0 | string | `individual`, `merge` | `individual` |
127
+ | `finderOuterShape` | shape of the outer ring of finder patterns | string | `square`, `rounded`, `circle`, `drop` | `undefined` |
128
+ | `finderInnerShape` | shape of the inner dot of finder patterns | string | `square`, `rounded`, `circle`, `drop` | `undefined` |
129
+ | `finderColor` | color of finder patterns (overrides `color`) | number/string | same as `color` | `undefined` |
78
130
  | `noExcavate` | don't remove partially covered modules | boolean | `true`, `false` | `false` |
131
+ | `labelText` | text label to display below QR | string | - | `undefined` |
132
+ | `labelStyle` | label presentation style | string | `below`, `pill`, `box` | `below` |
133
+ | `labelColor` | label text color | number/string | same as `color` | `color` (below) / `bgColor` (pill/box) |
134
+ | `labelBgColor` | label background color (pill/box only) | number/string | same as `color` | same as `color` |
135
+ | `labelFontSize`| font size as multiple of module size | number | `1` - n | `5` |
136
+ | `labelFontFamily`| font family | string | - | `sans-serif` |
79
137
 
80
138
 
81
139
  ## Benchmarks
@@ -761,7 +761,7 @@
761
761
  function requireCommon () {
762
762
  if (hasRequiredCommon) return common;
763
763
  hasRequiredCommon = 1;
764
- (function (exports) {
764
+ (function (exports$1) {
765
765
 
766
766
 
767
767
  var TYPED_OK = (typeof Uint8Array !== 'undefined') &&
@@ -772,7 +772,7 @@
772
772
  return Object.prototype.hasOwnProperty.call(obj, key);
773
773
  }
774
774
 
775
- exports.assign = function (obj /*from1, from2, from3, ...*/) {
775
+ exports$1.assign = function (obj /*from1, from2, from3, ...*/) {
776
776
  var sources = Array.prototype.slice.call(arguments, 1);
777
777
  while (sources.length) {
778
778
  var source = sources.shift();
@@ -794,7 +794,7 @@
794
794
 
795
795
 
796
796
  // reduce buffer size, avoiding mem copy
797
- exports.shrinkBuf = function (buf, size) {
797
+ exports$1.shrinkBuf = function (buf, size) {
798
798
  if (buf.length === size) { return buf; }
799
799
  if (buf.subarray) { return buf.subarray(0, size); }
800
800
  buf.length = size;
@@ -851,21 +851,21 @@
851
851
 
852
852
  // Enable/Disable typed arrays use, for testing
853
853
  //
854
- exports.setTyped = function (on) {
854
+ exports$1.setTyped = function (on) {
855
855
  if (on) {
856
- exports.Buf8 = Uint8Array;
857
- exports.Buf16 = Uint16Array;
858
- exports.Buf32 = Int32Array;
859
- exports.assign(exports, fnTyped);
856
+ exports$1.Buf8 = Uint8Array;
857
+ exports$1.Buf16 = Uint16Array;
858
+ exports$1.Buf32 = Int32Array;
859
+ exports$1.assign(exports$1, fnTyped);
860
860
  } else {
861
- exports.Buf8 = Array;
862
- exports.Buf16 = Array;
863
- exports.Buf32 = Array;
864
- exports.assign(exports, fnUntyped);
861
+ exports$1.Buf8 = Array;
862
+ exports$1.Buf16 = Array;
863
+ exports$1.Buf32 = Array;
864
+ exports$1.assign(exports$1, fnUntyped);
865
865
  }
866
866
  };
867
867
 
868
- exports.setTyped(TYPED_OK);
868
+ exports$1.setTyped(TYPED_OK);
869
869
  } (common));
870
870
  return common;
871
871
  }
@@ -25794,22 +25794,22 @@
25794
25794
  }
25795
25795
 
25796
25796
  function calculateEC(msg, ec_len) {
25797
- msg = [].slice.call(msg);
25797
+ const len = msg.length;
25798
+ const buf = new Array(len + ec_len);
25799
+ for (let i = 0; i < len; i++)
25800
+ buf[i] = msg[i];
25801
+ for (let i = len; i < len + ec_len; i++)
25802
+ buf[i] = 0;
25798
25803
  const poly = generatorPolynomial(ec_len);
25799
- for (let i = 0; i < ec_len; i++)
25800
- msg.push(0);
25801
- while (msg.length > ec_len) {
25802
- if (!msg[0]) {
25803
- msg.shift();
25804
+ for (let offset = 0; offset < len; offset++) {
25805
+ if (!buf[offset])
25804
25806
  continue;
25805
- }
25806
- const log_k = log(msg[0]);
25807
+ const log_k = log(buf[offset]);
25807
25808
  for (let i = 0; i <= ec_len; i++) {
25808
- msg[i] = msg[i] ^ exp(poly[i] + log_k);
25809
+ buf[offset + i] = buf[offset + i] ^ exp(poly[i] + log_k);
25809
25810
  }
25810
- msg.shift();
25811
25811
  }
25812
- return new Uint8Array(msg);
25812
+ return new Uint8Array(buf.slice(len));
25813
25813
  }
25814
25814
  const GF256_BASE = 285;
25815
25815
  const EXP_TABLE = [1];
@@ -25824,11 +25824,7 @@
25824
25824
  LOG_TABLE[EXP_TABLE[i]] = i;
25825
25825
  }
25826
25826
  function exp(k) {
25827
- while (k < 0)
25828
- k += 255;
25829
- while (k > 255)
25830
- k -= 255;
25831
- return EXP_TABLE[k];
25827
+ return EXP_TABLE[((k % 255) + 255) % 255];
25832
25828
  }
25833
25829
  function log(k) {
25834
25830
  if (k < 1 || k > 255) {
@@ -26192,9 +26188,17 @@
26192
26188
  bestMask = mask;
26193
26189
  }
26194
26190
  }
26195
- fillData(matrix, data, bestMask);
26196
- fillReserved(matrix, data.ec_level, bestMask);
26197
- return matrix.map((row) => row.map((cell) => (cell & 1)));
26191
+ if (bestMask !== 7) {
26192
+ fillData(matrix, data, bestMask);
26193
+ fillReserved(matrix, data.ec_level, bestMask);
26194
+ }
26195
+ for (let i = 0; i < matrix.length; i++) {
26196
+ const row = matrix[i];
26197
+ for (let j = 0; j < row.length; j++) {
26198
+ row[j] = (row[j] & 1);
26199
+ }
26200
+ }
26201
+ return matrix;
26198
26202
  }
26199
26203
 
26200
26204
  const EC_LEVELS = ["L", "M", "Q", "H"];
@@ -26210,7 +26214,7 @@
26210
26214
  for (; i < 10; i++) {
26211
26215
  let version = mappedVersions[i][ec_level];
26212
26216
  if (version.data_len >= len) {
26213
- return deepCopy(version);
26217
+ return copyTemplate(version);
26214
26218
  }
26215
26219
  }
26216
26220
  if (message.data10) {
@@ -26222,14 +26226,14 @@
26222
26226
  for (; i < 27; i++) {
26223
26227
  let version = mappedVersions[i][ec_level];
26224
26228
  if (version.data_len >= len) {
26225
- return deepCopy(version);
26229
+ return copyTemplate(version);
26226
26230
  }
26227
26231
  }
26228
26232
  len = Math.ceil(message.data27.length / 8);
26229
26233
  for (; i < 41; i++) {
26230
26234
  let version = mappedVersions[i][ec_level];
26231
26235
  if (version.data_len >= len) {
26232
- return deepCopy(version);
26236
+ return copyTemplate(version);
26233
26237
  }
26234
26238
  }
26235
26239
  throw new Error("Too much data");
@@ -26274,7 +26278,9 @@
26274
26278
  const data = fillTemplate(message, getTemplate(message, ec_level));
26275
26279
  return getMatrix(data);
26276
26280
  }
26277
- const deepCopy = typeof structuredClone !== "undefined" ? structuredClone : ((obj) => JSON.parse(JSON.stringify(obj)));
26281
+ function copyTemplate(t) {
26282
+ return { ...t, blocks: [...t.blocks], ec: [] };
26283
+ }
26278
26284
  const versions = [
26279
26285
  [],
26280
26286
  [26, 7, 1, 10, 1, 13, 1, 17, 1],
@@ -26822,6 +26828,12 @@
26822
26828
  const defaults = type === "png" ? BITMAP_OPTIONS : VECTOR_OPTIONS;
26823
26829
  return { ...defaults, ...inOptions };
26824
26830
  }
26831
+ function colorToHex(color) {
26832
+ if (typeof color === "string") {
26833
+ return colorString.to.hex(colorString.get.rgb(color));
26834
+ }
26835
+ return `#${(color >>> 8).toString(16).padStart(6, "0")}`;
26836
+ }
26825
26837
  const svgMove = (left, top) => ['M', left, top];
26826
26838
  const svgReturn = () => ['z'];
26827
26839
  const svgDeltaArc = (borderRadius, dx, dy, sweep = 0) => borderRadius > 0 ? ['a', borderRadius, borderRadius, 0, 0, sweep, dx, dy] : [];
@@ -26857,7 +26869,22 @@
26857
26869
  }
26858
26870
  return rectangles.join(" ");
26859
26871
  }
26860
- function getDotsSVGPath(matrix, size, margin = 0, borderRadius = 0) {
26872
+ function isActive(matrix, x, y) {
26873
+ return x >= 0 && x < matrix.length && y >= 0 && y < (matrix[0]?.length ?? 0) && !!matrix[x][y];
26874
+ }
26875
+ function getExposedCorners(matrix, x, y) {
26876
+ const left = isActive(matrix, x - 1, y);
26877
+ const right = isActive(matrix, x + 1, y);
26878
+ const top = isActive(matrix, x, y - 1);
26879
+ const bottom = isActive(matrix, x, y + 1);
26880
+ return [
26881
+ !left && !top,
26882
+ !right && !top,
26883
+ !right && !bottom,
26884
+ !left && !bottom,
26885
+ ];
26886
+ }
26887
+ function getDotsSVGPath(matrix, size, margin = 0, borderRadius = 0, cornerMode = 'individual') {
26861
26888
  let rectangles = [];
26862
26889
  for (let x = 0; x < matrix.length; x++) {
26863
26890
  const column = matrix[x];
@@ -26865,17 +26892,27 @@
26865
26892
  if (column[y]) {
26866
26893
  const leftX = x * size + margin;
26867
26894
  const topY = y * size + margin;
26868
- const delta = size - 2 * borderRadius;
26895
+ let rTL, rTR, rBR, rBL;
26896
+ if (cornerMode === 'merge') {
26897
+ const [eTL, eTR, eBR, eBL] = getExposedCorners(matrix, x, y);
26898
+ rTL = eTL ? borderRadius : 0;
26899
+ rTR = eTR ? borderRadius : 0;
26900
+ rBR = eBR ? borderRadius : 0;
26901
+ rBL = eBL ? borderRadius : 0;
26902
+ }
26903
+ else {
26904
+ rTL = rTR = rBR = rBL = borderRadius;
26905
+ }
26869
26906
  const rectangle = [
26870
- svgMove(leftX, topY + borderRadius),
26871
- svgVerticalDeltaLite(delta),
26872
- svgDeltaArc(borderRadius, borderRadius, borderRadius),
26873
- svgHorizontalDeltaLine(delta),
26874
- svgDeltaArc(borderRadius, borderRadius, -borderRadius),
26875
- svgVerticalDeltaLite(-delta),
26876
- svgDeltaArc(borderRadius, -borderRadius, -borderRadius),
26877
- svgHorizontalDeltaLine(-delta),
26878
- svgDeltaArc(borderRadius, -borderRadius, borderRadius),
26907
+ svgMove(leftX, topY + rTL),
26908
+ svgVerticalDeltaLite(size - rTL - rBL),
26909
+ svgDeltaArc(rBL, rBL, rBL),
26910
+ svgHorizontalDeltaLine(size - rBL - rBR),
26911
+ svgDeltaArc(rBR, rBR, -rBR),
26912
+ svgVerticalDeltaLite(-(size - rBR - rTR)),
26913
+ svgDeltaArc(rTR, -rTR, -rTR),
26914
+ svgHorizontalDeltaLine(-(size - rTR - rTL)),
26915
+ svgDeltaArc(rTL, -rTL, rTL),
26879
26916
  svgReturn(),
26880
26917
  ];
26881
26918
  rectangles.push(...rectangle.flat());
@@ -26884,6 +26921,62 @@
26884
26921
  }
26885
26922
  return rectangles.join(" ");
26886
26923
  }
26924
+ const FINDER_SIDES = [[0, 0], [1, 0], [0, 1]];
26925
+ const FINDER_END = 7;
26926
+ function getCornerRadii(shape, sideLength, borderRadius) {
26927
+ const maxR = sideLength / 2;
26928
+ switch (shape) {
26929
+ case 'square': return [0, 0, 0, 0];
26930
+ case 'rounded': return [borderRadius, borderRadius, borderRadius, borderRadius];
26931
+ case 'circle': return [maxR, maxR, maxR, maxR];
26932
+ case 'drop': return [maxR, 0, maxR, maxR];
26933
+ }
26934
+ }
26935
+ function drawFinderRect(xCorner, yCorner, sideLength, xSign, ySign, sweep, cornerRadii) {
26936
+ const [r0, r1, r2, r3] = cornerRadii;
26937
+ return [
26938
+ svgMove(xCorner, yCorner + r3 * ySign),
26939
+ svgVerticalDeltaLite(ySign * (sideLength - r3 - r0)),
26940
+ svgDeltaArc(r0, r0 * xSign, r0 * ySign, sweep),
26941
+ svgHorizontalDeltaLine(xSign * (sideLength - r0 - r1)),
26942
+ svgDeltaArc(r1, r1 * xSign, -r1 * ySign, sweep),
26943
+ svgVerticalDeltaLite(-ySign * (sideLength - r1 - r2)),
26944
+ svgDeltaArc(r2, -r2 * xSign, -r2 * ySign, sweep),
26945
+ svgHorizontalDeltaLine(-xSign * (sideLength - r2 - r3)),
26946
+ svgDeltaArc(r3, -r3 * xSign, r3 * ySign, sweep),
26947
+ svgReturn(),
26948
+ ].flat();
26949
+ }
26950
+ function getFinderOuterSVGPath(matrix, size, margin, borderRadius, shape) {
26951
+ const matrixSize = matrix.length * size + margin * 2;
26952
+ const rectangles = [];
26953
+ for (const side of FINDER_SIDES) {
26954
+ const [xSign, ySign] = side.map(s => s === 0 ? 1 : -1);
26955
+ const sweep = side[1] | side[0];
26956
+ for (const offset of [0, 1]) {
26957
+ const sideLength = size * (FINDER_END - 2 * offset);
26958
+ const xCorner = matrixSize * side[0] + xSign * (margin + size * offset);
26959
+ const yCorner = matrixSize * side[1] + ySign * (margin + size * offset);
26960
+ const radii = getCornerRadii(shape, sideLength, borderRadius);
26961
+ rectangles.push(...drawFinderRect(xCorner, yCorner, sideLength, xSign, ySign, sweep, radii));
26962
+ }
26963
+ }
26964
+ return rectangles.join(" ");
26965
+ }
26966
+ function getFinderInnerSVGPath(matrix, size, margin, borderRadius, shape) {
26967
+ const matrixSize = matrix.length * size + margin * 2;
26968
+ const rectangles = [];
26969
+ for (const side of FINDER_SIDES) {
26970
+ const [xSign, ySign] = side.map(s => s === 0 ? 1 : -1);
26971
+ const sweep = side[1] | side[0];
26972
+ const sideLength = size * (FINDER_END - 2 * 2);
26973
+ const xCorner = matrixSize * side[0] + xSign * (margin + size * 2);
26974
+ const yCorner = matrixSize * side[1] + ySign * (margin + size * 2);
26975
+ const radii = getCornerRadii(shape, sideLength, borderRadius);
26976
+ rectangles.push(...drawFinderRect(xCorner, yCorner, sideLength, xSign, ySign, sweep, radii));
26977
+ }
26978
+ return rectangles.join(" ");
26979
+ }
26887
26980
  const commonOptions = {
26888
26981
  type: "png",
26889
26982
  parse_url: false,
@@ -26904,6 +26997,83 @@
26904
26997
  margin: 1,
26905
26998
  size: 0,
26906
26999
  };
27000
+ function computeLabelLayout(options, qrSizePx, marginPx, moduleSize) {
27001
+ if (!options.labelText)
27002
+ return null;
27003
+ const style = options.labelStyle ?? "below";
27004
+ const fontSize = (options.labelFontSize ?? 5) * moduleSize;
27005
+ const fontFamily = options.labelFontFamily ?? "sans-serif";
27006
+ const fgColor = colorToHex(options.color ?? 0x000000ff);
27007
+ const bgColorHex = colorToHex(options.bgColor ?? 0xffffffff);
27008
+ const textColor = options.labelColor
27009
+ ? colorToHex(options.labelColor)
27010
+ : style === "below" ? fgColor : bgColorHex;
27011
+ const labelBgColor = options.labelBgColor
27012
+ ? colorToHex(options.labelBgColor)
27013
+ : fgColor;
27014
+ if (style === "below") {
27015
+ const stripHeight = fontSize * 2.5;
27016
+ return {
27017
+ totalWidth: qrSizePx,
27018
+ totalHeight: qrSizePx + stripHeight,
27019
+ qrSize: qrSizePx,
27020
+ label: {
27021
+ text: options.labelText,
27022
+ x: qrSizePx / 2,
27023
+ y: qrSizePx + stripHeight / 2,
27024
+ width: qrSizePx,
27025
+ height: stripHeight,
27026
+ fontSize,
27027
+ fontFamily,
27028
+ textColor,
27029
+ bgColor: null,
27030
+ borderRadius: 0,
27031
+ },
27032
+ };
27033
+ }
27034
+ if (style === "pill") {
27035
+ const pillHeight = fontSize * 2.2;
27036
+ const estimatedTextWidth = options.labelText.length * fontSize * 0.7;
27037
+ const pillWidth = Math.min(Math.max(estimatedTextWidth + fontSize * 2, qrSizePx * 0.3), qrSizePx * 0.95);
27038
+ const stripHeight = pillHeight + fontSize * 0.6;
27039
+ return {
27040
+ totalWidth: qrSizePx,
27041
+ totalHeight: qrSizePx + stripHeight,
27042
+ qrSize: qrSizePx,
27043
+ label: {
27044
+ text: options.labelText,
27045
+ x: qrSizePx / 2,
27046
+ y: qrSizePx + stripHeight / 2,
27047
+ width: pillWidth,
27048
+ height: pillHeight,
27049
+ fontSize,
27050
+ fontFamily,
27051
+ textColor,
27052
+ bgColor: labelBgColor,
27053
+ borderRadius: pillHeight / 2,
27054
+ },
27055
+ };
27056
+ }
27057
+ const boxHeight = fontSize * 2.2;
27058
+ const stripHeight = boxHeight + fontSize * 0.4;
27059
+ return {
27060
+ totalWidth: qrSizePx,
27061
+ totalHeight: qrSizePx + stripHeight,
27062
+ qrSize: qrSizePx,
27063
+ label: {
27064
+ text: options.labelText,
27065
+ x: qrSizePx / 2,
27066
+ y: qrSizePx + stripHeight / 2,
27067
+ width: qrSizePx,
27068
+ height: boxHeight,
27069
+ fontSize,
27070
+ fontFamily,
27071
+ textColor,
27072
+ bgColor: labelBgColor,
27073
+ borderRadius: 0,
27074
+ },
27075
+ };
27076
+ }
26907
27077
 
26908
27078
  function zeroFillFinders(matrix) {
26909
27079
  matrix = structuredClone(matrix);
@@ -26951,7 +27121,11 @@
26951
27121
  if (options.logo && options.logoWidth && options.logoHeight && !options.noExcavate) {
26952
27122
  matrix = clearMatrixCenter(matrix, options.logoWidth, options.logoHeight);
26953
27123
  }
26954
- return PDF({ matrix, ...options });
27124
+ const pdfSize = 9;
27125
+ const marginPx = options.margin * pdfSize;
27126
+ const imageSizePx = matrix.length * pdfSize + 2 * marginPx;
27127
+ const layout = computeLabelLayout(options, imageSizePx, marginPx, pdfSize);
27128
+ return PDF({ matrix, ...options, labelLayout: layout });
26955
27129
  }
26956
27130
  function colorToRGB(color) {
26957
27131
  if (typeof color === "string") {
@@ -26992,34 +27166,47 @@
26992
27166
  page.getContentStream = page.prevGetContentStream;
26993
27167
  page.contentStream.push = page.contentStream.prevPush;
26994
27168
  }
26995
- async function PDF({ matrix, margin, logo, logoWidth, logoHeight, color, bgColor, borderRadius, }) {
27169
+ async function PDF({ matrix, margin, logo, logoWidth, logoHeight, color, bgColor, borderRadius, cornerMode, finderOuterShape, finderInnerShape, finderColor, labelLayout, }) {
26996
27170
  const size = 9;
26997
27171
  const marginPx = margin * size;
26998
27172
  const matrixSizePx = matrix.length * size;
26999
27173
  const imageSizePx = matrixSizePx + 2 * marginPx;
27174
+ const totalWidth = labelLayout?.totalWidth ?? imageSizePx;
27175
+ const totalHeight = labelLayout?.totalHeight ?? imageSizePx;
27000
27176
  const document = await PDFDocument.create();
27001
- const page = document.addPage([imageSizePx, imageSizePx]);
27002
- page.drawSquare({
27003
- size: imageSizePx,
27177
+ const page = document.addPage([totalWidth, totalHeight]);
27178
+ page.drawRectangle({
27179
+ x: 0,
27180
+ y: 0,
27181
+ width: totalWidth,
27182
+ height: totalHeight,
27004
27183
  color: rgb(...colorToRGB(bgColor)),
27005
27184
  });
27006
27185
  page.moveTo(0, page.getHeight());
27007
- const path = getDotsSVGPath(matrix, size, marginPx, borderRadius);
27008
- page.drawSvgPath(path, {
27009
- color: rgb(...colorToRGB(color)),
27010
- opacity: getOpacity(color),
27011
- borderColor: rgb(...colorToRGB(color)),
27012
- borderOpacity: getOpacity(color),
27013
- });
27014
- const findersPath = getFindersSVGPath(matrix, size, marginPx, borderRadius);
27015
- patchContentStream(page);
27016
- page.drawSvgPath(findersPath, {
27017
- color: rgb(...colorToRGB(color)),
27018
- opacity: getOpacity(color),
27019
- borderColor: rgb(...colorToRGB(color)),
27020
- borderOpacity: getOpacity(color),
27021
- });
27022
- revertContentStream(page);
27186
+ const fgRGB = rgb(...colorToRGB(color));
27187
+ const fgOpacity = getOpacity(color);
27188
+ const fgStyle = { color: fgRGB, opacity: fgOpacity, borderColor: fgRGB, borderOpacity: fgOpacity };
27189
+ const path = getDotsSVGPath(matrix, size, marginPx, borderRadius, cornerMode);
27190
+ page.drawSvgPath(path, fgStyle);
27191
+ const hasFinderOptions = finderOuterShape || finderInnerShape || finderColor;
27192
+ if (hasFinderOptions) {
27193
+ const fc = finderColor ?? color;
27194
+ const fcRGB = rgb(...colorToRGB(fc));
27195
+ const fcOpacity = getOpacity(fc);
27196
+ const fcStyle = { color: fcRGB, opacity: fcOpacity, borderColor: fcRGB, borderOpacity: fcOpacity };
27197
+ const outerPath = getFinderOuterSVGPath(matrix, size, marginPx, borderRadius, finderOuterShape ?? 'rounded');
27198
+ patchContentStream(page);
27199
+ page.drawSvgPath(outerPath, fcStyle);
27200
+ revertContentStream(page);
27201
+ const innerPath = getFinderInnerSVGPath(matrix, size, marginPx, borderRadius, finderInnerShape ?? 'rounded');
27202
+ page.drawSvgPath(innerPath, fcStyle);
27203
+ }
27204
+ else {
27205
+ const findersPath = getFindersSVGPath(matrix, size, marginPx, borderRadius);
27206
+ patchContentStream(page);
27207
+ page.drawSvgPath(findersPath, fgStyle);
27208
+ revertContentStream(page);
27209
+ }
27023
27210
  if (logo) {
27024
27211
  let logoData;
27025
27212
  const header = new Uint8Array(logo.slice(0, 4));
@@ -27038,8 +27225,50 @@
27038
27225
  height: logoHeightPx,
27039
27226
  });
27040
27227
  }
27228
+ if (labelLayout) {
27229
+ await drawPDFLabel(document, page, labelLayout, totalHeight);
27230
+ }
27041
27231
  return document.save();
27042
27232
  }
27233
+ async function drawPDFLabel(document, page, layout, totalHeight) {
27234
+ const { label } = layout;
27235
+ const font = await document.embedFont(StandardFonts.Helvetica);
27236
+ const pdfLabelCenterY = totalHeight - label.y;
27237
+ if (label.bgColor) {
27238
+ const [r, g, b] = colorToRGB(label.bgColor);
27239
+ const rectX = label.x - label.width / 2;
27240
+ const rectY = pdfLabelCenterY - label.height / 2;
27241
+ if (label.borderRadius > 0) {
27242
+ const w = label.width;
27243
+ const h = label.height;
27244
+ const rad = Math.min(label.borderRadius, w / 2, h / 2);
27245
+ const pillPath = `M ${rectX + rad} 0 h ${w - 2 * rad} a ${rad} ${rad} 0 0 1 ${rad} ${-rad} v ${-(h - 2 * rad)} a ${rad} ${rad} 0 0 1 ${-rad} ${-rad} h ${-(w - 2 * rad)} a ${rad} ${rad} 0 0 1 ${-rad} ${rad} v ${h - 2 * rad} a ${rad} ${rad} 0 0 1 ${rad} ${rad} z`;
27246
+ page.moveTo(0, rectY + label.height);
27247
+ page.drawSvgPath(pillPath, {
27248
+ color: rgb(r, g, b),
27249
+ });
27250
+ page.moveTo(0, page.getHeight());
27251
+ }
27252
+ else {
27253
+ page.drawRectangle({
27254
+ x: rectX,
27255
+ y: rectY,
27256
+ width: label.width,
27257
+ height: label.height,
27258
+ color: rgb(r, g, b),
27259
+ });
27260
+ }
27261
+ }
27262
+ const textWidth = font.widthOfTextAtSize(label.text, label.fontSize);
27263
+ const [tr, tg, tb] = colorToRGB(label.textColor);
27264
+ page.drawText(label.text, {
27265
+ x: label.x - textWidth / 2,
27266
+ y: pdfLabelCenterY - label.fontSize * 0.35,
27267
+ size: label.fontSize,
27268
+ font,
27269
+ color: rgb(tr, tg, tb),
27270
+ });
27271
+ }
27043
27272
 
27044
27273
  exports.getPDF = getPDF;
27045
27274