1 /*
2 * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3 *
4 * Solarus is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Solarus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "solarus/core/CurrentQuest.h"
18 #include "solarus/core/FontResource.h"
19 #include "solarus/core/QuestFiles.h"
20 #include "solarus/graphics/TextSurface.h"
21 #include "solarus/graphics/Surface.h"
22 #include "solarus/lua/LuaContext.h"
23 #include "solarus/lua/LuaTools.h"
24
25 namespace Solarus {
26
27 using RenderingMode = TextSurface::RenderingMode;
28 using HorizontalAlignment = TextSurface::HorizontalAlignment;
29 using VerticalAlignment = TextSurface::VerticalAlignment;
30
31 /**
32 * Name of the Lua table representing the text surface module.
33 */
34 const std::string LuaContext::text_surface_module_name = "sol.text_surface";
35
36 namespace {
37
38 const std::map<RenderingMode, std::string> rendering_mode_names = {
39 { RenderingMode::SOLID, "solid" },
40 { RenderingMode::ANTIALIASING, "antialiasing" }
41 };
42
43 const std::map<HorizontalAlignment, std::string> horizontal_alignment_names = {
44 { HorizontalAlignment::LEFT, "left" },
45 { HorizontalAlignment::CENTER, "center" },
46 { HorizontalAlignment::RIGHT, "right" }
47 };
48
49 const std::map<VerticalAlignment, std::string> vertical_alignment_names = {
50 { VerticalAlignment::TOP, "top" },
51 { VerticalAlignment::MIDDLE, "middle" },
52 { VerticalAlignment::BOTTOM, "bottom" }
53 };
54
55 }
56
57 /**
58 * \brief Initializes the text surface features provided to Lua.
59 */
register_text_surface_module()60 void LuaContext::register_text_surface_module() {
61
62 // Functions of sol.surface.
63 std::vector<luaL_Reg> functions = {
64 { "create", text_surface_api_create }
65 };
66
67 // Methods of the text_surface type.
68 std::vector<luaL_Reg> methods = {
69 { "get_horizontal_alignment", text_surface_api_get_horizontal_alignment },
70 { "set_horizontal_alignment", text_surface_api_set_horizontal_alignment },
71 { "get_vertical_alignment", text_surface_api_get_vertical_alignment },
72 { "set_vertical_alignment", text_surface_api_set_vertical_alignment },
73 { "get_font", text_surface_api_get_font },
74 { "set_font", text_surface_api_set_font },
75 { "get_rendering_mode", text_surface_api_get_rendering_mode },
76 { "set_rendering_mode", text_surface_api_set_rendering_mode },
77 { "get_color", text_surface_api_get_color },
78 { "set_color", text_surface_api_set_color },
79 { "get_font_size", text_surface_api_get_font_size },
80 { "set_font_size", text_surface_api_set_font_size },
81 { "get_text", text_surface_api_get_text },
82 { "set_text", text_surface_api_set_text },
83 { "set_text_key", text_surface_api_set_text_key },
84 { "get_size", text_surface_api_get_size },
85 { "draw", drawable_api_draw },
86 { "draw_region", drawable_api_draw_region },
87 { "get_blend_mode", drawable_api_get_blend_mode },
88 { "set_blend_mode", drawable_api_set_blend_mode },
89 { "fade_in", drawable_api_fade_in },
90 { "fade_out", drawable_api_fade_out },
91 { "get_xy", drawable_api_get_xy },
92 { "set_xy", drawable_api_set_xy },
93 { "get_movement", drawable_api_get_movement },
94 { "stop_movement", drawable_api_stop_movement }
95 };
96
97 if (CurrentQuest::is_format_at_least({ 1, 6 })) {
98 methods.insert(methods.end(), {
99 { "set_shader", drawable_api_set_shader },
100 { "get_shader", drawable_api_get_shader },
101 { "set_color_modulation", drawable_api_set_color_modulation },
102 { "get_color_modulation", drawable_api_get_color_modulation },
103 { "get_opacity", drawable_api_get_opacity },
104 { "set_opacity", drawable_api_set_opacity },
105 { "set_rotation", drawable_api_set_rotation },
106 { "get_rotation", drawable_api_get_rotation },
107 { "set_scale", drawable_api_set_scale },
108 { "get_scale", drawable_api_get_scale },
109 { "set_transformation_origin", drawable_api_set_transformation_origin },
110 { "get_transformation_origin", drawable_api_get_transformation_origin },
111 });
112
113 functions.insert(functions.end(),{
114 { "get_predicted_size", text_surface_api_get_predicted_size}
115 });
116 }
117
118 const std::vector<luaL_Reg> metamethods = {
119 { "__gc", drawable_meta_gc },
120 { "__newindex", userdata_meta_newindex_as_table },
121 { "__index", userdata_meta_index_as_table }
122 };
123
124 register_type(text_surface_module_name, functions, methods, metamethods);
125 }
126
127 /**
128 * \brief Returns whether a value is a userdata of type text surface.
129 * \param l A Lua context.
130 * \param index An index in the stack.
131 * \return true if the value at this index is a text surface.
132 */
is_text_surface(lua_State * l,int index)133 bool LuaContext::is_text_surface(lua_State* l, int index) {
134 return is_userdata(l, index, text_surface_module_name);
135 }
136
137 /**
138 * \brief Checks that the userdata at the specified index of the stack is a
139 * text surface and returns it.
140 * \param l a Lua context
141 * \param index an index in the stack
142 * \return the text surface
143 */
check_text_surface(lua_State * l,int index)144 std::shared_ptr<TextSurface> LuaContext::check_text_surface(lua_State* l, int index) {
145 return std::static_pointer_cast<TextSurface>(check_userdata(
146 l, index, text_surface_module_name
147 ));
148 }
149
150 /**
151 * \brief Pushes a text surface userdata onto the stack.
152 * \param l a Lua context
153 * \param text_surface a text surface
154 */
push_text_surface(lua_State * l,TextSurface & text_surface)155 void LuaContext::push_text_surface(lua_State* l, TextSurface& text_surface) {
156 push_userdata(l, text_surface);
157 }
158
159 /**
160 * \brief Implementation of sol.text_surface.create().
161 * \param l The Lua context that is calling this function.
162 * \return Number of values to return to Lua.
163 */
text_surface_api_create(lua_State * l)164 int LuaContext::text_surface_api_create(lua_State* l) {
165
166 return state_boundary_handle(l, [&] {
167 std::shared_ptr<TextSurface> text_surface =
168 std::make_shared<TextSurface>(0, 0);
169
170 if (lua_gettop(l) > 0) {
171 LuaTools::check_type(l, 1, LUA_TTABLE);
172
173 const std::string& font_id = LuaTools::opt_string_field(
174 l, 1, "font", FontResource::get_default_font_id()
175 );
176 RenderingMode rendering_mode =
177 LuaTools::opt_enum_field<RenderingMode>(
178 l, 1, "rendering_mode", rendering_mode_names, RenderingMode::SOLID
179 );
180 HorizontalAlignment horizontal_alignment =
181 LuaTools::opt_enum_field<HorizontalAlignment>(
182 l, 1, "horizontal_alignment", horizontal_alignment_names, HorizontalAlignment::LEFT
183 );
184 VerticalAlignment vertical_alignment =
185 LuaTools::opt_enum_field<VerticalAlignment>(
186 l, 1, "vertical_alignment", vertical_alignment_names, VerticalAlignment::MIDDLE
187 );
188 const Color& color = LuaTools::opt_color_field(l, 1, "color", Color::white);
189 int font_size = LuaTools::opt_int_field(l, 1, "font_size", TextSurface::default_font_size);
190 const std::string& text = LuaTools::opt_string_field(l, 1, "text", "");
191 const std::string& text_key = LuaTools::opt_string_field(l, 1, "text_key", "");
192
193 if (!FontResource::exists(font_id)) {
194 LuaTools::error(l, std::string("No such font: '") + font_id + "'");
195 }
196 text_surface->set_font(font_id);
197 text_surface->set_rendering_mode(rendering_mode);
198 text_surface->set_horizontal_alignment(horizontal_alignment);
199 text_surface->set_vertical_alignment(vertical_alignment);
200 text_surface->set_text_color(color);
201 text_surface->set_font_size(font_size);
202
203 if (!text.empty()) {
204 text_surface->set_text(text);
205 }
206 else if (!text_key.empty()) {
207 if (!CurrentQuest::string_exists(text_key)) {
208 LuaTools::error(l, std::string("No value with key '") + text_key
209 + "' in strings.dat for language '"
210 + CurrentQuest::get_language() + "'"
211 );
212 }
213 text_surface->set_text(CurrentQuest::get_string(text_key));
214 }
215 }
216 get().add_drawable(text_surface);
217
218 push_text_surface(l, *text_surface);
219 return 1;
220 });
221 }
222
223 /**
224 * @brief Implementation of text_surface.get_predicted_size()
225 * @param l the lua state
226 * @return number of values to return to lua
227 */
text_surface_api_get_predicted_size(lua_State * l)228 int LuaContext::text_surface_api_get_predicted_size(lua_State* l) {
229 return state_boundary_handle(l,[&]{
230 std::string font_id = LuaTools::check_string(l,1);
231 int font_size = LuaTools::check_int(l,2);
232 std::string text = LuaTools::check_string(l,3);
233
234 if (!FontResource::exists(font_id)) {
235 LuaTools::error(l, std::string("No such font: '") + font_id + "'");
236 }
237 int w=0,h=0;
238 if(FontResource::is_bitmap_font(font_id)) {
239 int utf8len = 0;
240 for(char c : text) {
241 utf8len += (c & 0xc0) != 0x80;
242 }
243 const SurfacePtr& bitmap = FontResource::get_bitmap_font(font_id);
244 const Size& bitmap_size = bitmap->get_size();
245 int char_width = bitmap_size.width / 128;
246 h = bitmap_size.height / 16;
247 w = (char_width-1)*utf8len+1;
248 } else {
249 TTF_Font& font = FontResource::get_outline_font(font_id, font_size);
250 TTF_SizeUTF8(&font,text.c_str(),&w,&h);
251 }
252 lua_pushinteger(l,w);
253 lua_pushinteger(l,h);
254 return 2;
255 });
256 }
257
258 /**
259 * \brief Implementation of text_surface:get_horizontal_alignment().
260 * \param l the Lua context that is calling this function
261 * \return number of values to return to Lua
262 */
text_surface_api_get_horizontal_alignment(lua_State * l)263 int LuaContext::text_surface_api_get_horizontal_alignment(lua_State* l) {
264
265 return state_boundary_handle(l, [&] {
266 const TextSurface& text_surface = *check_text_surface(l, 1);
267
268 HorizontalAlignment alignment = text_surface.get_horizontal_alignment();
269
270 push_string(l, horizontal_alignment_names.find(alignment)->second);
271 return 1;
272 });
273 }
274
275 /**
276 * \brief Implementation of text_surface:set_horizontal_alignment().
277 * \param l the Lua context that is calling this function
278 * \return number of values to return to Lua
279 */
text_surface_api_set_horizontal_alignment(lua_State * l)280 int LuaContext::text_surface_api_set_horizontal_alignment(lua_State* l) {
281
282 return state_boundary_handle(l, [&] {
283 TextSurface& text_surface = *check_text_surface(l, 1);
284 TextSurface::HorizontalAlignment alignment =
285 LuaTools::check_enum<TextSurface::HorizontalAlignment>(
286 l, 2, horizontal_alignment_names);
287
288 text_surface.set_horizontal_alignment(alignment);
289
290 return 0;
291 });
292 }
293
294 /**
295 * \brief Implementation of text_surface:get_vertical_alignment().
296 * \param l the Lua context that is calling this function
297 * \return number of values to return to Lua
298 */
text_surface_api_get_vertical_alignment(lua_State * l)299 int LuaContext::text_surface_api_get_vertical_alignment(lua_State* l) {
300
301 return state_boundary_handle(l, [&] {
302 const TextSurface& text_surface = *check_text_surface(l, 1);
303
304 TextSurface::VerticalAlignment alignment = text_surface.get_vertical_alignment();
305
306 push_string(l, vertical_alignment_names.find(alignment)->second);
307 return 1;
308 });
309 }
310
311 /**
312 * \brief Implementation of text_surface:set_vertical_alignment().
313 * \param l the Lua context that is calling this function
314 * \return number of values to return to Lua
315 */
text_surface_api_set_vertical_alignment(lua_State * l)316 int LuaContext::text_surface_api_set_vertical_alignment(lua_State* l) {
317
318 return state_boundary_handle(l, [&] {
319 TextSurface& text_surface = *check_text_surface(l, 1);
320 TextSurface::VerticalAlignment alignment =
321 LuaTools::check_enum<TextSurface::VerticalAlignment>(
322 l, 2, vertical_alignment_names);
323
324 text_surface.set_vertical_alignment(alignment);
325
326 return 0;
327 });
328 }
329
330 /**
331 * \brief Implementation of text_surface:get_font().
332 * \param l the Lua context that is calling this function
333 * \return number of values to return to Lua
334 */
text_surface_api_get_font(lua_State * l)335 int LuaContext::text_surface_api_get_font(lua_State* l) {
336
337 return state_boundary_handle(l, [&] {
338 const TextSurface& text_surface = *check_text_surface(l, 1);
339
340 const std::string& font_id = text_surface.get_font();
341 push_string(l, font_id);
342
343 return 1;
344 });
345 }
346
347 /**
348 * \brief Implementation of text_surface:set_text().
349 * \param l the Lua context that is calling this function
350 * \return number of values to return to Lua
351 */
text_surface_api_set_font(lua_State * l)352 int LuaContext::text_surface_api_set_font(lua_State* l) {
353
354 return state_boundary_handle(l, [&] {
355 TextSurface& text_surface = *check_text_surface(l, 1);
356 const std::string& font_id = LuaTools::check_string(l, 2);
357
358 if (!FontResource::exists(font_id)) {
359 LuaTools::arg_error(l, 2, std::string("No such font: '") + font_id + "'");
360 }
361 text_surface.set_font(font_id);
362
363 return 0;
364 });
365 }
366
367 /**
368 * \brief Implementation of text_surface:get_rendering_mode().
369 * \param l the Lua context that is calling this function
370 * \return number of values to return to Lua
371 */
text_surface_api_get_rendering_mode(lua_State * l)372 int LuaContext::text_surface_api_get_rendering_mode(lua_State* l) {
373
374 return state_boundary_handle(l, [&] {
375 const TextSurface& text_surface = *check_text_surface(l, 1);
376
377 TextSurface::RenderingMode mode = text_surface.get_rendering_mode();
378
379 push_string(l, rendering_mode_names.find(mode)->second);
380 return 1;
381 });
382 }
383
384 /**
385 * \brief Implementation of text_surface:set_rendering_mode().
386 * \param l the Lua context that is calling this function
387 * \return number of values to return to Lua
388 */
text_surface_api_set_rendering_mode(lua_State * l)389 int LuaContext::text_surface_api_set_rendering_mode(lua_State* l) {
390
391 return state_boundary_handle(l, [&] {
392 TextSurface& text_surface = *check_text_surface(l, 1);
393 TextSurface::RenderingMode mode = LuaTools::check_enum<TextSurface::RenderingMode>(
394 l, 2, rendering_mode_names);
395
396 text_surface.set_rendering_mode(mode);
397
398 return 0;
399 });
400 }
401
402 /**
403 * \brief Implementation of text_surface:get_color().
404 * \param l the Lua context that is calling this function
405 * \return number of values to return to Lua
406 */
text_surface_api_get_color(lua_State * l)407 int LuaContext::text_surface_api_get_color(lua_State* l) {
408
409 return state_boundary_handle(l, [&] {
410 const TextSurface& text_surface = *check_text_surface(l, 1);
411
412 const Color& color = text_surface.get_text_color();
413
414 push_color(l, color);
415 return 1;
416 });
417 }
418
419 /**
420 * \brief Implementation of text_surface:set_color().
421 * \param l the Lua context that is calling this function
422 * \return number of values to return to Lua
423 */
text_surface_api_set_color(lua_State * l)424 int LuaContext::text_surface_api_set_color(lua_State* l) {
425
426 return state_boundary_handle(l, [&] {
427 TextSurface& text_surface = *check_text_surface(l, 1);
428 const Color& color = LuaTools::check_color(l, 2);
429
430 text_surface.set_text_color(color);
431
432 return 0;
433 });
434 }
435
436 /**
437 * \brief Implementation of text_surface:get_font_size().
438 * \param l the Lua context that is calling this function
439 * \return number of values to return to Lua
440 */
text_surface_api_get_font_size(lua_State * l)441 int LuaContext::text_surface_api_get_font_size(lua_State* l) {
442
443 return state_boundary_handle(l, [&] {
444 const TextSurface& text_surface = *check_text_surface(l, 1);
445
446 lua_pushinteger(l, text_surface.get_font_size());
447 return 1;
448 });
449 }
450
451 /**
452 * \brief Implementation of text_surface:set_font_size().
453 * \param l the Lua context that is calling this function
454 * \return number of values to return to Lua
455 */
text_surface_api_set_font_size(lua_State * l)456 int LuaContext::text_surface_api_set_font_size(lua_State* l) {
457
458 return state_boundary_handle(l, [&] {
459 TextSurface& text_surface = *check_text_surface(l, 1);
460 int font_size = LuaTools::check_int(l, 2);
461
462 text_surface.set_font_size(font_size);
463
464 return 0;
465 });
466 }
467
468 /**
469 * \brief Implementation of text_surface:get_text().
470 * \param l the Lua context that is calling this function
471 * \return number of values to return to Lua
472 */
text_surface_api_get_text(lua_State * l)473 int LuaContext::text_surface_api_get_text(lua_State* l) {
474
475 return state_boundary_handle(l, [&] {
476 const TextSurface& text_surface = *check_text_surface(l, 1);
477
478 const std::string& text = text_surface.get_text();
479
480 push_string(l, text);
481 return 1;
482 });
483 }
484
485 /**
486 * \brief Implementation of text_surface:set_text().
487 * \param l the Lua context that is calling this function
488 * \return number of values to return to Lua
489 */
text_surface_api_set_text(lua_State * l)490 int LuaContext::text_surface_api_set_text(lua_State* l) {
491
492 return state_boundary_handle(l, [&] {
493 TextSurface& text_surface = *check_text_surface(l, 1);
494 std::string text;
495 if (lua_gettop(l) >= 2 && !lua_isnil(l, 2)) {
496 text = LuaTools::check_string(l, 2);
497 }
498 text_surface.set_text(text);
499
500 return 0;
501 });
502 }
503
504 /**
505 * \brief Implementation of text_surface:set_text_key().
506 * \param l the Lua context that is calling this function
507 * \return number of values to return to Lua
508 */
text_surface_api_set_text_key(lua_State * l)509 int LuaContext::text_surface_api_set_text_key(lua_State* l) {
510
511 return state_boundary_handle(l, [&] {
512 TextSurface& text_surface = *check_text_surface(l, 1);
513 const std::string& key = LuaTools::check_string(l, 2);
514
515 if (!CurrentQuest::string_exists(key)) {
516 LuaTools::arg_error(l, 2, std::string("No value with key '") + key
517 + "' in strings.dat for language '"
518 + CurrentQuest::get_language() + "'"
519 );
520 }
521
522 text_surface.set_text(CurrentQuest::get_string(key));
523
524 return 0;
525 });
526 }
527
528 /**
529 * \brief Implementation of text_surface:get_size().
530 * \param l the Lua context that is calling this function
531 * \return number of values to return to Lua
532 */
text_surface_api_get_size(lua_State * l)533 int LuaContext::text_surface_api_get_size(lua_State* l) {
534
535 return state_boundary_handle(l, [&] {
536 const TextSurface& text_surface = *check_text_surface(l, 1);
537
538 lua_pushinteger(l, text_surface.get_width());
539 lua_pushinteger(l, text_surface.get_height());
540
541 return 2;
542 });
543 }
544
545 }
546
547