somdigi-qr-generator 1.0.5 → 1.0.7
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/package.json +1 -1
- package/src/core.js +95 -66
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -3,20 +3,19 @@ const sharp = require("sharp");
|
|
|
3
3
|
|
|
4
4
|
class QRGenerator {
|
|
5
5
|
/**
|
|
6
|
-
* Generate QR Buffer
|
|
6
|
+
* Generate QR Code (Buffer PNG)
|
|
7
7
|
* @param {Object} options
|
|
8
8
|
* @param {string} options.data (required)
|
|
9
9
|
* @param {"default"|"rounded"} options.type
|
|
10
10
|
* @param {number} options.size
|
|
11
11
|
* @param {string[]} options.gradient
|
|
12
|
-
* @param {string} options.logo (url
|
|
12
|
+
* @param {string} options.logo (url/base64)
|
|
13
13
|
*/
|
|
14
14
|
static async generate(options) {
|
|
15
15
|
if (!options || !options.data) {
|
|
16
16
|
throw new Error("data is required");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// 🔵 DEFAULT QR (FASTEST)
|
|
20
19
|
if (!options.type || options.type === "default") {
|
|
21
20
|
return QRCode.toBuffer(options.data, {
|
|
22
21
|
width: options.size ?? 512,
|
|
@@ -24,80 +23,110 @@ class QRGenerator {
|
|
|
24
23
|
});
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
// 🔵 ROUNDED QR
|
|
28
26
|
if (options.type === "rounded") {
|
|
29
|
-
return this
|
|
27
|
+
return this.#generateRounded(options);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
throw new Error("invalid type");
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const svg = `
|
|
81
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
82
|
-
width="${options.size ?? 512}"
|
|
83
|
-
height="${options.size ?? 512}"
|
|
84
|
-
viewBox="${viewBox}">
|
|
33
|
+
// ===============================
|
|
34
|
+
// ROUNDED QR CORE
|
|
35
|
+
// ===============================
|
|
36
|
+
static async #generateRounded({
|
|
37
|
+
data,
|
|
38
|
+
size = 512,
|
|
39
|
+
gradient,
|
|
40
|
+
logo
|
|
41
|
+
}) {
|
|
42
|
+
const qr = QRCode.create(data, {
|
|
43
|
+
errorCorrectionLevel: "H"
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const cells = qr.modules;
|
|
47
|
+
const count = cells.size;
|
|
48
|
+
|
|
49
|
+
const useGradient = Array.isArray(gradient) && gradient.length > 1;
|
|
50
|
+
const fill = useGradient ? "url(#g)" : "#000";
|
|
51
|
+
|
|
52
|
+
// --- helpers ---
|
|
53
|
+
const isFinder = (x, y) =>
|
|
54
|
+
(x < 7 && y < 7) ||
|
|
55
|
+
(x >= count - 7 && y < 7) ||
|
|
56
|
+
(x < 7 && y >= count - 7);
|
|
57
|
+
|
|
58
|
+
const renderFinder = (x, y) => `
|
|
59
|
+
<g>
|
|
60
|
+
<circle cx="${x + 3.5}" cy="${y + 3.5}" r="3.5" fill="${fill}"/>
|
|
61
|
+
<circle cx="${x + 3.5}" cy="${y + 3.5}" r="2.3" fill="#fff"/>
|
|
62
|
+
<circle cx="${x + 3.5}" cy="${y + 3.5}" r="1.3" fill="${fill}"/>
|
|
63
|
+
</g>
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// --- dots ---
|
|
67
|
+
const dots = [];
|
|
68
|
+
for (let y = 0; y < count; y++) {
|
|
69
|
+
for (let x = 0; x < count; x++) {
|
|
70
|
+
if (!cells.get(x, y)) continue;
|
|
71
|
+
if (isFinder(x, y)) continue;
|
|
72
|
+
|
|
73
|
+
dots.push(
|
|
74
|
+
`<circle cx="${x + 0.5}" cy="${y + 0.5}" r="0.45" fill="${fill}"/>`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
85
78
|
|
|
86
|
-
|
|
79
|
+
// --- finders ---
|
|
80
|
+
const finders = [
|
|
81
|
+
renderFinder(0, 0),
|
|
82
|
+
renderFinder(count - 7, 0),
|
|
83
|
+
renderFinder(0, count - 7)
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// --- gradient defs ---
|
|
87
|
+
const defs = useGradient
|
|
88
|
+
? `
|
|
89
|
+
<defs>
|
|
90
|
+
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
91
|
+
${gradient
|
|
92
|
+
.map(
|
|
93
|
+
(c, i) =>
|
|
94
|
+
`<stop offset="${(i / (gradient.length - 1)) * 100}%" stop-color="${c}"/>`
|
|
95
|
+
)
|
|
96
|
+
.join("")}
|
|
97
|
+
</linearGradient>
|
|
98
|
+
</defs>`
|
|
99
|
+
: "";
|
|
100
|
+
|
|
101
|
+
// --- logo ---
|
|
102
|
+
const logoSvg = logo
|
|
103
|
+
? `
|
|
104
|
+
<image
|
|
105
|
+
href="${logo}"
|
|
106
|
+
x="35%" y="35%"
|
|
107
|
+
width="30%" height="30%"
|
|
108
|
+
preserveAspectRatio="xMidYMid meet"
|
|
109
|
+
/>`
|
|
110
|
+
: "";
|
|
111
|
+
|
|
112
|
+
// --- final svg ---
|
|
113
|
+
const svg = `
|
|
114
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
115
|
+
width="${size}"
|
|
116
|
+
height="${size}"
|
|
117
|
+
viewBox="0 0 ${count} ${count}">
|
|
118
|
+
|
|
87
119
|
<rect width="100%" height="100%" fill="#fff"/>
|
|
88
|
-
|
|
89
120
|
${defs}
|
|
90
|
-
${
|
|
91
|
-
${
|
|
121
|
+
${finders.join("")}
|
|
122
|
+
${dots.join("")}
|
|
123
|
+
${logoSvg}
|
|
92
124
|
</svg>`;
|
|
93
125
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.toBuffer();
|
|
99
|
-
}
|
|
100
|
-
|
|
126
|
+
return sharp(Buffer.from(svg))
|
|
127
|
+
.png({ background: "#fff" })
|
|
128
|
+
.toBuffer();
|
|
129
|
+
}
|
|
101
130
|
}
|
|
102
131
|
|
|
103
132
|
module.exports = QRGenerator;
|