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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/core.js +95 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somdigi-qr-generator",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
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 / base64)
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._rounded(options);
27
+ return this.#generateRounded(options);
30
28
  }
31
29
 
32
30
  throw new Error("invalid type");
33
31
  }
34
32
 
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}">
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
- <!-- BACKGROUND WAJIB -->
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
- ${dots}
91
- ${logo}
121
+ ${finders.join("")}
122
+ ${dots.join("")}
123
+ ${logoSvg}
92
124
  </svg>`;
93
125
 
94
- return sharp(Buffer.from(svg))
95
- .png({
96
- background: "#ffffff"
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;