1 /*
2  * Copyright (C) 2003,2008 Red Hat, Inc.
3  * Copyright © 2019, 2020 Christian Persch
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 #pragma once
20 
21 #include <cassert>
22 
23 #include <glib.h>
24 #include <pango/pangocairo.h>
25 #include <gtk/gtk.h>
26 
27 #include "pango-glue.hh"
28 #include "refptr.hh"
29 #include "vteunistr.h"
30 
31 /* Overview:
32  *
33  *
34  * This file implements vte rendering using pangocairo.  Note that this does
35  * NOT implement any kind of complex text rendering.  That's not currently a
36  * goal.
37  *
38  * The aim is to be super-fast and avoid unneeded work as much as possible.
39  * Here is an overview of how that is accomplished:
40  *
41  *   - We attach a font_info to the draw.  A font_info has all the information
42  *     to quickly draw text.
43  *
44  *   - A font_info keeps uses unistr_font_info structs that represent all
45  *     information needed to quickly draw a single vteunistr.  The font_info
46  *     creates those unistr_font_info structs on demand and caches them
47  *     indefinitely.  It uses a direct array for the ASCII range and a hash
48  *     table for the rest.
49  *
50  *
51  * Fast rendering of unistrs:
52  *
53  * A unistr_font_info (uinfo) calls Pango to set text for the unistr upon
54  * initialization and then caches information needed to draw the results
55  * later.  It uses three different internal representations and respectively
56  * three drawing paths:
57  *
58  *   - Coverage::USE_CAIRO_GLYPH:
59  *     Keeping a single glyph index and a cairo scaled-font.  This is the
60  *     fastest way to draw text as it bypasses Pango completely and allows
61  *     for stuffing multiple glyphs into a single cairo_show_glyphs() request
62  *     (if scaled-fonts match).  This method is used if the glyphs used for
63  *     the vteunistr as determined by Pango consists of a single regular glyph
64  *     positioned at 0,0 using a regular font.  This method is used for more
65  *     than 99% of the cases.  Only exceptional cases fall through to the
66  *     other two methods.
67  *
68  *   - Coverage::USE_PANGO_GLYPH_STRING:
69  *     Keeping a pango glyphstring and a pango font.  This is slightly slower
70  *     than the previous case as drawing each glyph goes through pango
71  *     separately and causes a separate cairo_show_glyphs() call.  This method
72  *     is used when the previous method cannot be used but the glyphs for the
73  *     character all use a single font.  This is the method used for hexboxes
74  *     and "empty" characters like U+200C ZERO WIDTH NON-JOINER for example.
75  *
76  *   - Coverage::USE_PANGO_LAYOUT_LINE:
77  *     Keeping a pango layout line.  This method is used only in the very
78  *     weird and exceptional case that a single vteunistr uses more than one
79  *     font to be drawn.  This happens for example if some diacretics is not
80  *     available in the font chosen for the base character.
81  *
82  *
83  * Caching of font infos:
84  *
85  * To avoid recreating font info structs for the same font again and again we
86  * do the following:
87  *
88  *   - Use a global cache to share font info structs across different widgets.
89  *     We use pango language, cairo font options, resolution, and font description
90  *     as the key for our hash table.
91  *
92  *   - When a font info struct is no longer used by any widget, we delay
93  *     destroying it for a while (FONT_CACHE_TIMEOUT seconds).  This is
94  *     supposed to serve two purposes:
95  *
96  *       * Destroying a terminal widget and creating it again right after will
97  *         reuse the font info struct from the previous widget.
98  *
99  *       * Zooming in and out a terminal reuses the font info structs.
100  *
101  *
102  * Pre-caching ASCII letters:
103  *
104  * When initializing a font info struct we measure a string consisting of all
105  * ASCII letters and some other ASCII characters.  Since we have a shaped pango
106  * layout at hand, we walk over it and cache unistr font info for the ASCII
107  * letters if we can do that easily using Coverage::USE_CAIRO_GLYPH.  This
108  * means that we precache all ASCII letters without any extra pango shaping
109  * involved.
110  */
111 
112 namespace vte {
113 namespace view {
114 
115 class DrawingContext;
116 
117 class FontInfo {
118         friend class DrawingContext;
119 
120         int const font_cache_timeout = 30; // seconds
121 
122 public:
123         FontInfo(PangoContext* context);
124         ~FontInfo();
125 
ref()126         FontInfo* ref()
127         {
128                 // refcount is 0 when unused but still in cache
129                 assert(m_ref_count >= 0);
130 
131                 ++m_ref_count;
132 
133                 if (m_destroy_timeout != 0) {
134                         g_source_remove (m_destroy_timeout);
135                         m_destroy_timeout = 0;
136                 }
137 
138                 return this;
139         }
140 
unref()141         void unref()
142         {
143                 assert(m_ref_count > 0);
144                 if (--m_ref_count > 0)
145                         return;
146 
147                 /* Delay destruction by a few seconds, in case we need it again */
148                 m_destroy_timeout = g_timeout_add_seconds(font_cache_timeout,
149                                                           (GSourceFunc)destroy_delayed_cb,
150                                                           this);
151         }
152 
153         struct UnistrInfo {
154                 enum class Coverage : uint8_t {
155                         /* in increasing order of speed */
156                         UNKNOWN = 0u,           /* we don't know about the character yet    */
157                         USE_PANGO_LAYOUT_LINE,  /* use a PangoLayoutLine for the character  */
158                         USE_PANGO_GLYPH_STRING, /* use a PangoGlyphString for the character */
159                         USE_CAIRO_GLYPH         /* use a cairo_glyph_t for the character    */
160                 };
161 
162                 uint8_t m_coverage{uint8_t(Coverage::UNKNOWN)};
163                 uint8_t has_unknown_chars;
164                 uint16_t width;
165 
coveragevte::view::FontInfo::UnistrInfo166                 inline constexpr Coverage coverage() const noexcept { return Coverage{m_coverage}; }
set_coveragevte::view::FontInfo::UnistrInfo167                 inline constexpr void set_coverage(Coverage coverage) { m_coverage = uint8_t(coverage); }
168 
169                 // FIXME: use std::variant<std::monostate, RefPtr<PangoLayoutLine>, ...> ?
170                 union unistr_font_info {
171                         /* Coverage::USE_PANGO_LAYOUT_LINE */
172                         struct {
173                                 PangoLayoutLine *line;
174                         } using_pango_layout_line;
175                         /* Coverage::USE_PANGO_GLYPH_STRING */
176                         struct {
177                                 PangoFont *font;
178                                 PangoGlyphString *glyph_string;
179                         } using_pango_glyph_string;
180                         /* Coverage::USE_CAIRO_GLYPH */
181                         struct {
182                                 cairo_scaled_font_t *scaled_font;
183                                 unsigned int glyph_index;
184                         } using_cairo_glyph;
185                 } m_ufi;
186 
187                 UnistrInfo() noexcept = default;
188 
~UnistrInfovte::view::FontInfo::UnistrInfo189                 ~UnistrInfo() noexcept
190                 {
191                         switch (coverage()) {
192                         default:
193                         case Coverage::UNKNOWN:
194                                 break;
195                         case Coverage::USE_PANGO_LAYOUT_LINE:
196                                 /* we hold a manual reference on layout */
197                                 g_object_unref (m_ufi.using_pango_layout_line.line->layout);
198                                 m_ufi.using_pango_layout_line.line->layout = NULL;
199                                 pango_layout_line_unref (m_ufi.using_pango_layout_line.line);
200                                 m_ufi.using_pango_layout_line.line = NULL;
201                                 break;
202                         case Coverage::USE_PANGO_GLYPH_STRING:
203                                 if (m_ufi.using_pango_glyph_string.font)
204                                         g_object_unref (m_ufi.using_pango_glyph_string.font);
205                                 m_ufi.using_pango_glyph_string.font = NULL;
206                                 pango_glyph_string_free (m_ufi.using_pango_glyph_string.glyph_string);
207                                 m_ufi.using_pango_glyph_string.glyph_string = NULL;
208                                 break;
209                         case Coverage::USE_CAIRO_GLYPH:
210                                 cairo_scaled_font_destroy (m_ufi.using_cairo_glyph.scaled_font);
211                                 m_ufi.using_cairo_glyph.scaled_font = NULL;
212                                 break;
213                         }
214                 }
215 
216         }; // struct UnistrInfo
217 
218         UnistrInfo *get_unistr_info(vteunistr c);
width() const219         inline constexpr int width() const { return m_width; }
height() const220         inline constexpr int height() const { return m_height; }
ascent() const221         inline constexpr int ascent() const { return m_ascent; }
222 
223 private:
224 
unistr_info_destroy(UnistrInfo * uinfo)225         static void unistr_info_destroy(UnistrInfo* uinfo)
226         {
227                 delete uinfo;
228         }
229 
destroy_delayed_cb(void * that)230         static gboolean destroy_delayed_cb(void* that)
231         {
232                 auto info = reinterpret_cast<FontInfo*>(that);
233                 info->m_destroy_timeout = 0;
234                 delete info;
235                 return false;
236         }
237 
238         mutable int m_ref_count{1};
239 
240         UnistrInfo* find_unistr_info(vteunistr c);
241         void cache_ascii();
242         void measure_font();
243         guint m_destroy_timeout{0}; /* only used when ref_count == 0 */
244 
245 	/* reusable layout set with font and everything set */
246         vte::glib::RefPtr<PangoLayout> m_layout{};
247 
248 	/* cache of character info */
249         // FIXME: use std::array<UnistrInfo, 128>
250 	UnistrInfo m_ascii_unistr_info[128];
251         // FIXME: use std::unordered_map<vteunistr, UnistrInfo>
252 	GHashTable* m_other_unistr_info{nullptr};
253 
254         /* cell metrics as taken from the font, not yet scaled by cell_{width,height}_scale */
255 	int m_width{1};
256         int m_height{1};
257         int m_ascent{0};
258 
259 	/* reusable string for UTF-8 conversion */
260         // FIXME: use std::string
261 	GString* m_string{nullptr};
262 
263 #ifdef VTE_DEBUG
264 	/* profiling info */
265 	int m_coverage_count[4]{0, 0, 0, 0};
266 #endif
267 
268         static FontInfo* create_for_context(vte::glib::RefPtr<PangoContext> context,
269                                             PangoFontDescription const* desc,
270                                             PangoLanguage* language,
271                                             guint fontconfig_timestamp);
272         static FontInfo *create_for_screen(GdkScreen* screen,
273                                            PangoFontDescription const* desc,
274                                            PangoLanguage* language);
275 public:
276 
277         static FontInfo *create_for_widget(GtkWidget* widget,
278                                            PangoFontDescription const* desc);
279 
280 private:
281         static inline GHashTable* s_font_info_for_context{nullptr};
282 
283 }; // class FontInfo
284 
285 } // namespace view
286 } // namespace vte
287