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