Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
322 lines
10 KiB
C
322 lines
10 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 <stdint.h>
|
|
|
|
#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)
|
|
int use_byte_mode = 0;
|
|
|
|
// Handle options object (argv[1])
|
|
if (argc > 1 && !JS_IsUndefined(argv[1]) && JS_IsObject(argv[1])) {
|
|
JSValue opt = argv[1];
|
|
|
|
JSValue m = JS_GetPropertyStr(js, opt, "mode");
|
|
if (!JS_IsUndefined(m)) {
|
|
const char *smode = JS_ToCString(js, m);
|
|
if (!smode) { JS_FreeValue(js, m); return JS_EXCEPTION; }
|
|
if (!strcasecmp(smode, "byte") || !strcasecmp(smode, "binary"))
|
|
use_byte_mode = 1;
|
|
else if (!strcasecmp(smode, "text"))
|
|
use_byte_mode = 0;
|
|
else {
|
|
JS_FreeCString(js, smode); JS_FreeValue(js, m);
|
|
return JS_ThrowRangeError(js, "mode must be 'byte' or 'text'");
|
|
}
|
|
JS_FreeCString(js, smode);
|
|
JS_FreeValue(js, m);
|
|
}
|
|
|
|
// 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 (!use_byte_mode && 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 {
|
|
/* treat everything else as raw bytes */
|
|
use_byte_mode = 1;
|
|
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;
|
|
if (use_byte_mode)
|
|
qrcode = QRcode_encodeData(data_len, data, version, errc);
|
|
else
|
|
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
|
|
printf("Encoded %d of data into a QR of width %d\n", data_len, qrcode->width);
|
|
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;
|
|
}
|
|
|
|
static uint8_t rgba_to_gray(uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
return (uint8_t)(( 299 * r + 587 * g + 114 * b + 500) / 1000);
|
|
}
|
|
|
|
// QR decode function (unchanged)
|
|
static JSValue js_qr_decode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
size_t data_len;
|
|
uint8_t *src;
|
|
int w, h, pitch;
|
|
src = JS_GetArrayBuffer(js, &data_len, argv[0]);
|
|
|
|
if (!src)
|
|
return JS_ThrowTypeError(js, "decode expects an ArrayBuffer");
|
|
|
|
JS_ToInt32(js, &w, argv[1]);
|
|
JS_ToInt32(js, &h, argv[2]);
|
|
JS_ToInt32(js, &pitch, argv[3]);
|
|
|
|
if (w <= 0 || h <= 0)
|
|
return JS_ThrowInternalError(js, "Bad width or height: %dx%d", w, h);
|
|
|
|
struct quirc *qr = quirc_new();
|
|
if (!qr)
|
|
return JS_ThrowInternalError(js, "Failed to initialize QR decoder");
|
|
|
|
if (quirc_resize(qr, w, h) < 0) {
|
|
quirc_destroy(qr);
|
|
return JS_ThrowInternalError(js, "quirc_resize failed");
|
|
}
|
|
|
|
uint8_t *dst = quirc_begin(qr, NULL, NULL);
|
|
|
|
printf("decoding image size %dx%d, pitch %d and size %d\n", w, h, pitch, data_len);
|
|
|
|
for (int y = 0; y < h; ++y) {
|
|
uint8_t *row = src + y * pitch;
|
|
uint8_t *out = dst + y * w;
|
|
for (int x = 0; x < w; ++x) {
|
|
uint8_t a = row[x*4 + 3]; /* alpha */
|
|
if (a < 128) { /* mostly transparent */
|
|
out[x] = 255;
|
|
} else {
|
|
uint8_t r = row[x*4+0];
|
|
uint8_t g = row[x*4+1];
|
|
uint8_t b = row[x*4+2];
|
|
out[x] = rgba_to_gray(r, g, b);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
int err = quirc_decode(&code, &qdata);
|
|
if (err == QUIRC_SUCCESS) {
|
|
JSValue item = JS_NewArrayBufferCopy(js, qdata.payload, qdata.payload_len);
|
|
JS_SetPropertyUint32(js, result, i, item);
|
|
} else {
|
|
printf("QR error: %s\n", quirc_strerror(err));
|
|
}
|
|
}
|
|
|
|
quirc_destroy(qr);
|
|
return result;
|
|
}
|
|
|
|
static JSValue js_qr_rgba(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
|
{
|
|
if (argc < 1)
|
|
return JS_ThrowTypeError(js, "qr.rgba(mods, ...) needs a modules object");
|
|
|
|
JSValue mods = argv[0];
|
|
|
|
uint32_t on = 0xFF000000u; /* black opaque */
|
|
uint32_t off = 0xFFFFFFFFu; /* white opaque */
|
|
if (argc > 1) JS_ToUint32(js, &on, argv[1]);
|
|
if (argc > 2) JS_ToUint32(js, &off, argv[2]);
|
|
|
|
JSValue js_w = JS_GetPropertyStr(js, mods, "width");
|
|
if (JS_IsUndefined(js_w)) {
|
|
JS_FreeValue(js, js_w);
|
|
return JS_ThrowTypeError(js, "mods.width missing");
|
|
}
|
|
int32_t m = 0;
|
|
JS_ToInt32(js, &m, js_w);
|
|
JS_FreeValue(js, js_w);
|
|
|
|
if (m <= 0)
|
|
return JS_ThrowRangeError(js, "mods.width must be > 0");
|
|
|
|
JSValue js_bits = JS_GetPropertyStr(js, mods, "data");
|
|
size_t len = JS_ArrayLength(js, js_bits);
|
|
|
|
if (len < (size_t)m * (size_t)m) {
|
|
JS_FreeValue(js, js_bits);
|
|
return JS_ThrowRangeError(js, "mods.data too small for width²");
|
|
}
|
|
|
|
const int w = m, h = m, pitch = w * 4;
|
|
const size_t bytes = (size_t)h * (size_t)pitch;
|
|
uint8_t *tmp = js_malloc(js, bytes);
|
|
if (!tmp) { JS_FreeValue(js, js_bits); return JS_EXCEPTION; }
|
|
|
|
for (int y = 0; y < h; ++y) {
|
|
uint32_t *row = (uint32_t *)(tmp + y * pitch);
|
|
for (int x = 0; x < w; x++) {
|
|
uint32_t idx = y * m + x;
|
|
JSValue vbit = JS_GetPropertyUint32(js, js_bits, idx);
|
|
int on_bit = JS_ToBool(js, vbit);
|
|
JS_FreeValue(js, vbit);
|
|
row[x] = on_bit ? on : off;
|
|
}
|
|
}
|
|
|
|
JSValue ab = JS_NewArrayBufferCopy(js, tmp, bytes); /* GC owns copy */
|
|
js_free(js, tmp);
|
|
JS_FreeValue(js, js_bits); /* done with src */
|
|
|
|
JSValue obj = JS_NewObject(js);
|
|
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, w));
|
|
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, h));
|
|
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
|
|
JS_SetPropertyStr(js, obj, "buffer", ab);
|
|
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
|
|
return obj;
|
|
}
|
|
|
|
// 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", 4, js_qr_decode),
|
|
JS_CFUNC_DEF("rgba", 3, js_qr_rgba),
|
|
};
|
|
|
|
// 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;
|
|
}
|