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.
- checksums.yaml +7 -0
- data/BUILD.md +482 -0
- data/CHANGELOG.md +51 -0
- data/CMakeLists.txt +126 -0
- data/CONTRIBUTING.md +63 -0
- data/DISTRIBUTION.md +730 -0
- data/INSTALL.md +171 -0
- data/LICENSE +39 -0
- data/PREBUILT.md +171 -0
- data/README.md +312 -0
- data/VERSION +1 -0
- data/bindings/nodejs/binding.gyp +38 -0
- data/bindings/nodejs/fastqr_node.cpp +125 -0
- data/bindings/nodejs/index.d.ts +80 -0
- data/bindings/nodejs/index.js +187 -0
- data/bindings/nodejs/lib/platform.js +72 -0
- data/bindings/nodejs/package.json +45 -0
- data/bindings/nodejs/test/test.js +45 -0
- data/bindings/php/fastqr_php.cpp +85 -0
- data/bindings/php/src/FastQR.php +316 -0
- data/bindings/php/tests/FastQRTest.php +97 -0
- data/bindings/ruby/extconf.rb +29 -0
- data/bindings/ruby/fastqr_ruby.cpp +122 -0
- data/bindings/ruby/lib/fastqr/platform.rb +70 -0
- data/bindings/ruby/lib/fastqr/version.rb +6 -0
- data/bindings/ruby/lib/fastqr.rb +129 -0
- data/bindings/ruby/prebuilt/macos-arm64.tar.gz +1 -0
- data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +1 -0
- data/build.sh +109 -0
- data/cmake/fastqrConfig.cmake.in +6 -0
- data/composer.json +36 -0
- data/docs/CLI_USAGE.md +478 -0
- data/docs/NODEJS_USAGE.md +694 -0
- data/docs/PHP_USAGE.md +815 -0
- data/docs/README.md +191 -0
- data/docs/RUBY_USAGE.md +537 -0
- data/examples/CMakeLists.txt +7 -0
- data/examples/basic.cpp +58 -0
- data/include/fastqr.h +97 -0
- data/include/stb_image.h +7988 -0
- data/include/stb_image_write.h +1724 -0
- data/phpunit.xml +18 -0
- data/prebuilt/README.md +131 -0
- data/scripts/README.md +248 -0
- data/scripts/build-binaries.sh +98 -0
- data/scripts/build-local.sh +18 -0
- data/scripts/install.sh +87 -0
- data/scripts/release.sh +78 -0
- data/scripts/update-version.sh +58 -0
- data/src/cli.cpp +316 -0
- data/src/fastqr.cpp +665 -0
- data/test.sh +86 -0
- 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
|
+
|