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