The Battle for Wesnoth  1.15.0-dev
text.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #pragma once
16 
17 #include "font/font_options.hpp"
18 #include "color.hpp"
19 #include "sdl/surface.hpp"
20 #include "sdl/texture.hpp"
22 
23 #include <pango/pango.h>
24 #include <pango/pangocairo.h>
25 
26 #include <functional>
27 #include <map>
28 #include <memory>
29 #include <string>
30 #include <vector>
31 
32 /**
33  * Note: This is the cairo-pango code path, not the SDL_TTF code path.
34  */
35 
36 struct language_def;
37 struct point;
38 
39 namespace font
40 {
41 // add background color and also font markup.
42 
43 /**
44  * Text class.
45  *
46  * This class represents text which is rendered using Pango.
47  *
48  * It takes text, as a utf-8 std::string, plus formatting options including
49  * font and color. It provides a surface object which holds the rendered text.
50  *
51  * Besides this, it can do some additional calculations using the font layout.
52  *
53  * It can take an index into the text, and convert it to pixel coordinates,
54  * so that if we want to draw a cursor in an editbox, we know where to draw it.
55  *
56  * It can also take a pixel coordinate with respect to the text layout, and
57  * translate it back to an index into the original text. This is useful if the
58  * user clicks on the text, and we want to know where to move the cursor.
59  *
60  * The get_token method takes a pixel coordinate, which we assume represents a
61  * click position, and gets the corresponding "token" from the string. The default
62  * token delimiters are whitespace " \n\r\t". So, this returns the "word" that the
63  * user clicked on.
64  *
65  * Finally, the get_link method represents special support for hyperlinks in text.
66  * A token "looks like a link" if it begins "http://" or "https://".
67  * If a text has link_aware enabled, then any such token is rendered with an
68  * underline and in a special color, see `link_color`.
69  * The get_link method calls get_token and further checks if the clicked token
70  * looks like a link.
71  *
72  * This class stores the text to draw and uses pango with the cairo backend to
73  * render the text. See http://pango.org for more info.
74  *
75  */
77 {
78 public:
79 
80  pango_text();
81 
82  pango_text(const pango_text &) = delete;
83  pango_text & operator = (const pango_text &) = delete;
84 
85  /**
86  * Returns the rendered text texture from the cache.
87  *
88  * If the surface is flagged dirty it will first be re-rendered and a new
89  * texture added to the cache upon redraw.
90  */
92 
93  /**
94  * Returns the rendered text surface directly.
95  *
96  * If the surface is flagged dirty it will first be re-rendered and a new
97  * texture added to the cache upon redraw.
98  */
100 
101  /** Returns the width needed for the text. */
102  int get_width() const;
103 
104  /** Returns the height needed for the text. */
105  int get_height() const;
106 
107  /** Returns the pixel size needed for the text. */
108  point get_size() const;
109 
110  /** Has the text been truncated? This happens if it exceeds max width or height. */
111  bool is_truncated() const;
112 
113  /**
114  * Inserts UTF-8 text.
115  *
116  * @param offset The position to insert the text.
117  * @param text The UTF-8 text to insert.
118  *
119  * @returns The number of characters inserted.
120  */
121  unsigned insert_text(const unsigned offset, const std::string& text);
122 
123  /**
124  * Inserts a unicode char.
125  *
126  * @param offset The position to insert the char.
127  * @param unicode The character to insert.
128  *
129  * @returns True upon success, false otherwise.
130  */
131  bool insert_unicode(const unsigned offset, char32_t unicode);
132 
133  /**
134  * Inserts unicode text.
135  *
136  * @param offset The position to insert the text.
137  * @param unicode Vector with characters to insert.
138  *
139  * @returns The number of characters inserted.
140  */
141  unsigned insert_unicode(
142  const unsigned offset, const std::u32string& unicode);
143 
144  /***** ***** ***** ***** Font flags ***** ***** ***** *****/
145 
146  // NOTE: these values must be powers of 2 in order to be bit-unique
147  enum FONT_STYLE {
152  };
153 
154  /***** ***** ***** ***** Query details ***** ***** ***** *****/
155 
156  /**
157  * Gets the location for the cursor.
158  *
159  * @param column The column offset of the cursor.
160  * @param line The line offset of the cursor.
161  *
162  * @returns The position of the top of the cursor. It the
163  * requested location is out of range 0,0 is
164  * returned.
165  */
167  const unsigned column, const unsigned line = 0) const;
168 
169  /**
170  * Get maximum length.
171  *
172  * @returns The maximum length of the text. The length of text
173  * should not exceed this value.
174  */
175  std::size_t get_maximum_length() const;
176 
177  /**
178  * Gets the largest collection of characters, including the token at position,
179  * and not including any characters from the delimiters set.
180  *
181  * @param position The pixel position in the text area.
182  *
183  * @returns The token containing position, and none of the
184  * delimiter characters. If position is out of bounds,
185  * it returns the empty string.
186  */
187  std::string get_token(const point & position, const char * delimiters = " \n\r\t") const;
188 
189  /**
190  * Checks if position points to a character in a link in the text, returns it
191  * if so, empty string otherwise. Link-awareness must be enabled to get results.
192  * @param position The pixel position in the text area.
193  *
194  * @returns The link if one is found, the empty string otherwise.
195  */
196  std::string get_link(const point & position) const;
197 
198  /**
199  * Gets the column of line of the character at the position.
200  *
201  * @param position The pixel position in the text area.
202  *
203  * @returns A point with the x value the column and the y
204  * value the line of the character found (or last
205  * character if not found.
206  */
207  point get_column_line(const point& position) const;
208 
209  /**
210  * Gets the length of the text in bytes.
211  *
212  * The text set is UTF-8 so the length of the string might not be the length
213  * of the text.
214  */
215  std::size_t get_length() const { return length_; }
216 
217  /**
218  * Sets the text to render.
219  *
220  * @param text The text to render.
221  * @param markedup Should the text be rendered with pango
222  * markup. If the markup is invalid it's
223  * rendered as text without markup.
224  *
225  * @returns The status, if rendered as markup and the
226  * markup contains errors, false is returned
227  * else true.
228  */
229  bool set_text(const std::string& text, const bool markedup);
230 
231  /***** ***** ***** ***** Setters / getters ***** ***** ***** *****/
232 
233  const std::string& text() const { return text_; }
234 
236 
237  pango_text& set_font_size(const unsigned font_size);
238 
239  pango_text& set_font_style(const FONT_STYLE font_style);
240 
241  pango_text& set_foreground_color(const color_t& color);
242 
243  pango_text& set_maximum_width(int width);
244 
245  pango_text& set_characters_per_line(const unsigned characters_per_line);
246 
247  pango_text& set_maximum_height(int height, bool multiline);
248 
249  pango_text& set_ellipse_mode(const PangoEllipsizeMode ellipse_mode);
250 
251  pango_text& set_alignment(const PangoAlignment alignment);
252 
253  pango_text& set_maximum_length(const std::size_t maximum_length);
254 
255  bool link_aware() const { return link_aware_; }
256 
257  pango_text& set_link_aware(bool b);
258 
259  pango_text& set_link_color(const color_t& color);
260 
261  pango_text& set_add_outline(bool do_add);
262 
263 private:
264 
265  /***** ***** ***** ***** Pango variables ***** ***** ***** *****/
266  std::unique_ptr<PangoContext, std::function<void(void*)>> context_;
267  std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
268  mutable PangoRectangle rect_;
269 
270  // Used if the text is too long to fit into a single Cairo surface.
271  std::vector<std::unique_ptr<PangoLayout, std::function<void(void*)>>> sublayouts_;
272 
273  /** The SDL surface to render upon used as a cache. */
274  mutable surface surface_;
275 
276  /** The text to draw (stored as UTF-8). */
277  std::string text_;
278 
279  /** Does the text contain pango markup? If different render routines must be used. */
281 
282  /** Are hyperlinks in the text marked-up, and will get_link return them. */
284 
285  /**
286  * The color to render links in.
287  *
288  * Links are formatted using pango &lt;span> as follows:
289  *
290  * &lt;span underline="single" color=" + link_color_ + ">
291  */
293 
294  /** The font family class used. */
296 
297  /** The font size to draw. */
298  unsigned font_size_;
299 
300  /** The style of the font, this is an orred mask of the font flags. */
302 
303  /** The foreground color. */
305 
306  /** Whether to add an outline effect. */
308 
309  /**
310  * The maximum width of the text.
311  *
312  * Values less or equal to 0 mean no maximum and are internally stored as
313  * -1, since that's the value pango uses for it.
314  *
315  * See @ref characters_per_line_.
316  */
318 
319  /**
320  * The number of characters per line.
321  *
322  * This can be used as an alternative of @ref maximum_width_. The user can
323  * select a number of characters on a line for wrapping. When the value is
324  * non-zero it determines the maximum width based on the average character
325  * width.
326  *
327  * If both @ref maximum_width_ and @ref characters_per_line_ are set the
328  * minimum of the two will be the maximum.
329  *
330  * @note Long lines are often harder to read, setting this value can
331  * automatically wrap on a number of characters regardless of the font
332  * size. Often 66 characters is considered the optimal value for a one
333  * column text.
334  */
336 
337  /**
338  * The maximum height of the text.
339  *
340  * Values less or equal to 0 mean no maximum and are internally stored as
341  * -1, since that's the value pango uses for it.
342  */
344 
345  /** The way too long text is shown depends on this mode. */
346  PangoEllipsizeMode ellipse_mode_;
347 
348  /** The alignment of the text. */
349  PangoAlignment alignment_;
350 
351  /** The maximum length of the text. */
352  std::size_t maximum_length_;
353 
354  /**
355  * The text has two dirty states:
356  * - The setting of the state and the size calculations.
357  * - The rendering of the surface.
358  */
359 
360  /** The dirty state of the calculations. */
361  mutable bool calculation_dirty_;
362 
363  /** Length of the text. */
364  mutable std::size_t length_;
365 
366  /**
367  * Recalculates the text layout.
368  *
369  * When the text is recalculated the surface is dirtied.
370  *
371  * @param force Recalculate even if not dirty?
372  */
373  void recalculate(const bool force = false) const;
374 
375  /** Calculates surface size. */
376  PangoRectangle calculate_size(PangoLayout& layout) const;
377 
378  /** The dirty state of the surface. */
379  mutable bool surface_dirty_;
380 
381  /**
382  * Renders the text.
383  *
384  * It will do a recalculation first so no need to call both.
385  *
386  * @param force Render even if not dirty? This parameter is
387  * also send to recalculate().
388  */
389  void rerender(const bool force = false);
390 
391  void render(PangoLayout& layout, const PangoRectangle& rect,
392  const std::size_t surface_buffer_offset, const unsigned stride);
393 
394  /**
395  * Buffer to store the image on.
396  *
397  * We use a cairo surface to draw on this buffer and then use the buffer as
398  * data source for the SDL_Surface. This means the buffer needs to be stored
399  * in the object, since SDL_Surface doesn't own its buffer.
400  */
401  mutable std::vector<uint8_t> surface_buffer_;
402 
403  /**
404  * Creates a new buffer.
405  *
406  * If needed frees the other surface and then creates a new buffer and
407  * initializes the entire buffer with values 0.
408  *
409  * NOTE even though we're clearly modifying function we don't change the
410  * state of the object. The const is needed so other functions can also be
411  * marked const (those also don't change the state of the object.
412  *
413  * @param size The required size of the buffer.
414  */
415  void create_surface_buffer(const std::size_t size) const;
416 
417  /**
418  * Sets the markup'ed text.
419  *
420  * It tries to set the text as markup. If the markup is invalid it will try
421  * a bit harder to recover from the errors and still set the markup.
422  *
423  * @param text The text to set as markup.
424  *
425  * @returns Whether the markup was set or an
426  * unrecoverable error occurred and the text is
427  * set as plain text with an error message.
428  */
429  bool set_markup(utils::string_view text, PangoLayout& layout);
430 
431  bool validate_markup(utils::string_view text, char** raw_text, std::string& semi_escaped) const;
432 
433  /** Splits the text to two Cairo surfaces.
434  *
435  * The implementation isn't recursive: the function only splits the text once.
436  * As a result, it only doubles the maximum surface height to 64,000 pixels
437  * or so.
438  * The reason for this is that a recursive implementation would be more complex
439  * and it's unnecessary for now, as the longest surface in the game
440  * (end credits) is only about 40,000 pixels high with the default_large widget
441  * definition.
442  * If we need even larger surfaces in the future, the implementation can be made
443  * recursive.
444  */
445  void split_surface();
446 
447  bool is_surface_split() const
448  {
449  return sublayouts_.size() > 0;
450  }
451 
452  static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
453 
454  std::vector<std::string> find_links(utils::string_view text) const;
455  void format_links(std::string& text, const std::vector<std::string>& links) const;
456 
457  /** Hash for the current settings (text, size, etc) configuration. */
458  std::size_t hash_;
459 
460  // Allow specialization of std::hash for pango_text
461  friend struct std::hash<pango_text>;
462 };
463 
464 /**
465  * Returns a reference to a static pango_text object.
466  *
467  * Since the class is essentially a render pipeline, there's no need for individual
468  * areas of the game to own their own renderers. Not to mention it isn't a trivial
469  * class; constructing one is likely to be expensive.
470  */
471 pango_text& get_text_renderer();
472 
473 using pango_text_cache_t = std::map<std::size_t, texture>;
474 
475 /**
476  * The text texture cache.
477  *
478  * Each time a specific bit of text is rendered, a corresponding texture is created and
479  * added to the cache. We don't store the surface since there isn't really any use for
480  * it. If we need texture size that can be easily queried.
481  *
482  * @todo Figure out how this can be optimized with a texture atlas. It should be possible
483  * to store smaller bits of text in the atlas and construct new textures from them.
484  */
486 
487 } // namespace font
488 
489 // Specialize std::hash for pango_text
490 namespace std
491 {
492 template<>
493 struct hash<font::pango_text>
494 {
495  std::size_t operator()(const font::pango_text& t) const;
496 };
497 
498 } // namespace std
surface & render_and_get_surface()
Returns the rendered text surface directly.
Definition: text.cpp:99
unsigned font_size_
The font size to draw.
Definition: text.hpp:298
void format_links(std::string &text, const std::vector< std::string > &links) const
Definition: text.cpp:820
family_class
Font classes for get_font_families().
std::size_t get_length() const
Gets the length of the text in bytes.
Definition: text.hpp:215
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:291
Collection of helper functions relating to Pango formatting.
int get_width() const
Returns the width needed for the text.
Definition: text.cpp:105
int maximum_height_
The maximum height of the text.
Definition: text.hpp:343
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:335
std::size_t length_
Length of the text.
Definition: text.hpp:364
pango_text & set_link_aware(bool b)
Definition: text.cpp:457
STL namespace.
bool link_aware() const
Definition: text.hpp:255
std::size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:352
void recalculate(const bool force=false) const
Recalculates the text layout.
Definition: text.cpp:489
std::size_t get_maximum_length() const
Get maximum length.
Definition: text.cpp:205
font::family_class font_class_
The font family class used.
Definition: text.hpp:295
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:352
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:893
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:419
pango_text & set_maximum_length(const std::size_t maximum_length)
Definition: text.cpp:444
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:401
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:26
bool is_surface_split() const
Definition: text.hpp:447
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:433
bool set_markup(utils::string_view text, PangoLayout &layout)
Sets the markup&#39;ed text.
Definition: text.cpp:768
#define b
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor.
Definition: text.cpp:160
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
std::string get_token(const point &position, const char *delimiters=" \\) const
Gets the largest collection of characters, including the token at position, and not including any cha...
Definition: text.cpp:210
point get_size() const
Returns the pixel size needed for the text.
Definition: text.cpp:115
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:267
texture & render_and_get_texture()
Returns the rendered text texture from the cache.
Definition: text.cpp:93
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:122
int maximum_width_
The maximum width of the text.
Definition: text.hpp:317
void rerender(const bool force=false)
Renders the text.
Definition: text.cpp:699
color_t foreground_color_
The foreground color.
Definition: text.hpp:304
static pango_text_cache_t rendered_text_cache
The text texture cache.
Definition: text.hpp:485
std::string get_link(const point &position) const
Checks if position points to a character in a link in the text, returns it if so, empty string otherw...
Definition: text.cpp:242
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:340
bool validate_markup(utils::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:827
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:346
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:388
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:280
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:501
bool surface_dirty_
The dirty state of the surface.
Definition: text.hpp:379
PangoRectangle rect_
Definition: text.hpp:268
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:361
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:129
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:329
SDL_Rect rect
The coordinates of this image on the spritesheet.
bool insert_unicode(const unsigned offset, char32_t unicode)
Inserts a unicode char.
Definition: text.cpp:149
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:266
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:283
void create_surface_buffer(const std::size_t size) const
Creates a new buffer.
Definition: text.cpp:759
void render(PangoLayout &layout, const PangoRectangle &rect, const std::size_t surface_buffer_offset, const unsigned stride)
Definition: text.cpp:626
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:257
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:301
bool add_outline_
Whether to add an outline effect.
Definition: text.hpp:307
Holds a 2D point.
Definition: point.hpp:23
std::size_t hash_
Hash for the current settings (text, size, etc) configuration.
Definition: text.hpp:458
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:467
int get_height() const
Returns the height needed for the text.
Definition: text.cpp:110
std::vector< std::string > find_links(utils::string_view text) const
Definition: text.cpp:795
const std::string & text() const
Definition: text.hpp:233
color_t link_color_
The color to render links in.
Definition: text.hpp:292
Text class.
Definition: text.hpp:76
pango_text & set_maximum_width(int width)
Definition: text.cpp:373
double t
Definition: astarsearch.cpp:63
std::map< std::size_t, texture > pango_text_cache_t
Definition: text.hpp:473
surface surface_
The SDL surface to render upon used as a cache.
Definition: text.hpp:274
std::vector< std::unique_ptr< PangoLayout, std::function< void(void *)> > > sublayouts_
Definition: text.hpp:271
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:400
void split_surface()
Splits the text to two Cairo surfaces.
Definition: text.cpp:865
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:277
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:363
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:349
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:478
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:886
pango_text & operator=(const pango_text &)=delete