#include "quickjs.h" #include "quirc.h" #include "qrencode.h" #include // for sqrt #include // for size_t, etc. #include // for memcpy, strcmp #include #include // 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 */ 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 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); printf("found %d codes\n", count); for (int i = 0; i < count; i++) { struct quirc_code code; struct quirc_data qdata; quirc_extract(qr, i, &code); printf("code is %p\n", code.size); if (quirc_decode(&code, &qdata) == QUIRC_SUCCESS) { JSValue item = JS_NewArrayBufferCopy(js, qdata.payload, qdata.payload_len); // JSValue item = JS_NewStringLen(js, (const char *)qdata.payload, qdata.payload_len); JS_SetPropertyUint32(js, result, i, item); } } 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); 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; }