summaryrefslogtreecommitdiff
path: root/src/fonts-pangocairo.hh
blob: 587e64218ba09778df2f6eec4fd93886148c9191 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/*
 * Copyright (C) 2003,2008 Red Hat, Inc.
 * Copyright © 2019, 2020 Christian Persch
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once

#include <cassert>

#include <glib.h>
#include <pango/pangocairo.h>
#include <gtk/gtk.h>

#include "cairo-glue.hh"
#include "pango-glue.hh"
#include "refptr.hh"
#include "vteunistr.h"

/* Overview:
 *
 *
 * This file implements vte rendering using pangocairo.  Note that this does
 * NOT implement any kind of complex text rendering.  That's not currently a
 * goal.
 *
 * The aim is to be super-fast and avoid unneeded work as much as possible.
 * Here is an overview of how that is accomplished:
 *
 *   - We attach a font_info to the draw.  A font_info has all the information
 *     to quickly draw text.
 *
 *   - A font_info keeps uses unistr_font_info structs that represent all
 *     information needed to quickly draw a single vteunistr.  The font_info
 *     creates those unistr_font_info structs on demand and caches them
 *     indefinitely.  It uses a direct array for the ASCII range and a hash
 *     table for the rest.
 *
 *
 * Fast rendering of unistrs:
 *
 * A unistr_font_info (uinfo) calls Pango to set text for the unistr upon
 * initialization and then caches information needed to draw the results
 * later.  It uses three different internal representations and respectively
 * three drawing paths:
 *
 *   - Coverage::USE_CAIRO_GLYPH:
 *     Keeping a single glyph index and a cairo scaled-font.  This is the
 *     fastest way to draw text as it bypasses Pango completely and allows
 *     for stuffing multiple glyphs into a single cairo_show_glyphs() request
 *     (if scaled-fonts match).  This method is used if the glyphs used for
 *     the vteunistr as determined by Pango consists of a single regular glyph
 *     positioned at 0,0 using a regular font.  This method is used for more
 *     than 99% of the cases.  Only exceptional cases fall through to the
 *     other two methods.
 *
 *   - Coverage::USE_PANGO_GLYPH_STRING:
 *     Keeping a pango glyphstring and a pango font.  This is slightly slower
 *     than the previous case as drawing each glyph goes through pango
 *     separately and causes a separate cairo_show_glyphs() call.  This method
 *     is used when the previous method cannot be used but the glyphs for the
 *     character all use a single font.  This is the method used for hexboxes
 *     and "empty" characters like U+200C ZERO WIDTH NON-JOINER for example.
 *
 *   - Coverage::USE_PANGO_LAYOUT_LINE:
 *     Keeping a pango layout line.  This method is used only in the very
 *     weird and exceptional case that a single vteunistr uses more than one
 *     font to be drawn.  This happens for example if some diacretics is not
 *     available in the font chosen for the base character.
 *
 *
 * Caching of font infos:
 *
 * To avoid recreating font info structs for the same font again and again we
 * do the following:
 *
 *   - Use a global cache to share font info structs across different widgets.
 *     We use pango language, cairo font options, resolution, and font description
 *     as the key for our hash table.
 *
 *   - When a font info struct is no longer used by any widget, we delay
 *     destroying it for a while (FONT_CACHE_TIMEOUT seconds).  This is
 *     supposed to serve two purposes:
 *
 *       * Destroying a terminal widget and creating it again right after will
 *         reuse the font info struct from the previous widget.
 *
 *       * Zooming in and out a terminal reuses the font info structs.
 *
 *
 * Pre-caching ASCII letters:
 *
 * When initializing a font info struct we measure a string consisting of all
 * ASCII letters and some other ASCII characters.  Since we have a shaped pango
 * layout at hand, we walk over it and cache unistr font info for the ASCII
 * letters if we can do that easily using Coverage::USE_CAIRO_GLYPH.  This
 * means that we precache all ASCII letters without any extra pango shaping
 * involved.
 */

namespace vte {
namespace view {

class DrawingContext;

class FontInfo {
        friend class DrawingContext;

        int const font_cache_timeout = 30; // seconds

public:
        FontInfo(vte::glib::RefPtr<PangoContext> context);
        ~FontInfo();

        FontInfo* ref()
        {
                // refcount is 0 when unused but still in cache
                assert(m_ref_count >= 0);

                ++m_ref_count;

                if (m_destroy_timeout != 0) {
                        g_source_remove (m_destroy_timeout);
                        m_destroy_timeout = 0;
                }

                return this;
        }

        void unref()
        {
                assert(m_ref_count > 0);
                if (--m_ref_count > 0)
                        return;

                /* Delay destruction by a few seconds, in case we need it again */
                m_destroy_timeout = g_timeout_add_seconds(font_cache_timeout,
                                                          (GSourceFunc)destroy_delayed_cb,
                                                          this);
        }

        struct UnistrInfo {
                enum class Coverage : uint8_t {
                        /* in increasing order of speed */
                        UNKNOWN = 0u,           /* we don't know about the character yet    */
                        USE_PANGO_LAYOUT_LINE,  /* use a PangoLayoutLine for the character  */
                        USE_PANGO_GLYPH_STRING, /* use a PangoGlyphString for the character */
                        USE_CAIRO_GLYPH         /* use a cairo_glyph_t for the character    */
                };

                uint8_t m_coverage{uint8_t(Coverage::UNKNOWN)};
                uint8_t has_unknown_chars;
                uint16_t width;

                inline constexpr Coverage coverage() const noexcept { return Coverage{m_coverage}; }
                inline constexpr void set_coverage(Coverage coverage) { m_coverage = uint8_t(coverage); }

                // FIXME: use std::variant<std::monostate, RefPtr<PangoLayoutLine>, ...> ?
                union unistr_font_info {
                        /* Coverage::USE_PANGO_LAYOUT_LINE */
                        struct {
                                PangoLayoutLine *line;
                        } using_pango_layout_line;
                        /* Coverage::USE_PANGO_GLYPH_STRING */
                        struct {
                                PangoFont *font;
                                PangoGlyphString *glyph_string;
                        } using_pango_glyph_string;
                        /* Coverage::USE_CAIRO_GLYPH */
                        struct {
                                cairo_scaled_font_t *scaled_font;
                                unsigned int glyph_index;
                        } using_cairo_glyph;
                } m_ufi;

                UnistrInfo() noexcept = default;

                ~UnistrInfo() noexcept
                {
                        switch (coverage()) {
                        default:
                        case Coverage::UNKNOWN:
                                break;
                        case Coverage::USE_PANGO_LAYOUT_LINE:
                                /* we hold a manual reference on layout */
                                g_object_unref (m_ufi.using_pango_layout_line.line->layout);
                                m_ufi.using_pango_layout_line.line->layout = NULL;
                                pango_layout_line_unref (m_ufi.using_pango_layout_line.line);
                                m_ufi.using_pango_layout_line.line = NULL;
                                break;
                        case Coverage::USE_PANGO_GLYPH_STRING:
                                if (m_ufi.using_pango_glyph_string.font)
                                        g_object_unref (m_ufi.using_pango_glyph_string.font);
                                m_ufi.using_pango_glyph_string.font = NULL;
                                pango_glyph_string_free (m_ufi.using_pango_glyph_string.glyph_string);
                                m_ufi.using_pango_glyph_string.glyph_string = NULL;
                                break;
                        case Coverage::USE_CAIRO_GLYPH:
                                cairo_scaled_font_destroy (m_ufi.using_cairo_glyph.scaled_font);
                                m_ufi.using_cairo_glyph.scaled_font = NULL;
                                break;
                        }
                }

        }; // struct UnistrInfo

        UnistrInfo *get_unistr_info(vteunistr c);
        inline constexpr int width() const { return m_width; }
        inline constexpr int height() const { return m_height; }
        inline constexpr int ascent() const { return m_ascent; }

private:

        static void unistr_info_destroy(UnistrInfo* uinfo)
        {
                delete uinfo;
        }

        static gboolean destroy_delayed_cb(void* that)
        {
                auto info = reinterpret_cast<FontInfo*>(that);
                info->m_destroy_timeout = 0;
                delete info;
                return false;
        }

        mutable int m_ref_count{1};

        UnistrInfo* find_unistr_info(vteunistr c);
        void cache_ascii();
        void measure_font();
        guint m_destroy_timeout{0}; /* only used when ref_count == 0 */

	/* reusable layout set with font and everything set */
        vte::glib::RefPtr<PangoLayout> m_layout{};

	/* cache of character info */
        // FIXME: use std::array<UnistrInfo, 128>
	UnistrInfo m_ascii_unistr_info[128];
        // FIXME: use std::unordered_map<vteunistr, UnistrInfo>
	GHashTable* m_other_unistr_info{nullptr};

        /* cell metrics as taken from the font, not yet scaled by cell_{width,height}_scale */
	int m_width{1};
        int m_height{1};
        int m_ascent{0};

	/* reusable string for UTF-8 conversion */
        // FIXME: use std::string
	GString* m_string{nullptr};

#ifdef VTE_DEBUG
	/* profiling info */
	int m_coverage_count[4]{0, 0, 0, 0};
#endif

        static FontInfo* create_for_context(vte::glib::RefPtr<PangoContext> context,
                                            PangoFontDescription const* desc,
                                            PangoLanguage* language,
                                            cairo_font_options_t const* font_options,
                                            guint fontconfig_timestamp);
#if VTE_GTK == 3
        static FontInfo *create_for_screen(GdkScreen* screen,
                                           PangoFontDescription const* desc,
                                           PangoLanguage* language,
                                           cairo_font_options_t const* font_options);
#endif

public:

        static FontInfo *create_for_widget(GtkWidget* widget,
                                           PangoFontDescription const* desc,
                                           cairo_font_options_t const* font_options);

}; // class FontInfo

} // namespace view
} // namespace vte