qop
This commit is contained in:
17
meson.build
17
meson.build
@@ -255,22 +255,6 @@ else
|
|||||||
deps += soloud_dep
|
deps += soloud_dep
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# QR code support (optional)
|
|
||||||
qrencode_enabled = get_option('qrencode')
|
|
||||||
if qrencode_enabled
|
|
||||||
qr_dep = dependency('qrencode', static: true, required: false)
|
|
||||||
if not qr_dep.found()
|
|
||||||
message('⚙ System qrencode not found, building subproject...')
|
|
||||||
deps += dependency('libqrencode', static:true)
|
|
||||||
else
|
|
||||||
deps += qr_dep
|
|
||||||
endif
|
|
||||||
add_project_arguments('-DHAVE_QRENCODE', language: ['c', 'cpp'])
|
|
||||||
src += 'qjs_qr.c'
|
|
||||||
else
|
|
||||||
message('⚙ QR code support disabled')
|
|
||||||
endif
|
|
||||||
|
|
||||||
link_args = link
|
link_args = link
|
||||||
sources = []
|
sources = []
|
||||||
src += [
|
src += [
|
||||||
@@ -294,6 +278,7 @@ src += [
|
|||||||
'wildmatch.c',
|
'wildmatch.c',
|
||||||
'qjs_io.c',
|
'qjs_io.c',
|
||||||
'qjs_fd.c',
|
'qjs_fd.c',
|
||||||
|
'qjs_qop.c',
|
||||||
'qjs_os.c',
|
'qjs_os.c',
|
||||||
'qjs_actor.c',
|
'qjs_actor.c',
|
||||||
'qjs_wota.c',
|
'qjs_wota.c',
|
||||||
|
|||||||
@@ -954,26 +954,16 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search for .cell directory up the tree */
|
/* Check for .cell directory in the current directory */
|
||||||
char *search_dir = SDL_GetCurrentDirectory();
|
|
||||||
char *cell_parent_dir = NULL;
|
char *cell_parent_dir = NULL;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
char *current_dir = SDL_GetCurrentDirectory();
|
||||||
while (search_dir && strlen(search_dir) > 1) {
|
char test_path[PATH_MAX];
|
||||||
char test_path[PATH_MAX];
|
snprintf(test_path, sizeof(test_path), "%s/.cell", current_dir);
|
||||||
snprintf(test_path, sizeof(test_path), "%s/.cell", search_dir);
|
if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
cell_parent_dir = strdup(current_dir);
|
||||||
cell_parent_dir = strdup(search_dir);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
char *last_sep = strrchr(search_dir, '/');
|
|
||||||
if (!last_sep || last_sep == search_dir) {
|
|
||||||
if (stat("/.cell", &st) == 0 && S_ISDIR(st.st_mode))
|
|
||||||
cell_parent_dir = strdup("/");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*last_sep = '\0';
|
|
||||||
}
|
}
|
||||||
|
SDL_free(current_dir);
|
||||||
|
|
||||||
if (cell_parent_dir) {
|
if (cell_parent_dir) {
|
||||||
/* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */
|
/* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */
|
||||||
@@ -981,26 +971,18 @@ int main(int argc, char **argv)
|
|||||||
if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/')
|
if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/')
|
||||||
cell_parent_dir[proj_len - 1] = '\0';
|
cell_parent_dir[proj_len - 1] = '\0';
|
||||||
|
|
||||||
char scriptpath[PATH_MAX];
|
|
||||||
snprintf(scriptpath, sizeof(scriptpath), "%s/scripts", cell_parent_dir);
|
|
||||||
|
|
||||||
char cellpath[PATH_MAX];
|
char cellpath[PATH_MAX];
|
||||||
snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir);
|
snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir);
|
||||||
|
|
||||||
PHYSFS_mount(scriptpath, NULL, 1);
|
|
||||||
PHYSFS_mount(cellpath, NULL, 0);
|
PHYSFS_mount(cellpath, NULL, 0);
|
||||||
PHYSFS_mount(cell_parent_dir, NULL, 0);
|
PHYSFS_mount(cell_parent_dir, NULL, 0);
|
||||||
PHYSFS_setWriteDir(cell_parent_dir);
|
PHYSFS_setWriteDir(cell_parent_dir);
|
||||||
|
|
||||||
free(cell_parent_dir);
|
free(cell_parent_dir);
|
||||||
} else {
|
} else {
|
||||||
// Not in a project - use CELLPATH after confirming ..
|
printf("Cell requires a .cell folder to run.\n");
|
||||||
// TODO: implement
|
|
||||||
printf("Could not find project! Exiting!\n");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(search_dir);
|
|
||||||
|
|
||||||
/* Create the initial actor from the command line */
|
/* Create the initial actor from the command line */
|
||||||
int actor_argc = argc - script_start;
|
int actor_argc = argc - script_start;
|
||||||
|
|||||||
@@ -48,6 +48,3 @@
|
|||||||
|
|
||||||
#define QOI_IMPLEMENTATION
|
#define QOI_IMPLEMENTATION
|
||||||
#include "qoi.h"
|
#include "qoi.h"
|
||||||
|
|
||||||
#define QOP_IMPLEMENTATION
|
|
||||||
#include "qop.h"
|
|
||||||
|
|||||||
@@ -29,9 +29,6 @@
|
|||||||
#include "qjs_nota.h"
|
#include "qjs_nota.h"
|
||||||
#include "qjs_wota.h"
|
#include "qjs_wota.h"
|
||||||
#include "qjs_soloud.h"
|
#include "qjs_soloud.h"
|
||||||
#ifdef HAVE_QRENCODE
|
|
||||||
#include "qjs_qr.h"
|
|
||||||
#endif
|
|
||||||
#include "qjs_sdl.h"
|
#include "qjs_sdl.h"
|
||||||
#include "qjs_sdl_video.h"
|
#include "qjs_sdl_video.h"
|
||||||
#include "qjs_math.h"
|
#include "qjs_math.h"
|
||||||
@@ -55,6 +52,8 @@
|
|||||||
#include "qjs_io.h"
|
#include "qjs_io.h"
|
||||||
#include "qjs_fd.h"
|
#include "qjs_fd.h"
|
||||||
|
|
||||||
|
#include "qjs_qop.h"
|
||||||
|
|
||||||
// External transform function declarations
|
// External transform function declarations
|
||||||
extern JSClassID js_transform_id;
|
extern JSClassID js_transform_id;
|
||||||
JSValue transform2js(JSContext *js, transform *t);
|
JSValue transform2js(JSContext *js, transform *t);
|
||||||
@@ -1123,11 +1122,8 @@ void ffi_load(JSContext *js)
|
|||||||
// extra
|
// extra
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
|
||||||
arrput(rt->module_registry, MISTLINE(socket));
|
arrput(rt->module_registry, ((ModuleEntry){"qop", js_qop_use}));
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
||||||
#ifdef HAVE_QRENCODE
|
|
||||||
arrput(rt->module_registry, MISTLINE(qr));
|
|
||||||
#endif
|
|
||||||
arrput(rt->module_registry, MISTLINE(http));
|
arrput(rt->module_registry, MISTLINE(http));
|
||||||
arrput(rt->module_registry, MISTLINE(crypto));
|
arrput(rt->module_registry, MISTLINE(crypto));
|
||||||
arrput(rt->module_registry, MISTLINE(miniz));
|
arrput(rt->module_registry, MISTLINE(miniz));
|
||||||
|
|||||||
205
source/qjs_qop.c
Normal file
205
source/qjs_qop.c
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
#include "qjs_qop.h"
|
||||||
|
#define QOP_IMPLEMENTATION
|
||||||
|
#include "qop.h"
|
||||||
|
#include "qjs_blob.h"
|
||||||
|
#include "jsffi.h"
|
||||||
|
|
||||||
|
static JSClassID js_qop_archive_class_id;
|
||||||
|
|
||||||
|
static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
|
||||||
|
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
|
||||||
|
if (qop) {
|
||||||
|
if (qop->hashmap) {
|
||||||
|
js_free_rt(rt, qop->hashmap);
|
||||||
|
}
|
||||||
|
qop_close(qop);
|
||||||
|
js_free_rt(rt, qop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSClassDef js_qop_archive_class = {
|
||||||
|
"qop archive",
|
||||||
|
.finalizer = js_qop_archive_finalizer,
|
||||||
|
};
|
||||||
|
|
||||||
|
static qop_desc *js2qop(JSContext *js, JSValue v) {
|
||||||
|
return JS_GetOpaque(v, js_qop_archive_class_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
|
||||||
|
if (qop->hashmap != NULL) return 1;
|
||||||
|
void *buffer = js_malloc(js, qop->hashmap_size);
|
||||||
|
if (!buffer) return 0;
|
||||||
|
int num = qop_read_index(qop, buffer);
|
||||||
|
if (num == 0) {
|
||||||
|
js_free(js, buffer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSC_SCALL(qop_open,
|
||||||
|
qop_desc *qop = js_malloc(js, sizeof(qop_desc));
|
||||||
|
if (!qop)
|
||||||
|
ret = JS_ThrowOutOfMemory(js);
|
||||||
|
|
||||||
|
int size = qop_open(str, qop);
|
||||||
|
if (size == 0) {
|
||||||
|
js_free(js, qop);
|
||||||
|
ret = JS_ThrowReferenceError(js, "Failed to open QOP archive: %s", str);
|
||||||
|
} else {
|
||||||
|
JSValue obj = JS_NewObjectClass(js, js_qop_archive_class_id);
|
||||||
|
JS_SetOpaque(obj, qop);
|
||||||
|
ret = obj;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static JSValue js_qop_close(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||||
|
qop_desc *qop = js2qop(js, self);
|
||||||
|
if (!qop)
|
||||||
|
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||||
|
|
||||||
|
qop_close(qop);
|
||||||
|
JS_SetOpaque(self, NULL); // Prevent double free
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static JSValue js_qop_read(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||||
|
qop_desc *qop = js2qop(js, self);
|
||||||
|
if (!qop)
|
||||||
|
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||||
|
|
||||||
|
const char *path = JS_ToCString(js, argv[0]);
|
||||||
|
if (!path)
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
|
||||||
|
if (!js_qop_ensure_index(js, qop)) {
|
||||||
|
JS_FreeCString(js, path);
|
||||||
|
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||||
|
}
|
||||||
|
|
||||||
|
qop_file *file = qop_find(qop, path);
|
||||||
|
JS_FreeCString(js, path);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return JS_NULL;
|
||||||
|
|
||||||
|
unsigned char *dest = js_malloc(js, file->size);
|
||||||
|
if (!dest)
|
||||||
|
return JS_ThrowOutOfMemory(js);
|
||||||
|
|
||||||
|
int bytes = qop_read(qop, file, dest);
|
||||||
|
if (bytes == 0) {
|
||||||
|
js_free(js, dest);
|
||||||
|
return JS_ThrowReferenceError(js, "Failed to read file");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
|
||||||
|
js_free(js, dest);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_qop_read_ex(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||||
|
qop_desc *qop = js2qop(js, self);
|
||||||
|
if (!qop)
|
||||||
|
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||||
|
|
||||||
|
const char *path = JS_ToCString(js, argv[0]);
|
||||||
|
if (!path)
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
|
||||||
|
if (!js_qop_ensure_index(js, qop)) {
|
||||||
|
JS_FreeCString(js, path);
|
||||||
|
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||||
|
}
|
||||||
|
|
||||||
|
qop_file *file = qop_find(qop, path);
|
||||||
|
JS_FreeCString(js, path);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return JS_NULL;
|
||||||
|
|
||||||
|
unsigned int start;
|
||||||
|
unsigned int len;
|
||||||
|
if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0)
|
||||||
|
return JS_ThrowTypeError(js, "Invalid start or len");
|
||||||
|
|
||||||
|
unsigned char *dest = js_malloc(js, len);
|
||||||
|
if (!dest)
|
||||||
|
return JS_ThrowOutOfMemory(js);
|
||||||
|
|
||||||
|
int bytes = qop_read_ex(qop, file, dest, start, len);
|
||||||
|
if (bytes == 0) {
|
||||||
|
js_free(js, dest);
|
||||||
|
return JS_ThrowReferenceError(js, "Failed to read file part");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
|
||||||
|
js_free(js, dest);
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||||
|
qop_desc *qop = js2qop(js, self);
|
||||||
|
if (!qop)
|
||||||
|
return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||||
|
|
||||||
|
if (!js_qop_ensure_index(js, qop)) {
|
||||||
|
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue arr = JS_NewArray(js);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < qop->hashmap_len; i++) {
|
||||||
|
qop_file *file = &qop->hashmap[i];
|
||||||
|
if (file->size == 0) continue; // empty slot
|
||||||
|
|
||||||
|
char *path = js_malloc(js, file->path_len);
|
||||||
|
if (!path) {
|
||||||
|
return JS_ThrowOutOfMemory(js);
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = qop_read_path(qop, file, path);
|
||||||
|
if (len == 0) {
|
||||||
|
js_free(js, path);
|
||||||
|
continue; // skip on error
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator
|
||||||
|
js_free(js, path);
|
||||||
|
JS_SetPropertyUint32(js, arr, count++, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JSCFunctionListEntry js_qop_archive_funcs[] = {
|
||||||
|
JS_CFUNC_DEF("close", 0, js_qop_close),
|
||||||
|
JS_CFUNC_DEF("list", 0, js_qop_list),
|
||||||
|
JS_CFUNC_DEF("read", 1, js_qop_read),
|
||||||
|
JS_CFUNC_DEF("read_ex", 3, js_qop_read_ex),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||||
|
MIST_FUNC_DEF(qop, open, 1),
|
||||||
|
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE),
|
||||||
|
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE),
|
||||||
|
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE),
|
||||||
|
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, JS_PROP_ENUMERABLE),
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue js_qop_use(JSContext *js) {
|
||||||
|
JS_NewClassID(&js_qop_archive_class_id);
|
||||||
|
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
|
||||||
|
JSValue archive_proto = JS_NewObject(js);
|
||||||
|
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||||
|
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
|
||||||
|
|
||||||
|
JSValue mod = JS_NewObject(js);
|
||||||
|
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
8
source/qjs_qop.h
Normal file
8
source/qjs_qop.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef QJS_QOP_H
|
||||||
|
#define QJS_QOP_H
|
||||||
|
|
||||||
|
#include "cell.h"
|
||||||
|
|
||||||
|
JSValue js_qop_use(JSContext *js);
|
||||||
|
|
||||||
|
#endif // QJS_QOP_H
|
||||||
302
source/qjs_qr.c
302
source/qjs_qr.c
@@ -1,302 +0,0 @@
|
|||||||
#include "cell.h"
|
|
||||||
#include "qjs_blob.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_IsNull(argv[1]) && JS_IsObject(argv[1])) {
|
|
||||||
JSValue opt = argv[1];
|
|
||||||
|
|
||||||
JSValue m = JS_GetPropertyStr(js, opt, "mode");
|
|
||||||
if (!JS_IsNull(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_IsNull(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_IsNull(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_IsNull(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_get_blob_data(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_get_blob_data(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_new_blob_stoned_copy(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_IsNull(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_new_blob_stoned_copy(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;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#ifndef QJS_QR_H
|
|
||||||
#define QJS_QR_H
|
|
||||||
|
|
||||||
#include "cell.h"
|
|
||||||
|
|
||||||
JSValue js_qr_use(JSContext*);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
10
source/qop.h
10
source/qop.h
@@ -84,7 +84,7 @@ typedef struct {
|
|||||||
|
|
||||||
// Open an archive at path. The supplied qop_desc will be filled with the
|
// Open an archive at path. The supplied qop_desc will be filled with the
|
||||||
// information from the file header. Returns the size of the archvie or 0 on
|
// information from the file header. Returns the size of the archvie or 0 on
|
||||||
// failure
|
// failure.
|
||||||
int qop_open(const char *path, qop_desc *qop);
|
int qop_open(const char *path, qop_desc *qop);
|
||||||
|
|
||||||
// Read the index from an opened archive. The supplied buffer will be filled
|
// Read the index from an opened archive. The supplied buffer will be filled
|
||||||
@@ -94,10 +94,10 @@ int qop_open(const char *path, qop_desc *qop);
|
|||||||
// Returns the number of files in the archive or 0 on error.
|
// Returns the number of files in the archive or 0 on error.
|
||||||
int qop_read_index(qop_desc *qop, void *buffer);
|
int qop_read_index(qop_desc *qop, void *buffer);
|
||||||
|
|
||||||
// Close the archive
|
// Close the archive.
|
||||||
void qop_close(qop_desc *qop);
|
void qop_close(qop_desc *qop);
|
||||||
|
|
||||||
// Find a file with the supplied path. Returns NULL if the file is not found
|
// Find a file with the supplied path. Returns NULL if the file is not found.
|
||||||
qop_file *qop_find(qop_desc *qop, const char *path);
|
qop_file *qop_find(qop_desc *qop, const char *path);
|
||||||
|
|
||||||
// Copy the path of the file into dest. The dest buffer must be at least
|
// Copy the path of the file into dest. The dest buffer must be at least
|
||||||
@@ -107,7 +107,7 @@ int qop_read_path(qop_desc *qop, qop_file *file, char *dest);
|
|||||||
|
|
||||||
// Read the whole file into dest. The dest buffer must be at least file->size
|
// Read the whole file into dest. The dest buffer must be at least file->size
|
||||||
// bytes long.
|
// bytes long.
|
||||||
// Returns the number of bytes read
|
// Returns the number of bytes read.
|
||||||
int qop_read(qop_desc *qop, qop_file *file, unsigned char *dest);
|
int qop_read(qop_desc *qop, qop_file *file, unsigned char *dest);
|
||||||
|
|
||||||
// Read part of a file into dest. The dest buffer must be at least len bytes
|
// Read part of a file into dest. The dest buffer must be at least len bytes
|
||||||
@@ -279,4 +279,4 @@ int qop_read_ex(qop_desc *qop, qop_file *file, unsigned char *dest, unsigned int
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif /* QOP_IMPLEMENTATION */
|
#endif /* QOP_IMPLEMENTATION */
|
||||||
Reference in New Issue
Block a user