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 |
8 | static int RENDERER_FONT_REF = LUA_NOREF; |
9 | |
10 | static 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 | |
81 | static 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 | |
100 | static 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 | |
115 | static 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 | |
143 | static 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 | |
164 | static 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 | |
180 | static 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 | |
187 | static 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 | |
196 | static 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 | |
205 | static 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 | |
211 | static 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 | |
217 | static 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 | |
224 | static 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 | |
240 | static 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 | |
245 | static 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 | |
255 | static 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 | |
270 | static 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 | |
277 | static 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 | |
286 | static int f_begin_frame(UNUSED lua_State *L) { |
287 | rencache_begin_frame(&window_renderer); |
288 | return 0; |
289 | } |
290 | |
291 | |
292 | static 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 | |
301 | static 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 | |
308 | static 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 | |
319 | static 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 | |
330 | static 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 | |
356 | static 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 | |
367 | static 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 | |
381 | int 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 | |