Files
cell/source/qjs_qr.c
John Alanbrook 0ea21e86eb
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m15s
Build and Deploy / build-windows (CLANG64) (push) Successful in 9m2s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
add qr code encode and decode
2025-03-03 08:06:18 -06:00

208 lines
6.5 KiB
C

#include "quickjs.h"
#include "quirc.h"
#include "qrencode.h"
#include <math.h> // for sqrt
#include <stdlib.h> // for size_t, etc.
#include <string.h> // for memcpy, strcmp
#include <errno.h>
// QR encode function
static JSValue js_qr_encode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(js, "encode expects a string or ArrayBuffer as first argument");
}
// Default options
int version = 0; // 0 means auto-select in qrencode
int errc = QR_ECLEVEL_L; // Default to "L"
int casesensitive = 1; // Default to true (qrencode treats 1 as case-sensitive)
// Handle options object (argv[1])
if (argc > 1 && !JS_IsUndefined(argv[1]) && JS_IsObject(argv[1])) {
JSValue opt = argv[1];
// Version (1-40)
JSValue v = JS_GetPropertyStr(js, opt, "version");
if (!JS_IsUndefined(v)) {
int32_t ver;
if (JS_ToInt32(js, &ver, v) || ver < 0 || ver > 40) {
JS_FreeValue(js, v);
return JS_ThrowRangeError(js, "version must be between 0 and 40");
}
version = ver;
JS_FreeValue(js, v);
}
// Error correction level ("l", "m", "q", "h")
JSValue l = JS_GetPropertyStr(js, opt, "level");
if (!JS_IsUndefined(l)) {
const char *level = JS_ToCString(js, l);
if (!level) {
JS_FreeValue(js, l);
return JS_ThrowTypeError(js, "level must be a string");
}
if (strcasecmp(level, "l") == 0) errc = QR_ECLEVEL_L;
else if (strcasecmp(level, "m") == 0) errc = QR_ECLEVEL_M;
else if (strcasecmp(level, "q") == 0) errc = QR_ECLEVEL_Q;
else if (strcasecmp(level, "h") == 0) errc = QR_ECLEVEL_H;
else {
JS_FreeCString(js, level);
JS_FreeValue(js, l);
return JS_ThrowRangeError(js, "level must be 'l', 'm', 'q', or 'h'");
}
JS_FreeCString(js, level);
JS_FreeValue(js, l);
}
// Case sensitivity (true/false)
JSValue ci = JS_GetPropertyStr(js, opt, "caseinsensitive");
if (!JS_IsUndefined(ci)) {
int bool_val;
if (JS_ToBool(js, ci)) {
bool_val = 0; // qrencode: 0 = case-insensitive
} else {
bool_val = 1; // qrencode: 1 = case-sensitive
}
casesensitive = bool_val;
JS_FreeValue(js, ci);
}
}
const char *data = NULL;
size_t data_len = 0;
int must_free_data = 0;
// Handle string input
if (JS_IsString(argv[0])) {
data = JS_ToCStringLen(js, &data_len, argv[0]);
if (!data) {
return JS_ThrowTypeError(js, "Invalid string input");
}
must_free_data = 1;
} else {
// Check if it's an ArrayBuffer
uint8_t *buf = JS_GetArrayBuffer(js, &data_len, argv[0]);
if (!buf) {
return JS_ThrowTypeError(js, "encode expects a string or ArrayBuffer");
}
data = (const char *)buf; // Cast to char* for qrencode
}
// Encode using qrencode
QRcode *qrcode = QRcode_encodeString(data, version, errc, QR_MODE_8, casesensitive);
if (!qrcode) {
if (must_free_data)
JS_FreeCString(js, data);
return JS_ThrowInternalError(js, "Failed to encode QR code: %s", strerror(errno));
}
// qrcode->width is the size of one side
// qrcode->data is width * width bytes
int width = qrcode->width;
size_t size = (size_t)width * width; // total modules
// Create an array of booleans for the module data
JSValue dataArr = JS_NewArray(js);
for (size_t i = 0; i < size; i++) {
int is_black = (qrcode->data[i] & 1);
JSValue val = JS_NewBool(js, is_black);
JS_SetPropertyUint32(js, dataArr, (uint32_t)i, val);
}
// Build a JS object to hold { width, data }
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, result, "data", dataArr);
// Clean up
QRcode_free(qrcode);
if (must_free_data) {
JS_FreeCString(js, data);
}
return result;
}
// QR decode function (unchanged)
static JSValue js_qr_decode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
size_t data_len;
uint8_t *data;
if (argc < 1 || !(data = JS_GetArrayBuffer(js, &data_len, argv[0]))) {
return JS_ThrowTypeError(js, "decode expects an ArrayBuffer");
}
struct quirc *qr = quirc_new();
if (!qr) {
return JS_ThrowInternalError(js, "Failed to initialize QR decoder");
}
int width = (int)sqrt((double)data_len);
if (width * width != (int)data_len) {
quirc_destroy(qr);
return JS_ThrowTypeError(js, "ArrayBuffer must represent a square image");
}
if (quirc_resize(qr, width, width) < 0) {
quirc_destroy(qr);
return JS_ThrowInternalError(js, "Failed to resize QR decoder");
}
int h;
uint8_t *image = quirc_begin(qr, &width, &h);
memcpy(image, data, data_len);
quirc_end(qr);
int count = quirc_count(qr);
JSValue result = JS_NewArray(js);
for (int i = 0; i < count; i++) {
struct quirc_code code;
struct quirc_data qdata;
quirc_extract(qr, i, &code);
if (quirc_decode(&code, &qdata) == QUIRC_SUCCESS) {
JSValue item = JS_NewStringLen(js, (const char *)qdata.payload, qdata.payload_len);
JS_SetPropertyUint32(js, result, i, item);
}
}
quirc_destroy(qr);
return result;
}
// Exported functions for the module
static const JSCFunctionListEntry js_qr_funcs[] = {
JS_CFUNC_DEF("encode", 2, js_qr_encode), // Updated to expect 2 args
JS_CFUNC_DEF("decode", 1, js_qr_decode),
};
// Helper to return the default export object
JSValue js_qr_use(JSContext *js) {
JSValue exports = JS_NewObject(js);
JS_SetPropertyFunctionList(js, exports, js_qr_funcs, sizeof(js_qr_funcs) / sizeof(JSCFunctionListEntry));
return exports;
}
// Module init
static int js_qr_init(JSContext *js, JSModuleDef *m) {
JS_SetModuleExport(js, m, "default", js_qr_use(js));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_qr
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) {
JSModuleDef *m = JS_NewCModule(js, module_name, js_qr_init);
if (!m)
return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}