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,38 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "fastqr",
5
+ "sources": [
6
+ "fastqr_node.cpp",
7
+ "../../src/fastqr.cpp"
8
+ ],
9
+ "include_dirs": [
10
+ "<!@(node -p \"require('node-addon-api').include\")",
11
+ "../../include"
12
+ ],
13
+ "libraries": [
14
+ "-lqrencode",
15
+ "-lvips"
16
+ ],
17
+ "cflags!": [ "-fno-exceptions" ],
18
+ "cflags_cc!": [ "-fno-exceptions" ],
19
+ "cflags_cc": [ "-std=c++14" ],
20
+ "xcode_settings": {
21
+ "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
22
+ "CLANG_CXX_LIBRARY": "libc++",
23
+ "MACOSX_DEPLOYMENT_TARGET": "10.15",
24
+ "OTHER_CFLAGS": [
25
+ "-std=c++14"
26
+ ]
27
+ },
28
+ "msvs_settings": {
29
+ "VCCLCompilerTool": { "ExceptionHandling": 1 }
30
+ },
31
+ "dependencies": [
32
+ "<!(node -p \"require('node-addon-api').gyp\")"
33
+ ],
34
+ "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
35
+ }
36
+ ]
37
+ }
38
+
@@ -0,0 +1,125 @@
1
+ /*
2
+ * FastQR Node.js Binding
3
+ * Copyright (C) 2025 FastQR Project
4
+ */
5
+
6
+ #include "fastqr.h"
7
+ #include <napi.h>
8
+
9
+ // Convert JS object to QROptions
10
+ fastqr::QROptions js_to_options(const Napi::Object& obj) {
11
+ fastqr::QROptions options;
12
+
13
+ // Size (preferred) or width/height (backward compatibility)
14
+ if (obj.Has("size")) {
15
+ options.size = obj.Get("size").As<Napi::Number>().Int32Value();
16
+ } else {
17
+ if (obj.Has("width")) {
18
+ options.size = obj.Get("width").As<Napi::Number>().Int32Value();
19
+ }
20
+ if (obj.Has("height")) {
21
+ options.size = obj.Get("height").As<Napi::Number>().Int32Value();
22
+ }
23
+ }
24
+
25
+ // Optimize size
26
+ if (obj.Has("optimizeSize")) {
27
+ options.optimize_size = obj.Get("optimizeSize").As<Napi::Boolean>().Value();
28
+ }
29
+
30
+ // Foreground color
31
+ if (obj.Has("foreground")) {
32
+ auto fg = obj.Get("foreground").As<Napi::Array>();
33
+ options.foreground.r = fg.Get(uint32_t(0)).As<Napi::Number>().Uint32Value();
34
+ options.foreground.g = fg.Get(uint32_t(1)).As<Napi::Number>().Uint32Value();
35
+ options.foreground.b = fg.Get(uint32_t(2)).As<Napi::Number>().Uint32Value();
36
+ }
37
+
38
+ // Background color
39
+ if (obj.Has("background")) {
40
+ auto bg = obj.Get("background").As<Napi::Array>();
41
+ options.background.r = bg.Get(uint32_t(0)).As<Napi::Number>().Uint32Value();
42
+ options.background.g = bg.Get(uint32_t(1)).As<Napi::Number>().Uint32Value();
43
+ options.background.b = bg.Get(uint32_t(2)).As<Napi::Number>().Uint32Value();
44
+ }
45
+
46
+ // Error correction level
47
+ if (obj.Has("errorLevel")) {
48
+ std::string level = obj.Get("errorLevel").As<Napi::String>().Utf8Value();
49
+ if (level == "L") options.ec_level = fastqr::ErrorCorrectionLevel::LOW;
50
+ else if (level == "M") options.ec_level = fastqr::ErrorCorrectionLevel::MEDIUM;
51
+ else if (level == "Q") options.ec_level = fastqr::ErrorCorrectionLevel::QUARTILE;
52
+ else if (level == "H") options.ec_level = fastqr::ErrorCorrectionLevel::HIGH;
53
+ }
54
+
55
+ // Logo
56
+ if (obj.Has("logo")) {
57
+ options.logo_path = obj.Get("logo").As<Napi::String>().Utf8Value();
58
+ }
59
+ if (obj.Has("logoSize")) {
60
+ options.logo_size_percent = obj.Get("logoSize").As<Napi::Number>().Int32Value();
61
+ }
62
+
63
+ // Quality and format
64
+ if (obj.Has("quality")) {
65
+ options.quality = obj.Get("quality").As<Napi::Number>().Int32Value();
66
+ }
67
+ if (obj.Has("format")) {
68
+ options.format = obj.Get("format").As<Napi::String>().Utf8Value();
69
+ }
70
+
71
+ return options;
72
+ }
73
+
74
+ // generate(data, outputPath, options)
75
+ Napi::Value Generate(const Napi::CallbackInfo& info) {
76
+ Napi::Env env = info.Env();
77
+
78
+ if (info.Length() < 2) {
79
+ Napi::TypeError::New(env, "Expected at least 2 arguments")
80
+ .ThrowAsJavaScriptException();
81
+ return env.Null();
82
+ }
83
+
84
+ if (!info[0].IsString()) {
85
+ Napi::TypeError::New(env, "First argument must be a string")
86
+ .ThrowAsJavaScriptException();
87
+ return env.Null();
88
+ }
89
+
90
+ if (!info[1].IsString()) {
91
+ Napi::TypeError::New(env, "Second argument must be a string")
92
+ .ThrowAsJavaScriptException();
93
+ return env.Null();
94
+ }
95
+
96
+ std::string data = info[0].As<Napi::String>().Utf8Value();
97
+ std::string output_path = info[1].As<Napi::String>().Utf8Value();
98
+
99
+ fastqr::QROptions options;
100
+ if (info.Length() >= 3 && info[2].IsObject()) {
101
+ options = js_to_options(info[2].As<Napi::Object>());
102
+ }
103
+
104
+ bool result = fastqr::generate(data, output_path, options);
105
+
106
+ return Napi::Boolean::New(env, result);
107
+ }
108
+
109
+ // version()
110
+ Napi::Value Version(const Napi::CallbackInfo& info) {
111
+ Napi::Env env = info.Env();
112
+ return Napi::String::New(env, fastqr::version());
113
+ }
114
+
115
+ // Initialize module
116
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
117
+ exports.Set("generate", Napi::Function::New(env, Generate));
118
+ exports.Set("version", Napi::Function::New(env, Version));
119
+ exports.Set("VERSION", Napi::String::New(env, fastqr::version()));
120
+
121
+ return exports;
122
+ }
123
+
124
+ NODE_API_MODULE(fastqr, Init)
125
+
@@ -0,0 +1,80 @@
1
+ /**
2
+ * FastQR TypeScript definitions
3
+ */
4
+
5
+ export interface Color {
6
+ r: number;
7
+ g: number;
8
+ b: number;
9
+ }
10
+
11
+ export interface QROptions {
12
+ /** Output size in pixels (default: 300, QR codes are square) */
13
+ size?: number;
14
+
15
+ /** Auto round-up to nearest integer multiple for best performance (default: false) */
16
+ optimizeSize?: boolean;
17
+
18
+ /** @deprecated Use size instead */
19
+ width?: number;
20
+
21
+ /** @deprecated Use size instead */
22
+ height?: number;
23
+
24
+ /** QR code color as [R, G, B] (default: [0, 0, 0]) */
25
+ foreground?: [number, number, number];
26
+
27
+ /** Background color as [R, G, B] (default: [255, 255, 255]) */
28
+ background?: [number, number, number];
29
+
30
+ /** Error correction level: 'L', 'M', 'Q', 'H' (default: 'M') */
31
+ errorLevel?: 'L' | 'M' | 'Q' | 'H';
32
+
33
+ /** Path to logo image */
34
+ logo?: string;
35
+
36
+ /** Logo size as percentage (default: 20) */
37
+ logoSize?: number;
38
+
39
+ /** Image quality 1-100 (default: 95) */
40
+ quality?: number;
41
+
42
+ /** Output format: 'png', 'jpg', 'webp' (default: 'png') */
43
+ format?: 'png' | 'jpg' | 'jpeg' | 'webp';
44
+ }
45
+
46
+ /**
47
+ * Generate QR code
48
+ * @param data - Data to encode (UTF-8 supported)
49
+ * @param outputPath - Path to save the QR code image
50
+ * @param options - Generation options
51
+ * @returns true if successful
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import * as fastqr from 'fastqr';
56
+ *
57
+ * // Basic usage
58
+ * fastqr.generate('Hello World', 'qr.png');
59
+ *
60
+ * // With options
61
+ * fastqr.generate('Hello', 'qr.png', {
62
+ * size: 500,
63
+ * optimizeSize: true,
64
+ * foreground: [255, 0, 0],
65
+ * background: [255, 255, 200],
66
+ * errorLevel: 'H'
67
+ * });
68
+ * ```
69
+ */
70
+ export function generate(data: string, outputPath: string, options?: QROptions): boolean;
71
+
72
+ /**
73
+ * Get library version
74
+ * @returns Version string
75
+ */
76
+ export function version(): string;
77
+
78
+ /** Library version constant */
79
+ export const VERSION: string;
80
+
@@ -0,0 +1,187 @@
1
+ /**
2
+ * FastQR - Fast QR Code Generator
3
+ * Node.js Binding
4
+ */
5
+
6
+ const platform = require('./lib/platform');
7
+ const ffi = require('ffi-napi');
8
+ const ref = require('ref-napi');
9
+ const path = require('path');
10
+
11
+ let fastqr;
12
+
13
+ // Try to load pre-built binary first, fall back to compiled addon
14
+ if (platform.isPrebuiltAvailable()) {
15
+ // Load via FFI
16
+ const libPath = platform.getPrebuiltPath();
17
+
18
+ const lib = ffi.Library(libPath, {
19
+ 'fastqr_generate': ['bool', ['string', 'string', 'pointer']],
20
+ 'fastqr_version': ['string', []]
21
+ });
22
+
23
+ // Wrap FFI functions to match Node addon interface
24
+ fastqr = {
25
+ generate: function(data, outputPath, options = {}) {
26
+ // TODO: Convert options to C struct
27
+ // For now, use default options (NULL pointer)
28
+ return lib.fastqr_generate(data, outputPath, ref.NULL);
29
+ },
30
+ version: function() {
31
+ return lib.fastqr_version();
32
+ },
33
+ VERSION: lib.fastqr_version()
34
+ };
35
+ } else if (platform.isAddonAvailable()) {
36
+ // Load compiled addon
37
+ fastqr = require(platform.getAddonPath());
38
+ } else {
39
+ throw new Error(
40
+ 'FastQR native binding not found. ' +
41
+ 'Please run: npm install --build-from-source'
42
+ );
43
+ }
44
+
45
+ /**
46
+ * QR code generation options
47
+ * @typedef {Object} QROptions
48
+ * @property {number} [width=300] - Output width in pixels
49
+ * @property {number} [height=300] - Output height in pixels
50
+ * @property {number[]} [foreground=[0,0,0]] - QR code color as [R, G, B]
51
+ * @property {number[]} [background=[255,255,255]] - Background color as [R, G, B]
52
+ * @property {string} [errorLevel='M'] - Error correction level: 'L', 'M', 'Q', 'H'
53
+ * @property {string} [logo] - Path to logo image
54
+ * @property {number} [logoSize=20] - Logo size as percentage
55
+ * @property {number} [quality=95] - Image quality (1-100)
56
+ * @property {string} [format='png'] - Output format: 'png', 'jpg', 'webp'
57
+ */
58
+
59
+ /**
60
+ * Generate QR code
61
+ * @param {string} data - Data to encode (UTF-8 supported)
62
+ * @param {string} outputPath - Path to save the QR code image
63
+ * @param {QROptions} [options={}] - Generation options
64
+ * @returns {boolean} true if successful
65
+ *
66
+ * @example
67
+ * const fastqr = require('fastqr');
68
+ *
69
+ * // Basic usage
70
+ * fastqr.generate('Hello World', 'qr.png');
71
+ *
72
+ * // With options
73
+ * fastqr.generate('Hello', 'qr.png', {
74
+ * width: 500,
75
+ * height: 500,
76
+ * foreground: [255, 0, 0],
77
+ * background: [255, 255, 200],
78
+ * errorLevel: 'H'
79
+ * });
80
+ *
81
+ * // With logo
82
+ * fastqr.generate('Company', 'qr.png', {
83
+ * width: 600,
84
+ * height: 600,
85
+ * logo: 'logo.png',
86
+ * logoSize: 25
87
+ * });
88
+ *
89
+ * // UTF-8 support
90
+ * fastqr.generate('Xin chΓ o Việt Nam! πŸ‡»πŸ‡³', 'vietnamese.png');
91
+ * fastqr.generate('こんにけはζ—₯本', 'japanese.png');
92
+ */
93
+ function generate(data, outputPath, options = {}) {
94
+ if (!data || typeof data !== 'string') {
95
+ throw new TypeError('Data must be a non-empty string');
96
+ }
97
+ if (!outputPath || typeof outputPath !== 'string') {
98
+ throw new TypeError('Output path must be a non-empty string');
99
+ }
100
+
101
+ const result = fastqr.generate(data, outputPath, options);
102
+ if (!result) {
103
+ throw new Error('Failed to generate QR code');
104
+ }
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Get library version
110
+ * @returns {string} Version string
111
+ */
112
+ function version() {
113
+ return fastqr.version();
114
+ }
115
+
116
+ /**
117
+ * Generate multiple QR codes in batch mode (7x faster!)
118
+ * @param {string[]} dataArray - Array of strings to encode
119
+ * @param {string} outputDir - Directory to save QR codes (will be created if it doesn't exist)
120
+ * @param {QROptions} [options={}] - Generation options (same as generate)
121
+ * @returns {Object} Result with success and failed counts
122
+ *
123
+ * @example
124
+ * const fastqr = require('fastqr');
125
+ *
126
+ * // Batch generation
127
+ * const data = ['QR 1', 'QR 2', 'QR 3'];
128
+ * fastqr.generateBatch(data, 'output_dir/', { size: 500 });
129
+ * // Creates: output_dir/1.png, output_dir/2.png, output_dir/3.png
130
+ */
131
+ function generateBatch(dataArray, outputDir, options = {}) {
132
+ if (!Array.isArray(dataArray) || dataArray.length === 0) {
133
+ throw new TypeError('Data array must be a non-empty array');
134
+ }
135
+ if (!outputDir || typeof outputDir !== 'string') {
136
+ throw new TypeError('Output directory must be a non-empty string');
137
+ }
138
+
139
+ const fs = require('fs');
140
+ const { execFileSync } = require('child_process');
141
+ const os = require('os');
142
+ const path = require('path');
143
+
144
+ // Create output directory
145
+ if (!fs.existsSync(outputDir)) {
146
+ fs.mkdirSync(outputDir, { recursive: true });
147
+ }
148
+
149
+ // Create temporary batch file
150
+ const tempFile = path.join(os.tmpdir(), `fastqr_batch_${Date.now()}.txt`);
151
+ try {
152
+ fs.writeFileSync(tempFile, dataArray.join('\n'), 'utf8');
153
+
154
+ // Get CLI path
155
+ const cliPath = platform.getPrebuiltPath().replace('.dylib', '').replace('.so', '');
156
+ const actualCliPath = cliPath.endsWith('fastqr') ? cliPath : path.join(path.dirname(cliPath), 'fastqr');
157
+
158
+ // Build command arguments
159
+ const args = ['-F', tempFile, outputDir];
160
+ if (options.size) args.push('-s', options.size.toString());
161
+ if (options.optimizeSize) args.push('-o');
162
+ if (options.foreground) args.push('-f', options.foreground.join(','));
163
+ if (options.background) args.push('-b', options.background.join(','));
164
+ if (options.errorLevel) args.push('-e', options.errorLevel);
165
+ if (options.logo) args.push('-l', options.logo);
166
+ if (options.logoSize) args.push('-p', options.logoSize.toString());
167
+ if (options.quality) args.push('-q', options.quality.toString());
168
+
169
+ execFileSync(actualCliPath, args, { stdio: 'pipe' });
170
+
171
+ return { success: dataArray.length, failed: 0 };
172
+ } catch (error) {
173
+ throw new Error(`Batch generation failed: ${error.message}`);
174
+ } finally {
175
+ if (fs.existsSync(tempFile)) {
176
+ fs.unlinkSync(tempFile);
177
+ }
178
+ }
179
+ }
180
+
181
+ module.exports = {
182
+ generate,
183
+ generateBatch,
184
+ version,
185
+ VERSION: fastqr.VERSION
186
+ };
187
+
@@ -0,0 +1,72 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { execSync } = require('child_process');
5
+
6
+ /**
7
+ * Detects the current platform and architecture
8
+ * @returns {string} Platform identifier (e.g., 'macos-arm64', 'linux-x86_64')
9
+ */
10
+ function detectPlatform() {
11
+ const platform = os.platform();
12
+ const arch = os.arch();
13
+
14
+ if (platform === 'darwin') {
15
+ if (arch === 'arm64') return 'macos-arm64';
16
+ if (arch === 'x64') return 'macos-x86_64';
17
+ } else if (platform === 'linux') {
18
+ if (arch === 'x64') return 'linux-x86_64';
19
+ if (arch === 'arm64') return 'linux-arm64';
20
+ }
21
+
22
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
23
+ }
24
+
25
+ /**
26
+ * Extracts pre-built binary from tarball
27
+ * @param {string} tarballPath - Path to the tarball
28
+ * @param {string} destDir - Destination directory
29
+ */
30
+ function extractBinary(tarballPath, destDir) {
31
+ if (!fs.existsSync(destDir)) {
32
+ fs.mkdirSync(destDir, { recursive: true });
33
+ }
34
+
35
+ execSync(`tar -xzf "${tarballPath}" -C "${destDir}"`, { stdio: 'inherit' });
36
+ }
37
+
38
+ /**
39
+ * Finds the fastqr binary in prebuilt directory
40
+ * @returns {string} Path to fastqr binary
41
+ */
42
+ function findFastQRBinary() {
43
+ const platform = detectPlatform();
44
+ const prebuiltDir = path.join(__dirname, '..', 'prebuilt', platform);
45
+ const binaryPath = path.join(prebuiltDir, 'fastqr');
46
+
47
+ if (fs.existsSync(binaryPath)) {
48
+ // Make sure it's executable
49
+ fs.chmodSync(binaryPath, 0o755);
50
+ return binaryPath;
51
+ }
52
+
53
+ // Try to extract from tarball
54
+ const tarballPath = path.join(__dirname, '..', 'prebuilt', `${platform}.tar.gz`);
55
+ if (fs.existsSync(tarballPath)) {
56
+ console.log(`Extracting pre-built binary from ${tarballPath}...`);
57
+ extractBinary(tarballPath, prebuiltDir);
58
+
59
+ if (fs.existsSync(binaryPath)) {
60
+ fs.chmodSync(binaryPath, 0o755);
61
+ return binaryPath;
62
+ }
63
+ }
64
+
65
+ throw new Error(`Pre-built binary not found for ${platform}`);
66
+ }
67
+
68
+ module.exports = {
69
+ detectPlatform,
70
+ extractBinary,
71
+ findFastQRBinary
72
+ };
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "fastqr",
3
+ "version": "1.0.1",
4
+ "description": "Fast QR code generator with UTF-8 support, custom colors, logo embedding, and precise size control",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "install": "node-gyp rebuild",
9
+ "test": "node test/test.js"
10
+ },
11
+ "keywords": [
12
+ "qr",
13
+ "qrcode",
14
+ "qr-code",
15
+ "generator",
16
+ "utf8",
17
+ "unicode",
18
+ "vietnamese",
19
+ "japanese",
20
+ "fast",
21
+ "vips",
22
+ "image"
23
+ ],
24
+ "author": "FastQR Project",
25
+ "license": "LGPL-2.1",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/tranhuucanh/fastqr.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/tranhuucanh/fastqr/issues"
32
+ },
33
+ "homepage": "https://github.com/tranhuucanh/fastqr#readme",
34
+ "dependencies": {
35
+ "node-gyp": "^9.0.0",
36
+ "ffi-napi": "^4.0.3",
37
+ "ref-napi": "^3.0.3"
38
+ },
39
+ "devDependencies": {},
40
+ "gypfile": true,
41
+ "engines": {
42
+ "node": ">=14.0.0"
43
+ }
44
+ }
45
+
@@ -0,0 +1,45 @@
1
+ const fastqr = require('./index');
2
+
3
+ console.log('FastQR Node.js Test');
4
+ console.log('Version:', fastqr.version());
5
+ console.log('');
6
+
7
+ try {
8
+ // Basic test
9
+ console.log('Test 1: Basic QR code...');
10
+ fastqr.generate('Hello, FastQR from Node.js!', 'test_basic.png');
11
+ console.log('βœ“ Generated test_basic.png');
12
+
13
+ // Colored QR
14
+ console.log('\nTest 2: Colored QR code...');
15
+ fastqr.generate('Colored QR', 'test_colored.png', {
16
+ width: 500,
17
+ height: 500,
18
+ foreground: [255, 0, 0],
19
+ background: [255, 255, 200]
20
+ });
21
+ console.log('βœ“ Generated test_colored.png');
22
+
23
+ // UTF-8 Vietnamese
24
+ console.log('\nTest 3: Vietnamese text...');
25
+ fastqr.generate('Xin chΓ o Việt Nam! πŸ‡»πŸ‡³', 'test_vietnamese.png', {
26
+ width: 400,
27
+ height: 400
28
+ });
29
+ console.log('βœ“ Generated test_vietnamese.png');
30
+
31
+ // UTF-8 Japanese
32
+ console.log('\nTest 4: Japanese text...');
33
+ fastqr.generate('こんにけはζ—₯本', 'test_japanese.png', {
34
+ width: 400,
35
+ height: 400,
36
+ foreground: [0, 0, 255]
37
+ });
38
+ console.log('βœ“ Generated test_japanese.png');
39
+
40
+ console.log('\nβœ“ All tests passed!');
41
+ } catch (error) {
42
+ console.error('βœ— Test failed:', error.message);
43
+ process.exit(1);
44
+ }
45
+
@@ -0,0 +1,85 @@
1
+ /*
2
+ * FastQR PHP C Wrapper
3
+ * Copyright (C) 2025 FastQR Project
4
+ */
5
+
6
+ #include "fastqr.h"
7
+ #include <cstring>
8
+
9
+ extern "C" {
10
+
11
+ // C-compatible options structure
12
+ struct QROptions_C {
13
+ int size;
14
+ int optimize_size;
15
+ unsigned char foreground_r;
16
+ unsigned char foreground_g;
17
+ unsigned char foreground_b;
18
+ unsigned char background_r;
19
+ unsigned char background_g;
20
+ unsigned char background_b;
21
+ int ec_level; // 0=L, 1=M, 2=Q, 3=H
22
+ const char* logo_path;
23
+ int logo_size_percent;
24
+ const char* format;
25
+ int quality;
26
+ };
27
+
28
+ // Convert C struct to C++ options
29
+ fastqr::QROptions c_to_cpp_options(const QROptions_C* c_opts) {
30
+ fastqr::QROptions opts;
31
+
32
+ if (c_opts) {
33
+ opts.size = c_opts->size;
34
+ opts.optimize_size = c_opts->optimize_size != 0;
35
+
36
+ opts.foreground.r = c_opts->foreground_r;
37
+ opts.foreground.g = c_opts->foreground_g;
38
+ opts.foreground.b = c_opts->foreground_b;
39
+
40
+ opts.background.r = c_opts->background_r;
41
+ opts.background.g = c_opts->background_g;
42
+ opts.background.b = c_opts->background_b;
43
+
44
+ switch (c_opts->ec_level) {
45
+ case 0: opts.ec_level = fastqr::ErrorCorrectionLevel::LOW; break;
46
+ case 1: opts.ec_level = fastqr::ErrorCorrectionLevel::MEDIUM; break;
47
+ case 2: opts.ec_level = fastqr::ErrorCorrectionLevel::QUARTILE; break;
48
+ case 3: opts.ec_level = fastqr::ErrorCorrectionLevel::HIGH; break;
49
+ default: opts.ec_level = fastqr::ErrorCorrectionLevel::MEDIUM; break;
50
+ }
51
+
52
+ if (c_opts->logo_path) {
53
+ opts.logo_path = c_opts->logo_path;
54
+ }
55
+ opts.logo_size_percent = c_opts->logo_size_percent;
56
+
57
+ if (c_opts->format) {
58
+ opts.format = c_opts->format;
59
+ }
60
+ opts.quality = c_opts->quality;
61
+ }
62
+
63
+ return opts;
64
+ }
65
+
66
+ // C-compatible generate function
67
+ bool fastqr_generate(const char* data, const char* output_path, QROptions_C* options) {
68
+ if (!data || !output_path) {
69
+ return false;
70
+ }
71
+
72
+ std::string data_str(data);
73
+ std::string output_str(output_path);
74
+ fastqr::QROptions opts = c_to_cpp_options(options);
75
+
76
+ return fastqr::generate(data_str, output_str, opts);
77
+ }
78
+
79
+ // C-compatible version function
80
+ const char* fastqr_version(void) {
81
+ return fastqr::version();
82
+ }
83
+
84
+ } // extern "C"
85
+