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