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.
- package/README.md +59 -1
- package/lib/browser/pdf.umd.js +297 -68
- package/lib/browser/pdf.umd.js.map +1 -1
- package/lib/browser/png.d.ts +3 -1
- package/lib/browser/png.umd.js +257 -43
- package/lib/browser/png.umd.js.map +1 -1
- package/lib/browser/png_browser.d.ts +3 -1
- package/lib/browser/react.umd.js +291 -86
- package/lib/browser/react.umd.js.map +1 -1
- package/lib/browser/svg.d.ts +3 -1
- package/lib/browser/svg.umd.js +251 -46
- package/lib/browser/svg.umd.js.map +1 -1
- package/lib/browser/typing/types.d.ts +13 -0
- package/lib/browser/utils.d.ts +32 -2
- package/lib/errorcode.js +12 -16
- package/lib/errorcode.js.map +1 -1
- package/lib/matrix.js +11 -3
- package/lib/matrix.js.map +1 -1
- package/lib/pdf.js +82 -23
- package/lib/pdf.js.map +1 -1
- package/lib/png.d.ts +3 -1
- package/lib/png.js +19 -5
- package/lib/png.js.map +1 -1
- package/lib/png_browser.d.ts +3 -1
- package/lib/png_browser.js +60 -10
- package/lib/png_browser.js.map +1 -1
- package/lib/qr-base.js +6 -4
- package/lib/qr-base.js.map +1 -1
- package/lib/svg.d.ts +3 -1
- package/lib/svg.js +54 -12
- package/lib/svg.js.map +1 -1
- package/lib/tests/test.js +101 -1
- package/lib/tests/test.js.map +1 -1
- package/lib/typing/types.d.ts +13 -0
- package/lib/utils.d.ts +32 -2
- package/lib/utils.js +169 -11
- package/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/bitMatrix.ts +1 -0
- package/src/errorcode.ts +10 -17
- package/src/matrix.ts +13 -3
- package/src/pdf.ts +105 -23
- package/src/png.ts +26 -5
- package/src/png_browser.ts +70 -10
- package/src/qr-base.ts +6 -4
- package/src/svg.ts +69 -12
- package/src/tests/test.ts +101 -1
- package/src/typing/types.ts +93 -0
- 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
|
-
|
|
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
|
package/lib/browser/pdf.umd.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
25800
|
-
|
|
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
|
-
|
|
25809
|
+
buf[offset + i] = buf[offset + i] ^ exp(poly[i] + log_k);
|
|
25809
25810
|
}
|
|
25810
|
-
msg.shift();
|
|
25811
25811
|
}
|
|
25812
|
-
return new Uint8Array(
|
|
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
|
-
|
|
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
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 +
|
|
26871
|
-
svgVerticalDeltaLite(
|
|
26872
|
-
svgDeltaArc(
|
|
26873
|
-
svgHorizontalDeltaLine(
|
|
26874
|
-
svgDeltaArc(
|
|
26875
|
-
svgVerticalDeltaLite(-
|
|
26876
|
-
svgDeltaArc(
|
|
26877
|
-
svgHorizontalDeltaLine(-
|
|
26878
|
-
svgDeltaArc(
|
|
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
|
-
|
|
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([
|
|
27002
|
-
page.
|
|
27003
|
-
|
|
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
|
|
27008
|
-
|
|
27009
|
-
|
|
27010
|
-
|
|
27011
|
-
|
|
27012
|
-
|
|
27013
|
-
|
|
27014
|
-
|
|
27015
|
-
|
|
27016
|
-
|
|
27017
|
-
color:
|
|
27018
|
-
|
|
27019
|
-
|
|
27020
|
-
|
|
27021
|
-
|
|
27022
|
-
|
|
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
|
|