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
data/docs/PHP_USAGE.md ADDED
@@ -0,0 +1,815 @@
1
+ # FastQR PHP Usage Guide
2
+
3
+ Complete guide for using FastQR in PHP and Laravel applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ composer require fastqr/fastqr
9
+ ```
10
+
11
+ **Note:** No system dependencies required! Pre-built binaries are included. 🎉
12
+
13
+ **Requirements:**
14
+ - PHP 7.4 or higher
15
+ - FFI extension (usually enabled by default)
16
+
17
+ ## Basic Usage
18
+
19
+ ```php
20
+ <?php
21
+ require 'vendor/autoload.php';
22
+
23
+ use FastQR\FastQR;
24
+
25
+ // Generate QR code
26
+ FastQR::generate('Hello World', 'qr.png');
27
+ ```
28
+
29
+ ## API Reference
30
+
31
+ ### `FastQR::generate($data, $outputPath, $options = [])`
32
+
33
+ Generate a QR code and save to file.
34
+
35
+ **Parameters:**
36
+ - `$data` (string, required) - Data to encode (UTF-8 supported)
37
+ - `$outputPath` (string, required) - Path to save the QR code image
38
+ - `$options` (array, optional) - Generation options
39
+
40
+ **Returns:** `bool` - `true` if successful
41
+
42
+ **Throws:** `RuntimeException` if generation fails
43
+
44
+ ### Options
45
+
46
+ | Option | Type | Default | Description |
47
+ |--------|------|---------|-------------|
48
+ | `'size'` | int | `300` | Output size in pixels (QR codes are square) |
49
+ | `'optimizeSize'` | bool | `false` | Auto round-up for best performance |
50
+ | `'width'` | int | - | (Deprecated) Use `'size'` instead |
51
+ | `'height'` | int | - | (Deprecated) Use `'size'` instead |
52
+ | `'foreground'` | array[3] | `[0, 0, 0]` | QR code color (RGB) |
53
+ | `'background'` | array[3] | `[255, 255, 255]` | Background color (RGB) |
54
+ | `'errorLevel'` | string | `'M'` | Error correction: 'L', 'M', 'Q', 'H' |
55
+ | `'logo'` | string | `null` | Path to logo image |
56
+ | `'logoSize'` | int | `20` | Logo size as percentage (1-50) |
57
+ | `'quality'` | int | `95` | Image quality (1-100) |
58
+ | `'format'` | string | `'png'` | Output format: 'png', 'jpg', 'webp' |
59
+
60
+ ### `FastQR::generateBatch($dataArray, $outputDir, $options = [])`
61
+
62
+ Generate multiple QR codes at once - **7x faster** than calling `generate` multiple times!
63
+
64
+ **Parameters:**
65
+ - `$dataArray` (array, required) - Array of strings to encode
66
+ - `$outputDir` (string, required) - Directory to save QR codes (created if doesn't exist)
67
+ - `$options` (array, optional) - Same options as `generate`
68
+
69
+ **Returns:** array - `['success' => int, 'failed' => int]`
70
+
71
+ **Example:**
72
+ ```php
73
+ $data = ['QR 1', 'QR 2', 'QR 3'];
74
+ $result = FastQR::generateBatch($data, 'output/', ['size' => 500, 'optimizeSize' => true]);
75
+ // Creates: output/1.png, output/2.png, output/3.png
76
+ echo "Generated {$result['success']} QR codes";
77
+ ```
78
+
79
+ ### `FastQR::version()`
80
+
81
+ Get library version.
82
+
83
+ **Returns:** string (e.g., "1.0.0")
84
+
85
+ ```php
86
+ echo FastQR::version();
87
+ // => "1.0.0"
88
+ ```
89
+
90
+ ## Examples
91
+
92
+ ### 1. Basic QR Code
93
+
94
+ ```php
95
+ <?php
96
+ use FastQR\FastQR;
97
+
98
+ FastQR::generate('https://example.com', 'qr.png');
99
+ ```
100
+
101
+ ### 2. Custom Size
102
+
103
+ ```php
104
+ FastQR::generate('Large QR', 'large.png', [
105
+ 'size' => 1000
106
+ ]);
107
+ ```
108
+
109
+ ### 3. Colored QR Code
110
+
111
+ ```php
112
+ // Red QR on yellow background
113
+ FastQR::generate('Colored', 'colored.png', [
114
+ 'foreground' => [255, 0, 0], // Red
115
+ 'background' => [255, 255, 200] // Light yellow
116
+ ]);
117
+ ```
118
+
119
+ ### 4. QR Code with Logo
120
+
121
+ ```php
122
+ FastQR::generate('Company', 'company.png', [
123
+ 'size' => 800,
124
+ 'logo' => 'logo.png',
125
+ 'logoSize' => 25,
126
+ 'errorLevel' => 'H' // High error correction for logo
127
+ ]);
128
+ ```
129
+
130
+ ### 5. High Error Correction
131
+
132
+ ```php
133
+ FastQR::generate('Important Data', 'qr.png', [
134
+ 'errorLevel' => 'H' // ~30% recovery capability
135
+ ]);
136
+ ```
137
+
138
+ ### 6. UTF-8 Support
139
+
140
+ ```php
141
+ // Vietnamese
142
+ FastQR::generate('Xin chào Việt Nam! 🇻🇳', 'vietnamese.png');
143
+
144
+ // Japanese
145
+ FastQR::generate('こんにちは日本', 'japanese.png');
146
+
147
+ // Emoji
148
+ FastQR::generate('Hello 👋 World 🌍', 'emoji.png');
149
+ ```
150
+
151
+ ### 7. Different Formats
152
+
153
+ ```php
154
+ // PNG (default)
155
+ FastQR::generate('Data', 'output.png');
156
+
157
+ // JPEG
158
+ FastQR::generate('Data', 'output.jpg', ['quality' => 90]);
159
+
160
+ // WebP
161
+ FastQR::generate('Data', 'output.webp', ['quality' => 85]);
162
+ ```
163
+
164
+ ## Laravel Integration
165
+
166
+ ### Controller Example
167
+
168
+ ```php
169
+ <?php
170
+
171
+ namespace App\Http\Controllers;
172
+
173
+ use FastQR\FastQR;
174
+ use Illuminate\Http\Request;
175
+ use Illuminate\Support\Facades\Storage;
176
+ use Illuminate\Support\Str;
177
+
178
+ class QRCodeController extends Controller
179
+ {
180
+ public function generate(Request $request)
181
+ {
182
+ $validated = $request->validate([
183
+ 'data' => 'required|string',
184
+ 'size' => 'nullable|integer|min:100|max:5000',
185
+ 'height' => 'nullable|integer|min:100|max:5000',
186
+ ]);
187
+
188
+ $filename = 'qr_' . Str::random(16) . '.png';
189
+ $path = storage_path('app/public/qrcodes/' . $filename);
190
+
191
+ // Ensure directory exists
192
+ if (!is_dir(dirname($path))) {
193
+ mkdir(dirname($path), 0755, true);
194
+ }
195
+
196
+ FastQR::generate($validated['data'], $path, [
197
+ 'width' => $validated['width'] ?? 500,
198
+ 'height' => $validated['height'] ?? 500,
199
+ 'errorLevel' => 'M'
200
+ ]);
201
+
202
+ return response()->json([
203
+ 'success' => true,
204
+ 'url' => asset('storage/qrcodes/' . $filename),
205
+ 'filename' => $filename
206
+ ]);
207
+ }
208
+
209
+ public function generateWithLogo(Request $request)
210
+ {
211
+ $validated = $request->validate([
212
+ 'data' => 'required|string',
213
+ 'logo' => 'required|file|image|max:2048'
214
+ ]);
215
+
216
+ // Save logo
217
+ $logoPath = $request->file('logo')->store('temp', 'local');
218
+ $fullLogoPath = storage_path('app/' . $logoPath);
219
+
220
+ // Generate QR
221
+ $filename = 'qr_' . time() . '.png';
222
+ $qrPath = storage_path('app/public/qrcodes/' . $filename);
223
+
224
+ FastQR::generate($validated['data'], $qrPath, [
225
+ 'size' => 800,
226
+ 'height' => 800,
227
+ 'logo' => $fullLogoPath,
228
+ 'logoSize' => 25,
229
+ 'errorLevel' => 'H'
230
+ ]);
231
+
232
+ // Clean up logo
233
+ Storage::delete($logoPath);
234
+
235
+ return response()->file($qrPath);
236
+ }
237
+
238
+ public function download(Request $request)
239
+ {
240
+ $data = $request->input('data');
241
+
242
+ $filename = 'qr_' . time() . '.png';
243
+ $path = storage_path('app/temp/' . $filename);
244
+
245
+ if (!is_dir(dirname($path))) {
246
+ mkdir(dirname($path), 0755, true);
247
+ }
248
+
249
+ FastQR::generate($data, $path, [
250
+ 'size' => 600,
251
+ 'height' => 600
252
+ ]);
253
+
254
+ return response()->download($path)->deleteFileAfterSend(true);
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Routes
260
+
261
+ ```php
262
+ <?php
263
+
264
+ use App\Http\Controllers\QRCodeController;
265
+
266
+ Route::prefix('api/qr')->group(function () {
267
+ Route::post('/generate', [QRCodeController::class, 'generate']);
268
+ Route::post('/with-logo', [QRCodeController::class, 'generateWithLogo']);
269
+ Route::post('/download', [QRCodeController::class, 'download']);
270
+ });
271
+ ```
272
+
273
+ ### Service Class
274
+
275
+ ```php
276
+ <?php
277
+
278
+ namespace App\Services;
279
+
280
+ use FastQR\FastQR;
281
+ use Illuminate\Support\Str;
282
+
283
+ class QRCodeService
284
+ {
285
+ protected $outputPath;
286
+
287
+ public function __construct()
288
+ {
289
+ $this->outputPath = storage_path('app/public/qrcodes');
290
+
291
+ if (!is_dir($this->outputPath)) {
292
+ mkdir($this->outputPath, 0755, true);
293
+ }
294
+ }
295
+
296
+ public function generate(string $data, array $options = []): string
297
+ {
298
+ $filename = 'qr_' . Str::random(16) . '.png';
299
+ $filepath = $this->outputPath . '/' . $filename;
300
+
301
+ $defaultOptions = [
302
+ 'size' => 500,
303
+ 'height' => 500,
304
+ 'errorLevel' => 'M'
305
+ ];
306
+
307
+ FastQR::generate($data, $filepath, array_merge($defaultOptions, $options));
308
+
309
+ return $filename;
310
+ }
311
+
312
+ public function generateWithLogo(string $data, string $logoPath, array $options = []): string
313
+ {
314
+ $options['logo'] = $logoPath;
315
+ $options['logoSize'] = $options['logoSize'] ?? 25;
316
+ $options['errorLevel'] = 'H';
317
+
318
+ return $this->generate($data, $options);
319
+ }
320
+
321
+ public function getPath(string $filename): string
322
+ {
323
+ return $this->outputPath . '/' . $filename;
324
+ }
325
+
326
+ public function getUrl(string $filename): string
327
+ {
328
+ return asset('storage/qrcodes/' . $filename);
329
+ }
330
+
331
+ public function delete(string $filename): bool
332
+ {
333
+ $filepath = $this->getPath($filename);
334
+
335
+ if (file_exists($filepath)) {
336
+ return unlink($filepath);
337
+ }
338
+
339
+ return false;
340
+ }
341
+
342
+ public function cleanup(int $olderThanDays = 7): int
343
+ {
344
+ $deleted = 0;
345
+ $threshold = time() - ($olderThanDays * 24 * 60 * 60);
346
+
347
+ $files = glob($this->outputPath . '/qr_*.png');
348
+
349
+ foreach ($files as $file) {
350
+ if (filemtime($file) < $threshold) {
351
+ if (unlink($file)) {
352
+ $deleted++;
353
+ }
354
+ }
355
+ }
356
+
357
+ return $deleted;
358
+ }
359
+ }
360
+ ```
361
+
362
+ ### Model Integration
363
+
364
+ ```php
365
+ <?php
366
+
367
+ namespace App\Models;
368
+
369
+ use Illuminate\Database\Eloquent\Model;
370
+ use FastQR\FastQR;
371
+
372
+ class Event extends Model
373
+ {
374
+ protected $fillable = ['name', 'date', 'location', 'url', 'qr_code'];
375
+
376
+ protected static function boot()
377
+ {
378
+ parent::boot();
379
+
380
+ static::created(function ($event) {
381
+ $event->generateQRCode();
382
+ });
383
+ }
384
+
385
+ public function generateQRCode()
386
+ {
387
+ $filename = 'event_' . $this->id . '.png';
388
+ $path = storage_path('app/public/qrcodes/' . $filename);
389
+
390
+ if (!is_dir(dirname($path))) {
391
+ mkdir(dirname($path), 0755, true);
392
+ }
393
+
394
+ $eventInfo = "Event: {$this->name}\n";
395
+ $eventInfo .= "Date: {$this->date->format('d/m/Y')}\n";
396
+ $eventInfo .= "Location: {$this->location}\n";
397
+ $eventInfo .= "URL: {$this->url}";
398
+
399
+ FastQR::generate($eventInfo, $path, [
400
+ 'size' => 600,
401
+ 'height' => 600,
402
+ 'errorLevel' => 'H'
403
+ ]);
404
+
405
+ $this->update(['qr_code' => $filename]);
406
+ }
407
+
408
+ public function getQRCodeUrlAttribute()
409
+ {
410
+ return $this->qr_code ? asset('storage/qrcodes/' . $this->qr_code) : null;
411
+ }
412
+ }
413
+ ```
414
+
415
+ ### Job Example
416
+
417
+ ```php
418
+ <?php
419
+
420
+ namespace App\Jobs;
421
+
422
+ use Illuminate\Bus\Queueable;
423
+ use Illuminate\Contracts\Queue\ShouldQueue;
424
+ use Illuminate\Foundation\Bus\Dispatchable;
425
+ use Illuminate\Queue\InteractsWithQueue;
426
+ use Illuminate\Queue\SerializesModels;
427
+ use App\Models\User;
428
+ use FastQR\FastQR;
429
+
430
+ class GenerateUserQRCode implements ShouldQueue
431
+ {
432
+ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
433
+
434
+ protected $userId;
435
+
436
+ public function __construct(int $userId)
437
+ {
438
+ $this->userId = $userId;
439
+ }
440
+
441
+ public function handle()
442
+ {
443
+ $user = User::findOrFail($this->userId);
444
+
445
+ $vcard = "BEGIN:VCARD\n";
446
+ $vcard .= "VERSION:3.0\n";
447
+ $vcard .= "FN:{$user->name}\n";
448
+ $vcard .= "EMAIL:{$user->email}\n";
449
+ $vcard .= "TEL:{$user->phone}\n";
450
+ $vcard .= "END:VCARD";
451
+
452
+ $filename = 'user_' . $user->id . '.png';
453
+ $path = storage_path('app/public/qrcodes/' . $filename);
454
+
455
+ FastQR::generate($vcard, $path, [
456
+ 'size' => 600,
457
+ 'height' => 600,
458
+ 'errorLevel' => 'H'
459
+ ]);
460
+
461
+ $user->update(['qr_code' => $filename]);
462
+ }
463
+ }
464
+ ```
465
+
466
+ ### Command Example
467
+
468
+ ```php
469
+ <?php
470
+
471
+ namespace App\Console\Commands;
472
+
473
+ use Illuminate\Console\Command;
474
+ use FastQR\FastQR;
475
+
476
+ class GenerateQRCode extends Command
477
+ {
478
+ protected $signature = 'qr:generate {data} {filename?}';
479
+ protected $description = 'Generate a QR code';
480
+
481
+ public function handle()
482
+ {
483
+ $data = $this->argument('data');
484
+ $filename = $this->argument('filename') ?? 'qr_' . time() . '.png';
485
+ $path = storage_path('app/public/qrcodes/' . $filename);
486
+
487
+ if (!is_dir(dirname($path))) {
488
+ mkdir(dirname($path), 0755, true);
489
+ }
490
+
491
+ try {
492
+ FastQR::generate($data, $path, [
493
+ 'size' => 600,
494
+ 'height' => 600
495
+ ]);
496
+
497
+ $this->info("✓ QR code generated: {$filename}");
498
+ $this->info(" Path: {$path}");
499
+ } catch (\Exception $e) {
500
+ $this->error("✗ Failed: " . $e->getMessage());
501
+ return 1;
502
+ }
503
+
504
+ return 0;
505
+ }
506
+ }
507
+ ```
508
+
509
+ ## WordPress Integration
510
+
511
+ ### Plugin Example
512
+
513
+ ```php
514
+ <?php
515
+ /**
516
+ * Plugin Name: FastQR Generator
517
+ * Description: Generate QR codes using FastQR
518
+ * Version: 1.0.0
519
+ */
520
+
521
+ require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php';
522
+
523
+ use FastQR\FastQR;
524
+
525
+ // Add admin menu
526
+ add_action('admin_menu', 'fastqr_add_admin_menu');
527
+
528
+ function fastqr_add_admin_menu() {
529
+ add_menu_page(
530
+ 'FastQR Generator',
531
+ 'QR Codes',
532
+ 'manage_options',
533
+ 'fastqr-generator',
534
+ 'fastqr_admin_page',
535
+ 'dashicons-grid-view'
536
+ );
537
+ }
538
+
539
+ // Admin page
540
+ function fastqr_admin_page() {
541
+ if (isset($_POST['generate_qr'])) {
542
+ $data = sanitize_text_field($_POST['qr_data']);
543
+ $filename = 'qr_' . time() . '.png';
544
+ $upload_dir = wp_upload_dir();
545
+ $qr_path = $upload_dir['path'] . '/' . $filename;
546
+
547
+ FastQR::generate($data, $qr_path, [
548
+ 'size' => 500,
549
+ 'height' => 500
550
+ ]);
551
+
552
+ $qr_url = $upload_dir['url'] . '/' . $filename;
553
+
554
+ echo '<div class="notice notice-success"><p>QR Code generated!</p></div>';
555
+ echo '<img src="' . esc_url($qr_url) . '" alt="QR Code" />';
556
+ }
557
+
558
+ ?>
559
+ <div class="wrap">
560
+ <h1>Generate QR Code</h1>
561
+ <form method="post">
562
+ <table class="form-table">
563
+ <tr>
564
+ <th><label for="qr_data">Data</label></th>
565
+ <td>
566
+ <textarea id="qr_data" name="qr_data" rows="5" cols="50" required></textarea>
567
+ </td>
568
+ </tr>
569
+ </table>
570
+ <?php submit_button('Generate QR Code', 'primary', 'generate_qr'); ?>
571
+ </form>
572
+ </div>
573
+ <?php
574
+ }
575
+
576
+ // Shortcode
577
+ add_shortcode('fastqr', 'fastqr_shortcode');
578
+
579
+ function fastqr_shortcode($atts) {
580
+ $atts = shortcode_atts([
581
+ 'data' => '',
582
+ 'size' => 300,
583
+ 'height' => 300
584
+ ], $atts);
585
+
586
+ if (empty($atts['data'])) {
587
+ return '';
588
+ }
589
+
590
+ $filename = 'qr_' . md5($atts['data']) . '.png';
591
+ $upload_dir = wp_upload_dir();
592
+ $qr_path = $upload_dir['path'] . '/' . $filename;
593
+ $qr_url = $upload_dir['url'] . '/' . $filename;
594
+
595
+ if (!file_exists($qr_path)) {
596
+ FastQR::generate($atts['data'], $qr_path, [
597
+ 'width' => intval($atts['width']),
598
+ 'height' => intval($atts['height'])
599
+ ]);
600
+ }
601
+
602
+ return '<img src="' . esc_url($qr_url) . '" alt="QR Code" class="fastqr-image" />';
603
+ }
604
+ ```
605
+
606
+ Usage in WordPress:
607
+ ```
608
+ [fastqr data="https://example.com" width="400" height="400"]
609
+ ```
610
+
611
+ ## Plain PHP Examples
612
+
613
+ ### Simple Script
614
+
615
+ ```php
616
+ <?php
617
+ require 'vendor/autoload.php';
618
+
619
+ use FastQR\FastQR;
620
+
621
+ // Get data from form
622
+ $data = $_POST['data'] ?? 'Default Data';
623
+
624
+ // Generate QR code
625
+ $filename = 'qr_' . time() . '.png';
626
+ $path = __DIR__ . '/qrcodes/' . $filename;
627
+
628
+ // Ensure directory exists
629
+ if (!is_dir(__DIR__ . '/qrcodes')) {
630
+ mkdir(__DIR__ . '/qrcodes', 0755, true);
631
+ }
632
+
633
+ try {
634
+ FastQR::generate($data, $path, [
635
+ 'width' => 500,
636
+ 'height' => 500
637
+ ]);
638
+
639
+ echo "QR code generated: <a href='qrcodes/{$filename}'>Download</a><br>";
640
+ echo "<img src='qrcodes/{$filename}' alt='QR Code' />";
641
+ } catch (Exception $e) {
642
+ echo "Error: " . $e->getMessage();
643
+ }
644
+ ```
645
+
646
+ ### API Endpoint
647
+
648
+ ```php
649
+ <?php
650
+ require 'vendor/autoload.php';
651
+
652
+ use FastQR\FastQR;
653
+
654
+ header('Content-Type: application/json');
655
+
656
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
657
+ http_response_code(405);
658
+ echo json_encode(['error' => 'Method not allowed']);
659
+ exit;
660
+ }
661
+
662
+ $input = json_decode(file_get_contents('php://input'), true);
663
+ $data = $input['data'] ?? null;
664
+
665
+ if (empty($data)) {
666
+ http_response_code(400);
667
+ echo json_encode(['error' => 'Data is required']);
668
+ exit;
669
+ }
670
+
671
+ try {
672
+ $filename = 'qr_' . uniqid() . '.png';
673
+ $path = __DIR__ . '/qrcodes/' . $filename;
674
+
675
+ if (!is_dir(dirname($path))) {
676
+ mkdir(dirname($path), 0755, true);
677
+ }
678
+
679
+ $options = [
680
+ 'width' => $input['width'] ?? 500,
681
+ 'height' => $input['height'] ?? 500,
682
+ 'errorLevel' => $input['errorLevel'] ?? 'M'
683
+ ];
684
+
685
+ FastQR::generate($data, $path, $options);
686
+
687
+ echo json_encode([
688
+ 'success' => true,
689
+ 'url' => '/qrcodes/' . $filename,
690
+ 'filename' => $filename
691
+ ]);
692
+ } catch (Exception $e) {
693
+ http_response_code(500);
694
+ echo json_encode(['error' => $e->getMessage()]);
695
+ }
696
+ ```
697
+
698
+ ## Error Handling
699
+
700
+ ```php
701
+ <?php
702
+ use FastQR\FastQR;
703
+
704
+ try {
705
+ FastQR::generate('Data', 'output.png', [
706
+ 'width' => 1000,
707
+ 'height' => 1000
708
+ ]);
709
+ echo "✓ QR code generated successfully\n";
710
+ } catch (RuntimeException $e) {
711
+ echo "✗ Failed to generate QR code: " . $e->getMessage() . "\n";
712
+ // Log error
713
+ error_log("QR generation failed: " . $e->getMessage());
714
+ }
715
+ ```
716
+
717
+ ## Testing
718
+
719
+ ### PHPUnit Example
720
+
721
+ ```php
722
+ <?php
723
+ use PHPUnit\Framework\TestCase;
724
+ use FastQR\FastQR;
725
+
726
+ class FastQRTest extends TestCase
727
+ {
728
+ private $outputPath;
729
+
730
+ protected function setUp(): void
731
+ {
732
+ $this->outputPath = __DIR__ . '/test_qr.png';
733
+ }
734
+
735
+ protected function tearDown(): void
736
+ {
737
+ if (file_exists($this->outputPath)) {
738
+ unlink($this->outputPath);
739
+ }
740
+ }
741
+
742
+ public function testGenerateBasicQR()
743
+ {
744
+ $result = FastQR::generate('Test Data', $this->outputPath);
745
+
746
+ $this->assertTrue($result);
747
+ $this->assertFileExists($this->outputPath);
748
+ $this->assertGreaterThan(0, filesize($this->outputPath));
749
+ }
750
+
751
+ public function testGenerateWithOptions()
752
+ {
753
+ $result = FastQR::generate('Test', $this->outputPath, [
754
+ 'size' => 500,
755
+ 'height' => 500,
756
+ 'errorLevel' => 'H'
757
+ ]);
758
+
759
+ $this->assertTrue($result);
760
+ $this->assertFileExists($this->outputPath);
761
+ }
762
+
763
+ public function testEmptyDataThrowsException()
764
+ {
765
+ $this->expectException(RuntimeException::class);
766
+ FastQR::generate('', $this->outputPath);
767
+ }
768
+
769
+ public function testVersion()
770
+ {
771
+ $version = FastQR::version();
772
+ $this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', $version);
773
+ }
774
+ }
775
+ ```
776
+
777
+ ## Performance Tips
778
+
779
+ - Cache generated QR codes using data hash as filename
780
+ - Use lower quality for web display (`quality` => 75)
781
+ - Use PNG for best quality, JPG for smaller files
782
+ - Clean up old temporary files regularly
783
+
784
+ ## Troubleshooting
785
+
786
+ ### "Library not found" Error
787
+
788
+ The package includes pre-built binaries. If you see this error, check PHP FFI:
789
+
790
+ ```php
791
+ <?php
792
+ // Check if FFI is enabled
793
+ if (!extension_loaded('ffi')) {
794
+ echo "FFI extension is not enabled\n";
795
+ } else {
796
+ echo "FFI extension is enabled\n";
797
+ }
798
+ ```
799
+
800
+ ### File Permission Errors
801
+
802
+ Ensure output directory has write permissions:
803
+
804
+ ```bash
805
+ chmod 755 storage/app/public/qrcodes
806
+ ```
807
+
808
+ ## See Also
809
+
810
+ - [CLI Usage](CLI_USAGE.md) - Command-line usage
811
+ - [Ruby Usage](RUBY_USAGE.md) - Ruby/Rails guide
812
+ - [Node.js Usage](NODEJS_USAGE.md) - Node.js guide
813
+ - [GitHub Repository](https://github.com/tranhuucanh/fastqr)
814
+ - [Packagist Package](https://packagist.org/packages/fastqr/fastqr)
815
+