somdigi-qr-generator 1.0.7 → 1.0.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/core.js +58 -101
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somdigi-qr-generator",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
package/src/core.js CHANGED
@@ -2,131 +2,88 @@ 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
- if (options.type === "rounded") {
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 = 512,
39
- gradient,
40
- logo
8
+ size = 1024,
9
+ margin = 1,
10
+ dotRadius = 0.48,
11
+ gradient = null,
12
+ errorCorrectionLevel = "M"
41
13
  }) {
42
- const qr = QRCode.create(data, {
43
- errorCorrectionLevel: "H"
44
- });
14
+ if (!data) throw new Error("data required");
45
15
 
16
+ const qr = QRCode.create(data, { errorCorrectionLevel });
46
17
  const cells = qr.modules;
47
18
  const count = cells.size;
48
19
 
49
- const useGradient = Array.isArray(gradient) && gradient.length > 1;
50
- const fill = useGradient ? "url(#g)" : "#000";
20
+ const viewSize = count + margin * 2;
21
+ const fill = gradient ? "url(#g)" : "#000";
51
22
 
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
23
  const dots = [];
24
+
68
25
  for (let y = 0; y < count; y++) {
69
26
  for (let x = 0; x < count; x++) {
70
27
  if (!cells.get(x, y)) continue;
71
- if (isFinder(x, y)) continue;
28
+ if (this.#isFinder(x, y, count)) continue;
72
29
 
73
30
  dots.push(
74
- `<circle cx="${x + 0.5}" cy="${y + 0.5}" r="0.45" fill="${fill}"/>`
31
+ `<circle cx="${x + margin + 0.5}" cy="${y + margin + 0.5}" r="${dotRadius}" fill="${fill}" />`
75
32
  );
76
33
  }
77
34
  }
78
35
 
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
36
  const svg = `
114
- <svg xmlns="http://www.w3.org/2000/svg"
115
- width="${size}"
116
- height="${size}"
117
- viewBox="0 0 ${count} ${count}">
118
-
119
- <rect width="100%" height="100%" fill="#fff"/>
120
- ${defs}
121
- ${finders.join("")}
37
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${viewSize} ${viewSize}">
38
+ ${gradient ? this.#gradientDef(gradient) : ""}
39
+ <rect width="100%" height="100%" fill="white"/>
40
+
41
+ ${this.#finder(viewSize, margin, fill)}
42
+
122
43
  ${dots.join("")}
123
- ${logoSvg}
124
- </svg>`;
44
+ </svg>
45
+ `;
125
46
 
126
47
  return sharp(Buffer.from(svg))
127
- .png({ background: "#fff" })
48
+ .resize(size, size)
49
+ .png({ quality: 100 })
128
50
  .toBuffer();
129
51
  }
52
+
53
+ // =============================
54
+ // FINDER (SOLID + OUTLINE)
55
+ // =============================
56
+ static #finder(_, margin, fill) {
57
+ const p = [
58
+ { x: 0, y: 0 },
59
+ { x: 21, y: 0 },
60
+ { x: 0, y: 21 }
61
+ ];
62
+
63
+ return p.map(v => `
64
+ <rect x="${v.x + margin}" y="${v.y + margin}" width="7" height="7" rx="2" fill="${fill}"/>
65
+ <rect x="${v.x + margin + 1}" y="${v.y + margin + 1}" width="5" height="5" rx="1.5" fill="white"/>
66
+ <rect x="${v.x + margin + 2}" y="${v.y + margin + 2}" width="3" height="3" rx="1" fill="${fill}"/>
67
+ `).join("");
68
+ }
69
+
70
+ static #isFinder(x, y, size) {
71
+ return (
72
+ (x < 7 && y < 7) ||
73
+ (x >= size - 7 && y < 7) ||
74
+ (x < 7 && y >= size - 7)
75
+ );
76
+ }
77
+
78
+ static #gradientDef([from, to]) {
79
+ return `
80
+ <defs>
81
+ <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
82
+ <stop offset="0%" stop-color="${from}" />
83
+ <stop offset="100%" stop-color="${to}" />
84
+ </linearGradient>
85
+ </defs>`;
86
+ }
130
87
  }
131
88
 
132
89
  module.exports = QRGenerator;