somdigi-qr-generator 1.0.3 → 1.0.5
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/index.js +2 -2
- package/package.json +1 -1
- package/src/core.js +93 -67
- package/src/svg-helper.js +0 -49
package/index.js
CHANGED
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -1,77 +1,103 @@
|
|
|
1
1
|
const QRCode = require("qrcode");
|
|
2
2
|
const sharp = require("sharp");
|
|
3
|
-
const fs = require("fs/promises");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
extractPaths,
|
|
8
|
-
buildGradient,
|
|
9
|
-
pathsToDots,
|
|
10
|
-
insertLogo
|
|
11
|
-
} = require("./svg-helper");
|
|
12
|
-
|
|
13
|
-
class generateQr {
|
|
14
|
-
static async toSVG(text, options = {}) {
|
|
15
|
-
const rawSVG = await QRCode.toString(text, {
|
|
16
|
-
type: "svg",
|
|
17
|
-
errorCorrectionLevel: "H",
|
|
18
|
-
margin: options.margin ?? 1
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const sizeMatch = rawSVG.match(/viewBox="([^"]+)"/);
|
|
22
|
-
const viewBox = sizeMatch ? sizeMatch[1] : "0 0 100 100";
|
|
23
|
-
|
|
24
|
-
const paths = extractPaths(rawSVG);
|
|
25
|
-
|
|
26
|
-
const gradientId = "qrGradient";
|
|
27
|
-
const useGradient = options.gradient?.length > 1;
|
|
28
|
-
|
|
29
|
-
const fill = useGradient
|
|
30
|
-
? `url(#${gradientId})`
|
|
31
|
-
: options.color ?? "#000";
|
|
32
|
-
|
|
33
|
-
const defs = useGradient
|
|
34
|
-
? buildGradient(gradientId, options.gradient)
|
|
35
|
-
: "";
|
|
36
|
-
|
|
37
|
-
const dots = pathsToDots(
|
|
38
|
-
paths,
|
|
39
|
-
options.radius ?? 1.4,
|
|
40
|
-
fill
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const logo = options.logo
|
|
44
|
-
? insertLogo(options.logo, options.logoSize ?? 0.25)
|
|
45
|
-
: "";
|
|
46
|
-
|
|
47
|
-
// 🔒 SVG FINAL — VALID 100%
|
|
48
|
-
return `
|
|
49
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">
|
|
50
|
-
${defs}
|
|
51
|
-
${dots}
|
|
52
|
-
${logo}
|
|
53
|
-
</svg>`;
|
|
54
|
-
}
|
|
55
3
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
4
|
+
class QRGenerator {
|
|
5
|
+
/**
|
|
6
|
+
* Generate QR Buffer
|
|
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
|
+
}
|
|
64
18
|
|
|
65
|
-
|
|
19
|
+
// 🔵 DEFAULT QR (FASTEST)
|
|
20
|
+
if (!options.type || options.type === "default") {
|
|
21
|
+
return QRCode.toBuffer(options.data, {
|
|
22
|
+
width: options.size ?? 512,
|
|
23
|
+
margin: 2
|
|
24
|
+
});
|
|
25
|
+
}
|
|
66
26
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await fs.writeFile(fullPath, await this.toPNG(text, options));
|
|
27
|
+
// 🔵 ROUNDED QR
|
|
28
|
+
if (options.type === "rounded") {
|
|
29
|
+
return this._rounded(options);
|
|
71
30
|
}
|
|
72
31
|
|
|
73
|
-
|
|
32
|
+
throw new Error("invalid type");
|
|
74
33
|
}
|
|
34
|
+
|
|
35
|
+
static async _rounded(options) {
|
|
36
|
+
const rawSVG = await QRCode.toString(options.data, {
|
|
37
|
+
type: "svg",
|
|
38
|
+
errorCorrectionLevel: "H",
|
|
39
|
+
margin: 1
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const viewBox = rawSVG.match(/viewBox="([^"]+)"/)[1];
|
|
43
|
+
const paths = rawSVG.match(/<path[^>]*>/g) || [];
|
|
44
|
+
|
|
45
|
+
const useGradient = options.gradient?.length > 1;
|
|
46
|
+
const fill = useGradient ? "url(#g)" : "#000";
|
|
47
|
+
|
|
48
|
+
const defs = useGradient
|
|
49
|
+
? `
|
|
50
|
+
<defs>
|
|
51
|
+
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
52
|
+
${options.gradient
|
|
53
|
+
.map(
|
|
54
|
+
(c, i) =>
|
|
55
|
+
`<stop offset="${(i / (options.gradient.length - 1)) * 100}%" stop-color="${c}" />`
|
|
56
|
+
)
|
|
57
|
+
.join("")}
|
|
58
|
+
</linearGradient>
|
|
59
|
+
</defs>`
|
|
60
|
+
: "";
|
|
61
|
+
|
|
62
|
+
const dots = paths
|
|
63
|
+
.map(p => {
|
|
64
|
+
const m = p.match(/M(\d+) (\d+)/);
|
|
65
|
+
if (!m) return "";
|
|
66
|
+
return `<circle cx="${+m[1] + 0.5}" cy="${+m[2] + 0.5}" r="0.45" fill="${fill}" />`;
|
|
67
|
+
})
|
|
68
|
+
.join("");
|
|
69
|
+
|
|
70
|
+
const logo = options.logo
|
|
71
|
+
? `
|
|
72
|
+
<image
|
|
73
|
+
href="${options.logo}"
|
|
74
|
+
x="35%" y="35%"
|
|
75
|
+
width="30%" height="30%"
|
|
76
|
+
preserveAspectRatio="xMidYMid meet"
|
|
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}">
|
|
85
|
+
|
|
86
|
+
<!-- BACKGROUND WAJIB -->
|
|
87
|
+
<rect width="100%" height="100%" fill="#fff"/>
|
|
88
|
+
|
|
89
|
+
${defs}
|
|
90
|
+
${dots}
|
|
91
|
+
${logo}
|
|
92
|
+
</svg>`;
|
|
93
|
+
|
|
94
|
+
return sharp(Buffer.from(svg))
|
|
95
|
+
.png({
|
|
96
|
+
background: "#ffffff"
|
|
97
|
+
})
|
|
98
|
+
.toBuffer();
|
|
99
|
+
}
|
|
100
|
+
|
|
75
101
|
}
|
|
76
102
|
|
|
77
|
-
module.exports =
|
|
103
|
+
module.exports = QRGenerator;
|
package/src/svg-helper.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
function extractPaths(svg) {
|
|
2
|
-
return svg.match(/<path[^>]*>/g) || [];
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function buildGradient(id, colors) {
|
|
6
|
-
return `
|
|
7
|
-
<defs>
|
|
8
|
-
<linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
9
|
-
${colors
|
|
10
|
-
.map(
|
|
11
|
-
(c, i) =>
|
|
12
|
-
`<stop offset="${(i / (colors.length - 1)) * 100}%" stop-color="${c}" />`
|
|
13
|
-
)
|
|
14
|
-
.join("")}
|
|
15
|
-
</linearGradient>
|
|
16
|
-
</defs>`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function pathsToDots(paths, radius, fill) {
|
|
20
|
-
return paths
|
|
21
|
-
.map(p => {
|
|
22
|
-
const m = p.match(/M(\d+) (\d+)/);
|
|
23
|
-
if (!m) return "";
|
|
24
|
-
const x = Number(m[1]) + radius;
|
|
25
|
-
const y = Number(m[2]) + radius;
|
|
26
|
-
return `<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" />`;
|
|
27
|
-
})
|
|
28
|
-
.join("");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function insertLogo(logo, size = 0.25) {
|
|
32
|
-
const percent = size * 100;
|
|
33
|
-
return `
|
|
34
|
-
<image
|
|
35
|
-
href="${logo}"
|
|
36
|
-
x="${50 - percent / 2}%"
|
|
37
|
-
y="${50 - percent / 2}%"
|
|
38
|
-
width="${percent}%"
|
|
39
|
-
height="${percent}%"
|
|
40
|
-
preserveAspectRatio="xMidYMid meet"
|
|
41
|
-
/>`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
module.exports = {
|
|
45
|
-
extractPaths,
|
|
46
|
-
buildGradient,
|
|
47
|
-
pathsToDots,
|
|
48
|
-
insertLogo
|
|
49
|
-
};
|