The Battle for Wesnoth  1.17.21+dev
text.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2023
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  /**
88  * Returns the cached texture, or creates a new one otherwise.
89  *
90  * texture::w() and texture::h() methods will return the expected
91  * width and height of the texture in draw space. This may differ
92  * from the real value returned by texture::get_info().
93  *
94  * In almost all cases, use w() and h() to get the size of the
95  * rendered text for drawing.
96  */
98 
99 private:
100  /**
101  * Wrapper around render_surface which sets texture::w() and texture::h()
102  * in the same way that render_and_get_texture does.
103  *
104  * The viewport rect is interpreted at the scale of render-space, not
105  * drawing-space. This function has only been made private to preserve
106  * the drawing-space encapsulation.
107  */
108  texture render_texture(const SDL_Rect& viewport);
109 
110  /**
111  * Returns the rendered text.
112  *
113  * The viewport rect is interpreted at the scale of render-space, not
114  * drawing-space. This function has only been made private to preserve
115  * the drawing-space encapsulation.
116  *
117  * @param viewport Only this area needs to be drawn - the returned
118  * surface's origin will correspond to viewport.x and viewport.y, the
119  * width and height will be at least viewport.w and viewport.h (although
120  * they may be larger).
121  */
122  surface render_surface(const SDL_Rect& viewport);
123 
124 public:
125  /** Returns the size of the text, in drawing coordinates. */
126  point get_size();
127 
128  /** Has the text been truncated? This happens if it exceeds max width or height. */
129  bool is_truncated() const;
130 
131  /**
132  * Inserts UTF-8 text.
133  *
134  * @param offset The position to insert the text.
135  * @param text The UTF-8 text to insert.
136  *
137  * @returns The number of characters inserted.
138  */
139  unsigned insert_text(const unsigned offset, const std::string& text);
140 
141  /***** ***** ***** ***** Font flags ***** ***** ***** *****/
142 
143  // NOTE: these values must be powers of 2 in order to be bit-unique
144  enum FONT_STYLE {
149  };
150 
151  /***** ***** ***** ***** Query details ***** ***** ***** *****/
152 
153  /**
154  * Returns the maximum glyph height of a font, in drawing coordinates.
155  *
156  * @returns The height of the tallest possible glyph for the selected
157  * font. More specifically, the result is the sum of the maximum
158  * ascent and descent lengths.
159  */
160  int get_max_glyph_height() const;
161 
162  /**
163  * Gets the location for the cursor, in drawing coordinates.
164  *
165  * @param column The column offset of the cursor.
166  * @param line The line offset of the cursor.
167  *
168  * @returns The position of the top of the cursor. It the
169  * requested location is out of range 0,0 is
170  * returned.
171  */
173  const unsigned column, const unsigned line = 0) const;
174 
175  /**
176  * Get maximum length.
177  *
178  * @returns The maximum length of the text. The length of text
179  * should not exceed this value.
180  */
181  std::size_t get_maximum_length() const;
182 
183  /**
184  * Gets the largest collection of characters, including the token at position,
185  * and not including any characters from the delimiters set.
186  *
187  * @param position The pixel position in the text area.
188  * @param delimiters
189  *
190  * @returns The token containing position, and none of the
191  * delimiter characters. If position is out of bounds,
192  * it returns the empty string.
193  */
194  std::string get_token(const point & position, const char * delimiters = " \n\r\t") const;
195 
196  /**
197  * Checks if position points to a character in a link in the text, returns it
198  * if so, empty string otherwise. Link-awareness must be enabled to get results.
199  * @param position The pixel position in the text area.
200  *
201  * @returns The link if one is found, the empty string otherwise.
202  */
203  std::string get_link(const point & position) const;
204 
205  /**
206  * Gets the column of line of the character at the position.
207  *
208  * @param position The pixel position in the text area.
209  *
210  * @returns A point with the x value the column and the y
211  * value the line of the character found (or last
212  * character if not found.
213  */
214  point get_column_line(const point& position) const;
215 
216  /**
217  * Retrieves a list of strings with contents for each rendered line.
218  *
219  * This method is not const because it requires rendering the text.
220  *
221  * @note This is only intended for renderer implementation details. This
222  * is a rather expensive function because it copies everything at
223  * least once.
224  */
225  std::vector<std::string> get_lines() const;
226 
227  /**
228  * Gets the length of the text in bytes.
229  *
230  * The text set is UTF-8 so the length of the string might not be the length
231  * of the text.
232  */
233  std::size_t get_length() const { return length_; }
234 
235  /**
236  * Sets the text to render.
237  *
238  * @param text The text to render.
239  * @param markedup Should the text be rendered with pango
240  * markup. If the markup is invalid it's
241  * rendered as text without markup.
242  *
243  * @returns The status, if rendered as markup and the
244  * markup contains errors, false is returned
245  * else true.
246  */
247  bool set_text(const std::string& text, const bool markedup);
248 
249  /***** ***** ***** ***** Setters / getters ***** ***** ***** *****/
250 
251  const std::string& text() const { return text_; }
252 
254 
255  pango_text& set_font_size(unsigned font_size);
256 
257  pango_text& set_font_style(const FONT_STYLE font_style);
258 
259  pango_text& set_foreground_color(const color_t& color);
260 
261  pango_text& set_maximum_width(int width);
262 
263  pango_text& set_characters_per_line(const unsigned characters_per_line);
264 
265  pango_text& set_maximum_height(int height, bool multiline);
266 
267  pango_text& set_ellipse_mode(const PangoEllipsizeMode ellipse_mode);
268 
269  pango_text& set_alignment(const PangoAlignment alignment);
270 
271  pango_text& set_maximum_length(const std::size_t maximum_length);
272 
273  bool link_aware() const { return link_aware_; }
274 
275  pango_text& set_link_aware(bool b);
276 
277  pango_text& set_link_color(const color_t& color);
278 
279  pango_text& set_add_outline(bool do_add);
280 
281 private:
282 
283  /***** ***** ***** ***** Pango variables ***** ***** ***** *****/
284  std::unique_ptr<PangoContext, std::function<void(void*)>> context_;
285  std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
286  mutable PangoRectangle rect_;
287 
288  /** The text to draw (stored as UTF-8). */
289  std::string text_;
290 
291  /** Does the text contain pango markup? If different render routines must be used. */
293 
294  /** Are hyperlinks in the text marked-up, and will get_link return them. */
296 
297  /**
298  * The color to render links in.
299  *
300  * Links are formatted using pango &lt;span> as follows:
301  *
302  * &lt;span underline="single" color=" + link_color_ + ">
303  */
305 
306  /** The font family class used. */
308 
309  /** The font size to draw. */
310  unsigned font_size_;
311 
312  /** The style of the font, this is an orred mask of the font flags. */
314 
315  /** The foreground color. */
317 
318  /** Whether to add an outline effect. */
320 
321  /**
322  * The maximum width of the text.
323  *
324  * Values less or equal to 0 mean no maximum and are internally stored as
325  * -1, since that's the value pango uses for it.
326  *
327  * See @ref characters_per_line_.
328  */
330 
331  /**
332  * The number of characters per line.
333  *
334  * This can be used as an alternative of @ref maximum_width_. The user can
335  * select a number of characters on a line for wrapping. When the value is
336  * non-zero it determines the maximum width based on the average character
337  * width.
338  *
339  * If both @ref maximum_width_ and @ref characters_per_line_ are set the
340  * minimum of the two will be the maximum.
341  *
342  * @note Long lines are often harder to read, setting this value can
343  * automatically wrap on a number of characters regardless of the font
344  * size. Often 66 characters is considered the optimal value for a one
345  * column text.
346  */
348 
349  /**
350  * The maximum height of the text.
351  *
352  * Values less or equal to 0 mean no maximum and are internally stored as
353  * -1, since that's the value pango uses for it.
354  */
356 
357  /** The way too long text is shown depends on this mode. */
358  PangoEllipsizeMode ellipse_mode_;
359 
360  /** The alignment of the text. */
361  PangoAlignment alignment_;
362 
363  /** The maximum length of the text. */
364  std::size_t maximum_length_;
365 
366  /**
367  * The text has two dirty states:
368  * - The setting of the state and the size calculations.
369  * - The rendering of the surface.
370  */
371 
372  /** The dirty state of the calculations. */
373  mutable bool calculation_dirty_;
374 
375  /** Length of the text. */
376  mutable std::size_t length_;
377 
378  /** The pixel scale, used to render high-DPI text. */
380 
381  /** Recalculates the text layout. */
382  void recalculate() const;
383 
384  /** Calculates surface size. */
385  PangoRectangle calculate_size(PangoLayout& layout) const;
386 
387  /** Allow specialization of std::hash for pango_text. */
388  friend struct std::hash<pango_text>;
389 
390  /**
391  * Equivalent to create_surface(viewport), where the viewport's top-left is
392  * at (0,0) and the area is large enough to contain the full text.
393  *
394  * The top-left of the viewport will be at (0,0), regardless of the values
395  * of x and y in the rect_ member variable. If the x or y co-ordinates are
396  * non-zero, then x columns and y rows of blank space are included in the
397  * amount of memory allocated.
398  */
400 
401  /**
402  * Renders the text to a surface that uses surface_buffer_ as its data store,
403  * the buffer will be allocated or reallocated as necessary.
404  *
405  * The surface's origin will correspond to viewport.x and viewport.y, the
406  * width and height will be at least viewport.w and viewport.h (although
407  * they may be larger).
408  *
409  * @param viewport The area to draw, which can be a subset of the text. This
410  * rectangle's coordinates use render-space's scale.
411  */
412  surface create_surface(const SDL_Rect& viewport);
413 
414  /**
415  * This is part of create_surface(viewport). The separation is a legacy
416  * from workarounds to the size limits of cairo_surface_t.
417  */
418  void render(PangoLayout& layout, const SDL_Rect& viewport, const unsigned stride);
419 
420  /**
421  * Buffer to store the image on.
422  *
423  * We use a cairo surface to draw on this buffer and then use the buffer as
424  * data source for the SDL_Surface. This means the buffer needs to be stored
425  * in the object, since SDL_Surface doesn't own its buffer.
426  */
427  mutable std::vector<uint8_t> surface_buffer_;
428 
429  /**
430  * Sets the markup'ed text.
431  *
432  * It tries to set the text as markup. If the markup is invalid it will try
433  * a bit harder to recover from the errors and still set the markup.
434  *
435  * @param text The text to set as markup.
436  * @param layout
437  *
438  * @returns Whether the markup was set or an
439  * unrecoverable error occurred and the text is
440  * set as plain text with an error message.
441  */
442  bool set_markup(std::string_view text, PangoLayout& layout);
443 
444  bool validate_markup(std::string_view text, char** raw_text, std::string& semi_escaped) const;
445 
446  static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
447 
448  std::string format_links(std::string_view text) const;
449 
450  /**
451  * Adjust a texture's draw-width and height according to pixel scale.
452  *
453  * As fonts are rendered at output-scale, we need to do this just
454  * before returning the rendered texture. These attributes are stored
455  * as part of the returned texture object.
456  */
457  texture with_draw_scale(const texture& t) const;
458 
459  /** Scale the given render-space size to draw-space, rounding up. */
460  int to_draw_scale(int s) const;
461 
462  /** Scale the given render-space point to draw-space, rounding up. */
463  point to_draw_scale(const point& p) const;
464 
465  /** Update pixel scale, if necessary. */
466  void update_pixel_scale();
467 };
468 
469 /**
470  * Returns a reference to a static pango_text object.
471  *
472  * Since the class is essentially a render pipeline, there's no need for individual
473  * areas of the game to own their own renderers. Not to mention it isn't a trivial
474  * class; constructing one is likely to be expensive.
475  */
477 
478 /**
479  * Returns the maximum glyph height of a font, in pixels.
480  *
481  * @param size Desired font size in pixels.
482  * @param fclass Font family to use for measurement.
483  * @param style Font style to select the correct variant for measurement.
484  *
485  * @returns The height of the tallest possible glyph for the selected
486  * font. More specifically, the result is the sum of the maximum
487  * ascent and descent lengths.
488  */
490 
491 } // namespace font
492 
493 // Specialize std::hash for pango_text
494 namespace std
495 {
496 template<>
497 struct hash<font::pango_text>
498 {
499  std::size_t operator()(const font::pango_text&) const;
500 };
501 
502 } // namespace std
double t
Definition: astarsearch.cpp:65
Text class.
Definition: text.hpp:80
int pixel_scale_
The pixel scale, used to render high-DPI text.
Definition: text.hpp:379
pango_text & operator=(const pango_text &)=delete
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:384
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:358
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:928
bool add_outline_
Whether to add an outline effect.
Definition: text.hpp:319
bool validate_markup(std::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:890
bool set_markup(std::string_view text, PangoLayout &layout)
Sets the markup'ed text.
Definition: text.cpp:822
std::size_t get_length() const
Gets the length of the text in bytes.
Definition: text.hpp:233
pango_text & set_maximum_length(const std::size_t maximum_length)
Definition: text.cpp:485
void render(PangoLayout &layout, const SDL_Rect &viewport, const unsigned stride)
This is part of create_surface(viewport).
Definition: text.cpp:721
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:176
surface create_surface()
Equivalent to create_surface(viewport), where the viewport's top-left is at (0,0) and the area is lar...
Definition: text.cpp:772
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:361
PangoRectangle rect_
Definition: text.hpp:286
int maximum_height_
The maximum height of the text.
Definition: text.hpp:355
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:161
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:419
color_t link_color_
The color to render links in.
Definition: text.hpp:304
void recalculate() const
Recalculates the text layout.
Definition: text.cpp:568
color_t foreground_color_
The foreground color.
Definition: text.hpp:316
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:292
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:284
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:295
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:394
int to_draw_scale(int s) const
Scale the given render-space size to draw-space, rounding up.
Definition: text.cpp:150
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:852
pango_text(const pango_text &)=delete
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:347
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:292
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:362
font::family_class font_class_
The font family class used.
Definition: text.hpp:307
std::vector< std::string > get_lines() const
Retrieves a list of strings with contents for each rendered line.
Definition: text.cpp:935
void update_pixel_scale()
Update pixel scale, if necessary.
Definition: text.cpp:547
bool link_aware() const
Definition: text.hpp:273
unsigned font_size_
The font size to draw.
Definition: text.hpp:310
texture render_texture(const SDL_Rect &viewport)
Wrapper around render_surface which sets texture::w() and texture::h() in the same way that render_an...
Definition: text.cpp:111
surface render_surface(const SDL_Rect &viewport)
Returns the rendered text.
Definition: text.cpp:136
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:427
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:517
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:455
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:582
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:475
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:289
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor, in drawing coordinates.
Definition: text.cpp:196
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:373
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:285
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:372
pango_text & set_link_aware(bool b)
Definition: text.cpp:498
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:313
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:245
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:326
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:169
texture with_draw_scale(const texture &t) const
Adjust a texture's draw-width and height according to pixel scale.
Definition: text.cpp:143
std::size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:364
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:430
pango_text & set_maximum_width(int width)
Definition: text.cpp:403
std::size_t get_maximum_length() const
Get maximum length.
Definition: text.cpp:240
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:116
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:507
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:277
const std::string & text() const
Definition: text.hpp:251
std::size_t length_
Length of the text.
Definition: text.hpp:376
int get_max_glyph_height() const
Returns the maximum glyph height of a font, in drawing coordinates.
Definition: text.cpp:527
int maximum_width_
The maximum width of the text.
Definition: text.hpp:329
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
static void layout()
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:171
Collection of helper functions relating to Pango formatting.
family_class
Font classes for get_font_families().
@ FONT_SANS_SERIF
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:967
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:961
void flush_texture_cache()
Flush the rendered text cache.
Definition: text.cpp:65
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
Holds a 2D point.
Definition: point.hpp:25
mock_party p
static map_location::DIRECTION s
#define b