qreator 9.8.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 +41 -1
- package/lib/browser/pdf.umd.js +139 -5
- package/lib/browser/pdf.umd.js.map +1 -1
- package/lib/browser/png.d.ts +3 -1
- package/lib/browser/png.umd.js +121 -5
- package/lib/browser/png.umd.js.map +1 -1
- package/lib/browser/png_browser.d.ts +3 -1
- package/lib/browser/react.umd.js +109 -8
- package/lib/browser/react.umd.js.map +1 -1
- package/lib/browser/svg.d.ts +3 -1
- package/lib/browser/svg.umd.js +109 -8
- package/lib/browser/svg.umd.js.map +1 -1
- package/lib/browser/typing/types.d.ts +7 -0
- package/lib/browser/utils.d.ts +25 -1
- package/lib/pdf.js +58 -7
- package/lib/pdf.js.map +1 -1
- package/lib/png.d.ts +3 -1
- package/lib/png.js +15 -5
- package/lib/png.js.map +1 -1
- package/lib/png_browser.d.ts +3 -1
- package/lib/png_browser.js +45 -6
- package/lib/png_browser.js.map +1 -1
- package/lib/svg.d.ts +3 -1
- package/lib/svg.js +33 -8
- package/lib/svg.js.map +1 -1
- package/lib/tests/test.js +28 -0
- package/lib/tests/test.js.map +1 -1
- package/lib/typing/types.d.ts +7 -0
- package/lib/utils.d.ts +25 -1
- package/lib/utils.js +77 -0
- package/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/bitMatrix.ts +1 -0
- package/src/pdf.ts +76 -7
- package/src/png.ts +18 -5
- package/src/png_browser.ts +52 -6
- package/src/svg.ts +42 -7
- package/src/tests/test.ts +28 -0
- package/src/typing/types.ts +48 -0
- package/src/utils.ts +107 -1
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
|
|
|
@@ -16,6 +29,8 @@ QR Code generator for browser and node.js with tree shaking and logo support
|
|
|
16
29
|
- supports border-radius
|
|
17
30
|
- supports corner mode (merged rounded corners)
|
|
18
31
|
- supports finder pattern customization (shape + color)
|
|
32
|
+
- supports text labels (below, pill, box styles)
|
|
33
|
+
- optional React component
|
|
19
34
|
- tree shaking support
|
|
20
35
|
- browser / node.js
|
|
21
36
|
|
|
@@ -49,6 +64,15 @@ const pngBuffer = await getPNG("I love QR", {
|
|
|
49
64
|
});
|
|
50
65
|
```
|
|
51
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
|
+
|
|
52
76
|
### Finder pattern customization
|
|
53
77
|
|
|
54
78
|
```javascript
|
|
@@ -61,6 +85,16 @@ const svgString = await getSVG("I love QR", {
|
|
|
61
85
|
});
|
|
62
86
|
```
|
|
63
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
|
+
|
|
64
98
|
[More examples](./examples)
|
|
65
99
|
|
|
66
100
|
### Syntax
|
|
@@ -94,6 +128,12 @@ const svgString = await getSVG("I love QR", {
|
|
|
94
128
|
| `finderInnerShape` | shape of the inner dot of finder patterns | string | `square`, `rounded`, `circle`, `drop` | `undefined` |
|
|
95
129
|
| `finderColor` | color of finder patterns (overrides `color`) | number/string | same as `color` | `undefined` |
|
|
96
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` |
|
|
97
137
|
|
|
98
138
|
|
|
99
139
|
## Benchmarks
|
package/lib/browser/pdf.umd.js
CHANGED
|
@@ -26828,6 +26828,12 @@
|
|
|
26828
26828
|
const defaults = type === "png" ? BITMAP_OPTIONS : VECTOR_OPTIONS;
|
|
26829
26829
|
return { ...defaults, ...inOptions };
|
|
26830
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
|
+
}
|
|
26831
26837
|
const svgMove = (left, top) => ['M', left, top];
|
|
26832
26838
|
const svgReturn = () => ['z'];
|
|
26833
26839
|
const svgDeltaArc = (borderRadius, dx, dy, sweep = 0) => borderRadius > 0 ? ['a', borderRadius, borderRadius, 0, 0, sweep, dx, dy] : [];
|
|
@@ -26991,6 +26997,83 @@
|
|
|
26991
26997
|
margin: 1,
|
|
26992
26998
|
size: 0,
|
|
26993
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
|
+
}
|
|
26994
27077
|
|
|
26995
27078
|
function zeroFillFinders(matrix) {
|
|
26996
27079
|
matrix = structuredClone(matrix);
|
|
@@ -27038,7 +27121,11 @@
|
|
|
27038
27121
|
if (options.logo && options.logoWidth && options.logoHeight && !options.noExcavate) {
|
|
27039
27122
|
matrix = clearMatrixCenter(matrix, options.logoWidth, options.logoHeight);
|
|
27040
27123
|
}
|
|
27041
|
-
|
|
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 });
|
|
27042
27129
|
}
|
|
27043
27130
|
function colorToRGB(color) {
|
|
27044
27131
|
if (typeof color === "string") {
|
|
@@ -27079,15 +27166,20 @@
|
|
|
27079
27166
|
page.getContentStream = page.prevGetContentStream;
|
|
27080
27167
|
page.contentStream.push = page.contentStream.prevPush;
|
|
27081
27168
|
}
|
|
27082
|
-
async function PDF({ matrix, margin, logo, logoWidth, logoHeight, color, bgColor, borderRadius, cornerMode, finderOuterShape, finderInnerShape, finderColor, }) {
|
|
27169
|
+
async function PDF({ matrix, margin, logo, logoWidth, logoHeight, color, bgColor, borderRadius, cornerMode, finderOuterShape, finderInnerShape, finderColor, labelLayout, }) {
|
|
27083
27170
|
const size = 9;
|
|
27084
27171
|
const marginPx = margin * size;
|
|
27085
27172
|
const matrixSizePx = matrix.length * size;
|
|
27086
27173
|
const imageSizePx = matrixSizePx + 2 * marginPx;
|
|
27174
|
+
const totalWidth = labelLayout?.totalWidth ?? imageSizePx;
|
|
27175
|
+
const totalHeight = labelLayout?.totalHeight ?? imageSizePx;
|
|
27087
27176
|
const document = await PDFDocument.create();
|
|
27088
|
-
const page = document.addPage([
|
|
27089
|
-
page.
|
|
27090
|
-
|
|
27177
|
+
const page = document.addPage([totalWidth, totalHeight]);
|
|
27178
|
+
page.drawRectangle({
|
|
27179
|
+
x: 0,
|
|
27180
|
+
y: 0,
|
|
27181
|
+
width: totalWidth,
|
|
27182
|
+
height: totalHeight,
|
|
27091
27183
|
color: rgb(...colorToRGB(bgColor)),
|
|
27092
27184
|
});
|
|
27093
27185
|
page.moveTo(0, page.getHeight());
|
|
@@ -27133,8 +27225,50 @@
|
|
|
27133
27225
|
height: logoHeightPx,
|
|
27134
27226
|
});
|
|
27135
27227
|
}
|
|
27228
|
+
if (labelLayout) {
|
|
27229
|
+
await drawPDFLabel(document, page, labelLayout, totalHeight);
|
|
27230
|
+
}
|
|
27136
27231
|
return document.save();
|
|
27137
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
|
+
}
|
|
27138
27272
|
|
|
27139
27273
|
exports.getPDF = getPDF;
|
|
27140
27274
|
|