somdigi-qr-generator 1.0.0 → 1.0.2

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 CHANGED
@@ -1,7 +1,5 @@
1
- const { generateQR } = require("./src/core");
2
- const presets = require("./src/presets")
1
+ const generateQR = require("./src/core");
3
2
 
4
3
  module.exports = {
5
- generateQR,
6
- presets
4
+ generateQR
7
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somdigi-qr-generator",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -10,8 +10,7 @@
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "dependencies": {
13
- "canvas": "^3.2.0",
14
- "jsdom": "^26.1.0",
15
- "qr-code-styling": "^1.9.2"
13
+ "qrcode": "^1.5.4",
14
+ "sharp": "^0.34.5"
16
15
  }
17
16
  }
package/src/core.js CHANGED
@@ -1,61 +1,77 @@
1
- const QRCodeStyling = require("qr-code-styling");
2
- const { JSDOM } = require("jsdom");
3
- const canvas = require("canvas");
1
+ const QRCode = require("qrcode");
2
+ const sharp = require("sharp");
3
+ const fs = require("fs/promises");
4
+ const path = require("path");
4
5
 
5
- let isSetup = false;
6
+ const {
7
+ extractPaths,
8
+ buildGradient,
9
+ pathsToDots,
10
+ insertLogo
11
+ } = require("./svg-helper");
6
12
 
7
- function setupNodeCanvas() {
8
- if (isSetup) return;
13
+ class FastDotQR {
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
+ });
9
20
 
10
- const { window } = new JSDOM();
11
- global.window = window;
12
- global.document = window.document;
13
- global.Image = canvas.Image;
14
- global.HTMLCanvasElement = canvas.Canvas;
21
+ const sizeMatch = rawSVG.match(/viewBox="([^"]+)"/);
22
+ const viewBox = sizeMatch ? sizeMatch[1] : "0 0 100 100";
15
23
 
16
- isSetup = true;
17
- }
24
+ const paths = extractPaths(rawSVG);
18
25
 
19
- async function generateQR(options) {
20
- if (!options || !options.data) {
21
- throw new Error("options.data is required");
22
- }
26
+ const gradientId = "qrGradient";
27
+ const useGradient = options.gradient?.length > 1;
23
28
 
24
- setupNodeCanvas();
29
+ const fill = useGradient
30
+ ? `url(#${gradientId})`
31
+ : options.color ?? "#000";
25
32
 
26
- const qr = new QRCodeStyling({
27
- width: options.size || 300,
28
- height: options.size || 300,
29
- data: options.data,
30
- margin: options.margin ?? 10,
33
+ const defs = useGradient
34
+ ? buildGradient(gradientId, options.gradient)
35
+ : "";
31
36
 
32
- dotsOptions: {
33
- type: "dots",
34
- gradient: options.gradient
35
- },
37
+ const dots = pathsToDots(
38
+ paths,
39
+ options.radius ?? 1.4,
40
+ fill
41
+ );
36
42
 
37
- cornersSquareOptions: {
38
- type: "extra-rounded",
39
- gradient: options.gradient
40
- },
43
+ const logo = options.logo
44
+ ? insertLogo(options.logo, options.logoSize ?? 0.25)
45
+ : "";
41
46
 
42
- cornersDotOptions: {
43
- type: "dot",
44
- color: "#000000"
45
- },
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
+ }
46
55
 
47
- backgroundOptions: {
48
- color: options.background || "#ffffff"
49
- },
56
+ static async toPNG(text, options = {}) {
57
+ const svg = await this.toSVG(text, options);
58
+ return sharp(Buffer.from(svg)).png().toBuffer();
59
+ }
50
60
 
51
- qrOptions: {
52
- errorCorrectionLevel: "H"
61
+ static async toFile(text, filename, options = {}) {
62
+ const dir = path.join(process.cwd(), "storage/qr-codes");
63
+ await fs.mkdir(dir, { recursive: true });
64
+
65
+ const fullPath = path.join(dir, filename);
66
+
67
+ if (filename.endsWith(".svg")) {
68
+ await fs.writeFile(fullPath, await this.toSVG(text, options));
69
+ } else {
70
+ await fs.writeFile(fullPath, await this.toPNG(text, options));
53
71
  }
54
- });
55
72
 
56
- return qr.getRawData(options.format || "png");
73
+ return fullPath;
74
+ }
57
75
  }
58
76
 
59
- module.exports = {
60
- generateQR
61
- };
77
+ module.exports = FastDotQR;
@@ -0,0 +1,49 @@
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
+ };
package/src/presets.js DELETED
@@ -1,48 +0,0 @@
1
- const purpleBlue = {
2
- type: "linear",
3
- rotation: Math.PI / 4,
4
- colorStops: [
5
- { offset: 0, color: "#7F00FF" },
6
- { offset: 1, color: "#00C6FF" }
7
- ]
8
- };
9
-
10
- const sunset = {
11
- type: "linear",
12
- rotation: Math.PI / 2,
13
- colorStops: [
14
- { offset: 0, color: "#FF512F" },
15
- { offset: 1, color: "#F09819" }
16
- ]
17
- };
18
- const black = {
19
- type: "linear",
20
- rotation: Math.PI / 2,
21
- colorStops: [
22
- { offset: 0, color: "#000000" },
23
- { offset: 1, color: "#000000" }
24
- ]
25
- };
26
-
27
- const generatePresets = ({
28
- type = "linear",
29
- rotation = Math.PI / 4,
30
- colorPrimary = "#000000",
31
- colorsSecondary = "#000000"
32
- }) => {
33
- return{
34
- type,
35
- rotation,
36
- colorStops: [
37
- { offset: 0, color: colorPrimary },
38
- { offset: 1, color: colorsSecondary }
39
- ]
40
- }
41
- }
42
-
43
- module.exports = {
44
- purpleBlue,
45
- sunset,
46
- black,
47
- generatePresets
48
- };