isahaq-barcode 1.0.0 → 1.2.0
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/.eslintrc.js +29 -29
- package/.prettierignore +11 -0
- package/.prettierrc +11 -0
- package/README.md +596 -549
- package/bin/generate.js +274 -274
- package/eslint.config.js +66 -68
- package/examples/basic-usage.js +58 -58
- package/examples/batch-example.json +56 -56
- package/examples/express-server.js +163 -159
- package/index.js +134 -134
- package/jest.config.js +13 -13
- package/package.json +27 -15
- package/src/builders/QrCodeBuilder.js +401 -401
- package/src/renderers/HTMLRenderer.js +212 -212
- package/src/renderers/PDFRenderer.js +184 -184
- package/src/renderers/PNGRenderer.js +123 -123
- package/src/renderers/RenderFormats.js +87 -87
- package/src/renderers/SVGRenderer.js +221 -221
- package/src/services/BarcodeService.js +175 -175
- package/src/types/BarcodeTypes.js +195 -195
- package/src/validators/Validator.js +352 -352
- package/tests/BarcodeGenerator.test.js +187 -187
- package/tests/BarcodeService.test.js +76 -76
- package/tests/QrCodeBuilder.test.js +130 -130
- package/tests/setup.js +59 -59
|
@@ -1,401 +1,401 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QR Code Builder - Advanced QR code generation with logo and watermark support
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const QRCode = require('qrcode');
|
|
6
|
-
const { createCanvas, loadImage } = require('canvas');
|
|
7
|
-
const fs = require('fs').promises;
|
|
8
|
-
|
|
9
|
-
class QrCodeBuilder {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.options = {
|
|
12
|
-
data: '',
|
|
13
|
-
size: 300,
|
|
14
|
-
margin: 10,
|
|
15
|
-
errorCorrectionLevel: 'M',
|
|
16
|
-
foregroundColor: [0, 0, 0],
|
|
17
|
-
backgroundColor: [255, 255, 255],
|
|
18
|
-
logoPath: null,
|
|
19
|
-
logoSize: 60, // Percentage of QR code size
|
|
20
|
-
label: null,
|
|
21
|
-
labelFont: null,
|
|
22
|
-
labelFontSize: 16,
|
|
23
|
-
watermark: null,
|
|
24
|
-
watermarkPosition: 'center',
|
|
25
|
-
format: 'png',
|
|
26
|
-
...options,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create a new QR Code Builder instance
|
|
32
|
-
* @param {Object} options - Initial options
|
|
33
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
34
|
-
*/
|
|
35
|
-
static create(options = {}) {
|
|
36
|
-
return new QrCodeBuilder(options);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Set the data to encode
|
|
41
|
-
* @param {string} data - The data to encode
|
|
42
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
43
|
-
*/
|
|
44
|
-
data(data) {
|
|
45
|
-
this.options.data = data;
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Set the QR code size
|
|
51
|
-
* @param {number} size - Size in pixels
|
|
52
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
53
|
-
*/
|
|
54
|
-
size(size) {
|
|
55
|
-
this.options.size = size;
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Set the margin
|
|
61
|
-
* @param {number} margin - Margin in pixels
|
|
62
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
63
|
-
*/
|
|
64
|
-
margin(margin) {
|
|
65
|
-
this.options.margin = margin;
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Set the error correction level
|
|
71
|
-
* @param {string} level - Error correction level (L, M, Q, H)
|
|
72
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
73
|
-
*/
|
|
74
|
-
errorCorrectionLevel(level) {
|
|
75
|
-
this.options.errorCorrectionLevel = level;
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Set the foreground color
|
|
81
|
-
* @param {Array} color - RGB color array [r, g, b]
|
|
82
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
83
|
-
*/
|
|
84
|
-
foregroundColor(color) {
|
|
85
|
-
this.options.foregroundColor = color;
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Set the background color
|
|
91
|
-
* @param {Array} color - RGB color array [r, g, b]
|
|
92
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
93
|
-
*/
|
|
94
|
-
backgroundColor(color) {
|
|
95
|
-
this.options.backgroundColor = color;
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Set the logo path
|
|
101
|
-
* @param {string} logoPath - Path to logo image
|
|
102
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
103
|
-
*/
|
|
104
|
-
logoPath(logoPath) {
|
|
105
|
-
this.options.logoPath = logoPath;
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Set the logo size
|
|
111
|
-
* @param {number} logoSize - Logo size as percentage of QR code size
|
|
112
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
113
|
-
*/
|
|
114
|
-
logoSize(logoSize) {
|
|
115
|
-
this.options.logoSize = logoSize;
|
|
116
|
-
return this;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Set the label text
|
|
121
|
-
* @param {string} label - Label text
|
|
122
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
123
|
-
*/
|
|
124
|
-
label(label) {
|
|
125
|
-
this.options.label = label;
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Set the label font
|
|
131
|
-
* @param {string} fontPath - Path to font file
|
|
132
|
-
* @param {number} fontSize - Font size
|
|
133
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
134
|
-
*/
|
|
135
|
-
labelFont(fontPath, fontSize = 16) {
|
|
136
|
-
this.options.labelFont = fontPath;
|
|
137
|
-
this.options.labelFontSize = fontSize;
|
|
138
|
-
return this;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Set the watermark
|
|
143
|
-
* @param {string} watermark - Watermark text or image path
|
|
144
|
-
* @param {string} position - Watermark position
|
|
145
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
146
|
-
*/
|
|
147
|
-
watermark(watermark, position = 'center') {
|
|
148
|
-
this.options.watermark = watermark;
|
|
149
|
-
this.options.watermarkPosition = position;
|
|
150
|
-
return this;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Set the output format
|
|
155
|
-
* @param {string} format - Output format (png, svg, jpg)
|
|
156
|
-
* @returns {QrCodeBuilder} Builder instance
|
|
157
|
-
*/
|
|
158
|
-
format(format) {
|
|
159
|
-
this.options.format = format;
|
|
160
|
-
return this;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Build the QR code
|
|
165
|
-
* @returns {Object} QR code result
|
|
166
|
-
*/
|
|
167
|
-
build() {
|
|
168
|
-
return new QrCodeResult(this.options);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Generate QR code and return as buffer
|
|
173
|
-
* @returns {Promise<Buffer>} QR code buffer
|
|
174
|
-
*/
|
|
175
|
-
async generate() {
|
|
176
|
-
const result = this.build();
|
|
177
|
-
return await result.generate();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Generate QR code and save to file
|
|
182
|
-
* @param {string} filePath - Output file path
|
|
183
|
-
* @returns {Promise<void>}
|
|
184
|
-
*/
|
|
185
|
-
async saveToFile(filePath) {
|
|
186
|
-
const result = this.build();
|
|
187
|
-
return await result.saveToFile(filePath);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Generate QR code and return as data URI
|
|
192
|
-
* @returns {Promise<string>} Data URI
|
|
193
|
-
*/
|
|
194
|
-
async getDataUri() {
|
|
195
|
-
const result = this.build();
|
|
196
|
-
return await result.getDataUri();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* QR Code Result - Handles QR code generation and output
|
|
202
|
-
*/
|
|
203
|
-
class QrCodeResult {
|
|
204
|
-
constructor(options) {
|
|
205
|
-
this.options = options;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Generate the QR code
|
|
210
|
-
* @returns {Promise<Buffer>} QR code buffer
|
|
211
|
-
*/
|
|
212
|
-
async generate() {
|
|
213
|
-
try {
|
|
214
|
-
// Create canvas
|
|
215
|
-
const canvas = createCanvas(this.options.size, this.options.size);
|
|
216
|
-
const ctx = canvas.getContext('2d');
|
|
217
|
-
|
|
218
|
-
// Set background
|
|
219
|
-
ctx.fillStyle = this.rgbToHex(this.options.backgroundColor);
|
|
220
|
-
ctx.fillRect(0, 0, this.options.size, this.options.size);
|
|
221
|
-
|
|
222
|
-
// Generate QR code
|
|
223
|
-
const qrCodeDataURL = await QRCode.toDataURL(this.options.data, {
|
|
224
|
-
width: this.options.size - this.options.margin * 2,
|
|
225
|
-
margin: 0,
|
|
226
|
-
color: {
|
|
227
|
-
dark: this.rgbToHex(this.options.foregroundColor),
|
|
228
|
-
light: this.rgbToHex(this.options.backgroundColor),
|
|
229
|
-
},
|
|
230
|
-
errorCorrectionLevel: this.options.errorCorrectionLevel,
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Load QR code image
|
|
234
|
-
const qrCodeImage = await loadImage(qrCodeDataURL);
|
|
235
|
-
ctx.drawImage(qrCodeImage, this.options.margin, this.options.margin);
|
|
236
|
-
|
|
237
|
-
// Add logo if specified
|
|
238
|
-
if (this.options.logoPath) {
|
|
239
|
-
await this.addLogo(ctx);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Add watermark if specified
|
|
243
|
-
if (this.options.watermark) {
|
|
244
|
-
await this.addWatermark(ctx);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Add label if specified
|
|
248
|
-
if (this.options.label) {
|
|
249
|
-
this.addLabel(ctx);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Convert to buffer
|
|
253
|
-
return canvas.toBuffer(`image/${this.options.format}`);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
throw new Error(`QR code generation failed: ${error.message}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Add logo to QR code
|
|
261
|
-
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
262
|
-
*/
|
|
263
|
-
async addLogo(ctx) {
|
|
264
|
-
try {
|
|
265
|
-
const logoImage = await loadImage(this.options.logoPath);
|
|
266
|
-
const logoSize = (this.options.size * this.options.logoSize) / 100;
|
|
267
|
-
const logoX = (this.options.size - logoSize) / 2;
|
|
268
|
-
const logoY = (this.options.size - logoSize) / 2;
|
|
269
|
-
|
|
270
|
-
// Draw white background for logo
|
|
271
|
-
ctx.fillStyle = this.rgbToHex(this.options.backgroundColor);
|
|
272
|
-
ctx.fillRect(logoX - 5, logoY - 5, logoSize + 10, logoSize + 10);
|
|
273
|
-
|
|
274
|
-
// Draw logo
|
|
275
|
-
ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.warn(`Failed to add logo: ${error.message}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Add watermark to QR code
|
|
283
|
-
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
284
|
-
*/
|
|
285
|
-
async addWatermark(ctx) {
|
|
286
|
-
try {
|
|
287
|
-
// Check if watermark is an image path
|
|
288
|
-
if (
|
|
289
|
-
this.options.watermark.includes('.') &&
|
|
290
|
-
this.options.watermark.includes('/')
|
|
291
|
-
) {
|
|
292
|
-
const watermarkImage = await loadImage(this.options.watermark);
|
|
293
|
-
const watermarkSize = this.options.size / 4;
|
|
294
|
-
const position = this.getWatermarkPosition(watermarkSize);
|
|
295
|
-
|
|
296
|
-
ctx.drawImage(
|
|
297
|
-
watermarkImage,
|
|
298
|
-
position.x,
|
|
299
|
-
position.y,
|
|
300
|
-
watermarkSize,
|
|
301
|
-
watermarkSize
|
|
302
|
-
);
|
|
303
|
-
} else {
|
|
304
|
-
// Text watermark
|
|
305
|
-
ctx.font = `${this.options.labelFontSize}px Arial`;
|
|
306
|
-
ctx.fillStyle = this.rgbToHex(this.options.foregroundColor);
|
|
307
|
-
ctx.textAlign = 'center';
|
|
308
|
-
|
|
309
|
-
const position = this.getWatermarkPosition(0);
|
|
310
|
-
ctx.fillText(this.options.watermark, position.x, position.y);
|
|
311
|
-
}
|
|
312
|
-
} catch (error) {
|
|
313
|
-
console.warn(`Failed to add watermark: ${error.message}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Add label to QR code
|
|
319
|
-
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
320
|
-
*/
|
|
321
|
-
addLabel(ctx) {
|
|
322
|
-
ctx.font = `${this.options.labelFontSize}px Arial`;
|
|
323
|
-
ctx.fillStyle = this.rgbToHex(this.options.foregroundColor);
|
|
324
|
-
ctx.textAlign = 'center';
|
|
325
|
-
|
|
326
|
-
const labelY = this.options.size - 10;
|
|
327
|
-
ctx.fillText(this.options.label, this.options.size / 2, labelY);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Get watermark position
|
|
332
|
-
* @param {number} size - Watermark size
|
|
333
|
-
* @returns {Object} Position coordinates
|
|
334
|
-
*/
|
|
335
|
-
getWatermarkPosition(size) {
|
|
336
|
-
const positions = {
|
|
337
|
-
'top-left': { x: 10, y: 10 },
|
|
338
|
-
'top-right': { x: this.options.size - size - 10, y: 10 },
|
|
339
|
-
'bottom-left': { x: 10, y: this.options.size - size - 10 },
|
|
340
|
-
'bottom-right': {
|
|
341
|
-
x: this.options.size - size - 10,
|
|
342
|
-
y: this.options.size - size - 10,
|
|
343
|
-
},
|
|
344
|
-
center: {
|
|
345
|
-
x: (this.options.size - size) / 2,
|
|
346
|
-
y: (this.options.size - size) / 2,
|
|
347
|
-
},
|
|
348
|
-
'top-center': { x: (this.options.size - size) / 2, y: 10 },
|
|
349
|
-
'bottom-center': {
|
|
350
|
-
x: (this.options.size - size) / 2,
|
|
351
|
-
y: this.options.size - size - 10,
|
|
352
|
-
},
|
|
353
|
-
'left-center': { x: 10, y: (this.options.size - size) / 2 },
|
|
354
|
-
'right-center': {
|
|
355
|
-
x: this.options.size - size - 10,
|
|
356
|
-
y: (this.options.size - size) / 2,
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
return positions[this.options.watermarkPosition] || positions.center;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Convert RGB array to hex color
|
|
365
|
-
* @param {Array} rgb - RGB color array
|
|
366
|
-
* @returns {string} Hex color
|
|
367
|
-
*/
|
|
368
|
-
rgbToHex(rgb) {
|
|
369
|
-
return `#${rgb.map(
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Save QR code to file
|
|
374
|
-
* @param {string} filePath - Output file path
|
|
375
|
-
* @returns {Promise<void>}
|
|
376
|
-
*/
|
|
377
|
-
async saveToFile(filePath) {
|
|
378
|
-
const buffer = await this.generate();
|
|
379
|
-
await fs.writeFile(filePath, buffer);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Get QR code as data URI
|
|
384
|
-
* @returns {Promise<string>} Data URI
|
|
385
|
-
*/
|
|
386
|
-
async getDataUri() {
|
|
387
|
-
const buffer = await this.generate();
|
|
388
|
-
const base64 = buffer.toString('base64');
|
|
389
|
-
return `data:image/${this.options.format};base64,${base64}`;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Get QR code as string
|
|
394
|
-
* @returns {Promise<string>} QR code string
|
|
395
|
-
*/
|
|
396
|
-
async getString() {
|
|
397
|
-
return await this.generate();
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
module.exports = QrCodeBuilder;
|
|
1
|
+
/**
|
|
2
|
+
* QR Code Builder - Advanced QR code generation with logo and watermark support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const QRCode = require('qrcode');
|
|
6
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
|
|
9
|
+
class QrCodeBuilder {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.options = {
|
|
12
|
+
data: '',
|
|
13
|
+
size: 300,
|
|
14
|
+
margin: 10,
|
|
15
|
+
errorCorrectionLevel: 'M',
|
|
16
|
+
foregroundColor: [0, 0, 0],
|
|
17
|
+
backgroundColor: [255, 255, 255],
|
|
18
|
+
logoPath: null,
|
|
19
|
+
logoSize: 60, // Percentage of QR code size
|
|
20
|
+
label: null,
|
|
21
|
+
labelFont: null,
|
|
22
|
+
labelFontSize: 16,
|
|
23
|
+
watermark: null,
|
|
24
|
+
watermarkPosition: 'center',
|
|
25
|
+
format: 'png',
|
|
26
|
+
...options,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new QR Code Builder instance
|
|
32
|
+
* @param {Object} options - Initial options
|
|
33
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
34
|
+
*/
|
|
35
|
+
static create(options = {}) {
|
|
36
|
+
return new QrCodeBuilder(options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the data to encode
|
|
41
|
+
* @param {string} data - The data to encode
|
|
42
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
43
|
+
*/
|
|
44
|
+
data(data) {
|
|
45
|
+
this.options.data = data;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set the QR code size
|
|
51
|
+
* @param {number} size - Size in pixels
|
|
52
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
53
|
+
*/
|
|
54
|
+
size(size) {
|
|
55
|
+
this.options.size = size;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Set the margin
|
|
61
|
+
* @param {number} margin - Margin in pixels
|
|
62
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
63
|
+
*/
|
|
64
|
+
margin(margin) {
|
|
65
|
+
this.options.margin = margin;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set the error correction level
|
|
71
|
+
* @param {string} level - Error correction level (L, M, Q, H)
|
|
72
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
73
|
+
*/
|
|
74
|
+
errorCorrectionLevel(level) {
|
|
75
|
+
this.options.errorCorrectionLevel = level;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Set the foreground color
|
|
81
|
+
* @param {Array} color - RGB color array [r, g, b]
|
|
82
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
83
|
+
*/
|
|
84
|
+
foregroundColor(color) {
|
|
85
|
+
this.options.foregroundColor = color;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set the background color
|
|
91
|
+
* @param {Array} color - RGB color array [r, g, b]
|
|
92
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
93
|
+
*/
|
|
94
|
+
backgroundColor(color) {
|
|
95
|
+
this.options.backgroundColor = color;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set the logo path
|
|
101
|
+
* @param {string} logoPath - Path to logo image
|
|
102
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
103
|
+
*/
|
|
104
|
+
logoPath(logoPath) {
|
|
105
|
+
this.options.logoPath = logoPath;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Set the logo size
|
|
111
|
+
* @param {number} logoSize - Logo size as percentage of QR code size
|
|
112
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
113
|
+
*/
|
|
114
|
+
logoSize(logoSize) {
|
|
115
|
+
this.options.logoSize = logoSize;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set the label text
|
|
121
|
+
* @param {string} label - Label text
|
|
122
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
123
|
+
*/
|
|
124
|
+
label(label) {
|
|
125
|
+
this.options.label = label;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Set the label font
|
|
131
|
+
* @param {string} fontPath - Path to font file
|
|
132
|
+
* @param {number} fontSize - Font size
|
|
133
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
134
|
+
*/
|
|
135
|
+
labelFont(fontPath, fontSize = 16) {
|
|
136
|
+
this.options.labelFont = fontPath;
|
|
137
|
+
this.options.labelFontSize = fontSize;
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Set the watermark
|
|
143
|
+
* @param {string} watermark - Watermark text or image path
|
|
144
|
+
* @param {string} position - Watermark position
|
|
145
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
146
|
+
*/
|
|
147
|
+
watermark(watermark, position = 'center') {
|
|
148
|
+
this.options.watermark = watermark;
|
|
149
|
+
this.options.watermarkPosition = position;
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set the output format
|
|
155
|
+
* @param {string} format - Output format (png, svg, jpg)
|
|
156
|
+
* @returns {QrCodeBuilder} Builder instance
|
|
157
|
+
*/
|
|
158
|
+
format(format) {
|
|
159
|
+
this.options.format = format;
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Build the QR code
|
|
165
|
+
* @returns {Object} QR code result
|
|
166
|
+
*/
|
|
167
|
+
build() {
|
|
168
|
+
return new QrCodeResult(this.options);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate QR code and return as buffer
|
|
173
|
+
* @returns {Promise<Buffer>} QR code buffer
|
|
174
|
+
*/
|
|
175
|
+
async generate() {
|
|
176
|
+
const result = this.build();
|
|
177
|
+
return await result.generate();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generate QR code and save to file
|
|
182
|
+
* @param {string} filePath - Output file path
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async saveToFile(filePath) {
|
|
186
|
+
const result = this.build();
|
|
187
|
+
return await result.saveToFile(filePath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate QR code and return as data URI
|
|
192
|
+
* @returns {Promise<string>} Data URI
|
|
193
|
+
*/
|
|
194
|
+
async getDataUri() {
|
|
195
|
+
const result = this.build();
|
|
196
|
+
return await result.getDataUri();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* QR Code Result - Handles QR code generation and output
|
|
202
|
+
*/
|
|
203
|
+
class QrCodeResult {
|
|
204
|
+
constructor(options) {
|
|
205
|
+
this.options = options;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate the QR code
|
|
210
|
+
* @returns {Promise<Buffer>} QR code buffer
|
|
211
|
+
*/
|
|
212
|
+
async generate() {
|
|
213
|
+
try {
|
|
214
|
+
// Create canvas
|
|
215
|
+
const canvas = createCanvas(this.options.size, this.options.size);
|
|
216
|
+
const ctx = canvas.getContext('2d');
|
|
217
|
+
|
|
218
|
+
// Set background
|
|
219
|
+
ctx.fillStyle = this.rgbToHex(this.options.backgroundColor);
|
|
220
|
+
ctx.fillRect(0, 0, this.options.size, this.options.size);
|
|
221
|
+
|
|
222
|
+
// Generate QR code
|
|
223
|
+
const qrCodeDataURL = await QRCode.toDataURL(this.options.data, {
|
|
224
|
+
width: this.options.size - this.options.margin * 2,
|
|
225
|
+
margin: 0,
|
|
226
|
+
color: {
|
|
227
|
+
dark: this.rgbToHex(this.options.foregroundColor),
|
|
228
|
+
light: this.rgbToHex(this.options.backgroundColor),
|
|
229
|
+
},
|
|
230
|
+
errorCorrectionLevel: this.options.errorCorrectionLevel,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Load QR code image
|
|
234
|
+
const qrCodeImage = await loadImage(qrCodeDataURL);
|
|
235
|
+
ctx.drawImage(qrCodeImage, this.options.margin, this.options.margin);
|
|
236
|
+
|
|
237
|
+
// Add logo if specified
|
|
238
|
+
if (this.options.logoPath) {
|
|
239
|
+
await this.addLogo(ctx);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Add watermark if specified
|
|
243
|
+
if (this.options.watermark) {
|
|
244
|
+
await this.addWatermark(ctx);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add label if specified
|
|
248
|
+
if (this.options.label) {
|
|
249
|
+
this.addLabel(ctx);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Convert to buffer
|
|
253
|
+
return canvas.toBuffer(`image/${this.options.format}`);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw new Error(`QR code generation failed: ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Add logo to QR code
|
|
261
|
+
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
262
|
+
*/
|
|
263
|
+
async addLogo(ctx) {
|
|
264
|
+
try {
|
|
265
|
+
const logoImage = await loadImage(this.options.logoPath);
|
|
266
|
+
const logoSize = (this.options.size * this.options.logoSize) / 100;
|
|
267
|
+
const logoX = (this.options.size - logoSize) / 2;
|
|
268
|
+
const logoY = (this.options.size - logoSize) / 2;
|
|
269
|
+
|
|
270
|
+
// Draw white background for logo
|
|
271
|
+
ctx.fillStyle = this.rgbToHex(this.options.backgroundColor);
|
|
272
|
+
ctx.fillRect(logoX - 5, logoY - 5, logoSize + 10, logoSize + 10);
|
|
273
|
+
|
|
274
|
+
// Draw logo
|
|
275
|
+
ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.warn(`Failed to add logo: ${error.message}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Add watermark to QR code
|
|
283
|
+
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
284
|
+
*/
|
|
285
|
+
async addWatermark(ctx) {
|
|
286
|
+
try {
|
|
287
|
+
// Check if watermark is an image path
|
|
288
|
+
if (
|
|
289
|
+
this.options.watermark.includes('.') &&
|
|
290
|
+
this.options.watermark.includes('/')
|
|
291
|
+
) {
|
|
292
|
+
const watermarkImage = await loadImage(this.options.watermark);
|
|
293
|
+
const watermarkSize = this.options.size / 4;
|
|
294
|
+
const position = this.getWatermarkPosition(watermarkSize);
|
|
295
|
+
|
|
296
|
+
ctx.drawImage(
|
|
297
|
+
watermarkImage,
|
|
298
|
+
position.x,
|
|
299
|
+
position.y,
|
|
300
|
+
watermarkSize,
|
|
301
|
+
watermarkSize
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
// Text watermark
|
|
305
|
+
ctx.font = `${this.options.labelFontSize}px Arial`;
|
|
306
|
+
ctx.fillStyle = this.rgbToHex(this.options.foregroundColor);
|
|
307
|
+
ctx.textAlign = 'center';
|
|
308
|
+
|
|
309
|
+
const position = this.getWatermarkPosition(0);
|
|
310
|
+
ctx.fillText(this.options.watermark, position.x, position.y);
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.warn(`Failed to add watermark: ${error.message}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Add label to QR code
|
|
319
|
+
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
320
|
+
*/
|
|
321
|
+
addLabel(ctx) {
|
|
322
|
+
ctx.font = `${this.options.labelFontSize}px Arial`;
|
|
323
|
+
ctx.fillStyle = this.rgbToHex(this.options.foregroundColor);
|
|
324
|
+
ctx.textAlign = 'center';
|
|
325
|
+
|
|
326
|
+
const labelY = this.options.size - 10;
|
|
327
|
+
ctx.fillText(this.options.label, this.options.size / 2, labelY);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get watermark position
|
|
332
|
+
* @param {number} size - Watermark size
|
|
333
|
+
* @returns {Object} Position coordinates
|
|
334
|
+
*/
|
|
335
|
+
getWatermarkPosition(size) {
|
|
336
|
+
const positions = {
|
|
337
|
+
'top-left': { x: 10, y: 10 },
|
|
338
|
+
'top-right': { x: this.options.size - size - 10, y: 10 },
|
|
339
|
+
'bottom-left': { x: 10, y: this.options.size - size - 10 },
|
|
340
|
+
'bottom-right': {
|
|
341
|
+
x: this.options.size - size - 10,
|
|
342
|
+
y: this.options.size - size - 10,
|
|
343
|
+
},
|
|
344
|
+
center: {
|
|
345
|
+
x: (this.options.size - size) / 2,
|
|
346
|
+
y: (this.options.size - size) / 2,
|
|
347
|
+
},
|
|
348
|
+
'top-center': { x: (this.options.size - size) / 2, y: 10 },
|
|
349
|
+
'bottom-center': {
|
|
350
|
+
x: (this.options.size - size) / 2,
|
|
351
|
+
y: this.options.size - size - 10,
|
|
352
|
+
},
|
|
353
|
+
'left-center': { x: 10, y: (this.options.size - size) / 2 },
|
|
354
|
+
'right-center': {
|
|
355
|
+
x: this.options.size - size - 10,
|
|
356
|
+
y: (this.options.size - size) / 2,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
return positions[this.options.watermarkPosition] || positions.center;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Convert RGB array to hex color
|
|
365
|
+
* @param {Array} rgb - RGB color array
|
|
366
|
+
* @returns {string} Hex color
|
|
367
|
+
*/
|
|
368
|
+
rgbToHex(rgb) {
|
|
369
|
+
return `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Save QR code to file
|
|
374
|
+
* @param {string} filePath - Output file path
|
|
375
|
+
* @returns {Promise<void>}
|
|
376
|
+
*/
|
|
377
|
+
async saveToFile(filePath) {
|
|
378
|
+
const buffer = await this.generate();
|
|
379
|
+
await fs.writeFile(filePath, buffer);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get QR code as data URI
|
|
384
|
+
* @returns {Promise<string>} Data URI
|
|
385
|
+
*/
|
|
386
|
+
async getDataUri() {
|
|
387
|
+
const buffer = await this.generate();
|
|
388
|
+
const base64 = buffer.toString('base64');
|
|
389
|
+
return `data:image/${this.options.format};base64,${base64}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Get QR code as string
|
|
394
|
+
* @returns {Promise<string>} QR code string
|
|
395
|
+
*/
|
|
396
|
+
async getString() {
|
|
397
|
+
return await this.generate();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
module.exports = QrCodeBuilder;
|