somdigi-qr-generator 1.0.0 → 1.0.1

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.1",
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,80 @@
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
+ buildGradient,
8
+ squareToDot,
9
+ insertLogo
10
+ } = require("./svg-helper");
6
11
 
7
- function setupNodeCanvas() {
8
- if (isSetup) return;
12
+ class FastDotQR {
13
+ static async toSVG(text, options = {}) {
14
+ if (!text) throw new Error("text required");
9
15
 
10
- const { window } = new JSDOM();
11
- global.window = window;
12
- global.document = window.document;
13
- global.Image = canvas.Image;
14
- global.HTMLCanvasElement = canvas.Canvas;
16
+ const rawSVG = await QRCode.toString(text, {
17
+ type: "svg",
18
+ errorCorrectionLevel: "H",
19
+ margin: options.margin ?? 1
20
+ });
15
21
 
16
- isSetup = true;
17
- }
22
+ const gradientId = "qrGradient";
23
+ const gradient =
24
+ options.gradient?.length > 1
25
+ ? buildGradient(gradientId, options.gradient)
26
+ : "";
18
27
 
19
- async function generateQR(options) {
20
- if (!options || !options.data) {
21
- throw new Error("options.data is required");
22
- }
28
+ const fill =
29
+ options.gradient?.length > 1
30
+ ? `url(#${gradientId})`
31
+ : options.color ?? "#000";
23
32
 
24
- setupNodeCanvas();
33
+ let content = squareToDot(
34
+ rawSVG,
35
+ options.radius ?? 1.4,
36
+ fill
37
+ );
25
38
 
26
- const qr = new QRCodeStyling({
27
- width: options.size || 300,
28
- height: options.size || 300,
29
- data: options.data,
30
- margin: options.margin ?? 10,
39
+ if (options.logo) {
40
+ content = insertLogo(
41
+ content,
42
+ options.logo,
43
+ options.logoSize ?? 0.25
44
+ );
45
+ }
31
46
 
32
- dotsOptions: {
33
- type: "dots",
34
- gradient: options.gradient
35
- },
47
+ return rawSVG
48
+ .replace("</svg>", "")
49
+ .replace(
50
+ "<svg",
51
+ `<svg xmlns="http://www.w3.org/2000/svg">`
52
+ )
53
+ .replace(">", `>${gradient}`)
54
+ .replace("</svg>", `${content}</svg>`);
55
+ }
36
56
 
37
- cornersSquareOptions: {
38
- type: "extra-rounded",
39
- gradient: options.gradient
40
- },
57
+ static async toPNG(text, options = {}) {
58
+ const svg = await this.toSVG(text, options);
59
+ return sharp(Buffer.from(svg)).png().toBuffer();
60
+ }
41
61
 
42
- cornersDotOptions: {
43
- type: "dot",
44
- color: "#000000"
45
- },
62
+ static async toFile(text, filename, options = {}) {
63
+ const dir = path.join(process.cwd(), "storage/qr-codes");
64
+ await fs.mkdir(dir, { recursive: true });
46
65
 
47
- backgroundOptions: {
48
- color: options.background || "#ffffff"
49
- },
66
+ const fullPath = path.join(dir, filename);
50
67
 
51
- qrOptions: {
52
- errorCorrectionLevel: "H"
68
+ if (filename.endsWith(".svg")) {
69
+ const svg = await this.toSVG(text, options);
70
+ await fs.writeFile(fullPath, svg);
71
+ } else {
72
+ const buffer = await this.toPNG(text, options);
73
+ await fs.writeFile(fullPath, buffer);
53
74
  }
54
- });
55
75
 
56
- return qr.getRawData(options.format || "png");
76
+ return fullPath;
77
+ }
57
78
  }
58
79
 
59
- module.exports = {
60
- generateQR
61
- };
80
+ module.exports = FastDotQR;
@@ -0,0 +1,50 @@
1
+ function buildGradient(id, colors) {
2
+ return `
3
+ <defs>
4
+ <linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
5
+ ${colors
6
+ .map(
7
+ (c, i) =>
8
+ `<stop offset="${(i / (colors.length - 1)) * 100}%" stop-color="${c}" />`
9
+ )
10
+ .join("")}
11
+ </linearGradient>
12
+ </defs>
13
+ `;
14
+ }
15
+
16
+ function squareToDot(svg, radius, fill) {
17
+ return svg
18
+ .replace(/<path[^>]*>/g, match => {
19
+ const m = match.match(/M(\d+) (\d+)/);
20
+ if (!m) return "";
21
+ const x = Number(m[1]) + radius;
22
+ const y = Number(m[2]) + radius;
23
+ return `<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" />`;
24
+ })
25
+ .replace(/<svg[^>]*>/, s => s.replace(">", ` fill="none">`))
26
+ .replace(/<\/svg>/, "");
27
+ }
28
+
29
+ function insertLogo(svg, logo, sizePercent = 0.25) {
30
+ const size = `${sizePercent * 100}%`;
31
+
32
+ return `
33
+ ${svg}
34
+ <image
35
+ href="${logo}"
36
+ x="50%"
37
+ y="50%"
38
+ width="${size}"
39
+ height="${size}"
40
+ transform="translate(-${sizePercent * 50}%, -${sizePercent * 50}%)"
41
+ preserveAspectRatio="xMidYMid meet"
42
+ />
43
+ `;
44
+ }
45
+
46
+ module.exports = {
47
+ buildGradient,
48
+ squareToDot,
49
+ insertLogo
50
+ };
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
- };