The Battle for Wesnoth  1.17.12+dev
text.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2022
3  by Mark de Wever <koraq@xs4all.nl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #pragma once
17 
18 #include "font/font_options.hpp"
19 #include "color.hpp"
20 #include "sdl/surface.hpp"
21 #include "sdl/texture.hpp"
23 
24 #include <pango/pango.h>
25 #include <pango/pangocairo.h>
26 
27 #include <functional>
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 /** Flush the rendered text cache. */
42 void flush_texture_cache();
43 
44 // add background color and also font markup.
45 
46 /**
47  * Text class.
48  *
49  * This class represents text which is rendered using Pango.
50  *
51  * It takes text, as a utf-8 std::string, plus formatting options including
52  * font and color. It provides a surface object which holds the rendered text.
53  *
54  * Besides this, it can do some additional calculations using the font layout.
55  *
56  * It can take an index into the text, and convert it to pixel coordinates,
57  * so that if we want to draw a cursor in an editbox, we know where to draw it.
58  *
59  * It can also take a pixel coordinate with respect to the text layout, and
60  * translate it back to an index into the original text. This is useful if the
61  * user clicks on the text, and we want to know where to move the cursor.
62  *
63  * The get_token method takes a pixel coordinate, which we assume represents a
64  * click position, and gets the corresponding "token" from the string. The default
65  * token delimiters are whitespace " \n\r\t". So, this returns the "word" that the
66  * user clicked on.
67  *
68  * Finally, the get_link method represents special support for hyperlinks in text.
69  * A token "looks like a link" if it begins "http://" or "https://".
70  * If a text has link_aware enabled, then any such token is rendered with an
71  * underline and in a special color, see `link_color`.
72  * The get_link method calls get_token and further checks if the clicked token
73  * looks like a link.
74  *
75  * This class stores the text to draw and uses pango with the cairo backend to
76  * render the text. See http://pango.org for more info.
77  *
78  */
80 {
81 public:
82  pango_text();
83 
84  pango_text(const pango_text&) = delete;
85  pango_text& operator=(const pango_text&) = delete;
86 
87  /** Returns the cached texture, or creates a new one otherwise. */
89 
90  /**
91  * Returns the rendered text as a texture.
92  *
93  * texture::w() and texture::h() methods will return the expected
94  * width and height of the texture in draw space. This may differ
95  * from the real value returned by texture::get_info().
96  *
97  * In almost all cases, use w() and h() to get the size of the
98  * rendered text for drawing.
99  *
100  * This function is otherwise identical to render().
101  */
102  texture render_texture(const SDL_Rect& viewport);
103 
104  /**
105  * Returns the rendered text.
106  *
107  * @param viewport Only this area needs to be drawn - the returned
108  * surface's origin will correspond to viewport.x and viewport.y, the
109  * width and height will be at least viewport.w and viewport.h (although
110  * they may be larger).
111  */
112  surface render_surface(const SDL_Rect& viewport);
113 
114  /**
115  * Equivalent to render(viewport), where the viewport's top-left is at
116  * (0,0) and the area is large enough to contain the full text.
117  *
118  * The top-left of the viewport will be at (0,0), regardless of the values
119  * of x and y. If the x or y co-ordinates are non-zero, then x columns and
120  * y rows of blank space are included in the amount of memory allocated.
121  */
123 
124  /** Returns the size of the text, in drawing coordinates. */
125  point get_size();
126 
127  /** Has the text been truncated? This happens if it exceeds max width or height. */
128  bool is_truncated() const;
129 
130  /**
131  * Inserts UTF-8 text.
132  *
133  * @param offset The position to insert the text.
134  * @param text The UTF-8 text to insert.
135  *
136  * @returns The number of characters inserted.
137  */
138  unsigned insert_text(const unsigned offset, const std::string& text);
139 
140  /***** ***** ***** ***** Font flags ***** ***** ***** *****/
141 
142  // NOTE: these values must be powers of 2 in order to be bit-unique
143  enum FONT_STYLE {
148  };
149 
150  /***** ***** ***** ***** Query details ***** ***** ***** *****/
151 
152  /**
153  * Returns the maximum glyph height of a font, in drawing coordinates.
154  *
155  * @returns The height of the tallest possible glyph for the selected
156  * font. More specifically, the result is the sum of the maximum
157  * ascent and descent lengths.
158  */
159  int get_max_glyph_height() const;
160 
161  /**
162  * Gets the location for the cursor, in drawing coordinates.
163  *
164  * @param column The column offset of the cursor.
165  * @param line The line offset of the cursor.
166  *
167  * @returns The position of the top of the cursor. It the
168  * requested location is out of range 0,0 is
169  * returned.
170  */
172  const unsigned column, const unsigned line = 0) const;
173 
174  /**
175  * Get maximum length.
176  *
177  * @returns The maximum length of the text. The length of text
178  * should not exceed this value.
179  */
180  std::size_t get_maximum_length() const;
181 
182  /**
183  * Gets the largest collection of characters, including the token at position,
184  * and not including any characters from the delimiters set.
185  *
186  * @param position The pixel position in the text area.
187  * @param delimiters
188  *
189  * @returns The token containing position, and none of the
190  * delimiter characters. If position is out of bounds,
191  * it returns the empty string.
192  */
193  std::string get_token(const point & position, const char * delimiters = " \n\r\t") const;
194 
195  /**
196  * Checks if position points to a character in a link in the text, returns it
197  * if so, empty string otherwise. Link-awareness must be enabled to get results.
198  * @param position The pixel position in the text area.
199  *
200  * @returns The link if one is found, the empty string otherwise.
201  */
202  std::string get_link(const point & position) const;
203 
204  /**
205  * Gets the column of line of the character at the position.
206  *
207  * @param position The pixel position in the text area.
208  *
209  * @returns A point with the x value the column and the y
210  * value the line of the character found (or last
211  * character if not found.
212  */
213  point get_column_line(const point& position) const;
214 
215  /**
216  * Retrieves a list of strings with contents for each rendered line.
217  *
218  * This method is not const because it requires rendering the text.
219  *
220  * @note This is only intended for renderer implementation details. This
221  * is a rather expensive function because it copies everything at
222  * least once.
223  */
224  std::vector<std::string> get_lines() const;
225 
226  /**
227  * Gets the length of the text in bytes.
228  *
229  * The text set is UTF-8 so the length of the string might not be the length
230  * of the text.
231  */
232  std::size_t get_length() const { return length_; }
233 
234  /**
235  * Sets the text to render.
236  *
237  * @param text The text to render.
238  * @param markedup Should the text be rendered with pango
239  * markup. If the markup is invalid it's
240  * rendered as text without markup.
241  *
242  * @returns The status, if rendered as markup and the
243  * markup contains errors, false is returned
244  * else true.
245  */
246  bool set_text(const std::string& text, const bool markedup);
247 
248  /***** ***** ***** ***** Setters / getters ***** ***** ***** *****/
249 
250  const std::string& text() const { return text_; }
251 
253 
254  pango_text& set_font_size(unsigned font_size);
255 
256  pango_text& set_font_style(const FONT_STYLE font_style);
257 
258  pango_text& set_foreground_color(const color_t& color);
259 
260  pango_text& set_maximum_width(int width);
261 
262  pango_text& set_characters_per_line(const unsigned characters_per_line);
263 
264  pango_text& set_maximum_height(int height, bool multiline);
265 
266  pango_text& set_ellipse_mode(const PangoEllipsizeMode ellipse_mode);
267 
268  pango_text& set_alignment(const PangoAlignment alignment);
269 
270  pango_text& set_maximum_length(const std::size_t maximum_length);
271 
272  bool link_aware() const { return link_aware_; }
273 
274  pango_text& set_link_aware(bool b);
275 
276  pango_text& set_link_color(const color_t& color);
277 
278  pango_text& set_add_outline(bool do_add);
279 
280 private:
281 
282  /***** ***** ***** ***** Pango variables ***** ***** ***** *****/
283  std::unique_ptr<PangoContext, std::function<void(void*)>> context_;
284  std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
285  mutable PangoRectangle rect_;
286 
287  /** The text to draw (stored as UTF-8). */
288  std::string text_;
289 
290  /** Does the text contain pango markup? If different render routines must be used. */
292 
293  /** Are hyperlinks in the text marked-up, and will get_link return them. */
295 
296  /**
297  * The color to render links in.
298  *
299  * Links are formatted using pango &lt;span> as follows:
300  *
301  * &lt;span underline="single" color=" + link_color_ + ">
302  */
304 
305  /** The font family class used. */
307 
308  /** The font size to draw. */
309  unsigned font_size_;
310 
311  /** The style of the font, this is an orred mask of the font flags. */
313 
314  /** The foreground color. */
316 
317  /** Whether to add an outline effect. */
319 
320  /**
321  * The maximum width of the text.
322  *
323  * Values less or equal to 0 mean no maximum and are internally stored as
324  * -1, since that's the value pango uses for it.
325  *
326  * See @ref characters_per_line_.
327  */
329 
330  /**
331  * The number of characters per line.
332  *
333  * This can be used as an alternative of @ref maximum_width_. The user can
334  * select a number of characters on a line for wrapping. When the value is
335  * non-zero it determines the maximum width based on the average character
336  * width.
337  *
338  * If both @ref maximum_width_ and @ref characters_per_line_ are set the
339  * minimum of the two will be the maximum.
340  *
341  * @note Long lines are often harder to read, setting this value can
342  * automatically wrap on a number of characters regardless of the font
343  * size. Often 66 characters is considered the optimal value for a one
344  * column text.
345  */
347 
348  /**
349  * The maximum height of the text.
350  *
351  * Values less or equal to 0 mean no maximum and are internally stored as
352  * -1, since that's the value pango uses for it.
353  */
355 
356  /** The way too long text is shown depends on this mode. */
357  PangoEllipsizeMode ellipse_mode_;
358 
359  /** The alignment of the text. */
360  PangoAlignment alignment_;
361 
362  /** The maximum length of the text. */
363  std::size_t maximum_length_;
364 
365  /**
366  * The text has two dirty states:
367  * - The setting of the state and the size calculations.
368  * - The rendering of the surface.
369  */
370 
371  /** The dirty state of the calculations. */
372  mutable bool calculation_dirty_;
373 
374  /** Length of the text. */
375  mutable std::size_t length_;
376 
377  /** The pixel scale, used to render high-DPI text. */
379 
380  /** Recalculates the text layout. */
381  void recalculate() const;
382 
383  /** Calculates surface size. */
384  PangoRectangle calculate_size(PangoLayout& layout) const;
385 
386  /** Allow specialization of std::hash for pango_text. */
387  friend struct std::hash<pango_text>;
388 
389  /** Renders the text to a surface. */
391  surface create_surface(const SDL_Rect& viewport);
392 
393  void render(PangoLayout& layout, const SDL_Rect& viewport, const unsigned stride);
394 
395  /**
396  * Buffer to store the image on.
397  *
398  * We use a cairo surface to draw on this buffer and then use the buffer as
399  * data source for the SDL_Surface. This means the buffer needs to be stored
400  * in the object, since SDL_Surface doesn't own its buffer.
401  */
402  mutable std::vector<uint8_t> surface_buffer_;
403 
404  /**
405  * Sets the markup'ed text.
406  *
407  * It tries to set the text as markup. If the markup is invalid it will try
408  * a bit harder to recover from the errors and still set the markup.
409  *
410  * @param text The text to set as markup.
411  * @param layout
412  *
413  * @returns Whether the markup was set or an
414  * unrecoverable error occurred and the text is
415  * set as plain text with an error message.
416  */
417  bool set_markup(std::string_view text, PangoLayout& layout);
418 
419  bool validate_markup(std::string_view text, char** raw_text, std::string& semi_escaped) const;
420 
421  static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
422 
423  std::string format_links(std::string_view text) const;
424 
425  /**
426  * Adjust a texture's draw-width and height according to pixel scale.
427  *
428  * As fonts are rendered at output-scale, we need to do this just
429  * before returning the rendered texture. These attributes are stored
430  * as part of the returned texture object.
431  */
432  texture with_draw_scale(const texture& t) const;
433 
434  /** Scale the given render-space size to draw-space, rounding up. */
435  int to_draw_scale(int s) const;
436 
437  /** Scale the given render-space point to draw-space, rounding up. */
438  point to_draw_scale(const point& p) const;
439 
440  /** Update pixel scale, if necessary. */
441  void update_pixel_scale();
442 };
443 
444 /**
445  * Returns a reference to a static pango_text object.
446  *
447  * Since the class is essentially a render pipeline, there's no need for individual
448  * areas of the game to own their own renderers. Not to mention it isn't a trivial
449  * class; constructing one is likely to be expensive.
450  */
452 
453 /**
454  * Returns the maximum glyph height of a font, in pixels.
455  *
456  * @param size Desired font size in pixels.
457  * @param fclass Font family to use for measurement.
458  * @param style Font style to select the correct variant for measurement.
459  *
460  * @returns The height of the tallest possible glyph for the selected
461  * font. More specifically, the result is the sum of the maximum
462  * ascent and descent lengths.
463  */
465 
466 } // namespace font
467 
468 // Specialize std::hash for pango_text
469 namespace std
470 {
471 template<>
472 struct hash<font::pango_text>
473 {
474  std::size_t operator()(const font::pango_text&) const;
475 };
476 
477 } // namespace std
void recalculate() const
Recalculates the text layout.
Definition: text.cpp:575
surface render_surface()
Equivalent to render(viewport), where the viewport&#39;s top-left is at (0,0) and the area is large enoug...
Definition: text.cpp:143
unsigned font_size_
The font size to draw.
Definition: text.hpp:309
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:232
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:333
Collection of helper functions relating to Pango formatting.
bool set_markup(std::string_view text, PangoLayout &layout)
Sets the markup&#39;ed text.
Definition: text.cpp:829
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:116
texture with_draw_scale(const texture &t) const
Adjust a texture&#39;s draw-width and height according to pixel scale.
Definition: text.cpp:150
int maximum_height_
The maximum height of the text.
Definition: text.hpp:354
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:346
texture render_texture(const SDL_Rect &viewport)
Returns the rendered text as a texture.
Definition: text.cpp:111
std::size_t length_
Length of the text.
Definition: text.hpp:375
std::vector< std::string > get_lines() const
Retrieves a list of strings with contents for each rendered line.
Definition: text.cpp:942
pango_text & set_link_aware(bool b)
Definition: text.cpp:505
STL namespace.
bool link_aware() const
Definition: text.hpp:272
std::size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:363
std::size_t get_maximum_length() const
Get maximum length.
Definition: text.cpp:247
font::family_class font_class_
The font family class used.
Definition: text.hpp:306
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:391
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:168
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:968
int get_max_height(unsigned size, font::family_class fclass, pango_text::FONT_STYLE style)
Returns the maximum glyph height of a font, in pixels.
Definition: text.cpp:974
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:462
pango_text & set_maximum_length(const std::size_t maximum_length)
Definition: text.cpp:492
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:402
bool validate_markup(std::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:897
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
static void layout()
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:482
#define b
surface create_surface()
Renders the text to a surface.
Definition: text.cpp:779
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor, in drawing coordinates.
Definition: text.cpp:203
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
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:252
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:284
std::string format_links(std::string_view text) const
Replaces all instances of URLs in a given string with formatted links and returns the result...
Definition: text.cpp:859
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:176
int maximum_width_
The maximum width of the text.
Definition: text.hpp:328
color_t foreground_color_
The foreground color.
Definition: text.hpp:315
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:284
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:357
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:426
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:291
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:589
int pixel_scale_
The pixel scale, used to render high-DPI text.
Definition: text.hpp:378
PangoRectangle rect_
Definition: text.hpp:285
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:372
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:183
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:369
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:283
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:294
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:171
mock_party p
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:299
static map_location::DIRECTION s
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:379
void update_pixel_scale()
Update pixel scale, if necessary.
Definition: text.cpp:554
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:312
int get_max_glyph_height() const
Returns the maximum glyph height of a font, in drawing coordinates.
Definition: text.cpp:534
bool add_outline_
Whether to add an outline effect.
Definition: text.hpp:318
Holds a 2D point.
Definition: point.hpp:24
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:514
void flush_texture_cache()
Flush the rendered text cache.
Definition: text.cpp:65
const std::string & text() const
Definition: text.hpp:250
color_t link_color_
The color to render links in.
Definition: text.hpp:303
Text class.
Definition: text.hpp:79
pango_text & set_maximum_width(int width)
Definition: text.cpp:410
double t
Definition: astarsearch.cpp:65
int to_draw_scale(int s) const
Scale the given render-space size to draw-space, rounding up.
Definition: text.cpp:157
void render(PangoLayout &layout, const SDL_Rect &viewport, const unsigned stride)
Definition: text.cpp:728
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:437
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:288
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:401
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:360
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:524
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:935
pango_text & operator=(const pango_text &)=delete