[vte: 4/9] Merge vtepangocairo.c into vtedraw.c



commit 7676930c7e1bddd33455a6421fc335027ba50891
Author: Kristian Høgsberg <krh bitplanet net>
Date:   Wed Dec 23 10:31:08 2009 -0500

    Merge vtepangocairo.c into vtedraw.c

 src/Makefile.am     |    1 -
 src/vtedraw.c       | 1218 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/vtepangocairo.c | 1232 ---------------------------------------------------
 3 files changed, 1210 insertions(+), 1241 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index f76bd27..62a9a71 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,7 +65,6 @@ libvte_la_SOURCES = \
 	vtedraw.c \
 	vtedraw.h \
 	vteint.h \
-	vtepangocairo.c \
 	vteregex.c \
 	vteregex.h \
 	vterowdata.c \
diff --git a/src/vtedraw.c b/src/vtedraw.c
index c3dc176..f3a556a 100644
--- a/src/vtedraw.c
+++ b/src/vtedraw.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003,2008 Red Hat, Inc.
  *
  * This is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Library General Public License as published by
@@ -16,18 +16,829 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-/* The interfaces in this file are subject to change at any time. */
-
 
 #include <config.h>
 
-#include <stdio.h>
-#include <stdlib.h>
+#include <sys/param.h>
 #include <string.h>
-#include <glib.h>
 #include <gtk/gtk.h>
+#include <glib.h>
 #include "debug.h"
+#include "vtebg.h"
 #include "vtedraw.h"
+#include "vte-private.h"
+
+#include <pango/pangocairo.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 draw as our private data.  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 by 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 exception case that a single vteunistr uses more than one font
+ *     to be drawn.  This is not expected to happen, but exists for
+ *     completeness, to make sure we can deal with any junk pango decides to
+ *     throw at us.
+ *
+ *
+ * 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.
+ *
+ *     Since we use gdk timeout to schedule the delayed destruction, we also
+ *     add a gtk quit handler which is run when the innermost main loop exits
+ *     to cleanup any pending delayed destructions.
+ *
+ *
+ * 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.
+ *
+ * Setting this to a large value can cause dramatic slow-downs for some
+ * xservers (notably fglrx), see bug #410534.
+ *
+ * Moreover, setting it larger than %VTE_DRAW_MAX_LENGTH is nonsensical,
+ * as the higher layers will not submit runs longer than that value.
+ */
+#define MAX_RUN_LENGTH 100
+
+
+enum unistr_coverage {
+	/* in increasing order of speed */
+	COVERAGE_UNKNOWN = 0,		/* we don't know about the character yet */
+	COVERAGE_USE_PANGO_LAYOUT_LINE,	/* use a PangoLayoutLine for the character */
+	COVERAGE_USE_PANGO_GLYPH_STRING,	/* use a PangoGlyphString for the character */
+	COVERAGE_USE_CAIRO_GLYPH	/* use a cairo_glyph_t for the character */
+};
+
+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;
+};
+
+struct unistr_info {
+	guchar coverage;
+	guchar has_unknown_chars;
+	guint16 width;
+	union unistr_font_info ufi;
+};
+
+static struct unistr_info *
+unistr_info_create (void)
+{
+	return g_slice_new0 (struct unistr_info);
+}
+
+static void
+unistr_info_finish (struct unistr_info *uinfo)
+{
+	union unistr_font_info *ufi = &uinfo->ufi;
+
+	switch (uinfo->coverage) {
+	default:
+	case COVERAGE_UNKNOWN:
+		break;
+	case COVERAGE_USE_PANGO_LAYOUT_LINE:
+		/* we hold a manual reference on layout */
+		g_object_unref (ufi->using_pango_layout_line.line->layout);
+		ufi->using_pango_layout_line.line->layout = NULL;
+		pango_layout_line_unref (ufi->using_pango_layout_line.line);
+		ufi->using_pango_layout_line.line = NULL;
+		break;
+	case COVERAGE_USE_PANGO_GLYPH_STRING:
+		if (ufi->using_pango_glyph_string.font)
+			g_object_unref (ufi->using_pango_glyph_string.font);
+		ufi->using_pango_glyph_string.font = NULL;
+		pango_glyph_string_free (ufi->using_pango_glyph_string.glyph_string);
+		ufi->using_pango_glyph_string.glyph_string = NULL;
+		break;
+	case COVERAGE_USE_CAIRO_GLYPH:
+		cairo_scaled_font_destroy (ufi->using_cairo_glyph.scaled_font);
+		ufi->using_cairo_glyph.scaled_font = NULL;
+		break;
+	}
+}
+
+static void
+unistr_info_destroy (struct unistr_info *uinfo)
+{
+	unistr_info_finish (uinfo);
+	g_slice_free (struct unistr_info, uinfo);
+}
+
+struct font_info {
+	/* lifecycle */
+	int ref_count;
+	guint destroy_timeout; /* only used when ref_count == 0 */
+
+	/* reusable layout set with font and everything set */
+	PangoLayout *layout;
+
+	/* cache of character info */
+	struct unistr_info ascii_unistr_info[128];
+	GHashTable *other_unistr_info;
+
+	/* cell metrics */
+	gint width, height, ascent;
+
+	/* reusable string for UTF-8 conversion */
+	GString *string;
+
+#ifdef VTE_DEBUG
+	/* profiling info */
+	int coverage_count[4];
+#endif
+};
+
+
+static struct unistr_info *
+font_info_find_unistr_info (struct font_info    *info,
+			    vteunistr            c)
+{
+	struct unistr_info *uinfo;
+
+	if (G_LIKELY (c < G_N_ELEMENTS (info->ascii_unistr_info)))
+		return &info->ascii_unistr_info[c];
+
+	if (G_UNLIKELY (info->other_unistr_info == NULL))
+		info->other_unistr_info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) unistr_info_destroy);
+
+	uinfo = g_hash_table_lookup (info->other_unistr_info, GINT_TO_POINTER (c));
+	if (G_LIKELY (uinfo))
+		return uinfo;
+
+	uinfo = unistr_info_create ();
+	g_hash_table_insert (info->other_unistr_info, GINT_TO_POINTER (c), uinfo);
+	return uinfo;
+}
+
+
+static void
+font_info_cache_ascii (struct font_info *info)
+{
+	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 info->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 (info->layout) != 0)
+		return;
+
+	language = pango_context_get_language (pango_layout_get_context (info->layout));
+	if (language == NULL)
+		language = pango_language_get_default ();
+	latin_uses_default_language = pango_language_includes_script (language, PANGO_SCRIPT_LATIN);
+
+	text = pango_layout_get_text (info->layout);
+
+	line = pango_layout_get_line_readonly (info->layout, 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 = 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))
+	{
+		struct unistr_info *uinfo;
+		union unistr_font_info *ufi;
+	 	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 &&
+		    pango_script_for_unichar (c) <= PANGO_SCRIPT_INHERITED)
+			continue;
+
+		/* Only cache simple glyphs */
+		if (!(glyph <= 0xFFFF) || (geometry->x_offset | geometry->y_offset) != 0)
+			continue;
+
+		uinfo = font_info_find_unistr_info (info, c);
+		if (G_UNLIKELY (uinfo->coverage != COVERAGE_UNKNOWN))
+			continue;
+
+		ufi = &uinfo->ufi;
+
+		uinfo->width = PANGO_PIXELS_CEIL (geometry->width);
+		uinfo->has_unknown_chars = FALSE;
+
+		uinfo->coverage = 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
+		info->coverage_count[0]++;
+		info->coverage_count[uinfo->coverage]++;
+#endif
+	}
+
+#ifdef VTE_DEBUG
+	_vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+			  "vtepangocairo: %p cached %d ASCII letters\n",
+			  info, info->coverage_count[0]);
+#endif
+}
+
+static void
+font_info_measure_font (struct font_info *info)
+{
+	PangoRectangle logical;
+
+	/* Estimate for ASCII characters. */
+	pango_layout_set_text (info->layout, VTE_DRAW_SINGLE_WIDE_CHARACTERS, -1);
+	pango_layout_get_extents (info->layout, NULL, &logical);
+	/* We don't do CEIL for width since we are averaging;
+	 * rounding is more accurate */
+	info->width  = PANGO_PIXELS (howmany (logical.width, strlen(VTE_DRAW_SINGLE_WIDE_CHARACTERS)));
+	info->height = PANGO_PIXELS_CEIL (logical.height);
+	info->ascent = PANGO_PIXELS_CEIL (pango_layout_get_baseline (info->layout));
+
+	/* Now that we shaped the entire ASCII character string, cache glyph
+	 * info for them */
+	font_info_cache_ascii (info);
+
+
+	if (info->height == 0) {
+		info->height = PANGO_PIXELS_CEIL (logical.height);
+	}
+	if (info->ascent == 0) {
+		info->ascent = PANGO_PIXELS_CEIL (pango_layout_get_baseline (info->layout));
+	}
+
+	_vte_debug_print (VTE_DEBUG_MISC,
+			  "vtepangocairo: %p font metrics = %dx%d (%d)\n",
+			  info, info->width, info->height, info->ascent);
+}
+
+
+static struct font_info *
+font_info_allocate (PangoContext *context)
+{
+	struct font_info *info;
+
+	info = g_slice_new0 (struct font_info);
+
+	_vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+			  "vtepangocairo: %p allocating font_info\n",
+			  info);
+
+	info->layout = pango_layout_new (context);
+	info->string = g_string_sized_new (VTE_UTF8_BPC+1);
+
+	font_info_measure_font (info);
+
+	return info;
+}
+
+static void
+font_info_free (struct font_info *info)
+{
+	vteunistr i;
+
+#ifdef VTE_DEBUG
+	_vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+			  "vtepangocairo: %p freeing font_info.  coverages %d = %d + %d + %d\n",
+			  info,
+			  info->coverage_count[0],
+			  info->coverage_count[1],
+			  info->coverage_count[2],
+			  info->coverage_count[3]);
+#endif
+
+	g_string_free (info->string, TRUE);
+	g_object_unref (info->layout);
+
+	for (i = 0; i < G_N_ELEMENTS (info->ascii_unistr_info); i++)
+		unistr_info_finish (&info->ascii_unistr_info[i]);
+		
+	if (info->other_unistr_info) {
+		g_hash_table_destroy (info->other_unistr_info);
+	}
+
+	g_slice_free (struct font_info, info);
+}
+
+
+static GHashTable *font_info_for_context;
+static guint quit_id;
+
+static gboolean
+cleanup_delayed_font_info_destroys_predicate (PangoContext *context,
+					      struct font_info *info)
+{
+	if (info->destroy_timeout) {
+		g_source_remove (info->destroy_timeout);
+		info->destroy_timeout = 0;
+
+		font_info_free (info);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+cleanup_delayed_font_info_destroys (void)
+{
+	g_hash_table_foreach_remove (font_info_for_context,
+				     (GHRFunc) cleanup_delayed_font_info_destroys_predicate,
+				     NULL);
+
+	quit_id = 0;
+	return 0;
+}
+
+static void
+ensure_quit_handler (void)
+{
+	if (G_UNLIKELY (quit_id == 0))
+		quit_id = gtk_quit_add (1,
+					(GtkFunction) cleanup_delayed_font_info_destroys,
+					NULL);
+}
+
+static struct font_info *
+font_info_register (struct font_info *info)
+{
+	g_hash_table_insert (font_info_for_context,
+			     pango_layout_get_context (info->layout),
+			     info);
+
+	return info;
+}
+
+static void
+font_info_unregister (struct font_info *info)
+{
+	g_hash_table_remove (font_info_for_context,
+			     pango_layout_get_context (info->layout));
+}
+
+
+static struct font_info *
+font_info_reference (struct font_info *info)
+{
+	if (!info)
+		return info;
+
+	g_return_val_if_fail (info->ref_count >= 0, info);
+
+	if (info->destroy_timeout) {
+		g_source_remove (info->destroy_timeout);
+		info->destroy_timeout = 0;
+	}
+
+	info->ref_count++;
+
+	return info;
+}
+
+static gboolean
+font_info_destroy_delayed (struct font_info *info)
+{
+	info->destroy_timeout = 0;
+
+	font_info_unregister (info);
+	font_info_free (info);
+
+	return FALSE;
+}
+
+static void
+font_info_destroy (struct font_info *info)
+{
+	if (!info)
+		return;
+
+	g_return_if_fail (info->ref_count > 0);
+
+	info->ref_count--;
+	if (info->ref_count)
+		return;
+
+	/* Delay destruction by a few seconds, in case we need it again */
+	ensure_quit_handler ();
+	info->destroy_timeout = gdk_threads_add_timeout_seconds (FONT_CACHE_TIMEOUT,
+								 (GSourceFunc) font_info_destroy_delayed,
+								 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_cairo_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_cairo_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_cairo_get_fontconfig_timestamp (context);
+}
+
+static gboolean
+context_equal (PangoContext *a,
+	       PangoContext *b)
+{
+	return 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_cairo_get_fontconfig_timestamp (a) == vte_pango_cairo_get_fontconfig_timestamp (b);
+}
+
+static struct font_info *
+font_info_find_for_context (PangoContext *context)
+{
+	struct font_info *info;
+
+	if (G_UNLIKELY (font_info_for_context == NULL))
+		font_info_for_context = g_hash_table_new ((GHashFunc) context_hash, (GEqualFunc) context_equal);
+
+	info = g_hash_table_lookup (font_info_for_context, context);
+	if (G_LIKELY (info)) {
+		_vte_debug_print (VTE_DEBUG_PANGOCAIRO,
+				  "vtepangocairo: %p found font_info in cache\n",
+				  info);
+		return font_info_reference (info);
+	}
+
+	info = font_info_allocate (context);
+	info->ref_count = 1;
+	font_info_register (info);
+
+	g_object_unref (context);
+
+	return info;
+}
+
+/* assumes ownership/reference of context */
+static struct font_info *
+font_info_create_for_context (PangoContext               *context,
+			      const PangoFontDescription *desc,
+			      VteTerminalAntiAlias        antialias,
+			      PangoLanguage              *language,
+			      guint                       fontconfig_timestamp)
+{
+	if (!PANGO_IS_CAIRO_FONT_MAP (pango_context_get_font_map (context))) {
+		/* Ouch, Gtk+ switched over to some drawing system?
+		 * Lets just create one from the default font map.
+		 */
+		g_object_unref (context);
+		context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+	}
+
+	vte_pango_cairo_set_fontconfig_timestamp (context, fontconfig_timestamp);
+
+	pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
+
+	if (desc)
+		pango_context_set_font_description (context, desc);
+
+	pango_context_set_language (context, language);
+
+	switch (antialias) {
+		cairo_font_options_t *font_options;
+		cairo_antialias_t cr_aa;
+
+	case VTE_ANTI_ALIAS_FORCE_ENABLE:
+	case VTE_ANTI_ALIAS_FORCE_DISABLE:
+
+		if (antialias == VTE_ANTI_ALIAS_FORCE_ENABLE)
+			cr_aa = CAIRO_ANTIALIAS_DEFAULT; /* let surface decide between gray and subpixel */
+		else
+			cr_aa = CAIRO_ANTIALIAS_NONE;
+
+		font_options = cairo_font_options_copy (pango_cairo_context_get_font_options (context));
+		cairo_font_options_set_antialias (font_options, cr_aa);
+		pango_cairo_context_set_font_options (context, font_options);
+		cairo_font_options_destroy (font_options);
+
+		break;
+
+	default:
+	case VTE_ANTI_ALIAS_USE_DEFAULT:
+		/* 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)) {
+			font_options = cairo_font_options_create ();
+			pango_cairo_context_set_font_options (context, font_options);
+			cairo_font_options_destroy (font_options);
+		}
+		break;
+	}
+
+	return font_info_find_for_context (context);
+}
+
+static struct font_info *
+font_info_create_for_screen (GdkScreen                  *screen,
+			     const PangoFontDescription *desc,
+			     VteTerminalAntiAlias        antialias,
+			     PangoLanguage              *language)
+{
+	GtkSettings *settings = gtk_settings_get_for_screen (screen);
+	int fontconfig_timestamp;
+	g_object_get (settings, "gtk-fontconfig-timestamp", &fontconfig_timestamp, NULL);
+	return font_info_create_for_context (gdk_pango_context_get_for_screen (screen),
+					     desc, antialias, language, fontconfig_timestamp);
+}
+
+static struct font_info *
+font_info_create_for_widget (GtkWidget                  *widget,
+			     const PangoFontDescription *desc,
+			     VteTerminalAntiAlias        antialias)
+{
+	GdkScreen *screen = gtk_widget_get_screen (widget);
+	PangoLanguage *language = pango_context_get_language (gtk_widget_get_pango_context (widget));
+
+	return font_info_create_for_screen (screen, desc, antialias, language);
+}
+
+static struct unistr_info *
+font_info_get_unistr_info (struct font_info *info,
+			   vteunistr c)
+{
+	struct unistr_info *uinfo;
+	union unistr_font_info *ufi;
+	PangoRectangle logical;
+	PangoLayoutLine *line;
+
+	uinfo = font_info_find_unistr_info (info, c);
+	if (G_LIKELY (uinfo->coverage != COVERAGE_UNKNOWN))
+		return uinfo;
+
+	ufi = &uinfo->ufi;
+
+	g_string_set_size (info->string, 0);
+	_vte_unistr_append_to_string (c, info->string);
+	pango_layout_set_text (info->layout, info->string->str, -1);
+	pango_layout_get_extents (info->layout, NULL, &logical);
+
+	uinfo->width = PANGO_PIXELS_CEIL (logical.width);
+
+	line = pango_layout_get_line_readonly (info->layout, 0);
+
+	uinfo->has_unknown_chars = pango_layout_get_unknown_glyphs_count (info->layout) != 0;
+	/* we use PangoLayoutRun rendering unless there is exactly one run in the line. */
+	if (G_UNLIKELY (!line || !line->runs || line->runs->next))
+	{
+		uinfo->coverage = 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 (info->layout, "", -1); /* make layout disassociate from the line */
+		ufi->using_pango_layout_line.line->layout = g_object_ref (info->layout);
+
+	} else {
+		PangoGlyphItem *glyph_item = 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->coverage = 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 == COVERAGE_UNKNOWN)) {
+			uinfo->coverage = COVERAGE_USE_PANGO_GLYPH_STRING;
+
+			ufi->using_pango_glyph_string.font = pango_font ? 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 (info->layout, "", -1);
+
+#ifdef VTE_DEBUG
+	info->coverage_count[0]++;
+	info->coverage_count[uinfo->coverage]++;
+#endif
+
+	return uinfo;
+}
+
+
+struct _vte_pangocairo_data {
+	struct font_info *font;
+	struct font_info *font_bold;
+	cairo_pattern_t *bg_pattern;
+
+	cairo_t *cr;
+};
+
+const char impl_name[] = "pangocairo";
+
+struct _vte_draw *
+_vte_draw_new (GtkWidget *widget)
+{
+	struct _vte_draw *draw;
+	struct _vte_pangocairo_data *data;
+
+	/* Create the structure. */
+	draw = g_slice_new0 (struct _vte_draw);
+	draw->widget = g_object_ref (widget);
+	draw->requires_clear = FALSE;
+
+	_vte_debug_print (VTE_DEBUG_DRAW,
+			"draw_new (%s)\n", impl_name);
+	_vte_debug_print (VTE_DEBUG_MISC, "Using %s.\n", impl_name);
+
+	data = g_slice_new0 (struct _vte_pangocairo_data);
+	draw->impl_data = data;
+
+	return draw;
+}
+
+void
+_vte_draw_free (struct _vte_draw *draw)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_free\n");
+
+	if (data->bg_pattern != NULL) {
+		cairo_pattern_destroy (data->bg_pattern);
+		data->bg_pattern = NULL;
+	}
+
+	if (data->font != NULL) {
+		font_info_destroy (data->font);
+		data->font = NULL;
+	}
+
+	g_slice_free (struct _vte_pangocairo_data, draw->impl_data);
+	draw->impl_data = NULL;
+
+	if (draw->widget != NULL) {
+		g_object_unref (draw->widget);
+	}
+
+	g_slice_free (struct _vte_draw, draw);
+}
 
 GdkVisual *
 _vte_draw_get_visual (struct _vte_draw *draw)
@@ -53,10 +864,352 @@ _vte_draw_get_colormap (struct _vte_draw *draw, gboolean maybe_use_default)
 	return colormap;
 }
 
+void
+_vte_draw_start (struct _vte_draw *draw)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	g_return_if_fail (GTK_WIDGET_REALIZED (draw->widget));
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_start\n");
+
+	g_object_ref (draw->widget->window);
+
+	data->cr = gdk_cairo_create (draw->widget->window);
+	draw->started = TRUE;
+}
+
+void
+_vte_draw_end (struct _vte_draw *draw)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	g_return_if_fail (draw->started == TRUE);
+
+	if (data->cr != NULL) {
+		cairo_destroy (data->cr);
+		data->cr = NULL;
+	}
+
+	g_object_unref (draw->widget->window);
+
+	draw->started = FALSE;
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_end\n");
+}
+
+void
+_vte_draw_set_background_solid(struct _vte_draw *draw,
+			       GdkColor *color,
+			       guint16 opacity)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	draw->requires_clear = opacity != 0xFFFF;
+
+	if (data->bg_pattern)
+		cairo_pattern_destroy (data->bg_pattern);
+
+	data->bg_pattern = cairo_pattern_create_rgba (color->red / 65535.,
+						      color->green / 65535.,
+						      color->blue / 65535.,
+						      opacity / 65535.);
+}
+
+void
+_vte_draw_set_background_image (struct _vte_draw *draw,
+			        enum VteBgSourceType type,
+			        GdkPixbuf *pixbuf,
+			        const char *filename,
+			        const GdkColor *color,
+			        double saturation)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	GdkPixmap *pixmap;
+	cairo_surface_t *surface;
+	cairo_t *cr;
+
+	if (type != VTE_BG_SOURCE_NONE)
+		draw->requires_clear = TRUE;
+
+	pixmap = vte_bg_get_pixmap (vte_bg_get_for_screen (gtk_widget_get_screen (draw->widget)),
+				    type, pixbuf, filename,
+				    color, saturation,
+				    _vte_draw_get_colormap(draw, TRUE));
+
+	if (!pixmap)
+		return;
+
+	if (data->bg_pattern)
+		cairo_pattern_destroy (data->bg_pattern);
+
+	/* Ugh... We need to create a dummy cairo_t */
+	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
+	cr = cairo_create (surface);
+
+	gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0);
+	data->bg_pattern = cairo_pattern_reference (cairo_get_source (cr));
+
+	cairo_destroy (cr);
+	cairo_surface_destroy (surface);
+
+	/* Transfer the pixmap ownership to the pattern */
+	cairo_pattern_set_user_data (data->bg_pattern,
+				     (cairo_user_data_key_t *) data,
+				     pixmap,
+				     (cairo_destroy_func_t) g_object_unref);
+
+	cairo_pattern_set_extend (data->bg_pattern, CAIRO_EXTEND_REPEAT);
+}
+
+void
+_vte_draw_set_background_scroll (struct _vte_draw *draw,
+				 gint x, gint y)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	cairo_matrix_t matrix;
+
+	_vte_debug_print (VTE_DEBUG_DRAW,
+			"draw_set_scroll (%d, %d)\n",
+			x, y);
+
+	g_return_if_fail (data->bg_pattern != NULL);
+
+	cairo_matrix_init_translate (&matrix, x, y);
+	cairo_pattern_set_matrix (data->bg_pattern, &matrix);
+}
+
 gboolean
-_vte_draw_requires_clear (struct _vte_draw *draw)
+_vte_draw_clip (struct _vte_draw *draw, GdkRegion *region)
 {
-	return draw->requires_clear;
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_clip\n");
+	gdk_cairo_region(data->cr, region);
+	cairo_clip (data->cr);
+
+	return TRUE;
+}
+
+void
+_vte_draw_clear (struct _vte_draw *draw, gint x, gint y, gint width, gint height)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	g_return_if_fail (data->bg_pattern != NULL);
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_clear (%d, %d, %d, %d)\n",
+			  x,y,width, height);
+
+	cairo_rectangle (data->cr, x, y, width, height);
+	cairo_set_operator (data->cr, CAIRO_OPERATOR_SOURCE);
+	cairo_set_source (data->cr, data->bg_pattern);
+	cairo_fill (data->cr);
+}
+
+void
+_vte_draw_set_text_font (struct _vte_draw *draw,
+			const PangoFontDescription *fontdesc,
+			VteTerminalAntiAlias antialias)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	PangoFontDescription *bolddesc = NULL;
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_set_text_font (aa=%d)\n",
+			  antialias);
+
+	if (data->font_bold != data->font)
+		font_info_destroy (data->font_bold);
+	font_info_destroy (data->font);
+	data->font = font_info_create_for_widget (draw->widget, fontdesc, antialias);
+
+	/* calculate bold font desc */
+	bolddesc = pango_font_description_copy (fontdesc);
+	pango_font_description_set_weight (bolddesc, PANGO_WEIGHT_BOLD);
+
+	data->font_bold = font_info_create_for_widget (draw->widget, bolddesc, antialias);
+	pango_font_description_free (bolddesc);
+
+	/* Decide if we should keep this bold font face, per bug 54926:
+	 *  - reject bold font if it is not within 10% of normal font width
+	 */
+	if ( abs((data->font_bold->width * 100 / data->font->width) - 100) > 10 ) {
+		font_info_destroy (data->font_bold);
+		data->font_bold = data->font;
+	}
+}
+
+void
+_vte_draw_get_text_metrics(struct _vte_draw *draw,
+			   gint *width, gint *height, gint *ascent)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	
+	g_return_if_fail (data->font != NULL);
+
+	if (width)
+		*width  = data->font->width;
+	if (height)
+		*height = data->font->height;
+	if (ascent)
+		*ascent = data->font->ascent;
+}
+
+
+int
+_vte_draw_get_char_width (struct _vte_draw *draw, vteunistr c, int columns,
+			  gboolean bold)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	struct unistr_info *uinfo;
+
+	g_return_val_if_fail (data->font != NULL, 0);
+
+	uinfo = font_info_get_unistr_info (bold ? data->font_bold : data->font, c);
+	return uinfo->width;
+}
+
+static gboolean
+_vte_pangocairo_has_bold (struct _vte_draw *draw)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	return (data->font != data->font_bold);
+}
+
+static void
+set_source_color_alpha (cairo_t        *cr,
+			const GdkColor *color,
+			guchar alpha)
+{
+	cairo_set_source_rgba (cr,
+			      color->red / 65535.,
+			      color->green / 65535.,
+			      color->blue / 65535.,
+			      alpha / 255.);
+}
+
+static void
+_vte_pangocairo_draw_text (struct _vte_draw *draw,
+			   struct _vte_draw_text_request *requests, gsize n_requests,
+			   GdkColor *color, guchar alpha, gboolean bold)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	gsize i;
+	cairo_scaled_font_t *last_scaled_font = NULL;
+	int n_cr_glyphs = 0;
+	cairo_glyph_t cr_glyphs[MAX_RUN_LENGTH];
+	struct font_info *font = bold ? data->font_bold : data->font;
+
+	g_return_if_fail (font != NULL);
+
+	set_source_color_alpha (data->cr, color, alpha);
+	cairo_set_operator (data->cr, CAIRO_OPERATOR_OVER);
+
+	for (i = 0; i < n_requests; i++) {
+		vteunistr c = requests[i].c;
+		int x = requests[i].x;
+		int y = requests[i].y + font->ascent;
+		struct unistr_info *uinfo = font_info_get_unistr_info (font, c);
+		union unistr_font_info *ufi = &uinfo->ufi;
+
+		switch (uinfo->coverage) {
+		default:
+		case COVERAGE_UNKNOWN:
+			g_assert_not_reached ();
+			break;
+		case COVERAGE_USE_PANGO_LAYOUT_LINE:
+			cairo_move_to (data->cr, x, y);
+			pango_cairo_show_layout_line (data->cr,
+						      ufi->using_pango_layout_line.line);
+			break;
+		case COVERAGE_USE_PANGO_GLYPH_STRING:
+			cairo_move_to (data->cr, x, y);
+			pango_cairo_show_glyph_string (data->cr,
+						       ufi->using_pango_glyph_string.font,
+						       ufi->using_pango_glyph_string.glyph_string);
+			break;
+		case COVERAGE_USE_CAIRO_GLYPH:
+			if (last_scaled_font != ufi->using_cairo_glyph.scaled_font || n_cr_glyphs == MAX_RUN_LENGTH) {
+				if (n_cr_glyphs) {
+					cairo_set_scaled_font (data->cr, last_scaled_font);
+					cairo_show_glyphs (data->cr,
+							   cr_glyphs,
+							   n_cr_glyphs);
+					n_cr_glyphs = 0;
+				}
+				last_scaled_font = ufi->using_cairo_glyph.scaled_font;
+			}
+			cr_glyphs[n_cr_glyphs].index = ufi->using_cairo_glyph.glyph_index;
+			cr_glyphs[n_cr_glyphs].x = x;
+			cr_glyphs[n_cr_glyphs].y = y;
+			n_cr_glyphs++;
+			break;
+		}
+	}
+	if (n_cr_glyphs) {
+		cairo_set_scaled_font (data->cr, last_scaled_font);
+		cairo_show_glyphs (data->cr,
+				   cr_glyphs,
+				   n_cr_glyphs);
+		n_cr_glyphs = 0;
+	}
+}
+
+void
+_vte_draw_text (struct _vte_draw *draw,
+	       struct _vte_draw_text_request *requests, gsize n_requests,
+	       GdkColor *color, guchar alpha, gboolean bold)
+{
+	g_return_if_fail (draw->started == TRUE);
+
+	if (_vte_debug_on (VTE_DEBUG_DRAW)) {
+		GString *string = g_string_new ("");
+		gchar *str;
+		gsize n;
+		for (n = 0; n < n_requests; n++) {
+			g_string_append_unichar (string, requests[n].c);
+		}
+		str = g_string_free (string, FALSE);
+		g_printerr ("draw_text (\"%s\", len=%"G_GSIZE_FORMAT", color=(%d,%d,%d,%d), %s)\n",
+				str, n_requests, color->red, color->green, color->blue,
+				alpha, bold ? "bold" : "normal");
+		g_free (str);
+	}
+
+	_vte_pangocairo_draw_text (draw, requests,
+				   n_requests, color, alpha, bold);
+
+	/* handle fonts that lack a bold face by double-striking */
+	if (bold && !_vte_pangocairo_has_bold (draw)) {
+		gsize i;
+
+		/* Take a step to the right. */
+		for (i = 0; i < n_requests; i++) {
+			requests[i].x++;
+		}
+		_vte_pangocairo_draw_text (draw, requests,
+					   n_requests, color, alpha, FALSE);
+		/* Now take a step back. */
+		for (i = 0; i < n_requests; i++) {
+			requests[i].x--;
+		}
+	}
+}
+
+gboolean
+_vte_draw_has_char (struct _vte_draw *draw, vteunistr c, gboolean bold)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+	struct unistr_info *uinfo;
+
+	_vte_debug_print (VTE_DEBUG_DRAW, "draw_has_char ('0x%04X', %s)\n", c,
+			  bold ? "bold" : "normal");
+
+	g_return_val_if_fail (data->font != NULL, FALSE);
+
+	uinfo = font_info_get_unistr_info (bold ? data->font_bold : data->font, c);
+	return !uinfo->has_unknown_chars;
 }
 
 gboolean
@@ -78,3 +1231,52 @@ _vte_draw_char (struct _vte_draw *draw,
 
 	return has_char;
 }
+
+void
+_vte_draw_draw_rectangle (struct _vte_draw *draw,
+			 gint x, gint y, gint width, gint height,
+			 GdkColor *color, guchar alpha)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	g_return_if_fail (draw->started == TRUE);
+
+	_vte_debug_print (VTE_DEBUG_DRAW,
+			"draw_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%d))\n",
+			x,y,width,height,
+			color->red, color->green, color->blue,
+			alpha);
+
+	cairo_set_operator (data->cr, CAIRO_OPERATOR_OVER);
+	cairo_rectangle (data->cr, x+.5, y+.5, width-1, height-1);
+	set_source_color_alpha (data->cr, color, alpha);
+	cairo_set_line_width (data->cr, 1);
+	cairo_stroke (data->cr);
+}
+
+void
+_vte_draw_fill_rectangle (struct _vte_draw *draw,
+			 gint x, gint y, gint width, gint height,
+			 GdkColor *color, guchar alpha)
+{
+	struct _vte_pangocairo_data *data = draw->impl_data;
+
+	g_return_if_fail (draw->started == TRUE);
+
+	_vte_debug_print (VTE_DEBUG_DRAW,
+			"draw_fill_rectangle (%d, %d, %d, %d, color=(%d,%d,%d,%d))\n",
+			x,y,width,height,
+			color->red, color->green, color->blue,
+			alpha);
+
+	cairo_set_operator (data->cr, CAIRO_OPERATOR_OVER);
+	cairo_rectangle (data->cr, x, y, width, height);
+	set_source_color_alpha (data->cr, color, alpha);
+	cairo_fill (data->cr);
+}
+
+gboolean
+_vte_draw_requires_clear (struct _vte_draw *draw)
+{
+	return draw->requires_clear;
+}



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