fastqr 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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/BUILD.md +482 -0
  3. data/CHANGELOG.md +51 -0
  4. data/CMakeLists.txt +126 -0
  5. data/CONTRIBUTING.md +63 -0
  6. data/DISTRIBUTION.md +730 -0
  7. data/INSTALL.md +171 -0
  8. data/LICENSE +39 -0
  9. data/PREBUILT.md +171 -0
  10. data/README.md +312 -0
  11. data/VERSION +1 -0
  12. data/bindings/nodejs/binding.gyp +38 -0
  13. data/bindings/nodejs/fastqr_node.cpp +125 -0
  14. data/bindings/nodejs/index.d.ts +80 -0
  15. data/bindings/nodejs/index.js +187 -0
  16. data/bindings/nodejs/lib/platform.js +72 -0
  17. data/bindings/nodejs/package.json +45 -0
  18. data/bindings/nodejs/test/test.js +45 -0
  19. data/bindings/php/fastqr_php.cpp +85 -0
  20. data/bindings/php/src/FastQR.php +316 -0
  21. data/bindings/php/tests/FastQRTest.php +97 -0
  22. data/bindings/ruby/extconf.rb +29 -0
  23. data/bindings/ruby/fastqr_ruby.cpp +122 -0
  24. data/bindings/ruby/lib/fastqr/platform.rb +70 -0
  25. data/bindings/ruby/lib/fastqr/version.rb +6 -0
  26. data/bindings/ruby/lib/fastqr.rb +129 -0
  27. data/bindings/ruby/prebuilt/macos-arm64.tar.gz +1 -0
  28. data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +1 -0
  29. data/build.sh +109 -0
  30. data/cmake/fastqrConfig.cmake.in +6 -0
  31. data/composer.json +36 -0
  32. data/docs/CLI_USAGE.md +478 -0
  33. data/docs/NODEJS_USAGE.md +694 -0
  34. data/docs/PHP_USAGE.md +815 -0
  35. data/docs/README.md +191 -0
  36. data/docs/RUBY_USAGE.md +537 -0
  37. data/examples/CMakeLists.txt +7 -0
  38. data/examples/basic.cpp +58 -0
  39. data/include/fastqr.h +97 -0
  40. data/include/stb_image.h +7988 -0
  41. data/include/stb_image_write.h +1724 -0
  42. data/phpunit.xml +18 -0
  43. data/prebuilt/README.md +131 -0
  44. data/scripts/README.md +248 -0
  45. data/scripts/build-binaries.sh +98 -0
  46. data/scripts/build-local.sh +18 -0
  47. data/scripts/install.sh +87 -0
  48. data/scripts/release.sh +78 -0
  49. data/scripts/update-version.sh +58 -0
  50. data/src/cli.cpp +316 -0
  51. data/src/fastqr.cpp +665 -0
  52. data/test.sh +86 -0
  53. metadata +155 -0
@@ -0,0 +1,694 @@
1
+ # FastQR Node.js Usage Guide
2
+
3
+ Complete guide for using FastQR in Node.js and JavaScript applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install fastqr
9
+ ```
10
+
11
+ Or with Yarn:
12
+
13
+ ```bash
14
+ yarn add fastqr
15
+ ```
16
+
17
+ **Note:** No system dependencies required! Pre-built binaries are included. 🎉
18
+
19
+ ## Basic Usage
20
+
21
+ ```javascript
22
+ const fastqr = require('fastqr');
23
+
24
+ // Generate QR code
25
+ fastqr.generate('Hello World', 'qr.png');
26
+ ```
27
+
28
+ ## API Reference
29
+
30
+ ### `fastqr.generate(data, outputPath, options)`
31
+
32
+ Generate a QR code and save to file.
33
+
34
+ **Parameters:**
35
+ - `data` (string, required) - Data to encode (UTF-8 supported)
36
+ - `outputPath` (string, required) - Path to save the QR code image
37
+ - `options` (object, optional) - Generation options
38
+
39
+ **Returns:** `boolean` - `true` if successful
40
+
41
+ **Throws:** `Error` if generation fails
42
+
43
+ ### Options
44
+
45
+ | Option | Type | Default | Description |
46
+ |--------|------|---------|-------------|
47
+ | `size` | number | `300` | Output size in pixels (QR codes are square) |
48
+ | `optimizeSize` | boolean | `false` | Auto round-up for best performance |
49
+ | `width` | number | - | (Deprecated) Use `size` instead |
50
+ | `height` | number | - | (Deprecated) Use `size` instead |
51
+ | `foreground` | Array[3] | `[0, 0, 0]` | QR code color (RGB) |
52
+ | `background` | Array[3] | `[255, 255, 255]` | Background color (RGB) |
53
+ | `errorLevel` | string | `'M'` | Error correction: 'L', 'M', 'Q', 'H' |
54
+ | `logo` | string | `undefined` | Path to logo image |
55
+ | `logoSize` | number | `20` | Logo size as percentage (1-50) |
56
+ | `quality` | number | `95` | Image quality (1-100) |
57
+ | `format` | string | `'png'` | Output format: 'png', 'jpg', 'webp' |
58
+
59
+ ### `fastqr.generateBatch(dataArray, outputDir, options)`
60
+
61
+ Generate multiple QR codes at once - **7x faster** than calling `generate` multiple times!
62
+
63
+ **Parameters:**
64
+ - `dataArray` (Array[string], required) - Array of strings to encode
65
+ - `outputDir` (string, required) - Directory to save QR codes (created if doesn't exist)
66
+ - `options` (object, optional) - Same options as `generate`
67
+
68
+ **Returns:** `object` - `{ success: number, failed: number }`
69
+
70
+ **Example:**
71
+ ```javascript
72
+ const data = ['QR 1', 'QR 2', 'QR 3'];
73
+ const result = fastqr.generateBatch(data, 'output/', { size: 500, optimizeSize: true });
74
+ // Creates: output/1.png, output/2.png, output/3.png
75
+ console.log(`Generated ${result.success} QR codes`);
76
+ ```
77
+
78
+ ### `fastqr.version()`
79
+
80
+ Get library version.
81
+
82
+ **Returns:** string (e.g., "1.0.0")
83
+
84
+ ```javascript
85
+ console.log(fastqr.version());
86
+ // => "1.0.0"
87
+ ```
88
+
89
+ ### `fastqr.VERSION`
90
+
91
+ Version constant.
92
+
93
+ ```javascript
94
+ console.log(fastqr.VERSION);
95
+ // => "1.0.0"
96
+ ```
97
+
98
+ ## Examples
99
+
100
+ ### 1. Basic QR Code
101
+
102
+ ```javascript
103
+ const fastqr = require('fastqr');
104
+
105
+ fastqr.generate('https://example.com', 'qr.png');
106
+ ```
107
+
108
+ ### 2. Custom Size
109
+
110
+ ```javascript
111
+ fastqr.generate('Large QR', 'large.png', {
112
+ size: 1000
113
+ });
114
+ ```
115
+
116
+ ### 3. Optimized Size (faster generation)
117
+
118
+ ```javascript
119
+ fastqr.generate('Fast QR', 'fast.png', {
120
+ size: 500,
121
+ optimizeSize: true
122
+ });
123
+ ```
124
+
125
+ ### 4. Colored QR Code
126
+
127
+ ```javascript
128
+ // Red QR on yellow background
129
+ fastqr.generate('Colored', 'colored.png', {
130
+ size: 500,
131
+ foreground: [255, 0, 0], // Red
132
+ background: [255, 255, 200] // Light yellow
133
+ });
134
+ ```
135
+
136
+ ### 5. QR Code with Logo
137
+
138
+ ```javascript
139
+ fastqr.generate('Company', 'company.png', {
140
+ size: 800,
141
+ logo: 'logo.png',
142
+ logoSize: 25,
143
+ errorLevel: 'H' // High error correction for logo
144
+ });
145
+ ```
146
+
147
+ ### 6. High Error Correction
148
+
149
+ ```javascript
150
+ fastqr.generate('Important Data', 'qr.png', {
151
+ errorLevel: 'H' // ~30% recovery capability
152
+ });
153
+ ```
154
+
155
+ ### 7. UTF-8 Support
156
+
157
+ ```javascript
158
+ // Vietnamese
159
+ fastqr.generate('Xin chào Việt Nam! 🇻🇳', 'vietnamese.png');
160
+
161
+ // Japanese
162
+ fastqr.generate('こんにちは日本', 'japanese.png');
163
+
164
+ // Emoji
165
+ fastqr.generate('Hello 👋 World 🌍', 'emoji.png');
166
+ ```
167
+
168
+ ### 8. Batch Generation (7x faster!)
169
+
170
+ ```javascript
171
+ // Generate 1000 QR codes
172
+ const data = Array.from({ length: 1000 }, (_, i) => `Product ${i + 1}`);
173
+
174
+ // Old way (slow - ~3 seconds)
175
+ // for (let i = 0; i < data.length; i++) {
176
+ // fastqr.generate(data[i], `qr_${i+1}.png`, { size: 500 });
177
+ // }
178
+
179
+ // New way (fast - ~0.4 seconds!)
180
+ const result = fastqr.generateBatch(data, 'qr_codes/', {
181
+ size: 500,
182
+ optimizeSize: true
183
+ });
184
+ console.log(`Generated ${result.success} QR codes`);
185
+ // Creates: qr_codes/1.png, qr_codes/2.png, ..., qr_codes/1000.png
186
+ ```
187
+
188
+ ### 7. Different Formats
189
+
190
+ ```javascript
191
+ // PNG (default)
192
+ fastqr.generate('Data', 'output.png');
193
+
194
+ // JPEG
195
+ fastqr.generate('Data', 'output.jpg', { quality: 90 });
196
+
197
+ // WebP
198
+ fastqr.generate('Data', 'output.webp', { quality: 85 });
199
+ ```
200
+
201
+ ## Express.js Integration
202
+
203
+ ### Basic Route
204
+
205
+ ```javascript
206
+ const express = require('express');
207
+ const fastqr = require('fastqr');
208
+ const path = require('path');
209
+ const fs = require('fs').promises;
210
+
211
+ const app = express();
212
+ app.use(express.json());
213
+
214
+ // Generate QR code endpoint
215
+ app.post('/api/qr/generate', async (req, res) => {
216
+ try {
217
+ const { data, options = {} } = req.body;
218
+
219
+ if (!data) {
220
+ return res.status(400).json({ error: 'Data is required' });
221
+ }
222
+
223
+ // Generate unique filename
224
+ const filename = `qr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.png`;
225
+ const filepath = path.join(__dirname, 'public', 'qrcodes', filename);
226
+
227
+ // Ensure directory exists
228
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
229
+
230
+ // Generate QR code
231
+ fastqr.generate(data, filepath, {
232
+ width: options.width || 500,
233
+ height: options.height || 500,
234
+ errorLevel: options.errorLevel || 'M'
235
+ });
236
+
237
+ res.json({
238
+ success: true,
239
+ url: `/qrcodes/${filename}`,
240
+ filename
241
+ });
242
+ } catch (error) {
243
+ console.error('QR generation error:', error);
244
+ res.status(500).json({ error: 'Failed to generate QR code' });
245
+ }
246
+ });
247
+
248
+ app.listen(3000, () => {
249
+ console.log('Server running on http://localhost:3000');
250
+ });
251
+ ```
252
+
253
+ ### Advanced Route with Options
254
+
255
+ ```javascript
256
+ app.post('/api/qr/advanced', async (req, res) => {
257
+ try {
258
+ const {
259
+ data,
260
+ width = 600,
261
+ height = 600,
262
+ foreground = [0, 0, 0],
263
+ background = [255, 255, 255],
264
+ errorLevel = 'M',
265
+ logo,
266
+ logoSize = 20
267
+ } = req.body;
268
+
269
+ if (!data) {
270
+ return res.status(400).json({ error: 'Data is required' });
271
+ }
272
+
273
+ const filename = `qr_${Date.now()}.png`;
274
+ const filepath = path.join(__dirname, 'tmp', filename);
275
+
276
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
277
+
278
+ const options = {
279
+ width,
280
+ height,
281
+ foreground,
282
+ background,
283
+ errorLevel
284
+ };
285
+
286
+ if (logo) {
287
+ options.logo = logo;
288
+ options.logoSize = logoSize;
289
+ }
290
+
291
+ fastqr.generate(data, filepath, options);
292
+
293
+ res.sendFile(filepath);
294
+ } catch (error) {
295
+ res.status(500).json({ error: error.message });
296
+ }
297
+ });
298
+ ```
299
+
300
+ ### Middleware Example
301
+
302
+ ```javascript
303
+ // QR code generation middleware
304
+ function qrGenerator(options = {}) {
305
+ return async (req, res, next) => {
306
+ req.generateQR = async (data, customOptions = {}) => {
307
+ const filename = `qr_${Date.now()}.png`;
308
+ const filepath = path.join(__dirname, 'tmp', filename);
309
+
310
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
311
+
312
+ const qrOptions = { ...options, ...customOptions };
313
+
314
+ fastqr.generate(data, filepath, qrOptions);
315
+
316
+ return { filepath, filename };
317
+ };
318
+
319
+ next();
320
+ };
321
+ }
322
+
323
+ // Use middleware
324
+ app.use(qrGenerator({ size: 500, optimizeSize: true }));
325
+
326
+ app.post('/qr', async (req, res) => {
327
+ const { data } = req.body;
328
+ const { filepath } = await req.generateQR(data);
329
+ res.sendFile(filepath);
330
+ });
331
+ ```
332
+
333
+ ### Return Base64 Image
334
+
335
+ ```javascript
336
+ app.post('/api/qr/base64', async (req, res) => {
337
+ try {
338
+ const { data } = req.body;
339
+ const filename = `qr_${Date.now()}.png`;
340
+ const filepath = path.join(__dirname, 'tmp', filename);
341
+
342
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
343
+
344
+ // Generate QR code
345
+ fastqr.generate(data, filepath, {
346
+ width: 500,
347
+ height: 500
348
+ });
349
+
350
+ // Read file and convert to base64
351
+ const imageBuffer = await fs.readFile(filepath);
352
+ const base64Image = imageBuffer.toString('base64');
353
+
354
+ // Clean up temp file
355
+ await fs.unlink(filepath);
356
+
357
+ res.json({
358
+ success: true,
359
+ image: `data:image/png;base64,${base64Image}`
360
+ });
361
+ } catch (error) {
362
+ res.status(500).json({ error: error.message });
363
+ }
364
+ });
365
+ ```
366
+
367
+ ## TypeScript Usage
368
+
369
+ ### Type Definitions
370
+
371
+ FastQR includes TypeScript definitions (`index.d.ts`).
372
+
373
+ ```typescript
374
+ import * as fastqr from 'fastqr';
375
+
376
+ // Generate with type checking
377
+ fastqr.generate('Hello TypeScript', 'output.png', {
378
+ width: 600,
379
+ height: 600,
380
+ foreground: [0, 0, 255],
381
+ errorLevel: 'H'
382
+ });
383
+
384
+ // Options interface
385
+ interface QROptions {
386
+ width?: number;
387
+ height?: number;
388
+ foreground?: [number, number, number];
389
+ background?: [number, number, number];
390
+ errorLevel?: 'L' | 'M' | 'Q' | 'H';
391
+ logo?: string;
392
+ logoSize?: number;
393
+ quality?: number;
394
+ format?: 'png' | 'jpg' | 'jpeg' | 'webp';
395
+ }
396
+
397
+ const options: QROptions = {
398
+ width: 800,
399
+ height: 800,
400
+ errorLevel: 'H',
401
+ foreground: [255, 0, 0]
402
+ };
403
+
404
+ fastqr.generate('TypeScript QR', 'ts_qr.png', options);
405
+ ```
406
+
407
+ ### Express with TypeScript
408
+
409
+ ```typescript
410
+ import express, { Request, Response } from 'express';
411
+ import * as fastqr from 'fastqr';
412
+ import path from 'path';
413
+ import fs from 'fs/promises';
414
+
415
+ interface QRRequest {
416
+ data: string;
417
+ options?: fastqr.QROptions;
418
+ }
419
+
420
+ const app = express();
421
+ app.use(express.json());
422
+
423
+ app.post('/api/qr', async (req: Request<{}, {}, QRRequest>, res: Response) => {
424
+ try {
425
+ const { data, options = {} } = req.body;
426
+
427
+ const filename = `qr_${Date.now()}.png`;
428
+ const filepath = path.join(__dirname, 'public', filename);
429
+
430
+ fastqr.generate(data, filepath, options);
431
+
432
+ res.json({ success: true, filename });
433
+ } catch (error) {
434
+ res.status(500).json({ error: 'Generation failed' });
435
+ }
436
+ });
437
+ ```
438
+
439
+ ## Async/Await Patterns
440
+
441
+ ### Promise Wrapper
442
+
443
+ ```javascript
444
+ function generateQRAsync(data, outputPath, options) {
445
+ return new Promise((resolve, reject) => {
446
+ try {
447
+ const result = fastqr.generate(data, outputPath, options);
448
+ resolve(result);
449
+ } catch (error) {
450
+ reject(error);
451
+ }
452
+ });
453
+ }
454
+
455
+ // Usage
456
+ async function createQR() {
457
+ try {
458
+ await generateQRAsync('Async Data', 'async_qr.png', {
459
+ width: 500,
460
+ height: 500
461
+ });
462
+ console.log('✓ QR code generated');
463
+ } catch (error) {
464
+ console.error('✗ Generation failed:', error);
465
+ }
466
+ }
467
+ ```
468
+
469
+ ### Batch Generation
470
+
471
+ ```javascript
472
+ async function generateBatch(items) {
473
+ const promises = items.map(async (item, index) => {
474
+ const filename = `qr_${index}.png`;
475
+ const filepath = path.join(__dirname, 'output', filename);
476
+
477
+ return generateQRAsync(item.data, filepath, {
478
+ width: item.width || 500,
479
+ height: item.height || 500
480
+ });
481
+ });
482
+
483
+ try {
484
+ await Promise.all(promises);
485
+ console.log(`✓ Generated ${items.length} QR codes`);
486
+ } catch (error) {
487
+ console.error('✗ Batch generation failed:', error);
488
+ }
489
+ }
490
+
491
+ // Old way (slow - loop with individual calls)
492
+ // const items = [
493
+ // { data: 'https://example.com/1', size: 500 },
494
+ // { data: 'https://example.com/2', size: 600 },
495
+ // { data: 'https://example.com/3', size: 700 }
496
+ // ];
497
+
498
+ // New way (fast - batch mode)
499
+ const dataArray = ['https://example.com/1', 'https://example.com/2', 'https://example.com/3'];
500
+ fastqr.generateBatch(dataArray, 'output/', { size: 500, optimizeSize: true });
501
+
502
+ generateBatch(items);
503
+ ```
504
+
505
+ ## Advanced Examples
506
+
507
+ ### Dynamic Logo QR
508
+
509
+ ```javascript
510
+ const sharp = require('sharp'); // For logo processing
511
+
512
+ async function generateBrandedQR(data, brandColor, logoPath) {
513
+ const [r, g, b] = brandColor;
514
+ const filename = `branded_${Date.now()}.png`;
515
+
516
+ fastqr.generate(data, filename, {
517
+ width: 800,
518
+ height: 800,
519
+ foreground: [r, g, b],
520
+ logo: logoPath,
521
+ logoSize: 25,
522
+ errorLevel: 'H'
523
+ });
524
+
525
+ return filename;
526
+ }
527
+
528
+ // Usage
529
+ const filename = await generateBrandedQR(
530
+ 'Company Data',
531
+ [0, 120, 215], // Microsoft blue
532
+ 'company_logo.png'
533
+ );
534
+ ```
535
+
536
+ ### QR Code Service Class
537
+
538
+ ```javascript
539
+ class QRCodeService {
540
+ constructor(outputDir = 'public/qrcodes') {
541
+ this.outputDir = outputDir;
542
+ this.ensureDirectory();
543
+ }
544
+
545
+ async ensureDirectory() {
546
+ const fs = require('fs').promises;
547
+ await fs.mkdir(this.outputDir, { recursive: true });
548
+ }
549
+
550
+ generate(data, options = {}) {
551
+ const filename = `qr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.png`;
552
+ const filepath = path.join(this.outputDir, filename);
553
+
554
+ const defaultOptions = {
555
+ width: 500,
556
+ height: 500,
557
+ errorLevel: 'M'
558
+ };
559
+
560
+ fastqr.generate(data, filepath, { ...defaultOptions, ...options });
561
+
562
+ return { filepath, filename };
563
+ }
564
+
565
+ generateWithLogo(data, logoPath, options = {}) {
566
+ return this.generate(data, {
567
+ ...options,
568
+ logo: logoPath,
569
+ logoSize: options.logoSize || 25,
570
+ errorLevel: 'H'
571
+ });
572
+ }
573
+
574
+ async cleanup(olderThan = 24 * 60 * 60 * 1000) { // 24 hours
575
+ const fs = require('fs').promises;
576
+ const files = await fs.readdir(this.outputDir);
577
+ const now = Date.now();
578
+
579
+ for (const file of files) {
580
+ const filepath = path.join(this.outputDir, file);
581
+ const stats = await fs.stat(filepath);
582
+
583
+ if (now - stats.mtimeMs > olderThan) {
584
+ await fs.unlink(filepath);
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ // Usage
591
+ const qrService = new QRCodeService();
592
+
593
+ const { filename } = qrService.generate('Hello World', {
594
+ width: 600,
595
+ height: 600
596
+ });
597
+
598
+ const { filename: logoQR } = qrService.generateWithLogo(
599
+ 'Company',
600
+ 'logo.png',
601
+ { logoSize: 30 }
602
+ );
603
+
604
+ // Cleanup old files daily
605
+ setInterval(() => qrService.cleanup(), 24 * 60 * 60 * 1000);
606
+ ```
607
+
608
+ ## Testing
609
+
610
+ ### Jest Example
611
+
612
+ ```javascript
613
+ const fastqr = require('fastqr');
614
+ const fs = require('fs');
615
+ const path = require('path');
616
+
617
+ describe('FastQR', () => {
618
+ const outputPath = path.join(__dirname, 'test_qr.png');
619
+
620
+ afterEach(() => {
621
+ if (fs.existsSync(outputPath)) {
622
+ fs.unlinkSync(outputPath);
623
+ }
624
+ });
625
+
626
+ test('generates basic QR code', () => {
627
+ const result = fastqr.generate('Test Data', outputPath);
628
+
629
+ expect(result).toBe(true);
630
+ expect(fs.existsSync(outputPath)).toBe(true);
631
+ expect(fs.statSync(outputPath).size).toBeGreaterThan(0);
632
+ });
633
+
634
+ test('generates QR code with options', () => {
635
+ const result = fastqr.generate('Test', outputPath, {
636
+ width: 500,
637
+ height: 500,
638
+ errorLevel: 'H'
639
+ });
640
+
641
+ expect(result).toBe(true);
642
+ expect(fs.existsSync(outputPath)).toBe(true);
643
+ });
644
+
645
+ test('throws error for empty data', () => {
646
+ expect(() => {
647
+ fastqr.generate('', outputPath);
648
+ }).toThrow();
649
+ });
650
+
651
+ test('returns version', () => {
652
+ const version = fastqr.version();
653
+ expect(version).toMatch(/^\d+\.\d+\.\d+$/);
654
+ });
655
+ });
656
+ ```
657
+
658
+ ## Performance Tips
659
+
660
+ - Generate QR codes in worker threads for CPU-intensive operations
661
+ - Cache generated QR codes using data hash as filename
662
+ - Use lower quality for web display (`quality: 75`)
663
+ - Use PNG for best quality, JPG for smaller files
664
+
665
+ ## Troubleshooting
666
+
667
+ ### "Binary not found" Error
668
+
669
+ The package includes pre-built binaries. If you see this error:
670
+
671
+ ```javascript
672
+ const platform = require('./lib/platform');
673
+ console.log('Platform:', platform.detectPlatform());
674
+ console.log('Binary available:', platform.isPrebuiltAvailable());
675
+ console.log('Binary path:', platform.getPrebuiltPath());
676
+ ```
677
+
678
+ ### File Permission Errors
679
+
680
+ Ensure output directory has write permissions:
681
+
682
+ ```javascript
683
+ const fs = require('fs').promises;
684
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
685
+ ```
686
+
687
+ ## See Also
688
+
689
+ - [CLI Usage](CLI_USAGE.md) - Command-line usage
690
+ - [Ruby Usage](RUBY_USAGE.md) - Ruby/Rails guide
691
+ - [PHP Usage](PHP_USAGE.md) - PHP guide
692
+ - [GitHub Repository](https://github.com/tranhuucanh/fastqr)
693
+ - [npm Package](https://www.npmjs.com/package/fastqr)
694
+