refactor out duplicate and dead code

This commit is contained in:
2025-08-08 14:40:59 -04:00
parent faf19db27e
commit b5496d93bc
20 changed files with 716 additions and 1957 deletions

View File

@@ -1,16 +1,7 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build Commands
### Build variants
- `make` - Make and install debug version. Usually all that's needed.
- `make fast` - Build optimized version
- `make release` - Build release version with LTO and optimizations
- `make small` - Build minimal size version
- `make web` - Build for web/emscripten platform
- `make crosswin` - Cross-compile for Windows using mingw32
Run 'make' to make cell.
### Testing
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
@@ -18,15 +9,9 @@ After install with 'make', just run 'cell' and point it at the actor you want to
## Scripting language
This is called "cell", a variant of JavaScript with important differences. See docs/cell.md for detailed language documentation.
### Common development commands
- `meson setup build_<variant>` - Configure build directory
- `meson compile -C build_<variant>` - Compile in build directory
- `./build_dbg/prosperon examples/<example>` - Run example from build directory
- Copy prosperon to game directory and run: `cp build_dbg/prosperon <game-dir>/ && cd <game-dir> && ./prosperon`
## Architecture Overview
Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty system. Key architectural principles:
Prosperon is an actor-based game engine.
### Actor Model
- Each actor runs on its own thread

View File

@@ -329,7 +329,7 @@ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_sdl_gpu.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.c'
'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_staef.c', 'qjs_layout.c', 'cell_qoi.c'
]
# quirc src
src += [

View File

@@ -8,6 +8,7 @@ var graphics = use('graphics')
var util = use('util')
var input = use('input')
var prosperon = use('prosperon')
var staef = use('staef')
function normalizeSpacing(spacing) {
if (typeof spacing == 'number') {
@@ -216,7 +217,7 @@ clay.text = function text(str, ...configs)
var config = rectify_configs(configs);
config.size ??= [0,0];
config.font = graphics.get_font(config.font)
var tsize = graphics.font_text_size(config.font, str, 0, config.size.x);
var tsize = config.font.text_size(config.font, str, 0, config.size.x);
tsize.x = Math.ceil(tsize.x)
tsize.y = Math.ceil(tsize.y)
config.size = config.size.map((x,i) => Math.max(x, tsize[i]));
@@ -240,7 +241,7 @@ clay.button = function button(str, action, config = {})
{
config.__proto__ = button_base;
config.font = graphics.get_font(config.font)
config.size = graphics.font_text_size(config.font, str, 0, 0)
config.size = confit.font.text_size(config.font, str, 0, 0)
add_item(config);
config.text = str;
config.action = action;
@@ -251,7 +252,7 @@ clay.textbox = function(str, on_change, ...configs) {
config.on_change = on_change
config.text = str
config.font = graphics.get_font(config.font)
var tsize = graphics.font_text_size(config.font, str, 0, 0)
var tsize = config.font.text_size(config.font, str, 0, 0)
config.size ??= [0,0]
config.size = [Math.ceil(tsize.x), Math.ceil(tsize.y)]
config.size = [Math.max(config.size[0], config.size[0]), Math.max(config.size[1], config.size[1])]

View File

@@ -703,7 +703,7 @@ cmd_fns.draw_text = function(cmd)
var font = graphics.get_font(cmd.font)
if (!font[GPU])
font[GPU] = get_img_gpu(font.surface)
var mesh = graphics.make_text_buffer(
var mesh = font.make_text_buffer(
cmd.text,
cmd.pos,
[cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a],
@@ -759,7 +759,6 @@ cmd_fns.draw_slice9 = function(cmd)
var img = graphics.texture(cmd.image)
if (!img) return
// Use the gpu_slice9 function from geometry module to generate the mesh
var slice_info = {
tile_top: true,
tile_bottom: true,

View File

@@ -102,8 +102,6 @@ tilemap.prototype =
for (var y = 0; y < this.tiles[x].length; y++) {
var tile = this.tiles[x][y];
if (tile) {
// tile should already be an image object
// Create a unique key for each distinct image object
if (!imageToKey.has(tile)) {
var key = `texture_${keyCounter++}`;
imageToKey.set(tile, key);

View File

@@ -49,7 +49,7 @@ log.console = function(msg)
log.error = function(msg = new Error())
{
var caller = caller_data(4)
var caller = caller_data(1)
if (msg instanceof Error)
msg = msg + "\n" + msg.stack

View File

@@ -5,6 +5,8 @@ var time = use('time')
var res = use('resources')
var json = use('json')
var os = use('os')
var staef = use('staef')
var qoi = use('qoi')
var LASTUSE = Symbol()
var LOADING = Symbol()
@@ -95,7 +97,13 @@ function decode_image(bytes, ext)
case 'gif': return graphics.make_gif(bytes) // returns array of surfaces
case 'ase':
case 'aseprite': return graphics.make_aseprite(bytes)
default: return graphics.make_texture(bytes) // returns single surface
case 'qoi': return qoi.decode(bytes) // returns single surface
default:
// Try QOI first since it's fast to check
var qoi_result = qoi.decode(bytes)
if (qoi_result) return qoi_result
// Fall back to make_texture for other formats
return graphics.image_decode(bytes) // returns single surface
}
}
@@ -221,8 +229,84 @@ graphics.texture = function texture(path) {
if (!cache[id]) {
var ipath = res.find_image(id)
if (!ipath)
// If not found normally, check in accio/32k folder
if (!ipath) {
// First check if we have a cached QOI version
var cache_dir = '.prosperon/texture_cache'
var qoi_cache_path = `${cache_dir}/${id}.qoi`
if (io.exists(qoi_cache_path)) {
// Load the cached QOI file
var qoi_bytes = io.slurpbytes(qoi_cache_path)
var cached_img = qoi.decode(qoi_bytes)
if (cached_img) {
var result = new graphics.Image(cached_img)
cache[id] = result
return cache[id]
}
}
// Try to find the file in accio/32k with any extension
var accio_base = `accio/32k/${id}`
var found_path = null
// Check for common image extensions
var extensions = ['', '.qoi', '.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.gif', '.ase', '.aseprite']
for (var ext of extensions) {
var test_path = accio_base + ext
if (io.exists(test_path)) {
found_path = test_path
break
}
}
if (found_path) {
// Load the 32k image
var bytes = io.slurpbytes(found_path)
var img_32k = decode_image(bytes, found_path.ext())
// Handle single surface images
if (img_32k && img_32k.width && img_32k.pixels) {
// Get the surface module for scaling
var surface = use('surface')
var surf_4k = surface.scale(img_32k, {
width: Math.floor(img_32k.width / 8),
height: Math.floor(img_32k.height / 8),
mode: 'linear'
})
log.console(img_32k.pixels.length)
log.console(surf_4k.pixels.length)
log.console(json.encode(surf_4k))
var qoi_data = surface.compress_qoi(surf_4k)
log.console(json.encode(qoi_data))
if (qoi_data && qoi_data.pixels) {
if (!io.exists(cache_dir)) {
io.mkdir(cache_dir)
}
io.slurpwrite(qoi_cache_path, qoi_data.pixels)
}
// Use the 4k version
var result = new graphics.Image(surf_4k)
cache[id] = result
return cache[id]
}
// For now, if it's not a simple surface, just return it as-is
// (animations, etc. would need more complex handling)
var result = create_image(found_path)
cache[id] = result
return cache[id]
}
// If still not found, return notex
return graphics.texture('notex')
}
var result = create_image(ipath)
cache[id] = result
@@ -380,7 +464,7 @@ graphics.get_font = function get_font(path) {
if (fontcache[fontstr]) return fontcache[fontstr]
var data = io.slurpbytes(fullpath)
var font = graphics.make_font(data, size)
var font = new staef.font(data, size)
fontcache[fontstr] = font

180
source/cell_qoi.c Normal file
View File

@@ -0,0 +1,180 @@
#include "cell_qoi.h"
#include "qoi.h"
#include "qjs_blob.h"
#include "qjs_sdl.h"
#include <SDL3/SDL.h>
#include <string.h>
// Helper function to check for integer overflow in size calculations
static int check_size_overflow(size_t a, size_t b, size_t c, size_t *result)
{
if (a > SIZE_MAX / b) return 1;
size_t temp = a * b;
if (temp > SIZE_MAX / c) return 1;
*result = temp * c;
return 0;
}
// QOI compression/encoding
JSValue js_qoi_encode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
if (argc < 1)
return JS_ThrowTypeError(js, "compress_qoi requires an object argument");
// Check if width/height properties exist
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width");
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height");
if (JS_IsNull(width_val) || JS_IsNull(height_val)) {
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
return JS_ThrowTypeError(js, "compress_qoi requires width and height properties");
}
int width, height;
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) {
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
return JS_ThrowTypeError(js, "width and height must be numbers");
}
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
if (width < 1 || height < 1)
return JS_ThrowRangeError(js, "width and height must be at least 1");
// Get pixel format
JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
SDL_PixelFormat format = js2SDL_PixelFormat(js, format_val);
JS_FreeValue(js, format_val);
if (format == SDL_PIXELFORMAT_UNKNOWN)
return JS_ThrowTypeError(js, "Invalid or missing pixel format");
// Get pixels
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
size_t pixel_len;
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val);
JS_FreeValue(js, pixels_val);
if (!pixel_data)
return JS_ThrowTypeError(js, "pixels property must be a stoned blob");
// Validate buffer size
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
size_t required_size;
if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) {
JS_FreeValue(js, pixels_val);
return JS_ThrowRangeError(js, "Image dimensions too large");
}
if (pixel_len < required_size)
return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)",
width, height, required_size, pixel_len);
// Get colorspace (optional, default to sRGB)
int colorspace = 0; // QOI_SRGB
if (argc > 1) {
colorspace = JS_ToBool(js, argv[1]);
}
// Determine number of channels based on format
int channels = SDL_ISPIXELFORMAT_ALPHA(format) ? 4 : 3;
// Prepare QOI descriptor
qoi_desc desc = {
.width = width,
.height = height,
.channels = channels,
.colorspace = colorspace
};
// Encode to QOI
int out_len;
void *qoi_data = qoi_encode(pixel_data, &desc, &out_len);
if (!qoi_data)
return JS_ThrowInternalError(js, "QOI encoding failed");
// Create result object
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, result, "format", JS_NewString(js, "qoi"));
JS_SetPropertyStr(js, result, "channels", JS_NewInt32(js, channels));
JS_SetPropertyStr(js, result, "colorspace", JS_NewInt32(js, colorspace));
JSValue compressed_pixels = js_new_blob_stoned_copy(js, qoi_data, out_len);
free(qoi_data); // Free the QOI buffer after copying to blob
JS_SetPropertyStr(js, result, "pixels", compressed_pixels);
return result;
}
// QOI decompression/decoding
JSValue js_qoi_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not get QOI data from array buffer");
qoi_desc desc;
void *data = qoi_decode(raw, len, &desc, 0); // 0 means use channels from file
if (!data)
return JS_NULL; // Return null if not valid QOI
// QOI always decodes to either RGB or RGBA based on the file's channel count
int channels = desc.channels;
int pitch = desc.width * channels;
size_t pixels_size = pitch * desc.height;
// If it's RGB, convert to RGBA for consistency
void *rgba_data = data;
if (channels == 3) {
rgba_data = malloc(desc.width * desc.height * 4);
if (!rgba_data) {
free(data);
return JS_ThrowOutOfMemory(js);
}
// Convert RGB to RGBA
unsigned char *src = (unsigned char*)data;
unsigned char *dst = (unsigned char*)rgba_data;
for (int i = 0; i < desc.width * desc.height; i++) {
dst[i*4] = src[i*3];
dst[i*4+1] = src[i*3+1];
dst[i*4+2] = src[i*3+2];
dst[i*4+3] = 255;
}
free(data);
pitch = desc.width * 4;
pixels_size = pitch * desc.height;
}
// Create JS object with surface data
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, desc.width));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, desc.height));
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, rgba_data, pixels_size));
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8));
JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, 0));
JS_SetPropertyStr(js, obj, "colorspace", JS_NewInt32(js, desc.colorspace));
free(rgba_data);
return obj;
}
static const JSCFunctionListEntry js_qoi_funcs[] = {
MIST_FUNC_DEF(qoi, encode, 1),
MIST_FUNC_DEF(qoi, decode, 1)
};
JSValue js_qoi_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_qoi_funcs, countof(js_qoi_funcs));
return mod;
}

8
source/cell_qoi.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef CELL_QOI_H
#define CELL_QOI_H
#include <quickjs.h>
JSValue js_qoi_use(JSContext *js);
#endif // CELL_QOI_H

View File

@@ -26,9 +26,6 @@
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

View File

@@ -3,13 +3,10 @@
#include "datastream.h"
#include "qjs_sdl.h"
#include "qjs_sdl_input.h"
#include "qjs_io.h"
#include "qjs_fd.h"
#include "transform.h"
#include "stb_ds.h"
#include "stb_image.h"
#include "stb_rect_pack.h"
#include "stb_image_write.h"
#include "string.h"
#include <assert.h>
#include <time.h>
@@ -19,13 +16,11 @@
#include <unistd.h>
#include <limits.h>
#include "render.h"
#include "model.h"
#include "HandmadeMath.h"
#include "par/par_streamlines.h"
#include "par/par_shapes.h"
#include <stdint.h>
#include "cute_aseprite.h"
#include "cgltf.h"
#include "cell.h"
#include "qjs_blob.h"
@@ -57,52 +52,18 @@
#include "qjs_utf8.h"
#include "qjs_fit.h"
#include "qjs_text.h"
#include "qjs_staef.h"
#include "qjs_io.h"
#include "qjs_fd.h"
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
#include <signal.h>
void gui_input(SDL_Event *e);
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/utsname.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#endif
#endif
#include "wildmatch.h"
#include "freelist.h"
#include "sprite.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_cpuinfo.h>
int randombytes(void *buf, size_t n);
int trace = 0;
// External transform function declarations
extern JSClassID js_transform_id;
JSValue transform2js(JSContext *js, transform *t);
transform *js2transform(JSContext *js, JSValue v);
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
//#else
//#include <cblas.h>
#endif
// Random number generation constants for MT19937-64
#define NN STATE_VECTOR_LENGTH
#define MM STATE_VECTOR_M
@@ -233,39 +194,6 @@ JSValue js_getproperty(JSContext *js, JSValue v, const char *prop)
return ret;
}
void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr)
{
free(ptr);
}
JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
{
JSValue tstack[3];
tstack[1] = JS_NULL;
tstack[2] = JS_NULL;
// TODO: always copying; implement "takeover"
tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1);
// JSValue ret = JS_NewTypedArray(js, 3, tstack, type);
// JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements));
// JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type)));
// JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index));
// JS_FreeValue(js,tstack[0]);
// return ret;
return JS_NULL;
}
void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
{
return NULL;
/* size_t o, len, bytes, msize;
JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes);
void *data = js_get_blob_data(js, &msize, buf);
JS_FreeValue(js,buf);
if (stride) *stride = js_getnum_str(js, argv, "stride");
if (size) *size = msize;
return data;*/
}
JSValue make_quad_indices_buffer(JSContext *js, int quads)
{
cell_rt *rt = JS_GetContextOpaque(js);
@@ -355,10 +283,6 @@ JSValue quads_to_mesh(JSContext *js, text_vert *buffer)
return ret;
}
#ifndef _WIN32
#include <sys/resource.h>
#endif
typedef struct lrtb lrtb;
lrtb js2lrtb(JSContext *js, JSValue v)
@@ -388,7 +312,6 @@ char *js2strdup(JSContext *js, JSValue v) {
#include "qjs_macros.h"
QJSCLASS(font,)
QJSCLASS(datastream,)
JSValue angle2js(JSContext *js,double g) {
@@ -613,27 +536,6 @@ irect js2irect(JSContext *js, JSValue v)
return rect;
}
rect transform_rect(SDL_Renderer *ren, rect in, HMM_Mat3 *t)
{
HMM_Vec3 bottom_left = (HMM_Vec3){in.x,in.y,1.0};
HMM_Vec3 transformed_bl = HMM_MulM3V3(*t, bottom_left);
in.x = transformed_bl.x;
in.y = transformed_bl.y;
in.y = in.y - in.h; // should be done for any platform that draws rectangles from top left
return in;
}
HMM_Vec2 transform_point(SDL_Renderer *ren, HMM_Vec2 in, HMM_Mat3 *t)
{
rect logical;
SDL_GetRenderLogicalPresentationRect(ren, &logical);
in.y *= -1;
in.y += logical.h;
in.x -= t->Columns[2].x;
in.y -= t->Columns[2].y;
return in;
}
JSValue rect2js(JSContext *js,rect rect) {
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "x", number2js(js, rect.x));
@@ -643,168 +545,6 @@ JSValue rect2js(JSContext *js,rect rect) {
return obj;
}
JSValue ints2js(JSContext *js,int *ints) {
JSValue arr = JS_NewArray(js);
for (int i = 0; i < arrlen(ints); i++)
JS_SetPropertyUint32(js, arr,i, number2js(js,ints[i]));
return arr;
}
int vec_between(HMM_Vec2 p, HMM_Vec2 a, HMM_Vec2 b) {
HMM_Vec2 n;
n.x = b.x - a.x;
n.y = b.y - a.y;
n = HMM_NormV2(n);
return HMM_DotV2(n, HMM_SubV2(p,a)) > 0 && HMM_DotV2(HMM_MulV2F(n, -1), HMM_SubV2(p,b)) > 0;
}
/* Determines between which two points in 'segs' point 'p' falls.
0 indicates 'p' comes before the first point.
arrlen(segs) indicates it comes after the last point.
*/
int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) {
float shortest = slop < 0 ? INFINITY : slop;
int best = -1;
for (int i = 0; i < arrlen(segs) - 1; i++) {
float a = (segs[i + 1].y - segs[i].y) / (segs[i + 1].x - segs[i].x);
float c = segs[i].y - (a * segs[i].x);
float b = -1;
float dist = fabsf(a * p.x + b * p.y + c) / sqrt(pow(a, 2) + 1);
if (dist > shortest) continue;
int between = vec_between(p, segs[i], segs[i + 1]);
if (between) {
shortest = dist;
best = i + 1;
} else {
if (i == 0 && HMM_DistV2(p,segs[0]) < slop) {
shortest = dist;
best = i;
} else if (i == arrlen(segs) - 2 && HMM_DistV2(p,arrlast(segs)) < slop) {
shortest = dist;
best = arrlen(segs);
}
}
}
if (best == 1) {
HMM_Vec2 n;
n.x = segs[1].x - segs[0].x;
n.y = segs[1].y - segs[0].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p,segs[0])) < 0 ){
if (HMM_DistV2(p, segs[0]) >= slop)
best = -1;
else
best = 0;
}
}
if (best == arrlen(segs) - 1) {
HMM_Vec2 n;
n.x = segs[best - 1].x - segs[best].x;
n.y = segs[best - 1].y - segs[best - 1].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p, segs[best])) < 0) {
if (HMM_DistV2(p, segs[best]) >= slop)
best = -1;
else
best = arrlen(segs);
}
}
return best;
}
JSC_CCALL(os_make_text_buffer,
const char *s = JS_ToCString(js, argv[0]);
rect rectpos = js2rect(js,argv[1]);
font *f = js2font(js,argv[4]);
colorf c = js2color(js,argv[2]);
int wrap = js2number(js,argv[3]);
HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
text_vert *buffer = renderText(s, startpos, f, c, wrap);
ret = quads_to_mesh(js,buffer);
JS_FreeCString(js, s);
arrfree(buffer);
)
JSValue js_util_camera_globals(JSContext *js, JSValue self, int argc, JSValue *argv)
{
JSValue camera = argv[0];
if(JS_IsNull(camera)) return JS_NULL;
HMM_Vec2 size; HMM_Vec3 pos; HMM_Quat rotation;
double fov = 0; int ortho; double near_z = 0; double far_z = 0;
HMM_Vec2 anchor;
JS_GETPROP(js, size, camera, size, vec2)
JS_GETPROP(js, fov, camera, fov, number)
JS_GETPROP(js, ortho, camera, ortho, bool)
JS_GETPROP(js, near_z, camera, near_z, number)
JS_GETPROP(js, far_z, camera, far_z, number)
JS_GETPROP(js, anchor, camera, anchor, vec2)
JS_GETPROP(js, pos, camera, pos, vec3)
JS_GETPROP(js, rotation, camera, rotation, quat)
rotation.w = 1;
HMM_Mat4 proj, view;
if(ortho) {
float left = -anchor.x * size.x;
float bottom = -anchor.y * size.y;
float right = left + size.x;
float top = bottom + size.y;
proj = HMM_Orthographic_RH_NO(left, right, bottom, top, -1.0f, 1.0f);
} else {
proj = HMM_Perspective_RH_NO(fov, size.x/size.y, near_z, far_z);
proj.Columns[1] = HMM_MulV4F(proj.Columns[1], -1.0f);
}
view = HMM_MulM4(
HMM_InvTranslate(HMM_Translate(pos)),
HMM_InvRotate (HMM_QToM4(rotation))
);
JSValue data = JS_NewObject(js);
HMM_Mat4 world_to_projection = HMM_MulM4(proj, view);
HMM_Mat4 projection_to_world = HMM_InvGeneralM4(world_to_projection);
HMM_Vec3 camera_dir_world = HMM_NormV3(
HMM_QVRot((HMM_Vec3){0,0,-1}, rotation)
);
JS_SetPropertyStr(js, data, "world_to_projection",
js_new_blob_stoned_copy(js, world_to_projection.em,
sizeof(float)*16));
JS_SetPropertyStr(js, data, "projection_to_world",
js_new_blob_stoned_copy(js, projection_to_world.em,
sizeof(float)*16));
JS_SetPropertyStr(js, data, "world_to_view",
js_new_blob_stoned_copy(js, view.em, sizeof(float)*16));
JS_SetPropertyStr(js, data, "view_to_projection",
js_new_blob_stoned_copy(js, proj.em, sizeof(float)*16));
JS_SetPropertyStr(js, data, "camera_pos_world", vec32js(js, pos));
JS_SetPropertyStr(js, data, "camera_dir_world", vec32js(js, camera_dir_world));
JS_SetPropertyStr(js, data, "render_size", vec22js(js, size));
JS_SetPropertyStr(js, data, "viewport_size", vec22js(js, (HMM_Vec2){0.5,0.5}));
JS_SetPropertyStr(js, data, "viewport_offset", vec22js(js, (HMM_Vec2){0,0}));
JS_SetPropertyStr(js, data, "viewport_min_z", number2js(js, near_z));
JS_SetPropertyStr(js, data, "viewport_max_z", number2js(js, far_z));
return data;
}
static JSValue floats2array(JSContext *js, float *vals, size_t len) {
JSValue arr = JS_NewArray(js);
for (size_t i = 0; i < len; i++) {
@@ -865,21 +605,6 @@ JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropert
JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); }
JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_NULL; }
JSValue js_array_get_xy(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js,arr,0,JS_GetPropertyUint32(js,self,0));
JS_SetPropertyUint32(js,arr,1,JS_GetPropertyUint32(js,self,1));
return arr;
}
JSValue js_array_set_xy(JSContext *js, JSValue self, JSValue v)
{
JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0));
JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1));
return JS_NULL;
}
// Single-value accessors
JSValue js_array_get_r(JSContext *js, JSValue self)
@@ -907,43 +632,6 @@ JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 3, val); return JS_NULL; }
// Multi-value accessors
JSValue js_array_get_rgb(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
return arr;
}
JSValue js_array_set_rgb(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
return JS_NULL;
}
JSValue js_array_get_rgba(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
JS_SetPropertyUint32(js, arr, 3, JS_GetPropertyUint32(js, self, 3));
return arr;
}
JSValue js_array_set_rgba(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3));
return JS_NULL;
}
static const JSCFunctionListEntry js_array_funcs[] = {
PROTO_FUNC_DEF(array, add, 1),
PROTO_FUNC_DEF(array, sub, 1),
@@ -952,13 +640,10 @@ static const JSCFunctionListEntry js_array_funcs[] = {
PROTO_FUNC_DEF(array, lerp, 2),
JS_CGETSET_DEF("x", js_array_get_x,js_array_set_x),
JS_CGETSET_DEF("y", js_array_get_y, js_array_set_y),
JS_CGETSET_DEF("xy", js_array_get_xy, js_array_set_xy),
JS_CGETSET_DEF("r", js_array_get_r, js_array_set_r),
JS_CGETSET_DEF("g", js_array_get_g, js_array_set_g),
JS_CGETSET_DEF("b", js_array_get_b, js_array_set_b),
JS_CGETSET_DEF("a", js_array_get_a, js_array_set_a),
JS_CGETSET_DEF("rgb", js_array_get_rgb, js_array_set_rgb),
JS_CGETSET_DEF("rgba", js_array_get_rgba, js_array_set_rgba),
};
JSC_CCALL(number_lerp,
@@ -1022,52 +707,8 @@ static const JSCFunctionListEntry js_datastream_funcs[] = {
CGETSET_ADD(datastream, callback),
};
JSC_GETSET(font, linegap, number)
JSC_GET(font, height, number)
JSC_GET(font, ascent, number)
JSC_GET(font, descent, number)
JSC_CCALL(graphics_font_text_size,
font *f = js2font(js,argv[0]);
const char *str = JS_ToCString(js, argv[1]);
float letterSpacing = js2number(js,argv[2]);
float wrap = js2number(js,argv[3]);
ret = vec22js(js,measure_text(str, f, letterSpacing, wrap));
JS_FreeCString(js, str);
)
static const JSCFunctionListEntry js_font_funcs[] = {
CGETSET_ADD(font, linegap),
MIST_GET(font, height),
MIST_GET(font, ascent),
MIST_GET(font, descent),
};
JSC_CCALL(os_image_info,
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (!raw)
return JS_ThrowReferenceError(js, "could not load image with array buffer");
int depth = stbi_is_16_bit_from_memory(raw, len) ? 16 : 8;
int width, height, comp;
if (!stbi_info_from_memory(raw, len, &width, &height, &comp))
return JS_ThrowReferenceError(js, "could not parse image info: %s", stbi_failure_reason());
int hdr = stbi_is_hdr_from_memory(raw, len);
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, obj, "components", JS_NewInt32(js, comp));
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, depth));
JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, hdr));
ret = obj;
)
// input: (encoded image data of jpg, png, bmp, tiff)
JSC_CCALL(os_make_texture,
JSC_CCALL(os_image_decode,
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer");
@@ -1228,39 +869,6 @@ JSC_CCALL(os_make_aseprite,
cute_aseprite_free(ase);
)
JSC_CCALL(os_make_font,
size_t len;
void *data = js_get_blob_data(js,&len,argv[0]);
if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
font *f = MakeFont(data, len, js2number(js,argv[1]));
if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js,f);
// Create surface data object for the font's atlas
if (f->surface) {
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, f->surface->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, f->surface->h));
JS_SetPropertyStr(js, surfData, "format", pixelformat2js(js, f->surface->format));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, f->surface->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(f->surface)) {
if (SDL_LockSurface(f->surface) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = f->surface->pitch * f->surface->h;
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, f->surface->pixels, byte_size));
if (locked)
SDL_UnlockSurface(f->surface);
JS_SetPropertyStr(js, ret, "surface", surfData);
}
)
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
@@ -1279,15 +887,6 @@ JSC_CCALL(os_srand,
m_seedRand(mrand, js2number(js,argv[0]));
)
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
{
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < verts; i++)
colordata[i] = c;
return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, 0, 4, 0, 0);
}
JSC_CCALL(os_make_line_prim,
return JS_NULL;
/*
@@ -1322,7 +921,6 @@ JSC_CCALL(os_make_line_prim,
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), 0,2,1,0));
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices));
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
JS_SetPropertyStr(js,prim,"first_index", number2js(js,0));
@@ -1397,60 +995,8 @@ JSC_CCALL(os_rectpack,
}
)
JSC_CCALL(os_insertion_sort,
JSValue arr = argv[0];
JSValue cmp = argv[1];
int len = JS_ArrayLength(js, arr);
for (int i = 1; i < len; i++) {
JSValue key = JS_GetPropertyUint32(js, arr, i);
int j = i - 1;
while (j >= 0) {
JSValue arr_j = JS_GetPropertyUint32(js, arr, j);
JSValue ret = JS_Call(js, cmp, JS_NULL, 2, (JSValue[]){ arr_j, key });
if (JS_IsException(ret)) {
JS_FreeValue(js,arr_j);
JS_FreeValue(js,key);
return ret;
}
double c = js2number(js, ret);
JS_FreeValue(js, ret);
if (c > 0) {
JS_SetPropertyUint32(js, arr, j + 1, arr_j);
j--;
} else {
JS_FreeValue(js, arr_j);
break;
}
}
JS_SetPropertyUint32(js, arr, j + 1, key);
}
ret = JS_DupValue(js,arr);
)
JSC_CCALL(os_power_state,
SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL);
switch(state) {
case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status");
case SDL_POWERSTATE_UNKNOWN: return JS_NULL;
case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery");
case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery");
case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging");
case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged");
}
return JS_NULL;
)
static const JSCFunctionListEntry js_util_funcs[] = {
MIST_FUNC_DEF(os, guid, 0),
MIST_FUNC_DEF(os, insertion_sort, 2),
MIST_FUNC_DEF(util, camera_globals, 1),
};
JSC_CCALL(graphics_hsl_to_rgb,
@@ -1473,47 +1019,13 @@ JSC_CCALL(graphics_hsl_to_rgb,
return color2js(js, (colorf){r+m, g+m, b+m, 1});
)
JSC_CCALL(graphics_save_png,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
size_t size;
void *data = js_get_blob_data(js, &size, argv[3]);
if (!stbi_write_png(file, w, h, 4, data, pitch))
return JS_ThrowInternalError(js, "Could not write png");
)
JSC_CCALL(graphics_save_jpg,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch, quality;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
JS_ToInt32(js, &quality, argv[5]);
if (!quality) quality = 80;
size_t size;
void *data = js_get_blob_data(js, &size, argv[3]);
if (!stbi_write_jpg(file, w, h, 4, data, quality))
return JS_ThrowInternalError(js, "Could not write png");
)
static const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(os, make_text_buffer, 5),
MIST_FUNC_DEF(os, rectpack, 3),
MIST_FUNC_DEF(os, image_info, 1),
MIST_FUNC_DEF(os, make_texture, 1),
MIST_FUNC_DEF(os, image_decode, 1),
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
MIST_FUNC_DEF(graphics, save_png, 4),
MIST_FUNC_DEF(graphics, save_jpg, 4),
MIST_FUNC_DEF(graphics, font_text_size, 4),
};
static const JSCFunctionListEntry js_video_funcs[] = {
@@ -1592,6 +1104,7 @@ JSC_CCALL(os_value_id,
#include "qjs_nota.h"
#include "qjs_layout.h"
#include "qjs_sdl_gpu.h"
#include "cell_qoi.h"
JSValue js_imgui_use(JSContext *js);
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
@@ -1627,9 +1140,11 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(utf8));
arrput(rt->module_registry, MISTLINE(fit));
arrput(rt->module_registry, MISTLINE(text));
arrput(rt->module_registry, MISTLINE(staef));
arrput(rt->module_registry, MISTLINE(wota));
arrput(rt->module_registry, MISTLINE(nota));
arrput(rt->module_registry, MISTLINE(sdl_gpu));
arrput(rt->module_registry, MISTLINE(qoi));
// power user
arrput(rt->module_registry, MISTLINE(js));

View File

@@ -4,5 +4,6 @@
#include "cell.h"
JSValue js_crypto_use(JSContext *ctx);
int randombytes(void *buf, size_t n);
#endif

View File

@@ -473,117 +473,6 @@ static HMM_Vec3 base_quad[4] = {
{1.0,1.0,1.0}
};
JSC_CCALL(gpu_make_sprite_mesh,
size_t quads = JS_ArrayLength(js, argv[0]);
size_t verts = quads*4;
size_t count = quads*6;
// Prepare arrays on CPU
HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js,argv[0],i);
transform *tr;
rect src;
HMM_Vec4 color;
JS_GETATOM(js,src,sub,src,rect)
JS_GETATOM(js,color,sub,color,color)
JS_GETATOM(js,tr,sub,transform,transform)
JS_FreeValue(js,sub);
size_t base = i*4;
if (tr) {
HMM_Mat3 trmat = transform2mat3(tr);
for (int j = 0; j < 4; j++)
posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy;
} else {
rect dst;
JS_GETATOM(js,dst,sub,rect,rect);
posdata[base+0] = (HMM_Vec2){dst.x,dst.y};
posdata[base + 1] = (HMM_Vec2){ dst.x+dst.w, dst.y };
posdata[base + 2] = (HMM_Vec2){ dst.x, dst.y+dst.h };
posdata[base + 3] = (HMM_Vec2){ dst.x+dst.w, dst.y+dst.h };
}
uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h};
uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h};
uvdata[base+2] = (HMM_Vec2){src.x, src.y};
uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y};
colordata[base+0] = color;
colordata[base+1] = color;
colordata[base+2] = color;
colordata[base+3] = color;
}
// Check old mesh
JSValue old_mesh = JS_NULL;
if (argc > 1)
old_mesh = argv[1];
// Needed sizes
size_t pos_size = sizeof(*posdata)*verts;
size_t uv_size = sizeof(*uvdata)*verts;
size_t color_size = sizeof(*colordata)*verts;
BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, "pos", pos_size, 0, 2, 1, 0);
BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, 0, 2, 1, 0);
BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", color_size, 0, 4, 1, 0);
int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new;
ret = JS_NewObject(js);
if (need_new_all) {
// Create all new buffers
JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, 0, 2, 1,0);
JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, 0, 2, 1,0);
JSValue new_color = make_gpu_buffer(js, colordata, color_size, 0, 0, 1,0);
JS_SetPropertyStr(js, ret, "pos", new_pos);
JS_SetPropertyStr(js, ret, "uv", new_uv);
JS_SetPropertyStr(js, ret, "color", new_color);
// Indices
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetPropertyStr(js, ret, "indices", indices);
} else {
// Reuse the old buffers
// Just copy data into existing buffers via their pointers
memcpy(pos_chk.ptr, posdata, pos_size);
memcpy(uv_chk.ptr, uvdata, uv_size);
memcpy(color_chk.ptr, colordata, color_size);
// Duplicate old references since we're returning a new object
JS_SetPropertyStr(js, ret, "pos", JS_DupValue(js, pos_chk.val));
JS_SetPropertyStr(js, ret, "uv", JS_DupValue(js, uv_chk.val));
JS_SetPropertyStr(js, ret, "color", JS_DupValue(js, color_chk.val));
// Indices can remain the same if they were also large enough. If using a shared global index buffer:
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetPropertyStr(js, ret, "indices", indices);
}
JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
JS_SetPropertyStr(js, ret, "count", number2js(js, count));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
// Free temporary CPU arrays
free(posdata);
free(uvdata);
free(colordata);
// Free old buffer values if they were fetched
if (!JS_IsNull(pos_chk.val)) JS_FreeValue(js, pos_chk.val);
if (!JS_IsNull(uv_chk.val)) JS_FreeValue(js, uv_chk.val);
if (!JS_IsNull(color_chk.val)) JS_FreeValue(js, color_chk.val);
return ret;
)
struct quad_buffers {
HMM_Vec2 *pos;
HMM_Vec2 *uv;
@@ -601,27 +490,6 @@ struct quad_buffers quad_buffers_new(int verts)
return b;
}
JSValue quadbuffers_to_mesh(JSContext *js, struct quad_buffers buffers)
{
JSValue jspos = make_gpu_buffer(js, buffers.pos, sizeof(HMM_Vec2)*buffers.verts, 0, 2, 0, 0);
JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, 0, 2,0,0);
JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, 0, 4,0,0);
size_t quads = buffers.verts/4;
size_t count = buffers.verts/2*3;
JSValue jsidx = make_quad_indices_buffer(js, quads);
JSValue ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "pos", jspos);
JS_SetPropertyStr(js, ret, "uv", jsuv);
JS_SetPropertyStr(js, ret, "color", jscolor);
JS_SetPropertyStr(js, ret, "indices", jsidx);
JS_SetPropertyStr(js, ret, "vertices", number2js(js, buffers.verts));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
return ret;
}
int sort_sprite(const sprite *a, const sprite *b)
{
if (a->layer != b->layer) return a->layer - b->layer;
@@ -634,142 +502,6 @@ int sort_sprite(const sprite *a, const sprite *b)
return 0;
}
JSC_CCALL(gpu_make_sprite_queue,
sprite *sprites = NULL;
size_t quads = 0;
int needfree = 1;
if (js_is_blob(js, argv[0])) {
// test for fastest
size_t size;
sprite *sprites = js_get_blob_data(js, &size, argv[0]);
quads = size/sizeof(*sprites);
needfree = 0;
for (int i = 0; i < quads; i++)
JS_DupValue(js,sprites[i].image);
} else {
quads = JS_ArrayLength(js, argv[0]);
if (quads == 0)
return JS_ThrowReferenceError(js, "Expected an array of sprites with length > 0.");
arrsetcap(sprites, quads);
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js, argv[0], i);
sprite *jsp = js2sprite(js, sub);
if (jsp) {
arrput(sprites, *jsp);
JS_DupValue(js,jsp->image);
}
else {
sprite sp = {0};
JS_GETATOM(js, sp.pos, sub, pos, vec2)
JS_GETATOM(js, sp.center, sub, center, vec2)
JS_GETATOM(js, sp.skew, sub, skew, vec2)
JS_GETATOM(js, sp.scale, sub, scale, vec2)
JS_GETATOM(js, sp.color,sub,color,color)
JS_GETATOM(js, sp.layer,sub,layer,number)
sp.image = JS_GetPropertyStr(js,sub,"image");
sprite_apply(&sp);
arrput(sprites,sp);
}
JS_FreeValue(js, sub);
}
}
qsort(sprites, quads, sizeof(sprite), sort_sprite);
struct quad_buffers buffers = quad_buffers_new(quads*4);
const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} };
rect uv;
rect uv_px;
JSValue cur_img = JS_NULL;
for (size_t i = 0; i < quads; i++) {
sprite *s = &sprites[i];
if (JS_IsNull(cur_img) || !JS_StrictEq(js, s->image, cur_img)) {
cur_img = s->image;
JS_GETATOM(js, uv, cur_img, rect, rect)
JS_GETATOM(js, uv_px, cur_img, rect_px, rect)
}
HMM_Vec2 px_size = {
uv_px.w * s->scale.X,
uv_px.h * s->scale.Y
};
HMM_Vec2 anchor = {
px_size.X * s->center.X,
px_size.Y * s->center.Y
};
size_t base = i * 4;
for (int v = 0; v < 4; v++) {
HMM_Vec2 lp = {
local[v].X * px_size.X - anchor.X,
local[v].Y * px_size.Y - anchor.Y
};
HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp));
buffers.pos[base + v] = world;
buffers.color[base + v] = s->color;
}
/* UVs are still top-left-origin pixel coords, so keep previous packing */
buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h };
buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h };
buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y };
buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y };
}
JSValue mesh = quadbuffers_to_mesh(js, buffers);
ret = JS_NewArray(js);
int first_index = 0;
int count = 0;
int n = 0;
JSValue img = JS_NULL;
for (int i = 0; i < quads; i++) {
if (!JS_SameValue(js, sprites[i].image, img)) {
if (count > 0) {
JSValue q = JS_NewObject(js);
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
JS_SetPropertyStr(js, q, "image", img);
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
JS_SetPropertyUint32(js, ret, n++, q);
}
first_index = i*6;
count = 1;
img = JS_DupValue(js, sprites[i].image);
} else count++;
JS_FreeValue(js,sprites[i].image);
}
if (count > 0) {
JSValue q = JS_NewObject(js);
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
JS_SetPropertyStr(js, q, "image", img);
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
JS_SetPropertyUint32(js, ret, n++, q);
}
if (needfree)
arrfree(sprites);
JS_FreeValue(js, mesh);
)
JSC_CCALL(geometry_rect_transform,
// argv[0] = worldspace rect
rect r = js2rect(js, argv[0]);
@@ -899,14 +631,11 @@ JSC_CCALL(geometry_tilemap_to_data,
xy_data[base + 7] = world_y + size_y;
// Set UVs (normalized 0-1 for now)
uv_data[base + 0] = 0.0f;
uv_data[base + 1] = 0.0f;
uv_data[base + 2] = 1.0f;
uv_data[base + 3] = 0.0f;
uv_data[base + 4] = 0.0f;
uv_data[base + 5] = 1.0f;
uv_data[base + 6] = 1.0f;
uv_data[base + 7] = 1.0f;
uv_data[base + 0] = 0; uv_data[base + 1] = 1.0f; // now samples the TOP of the image
uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f;
uv_data[base + 4] = 0; uv_data[base + 5] = 0;
uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0;
// Set colors (check if tile has color property)
SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f};
@@ -1270,82 +999,6 @@ JSC_CCALL(geometry_transform_xy_blob,
ret = transformed_blob;
)
// RENDERITEM SYSTEM
typedef struct {
int layer;
float y;
JSValue val;
} RenderItem;
#define MAX_RENDER_ITEMS 64000
static RenderItem render_items[MAX_RENDER_ITEMS];
static int render_item_count = 0;
JSC_CCALL(geometry_renderitem_push,
if (argc < 3) {
return JS_ThrowTypeError(js, "renderitem_push requires 3 arguments: layer, y, val");
}
if (render_item_count >= MAX_RENDER_ITEMS) {
return JS_ThrowTypeError(js, "Maximum render items exceeded");
}
int layer;
double y;
if (JS_ToInt32(js, &layer, argv[0]) < 0) {
return JS_ThrowTypeError(js, "layer must be a number");
}
if (JS_ToFloat64(js, &y, argv[1]) < 0) {
return JS_ThrowTypeError(js, "y must be a number");
}
render_items[render_item_count].layer = layer;
render_items[render_item_count].y = (float)y;
render_items[render_item_count].val = JS_DupValue(js, argv[2]);
render_item_count++;
return JS_NULL;
)
static int compare_render_items(const void *a, const void *b)
{
const RenderItem *item_a = (const RenderItem *)a;
const RenderItem *item_b = (const RenderItem *)b;
if (item_a->layer != item_b->layer) {
return item_a->layer - item_b->layer;
}
// if (item_a->y != item_b->y) {
// return (item_b->y > item_a->y) ? 1 : -1;
// }
return 0;
}
JSC_CCALL(geometry_renderitem_sort,
qsort(render_items, render_item_count, sizeof(RenderItem), compare_render_items);
JSValue result = JS_NewArray(js);
for (int i = 0; i < render_item_count; i++) {
JS_SetPropertyUint32(js, result, i, JS_DupValue(js, render_items[i].val));
}
return result;
)
JSC_CCALL(geometry_renderitem_clear,
for (int i = 0; i < render_item_count; i++) {
JS_FreeValue(js, render_items[i].val);
}
render_item_count = 0;
return JS_NULL;
)
JSC_CCALL(geometry_array_blob,
JSValue arr = argv[0];
size_t len = JS_ArrayLength(js,arr);
@@ -1463,11 +1116,6 @@ static const JSCFunctionListEntry js_geometry_funcs[] = {
MIST_FUNC_DEF(geometry, transform_xy_blob, 2),
MIST_FUNC_DEF(gpu, tile, 4),
MIST_FUNC_DEF(gpu, slice9, 4),
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
MIST_FUNC_DEF(gpu, make_sprite_queue, 4),
MIST_FUNC_DEF(geometry, renderitem_push, 3),
MIST_FUNC_DEF(geometry, renderitem_sort, 0),
MIST_FUNC_DEF(geometry, renderitem_clear, 0),
MIST_FUNC_DEF(geometry, array_blob, 2),
MIST_FUNC_DEF(geometry, make_quad_indices, 1),
MIST_FUNC_DEF(geometry, make_rect_quad, 3),

View File

@@ -23,6 +23,8 @@
#include "imgui_impl_sdlrenderer3.h"
//#include "imgui_impl_sdlgpu3.h"
#include "qjs_renderer.h"
#include "qjs_macros.h"
// Forward declarations for the functions we need
@@ -338,14 +340,17 @@ JSC_CCALL(imgui_pushid,
JSC_CCALL(imgui_popid, ImGui::PopID(); )
JSC_CCALL(imgui_image,
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
/* SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
ImGui::Image((ImTextureID)tex, ImVec2(100, 100), ImVec2(0,0), ImVec2(1,1));
*/
)
JSC_SCALL(imgui_imagebutton,
/*
SDL_Texture *tex = js2SDL_Texture(js,argv[1]);
if (ImGui::ImageButton(str, (ImTextureID)tex, ImVec2(100, 100)))
JS_Call(js, argv[2], JS_NULL, 0, NULL);
*/
)
JSC_CCALL(imgui_sameline, ImGui::SameLine(js2number(js, argv[0])) )
@@ -589,7 +594,7 @@ JSC_CCALL(imgui_newframe,
)
JSC_CCALL(imgui_endframe,
ImGui::Render();
/* ImGui::Render();
SDL_Renderer *renderer = js2SDL_Renderer(js,argv[0]);
int w, h;
@@ -598,6 +603,7 @@ JSC_CCALL(imgui_endframe,
SDL_SetRenderLogicalPresentation(renderer,0,0,SDL_LOGICAL_PRESENTATION_DISABLED);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
SDL_SetRenderLogicalPresentation(renderer,w,h,mode);
*/
)
JSC_CCALL(imgui_wantmouse,
@@ -609,7 +615,7 @@ JSC_CCALL(imgui_wantkeys,
)
JSC_CCALL(imgui_init,
ImGui::CreateContext();
/* ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
@@ -632,6 +638,7 @@ JSC_CCALL(imgui_init,
#ifdef ENABLE_IMNODES
ImNodes::CreateContext();
#endif
*/
)
// ==================== IMPLOT FUNCTIONS ====================

View File

@@ -601,7 +601,7 @@ JSC_CCALL(surface_constructor,
if (!raw) {
JS_FreeValue(js, pixels_val);
return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer");
return JS_ThrowTypeError(js, "pixels property must be a stoned blob");
}
// Get pitch if provided, otherwise calculate it
@@ -658,6 +658,251 @@ static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
};
// Helper function to create SDL_Surface from image object
static SDL_Surface* image_to_surface(JSContext *js, JSValue img_obj)
{
// Get width and height
JSValue width_val = JS_GetPropertyStr(js, img_obj, "width");
JSValue height_val = JS_GetPropertyStr(js, img_obj, "height");
if (JS_IsNull(width_val) || JS_IsNull(height_val)) {
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
return NULL;
}
int width, height;
if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) {
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
return NULL;
}
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
// Get format
JSValue format_val = JS_GetPropertyStr(js, img_obj, "format");
SDL_PixelFormat format = js2pixelformat(js, format_val);
JS_FreeValue(js, format_val);
if (format == SDL_PIXELFORMAT_UNKNOWN)
format = SDL_PIXELFORMAT_RGBA32; // default
// Get pixels
JSValue pixels_val = JS_GetPropertyStr(js, img_obj, "pixels");
size_t pixel_len;
void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val);
if (!pixel_data) {
JS_FreeValue(js, pixels_val);
return NULL;
}
// Get pitch (optional)
int pitch;
JSValue pitch_val = JS_GetPropertyStr(js, img_obj, "pitch");
if (!JS_IsNull(pitch_val)) {
pitch = js2number(js, pitch_val);
JS_FreeValue(js, pitch_val);
} else {
pitch = width * SDL_BYTESPERPIXEL(format);
}
// Create a copy of pixel data since SDL_Surface will own it
void *pixels_copy = malloc(pixel_len);
if (!pixels_copy) {
JS_FreeValue(js, pixels_val);
return NULL;
}
memcpy(pixels_copy, pixel_data, pixel_len);
JS_FreeValue(js, pixels_val);
SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch);
if (!surface) {
free(pixels_copy);
return NULL;
}
return surface;
}
// Helper function to convert SDL_Surface back to image object
static JSValue surface_to_image(JSContext *js, SDL_Surface *surf)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h));
JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format));
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0) {
JS_FreeValue(js, obj);
return JS_NULL;
}
locked = 1;
}
// Add pixels as stoned blob
size_t byte_size = surf->pitch * surf->h;
JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size);
JS_SetPropertyStr(js, obj, "pixels", pixels);
// Unlock if we locked
if (locked)
SDL_UnlockSurface(surf);
// Add depth and hdr properties for completeness
JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(surf->format)));
JS_SetPropertyStr(js, obj, "hdr", JS_FALSE);
return obj;
}
// Scale function for image objects
JSC_CCALL(surface_scale_img,
if (argc < 2)
return JS_ThrowTypeError(js, "scale requires image and options objects");
SDL_Surface *src = image_to_surface(js, argv[0]);
if (!src)
return JS_ThrowTypeError(js, "First argument must be a valid image object");
// Get width and height from options
JSValue width_val = JS_GetPropertyStr(js, argv[1], "width");
JSValue height_val = JS_GetPropertyStr(js, argv[1], "height");
int new_width = src->w, new_height = src->h;
if (!JS_IsNull(width_val))
JS_ToInt32(js, &new_width, width_val);
if (!JS_IsNull(height_val))
JS_ToInt32(js, &new_height, height_val);
JS_FreeValue(js, width_val);
JS_FreeValue(js, height_val);
// Get scale mode
JSValue mode_val = JS_GetPropertyStr(js, argv[1], "mode");
SDL_ScaleMode mode = js2SDL_ScaleMode(js, mode_val);
JS_FreeValue(js, mode_val);
SDL_Surface *dst = SDL_ScaleSurface(src, new_width, new_height, mode);
SDL_DestroySurface(src);
if (!dst)
return JS_ThrowInternalError(js, "Scale failed: %s", SDL_GetError());
JSValue result = surface_to_image(js, dst);
SDL_DestroySurface(dst);
return result;
)
// Fill function for image objects
JSC_CCALL(surface_fill_img,
if (argc < 2)
return JS_ThrowTypeError(js, "fill requires image and color");
SDL_Surface *surf = image_to_surface(js, argv[0]);
if (!surf)
return JS_ThrowTypeError(js, "First argument must be a valid image object");
colorf color = js2color(js, argv[1]);
rect r = {
.x = 0,
.y = 0,
.w = surf->w,
.h = surf->h
};
SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255));
JSValue result = surface_to_image(js, surf);
SDL_DestroySurface(surf);
return result;
)
// Rect function for image objects
JSC_CCALL(surface_rect_img,
if (argc < 3)
return JS_ThrowTypeError(js, "rect requires image, rectangle, and color");
SDL_Surface *surf = image_to_surface(js, argv[0]);
if (!surf)
return JS_ThrowTypeError(js, "First argument must be a valid image object");
rect r = js2rect(js, argv[1]);
colorf color = js2color(js, argv[2]);
SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255));
JSValue result = surface_to_image(js, surf);
SDL_DestroySurface(surf);
return result;
)
// Blit function for image objects
JSC_CCALL(surface_blit_img,
if (argc < 2)
return JS_ThrowTypeError(js, "blit requires destination and source images");
SDL_Surface *dst = image_to_surface(js, argv[0]);
if (!dst)
return JS_ThrowTypeError(js, "First argument must be a valid destination image");
SDL_Surface *src = image_to_surface(js, argv[1]);
if (!src) {
SDL_DestroySurface(dst);
return JS_ThrowTypeError(js, "Second argument must be a valid source image");
}
irect dr = {0}, *pdr = NULL;
if (argc > 2 && !JS_IsNull(argv[2])) {
dr = js2irect(js, argv[2]);
pdr = &dr;
}
irect sr = {0}, *psr = NULL;
if (argc > 3 && !JS_IsNull(argv[3])) {
sr = js2irect(js, argv[3]);
psr = &sr;
}
SDL_ScaleMode mode = SDL_SCALEMODE_LINEAR;
if (argc > 4)
mode = js2SDL_ScaleMode(js, argv[4]);
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
SDL_DestroySurface(src);
JSValue result = surface_to_image(js, dst);
SDL_DestroySurface(dst);
return result;
)
// Duplicate function for image objects
JSC_CCALL(surface_dup_img,
if (argc < 1)
return JS_ThrowTypeError(js, "dup requires an image object");
SDL_Surface *surf = image_to_surface(js, argv[0]);
if (!surf)
return JS_ThrowTypeError(js, "Argument must be a valid image object");
SDL_Surface *dup = SDL_DuplicateSurface(surf);
SDL_DestroySurface(surf);
if (!dup)
return JS_ThrowInternalError(js, "Duplicate failed: %s", SDL_GetError());
JSValue result = surface_to_image(js, dup);
SDL_DestroySurface(dup);
return result;
)
// Generic convert function for pixel format/colorspace conversion
JSC_CCALL(surface_convert_generic,
if (argc < 2)
@@ -776,9 +1021,22 @@ JSC_CCALL(surface_convert_generic,
return JS_ThrowInternalError(js, "Pixel conversion failed: %s", SDL_GetError());
}
// Return a stoned blob with the converted pixels
ret = js_new_blob_stoned_copy(js, dst_pixels, dst_size);
// Create result image object
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, src_width));
JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, src_height));
JS_SetPropertyStr(js, result, "format", pixelformat2js(js, dst_format));
JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, dst_pitch));
JSValue pixels = js_new_blob_stoned_copy(js, dst_pixels, dst_size);
free(dst_pixels);
JS_SetPropertyStr(js, result, "pixels", pixels);
// Add depth and hdr for consistency
JS_SetPropertyStr(js, result, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(dst_format)));
JS_SetPropertyStr(js, result, "hdr", JS_FALSE);
return result;
)
JSValue js_sdl_surface_use(JSContext *js)
@@ -801,5 +1059,12 @@ JSValue js_sdl_surface_use(JSContext *js)
JS_SetPropertyStr(js, ctor, "compress_bc4", JS_NewCFunction(js, js_surface_compress_bc4, "compress_bc4", 1));
JS_SetPropertyStr(js, ctor, "compress_bc5", JS_NewCFunction(js, js_surface_compress_bc5, "compress_bc5", 1));
// Add standalone image manipulation functions
JS_SetPropertyStr(js, ctor, "scale", JS_NewCFunction(js, js_surface_scale_img, "scale", 2));
JS_SetPropertyStr(js, ctor, "fill", JS_NewCFunction(js, js_surface_fill_img, "fill", 2));
JS_SetPropertyStr(js, ctor, "rect", JS_NewCFunction(js, js_surface_rect_img, "rect", 3));
JS_SetPropertyStr(js, ctor, "blit", JS_NewCFunction(js, js_surface_blit_img, "blit", 5));
JS_SetPropertyStr(js, ctor, "dup", JS_NewCFunction(js, js_surface_dup_img, "dup", 1));
return ctor;
}

File diff suppressed because it is too large Load Diff

128
source/qjs_staef.c Normal file
View File

@@ -0,0 +1,128 @@
#include "qjs_staef.h"
#include "font.h"
#include "qjs_macros.h"
#include "HandmadeMath.h"
#include "render.h"
#include "stb_ds.h"
#include "cell.h"
#include "qjs_sdl.h"
#include <SDL3/SDL.h>
// External functions from jsffi.c
typedef HMM_Vec4 colorf;
extern colorf js2color(JSContext *js, JSValue v);
extern JSValue vec22js(JSContext *js, HMM_Vec2 v);
extern HMM_Vec2 js2vec2(JSContext *js, JSValue v);
extern rect js2rect(JSContext *js, JSValue v);
extern double js2number(JSContext *js, JSValue v);
extern JSValue number2js(JSContext *js, double g);
extern void *js_get_blob_data(JSContext *js, size_t *len, JSValue v);
extern JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t size);
extern JSValue quads_to_mesh(JSContext *js, text_vert *buffer);
extern int uncaught_exception(JSContext *js, JSValue ret);
// QuickJS class for font
QJSCLASS(font,)
// Font constructor
JSC_CCALL(staef_font_new,
size_t len;
void *data = js_get_blob_data(js, &len, argv[0]);
if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
double height = js2number(js, argv[1]);
font *f = MakeFont(data, len, (int)height);
if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js, f);
// Create surface data object for the font's atlas
if (f->surface) {
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, f->surface->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, f->surface->h));
JS_SetPropertyStr(js, surfData, "format", pixelformat2js(js, f->surface->format));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, f->surface->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(f->surface)) {
if (SDL_LockSurface(f->surface) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = f->surface->pitch * f->surface->h;
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, f->surface->pixels, byte_size));
if (locked)
SDL_UnlockSurface(f->surface);
JS_SetPropertyStr(js, ret, "surface", surfData);
}
)
// Calculate text size
JSC_CCALL(staef_font_text_size,
font *f = js2font(js, self);
const char *str = JS_ToCString(js, argv[0]);
float letterSpacing = argc > 1 ? js2number(js, argv[1]) : 0;
float wrap = argc > 2 ? js2number(js, argv[2]) : -1;
ret = vec22js(js, measure_text(str, f, letterSpacing, wrap));
JS_FreeCString(js, str);
)
// Generate text buffer (mesh data)
JSC_CCALL(staef_font_make_text_buffer,
font *f = js2font(js, self);
const char *s = JS_ToCString(js, argv[0]);
rect rectpos = js2rect(js, argv[1]);
colorf c = js2color(js, argv[2]);
float wrap = argc > 3 ? js2number(js, argv[3]) : -1;
HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
text_vert *buffer = renderText(s, startpos, f, c, wrap);
ret = quads_to_mesh(js, buffer);
JS_FreeCString(js, s);
arrfree(buffer);
)
// Font property getters/setters
JSC_GETSET(font, linegap, number)
// Font methods
static const JSCFunctionListEntry js_font_funcs[] = {
MIST_FUNC_DEF(staef_font, text_size, 3),
MIST_FUNC_DEF(staef_font, make_text_buffer, 4),
CGETSET_ADD(font, linegap),
};
// Font constructor function
static JSValue js_font_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
return js_staef_font_new(ctx, JS_NULL, argc, argv);
}
// Initialize the staef module
JSValue js_staef_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JSValue proto;
// Initialize font class
JS_NewClassID(&js_font_id);
JS_NewClass(JS_GetRuntime(js), js_font_id, &js_font_class);
proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, proto, js_font_funcs, countof(js_font_funcs));
JS_SetClassProto(js, js_font_id, proto);
// Create font constructor
JSValue font_ctor = JS_NewCFunction2(js, js_font_constructor, "font", 2, JS_CFUNC_constructor, 0);
JS_SetConstructor(js, font_ctor, proto);
// Add font constructor to module (lowercase to match "new staef.font")
JS_SetPropertyStr(js, mod, "font", font_ctor);
return mod;
}

8
source/qjs_staef.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_STAEF_H
#define QJS_STAEF_H
#include "quickjs.h"
JSValue js_staef_use(JSContext *js);
#endif // QJS_STAEF_H

View File

@@ -427,7 +427,7 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
run = 0;
}
index_pos = QOI_COLOR_HASH(px) % 64;
index_pos = QOI_COLOR_HASH(px) & (64 - 1);
if (index[index_pos].v == px.v) {
bytes[p++] = QOI_OP_INDEX | index_pos;
@@ -574,7 +574,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
run = (b1 & 0x3f);
}
index[QOI_COLOR_HASH(px) % 64] = px;
index[QOI_COLOR_HASH(px) & (64 - 1)] = px;
}
pixels[px_pos + 0] = px.rgba.r;
@@ -646,4 +646,4 @@ void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
}
#endif /* QOI_NO_STDIO */
#endif /* QOI_IMPLEMENTATION */
#endif /* QOI_IMPLEMENTATION */

View File

@@ -136,7 +136,7 @@ $_.receiver(e => {
case "drop_file":
log.console(`got ${e.data} dropped`)
var data = io.slurpbytes(e.data)
img = graphics.make_texture(data)
img = graphics.image_decode(data)
var qr_surf = img;//extract_qr_surface(img)
var qr_surf_scaled = qr_surf.scale([qr_surf.width/4, qr_surf.height/4])
var image = {surface:qr_surf_scaled}