Skip to content

Commit ae2de0b

Browse files
feat: add text rendering functionality with custom font support
1 parent 16ef114 commit ae2de0b

5 files changed

Lines changed: 136 additions & 16 deletions

File tree

examples/2d/text.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {Application, Rectangle, Vector2, Palette, Font} from "vectorjs";
2+
3+
const screenWidth = 600;
4+
const screenHeight = 800;
5+
const fpsPos = new Vector2(10, 10);
6+
const origin = new Vector2(0, 0);
7+
8+
const app = new Application(screenHeight, screenWidth,"Window");
9+
app.run({
10+
11+
onInit() {
12+
this.customFont = new Font("C:\\workspace\\c\\vectorjs\\examples\\assets\\AnonymousPro-Regular.ttf");
13+
},
14+
15+
onDraw(render) {
16+
render.withLayer2D((ctx) => {
17+
ctx.text.drawText(fpsPos, "this is a sample text", {
18+
font: this.customFont,
19+
color: Palette.RED,
20+
fontSize: 24.0,
21+
spacing: 2.0,
22+
rotation: 0.0,
23+
origin: origin
24+
})
25+
});
26+
}
27+
});

src/api/api_classes.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ namespace HostApi {
5656
Utils::ScopedJSValue render_obj(ctx, create_draw_render_object(ctx));
5757

5858
if (JS_IsFunction(ctx, on_init_func)) {
59-
Utils::ScopedJSValue ret(ctx, JS_Call(ctx, on_init_func, user_app, 0, nullptr));
60-
if (JS_IsException(ret)) return JS_EXCEPTION;
59+
if (Utils::ScopedJSValue ret(ctx, JS_Call(ctx, on_init_func, user_app, 0, nullptr)); JS_IsException(ret)) {
60+
return JS_EXCEPTION;
61+
}
6162
}
6263

6364
while (!WindowShouldClose()) {
@@ -183,11 +184,36 @@ namespace HostApi {
183184
});
184185
}
185186

187+
static void register_font_class(JSContext* ctx, JSModuleDef* m) {
188+
Utils::register_js_class(ctx, m, {
189+
.name = "Font",
190+
.class_id = js_font_class_id,
191+
.finalizer = [](auto, JSValue val) {
192+
delete Utils::get_opaque<JSFont>(val, js_font_class_id);
193+
},
194+
.constructor = [](auto c, auto new_target, int argc, auto argv) -> JSValue {
195+
if (argc < 1) return JS_ThrowTypeError(c, "Font requires at least a path argument");
196+
197+
auto font_path = Utils::js_to_std_string(c, argv[0]);
198+
if (font_path.empty()) return JS_EXCEPTION;
199+
200+
if (argc >= 2) {
201+
int32_t size = 0;
202+
JS_ToInt32(c, &size, argv[1]);
203+
return Utils::create_js_instance<JSFont>(c, new_target, js_font_class_id, font_path, size);
204+
}
205+
206+
return Utils::create_js_instance<JSFont>(c, new_target, js_font_class_id, font_path);
207+
}
208+
});
209+
}
210+
186211
void register_hapi_classes(JSContext* ctx, JSModuleDef* m) {
187212
register_application_class(ctx, m);
188213
register_color_class(ctx, m);
189214
register_vector2_class(ctx, m);
190215
register_rectangle_class(ctx, m);
216+
register_font_class(ctx, m);
191217
}
192218

193219
} // namespace HostApi

src/api/api_context.cpp

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,7 @@ namespace HostApi {
2020
return JS_NewObject(ctx);
2121
}
2222

23-
JSValue create_draw_render_object(JSContext* ctx) {
24-
Utils::ScopedJSValue render2d_obj(ctx, JS_NewObject(ctx));
25-
26-
// --- FPS Binding ---
27-
JS_SetPropertyStr(ctx, render2d_obj, "drawFPS", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue {
28-
if (argc < 1) return JS_ThrowTypeError(c, "drawFPS requires a Vector2 position argument");
29-
const auto pos = Utils::get_opaque_or<JSVector2>(c, argv[0], js_vector2_class_id, {0, 0});
30-
::DrawFPS(static_cast<int>(pos.x), static_cast<int>(pos.y));
31-
return JS_UNDEFINED;
32-
}, "drawFPS", 1));
33-
34-
// --- Shapes Sub-Object ---
23+
static JSValue create_shapes_object(JSContext* ctx) {
3524
Utils::ScopedJSValue shape_obj(ctx, JS_NewObject(ctx));
3625

3726
JS_SetPropertyStr(ctx, shape_obj, "drawPixel", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue {
@@ -93,7 +82,56 @@ namespace HostApi {
9382
return JS_UNDEFINED;
9483
}, "drawEllipse", 4));
9584

96-
JS_SetPropertyStr(ctx, render2d_obj, "shapes", shape_obj.release());
85+
return shape_obj.release();
86+
}
87+
88+
static JSTextOptions parse_text_options(JSContext* ctx, JSValueConst optionsObj) {
89+
JSTextOptions options;
90+
if (JS_IsObject(optionsObj)) {
91+
Utils::try_get_opaque_property<JSFont>(ctx, optionsObj, "font", js_font_class_id, options.font);
92+
Utils::try_get_opaque_property<JSColor>(ctx, optionsObj, "color", js_color_class_id, options.color);
93+
Utils::try_get_float_property(ctx, optionsObj, "rotation", options.rotation);
94+
Utils::try_get_float_property(ctx, optionsObj, "fontSize", options.fontSize);
95+
Utils::try_get_float_property(ctx, optionsObj, "spacing", options.spacing);
96+
Utils::try_get_opaque_property<JSVector2>(ctx, optionsObj, "origin", js_vector2_class_id, options.origin);
97+
}
98+
return options;
99+
}
100+
101+
static JSValue create_text_object(JSContext* ctx) {
102+
Utils::ScopedJSValue text_obj(ctx, JS_NewObject(ctx));
103+
JS_SetPropertyStr(ctx, text_obj, "drawText", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue {
104+
if (argc < 2) return JS_ThrowTypeError(c, "drawText requires position and text string arguments");
105+
const auto pos = Utils::get_opaque_or<JSVector2>(c, argv[0], js_vector2_class_id, {0, 0});
106+
107+
const std::string txt_str = Utils::js_to_std_string(c, argv[1]);
108+
JSTextOptions options = parse_text_options(c, argc > 2 ? argv[2] : JS_UNDEFINED);
109+
Font fontToUse = GetFontDefault();
110+
if (options.font.font_ptr && options.font.font_ptr->texture.id != 0) {
111+
fontToUse = *options.font.font_ptr;
112+
}
113+
::DrawTextPro(fontToUse, txt_str.c_str(), pos, options.origin, options.rotation, options.fontSize, options.spacing, options.color);
114+
return JS_UNDEFINED;
115+
}, "drawText", 3));
116+
117+
return text_obj.release();
118+
}
119+
120+
JSValue create_draw_render_object(JSContext* ctx) {
121+
122+
Utils::ScopedJSValue render2d_obj(ctx, JS_NewObject(ctx));
123+
124+
// Add FPS
125+
JS_SetPropertyStr(ctx, render2d_obj, "drawFPS", JS_NewCFunction(ctx, [](JSContext* c, JSValueConst, int argc, JSValueConst* argv) -> JSValue {
126+
if (argc < 1) return JS_ThrowTypeError(c, "drawFPS requires a Vector2 position argument");
127+
const auto pos = Utils::get_opaque_or<JSVector2>(c, argv[0], js_vector2_class_id, {0, 0});
128+
::DrawFPS(static_cast<int>(pos.x), static_cast<int>(pos.y));
129+
return JS_UNDEFINED;
130+
}, "drawFPS", 1));
131+
132+
// Add Sub Objects
133+
JS_SetPropertyStr(ctx, render2d_obj, "shapes", create_shapes_object(ctx));
134+
JS_SetPropertyStr(ctx, render2d_obj, "text", create_text_object(ctx));
97135

98136
// --- Layer Wrappers ---
99137
const JSValue render_obj = JS_NewObject(ctx);

src/api/hostapi.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace HostApi {
2323
JS_AddModuleExport(ctx, m, "Vector2");
2424
JS_AddModuleExport(ctx, m, "Rectangle");
2525
JS_AddModuleExport(ctx, m, "Palette");
26+
JS_AddModuleExport(ctx, m, "Font");
2627
JS_AddModuleExport(ctx, m, "Info");
2728
JS_AddModuleExport(ctx, m, "ConfigFlags");
2829
}

src/api/js_types.hpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
#pragma once
22

33
#include <string>
4+
#include <vector>
5+
#include <memory>
46
#include <cstdint>
57
#include <raylib.h>
6-
#include "quickjs.h"
78

89
namespace HostApi {
910

1011
inline JSClassID js_color_class_id;
1112
inline JSClassID js_vector2_class_id;
1213
inline JSClassID js_rectangle_class_id;
14+
inline JSClassID js_font_class_id;
1315
inline JSClassID js_application_class_id;
1416

1517
struct JSApplication {
@@ -48,11 +50,37 @@ namespace HostApi {
4850
[[nodiscard]] constexpr operator Rectangle() const { return Rectangle { x, y, width, height }; }
4951
};
5052

53+
struct JSFont {
54+
std::shared_ptr<Font> font_ptr;
55+
JSFont() = default;
56+
explicit JSFont(const std::string& path, const int baseSize = 64) {
57+
const Font f = LoadFontEx(path.c_str(), baseSize, nullptr, 0);
58+
if (f.texture.id != 0) {
59+
SetTextureFilter(f.texture, TEXTURE_FILTER_BILINEAR);
60+
}
61+
font_ptr = std::shared_ptr<Font>(new Font(f), [](const Font* pf) {
62+
if (pf->texture.id != 0) {
63+
UnloadFont(*pf);
64+
}
65+
delete pf;
66+
});
67+
}
68+
};
69+
5170
struct JSDrawOptions {
5271
JSColor color = JSColor(BLACK);
5372
float rotation = 0.0f;
5473
bool wireframe = false;
5574
JSVector2 origin = JSVector2(0, 0);
5675
};
5776

77+
struct JSTextOptions {
78+
JSFont font;
79+
JSColor color = JSColor(BLACK);
80+
float rotation = 0.0f;
81+
float fontSize = 24.0f;
82+
float spacing = 1.0f;
83+
JSVector2 origin = JSVector2(0, 0);
84+
};
85+
5886
}

0 commit comments

Comments
 (0)