isahaq-barcode 1.9.0 → 2.0.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/CHANGELOG.md +107 -0
- package/LICENSE +21 -0
- package/README.md +317 -553
- package/bin/generate.js +171 -134
- package/browser.js +163 -170
- package/index.d.ts +497 -0
- package/index.js +119 -51
- package/package.json +69 -41
- package/src/builders/QrCodeBuilder.js +328 -165
- package/src/encoders/BarcodeEncoder.js +260 -0
- package/src/renderers/HTMLRenderer.js +36 -169
- package/src/renderers/PDFRenderer.js +108 -130
- package/src/renderers/PNGRenderer.js +7 -83
- package/src/renderers/RenderFormats.js +89 -38
- package/src/renderers/SVGRenderer.js +8 -182
- package/src/services/BarcodeService.js +96 -36
- package/src/types/BarcodeTypes.js +535 -144
- package/src/validators/Validator.js +140 -122
- package/.eslintrc.js +0 -29
- package/.prettierignore +0 -11
- package/.prettierrc +0 -11
- package/eslint.config.js +0 -67
- package/examples/basic-usage.js +0 -58
- package/examples/batch-example.json +0 -56
- package/examples/express-server.js +0 -163
- package/jest.config.js +0 -13
- package/tests/BarcodeGenerator.test.js +0 -187
- package/tests/BarcodeService.test.js +0 -76
- package/tests/QrCodeBuilder.test.js +0 -130
- package/tests/setup.js +0 -59
- package/webpack.config.js +0 -26
package/bin/generate.js
CHANGED
|
@@ -5,20 +5,91 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { Command } = require('commander');
|
|
8
|
-
const chalk = require('chalk');
|
|
9
|
-
const ora = require('ora');
|
|
10
8
|
const fs = require('fs').promises;
|
|
11
9
|
const path = require('path');
|
|
12
10
|
const BarcodeGenerator = require('../index');
|
|
11
|
+
const { version } = require('../package.json');
|
|
12
|
+
|
|
13
|
+
/** Minimal ANSI colouring - honours NO_COLOR and non-TTY output */
|
|
14
|
+
const useColor =
|
|
15
|
+
!process.env.NO_COLOR && process.stderr.isTTY && process.env.TERM !== 'dumb';
|
|
16
|
+
|
|
17
|
+
const color = {
|
|
18
|
+
red: text => (useColor ? `[31m${text}[39m` : text),
|
|
19
|
+
green: text => (useColor ? `[32m${text}[39m` : text),
|
|
20
|
+
blue: text => (useColor ? `[34m${text}[39m` : text),
|
|
21
|
+
dim: text => (useColor ? `[2m${text}[22m` : text),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Report progress on stderr so stdout stays pipeable
|
|
26
|
+
* @param {string} message - Message to print
|
|
27
|
+
*/
|
|
28
|
+
function status(message) {
|
|
29
|
+
process.stderr.write(`${color.dim(message)}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Report success on stderr
|
|
34
|
+
* @param {string} message - Message to print
|
|
35
|
+
*/
|
|
36
|
+
function succeed(message) {
|
|
37
|
+
process.stderr.write(`${color.green('✔')} ${message}\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Print an error and exit with a non-zero status
|
|
42
|
+
* @param {string} message - Message to print
|
|
43
|
+
*/
|
|
44
|
+
function fail(message) {
|
|
45
|
+
process.stderr.write(`${color.red('✖')} ${message}\n`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert a hex colour into an RGB array
|
|
51
|
+
* @param {string} hex - Hex colour
|
|
52
|
+
* @returns {number[]} RGB components
|
|
53
|
+
*/
|
|
54
|
+
function hexToRgb(hex) {
|
|
55
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
56
|
+
return result
|
|
57
|
+
? [
|
|
58
|
+
parseInt(result[1], 16),
|
|
59
|
+
parseInt(result[2], 16),
|
|
60
|
+
parseInt(result[3], 16),
|
|
61
|
+
]
|
|
62
|
+
: [0, 0, 0];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Write output to a file, or to stdout when no path is given
|
|
67
|
+
* @param {Buffer|string} result - Generated barcode
|
|
68
|
+
* @param {string|undefined} output - Output file path
|
|
69
|
+
* @param {string} label - Label used in the success message
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function emit(result, output, label) {
|
|
73
|
+
if (output) {
|
|
74
|
+
await fs.writeFile(output, result);
|
|
75
|
+
succeed(`${label} saved to ${output}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (Buffer.isBuffer(result)) {
|
|
80
|
+
process.stdout.write(`${result.toString('base64')}\n`);
|
|
81
|
+
} else {
|
|
82
|
+
process.stdout.write(`${result}\n`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
13
85
|
|
|
14
86
|
const program = new Command();
|
|
15
87
|
|
|
16
88
|
program
|
|
17
89
|
.name('barcode-generate')
|
|
18
|
-
.description('Generate barcodes and QR codes from command line')
|
|
19
|
-
.version(
|
|
90
|
+
.description('Generate barcodes and QR codes from the command line')
|
|
91
|
+
.version(version);
|
|
20
92
|
|
|
21
|
-
// Barcode generation command
|
|
22
93
|
program
|
|
23
94
|
.command('barcode')
|
|
24
95
|
.description('Generate a barcode')
|
|
@@ -26,103 +97,83 @@ program
|
|
|
26
97
|
.option('-t, --type <type>', 'Barcode type', 'code128')
|
|
27
98
|
.option('-f, --format <format>', 'Output format (png, svg, html, pdf)', 'png')
|
|
28
99
|
.option('-o, --output <file>', 'Output file path')
|
|
29
|
-
.option('-w, --width <width>', '
|
|
30
|
-
.option('-h, --height <height>', '
|
|
31
|
-
.option('--
|
|
32
|
-
.option('--
|
|
33
|
-
.option('--
|
|
100
|
+
.option('-w, --width <width>', 'Module (narrow bar) width in pixels', '2')
|
|
101
|
+
.option('-h, --height <height>', 'Bar height in pixels', '100')
|
|
102
|
+
.option('-s, --size <size>', 'Target size in pixels for 2D barcodes')
|
|
103
|
+
.option('-m, --margin <margin>', 'Quiet zone in pixels (default: 10 modules)')
|
|
104
|
+
.option('-q, --quiet-zone <modules>', 'Quiet zone in modules', '10')
|
|
105
|
+
.option('--no-text', 'Hide the human readable text')
|
|
106
|
+
.option('--foreground <color>', 'Foreground colour (hex)', '#000000')
|
|
107
|
+
.option('--background <color>', 'Background colour (hex)', '#ffffff')
|
|
108
|
+
.option('--rotate <rotation>', 'Rotation: N, R, L or I', 'N')
|
|
34
109
|
.action(async options => {
|
|
35
|
-
|
|
110
|
+
status('Generating barcode...');
|
|
36
111
|
|
|
37
112
|
try {
|
|
38
113
|
const renderOptions = {
|
|
39
|
-
width: parseInt(options.width),
|
|
40
|
-
height: parseInt(options.height),
|
|
114
|
+
width: parseInt(options.width, 10),
|
|
115
|
+
height: parseInt(options.height, 10),
|
|
116
|
+
quietZone: parseInt(options.quietZone, 10),
|
|
41
117
|
displayValue: options.text,
|
|
42
118
|
lineColor: options.foreground,
|
|
43
119
|
background: options.background,
|
|
120
|
+
rotate: options.rotate,
|
|
44
121
|
};
|
|
45
122
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
result = BarcodeGenerator.png(
|
|
50
|
-
options.data,
|
|
51
|
-
options.type,
|
|
52
|
-
renderOptions
|
|
53
|
-
);
|
|
54
|
-
break;
|
|
55
|
-
case 'svg':
|
|
56
|
-
result = BarcodeGenerator.svg(
|
|
57
|
-
options.data,
|
|
58
|
-
options.type,
|
|
59
|
-
renderOptions
|
|
60
|
-
);
|
|
61
|
-
break;
|
|
62
|
-
case 'html':
|
|
63
|
-
result = BarcodeGenerator.html(
|
|
64
|
-
options.data,
|
|
65
|
-
options.type,
|
|
66
|
-
renderOptions
|
|
67
|
-
);
|
|
68
|
-
break;
|
|
69
|
-
case 'pdf':
|
|
70
|
-
result = BarcodeGenerator.pdf(
|
|
71
|
-
options.data,
|
|
72
|
-
options.type,
|
|
73
|
-
renderOptions
|
|
74
|
-
);
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
throw new Error(`Unsupported format: ${options.format}`);
|
|
123
|
+
// Only override the module based quiet zone when a pixel margin is asked for
|
|
124
|
+
if (options.margin !== undefined) {
|
|
125
|
+
renderOptions.margin = parseInt(options.margin, 10);
|
|
78
126
|
}
|
|
79
127
|
|
|
80
|
-
if (options.
|
|
81
|
-
|
|
82
|
-
spinner.succeed(`Barcode saved to ${options.output}`);
|
|
83
|
-
} else {
|
|
84
|
-
// Output to stdout (for base64 encoding)
|
|
85
|
-
if (Buffer.isBuffer(result)) {
|
|
86
|
-
console.log(result.toString('base64'));
|
|
87
|
-
} else {
|
|
88
|
-
console.log(result);
|
|
89
|
-
}
|
|
128
|
+
if (options.size) {
|
|
129
|
+
renderOptions.size = parseInt(options.size, 10);
|
|
90
130
|
}
|
|
131
|
+
|
|
132
|
+
const result = await BarcodeGenerator.generate(
|
|
133
|
+
options.data,
|
|
134
|
+
options.type,
|
|
135
|
+
options.format,
|
|
136
|
+
renderOptions
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await emit(result, options.output, 'Barcode');
|
|
91
140
|
} catch (error) {
|
|
92
|
-
|
|
93
|
-
process.exit(1);
|
|
141
|
+
fail(error.message);
|
|
94
142
|
}
|
|
95
143
|
});
|
|
96
144
|
|
|
97
|
-
// QR code generation command
|
|
98
145
|
program
|
|
99
146
|
.command('qr')
|
|
100
147
|
.description('Generate a QR code')
|
|
101
148
|
.requiredOption('-d, --data <data>', 'Data to encode')
|
|
102
|
-
.option('-s, --size <size>', 'QR code size', '300')
|
|
103
|
-
.option('-f, --format <format>', 'Output format (png,
|
|
149
|
+
.option('-s, --size <size>', 'QR code size in pixels', '300')
|
|
150
|
+
.option('-f, --format <format>', 'Output format (png, jpeg, svg)', 'png')
|
|
104
151
|
.option('-o, --output <file>', 'Output file path')
|
|
105
|
-
.option('-m, --margin <margin>', 'Margin
|
|
152
|
+
.option('-m, --margin <margin>', 'Margin in pixels', '10')
|
|
106
153
|
.option(
|
|
107
154
|
'-e, --error-correction <level>',
|
|
108
155
|
'Error correction level (L, M, Q, H)',
|
|
109
156
|
'M'
|
|
110
157
|
)
|
|
111
|
-
.option('--foreground <color>', 'Foreground
|
|
112
|
-
.option('--background <color>', 'Background
|
|
113
|
-
.option('--logo <path>', 'Logo image path')
|
|
114
|
-
.option(
|
|
115
|
-
|
|
116
|
-
|
|
158
|
+
.option('--foreground <color>', 'Foreground colour (hex)', '#000000')
|
|
159
|
+
.option('--background <color>', 'Background colour (hex)', '#ffffff')
|
|
160
|
+
.option('--logo <path>', 'Logo image path (requires the canvas package)')
|
|
161
|
+
.option(
|
|
162
|
+
'--logo-size <size>',
|
|
163
|
+
'Logo size as a percentage of the QR code',
|
|
164
|
+
'20'
|
|
165
|
+
)
|
|
166
|
+
.option('--label <text>', 'Label text (requires the canvas package)')
|
|
167
|
+
.option('--watermark <text>', 'Watermark text or image path')
|
|
117
168
|
.option('--watermark-position <position>', 'Watermark position', 'center')
|
|
118
169
|
.action(async options => {
|
|
119
|
-
|
|
170
|
+
status('Generating QR code...');
|
|
120
171
|
|
|
121
172
|
try {
|
|
122
173
|
const qrOptions = {
|
|
123
174
|
data: options.data,
|
|
124
|
-
size: parseInt(options.size),
|
|
125
|
-
margin: parseInt(options.margin),
|
|
175
|
+
size: parseInt(options.size, 10),
|
|
176
|
+
margin: parseInt(options.margin, 10),
|
|
126
177
|
errorCorrectionLevel: options.errorCorrection,
|
|
127
178
|
foregroundColor: hexToRgb(options.foreground),
|
|
128
179
|
backgroundColor: hexToRgb(options.background),
|
|
@@ -131,7 +182,7 @@ program
|
|
|
131
182
|
|
|
132
183
|
if (options.logo) {
|
|
133
184
|
qrOptions.logoPath = options.logo;
|
|
134
|
-
qrOptions.logoSize = parseInt(options.logoSize);
|
|
185
|
+
qrOptions.logoSize = parseInt(options.logoSize, 10);
|
|
135
186
|
}
|
|
136
187
|
|
|
137
188
|
if (options.label) {
|
|
@@ -143,30 +194,20 @@ program
|
|
|
143
194
|
qrOptions.watermarkPosition = options.watermarkPosition;
|
|
144
195
|
}
|
|
145
196
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (options.output) {
|
|
150
|
-
await fs.writeFile(options.output, result);
|
|
151
|
-
spinner.succeed(`QR code saved to ${options.output}`);
|
|
152
|
-
} else {
|
|
153
|
-
// Output to stdout (for base64 encoding)
|
|
154
|
-
console.log(result.toString('base64'));
|
|
155
|
-
}
|
|
197
|
+
const result = await BarcodeGenerator.modernQr(qrOptions).generate();
|
|
198
|
+
await emit(result, options.output, 'QR code');
|
|
156
199
|
} catch (error) {
|
|
157
|
-
|
|
158
|
-
process.exit(1);
|
|
200
|
+
fail(error.message);
|
|
159
201
|
}
|
|
160
202
|
});
|
|
161
203
|
|
|
162
|
-
// Batch generation command
|
|
163
204
|
program
|
|
164
205
|
.command('batch')
|
|
165
206
|
.description('Generate multiple barcodes from a JSON file')
|
|
166
207
|
.requiredOption('-i, --input <file>', 'Input JSON file')
|
|
167
208
|
.option('-o, --output-dir <dir>', 'Output directory', './output')
|
|
168
209
|
.action(async options => {
|
|
169
|
-
|
|
210
|
+
status('Processing batch generation...');
|
|
170
211
|
|
|
171
212
|
try {
|
|
172
213
|
const inputData = JSON.parse(await fs.readFile(options.input, 'utf8'));
|
|
@@ -177,10 +218,9 @@ program
|
|
|
177
218
|
);
|
|
178
219
|
}
|
|
179
220
|
|
|
180
|
-
// Create output directory if it doesn't exist
|
|
181
221
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
182
222
|
|
|
183
|
-
const results = BarcodeGenerator.batch(inputData);
|
|
223
|
+
const results = await BarcodeGenerator.batch(inputData);
|
|
184
224
|
|
|
185
225
|
let successCount = 0;
|
|
186
226
|
let errorCount = 0;
|
|
@@ -188,87 +228,84 @@ program
|
|
|
188
228
|
for (const result of results) {
|
|
189
229
|
if (result.success) {
|
|
190
230
|
const filename = `barcode_${result.index}.${result.format}`;
|
|
191
|
-
|
|
192
|
-
|
|
231
|
+
await fs.writeFile(
|
|
232
|
+
path.join(options.outputDir, filename),
|
|
233
|
+
result.result
|
|
234
|
+
);
|
|
193
235
|
successCount++;
|
|
194
236
|
} else {
|
|
195
237
|
errorCount++;
|
|
196
|
-
|
|
197
|
-
|
|
238
|
+
process.stderr.write(
|
|
239
|
+
`${color.red(`Error in item ${result.index}:`)} ${result.error}\n`
|
|
198
240
|
);
|
|
199
241
|
}
|
|
200
242
|
}
|
|
201
243
|
|
|
202
|
-
|
|
203
|
-
`Batch generation completed: ${successCount} successful, ${errorCount}
|
|
244
|
+
succeed(
|
|
245
|
+
`Batch generation completed: ${successCount} successful, ${errorCount} failed`
|
|
204
246
|
);
|
|
247
|
+
|
|
248
|
+
if (errorCount > 0) {
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
}
|
|
205
251
|
} catch (error) {
|
|
206
|
-
|
|
207
|
-
process.exit(1);
|
|
252
|
+
fail(error.message);
|
|
208
253
|
}
|
|
209
254
|
});
|
|
210
255
|
|
|
211
|
-
// List supported types command
|
|
212
256
|
program
|
|
213
257
|
.command('types')
|
|
214
258
|
.description('List supported barcode types')
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
|
|
259
|
+
.option('-c, --category <category>', 'Only list types in this category')
|
|
260
|
+
.action(options => {
|
|
261
|
+
const types = options.category
|
|
262
|
+
? BarcodeGenerator.getBarcodeTypesByCategory(options.category)
|
|
263
|
+
: BarcodeGenerator.getBarcodeTypes();
|
|
264
|
+
|
|
265
|
+
if (!types.length) {
|
|
266
|
+
fail(
|
|
267
|
+
`Unknown category: ${options.category}. Categories: ${BarcodeGenerator.getBarcodeCategories().join(
|
|
268
|
+
', '
|
|
269
|
+
)}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
process.stderr.write(`${color.blue('Supported barcode types:')}\n`);
|
|
218
274
|
types.forEach(type => {
|
|
219
|
-
|
|
275
|
+
const info = BarcodeGenerator.getBarcodeTypeInfo(type);
|
|
276
|
+
process.stdout.write(` ${type.padEnd(24)} ${info.description}\n`);
|
|
220
277
|
});
|
|
221
278
|
});
|
|
222
279
|
|
|
223
|
-
// List supported formats command
|
|
224
280
|
program
|
|
225
281
|
.command('formats')
|
|
226
282
|
.description('List supported output formats')
|
|
227
283
|
.action(() => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
284
|
+
process.stderr.write(`${color.blue('Supported output formats:')}\n`);
|
|
285
|
+
BarcodeGenerator.getRenderFormats().forEach(format => {
|
|
286
|
+
const info = BarcodeGenerator.getRenderFormatInfo(format);
|
|
287
|
+
process.stdout.write(` ${format.padEnd(6)} ${info.description}\n`);
|
|
232
288
|
});
|
|
233
289
|
});
|
|
234
290
|
|
|
235
|
-
// Validate data command
|
|
236
291
|
program
|
|
237
292
|
.command('validate')
|
|
238
293
|
.description('Validate data for a specific barcode type')
|
|
239
294
|
.requiredOption('-d, --data <data>', 'Data to validate')
|
|
240
295
|
.requiredOption('-t, --type <type>', 'Barcode type')
|
|
241
296
|
.action(options => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
console.log(` Type: ${validation.type}`);
|
|
248
|
-
console.log(` Length: ${validation.length}`);
|
|
249
|
-
console.log(` Charset: ${validation.charset}`);
|
|
250
|
-
} else {
|
|
251
|
-
console.log(chalk.red('✗ Data is invalid'));
|
|
252
|
-
console.log(` Error: ${validation.error}`);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
} catch (error) {
|
|
256
|
-
console.error(chalk.red(`Validation error: ${error.message}`));
|
|
297
|
+
const validation = BarcodeGenerator.validate(options.data, options.type);
|
|
298
|
+
|
|
299
|
+
if (!validation.valid) {
|
|
300
|
+
process.stderr.write(`${color.red('✖ Data is invalid')}\n`);
|
|
301
|
+
process.stderr.write(` Error: ${validation.error}\n`);
|
|
257
302
|
process.exit(1);
|
|
258
303
|
}
|
|
259
|
-
});
|
|
260
304
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
parseInt(result[1], 16),
|
|
267
|
-
parseInt(result[2], 16),
|
|
268
|
-
parseInt(result[3], 16),
|
|
269
|
-
]
|
|
270
|
-
: [0, 0, 0];
|
|
271
|
-
}
|
|
305
|
+
process.stderr.write(`${color.green('✔ Data is valid')}\n`);
|
|
306
|
+
process.stdout.write(` Type: ${validation.type}\n`);
|
|
307
|
+
process.stdout.write(` Length: ${validation.length}\n`);
|
|
308
|
+
process.stdout.write(` Charset: ${validation.charset}\n`);
|
|
309
|
+
});
|
|
272
310
|
|
|
273
|
-
// Parse command line arguments
|
|
274
311
|
program.parse();
|