1#include <string.h>
2#include "api.h"
3#include "../renderer.h"
4#include "../rencache.h"
5#include "lua.h"
6
7// a reference index to a table that stores the fonts
8static int RENDERER_FONT_REF = LUA_NOREF;
9
10static int font_get_options(
11 lua_State *L,
12 ERenFontAntialiasing *antialiasing,
13 ERenFontHinting *hinting,
14 int *style
15) {
16 if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
17 lua_getfield(L, 3, "antialiasing");
18 if (lua_isstring(L, -1)) {
19 const char *antialiasing_str = lua_tostring(L, -1);
20 if (antialiasing_str) {
21 if (strcmp(antialiasing_str, "none") == 0) {
22 *antialiasing = FONT_ANTIALIASING_NONE;
23 } else if (strcmp(antialiasing_str, "grayscale") == 0) {
24 *antialiasing = FONT_ANTIALIASING_GRAYSCALE;
25 } else if (strcmp(antialiasing_str, "subpixel") == 0) {
26 *antialiasing = FONT_ANTIALIASING_SUBPIXEL;
27 } else {
28 return luaL_error(
29 L,
30 "error in font options, unknown antialiasing option: \"%s\"",
31 antialiasing_str
32 );
33 }
34 }
35 }
36 lua_getfield(L, 3, "hinting");
37 if (lua_isstring(L, -1)) {
38 const char *hinting_str = lua_tostring(L, -1);
39 if (hinting_str) {
40 if (strcmp(hinting_str, "slight") == 0) {
41 *hinting = FONT_HINTING_SLIGHT;
42 } else if (strcmp(hinting_str, "none") == 0) {
43 *hinting = FONT_HINTING_NONE;
44 } else if (strcmp(hinting_str, "full") == 0) {
45 *hinting = FONT_HINTING_FULL;
46 } else {
47 return luaL_error(
48 L,
49 "error in font options, unknown hinting option: \"%s\"",
50 hinting
51 );
52 }
53 }
54 }
55 int style_local = 0;
56 lua_getfield(L, 3, "italic");
57 if (lua_toboolean(L, -1))
58 style_local |= FONT_STYLE_ITALIC;
59 lua_getfield(L, 3, "bold");
60 if (lua_toboolean(L, -1))
61 style_local |= FONT_STYLE_BOLD;
62 lua_getfield(L, 3, "underline");
63 if (lua_toboolean(L, -1))
64 style_local |= FONT_STYLE_UNDERLINE;
65 lua_getfield(L, 3, "smoothing");
66 if (lua_toboolean(L, -1))
67 style_local |= FONT_STYLE_SMOOTH;
68 lua_getfield(L, 3, "strikethrough");
69 if (lua_toboolean(L, -1))
70 style_local |= FONT_STYLE_STRIKETHROUGH;
71
72 lua_pop(L, 5);
73
74 if (style_local != 0)
75 *style = style_local;
76 }
77
78 return 0;
79}
80
81static int f_font_load(lua_State *L) {
82 const char *filename = luaL_checkstring(L, 1);
83 float size = luaL_checknumber(L, 2);
84 int style = 0;
85 ERenFontHinting hinting = FONT_HINTING_SLIGHT;
86 ERenFontAntialiasing antialiasing = FONT_ANTIALIASING_SUBPIXEL;
87
88 int ret_code = font_get_options(L, &antialiasing, &hinting, &style);
89 if (ret_code > 0)
90 return ret_code;
91
92 RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
93 *font = ren_font_load(&window_renderer, filename, size, antialiasing, hinting, style);
94 if (!*font)
95 return luaL_error(L, "failed to load font");
96 luaL_setmetatable(L, API_TYPE_FONT);
97 return 1;
98}
99
100static bool font_retrieve(lua_State* L, RenFont** fonts, int idx) {
101 memset(fonts, 0, sizeof(RenFont*)*FONT_FALLBACK_MAX);
102 if (lua_type(L, idx) != LUA_TTABLE) {
103 fonts[0] = *(RenFont**)luaL_checkudata(L, idx, API_TYPE_FONT);
104 return false;
105 }
106 int len = luaL_len(L, idx); len = len > FONT_FALLBACK_MAX ? FONT_FALLBACK_MAX : len;
107 for (int i = 0; i < len; i++) {
108 lua_rawgeti(L, idx, i+1);
109 fonts[i] = *(RenFont**) luaL_checkudata(L, -1, API_TYPE_FONT);
110 lua_pop(L, 1);
111 }
112 return true;
113}
114
115static int f_font_copy(lua_State *L) {
116 RenFont* fonts[FONT_FALLBACK_MAX];
117 bool table = font_retrieve(L, fonts, 1);
118 float size = lua_gettop(L) >= 2 ? luaL_checknumber(L, 2) : ren_font_group_get_height(fonts);
119 int style = -1;
120 ERenFontHinting hinting = -1;
121 ERenFontAntialiasing antialiasing = -1;
122
123 int ret_code = font_get_options(L, &antialiasing, &hinting, &style);
124 if (ret_code > 0)
125 return ret_code;
126
127 if (table) {
128 lua_newtable(L);
129 luaL_setmetatable(L, API_TYPE_FONT);
130 }
131 for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
132 RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
133 *font = ren_font_copy(&window_renderer, fonts[i], size, antialiasing, hinting, style);
134 if (!*font)
135 return luaL_error(L, "failed to copy font");
136 luaL_setmetatable(L, API_TYPE_FONT);
137 if (table)
138 lua_rawseti(L, -2, i+1);
139 }
140 return 1;
141}
142
143static int f_font_group(lua_State* L) {
144 int table_size;
145 luaL_checktype(L, 1, LUA_TTABLE);
146
147 table_size = lua_rawlen(L, 1);
148 if (table_size <= 0)
149 return luaL_error(L, "failed to create font group: table is empty");
150 if (table_size > FONT_FALLBACK_MAX)
151 return luaL_error(L, "failed to create font group: table size too large");
152
153 // we also need to ensure that there are no fontgroups inside it
154 for (int i = 1; i <= table_size; i++) {
155 if (lua_rawgeti(L, 1, i) != LUA_TUSERDATA)
156 return luaL_typeerror(L, -1, API_TYPE_FONT "(userdata)");
157 lua_pop(L, 1);
158 }
159
160 luaL_setmetatable(L, API_TYPE_FONT);
161 return 1;
162}
163
164static int f_font_get_path(lua_State *L) {
165 RenFont* fonts[FONT_FALLBACK_MAX];
166 bool table = font_retrieve(L, fonts, 1);
167
168 if (table) {
169 lua_newtable(L);
170 }
171 for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
172 const char* path = ren_font_get_path(fonts[i]);
173 lua_pushstring(L, path);
174 if (table)
175 lua_rawseti(L, -2, i+1);
176 }
177 return 1;
178}
179
180static int f_font_set_tab_size(lua_State *L) {
181 RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
182 int n = luaL_checknumber(L, 2);
183 ren_font_group_set_tab_size(fonts, n);
184 return 0;
185}
186
187static int f_font_gc(lua_State *L) {
188 if (lua_istable(L, 1)) return 0; // do not run if its FontGroup
189 RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
190 ren_font_free(*self);
191
192 return 0;
193}
194
195
196static int f_font_get_width(lua_State *L) {
197 RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
198 size_t len;
199 const char *text = luaL_checklstring(L, 2, &len);
200
201 lua_pushnumber(L, ren_font_group_get_width(&window_renderer, fonts, text, len));
202 return 1;
203}
204
205static int f_font_get_height(lua_State *L) {
206 RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
207 lua_pushnumber(L, ren_font_group_get_height(fonts));
208 return 1;
209}
210
211static int f_font_get_size(lua_State *L) {
212 RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
213 lua_pushnumber(L, ren_font_group_get_size(fonts));
214 return 1;
215}
216
217static int f_font_set_size(lua_State *L) {
218 RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
219 float size = luaL_checknumber(L, 2);
220 ren_font_group_set_size(&window_renderer, fonts, size);
221 return 0;
222}
223
224static int color_value_error(lua_State *L, int idx, int table_idx) {
225 const char *type, *msg;
226 // generate an appropriate error message
227 if (luaL_getmetafield(L, -1, "__name") == LUA_TSTRING) {
228 type = lua_tostring(L, -1); // metatable name
229 } else if (lua_type(L, -1) == LUA_TLIGHTUSERDATA) {
230 type = "light userdata"; // special name for light userdata
231 } else {
232 type = lua_typename(L, lua_type(L, -1)); // default name
233 }
234 // the reason it went through so much hoops is to generate the correct error
235 // message (with function name and proper index).
236 msg = lua_pushfstring(L, "table[%d]: %s expected, got %s", table_idx, lua_typename(L, LUA_TNUMBER), type);
237 return luaL_argerror(L, idx, msg);
238}
239
240static int get_color_value(lua_State *L, int idx, int table_idx) {
241 lua_rawgeti(L, idx, table_idx);
242 return lua_isnumber(L, -1) ? lua_tonumber(L, -1) : color_value_error(L, idx, table_idx);
243}
244
245static int get_color_value_opt(lua_State *L, int idx, int table_idx, int default_value) {
246 lua_rawgeti(L, idx, table_idx);
247 if (lua_isnoneornil(L, -1))
248 return default_value;
249 else if (lua_isnumber(L, -1))
250 return lua_tonumber(L, -1);
251 else
252 return color_value_error(L, idx, table_idx);
253}
254
255static RenColor checkcolor(lua_State *L, int idx, int def) {
256 RenColor color;
257 if (lua_isnoneornil(L, idx)) {
258 return (RenColor) { def, def, def, 255 };
259 }
260 luaL_checktype(L, idx, LUA_TTABLE);
261 color.r = get_color_value(L, idx, 1);
262 color.g = get_color_value(L, idx, 2);
263 color.b = get_color_value(L, idx, 3);
264 color.a = get_color_value_opt(L, idx, 4, 255);
265 lua_pop(L, 4);
266 return color;
267}
268
269
270static int f_show_debug(lua_State *L) {
271 luaL_checkany(L, 1);
272 rencache_show_debug(lua_toboolean(L, 1));
273 return 0;
274}
275
276
277static int f_get_size(lua_State *L) {
278 int w, h;
279 ren_get_size(&window_renderer, &w, &h);
280 lua_pushnumber(L, w);
281 lua_pushnumber(L, h);
282 return 2;
283}
284
285
286static int f_begin_frame(UNUSED lua_State *L) {
287 rencache_begin_frame(&window_renderer);
288 return 0;
289}
290
291
292static int f_end_frame(UNUSED lua_State *L) {
293 rencache_end_frame(&window_renderer);
294 // clear the font reference table
295 lua_newtable(L);
296 lua_rawseti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
297 return 0;
298}
299
300
301static RenRect rect_to_grid(lua_Number x, lua_Number y, lua_Number w, lua_Number h) {
302 int x1 = (int) (x + 0.5), y1 = (int) (y + 0.5);
303 int x2 = (int) (x + w + 0.5), y2 = (int) (y + h + 0.5);
304 return (RenRect) {x1, y1, x2 - x1, y2 - y1};
305}
306
307
308static int f_set_clip_rect(lua_State *L) {
309 lua_Number x = luaL_checknumber(L, 1);
310 lua_Number y = luaL_checknumber(L, 2);
311 lua_Number w = luaL_checknumber(L, 3);
312 lua_Number h = luaL_checknumber(L, 4);
313 RenRect rect = rect_to_grid(x, y, w, h);
314 rencache_set_clip_rect(rect);
315 return 0;
316}
317
318
319static int f_draw_rect(lua_State *L) {
320 lua_Number x = luaL_checknumber(L, 1);
321 lua_Number y = luaL_checknumber(L, 2);
322 lua_Number w = luaL_checknumber(L, 3);
323 lua_Number h = luaL_checknumber(L, 4);
324 RenRect rect = rect_to_grid(x, y, w, h);
325 RenColor color = checkcolor(L, 5, 255);
326 rencache_draw_rect(rect, color);
327 return 0;
328}
329
330static int f_draw_text(lua_State *L) {
331 RenFont* fonts[FONT_FALLBACK_MAX];
332 font_retrieve(L, fonts, 1);
333
334 // stores a reference to this font to the reference table
335 lua_rawgeti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
336 if (lua_istable(L, -1))
337 {
338 lua_pushvalue(L, 1);
339 lua_pushboolean(L, 1);
340 lua_rawset(L, -3);
341 } else {
342 fprintf(stderr, "warning: failed to reference count fonts\n");
343 }
344 lua_pop(L, 1);
345
346 size_t len;
347 const char *text = luaL_checklstring(L, 2, &len);
348 double x = luaL_checknumber(L, 3);
349 int y = luaL_checknumber(L, 4);
350 RenColor color = checkcolor(L, 5, 255);
351 x = rencache_draw_text(&window_renderer, fonts, text, len, x, y, color);
352 lua_pushnumber(L, x);
353 return 1;
354}
355
356static const luaL_Reg lib[] = {
357 { "show_debug", f_show_debug },
358 { "get_size", f_get_size },
359 { "begin_frame", f_begin_frame },
360 { "end_frame", f_end_frame },
361 { "set_clip_rect", f_set_clip_rect },
362 { "draw_rect", f_draw_rect },
363 { "draw_text", f_draw_text },
364 { NULL, NULL }
365};
366
367static const luaL_Reg fontLib[] = {
368 { "__gc", f_font_gc },
369 { "load", f_font_load },
370 { "copy", f_font_copy },
371 { "group", f_font_group },
372 { "set_tab_size", f_font_set_tab_size },
373 { "get_width", f_font_get_width },
374 { "get_height", f_font_get_height },
375 { "get_size", f_font_get_size },
376 { "set_size", f_font_set_size },
377 { "get_path", f_font_get_path },
378 { NULL, NULL }
379};
380
381int luaopen_renderer(lua_State *L) {
382 // gets a reference on the registry to store font data
383 lua_newtable(L);
384 RENDERER_FONT_REF = luaL_ref(L, LUA_REGISTRYINDEX);
385
386 luaL_newlib(L, lib);
387 luaL_newmetatable(L, API_TYPE_FONT);
388 luaL_setfuncs(L, fontLib, 0);
389 lua_pushvalue(L, -1);
390 lua_setfield(L, -2, "__index");
391 lua_setfield(L, -2, "font");
392 return 1;
393}
394