The Battle for Wesnoth  1.17.0-dev
text.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2021
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"
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  * @param viewport Only this area needs to be drawn - the returned
88  * surface's origin will correspond to viewport.x and viewport.y, the
89  * width and height will be at least viewport.w and viewport.h (although
90  * they may be larger).
91  */
92  surface& render(const SDL_Rect& viewport);
93 
94  /**
95  * Equivalent to render(viewport), where the viewport's top-left is at
96  * (0,0) and the area is large enough to contain the full text.
97  *
98  * The top-left of the viewport will be at (0,0), regardless of the values
99  * of x and y. If the x or y co-ordinates are non-zero, then x columns and
100  * y rows of blank space are included in the amount of memory allocated.
101  */
102  surface& render();
103 
104  /** Returns the width needed for the text. */
105  int get_width() const;
106 
107  /** Returns the height needed for the text. */
108  int get_height() const;
109 
110  /** Returns the pixel size needed for the text. */
111  point get_size() const;
112 
113  /** Has the text been truncated? This happens if it exceeds max width or height. */
114  bool is_truncated() const;
115 
116  /**
117  * Inserts UTF-8 text.
118  *
119  * @param offset The position to insert the text.
120  * @param text The UTF-8 text to insert.
121  *
122  * @returns The number of characters inserted.
123  */
124  unsigned insert_text(const unsigned offset, const std::string& text);
125 
126  /***** ***** ***** ***** Font flags ***** ***** ***** *****/
127 
128  // NOTE: these values must be powers of 2 in order to be bit-unique
129  enum FONT_STYLE {
134  };
135 
136  /***** ***** ***** ***** Query details ***** ***** ***** *****/
137 
138  /**
139  * Returns the maximum glyph height of a font, in pixels.
140  *
141  * @returns The height of the tallest possible glyph for the selected
142  * font. More specifically, the result is the sum of the maximum
143  * ascent and descent lengths.
144  */
145  int get_max_glyph_height() const;
146 
147  /**
148  * Gets the location for the cursor.
149  *
150  * @param column The column offset of the cursor.
151  * @param line The line offset of the cursor.
152  *
153  * @returns The position of the top of the cursor. It the
154  * requested location is out of range 0,0 is
155  * returned.
156  */
158  const unsigned column, const unsigned line = 0) const;
159 
160  /**
161  * Get maximum length.
162  *
163  * @returns The maximum length of the text. The length of text
164  * should not exceed this value.
165  */
166  std::size_t get_maximum_length() const;
167 
168  /**
169  * Gets the largest collection of characters, including the token at position,
170  * and not including any characters from the delimiters set.
171  *
172  * @param position The pixel position in the text area.
173  * @param delimiters
174  *
175  * @returns The token containing position, and none of the
176  * delimiter characters. If position is out of bounds,
177  * it returns the empty string.
178  */
179  std::string get_token(const point & position, const char * delimiters = " \n\r\t") const;
180 
181  /**
182  * Checks if position points to a character in a link in the text, returns it
183  * if so, empty string otherwise. Link-awareness must be enabled to get results.
184  * @param position The pixel position in the text area.
185  *
186  * @returns The link if one is found, the empty string otherwise.
187  */
188  std::string get_link(const point & position) const;
189 
190  /**
191  * Gets the column of line of the character at the position.
192  *
193  * @param position The pixel position in the text area.
194  *
195  * @returns A point with the x value the column and the y
196  * value the line of the character found (or last
197  * character if not found.
198  */
199  point get_column_line(const point& position) const;
200 
201  /**
202  * Retrieves a list of strings with contents for each rendered line.
203  *
204  * This method is not const because it requires rendering the text.
205  *
206  * @note This is only intended for renderer implementation details. This
207  * is a rather expensive function because it copies everything at
208  * least once.
209  */
210  std::vector<std::string> get_lines() const;
211 
212  /**
213  * Gets the length of the text in bytes.
214  *
215  * The text set is UTF-8 so the length of the string might not be the length
216  * of the text.
217  */
218  std::size_t get_length() const { return length_; }
219 
220  /**
221  * Sets the text to render.
222  *
223  * @param text The text to render.
224  * @param markedup Should the text be rendered with pango
225  * markup. If the markup is invalid it's
226  * rendered as text without markup.
227  *
228  * @returns The status, if rendered as markup and the
229  * markup contains errors, false is returned
230  * else true.
231  */
232  bool set_text(const std::string& text, const bool markedup);
233 
234  /***** ***** ***** ***** Setters / getters ***** ***** ***** *****/
235 
236  const std::string& text() const { return text_; }
237 
239 
240  pango_text& set_font_size(const unsigned font_size);
241 
242  pango_text& set_font_style(const FONT_STYLE font_style);
243 
244  pango_text& set_foreground_color(const color_t& color);
245 
246  pango_text& set_maximum_width(int width);
247 
248  pango_text& set_characters_per_line(const unsigned characters_per_line);
249 
250  pango_text& set_maximum_height(int height, bool multiline);
251 
252  pango_text& set_ellipse_mode(const PangoEllipsizeMode ellipse_mode);
253 
254  pango_text& set_alignment(const PangoAlignment alignment);
255 
256  pango_text& set_maximum_length(const std::size_t maximum_length);
257 
258  bool link_aware() const { return link_aware_; }
259 
260  pango_text& set_link_aware(bool b);
261 
262  pango_text& set_link_color(const color_t& color);
263 
264  pango_text& set_add_outline(bool do_add);
265 
266 private:
267 
268  /***** ***** ***** ***** Pango variables ***** ***** ***** *****/
269  std::unique_ptr<PangoContext, std::function<void(void*)>> context_;
270  std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
271  mutable PangoRectangle rect_;
272 
273  /** The SDL surface to render upon used as a cache. */
274  mutable surface surface_;
275 
276 
277  /** The text to draw (stored as UTF-8). */
278  std::string text_;
279 
280  /** Does the text contain pango markup? If different render routines must be used. */
282 
283  /** Are hyperlinks in the text marked-up, and will get_link return them. */
285 
286  /**
287  * The color to render links in.
288  *
289  * Links are formatted using pango &lt;span> as follows:
290  *
291  * &lt;span underline="single" color=" + link_color_ + ">
292  */
294 
295  /** The font family class used. */
297 
298  /** The font size to draw. */
299  unsigned font_size_;
300 
301  /** The style of the font, this is an orred mask of the font flags. */
303 
304  /** The foreground color. */
306 
307  /** Whether to add an outline effect. */
309 
310  /**
311  * The maximum width of the text.
312  *
313  * Values less or equal to 0 mean no maximum and are internally stored as
314  * -1, since that's the value pango uses for it.
315  *
316  * See @ref characters_per_line_.
317  */
319 
320  /**
321  * The number of characters per line.
322  *
323  * This can be used as an alternative of @ref maximum_width_. The user can
324  * select a number of characters on a line for wrapping. When the value is
325  * non-zero it determines the maximum width based on the average character
326  * width.
327  *
328  * If both @ref maximum_width_ and @ref characters_per_line_ are set the
329  * minimum of the two will be the maximum.
330  *
331  * @note Long lines are often harder to read, setting this value can
332  * automatically wrap on a number of characters regardless of the font
333  * size. Often 66 characters is considered the optimal value for a one
334  * column text.
335  */
337 
338  /**
339  * The maximum height of the text.
340  *
341  * Values less or equal to 0 mean no maximum and are internally stored as
342  * -1, since that's the value pango uses for it.
343  */
345 
346  /** The way too long text is shown depends on this mode. */
347  PangoEllipsizeMode ellipse_mode_;
348 
349  /** The alignment of the text. */
350  PangoAlignment alignment_;
351 
352  /** The maximum length of the text. */
353  std::size_t maximum_length_;
354 
355  /**
356  * The text has two dirty states:
357  * - The setting of the state and the size calculations.
358  * - The rendering of the surface.
359  */
360 
361  /** The dirty state of the calculations. */
362  mutable bool calculation_dirty_;
363 
364  /** Length of the text. */
365  mutable std::size_t length_;
366 
367  /**
368  * Recalculates the text layout.
369  */
370  void recalculate() const;
371 
372  /** Calculates surface size. */
373  PangoRectangle calculate_size(PangoLayout& layout) const;
374 
375  /** The dirty state of the surface. */
376  mutable bool surface_dirty_;
377 
378  /** The area that's cached in surface_, which is the area that was rendered when surface_dirty_ was last set to false. */
380 
381  /**
382  * Renders the text.
383  *
384  * It will do a recalculation first so no need to call both.
385  */
386  void rerender(const SDL_Rect& viewport);
387 
388  void render(PangoLayout& layout, const SDL_Rect& viewport, const unsigned stride);
389 
390  /**
391  * Buffer to store the image on.
392  *
393  * We use a cairo surface to draw on this buffer and then use the buffer as
394  * data source for the SDL_Surface. This means the buffer needs to be stored
395  * in the object, since SDL_Surface doesn't own its buffer.
396  */
397  mutable std::vector<uint8_t> surface_buffer_;
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  * @param layout
407  *
408  * @returns Whether the markup was set or an
409  * unrecoverable error occurred and the text is
410  * set as plain text with an error message.
411  */
412  bool set_markup(std::string_view text, PangoLayout& layout);
413 
414  bool validate_markup(std::string_view text, char** raw_text, std::string& semi_escaped) const;
415 
416  static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
417 
418  std::string format_links(std::string_view text) const;
419 };
420 
421 /**
422  * Returns a reference to a static pango_text object.
423  *
424  * Since the class is essentially a render pipeline, there's no need for individual
425  * areas of the game to own their own renderers. Not to mention it isn't a trivial
426  * class; constructing one is likely to be expensive.
427  */
429 
430 /**
431  * Returns the maximum glyph height of a font, in pixels.
432  *
433  * @param size Desired font size in pixels.
434  * @param fclass Font family to use for measurement.
435  * @param style Font style to select the correct variant for measurement.
436  *
437  * @returns The height of the tallest possible glyph for the selected
438  * font. More specifically, the result is the sum of the maximum
439  * ascent and descent lengths.
440  */
442 
443 } // namespace font
void recalculate() const
Recalculates the text layout.
Definition: text.cpp:500
unsigned font_size_
The font size to draw.
Definition: text.hpp:299
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:218
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:283
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:753
int get_width() const
Returns the width needed for the text.
Definition: text.cpp:108
int maximum_height_
The maximum height of the text.
Definition: text.hpp:344
unsigned characters_per_line_
The number of characters per line.
Definition: text.hpp:336
std::size_t length_
Length of the text.
Definition: text.hpp:365
std::vector< std::string > get_lines() const
Retrieves a list of strings with contents for each rendered line.
Definition: text.cpp:865
pango_text & set_link_aware(bool b)
Definition: text.cpp:448
bool link_aware() const
Definition: text.hpp:258
std::size_t maximum_length_
The maximum length of the text.
Definition: text.hpp:353
void rerender(const SDL_Rect &viewport)
Renders the text.
Definition: text.cpp:702
std::size_t get_maximum_length() const
Get maximum length.
Definition: text.cpp:197
font::family_class font_class_
The font family class used.
Definition: text.hpp:296
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:343
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:891
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:897
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:410
pango_text & set_maximum_length(const std::size_t maximum_length)
Definition: text.cpp:435
std::vector< uint8_t > surface_buffer_
Buffer to store the image on.
Definition: text.hpp:397
bool validate_markup(std::string_view text, char **raw_text, std::string &semi_escaped) const
Definition: text.cpp:820
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:424
#define b
point get_cursor_position(const unsigned column, const unsigned line=0) const
Gets the location for the cursor.
Definition: text.cpp:152
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:202
point get_size() const
Returns the pixel size needed for the text.
Definition: text.cpp:118
std::unique_ptr< PangoLayout, std::function< void(void *)> > layout_
Definition: text.hpp:270
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:782
SDL_Rect rendered_viewport_
The area that&#39;s cached in surface_, which is the area that was rendered when surface_dirty_ was last ...
Definition: text.hpp:379
bool is_truncated() const
Has the text been truncated? This happens if it exceeds max width or height.
Definition: text.cpp:125
int maximum_width_
The maximum width of the text.
Definition: text.hpp:318
color_t foreground_color_
The foreground color.
Definition: text.hpp:305
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
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:331
PangoEllipsizeMode ellipse_mode_
The way too long text is shown depends on this mode.
Definition: text.hpp:347
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:379
bool markedup_text_
Does the text contain pango markup? If different render routines must be used.
Definition: text.hpp:281
PangoRectangle calculate_size(PangoLayout &layout) const
Calculates surface size.
Definition: text.cpp:512
bool surface_dirty_
The dirty state of the surface.
Definition: text.hpp:376
PangoRectangle rect_
Definition: text.hpp:271
bool calculation_dirty_
The text has two dirty states:
Definition: text.hpp:362
unsigned insert_text(const unsigned offset, const std::string &text)
Inserts UTF-8 text.
Definition: text.cpp:132
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:320
std::unique_ptr< PangoContext, std::function< void(void *)> > context_
Definition: text.hpp:269
bool link_aware_
Are hyperlinks in the text marked-up, and will get_link return them.
Definition: text.hpp:284
point get_column_line(const point &position) const
Gets the column of line of the character at the position.
Definition: text.cpp:249
FONT_STYLE font_style_
The style of the font, this is an orred mask of the font flags.
Definition: text.hpp:302
int get_max_glyph_height() const
Returns the maximum glyph height of a font, in pixels.
Definition: text.cpp:480
bool add_outline_
Whether to add an outline effect.
Definition: text.hpp:308
Holds a 2D point.
Definition: point.hpp:24
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:458
int get_height() const
Returns the height needed for the text.
Definition: text.cpp:113
const std::string & text() const
Definition: text.hpp:236
color_t link_color_
The color to render links in.
Definition: text.hpp:293
Text class.
Definition: text.hpp:75
pango_text & set_maximum_width(int width)
Definition: text.cpp:364
surface surface_
The SDL surface to render upon used as a cache.
Definition: text.hpp:274
surface & render()
Equivalent to render(viewport), where the viewport&#39;s top-left is at (0,0) and the area is large enoug...
Definition: text.cpp:100
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:391
std::string text_
The text to draw (stored as UTF-8).
Definition: text.hpp:278
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:354
PangoAlignment alignment_
The alignment of the text.
Definition: text.hpp:350
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:469
static void copy_layout_properties(PangoLayout &src, PangoLayout &dst)
Definition: text.cpp:858
pango_text & operator=(const pango_text &)=delete