[vte] draw: Move FontInfo to its own file



commit 42ea29e93c08266c3b3fd30cc4a838f3970f9892
Author: Christian Persch <chpe src gnome org>
Date:   Mon Jun 1 22:48:43 2020 +0200

    draw: Move FontInfo to its own file

 src/fonts-pangocairo.cc | 474 ++++++++++++++++++++++++++++++++
 src/fonts-pangocairo.hh | 286 ++++++++++++++++++++
 src/fwd.hh              |   7 +
 src/meson.build         |   2 +
 src/vtedraw.cc          | 700 +-----------------------------------------------
 5 files changed, 770 insertions(+), 699 deletions(-)
---
diff --git a/src/fonts-pangocairo.cc b/src/fonts-pangocairo.cc
new file mode 100644
index 00000000..fabecf66
--- /dev/null
+++ b/src/fonts-pangocairo.cc
@@ -0,0 +1,474 @@
+/*
+ * 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 General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "fonts-pangocairo.hh"
+
+#include "debug.h"
+#include "vtedefines.hh"
+
+/* Have a space between letters to make sure ligatures aren't used when caching the glyphs: bug 793391. */
+#define VTE_DRAW_SINGLE_WIDE_CHARACTERS        \
+                                       "  ! \" # $ % & ' ( ) * + , - . / " \
+                                       "0 1 2 3 4 5 6 7 8 9 " \
+                                       ": ; < = > ? @ " \
+                                       "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z " \
+                                       "[ \\ ] ^ _ ` " \
+                                       "a b c d e f g h i j k l m n o p q r s t u v w x y z " \
+                                       "{ | } ~ " \
+                                       ""
+
+static inline bool
+_vte_double_equal(double a,
+                  double b)
+{
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+        return a == b;
+#pragma GCC diagnostic pop
+}
+
+#define FONT_CACHE_TIMEOUT (30) /* seconds */
+
+namespace vte {
+namespace view {
+
+FontInfo::UnistrInfo*
+FontInfo::find_unistr_info(vteunistr c)
+{
+       if (G_LIKELY (c < G_N_ELEMENTS(m_ascii_unistr_info)))
+               return &m_ascii_unistr_info[c];
+
+       if (G_UNLIKELY (m_other_unistr_info == nullptr))
+               m_other_unistr_info = g_hash_table_new_full(nullptr, nullptr, nullptr, 
(GDestroyNotify)unistr_info_destroy);
+
+       auto uinfo = reinterpret_cast<UnistrInfo*>(g_hash_table_lookup(m_other_unistr_info, 
GINT_TO_POINTER(c)));
+       if (G_LIKELY (uinfo))
+               return uinfo;
+
+       uinfo = new UnistrInfo{};
+       g_hash_table_insert(m_other_unistr_info, GINT_TO_POINTER (c), uinfo);
+       return uinfo;
+}
+
+void
+FontInfo::cache_ascii()
+{
+       PangoLayoutLine *line;
+       PangoGlyphItemIter iter;
+       PangoGlyphItem *glyph_item;
+       PangoGlyphString *glyph_string;
+       PangoFont *pango_font;
+       cairo_scaled_font_t *scaled_font;
+       const char *text;
+       gboolean more;
+       PangoLanguage *language;
+       gboolean latin_uses_default_language;
+
+       /* We have m_layout holding most ASCII characters.  We want to
+        * cache as much info as we can about the ASCII letters so we don't
+        * have to look them up again later */
+
+       /* Don't cache if unknown glyphs found in layout */
+       if (pango_layout_get_unknown_glyphs_count(m_layout.get()) != 0)
+               return;
+
+       language = pango_context_get_language(pango_layout_get_context(m_layout.get()));
+       if (language == nullptr)
+               language = pango_language_get_default ();
+       latin_uses_default_language = pango_language_includes_script (language, PANGO_SCRIPT_LATIN);
+
+       text = pango_layout_get_text(m_layout.get());
+
+       line = pango_layout_get_line_readonly(m_layout.get(), 0);
+
+       /* Don't cache if more than one font used for the line */
+       if (G_UNLIKELY (!line || !line->runs || line->runs->next))
+               return;
+
+       glyph_item = (PangoGlyphItem *)line->runs->data;
+       glyph_string = glyph_item->glyphs;
+       pango_font = glyph_item->item->analysis.font;
+       if (!pango_font)
+               return;
+       scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *) pango_font);
+       if (!scaled_font)
+               return;
+
+       for (more = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
+            more;
+            more = pango_glyph_item_iter_next_cluster (&iter))
+       {
+               PangoGlyphGeometry *geometry;
+               PangoGlyph glyph;
+               vteunistr c;
+
+               /* Only cache simple clusters */
+               if (iter.start_char +1 != iter.end_char  ||
+                   iter.start_index+1 != iter.end_index ||
+                   iter.start_glyph+1 != iter.end_glyph)
+                       continue;
+
+               c = text[iter.start_index];
+               glyph = glyph_string->glyphs[iter.start_glyph].glyph;
+               geometry = &glyph_string->glyphs[iter.start_glyph].geometry;
+
+               /* If not using the default locale language, only cache non-common
+                * characters as common characters get their font from their neighbors
+                * and we don't want to force Latin on them. */
+               if (!latin_uses_default_language &&
+                    g_unichar_get_script (c) <= G_UNICODE_SCRIPT_INHERITED)
+                       continue;
+
+               /* Only cache simple glyphs */
+               if (!(glyph <= 0xFFFF) || (geometry->x_offset | geometry->y_offset) != 0)
+                       continue;
+
+               auto uinfo = find_unistr_info(c);
+               if (G_UNLIKELY (uinfo->coverage() != UnistrInfo::Coverage::UNKNOWN))
+                       continue;
+
+               auto ufi = &uinfo->m_ufi;
+
+               uinfo->width = PANGO_PIXELS_CEIL (geometry->width);
+               uinfo->has_unknown_chars = false;
+
+               uinfo->set_coverage(UnistrInfo::Coverage::USE_CAIRO_GLYPH);
+
+               ufi->using_cairo_glyph.scaled_font = cairo_scaled_font_reference (scaled_font);
+               ufi->using_cairo_glyph.glyph_index = glyph;
+
+#ifdef VTE_DEBUG
+               m_coverage_count[0]++;
+               m_coverage_count[(unsigned)uinfo->coverage()]++;
+#endif
+       }
+
+#ifdef VTE_DEBUG
+       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+                         "vtepangocairo: %p cached %d ASCII letters\n",
+                         (void*)this, m_coverage_count[0]);
+#endif
+}
+
+void
+FontInfo::measure_font()
+{
+       PangoRectangle logical;
+
+        /* Measure U+0021..U+007E individually instead of all together and then
+         * averaging. For monospace fonts, the results should be the same, but
+         * if the user (by design, or trough mis-configuration) uses a proportional
+         * font, the latter method will greatly underestimate the required width,
+         * leading to unreadable, overlapping characters.
+         * https://gitlab.gnome.org/GNOME/vte/issues/138
+         */
+        int max_width{1};
+        int max_height{1};
+        for (char c = 0x21; c < 0x7f; ++c) {
+                pango_layout_set_text(m_layout.get(), &c, 1);
+                pango_layout_get_extents(m_layout.get(), nullptr, &logical);
+                max_width = std::max(max_width, PANGO_PIXELS_CEIL(logical.width));
+                max_height = std::max(max_height, PANGO_PIXELS_CEIL(logical.height));
+        }
+
+        /* Use the sample text to get the baseline */
+       pango_layout_set_text(m_layout.get(), VTE_DRAW_SINGLE_WIDE_CHARACTERS, -1);
+       pango_layout_get_extents(m_layout.get(), nullptr, &logical);
+       /* We don't do CEIL for width since we are averaging;
+        * rounding is more accurate */
+       m_ascent = PANGO_PIXELS_CEIL(pango_layout_get_baseline(m_layout.get()));
+
+        m_height = max_height;
+        m_width = max_width;
+
+       /* Now that we shaped the entire ASCII character string, cache glyph
+        * info for them */
+       cache_ascii();
+
+       if (m_height == 0) {
+               m_height = PANGO_PIXELS_CEIL (logical.height);
+       }
+       if (m_ascent == 0) {
+               m_ascent = PANGO_PIXELS_CEIL(pango_layout_get_baseline(m_layout.get()));
+       }
+
+       _vte_debug_print (VTE_DEBUG_MISC,
+                         "vtepangocairo: %p font metrics = %dx%d (%d)\n",
+                         (void*)this, m_width, m_height, m_ascent);
+}
+
+FontInfo::FontInfo(PangoContext *context)
+{
+       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+                         "vtepangocairo: %p allocating FontInfo\n",
+                         (void*)this);
+
+        // FIXME: placement new
+        memset(m_ascii_unistr_info, 0, sizeof(m_ascii_unistr_info));
+
+       m_layout = vte::glib::take_ref(pango_layout_new(context));
+
+       auto tabs = pango_tab_array_new_with_positions(1, FALSE, PANGO_TAB_LEFT, 1);
+       pango_layout_set_tabs(m_layout.get(), tabs);
+       pango_tab_array_free(tabs);
+
+        // FIXME!!!
+       m_string = g_string_sized_new(VTE_UTF8_BPC+1);
+
+       measure_font();
+
+       g_hash_table_insert(s_font_info_for_context,
+                            pango_layout_get_context(m_layout.get()),
+                            this);
+
+}
+
+FontInfo::~FontInfo()
+{
+       g_hash_table_remove(s_font_info_for_context,
+                            pango_layout_get_context(m_layout.get()));
+
+       vteunistr i;
+
+#ifdef VTE_DEBUG
+       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+                         "vtepangocairo: %p freeing font_info.  coverages %d = %d + %d + %d\n",
+                         (void*)this,
+                         m_coverage_count[0],
+                         m_coverage_count[1],
+                         m_coverage_count[2],
+                         m_coverage_count[3]);
+#endif
+
+       g_string_free(m_string, true);
+
+       for (i = 0; i < G_N_ELEMENTS(m_ascii_unistr_info); i++)
+               m_ascii_unistr_info[i].~UnistrInfo();
+
+       if (m_other_unistr_info) {
+               g_hash_table_destroy(m_other_unistr_info);
+       }
+}
+
+static GQuark
+fontconfig_timestamp_quark (void)
+{
+       static GQuark quark;
+
+       if (G_UNLIKELY (!quark))
+               quark = g_quark_from_static_string ("vte-fontconfig-timestamp");
+
+       return quark;
+}
+
+static void
+vte_pango_context_set_fontconfig_timestamp (PangoContext *context,
+                                           guint         fontconfig_timestamp)
+{
+       g_object_set_qdata ((GObject *) context,
+                           fontconfig_timestamp_quark (),
+                           GUINT_TO_POINTER (fontconfig_timestamp));
+}
+
+static guint
+vte_pango_context_get_fontconfig_timestamp (PangoContext *context)
+{
+       return GPOINTER_TO_UINT (g_object_get_qdata ((GObject *) context,
+                                                    fontconfig_timestamp_quark ()));
+}
+
+static guint
+context_hash (PangoContext *context)
+{
+       return pango_units_from_double (pango_cairo_context_get_resolution (context))
+            ^ pango_font_description_hash (pango_context_get_font_description (context))
+            ^ cairo_font_options_hash (pango_cairo_context_get_font_options (context))
+            ^ GPOINTER_TO_UINT (pango_context_get_language (context))
+            ^ vte_pango_context_get_fontconfig_timestamp (context);
+}
+
+static gboolean
+context_equal (PangoContext *a,
+              PangoContext *b)
+{
+       return _vte_double_equal(pango_cairo_context_get_resolution(a), pango_cairo_context_get_resolution 
(b))
+           && pango_font_description_equal (pango_context_get_font_description (a), 
pango_context_get_font_description (b))
+           && cairo_font_options_equal (pango_cairo_context_get_font_options (a), 
pango_cairo_context_get_font_options (b))
+           && pango_context_get_language (a) == pango_context_get_language (b)
+           && vte_pango_context_get_fontconfig_timestamp (a) == vte_pango_context_get_fontconfig_timestamp 
(b);
+}
+
+// FIXMEchpe return vte::base::RefPtr<FontInfo>
+/* assumes ownership/reference of context */
+FontInfo*
+FontInfo::find_for_context(vte::glib::RefPtr<PangoContext>& context)
+{
+       if (G_UNLIKELY (s_font_info_for_context == nullptr))
+               s_font_info_for_context = g_hash_table_new((GHashFunc) context_hash, (GEqualFunc) 
context_equal);
+
+       auto info = reinterpret_cast<FontInfo*>(g_hash_table_lookup(s_font_info_for_context, context.get()));
+       if (G_LIKELY(info)) {
+               _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+                                 "vtepangocairo: %p found font_info in cache\n",
+                                 info);
+               info = info->ref();
+       } else {
+                info = new FontInfo{context.release()};
+       }
+
+       return info;
+}
+
+/* assumes ownership/reference of context */
+FontInfo*
+FontInfo::create_for_context(vte::glib::RefPtr<PangoContext> context,
+                             PangoFontDescription const* desc,
+                             PangoLanguage* language,
+                             guint fontconfig_timestamp)
+{
+       if (!PANGO_IS_CAIRO_FONT_MAP(pango_context_get_font_map(context.get()))) {
+               /* Ouch, Gtk+ switched over to some drawing system?
+                * Lets just create one from the default font map.
+                */
+               context = 
vte::glib::take_ref(pango_font_map_create_context(pango_cairo_font_map_get_default()));
+       }
+
+       vte_pango_context_set_fontconfig_timestamp(context.get(), fontconfig_timestamp);
+
+       pango_context_set_base_dir(context.get(), PANGO_DIRECTION_LTR);
+
+       if (desc)
+               pango_context_set_font_description(context.get(), desc);
+
+       pango_context_set_language(context.get(), language);
+
+        /* Make sure our contexts have a font_options set.  We use
+          * this invariant in our context hash and equal functions.
+          */
+        if (!pango_cairo_context_get_font_options(context.get())) {
+                cairo_font_options_t *font_options;
+
+                font_options = cairo_font_options_create ();
+                pango_cairo_context_set_font_options(context.get(), font_options);
+                cairo_font_options_destroy (font_options);
+        }
+
+       return find_for_context(context);
+}
+
+FontInfo*
+FontInfo::create_for_screen(GdkScreen* screen,
+                            PangoFontDescription const* desc,
+                            PangoLanguage* language)
+{
+       auto settings = gtk_settings_get_for_screen(screen);
+       auto fontconfig_timestamp = guint{};
+       g_object_get (settings, "gtk-fontconfig-timestamp", &fontconfig_timestamp, nullptr);
+       return create_for_context(vte::glib::take_ref(gdk_pango_context_get_for_screen(screen)),
+                                  desc, language, fontconfig_timestamp);
+}
+
+FontInfo*
+FontInfo::create_for_widget(GtkWidget* widget,
+                            PangoFontDescription const* desc)
+{
+       auto screen = gtk_widget_get_screen(widget);
+       auto language = pango_context_get_language(gtk_widget_get_pango_context(widget));
+
+       return create_for_screen(screen, desc, language);
+}
+
+FontInfo::UnistrInfo*
+FontInfo::get_unistr_info(vteunistr c)
+{
+       PangoRectangle logical;
+       PangoLayoutLine *line;
+
+       auto uinfo = find_unistr_info(c);
+       if (G_LIKELY (uinfo->coverage() != UnistrInfo::Coverage::UNKNOWN))
+               return uinfo;
+
+       auto ufi = &uinfo->m_ufi;
+
+       g_string_set_size(m_string, 0);
+       _vte_unistr_append_to_string(c, m_string);
+       pango_layout_set_text(m_layout.get(), m_string->str, m_string->len);
+       pango_layout_get_extents(m_layout.get(), NULL, &logical);
+
+       uinfo->width = PANGO_PIXELS_CEIL (logical.width);
+
+       line = pango_layout_get_line_readonly(m_layout.get(), 0);
+
+       uinfo->has_unknown_chars = pango_layout_get_unknown_glyphs_count(m_layout.get()) != 0;
+       /* we use PangoLayoutRun rendering unless there is exactly one run in the line. */
+       if (G_UNLIKELY (!line || !line->runs || line->runs->next))
+       {
+               uinfo->set_coverage(UnistrInfo::Coverage::USE_PANGO_LAYOUT_LINE);
+
+               ufi->using_pango_layout_line.line = pango_layout_line_ref (line);
+               /* we hold a manual reference on layout.  pango currently
+                * doesn't work if line->layout is NULL.  ugh! */
+               pango_layout_set_text(m_layout.get(), "", -1); /* make layout disassociate from the line */
+               ufi->using_pango_layout_line.line->layout = (PangoLayout *)g_object_ref(m_layout.get());
+
+       } else {
+               PangoGlyphItem *glyph_item = (PangoGlyphItem *)line->runs->data;
+               PangoFont *pango_font = glyph_item->item->analysis.font;
+               PangoGlyphString *glyph_string = glyph_item->glyphs;
+
+               /* we use fast cairo path if glyph string has only one real
+                * glyph and at origin */
+               if (!uinfo->has_unknown_chars &&
+                   glyph_string->num_glyphs == 1 && glyph_string->glyphs[0].glyph <= 0xFFFF &&
+                   (glyph_string->glyphs[0].geometry.x_offset |
+                    glyph_string->glyphs[0].geometry.y_offset) == 0)
+               {
+                       cairo_scaled_font_t *scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont 
*) pango_font);
+
+                       if (scaled_font) {
+                               uinfo->set_coverage(UnistrInfo::Coverage::USE_CAIRO_GLYPH);
+
+                               ufi->using_cairo_glyph.scaled_font = cairo_scaled_font_reference 
(scaled_font);
+                               ufi->using_cairo_glyph.glyph_index = glyph_string->glyphs[0].glyph;
+                       }
+               }
+
+               /* use pango fast path otherwise */
+               if (G_UNLIKELY (uinfo->coverage() == UnistrInfo::Coverage::UNKNOWN)) {
+                       uinfo->set_coverage(UnistrInfo::Coverage::USE_PANGO_GLYPH_STRING);
+
+                       ufi->using_pango_glyph_string.font = pango_font ? (PangoFont *)g_object_ref 
(pango_font) : NULL;
+                       ufi->using_pango_glyph_string.glyph_string = pango_glyph_string_copy (glyph_string);
+               }
+       }
+
+       /* release internal layout resources */
+       pango_layout_set_text(m_layout.get(), "", -1);
+
+#ifdef VTE_DEBUG
+       m_coverage_count[0]++;
+       m_coverage_count[uinfo->m_coverage]++;
+#endif
+
+       return uinfo;
+}
+
+} // namespace view
+} // namespace vte
diff --git a/src/fonts-pangocairo.hh b/src/fonts-pangocairo.hh
new file mode 100644
index 00000000..3a30845c
--- /dev/null
+++ b/src/fonts-pangocairo.hh
@@ -0,0 +1,286 @@
+/*
+ * 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 General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <cassert>
+
+#include <glib.h>
+#include <pango/pangocairo.h>
+#include <gtk/gtk.h>
+
+#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(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 = gdk_threads_add_timeout_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* find_for_context(vte::glib::RefPtr<PangoContext>& context);
+        static FontInfo* create_for_context(vte::glib::RefPtr<PangoContext> context,
+                                            PangoFontDescription const* desc,
+                                            PangoLanguage* language,
+                                            guint fontconfig_timestamp);
+        static FontInfo *create_for_screen(GdkScreen* screen,
+                                           PangoFontDescription const* desc,
+                                           PangoLanguage* language);
+public:
+
+        static FontInfo *create_for_widget(GtkWidget* widget,
+                                           PangoFontDescription const* desc);
+
+private:
+        static inline GHashTable* s_font_info_for_context{nullptr};
+
+}; // class FontInfo
+
+} // namespace view
+} // namespace vte
diff --git a/src/fwd.hh b/src/fwd.hh
index 7cdfb741..24d7eea7 100644
--- a/src/fwd.hh
+++ b/src/fwd.hh
@@ -31,4 +31,11 @@ class Widget;
 
 } // namespace platform
 
+namespace view {
+
+class FontInfo;
+class DrawingContext;
+
+} // namespace view
+
 } // namespace vte
diff --git a/src/meson.build b/src/meson.build
index 1fe64810..6cd1b67a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -104,6 +104,8 @@ libvte_common_sources = debug_sources + glib_glue_sources + libc_glue_sources +
   'chunk.hh',
   'color-triple.hh',
   'cxx-utils.hh',
+  'fonts-pangocairo.cc',
+  'fonts-pangocairo.hh',
   'gobject-glue.hh',
   'keymap.cc',
   'keymap.h',
diff --git a/src/vtedraw.cc b/src/vtedraw.cc
index 06c8626d..b5af3d37 100644
--- a/src/vtedraw.cc
+++ b/src/vtedraw.cc
@@ -36,119 +36,9 @@
 
 #include <pango/pangocairo.h>
 
+#include "fonts-pangocairo.hh"
 #include "refptr.hh"
 
-/* Have a space between letters to make sure ligatures aren't used when caching the glyphs: bug 793391. */
-#define VTE_DRAW_SINGLE_WIDE_CHARACTERS        \
-                                       "  ! \" # $ % & ' ( ) * + , - . / " \
-                                       "0 1 2 3 4 5 6 7 8 9 " \
-                                       ": ; < = > ? @ " \
-                                       "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z " \
-                                       "[ \\ ] ^ _ ` " \
-                                       "a b c d e f g h i j k l m n o p q r s t u v w x y z " \
-                                       "{ | } ~ " \
-                                       ""
-
-static inline bool
-_vte_double_equal(double a,
-                  double b)
-{
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-        return a == b;
-#pragma GCC diagnostic pop
-}
-
-/* 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.
- */
-
-
-
-#define FONT_CACHE_TIMEOUT (30) /* seconds */
-
-
-/* All shared data structures are implicitly protected by GDK mutex, because
- * that's how vte.c works and we only get called from there. */
-
-
 /* cairo_show_glyphs accepts runs up to 102 glyphs before it allocates a
  * temporary array.
  *
@@ -187,594 +77,6 @@ _vte_draw_get_undercurl_height(gint width, double line_width)
 namespace vte {
 namespace view {
 
-class FontInfo {
-        friend class DrawingContext;
-
-public:
-        FontInfo(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 = gdk_threads_add_timeout_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* find_for_context(vte::glib::RefPtr<PangoContext>& context);
-        static FontInfo* create_for_context(vte::glib::RefPtr<PangoContext> context,
-                                            PangoFontDescription const* desc,
-                                            PangoLanguage* language,
-                                            guint fontconfig_timestamp);
-        static FontInfo *create_for_screen(GdkScreen* screen,
-                                           PangoFontDescription const* desc,
-                                           PangoLanguage* language);
-public:
-
-        static FontInfo *create_for_widget(GtkWidget* widget,
-                                           PangoFontDescription const* desc);
-
-private:
-        static inline GHashTable* s_font_info_for_context{nullptr};
-
-}; // class FontInfo
-
-FontInfo::UnistrInfo*
-FontInfo::find_unistr_info(vteunistr c)
-{
-       if (G_LIKELY (c < G_N_ELEMENTS(m_ascii_unistr_info)))
-               return &m_ascii_unistr_info[c];
-
-       if (G_UNLIKELY (m_other_unistr_info == nullptr))
-               m_other_unistr_info = g_hash_table_new_full(nullptr, nullptr, nullptr, 
(GDestroyNotify)unistr_info_destroy);
-
-       auto uinfo = reinterpret_cast<UnistrInfo*>(g_hash_table_lookup(m_other_unistr_info, 
GINT_TO_POINTER(c)));
-       if (G_LIKELY (uinfo))
-               return uinfo;
-
-       uinfo = new UnistrInfo{};
-       g_hash_table_insert(m_other_unistr_info, GINT_TO_POINTER (c), uinfo);
-       return uinfo;
-}
-
-void
-FontInfo::cache_ascii()
-{
-       PangoLayoutLine *line;
-       PangoGlyphItemIter iter;
-       PangoGlyphItem *glyph_item;
-       PangoGlyphString *glyph_string;
-       PangoFont *pango_font;
-       cairo_scaled_font_t *scaled_font;
-       const char *text;
-       gboolean more;
-       PangoLanguage *language;
-       gboolean latin_uses_default_language;
-
-       /* We have m_layout holding most ASCII characters.  We want to
-        * cache as much info as we can about the ASCII letters so we don't
-        * have to look them up again later */
-
-       /* Don't cache if unknown glyphs found in layout */
-       if (pango_layout_get_unknown_glyphs_count(m_layout.get()) != 0)
-               return;
-
-       language = pango_context_get_language(pango_layout_get_context(m_layout.get()));
-       if (language == nullptr)
-               language = pango_language_get_default ();
-       latin_uses_default_language = pango_language_includes_script (language, PANGO_SCRIPT_LATIN);
-
-       text = pango_layout_get_text(m_layout.get());
-
-       line = pango_layout_get_line_readonly(m_layout.get(), 0);
-
-       /* Don't cache if more than one font used for the line */
-       if (G_UNLIKELY (!line || !line->runs || line->runs->next))
-               return;
-
-       glyph_item = (PangoGlyphItem *)line->runs->data;
-       glyph_string = glyph_item->glyphs;
-       pango_font = glyph_item->item->analysis.font;
-       if (!pango_font)
-               return;
-       scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *) pango_font);
-       if (!scaled_font)
-               return;
-
-       for (more = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
-            more;
-            more = pango_glyph_item_iter_next_cluster (&iter))
-       {
-               PangoGlyphGeometry *geometry;
-               PangoGlyph glyph;
-               vteunistr c;
-
-               /* Only cache simple clusters */
-               if (iter.start_char +1 != iter.end_char  ||
-                   iter.start_index+1 != iter.end_index ||
-                   iter.start_glyph+1 != iter.end_glyph)
-                       continue;
-
-               c = text[iter.start_index];
-               glyph = glyph_string->glyphs[iter.start_glyph].glyph;
-               geometry = &glyph_string->glyphs[iter.start_glyph].geometry;
-
-               /* If not using the default locale language, only cache non-common
-                * characters as common characters get their font from their neighbors
-                * and we don't want to force Latin on them. */
-               if (!latin_uses_default_language &&
-                    g_unichar_get_script (c) <= G_UNICODE_SCRIPT_INHERITED)
-                       continue;
-
-               /* Only cache simple glyphs */
-               if (!(glyph <= 0xFFFF) || (geometry->x_offset | geometry->y_offset) != 0)
-                       continue;
-
-               auto uinfo = find_unistr_info(c);
-               if (G_UNLIKELY (uinfo->coverage() != UnistrInfo::Coverage::UNKNOWN))
-                       continue;
-
-               auto ufi = &uinfo->m_ufi;
-
-               uinfo->width = PANGO_PIXELS_CEIL (geometry->width);
-               uinfo->has_unknown_chars = false;
-
-               uinfo->set_coverage(UnistrInfo::Coverage::USE_CAIRO_GLYPH);
-
-               ufi->using_cairo_glyph.scaled_font = cairo_scaled_font_reference (scaled_font);
-               ufi->using_cairo_glyph.glyph_index = glyph;
-
-#ifdef VTE_DEBUG
-               m_coverage_count[0]++;
-               m_coverage_count[(unsigned)uinfo->coverage()]++;
-#endif
-       }
-
-#ifdef VTE_DEBUG
-       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
-                         "vtepangocairo: %p cached %d ASCII letters\n",
-                         (void*)this, m_coverage_count[0]);
-#endif
-}
-
-void
-FontInfo::measure_font()
-{
-       PangoRectangle logical;
-
-        /* Measure U+0021..U+007E individually instead of all together and then
-         * averaging. For monospace fonts, the results should be the same, but
-         * if the user (by design, or trough mis-configuration) uses a proportional
-         * font, the latter method will greatly underestimate the required width,
-         * leading to unreadable, overlapping characters.
-         * https://gitlab.gnome.org/GNOME/vte/issues/138
-         */
-        int max_width{1};
-        int max_height{1};
-        for (char c = 0x21; c < 0x7f; ++c) {
-                pango_layout_set_text(m_layout.get(), &c, 1);
-                pango_layout_get_extents(m_layout.get(), nullptr, &logical);
-                max_width = std::max(max_width, PANGO_PIXELS_CEIL(logical.width));
-                max_height = std::max(max_height, PANGO_PIXELS_CEIL(logical.height));
-        }
-
-        /* Use the sample text to get the baseline */
-       pango_layout_set_text(m_layout.get(), VTE_DRAW_SINGLE_WIDE_CHARACTERS, -1);
-       pango_layout_get_extents(m_layout.get(), nullptr, &logical);
-       /* We don't do CEIL for width since we are averaging;
-        * rounding is more accurate */
-       m_ascent = PANGO_PIXELS_CEIL(pango_layout_get_baseline(m_layout.get()));
-
-        m_height = max_height;
-        m_width = max_width;
-
-       /* Now that we shaped the entire ASCII character string, cache glyph
-        * info for them */
-       cache_ascii();
-
-       if (m_height == 0) {
-               m_height = PANGO_PIXELS_CEIL (logical.height);
-       }
-       if (m_ascent == 0) {
-               m_ascent = PANGO_PIXELS_CEIL(pango_layout_get_baseline(m_layout.get()));
-       }
-
-       _vte_debug_print (VTE_DEBUG_MISC,
-                         "vtepangocairo: %p font metrics = %dx%d (%d)\n",
-                         (void*)this, m_width, m_height, m_ascent);
-}
-
-FontInfo::FontInfo(PangoContext *context)
-{
-       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
-                         "vtepangocairo: %p allocating FontInfo\n",
-                         (void*)this);
-
-        // FIXME: placement new
-        memset(m_ascii_unistr_info, 0, sizeof(m_ascii_unistr_info));
-
-       m_layout = vte::glib::take_ref(pango_layout_new(context));
-
-       auto tabs = pango_tab_array_new_with_positions(1, FALSE, PANGO_TAB_LEFT, 1);
-       pango_layout_set_tabs(m_layout.get(), tabs);
-       pango_tab_array_free(tabs);
-
-        // FIXME!!!
-       m_string = g_string_sized_new(VTE_UTF8_BPC+1);
-
-       measure_font();
-
-       g_hash_table_insert(s_font_info_for_context,
-                            pango_layout_get_context(m_layout.get()),
-                            this);
-
-}
-
-FontInfo::~FontInfo()
-{
-       g_hash_table_remove(s_font_info_for_context,
-                            pango_layout_get_context(m_layout.get()));
-
-       vteunistr i;
-
-#ifdef VTE_DEBUG
-       _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
-                         "vtepangocairo: %p freeing font_info.  coverages %d = %d + %d + %d\n",
-                         (void*)this,
-                         m_coverage_count[0],
-                         m_coverage_count[1],
-                         m_coverage_count[2],
-                         m_coverage_count[3]);
-#endif
-
-       g_string_free(m_string, true);
-
-       for (i = 0; i < G_N_ELEMENTS(m_ascii_unistr_info); i++)
-               m_ascii_unistr_info[i].~UnistrInfo();
-
-       if (m_other_unistr_info) {
-               g_hash_table_destroy(m_other_unistr_info);
-       }
-}
-
-static GQuark
-fontconfig_timestamp_quark (void)
-{
-       static GQuark quark;
-
-       if (G_UNLIKELY (!quark))
-               quark = g_quark_from_static_string ("vte-fontconfig-timestamp");
-
-       return quark;
-}
-
-static void
-vte_pango_context_set_fontconfig_timestamp (PangoContext *context,
-                                           guint         fontconfig_timestamp)
-{
-       g_object_set_qdata ((GObject *) context,
-                           fontconfig_timestamp_quark (),
-                           GUINT_TO_POINTER (fontconfig_timestamp));
-}
-
-static guint
-vte_pango_context_get_fontconfig_timestamp (PangoContext *context)
-{
-       return GPOINTER_TO_UINT (g_object_get_qdata ((GObject *) context,
-                                                    fontconfig_timestamp_quark ()));
-}
-
-static guint
-context_hash (PangoContext *context)
-{
-       return pango_units_from_double (pango_cairo_context_get_resolution (context))
-            ^ pango_font_description_hash (pango_context_get_font_description (context))
-            ^ cairo_font_options_hash (pango_cairo_context_get_font_options (context))
-            ^ GPOINTER_TO_UINT (pango_context_get_language (context))
-            ^ vte_pango_context_get_fontconfig_timestamp (context);
-}
-
-static gboolean
-context_equal (PangoContext *a,
-              PangoContext *b)
-{
-       return _vte_double_equal(pango_cairo_context_get_resolution(a), pango_cairo_context_get_resolution 
(b))
-           && pango_font_description_equal (pango_context_get_font_description (a), 
pango_context_get_font_description (b))
-           && cairo_font_options_equal (pango_cairo_context_get_font_options (a), 
pango_cairo_context_get_font_options (b))
-           && pango_context_get_language (a) == pango_context_get_language (b)
-           && vte_pango_context_get_fontconfig_timestamp (a) == vte_pango_context_get_fontconfig_timestamp 
(b);
-}
-
-// FIXMEchpe return vte::base::RefPtr<FontInfo>
-/* assumes ownership/reference of context */
-FontInfo*
-FontInfo::find_for_context(vte::glib::RefPtr<PangoContext>& context)
-{
-       if (G_UNLIKELY (s_font_info_for_context == nullptr))
-               s_font_info_for_context = g_hash_table_new((GHashFunc) context_hash, (GEqualFunc) 
context_equal);
-
-       auto info = reinterpret_cast<FontInfo*>(g_hash_table_lookup(s_font_info_for_context, context.get()));
-       if (G_LIKELY(info)) {
-               _vte_debug_print (VTE_DEBUG_PANGOCAIRO,
-                                 "vtepangocairo: %p found font_info in cache\n",
-                                 info);
-               info = info->ref();
-       } else {
-                info = new FontInfo{context.release()};
-       }
-
-       return info;
-}
-
-/* assumes ownership/reference of context */
-FontInfo*
-FontInfo::create_for_context(vte::glib::RefPtr<PangoContext> context,
-                             PangoFontDescription const* desc,
-                             PangoLanguage* language,
-                             guint fontconfig_timestamp)
-{
-       if (!PANGO_IS_CAIRO_FONT_MAP(pango_context_get_font_map(context.get()))) {
-               /* Ouch, Gtk+ switched over to some drawing system?
-                * Lets just create one from the default font map.
-                */
-               context = 
vte::glib::take_ref(pango_font_map_create_context(pango_cairo_font_map_get_default()));
-       }
-
-       vte_pango_context_set_fontconfig_timestamp(context.get(), fontconfig_timestamp);
-
-       pango_context_set_base_dir(context.get(), PANGO_DIRECTION_LTR);
-
-       if (desc)
-               pango_context_set_font_description(context.get(), desc);
-
-       pango_context_set_language(context.get(), language);
-
-        /* Make sure our contexts have a font_options set.  We use
-          * this invariant in our context hash and equal functions.
-          */
-        if (!pango_cairo_context_get_font_options(context.get())) {
-                cairo_font_options_t *font_options;
-
-                font_options = cairo_font_options_create ();
-                pango_cairo_context_set_font_options(context.get(), font_options);
-                cairo_font_options_destroy (font_options);
-        }
-
-       return find_for_context(context);
-}
-
-FontInfo*
-FontInfo::create_for_screen(GdkScreen* screen,
-                            PangoFontDescription const* desc,
-                            PangoLanguage* language)
-{
-       auto settings = gtk_settings_get_for_screen(screen);
-       auto fontconfig_timestamp = guint{};
-       g_object_get (settings, "gtk-fontconfig-timestamp", &fontconfig_timestamp, nullptr);
-       return create_for_context(vte::glib::take_ref(gdk_pango_context_get_for_screen(screen)),
-                                  desc, language, fontconfig_timestamp);
-}
-
-FontInfo*
-FontInfo::create_for_widget(GtkWidget* widget,
-                            PangoFontDescription const* desc)
-{
-       auto screen = gtk_widget_get_screen(widget);
-       auto language = pango_context_get_language(gtk_widget_get_pango_context(widget));
-
-       return create_for_screen(screen, desc, language);
-}
-
-FontInfo::UnistrInfo*
-FontInfo::get_unistr_info(vteunistr c)
-{
-       PangoRectangle logical;
-       PangoLayoutLine *line;
-
-       auto uinfo = find_unistr_info(c);
-       if (G_LIKELY (uinfo->coverage() != UnistrInfo::Coverage::UNKNOWN))
-               return uinfo;
-
-       auto ufi = &uinfo->m_ufi;
-
-       g_string_set_size(m_string, 0);
-       _vte_unistr_append_to_string(c, m_string);
-       pango_layout_set_text(m_layout.get(), m_string->str, m_string->len);
-       pango_layout_get_extents(m_layout.get(), NULL, &logical);
-
-       uinfo->width = PANGO_PIXELS_CEIL (logical.width);
-
-       line = pango_layout_get_line_readonly(m_layout.get(), 0);
-
-       uinfo->has_unknown_chars = pango_layout_get_unknown_glyphs_count(m_layout.get()) != 0;
-       /* we use PangoLayoutRun rendering unless there is exactly one run in the line. */
-       if (G_UNLIKELY (!line || !line->runs || line->runs->next))
-       {
-               uinfo->set_coverage(UnistrInfo::Coverage::USE_PANGO_LAYOUT_LINE);
-
-               ufi->using_pango_layout_line.line = pango_layout_line_ref (line);
-               /* we hold a manual reference on layout.  pango currently
-                * doesn't work if line->layout is NULL.  ugh! */
-               pango_layout_set_text(m_layout.get(), "", -1); /* make layout disassociate from the line */
-               ufi->using_pango_layout_line.line->layout = (PangoLayout *)g_object_ref(m_layout.get());
-
-       } else {
-               PangoGlyphItem *glyph_item = (PangoGlyphItem *)line->runs->data;
-               PangoFont *pango_font = glyph_item->item->analysis.font;
-               PangoGlyphString *glyph_string = glyph_item->glyphs;
-
-               /* we use fast cairo path if glyph string has only one real
-                * glyph and at origin */
-               if (!uinfo->has_unknown_chars &&
-                   glyph_string->num_glyphs == 1 && glyph_string->glyphs[0].glyph <= 0xFFFF &&
-                   (glyph_string->glyphs[0].geometry.x_offset |
-                    glyph_string->glyphs[0].geometry.y_offset) == 0)
-               {
-                       cairo_scaled_font_t *scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont 
*) pango_font);
-
-                       if (scaled_font) {
-                               uinfo->set_coverage(UnistrInfo::Coverage::USE_CAIRO_GLYPH);
-
-                               ufi->using_cairo_glyph.scaled_font = cairo_scaled_font_reference 
(scaled_font);
-                               ufi->using_cairo_glyph.glyph_index = glyph_string->glyphs[0].glyph;
-                       }
-               }
-
-               /* use pango fast path otherwise */
-               if (G_UNLIKELY (uinfo->coverage() == UnistrInfo::Coverage::UNKNOWN)) {
-                       uinfo->set_coverage(UnistrInfo::Coverage::USE_PANGO_GLYPH_STRING);
-
-                       ufi->using_pango_glyph_string.font = pango_font ? (PangoFont *)g_object_ref 
(pango_font) : NULL;
-                       ufi->using_pango_glyph_string.glyph_string = pango_glyph_string_copy (glyph_string);
-               }
-       }
-
-       /* release internal layout resources */
-       pango_layout_set_text(m_layout.get(), "", -1);
-
-#ifdef VTE_DEBUG
-       m_coverage_count[0]++;
-       m_coverage_count[uinfo->m_coverage]++;
-#endif
-
-       return uinfo;
-}
-
 DrawingContext::~DrawingContext()
 {
         clear_font_cache();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]