fastqr 1.0.20 → 1.0.22

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * FastQR - Fast QR Code Generator
5
- * PHP Binding using FFI
5
+ * PHP Binding using CLI binary (no FFI needed!)
6
6
  *
7
7
  * Copyright (C) 2025 FastQR Project
8
8
  * Licensed under LGPL-2.1
@@ -10,21 +10,19 @@
10
10
 
11
11
  namespace FastQR;
12
12
 
13
- use FFI;
14
13
  use RuntimeException;
15
14
 
16
15
  class FastQR
17
16
  {
18
- private static ?FFI $ffi = null;
19
- private static bool $initialized = false;
17
+ private static ?string $cliPath = null;
20
18
 
21
19
  /**
22
- * Initialize FFI
20
+ * Find CLI binary
23
21
  */
24
- private static function init(): void
22
+ private static function findBinary(): string
25
23
  {
26
- if (self::$initialized) {
27
- return;
24
+ if (self::$cliPath !== null) {
25
+ return self::$cliPath;
28
26
  }
29
27
 
30
28
  // Detect platform
@@ -33,58 +31,30 @@ class FastQR
33
31
  if ($arch === 'x86_64' || $arch === 'amd64') {
34
32
  $arch = 'x86_64';
35
33
  } elseif ($arch === 'aarch64' || $arch === 'arm64') {
36
- $arch = 'arm64';
34
+ $arch = $os === 'macos' ? 'arm64' : 'aarch64';
37
35
  }
38
36
  $platform = "$os-$arch";
39
- $ext = $os === 'macos' ? 'dylib' : 'so';
40
-
41
- // Try to find the library (pre-built first, then system)
42
- $libPaths = [
43
- __DIR__ . "/../../prebuilt/$platform/lib/libfastqr.$ext", // Pre-built binary
44
- '/usr/local/lib/libfastqr.' . $ext, // System install
45
- '/usr/lib/libfastqr.' . $ext,
46
- __DIR__ . '/../../../build/libfastqr.' . $ext, // Local build
37
+
38
+ // Try to find the binary (pre-built first, then system)
39
+ $binaryPaths = [
40
+ __DIR__ . "/../../prebuilt/$platform/bin/fastqr", // Pre-built binary
41
+ '/usr/local/bin/fastqr', // System install
42
+ '/usr/bin/fastqr',
43
+ __DIR__ . '/../../../build/fastqr', // Local build
47
44
  ];
48
45
 
49
- $libPath = null;
50
- foreach ($libPaths as $path) {
51
- if (file_exists($path)) {
52
- $libPath = $path;
53
- break;
46
+ foreach ($binaryPaths as $path) {
47
+ if (file_exists($path) && is_executable($path)) {
48
+ self::$cliPath = $path;
49
+ return $path;
54
50
  }
55
51
  }
56
52
 
57
- if ($libPath === null) {
58
- throw new RuntimeException(
59
- "FastQR library not found for platform: $platform\n" .
60
- "Please install fastqr or build from source."
61
- );
62
- }
63
-
64
- // Define C interface
65
- $header = '
66
- typedef struct {
67
- int size;
68
- int optimize_size;
69
- unsigned char foreground_r;
70
- unsigned char foreground_g;
71
- unsigned char foreground_b;
72
- unsigned char background_r;
73
- unsigned char background_g;
74
- unsigned char background_b;
75
- int ec_level;
76
- const char* logo_path;
77
- int logo_size_percent;
78
- const char* format;
79
- int quality;
80
- } QROptions;
81
-
82
- bool fastqr_generate(const char* data, const char* output_path, QROptions* options);
83
- const char* fastqr_version(void);
84
- ';
85
-
86
- self::$ffi = FFI::cdef($header, $libPath);
87
- self::$initialized = true;
53
+ throw new RuntimeException(
54
+ "FastQR CLI binary not found for platform: $platform\n" .
55
+ "Searched in:\n" . implode("\n", $binaryPaths) . "\n" .
56
+ "Please install fastqr or build from source."
57
+ );
88
58
  }
89
59
 
90
60
  /**
@@ -127,8 +97,6 @@ class FastQR
127
97
  */
128
98
  public static function generate(string $data, string $outputPath, array $options = []): bool
129
99
  {
130
- self::init();
131
-
132
100
  if (empty($data)) {
133
101
  throw new RuntimeException('Data cannot be empty');
134
102
  }
@@ -136,56 +104,64 @@ class FastQR
136
104
  throw new RuntimeException('Output path cannot be empty');
137
105
  }
138
106
 
139
- // Create options struct
140
- $opts = self::$ffi->new('QROptions');
107
+ $cliPath = self::findBinary();
108
+
109
+ // Build command arguments
110
+ $args = [
111
+ escapeshellarg($cliPath),
112
+ escapeshellarg($data),
113
+ escapeshellarg($outputPath)
114
+ ];
141
115
 
142
116
  // Size (preferred) or width/height (backward compatibility)
143
117
  if (isset($options['size'])) {
144
- $opts->size = $options['size'];
118
+ $args[] = '-s ' . (int)$options['size'];
145
119
  } elseif (isset($options['width']) || isset($options['height'])) {
146
- $opts->size = $options['width'] ?? $options['height'] ?? 300;
147
- } else {
148
- $opts->size = 300;
120
+ $size = $options['width'] ?? $options['height'] ?? 300;
121
+ $args[] = '-s ' . (int)$size;
149
122
  }
150
123
 
151
124
  // Optimize size
152
- $opts->optimize_size = $options['optimizeSize'] ?? false ? 1 : 0;
125
+ if (!empty($options['optimizeSize'])) {
126
+ $args[] = '-o';
127
+ }
153
128
 
154
129
  // Foreground color
155
- $fg = $options['foreground'] ?? [0, 0, 0];
156
- $opts->foreground_r = $fg[0] ?? 0;
157
- $opts->foreground_g = $fg[1] ?? 0;
158
- $opts->foreground_b = $fg[2] ?? 0;
130
+ if (isset($options['foreground'])) {
131
+ $fg = $options['foreground'];
132
+ $args[] = '-f ' . implode(',', $fg);
133
+ }
159
134
 
160
135
  // Background color
161
- $bg = $options['background'] ?? [255, 255, 255];
162
- $opts->background_r = $bg[0] ?? 255;
163
- $opts->background_g = $bg[1] ?? 255;
164
- $opts->background_b = $bg[2] ?? 255;
136
+ if (isset($options['background'])) {
137
+ $bg = $options['background'];
138
+ $args[] = '-b ' . implode(',', $bg);
139
+ }
165
140
 
166
141
  // Error correction level
167
- $ecLevel = $options['errorLevel'] ?? 'M';
168
- $opts->ec_level = match($ecLevel) {
169
- 'L' => 0,
170
- 'M' => 1,
171
- 'Q' => 2,
172
- 'H' => 3,
173
- default => 1
174
- };
142
+ if (isset($options['errorLevel'])) {
143
+ $args[] = '-e ' . escapeshellarg($options['errorLevel']);
144
+ }
175
145
 
176
146
  // Logo
177
- $opts->logo_path = $options['logo'] ?? null;
178
- $opts->logo_size_percent = $options['logoSize'] ?? 20;
147
+ if (isset($options['logo'])) {
148
+ $args[] = '-l ' . escapeshellarg($options['logo']);
149
+ }
150
+ if (isset($options['logoSize'])) {
151
+ $args[] = '-p ' . (int)$options['logoSize'];
152
+ }
179
153
 
180
- // Format and quality
181
- $opts->format = $options['format'] ?? 'png';
182
- $opts->quality = $options['quality'] ?? 95;
154
+ // Quality
155
+ if (isset($options['quality'])) {
156
+ $args[] = '-q ' . (int)$options['quality'];
157
+ }
183
158
 
184
- // Call C function
185
- $result = self::$ffi->fastqr_generate($data, $outputPath, FFI::addr($opts));
159
+ // Execute command
160
+ $cmd = implode(' ', $args) . ' 2>&1';
161
+ exec($cmd, $output, $returnCode);
186
162
 
187
- if (!$result) {
188
- throw new RuntimeException('Failed to generate QR code');
163
+ if ($returnCode !== 0) {
164
+ throw new RuntimeException('Failed to generate QR code: ' . implode("\n", $output));
189
165
  }
190
166
 
191
167
  return true;
@@ -198,8 +174,15 @@ class FastQR
198
174
  */
199
175
  public static function version(): string
200
176
  {
201
- self::init();
202
- return self::$ffi->fastqr_version();
177
+ $cliPath = self::findBinary();
178
+ $cmd = escapeshellarg($cliPath) . ' -v 2>&1';
179
+ $output = shell_exec($cmd);
180
+
181
+ if ($output === null) {
182
+ return 'unknown';
183
+ }
184
+
185
+ return trim(str_replace('FastQR v', '', $output));
203
186
  }
204
187
 
205
188
  /**
@@ -241,65 +224,45 @@ class FastQR
241
224
  try {
242
225
  file_put_contents($tempFile, implode("\n", $dataArray));
243
226
 
244
- // Find CLI binary
245
- $os = PHP_OS_FAMILY === 'Darwin' ? 'macos' : (PHP_OS_FAMILY === 'Linux' ? 'linux' : 'unknown');
246
- $arch = php_uname('m');
247
- if ($arch === 'x86_64' || $arch === 'amd64') {
248
- $arch = 'x86_64';
249
- } elseif ($arch === 'aarch64' || $arch === 'arm64') {
250
- $arch = 'arm64';
251
- }
252
- $platform = "$os-$arch";
253
-
254
- $cliPaths = [
255
- __DIR__ . "/../../prebuilt/$platform/bin/fastqr",
256
- '/usr/local/bin/fastqr',
257
- __DIR__ . '/../../../build/fastqr',
258
- ];
259
-
260
- $cliPath = null;
261
- foreach ($cliPaths as $path) {
262
- if (file_exists($path)) {
263
- $cliPath = $path;
264
- break;
265
- }
266
- }
267
-
268
- if ($cliPath === null) {
269
- throw new RuntimeException("FastQR CLI not found for platform: $platform");
270
- }
227
+ $cliPath = self::findBinary();
271
228
 
272
229
  // Build command
273
- $cmd = escapeshellarg($cliPath) . ' -F ' . escapeshellarg($tempFile) . ' ' . escapeshellarg($outputDir);
230
+ $args = [
231
+ escapeshellarg($cliPath),
232
+ '-F',
233
+ escapeshellarg($tempFile),
234
+ escapeshellarg($outputDir)
235
+ ];
274
236
 
275
237
  if (isset($options['size'])) {
276
- $cmd .= ' -s ' . (int)$options['size'];
238
+ $args[] = '-s ' . (int)$options['size'];
277
239
  }
278
240
  if (!empty($options['optimizeSize'])) {
279
- $cmd .= ' -o';
241
+ $args[] = '-o';
280
242
  }
281
243
  if (isset($options['foreground'])) {
282
244
  $fg = $options['foreground'];
283
- $cmd .= ' -f ' . implode(',', $fg);
245
+ $args[] = '-f ' . implode(',', $fg);
284
246
  }
285
247
  if (isset($options['background'])) {
286
248
  $bg = $options['background'];
287
- $cmd .= ' -b ' . implode(',', $bg);
249
+ $args[] = '-b ' . implode(',', $bg);
288
250
  }
289
251
  if (isset($options['errorLevel'])) {
290
- $cmd .= ' -e ' . escapeshellarg($options['errorLevel']);
252
+ $args[] = '-e ' . escapeshellarg($options['errorLevel']);
291
253
  }
292
254
  if (isset($options['logo'])) {
293
- $cmd .= ' -l ' . escapeshellarg($options['logo']);
255
+ $args[] = '-l ' . escapeshellarg($options['logo']);
294
256
  }
295
257
  if (isset($options['logoSize'])) {
296
- $cmd .= ' -p ' . (int)$options['logoSize'];
258
+ $args[] = '-p ' . (int)$options['logoSize'];
297
259
  }
298
260
  if (isset($options['quality'])) {
299
- $cmd .= ' -q ' . (int)$options['quality'];
261
+ $args[] = '-q ' . (int)$options['quality'];
300
262
  }
301
263
 
302
- exec($cmd . ' 2>&1', $output, $returnCode);
264
+ $cmd = implode(' ', $args) . ' 2>&1';
265
+ exec($cmd, $output, $returnCode);
303
266
 
304
267
  if ($returnCode !== 0) {
305
268
  throw new RuntimeException('Batch generation failed: ' . implode("\n", $output));
@@ -313,4 +276,3 @@ class FastQR
313
276
  }
314
277
  }
315
278
  }
316
-
@@ -64,8 +64,8 @@ unless have_library('qrencode')
64
64
  abort "ERROR: libqrencode is required. Install it first."
65
65
  end
66
66
 
67
- unless have_library('vips')
68
- abort "ERROR: libvips is required. Install it first."
67
+ unless have_library('png')
68
+ abort "ERROR: libpng is required. Install it first."
69
69
  end
70
70
 
71
71
  # Check for headers
@@ -73,8 +73,8 @@ unless have_header('qrencode.h')
73
73
  abort "ERROR: qrencode.h not found"
74
74
  end
75
75
 
76
- unless have_header('vips/vips.h')
77
- abort "ERROR: vips/vips.h not found"
76
+ unless have_header('png.h')
77
+ abort "ERROR: png.h not found"
78
78
  end
79
79
 
80
80
  # Add C++14 support
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FastQR
4
- VERSION = "1.0.20"
4
+ VERSION = "1.0.22"
5
5
  end
6
6
 
data/build.sh CHANGED
@@ -49,16 +49,16 @@ if ! pkg-config --exists libqrencode; then
49
49
  fi
50
50
  echo "✅ libqrencode found: $(pkg-config --modversion libqrencode)"
51
51
 
52
- if ! pkg-config --exists vips; then
53
- echo "❌ libvips not found. Install with:"
52
+ if ! pkg-config --exists libpng; then
53
+ echo "❌ libpng not found. Install with:"
54
54
  if [[ "$OS" == "macos" ]]; then
55
- echo " brew install vips"
55
+ echo " brew install libpng"
56
56
  else
57
- echo " sudo apt-get install libvips-dev"
57
+ echo " sudo apt-get install libpng-dev"
58
58
  fi
59
59
  exit 1
60
60
  fi
61
- echo "✅ libvips found: $(pkg-config --modversion vips)"
61
+ echo "✅ libpng found: $(pkg-config --modversion libpng)"
62
62
 
63
63
  # Build
64
64
  echo ""
data/composer.json CHANGED
@@ -12,8 +12,7 @@
12
12
  }
13
13
  ],
14
14
  "require": {
15
- "php": ">=7.4",
16
- "ext-ffi": "*"
15
+ "php": ">=7.4"
17
16
  },
18
17
  "require-dev": {
19
18
  "phpunit/phpunit": "^9.0"
data/docs/NODEJS_USAGE.md CHANGED
@@ -5,13 +5,13 @@ Complete guide for using FastQR in Node.js and JavaScript applications.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install fastqr
8
+ npm install fastqr-pro
9
9
  ```
10
10
 
11
11
  Or with Yarn:
12
12
 
13
13
  ```bash
14
- yarn add fastqr
14
+ yarn add fastqr-pro
15
15
  ```
16
16
 
17
17
  **Note:** No system dependencies required! Pre-built binaries are included. 🎉
data/docs/README.md CHANGED
@@ -40,10 +40,10 @@ FastQR is a fast, powerful QR code generator with full UTF-8 support, custom col
40
40
  ### 📦 No Dependencies!
41
41
  Starting from v1.0.0, all language packages include pre-built binaries:
42
42
  - Ruby: `gem install fastqr` ✅
43
- - Node.js: `npm install fastqr` ✅
43
+ - Node.js: `npm install fastqr-pro` ✅
44
44
  - PHP: `composer require fastqr/fastqr` ✅
45
45
 
46
- No need to install libqrencode or libvips separately!
46
+ No need to install libqrencode separately!
47
47
 
48
48
  ## Quick Start
49
49
 
@@ -69,7 +69,7 @@ FastQR.generate("Hello World", "qr.png", width: 500, height: 500)
69
69
 
70
70
  ### Node.js
71
71
  ```javascript
72
- npm install fastqr
72
+ npm install fastqr-pro
73
73
 
74
74
  const fastqr = require('fastqr');
75
75
  fastqr.generate('Hello World', 'qr.png', { width: 500, height: 500 });
@@ -160,7 +160,8 @@ fastqr -s 2000x2000 -q 100 -e H "SKU-12345" product_qr.png
160
160
 
161
161
  FastQR is built on:
162
162
  - **[libqrencode](https://fukuchi.org/works/qrencode/)** (LGPL v2.1) - QR code generation
163
- - **[libvips](https://libvips.github.io/libvips/)** (LGPL v2.1+) - Image processing
163
+ - **[libpng](http://www.libpng.org/pub/png/libpng.html)** - PNG image encoding
164
+ - **[stb_image](https://github.com/nothings/stb)** (Public Domain) - Image loading
164
165
 
165
166
  Pre-built binaries are automatically generated for:
166
167
  - macOS (Intel & Apple Silicon)
data/prebuilt/README.md CHANGED
@@ -48,7 +48,7 @@ Pre-built binaries are automatically included in the gem and loaded via FFI.
48
48
 
49
49
  ```bash
50
50
  gem install fastqr
51
- # No need to install qrencode or vips!
51
+ # No need to install qrencode!
52
52
  ```
53
53
 
54
54
  ### For Node.js
@@ -56,8 +56,8 @@ gem install fastqr
56
56
  Pre-built binaries are automatically included in the npm package.
57
57
 
58
58
  ```bash
59
- npm install fastqr
60
- # No need to install qrencode or vips!
59
+ npm install fastqr-pro
60
+ # No need to install qrencode!
61
61
  ```
62
62
 
63
63
  ### For PHP
@@ -66,7 +66,7 @@ Pre-built binaries are included in the Composer package.
66
66
 
67
67
  ```bash
68
68
  composer require fastqr/fastqr
69
- # No need to install qrencode or vips!
69
+ # No need to install qrencode!
70
70
  ```
71
71
 
72
72
  ### Manual Installation
@@ -105,7 +105,7 @@ The bindings automatically detect your platform and load the appropriate binary:
105
105
 
106
106
  ## Notes
107
107
 
108
- - Binaries are built with static linking of libqrencode and libvips
108
+ - Binaries are built with static linking of libqrencode
109
109
  - No external dependencies required at runtime
110
110
  - Binaries are built on GitHub Actions for consistency
111
111
  - All binaries are tested before release
@@ -125,7 +125,6 @@ This will trigger the `build-binaries.yml` workflow which builds for all platfor
125
125
 
126
126
  The pre-built binaries include statically linked libraries:
127
127
  - libqrencode (LGPL 2.1)
128
- - libvips (LGPL 2.1+)
129
128
 
130
129
  As required by LGPL, users can rebuild with different versions. See BUILD.md for instructions.
131
130
 
data/src/fastqr.cpp CHANGED
@@ -20,12 +20,8 @@
20
20
  #include <vector>
21
21
  #include <algorithm>
22
22
  #include <cstdio>
23
- #include <chrono>
24
23
 
25
- // Enable benchmarking
26
- // #define FASTQR_BENCHMARK
27
-
28
- #define FASTQR_VERSION "1.0.20"
24
+ #define FASTQR_VERSION "1.0.22"
29
25
 
30
26
  namespace fastqr {
31
27
 
@@ -343,21 +339,12 @@ static void add_logo_to_image(std::vector<unsigned char>& qr_img, int qr_size, i
343
339
  }
344
340
 
345
341
  bool generate(const std::string& data, const std::string& output_path, const QROptions& options) {
346
- #ifdef FASTQR_BENCHMARK
347
- auto t_start = std::chrono::high_resolution_clock::now();
348
- #endif
349
-
350
342
  // Generate QR code
351
343
  auto qr = generate_qr_code(data, options.ec_level);
352
344
  if (!qr) {
353
345
  return false;
354
346
  }
355
347
 
356
- #ifdef FASTQR_BENCHMARK
357
- auto t_qr_gen = std::chrono::high_resolution_clock::now();
358
- auto dur_qr = std::chrono::duration_cast<std::chrono::microseconds>(t_qr_gen - t_start).count();
359
- #endif
360
-
361
348
  int qr_size = qr->width;
362
349
  unsigned char* qr_data = qr->data;
363
350
 
@@ -381,15 +368,6 @@ bool generate(const std::string& data, const std::string& output_path, const QRO
381
368
  // Pack 8 pixels into 1 byte
382
369
  int scale = final_size / qr_size;
383
370
 
384
- #ifdef FASTQR_BENCHMARK
385
- FILE* debug_log = fopen("/tmp/fastqr_debug.log", "a");
386
- if (debug_log) {
387
- fprintf(debug_log, "DEBUG: is_bw=%d, qr_size=%d, final_size=%d, scale=%d, scale*qr=%d\n",
388
- is_bw, qr_size, final_size, scale, scale*qr_size);
389
- fclose(debug_log);
390
- }
391
- #endif
392
-
393
371
  if (scale * qr_size == final_size) {
394
372
  // Integer scaling - optimized bit packing
395
373
  int bytes_per_row = (final_size + 7) / 8;
@@ -424,27 +402,7 @@ bool generate(const std::string& data, const std::string& output_path, const QRO
424
402
  }
425
403
  }
426
404
 
427
- #ifdef FASTQR_BENCHMARK
428
- auto t_scale = std::chrono::high_resolution_clock::now();
429
- auto dur_scale = std::chrono::duration_cast<std::chrono::microseconds>(t_scale - t_qr_gen).count();
430
- #endif
431
-
432
- bool result = write_indexed_png(output_path.c_str(), packed_data, final_size, final_size);
433
-
434
- #ifdef FASTQR_BENCHMARK
435
- auto t_write = std::chrono::high_resolution_clock::now();
436
- auto dur_write = std::chrono::duration_cast<std::chrono::microseconds>(t_write - t_scale).count();
437
- auto dur_total = std::chrono::duration_cast<std::chrono::microseconds>(t_write - t_start).count();
438
-
439
- FILE* timing_log = fopen("/tmp/fastqr_timing.log", "a");
440
- if (timing_log) {
441
- fprintf(timing_log, "TIMING: QR=%ldus, Scale=%ldus, Write=%ldus, Total=%ldus\n",
442
- dur_qr, dur_scale, dur_write, dur_total);
443
- fclose(timing_log);
444
- }
445
- #endif
446
-
447
- return result;
405
+ return write_indexed_png(output_path.c_str(), packed_data, final_size, final_size);
448
406
  } else {
449
407
  // Non-integer scaling - use grayscale
450
408
  std::vector<unsigned char> final_image(final_size * final_size);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastqr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.20
4
+ version: 1.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - FastQR Project
@@ -52,8 +52,8 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
- description: FastQR is a fast QR code generator with full UTF-8 support, custom colors,
56
- logo embedding, and precise size control. Built on libqrencode and libvips.
55
+ description: Generate 1,000 QR codes in **0.37 seconds**. Full UTF-8 support. Custom
56
+ colors. Logo embedding. Precise size control.
57
57
  email:
58
58
  - fastqr@example.com
59
59
  executables: []
@@ -71,6 +71,7 @@ files:
71
71
  - PREBUILT.md
72
72
  - README.md
73
73
  - VERSION
74
+ - bindings/nodejs/README.md
74
75
  - bindings/nodejs/index.d.ts
75
76
  - bindings/nodejs/index.js
76
77
  - bindings/nodejs/lib/platform.js
@@ -158,5 +159,5 @@ requirements: []
158
159
  rubygems_version: 3.4.19
159
160
  signing_key:
160
161
  specification_version: 4
161
- summary: Fast QR code generator with UTF-8 support
162
+ summary: The fastest QR code generator on the planet.
162
163
  test_files: []