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