From ae2de0b3e722b7dc0737fc73cf74b8f45a13276c Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:36:25 +0100 Subject: [PATCH 01/12] feat: add text rendering functionality with custom font support --- examples/2d/text.js | 27 +++++++++++++++++ src/api/api_classes.cpp | 30 +++++++++++++++++-- src/api/api_context.cpp | 64 ++++++++++++++++++++++++++++++++--------- src/api/hostapi.hpp | 1 + src/api/js_types.hpp | 30 ++++++++++++++++++- 5 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 examples/2d/text.js diff --git a/examples/2d/text.js b/examples/2d/text.js new file mode 100644 index 0000000..6dc78d1 --- /dev/null +++ b/examples/2d/text.js @@ -0,0 +1,27 @@ +import {Application, Rectangle, Vector2, Palette, Font} from "vectorjs"; + +const screenWidth = 600; +const screenHeight = 800; +const fpsPos = new Vector2(10, 10); +const origin = new Vector2(0, 0); + +const app = new Application(screenHeight, screenWidth,"Window"); +app.run({ + + onInit() { + this.customFont = new Font("C:\\workspace\\c\\vectorjs\\examples\\assets\\AnonymousPro-Regular.ttf"); + }, + + onDraw(render) { + render.withLayer2D((ctx) => { + ctx.text.drawText(fpsPos, "this is a sample text", { + font: this.customFont, + color: Palette.RED, + fontSize: 24.0, + spacing: 2.0, + rotation: 0.0, + origin: origin + }) + }); + } +}); \ No newline at end of file diff --git a/src/api/api_classes.cpp b/src/api/api_classes.cpp index a80bee6..ae480d9 100644 --- a/src/api/api_classes.cpp +++ b/src/api/api_classes.cpp @@ -56,8 +56,9 @@ namespace HostApi { Utils::ScopedJSValue render_obj(ctx, create_draw_render_object(ctx)); if (JS_IsFunction(ctx, on_init_func)) { - Utils::ScopedJSValue ret(ctx, JS_Call(ctx, on_init_func, user_app, 0, nullptr)); - if (JS_IsException(ret)) return JS_EXCEPTION; + if (Utils::ScopedJSValue ret(ctx, JS_Call(ctx, on_init_func, user_app, 0, nullptr)); JS_IsException(ret)) { + return JS_EXCEPTION; + } } while (!WindowShouldClose()) { @@ -183,11 +184,36 @@ namespace HostApi { }); } + static void register_font_class(JSContext* ctx, JSModuleDef* m) { + Utils::register_js_class(ctx, m, { + .name = "Font", + .class_id = js_font_class_id, + .finalizer = [](auto, JSValue val) { + delete Utils::get_opaque(val, js_font_class_id); + }, + .constructor = [](auto c, auto new_target, int argc, auto argv) -> JSValue { + if (argc < 1) return JS_ThrowTypeError(c, "Font requires at least a path argument"); + + auto font_path = Utils::js_to_std_string(c, argv[0]); + if (font_path.empty()) return JS_EXCEPTION; + + if (argc >= 2) { + int32_t size = 0; + JS_ToInt32(c, &size, argv[1]); + return Utils::create_js_instance(c, new_target, js_font_class_id, font_path, size); + } + + return Utils::create_js_instance(c, new_target, js_font_class_id, font_path); + } + }); + } + void register_hapi_classes(JSContext* ctx, JSModuleDef* m) { register_application_class(ctx, m); register_color_class(ctx, m); register_vector2_class(ctx, m); register_rectangle_class(ctx, m); + register_font_class(ctx, m); } } // namespace HostApi diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 2f18a5c..2df2ba3 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -20,18 +20,7 @@ namespace HostApi { return JS_NewObject(ctx); } - JSValue create_draw_render_object(JSContext* ctx) { - Utils::ScopedJSValue render2d_obj(ctx, JS_NewObject(ctx)); - - // --- FPS Binding --- - JS_SetPropertyStr(ctx, render2d_obj, "drawFPS", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue { - if (argc < 1) return JS_ThrowTypeError(c, "drawFPS requires a Vector2 position argument"); - const auto pos = Utils::get_opaque_or(c, argv[0], js_vector2_class_id, {0, 0}); - ::DrawFPS(static_cast(pos.x), static_cast(pos.y)); - return JS_UNDEFINED; - }, "drawFPS", 1)); - - // --- Shapes Sub-Object --- + static JSValue create_shapes_object(JSContext* ctx) { Utils::ScopedJSValue shape_obj(ctx, JS_NewObject(ctx)); JS_SetPropertyStr(ctx, shape_obj, "drawPixel", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue { @@ -93,7 +82,56 @@ namespace HostApi { return JS_UNDEFINED; }, "drawEllipse", 4)); - JS_SetPropertyStr(ctx, render2d_obj, "shapes", shape_obj.release()); + return shape_obj.release(); + } + + static JSTextOptions parse_text_options(JSContext* ctx, JSValueConst optionsObj) { + JSTextOptions options; + if (JS_IsObject(optionsObj)) { + Utils::try_get_opaque_property(ctx, optionsObj, "font", js_font_class_id, options.font); + Utils::try_get_opaque_property(ctx, optionsObj, "color", js_color_class_id, options.color); + Utils::try_get_float_property(ctx, optionsObj, "rotation", options.rotation); + Utils::try_get_float_property(ctx, optionsObj, "fontSize", options.fontSize); + Utils::try_get_float_property(ctx, optionsObj, "spacing", options.spacing); + Utils::try_get_opaque_property(ctx, optionsObj, "origin", js_vector2_class_id, options.origin); + } + return options; + } + + static JSValue create_text_object(JSContext* ctx) { + Utils::ScopedJSValue text_obj(ctx, JS_NewObject(ctx)); + JS_SetPropertyStr(ctx, text_obj, "drawText", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue { + if (argc < 2) return JS_ThrowTypeError(c, "drawText requires position and text string arguments"); + const auto pos = Utils::get_opaque_or(c, argv[0], js_vector2_class_id, {0, 0}); + + const std::string txt_str = Utils::js_to_std_string(c, argv[1]); + JSTextOptions options = parse_text_options(c, argc > 2 ? argv[2] : JS_UNDEFINED); + Font fontToUse = GetFontDefault(); + if (options.font.font_ptr && options.font.font_ptr->texture.id != 0) { + fontToUse = *options.font.font_ptr; + } + ::DrawTextPro(fontToUse, txt_str.c_str(), pos, options.origin, options.rotation, options.fontSize, options.spacing, options.color); + return JS_UNDEFINED; + }, "drawText", 3)); + + return text_obj.release(); + } + + JSValue create_draw_render_object(JSContext* ctx) { + + Utils::ScopedJSValue render2d_obj(ctx, JS_NewObject(ctx)); + + // Add FPS + JS_SetPropertyStr(ctx, render2d_obj, "drawFPS", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue { + if (argc < 1) return JS_ThrowTypeError(c, "drawFPS requires a Vector2 position argument"); + const auto pos = Utils::get_opaque_or(c, argv[0], js_vector2_class_id, {0, 0}); + ::DrawFPS(static_cast(pos.x), static_cast(pos.y)); + return JS_UNDEFINED; + }, "drawFPS", 1)); + + // Add Sub Objects + JS_SetPropertyStr(ctx, render2d_obj, "shapes", create_shapes_object(ctx)); + JS_SetPropertyStr(ctx, render2d_obj, "text", create_text_object(ctx)); // --- Layer Wrappers --- const JSValue render_obj = JS_NewObject(ctx); diff --git a/src/api/hostapi.hpp b/src/api/hostapi.hpp index f02d65e..fddeb00 100644 --- a/src/api/hostapi.hpp +++ b/src/api/hostapi.hpp @@ -23,6 +23,7 @@ namespace HostApi { JS_AddModuleExport(ctx, m, "Vector2"); JS_AddModuleExport(ctx, m, "Rectangle"); JS_AddModuleExport(ctx, m, "Palette"); + JS_AddModuleExport(ctx, m, "Font"); JS_AddModuleExport(ctx, m, "Info"); JS_AddModuleExport(ctx, m, "ConfigFlags"); } diff --git a/src/api/js_types.hpp b/src/api/js_types.hpp index eea77da..45e8e4f 100644 --- a/src/api/js_types.hpp +++ b/src/api/js_types.hpp @@ -1,15 +1,17 @@ #pragma once #include +#include +#include #include #include -#include "quickjs.h" namespace HostApi { inline JSClassID js_color_class_id; inline JSClassID js_vector2_class_id; inline JSClassID js_rectangle_class_id; + inline JSClassID js_font_class_id; inline JSClassID js_application_class_id; struct JSApplication { @@ -48,6 +50,23 @@ namespace HostApi { [[nodiscard]] constexpr operator Rectangle() const { return Rectangle { x, y, width, height }; } }; + struct JSFont { + std::shared_ptr font_ptr; + JSFont() = default; + explicit JSFont(const std::string& path, const int baseSize = 64) { + const Font f = LoadFontEx(path.c_str(), baseSize, nullptr, 0); + if (f.texture.id != 0) { + SetTextureFilter(f.texture, TEXTURE_FILTER_BILINEAR); + } + font_ptr = std::shared_ptr(new Font(f), [](const Font* pf) { + if (pf->texture.id != 0) { + UnloadFont(*pf); + } + delete pf; + }); + } + }; + struct JSDrawOptions { JSColor color = JSColor(BLACK); float rotation = 0.0f; @@ -55,4 +74,13 @@ namespace HostApi { JSVector2 origin = JSVector2(0, 0); }; + struct JSTextOptions { + JSFont font; + JSColor color = JSColor(BLACK); + float rotation = 0.0f; + float fontSize = 24.0f; + float spacing = 1.0f; + JSVector2 origin = JSVector2(0, 0); + }; + } \ No newline at end of file From b0a397187e43f947cd676998280d02e0f13e7b49 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:42:45 +0100 Subject: [PATCH 02/12] add QuickJS header inclusion for enhanced scripting support --- src/api/js_types.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/js_types.hpp b/src/api/js_types.hpp index 45e8e4f..96536dd 100644 --- a/src/api/js_types.hpp +++ b/src/api/js_types.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace HostApi { From 2bdf864dcd88104ef7dbccebcfc29cdd5358c87e Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:44:32 +0100 Subject: [PATCH 03/12] feat: improve font loading error handling with detailed messages --- src/api/api_classes.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/api_classes.cpp b/src/api/api_classes.cpp index ae480d9..46f44db 100644 --- a/src/api/api_classes.cpp +++ b/src/api/api_classes.cpp @@ -195,11 +195,12 @@ namespace HostApi { if (argc < 1) return JS_ThrowTypeError(c, "Font requires at least a path argument"); auto font_path = Utils::js_to_std_string(c, argv[0]); - if (font_path.empty()) return JS_EXCEPTION; + if (font_path.empty()) return JS_ThrowTypeError(c, "Font path must be a non-empty string"); if (argc >= 2) { int32_t size = 0; - JS_ToInt32(c, &size, argv[1]); + if (JS_ToInt32(c, &size, argv[1]) < 0) return JS_EXCEPTION; + if (size <= 0) return JS_ThrowRangeError(c, "Font size must be a positive integer"); return Utils::create_js_instance(c, new_target, js_font_class_id, font_path, size); } From 1d06a58c64828564877a106aef864711a50ccb8c Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:45:16 +0100 Subject: [PATCH 04/12] feat: enhance text rendering by improving string conversion and memory management --- src/api/api_context.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 2df2ba3..e8f8a55 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -104,7 +104,11 @@ namespace HostApi { if (argc < 2) return JS_ThrowTypeError(c, "drawText requires position and text string arguments"); const auto pos = Utils::get_opaque_or(c, argv[0], js_vector2_class_id, {0, 0}); - const std::string txt_str = Utils::js_to_std_string(c, argv[1]); + const char* txt_cstr = JS_ToCString(c, argv[1]); + if (!txt_cstr) return JS_EXCEPTION; + const std::string txt_str(txt_cstr); + JS_FreeCString(c, txt_cstr); + JSTextOptions options = parse_text_options(c, argc > 2 ? argv[2] : JS_UNDEFINED); Font fontToUse = GetFontDefault(); if (options.font.font_ptr && options.font.font_ptr->texture.id != 0) { From 7bd56beeca471f6d86478110fb98f8e29ae3b21b Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:45:51 +0100 Subject: [PATCH 05/12] fix: correct application initialization parameters for proper window sizing --- examples/2d/text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/2d/text.js b/examples/2d/text.js index 6dc78d1..bd4eac5 100644 --- a/examples/2d/text.js +++ b/examples/2d/text.js @@ -5,7 +5,7 @@ const screenHeight = 800; const fpsPos = new Vector2(10, 10); const origin = new Vector2(0, 0); -const app = new Application(screenHeight, screenWidth,"Window"); +const app = new Application(screenWidth, screenHeight,"Window"); app.run({ onInit() { From 8834b9ae97434f06e7e2718946fc0ab8c13c93d5 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:47:07 +0100 Subject: [PATCH 06/12] fix: adjust screen dimensions and update font path for improved asset loading --- examples/2d/text.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/2d/text.js b/examples/2d/text.js index bd4eac5..fa12e54 100644 --- a/examples/2d/text.js +++ b/examples/2d/text.js @@ -1,7 +1,7 @@ import {Application, Rectangle, Vector2, Palette, Font} from "vectorjs"; -const screenWidth = 600; -const screenHeight = 800; +const screenWidth = 800; +const screenHeight = 600; const fpsPos = new Vector2(10, 10); const origin = new Vector2(0, 0); @@ -9,7 +9,7 @@ const app = new Application(screenWidth, screenHeight,"Window"); app.run({ onInit() { - this.customFont = new Font("C:\\workspace\\c\\vectorjs\\examples\\assets\\AnonymousPro-Regular.ttf"); + this.customFont = new Font("examples/assets/AnonymousPro-Regular.ttf"); }, onDraw(render) { From 3c22f83023caef2cb388df54b35349410375d0b2 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:48:20 +0100 Subject: [PATCH 07/12] feat: add Font class and TextOptions interface for enhanced text rendering customization --- src/vectorjs.d.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/vectorjs.d.ts b/src/vectorjs.d.ts index 4992900..09cb1d9 100644 --- a/src/vectorjs.d.ts +++ b/src/vectorjs.d.ts @@ -97,6 +97,26 @@ declare module "vectorjs" { withLayer2D(callback: (layer: RenderContext) => void): void; } + /** + * Custom font resource loaded from a path. + */ + export class Font { + constructor(path: string); + constructor(path: string, baseSize: number); + } + + /** + * Custom options configuring text rendering layout and styling. + */ + export interface TextOptions { + readonly font?: Font; + readonly color?: Color; + readonly rotation?: number; + readonly fontSize?: number; + readonly spacing?: number; + readonly origin?: Vector2; + } + /** * Context parameter exposed to the `onUpdate` hook loop. */ From 6287dcd023e25fd61cf8cd1145985df1e1538ac0 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:48:26 +0100 Subject: [PATCH 08/12] fix: remove unused Rectangle import from text.js --- examples/2d/text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/2d/text.js b/examples/2d/text.js index fa12e54..96a1116 100644 --- a/examples/2d/text.js +++ b/examples/2d/text.js @@ -1,4 +1,4 @@ -import {Application, Rectangle, Vector2, Palette, Font} from "vectorjs"; +import {Application, Vector2, Palette, Font} from "vectorjs"; const screenWidth = 800; const screenHeight = 600; From 16e67f9cf2b09f4af5a74553fb272d62dd597b43 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:51:26 +0100 Subject: [PATCH 09/12] fix: update fps position and origin values for better alignment and rotation adjustment --- examples/2d/text.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/2d/text.js b/examples/2d/text.js index 96a1116..f2b3c08 100644 --- a/examples/2d/text.js +++ b/examples/2d/text.js @@ -2,8 +2,8 @@ import {Application, Vector2, Palette, Font} from "vectorjs"; const screenWidth = 800; const screenHeight = 600; -const fpsPos = new Vector2(10, 10); -const origin = new Vector2(0, 0); +const fpsPos = new Vector2(screenWidth/2, screenHeight/2); +const origin = new Vector2(1, 1) const app = new Application(screenWidth, screenHeight,"Window"); app.run({ @@ -19,7 +19,7 @@ app.run({ color: Palette.RED, fontSize: 24.0, spacing: 2.0, - rotation: 0.0, + rotation: 25.0, origin: origin }) }); From ed41346512350c776b52e23ac1ecdef58532d786 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:04:01 +0100 Subject: [PATCH 10/12] fix: update property retrieval methods for improved type handling in JS context --- src/api/api_context.cpp | 6 +++--- src/api/js_utils.hpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index e8f8a55..7dde72a 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -9,9 +9,9 @@ namespace HostApi { JSDrawOptions options; if (JS_IsObject(optionsObj)) { Utils::try_get_opaque_property(ctx, optionsObj, "color", js_color_class_id, options.color); - Utils::try_get_opaque_property(ctx, optionsObj, "origin", js_color_class_id, options.origin); - Utils::try_get_opaque_property(ctx, optionsObj, "rotation", js_color_class_id, options.rotation); - Utils::try_get_opaque_property(ctx, optionsObj, "wireframe", js_color_class_id, options.wireframe); + Utils::try_get_opaque_property(ctx, optionsObj, "origin", js_vector2_class_id, options.origin); + Utils::try_get_float_property(ctx, optionsObj, "rotation", options.rotation); + Utils::try_get_bool_property(ctx, optionsObj, "wireframe", options.wireframe); } return options; } diff --git a/src/api/js_utils.hpp b/src/api/js_utils.hpp index 9d2e292..6c1f304 100644 --- a/src/api/js_utils.hpp +++ b/src/api/js_utils.hpp @@ -101,6 +101,17 @@ namespace HostApi::Utils { return false; } + inline bool try_get_bool_property(JSContext* ctx, JSValueConst obj, const char* prop_name, bool& out_val) { + if (!JS_IsObject(obj)) return false; + + ScopedJSValue prop(ctx, JS_GetPropertyStr(ctx, obj, prop_name)); + if (!JS_IsUndefined(prop.get()) && JS_IsBool(prop.get())) { + out_val = static_cast(JS_ToBool(ctx, prop.get())); + return true; + } + return false; + } + // --- 1. Generic Getters and Setters --- template From 4e7662ae54ce640173ef923b67a263dca6378b16 Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:05:34 +0100 Subject: [PATCH 11/12] fix: remove unused vector import from js_types.hpp --- src/api/js_types.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/js_types.hpp b/src/api/js_types.hpp index 96536dd..918b4ad 100644 --- a/src/api/js_types.hpp +++ b/src/api/js_types.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include From 6f0588e0fcd0c69d7550dba2b7b42e3c27b212cd Mon Sep 17 00:00:00 2001 From: Peter Richmond <3448039+burdockcascade@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:39:05 +0100 Subject: [PATCH 12/12] fix: optimize text rendering by reusing CString for DrawTextPro --- src/api/api_context.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 7dde72a..2f7d25b 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -106,15 +106,14 @@ namespace HostApi { const char* txt_cstr = JS_ToCString(c, argv[1]); if (!txt_cstr) return JS_EXCEPTION; - const std::string txt_str(txt_cstr); - JS_FreeCString(c, txt_cstr); JSTextOptions options = parse_text_options(c, argc > 2 ? argv[2] : JS_UNDEFINED); Font fontToUse = GetFontDefault(); if (options.font.font_ptr && options.font.font_ptr->texture.id != 0) { fontToUse = *options.font.font_ptr; } - ::DrawTextPro(fontToUse, txt_str.c_str(), pos, options.origin, options.rotation, options.fontSize, options.spacing, options.color); + ::DrawTextPro(fontToUse, txt_cstr, pos, options.origin, options.rotation, options.fontSize, options.spacing, options.color); + JS_FreeCString(c, txt_cstr); return JS_UNDEFINED; }, "drawText", 3));