The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
text.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2017 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"
22 
23 #include <pango/pango.h>
24 #include <pango/pangocairo.h>
25 
26 #include <functional>
27 #include <memory>
28 #include <string>
29 #include <vector>
30 
31 /***
32  * Note: This is the cairo-pango code path, not the SDL_TTF code path.
33  */
34 
35 struct language_def;
36 struct point;
37 
38 namespace font {
39 
40 // add background color and also font markup.
41 
42 /**
43  * Text class.
44  *
45  * This class represents text which is rendered using Pango.
46  *
47  * It takes text, as a utf-8 std::string, plus formatting options including
48  * font and color. It provides a surface object which holds the rendered text.
49  *
50  * Besides this, it can do some additional calculations using the font layout.
51  *
52  * It can take an index into the text, and convert it to pixel coordinates,
53  * so that if we want to draw a cursor in an editbox, we know where to draw it.
54  *
55  * It can also take a pixel coordinate with respect to the text layout, and
56  * translate it back to an index into the original text. This is useful if the
57  * user clicks on the text, and we want to know where to move the cursor.
58  *
59  * The get_token method takes a pixel coordinate, which we assume represents a
60  * click position, and gets the corresponding "token" from the string. The default
61  * token delimiters are whitespace " \n\r\t". So, this returns the "word" that the
62  * user clicked on.
63  *
64  * Finally, the get_link method represents special support for hyperlinks in text.
65  * A token "looks like a link" if it begins "http://" or "https://".
66  * If a text has link_aware enabled, then any such token is rendered with an
67  * underline and in a special color, see `link_color`.
68  * The get_link method calls get_token and further checks if the clicked token
69  * looks like a link.
70  *
71  * This class stores the text to draw and uses pango with the cairo backend to
72  * render the text. See http://pango.org for more info.
73  *
74  */
76 {
77 public:
78 
79  pango_text();
80 
81  pango_text(const pango_text &) = delete;
82  pango_text & operator = (const pango_text &) = delete;
83 
84  /**
85  * Returns the rendered text.
86  *
87  * Before rendering it tests whether a redraw is needed and if so it first
88  * redraws the surface before returning it.
89  */
90  surface& render();
91 
92  /** Returns the width needed for the text. */
93  int get_width() const;
94 
95  /** Returns the height needed for the text. */
96  int get_height() const;
97 
98  /** Returns the pixel size needed for the text. */
99  point get_size() const;
100 
101  /** Has the text been truncated? This happens if it exceeds max width or height. */
102  bool is_truncated() const;
103 
104  /**
105  * Inserts UTF-8 text.
106  *
107  * @param offset The position to insert the text.
108  * @param text The UTF-8 text to insert.
109  *
110  * @returns The number of characters inserted.
111  */
112  unsigned insert_text(const unsigned offset, const std::string& text);
113 
114  /**
115  * Inserts a unicode char.
116  *
117  * @param offset The position to insert the char.
118  * @param unicode The character to insert.
119  *
120  * @returns True upon success, false otherwise.
121  */
122  bool insert_unicode(const unsigned offset, ucs4::char_t unicode);
123 
124  /**
125  * Inserts unicode text.
126  *
127  * @param offset The position to insert the text.
128  * @param unicode Vector with characters to insert.
129  *
130  * @returns The number of characters inserted.
131  */
132  unsigned insert_unicode(
133  const unsigned offset, const ucs4::string& unicode);
134 
135  /***** ***** ***** ***** Font flags ***** ***** ***** *****/
136 
137  // NOTE: these values must be powers of 2 in order to be bit-unique
138  enum FONT_STYLE {
144  };
145 
146  /***** ***** ***** ***** Query details ***** ***** ***** *****/
147 
148  /**
149  * Gets the location for the cursor.
150  *
151  * @param column The column offset of the cursor.
152  * @param line The line offset of the cursor.
153  *
154  * @returns The position of the top of the cursor. It the
155  * requested location is out of range 0,0 is
156  * returned.
157  */
159  const unsigned column, const unsigned line = 0) const;
160 
161  /**
162  * Gets the largest collection of characters, including the token at position,
163  * and not including any characters from the delimiters set.
164  *
165  * @param position The pixel position in the text area.
166  *
167  * @returns The token containing position, and none of the
168  * delimiter characters. If position is out of bounds,
169  * it returns the empty string.
170  */
171  std::string get_token(const point & position, const char * delimiters = " \n\r\t") const;
172 
173  /**
174  * Checks if position points to a character in a link in the text, returns it
175  * if so, empty string otherwise. Link-awareness must be enabled to get results.
176  * @param position The pixel position in the text area.
177  *
178  * @returns The link if one is found, the empty string otherwise.
179  */
180  std::string get_link(const point & position) const;
181 
182  /**
183  * Gets the column of line of the character at the position.
184  *
185  * @param position The pixel position in the text area.
186  *
187  * @returns A point with the x value the column and the y
188  * value the line of the character found (or last
189  * character if not found.
190  */
191  point get_column_line(const point& position) const;
192 
193  /**
194  * Gets the length of the text in bytes.
195  *
196  * The text set is UTF-8 so the length of the string might not be the length
197  * of the text.
198  */
199  size_t get_length() const { return length_; }
200 
201  /**
202  * Sets the text to render.
203  *
204  * @param text The text to render.
205  * @param markedup Should the text be rendered with pango
206  * markup. If the markup is invalid it's
207  * rendered as text without markup.
208  *
209  * @returns The status, if rendered as markup and the
210  * markup contains errors, false is returned
211  * else true.
212  */
213  bool set_text(const std::string& text, const bool markedup);
214 
215  /***** ***** ***** ***** Setters / getters ***** ***** ***** *****/
216 
217  const std::string& text() const { return text_; }
218 
220 
221  pango_text& set_font_size(const unsigned font_size);
222 
223  pango_text& set_font_style(const FONT_STYLE font_style);
224 
225  pango_text& set_foreground_color(const color_t& color);
226 
227  pango_text& set_maximum_width(int width);
228 
229  pango_text& set_characters_per_line(const unsigned characters_per_line);
230 
231  pango_text& set_maximum_height(int height, bool multiline);
232 
233  pango_text& set_ellipse_mode(const PangoEllipsizeMode ellipse_mode);
234 
235  pango_text& set_alignment(const PangoAlignment alignment);
236 
237  pango_text& set_maximum_length(const size_t maximum_length);
238 
239  bool link_aware() const { return link_aware_; }
240 
241  pango_text& set_link_aware(bool b);
242 
243  pango_text& set_link_color(const color_t& color);
244 private:
245 
246  /***** ***** ***** ***** Pango variables ***** ***** ***** *****/
247  std::unique_ptr<PangoContext, std::function<void(void*)>> context_;
248  std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
249  mutable PangoRectangle rect_;
250 
251  // Used if the text is too long to fit into a single Cairo surface.
252  std::vector<std::unique_ptr<PangoLayout, std::function<void(void*)>>> sublayouts_;
253 
254  /** The SDL surface to render upon used as a cache. */
255  mutable surface surface_;
256 
257 
258  /** The text to draw (stored as UTF-8). */
260 
261  /** Does the text contain pango markup? If different render routines must be used. */
263 
264  /** Are hyperlinks in the text marked-up, and will get_link return them. */
266 
267  /**
268  * The color to render links in.
269  *
270  * Links are formatted using pango &lt;span> as follows:
271  *
272  * &lt;span underline="single" color=" + link_color_ + ">
273  */
275 
276  /** The font family class used. */
278 
279  /** The font size to draw. */
280  unsigned font_size_;
281 
282  /** The style of the font, this is an orred mask of the font flags. */
284 
285  /** The foreground color. */
287 
288  /**
289  * The maximum width of the text.
290  *
291  * Values less or equal to 0 mean no maximum and are internally stored as
292  * -1, since that's the value pango uses for it.
293  *
294  * See @ref characters_per_line_.
295  */
297 
298  /**
299  * The number of characters per line.
300  *
301  * This can be used as an alternative of @ref maximum_width_. The user can
302  * select a number of characters on a line for wrapping. When the value is
303  * non-zero it determines the maximum width based on the average character
304  * width.
305  *
306  * If both @ref maximum_width_ and @ref characters_per_line_ are set the
307  * minimum of the two will be the maximum.
308  *
309  * @note Long lines are often harder to read, setting this value can
310  * automatically wrap on a number of characters regardless of the font
311  * size. Often 66 characters is considered the optimal value for a one
312  * column text.
313  */
315 
316  /**
317  * The maximum height of the text.
318  *
319  * Values less or equal to 0 mean no maximum and are internally stored as
320  * -1, since that's the value pango uses for it.
321  */
323 
324  /** The way too long text is shown depends on this mode. */
325  PangoEllipsizeMode ellipse_mode_;
326 
327  /** The alignment of the text. */
328  PangoAlignment alignment_;
329 
330  /** The maximum length of the text. */
332 
333  /**
334  * The text has two dirty states:
335  * - The setting of the state and the size calculations.
336  * - The rendering of the surface.
337  */
338 
339  /** The dirty state of the calculations. */
340  mutable bool calculation_dirty_;
341 
342  /** Length of the text. */
343  mutable size_t length_;
344 
345  /**
346  * Recalculates the text layout.
347  *
348  * When the text is recalculated the surface is dirtied.
349  *
350  * @param force Recalculate even if not dirty?
351  */
352  void recalculate(const bool force = false) const;
353 
354  /** Calculates surface size. */
355  PangoRectangle calculate_size(PangoLayout& layout) const;
356 
357  /** The dirty state of the surface. */
358  mutable bool surface_dirty_;
359 
360  /**
361  * Renders the text.
362  *
363  * It will do a recalculation first so no need to call both.
364  *
365  * @param force Render even if not dirty? This parameter is
366  * also send to recalculate().
367  */
368  void rerender(const bool force = false);
369 
370  void render(PangoLayout& layout, const PangoRectangle& rect,
371  const size_t surface_buffer_offset, const unsigned stride);
372 
373  /**
374  * Buffer to store the image on.
375  *
376  * We use a cairo surface to draw on this buffer and then use the buffer as
377  * data source for the SDL_Surface. This means the buffer needs to be stored
378  * in the object, since SDL_Surface doesn't own its buffer.
379  */
380  mutable std::vector<uint8_t> surface_buffer_;
381 
382  /**
383  * Creates a new buffer.
384  *
385  * If needed frees the other surface and then creates a new buffer and
386  * initializes the entire buffer with values 0.
387  *
388  * NOTE eventhough we're clearly modifying function we don't change the
389  * state of the object. The const is needed so other functions can also be
390  * marked const (those also don't change the state of the object.
391  *
392  * @param size The required size of the buffer.
393  */
394  void create_surface_buffer(const size_t size) const;
395 
396  /**
397  * Sets the markup'ed text.
398  *
399  * It tries to set the text as markup. If the markup is invalid it will try
400  * a bit harder to recover from the errors and still set the markup.
401  *
402  * @param text The text to set as markup.
403  *
404  * @returns Whether the markup was set or an
405  * unrecoverable error occurred and the text is
406  * set as plain text with an error message.
407  */
408  bool set_markup(utils::string_view text, PangoLayout& layout);
409 
410  bool validate_markup(utils::string_view text, char** raw_text, std::string& semi_escaped) const;
411 
412  /** Splits the text to two Cairo surfaces.
413  *
414  * The implementation isn't recursive: the function only splits the text once.
415  * As a result, it only doubles the maximum surface height to 64,000 pixels
416  * or so.
417  * The reason for this is that a recursive implementation would be more complex
418  * and it's unnecessary for now, as the longest surface in the game
419  * (end credits) is only about 40,000 pixels high with the default_large widget
420  * definition.
421  * If we need even larger surfaces in the future, the implementation can be made
422  * recursive.
423  */
424  void split_surface();
425 
426  bool is_surface_split() const
427  {
428  return sublayouts_.size() > 0;
429  }
430 
431  static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
432 
433  std::vector<std::string> find_links(utils::string_view text) const;
434  void format_links(std::string& text, const std::vector<std::string>& links) const;
435 };
436 
437 } // namespace font
bool link_aware() const
Definition: text.hpp:239
std::vector< char_t > string
unsigned font_size_
The font size to draw.
Definition: text.hpp:280
family_class
Font classes for get_font_families().
point get_size() const
Returns the pixel size needed for the text.
Definition: text.cpp:112
bool insert_unicode(const unsigned offset, ucs4::char_t unicode)
Inserts a unicode char.
Definition: text.cpp:146
std::string get_token(const point &position, const char *delimiters=" \n\r\t") const
Gets the largest collection of characters, including the token at position, and not including any cha...
Definition: text.cpp:202
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:283
const std::string & text() const
Definition: text.hpp:217
Note: Specific to sdl_ttf.
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:249
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:234
int maximum_height_
The maximum height of the text.
Definition: text.hpp:322
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:314
pango_text & set_maximum_length(const size_t maximum_length)
Definition: text.cpp:454
pango_text & set_link_aware(bool b)
Definition: text.cpp:467
int get_width() const
Returns the width needed for the text.
Definition: text.cpp:102
std::vector< std::string > find_links(utils::string_view text) const
Definition: text.cpp:766
font::family_class font_class_
The font family class used.
Definition: text.hpp:277
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:344
uint32_t char_t
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:429
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:380
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:443
bool set_markup(utils::string_view text, PangoLayout &layout)
Sets the markup'ed text.
Definition: text.cpp:739
#define b
int get_height() const
Returns the height needed for the text.
Definition: text.cpp:107
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:248
void recalculate(const bool force=false) const
Recalculates the text layout.
Definition: text.cpp:489
int maximum_width_
The maximum width of the text.
Definition: text.hpp:296
void rerender(const bool force=false)
Renders the text.
Definition: text.cpp:684
color_t foreground_color_
The foreground color.
Definition: text.hpp:286
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:332
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:325
bool validate_markup(utils::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:798
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:398
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:262
size_t get_length() const
Gets the length of the text in bytes.
Definition: text.hpp:199
bool surface_dirty_
The dirty state of the surface.
Definition: text.hpp:358
PangoRectangle rect_
Definition: text.hpp:249
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:340
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:126
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:321
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:247
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:265
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:119
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:501
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:283
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor.
Definition: text.cpp:157
size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:331
Holds a 2D point.
Definition: point.hpp:22
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:477
void create_surface_buffer(const size_t size) const
Creates a new buffer.
Definition: text.cpp:730
color_t link_color_
The color to render links in.
Definition: text.hpp:274
Text class.
Definition: text.hpp:75
pango_text & set_maximum_width(int width)
Definition: text.cpp:365
void format_links(std::string &text, const std::vector< std::string > &links) const
Definition: text.cpp:791
bool is_surface_split() const
Definition: text.hpp:426
size_t length_
Length of the text.
Definition: text.hpp:343
surface surface_
The SDL surface to render upon used as a cache.
Definition: text.hpp:255
std::vector< std::unique_ptr< PangoLayout, std::function< void(void *)> > > sublayouts_
Definition: text.hpp:252
surface & render()
Returns the rendered text.
Definition: text.cpp:95
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:410
void split_surface()
Splits the text to two Cairo surfaces.
Definition: text.cpp:836
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:259
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:355
const int font_size
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:328
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:857
pango_text & operator=(const pango_text &)=delete