ibus/ibus-xx-emoji-harfbuzz.patch

1648 lines
56 KiB
Diff
Raw Normal View History

From c5e3a76dc92ea967b138d43dc9ed7ecdb2e3fc7a Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 14 Sep 2017 15:55:21 +0900
Subject: [PATCH] Integrate custom rendering to use HarfBuzz glyph info
IBusFontSet offers FcFontSet, glyph info with HarfBuzz and rendering
on Cairo context.
Current Pango changes fonts by emoji variants and draws the separated
glyphs [1] but actually the emoji characters with variants can be drawn
as one glyph so this class manages Fontconfig fontsets to select a font,
HarfBuzz to get glyphs for emoji variants, Cairo to draw glyphs.
Need configure --enable-harfbuzz-for-emoji option to enable this feature.
[1]: https://bugzilla.gnome.org/show_bug.cgi?id=780669
https://bugzilla.gnome.org/show_bug.cgi?id=781123
---
bindings/vala/IBusFontSet-1.0.metadata | 1 +
bindings/vala/Makefile.am | 83 +++
bindings/vala/ibus-fontset-1.0.deps | 1 +
configure.ac | 29 +
ui/gtk3/Makefile.am | 32 ++
ui/gtk3/emojier.vala | 100 +++-
ui/gtk3/ibusfontset.c | 932 +++++++++++++++++++++++++++++++++
ui/gtk3/ibusfontset.h | 302 +++++++++++
8 files changed, 1478 insertions(+), 2 deletions(-)
create mode 100644 bindings/vala/IBusFontSet-1.0.metadata
create mode 100644 bindings/vala/ibus-fontset-1.0.deps
create mode 100644 ui/gtk3/ibusfontset.c
create mode 100644 ui/gtk3/ibusfontset.h
diff --git a/bindings/vala/IBusFontSet-1.0.metadata b/bindings/vala/IBusFontSet-1.0.metadata
new file mode 100644
index 00000000..73037d7f
--- /dev/null
+++ b/bindings/vala/IBusFontSet-1.0.metadata
@@ -0,0 +1 @@
+IBusFontSet cheader_filename="ibusfontset.h"
diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am
index fc8e2f01..f7b9e97a 100644
--- a/bindings/vala/Makefile.am
+++ b/bindings/vala/Makefile.am
@@ -83,8 +83,10 @@ EXTRA_DIST = \
IBus-1.0.metadata \
IBus-1.0-custom.vala \
IBusEmojiDialog-1.0.metadata \
+ IBusFontSet-1.0.metadata \
ibus-1.0.deps \
ibus-emoji-dialog-1.0.deps \
+ ibus-fontset-1.0.deps \
config.vapi \
xi.vapi \
$(NULL)
@@ -131,6 +133,15 @@ libibus_emoji_dialog_1_0_la_LDFLAGS = \
if test ! -f $@ ; then \
$(LN_S) $(top_srcdir)/ui/gtk3/$@ .; \
fi;
+ibusfontset.c: $(ibus_vapi) ibusfontset.h
+ if test ! -f $@ ; then \
+ $(LN_S) $(top_srcdir)/ui/gtk3/$@ .; \
+ fi;
+ibusfontset.h: $(ibus_vapi)
+ if test ! -f $@ ; then \
+ $(LN_S) $(top_srcdir)/ui/gtk3/$@ .; \
+ fi;
+
MAINTAINERCLEANFILES += $(libibus_emoji_dialog_1_0_la_SOURCES)
DISTCLEANFILES += $(libibus_emoji_dialog_1_0_la_SOURCES)
@@ -184,6 +195,78 @@ DISTCLEANFILES += $(ibus_emoji_dialog_vapi)
endif
#end of HAVE_INTROSPECTION
+
+
+if ENABLE_HARFBUZZ_FOR_EMOJI
+libibus_fontset = libibus-fontset-1.0.la
+noinst_LTLIBRARIES += $(libibus_fontset)
+
+libibus_fontset_1_0_la_SOURCES = \
+ ibusfontset.c \
+ $(NULL)
+libibus_fontset_1_0_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ @CAIRO_CFLAGS@ \
+ @FONTCONFIG_CFLAGS@ \
+ @GLIB2_CFLAGS@ \
+ @HARFBUZZ_CFLAGS@ \
+ @PANGO_CFLAGS@ \
+ $(NULL)
+libibus_fontset_1_0_la_LIBADD = \
+ @CAIRO_LIBS@ \
+ @FONTCONFIG_LIBS@ \
+ @GLIB2_LIBS@ \
+ @HARFBUZZ_LIBS@ \
+ @PANGO_LIBS@ \
+ $(NULL)
+libibus_fontset_1_0_la_LDFLAGS = \
+ -no-undefined \
+ -export-symbols-regex "ibus_.*" \
+ $(NULL)
+
+MAINTAINERCLEANFILES += ibusfontset.c ibusfontset.h
+DISTCLEANFILES += ibusfontset.c ibusfontset.h
+
+if HAVE_INTROSPECTION
+IBusFontSet-1.0.gir: $(libibus_fontset) Makefile
+IBusFontSet_1_0_gir_SCANNERFLAGS = \
+ --pkg-export=ibus-1.0 \
+ --pkg=cairo \
+ --pkg=fontconfig \
+ --pkg=harfbuzz \
+ $(IBUS_GIR_SCANNERFLAGS) \
+ $(NULL)
+IBusFontSet_1_0_gir_LIBS = $(libibus_fontset) $(libibus)
+IBusFontSet_1_0_gir_INCLUDES = cairo-1.0 GLib-2.0 GObject-2.0
+IBusFontSet_1_0_gir_FILES = \
+ ibusfontset.h \
+ $(NULL)
+IBusFontSet_1_0_gir_CFLAGS = \
+ -I$(srcdir) \
+ -I$(builddir) \
+ -I$(top_srcdir)/src \
+ $(NULL)
+ibus_fontset_gir = IBusFontSet-1.0.gir
+INTROSPECTION_GIRS += $(ibus_fontset_gir)
+noinst_DATA += $(ibus_fontset_gir)
+EXTRA_DIST += $(ibus_fontset_gir)
+MAINTAINERCLEANFILES += $(ibus_fontset_gir)
+DISTCLEANFILES += $(ibus_fontset_gir)
+
+ibus-fontset-1.0.vapi: $(ibus_fontset_gir) IBusFontSet-1.0.metadata
+ibus_fontset_vapi = ibus-fontset-1.0.vapi
+ibus_fontset_1_0_vapi_METADATADIRS = $(srcdir)
+ibus_fontset_1_0_vapi_FILES = IBusFontSet-1.0.gir
+VAPIGEN_VAPIS += $(ibus_fontset_vapi)
+noinst_DATA += $(ibus_fontset_vapi)
+EXTRA_DIST += $(ibus_fontset_vapi)
+MAINTAINERCLEANFILES += $(ibus_fontset_vapi)
+DISTCLEANFILES += $(ibus_fontset_vapi)
+
+endif
+# end of HAVE_INTROSPECTION
+endif
+# end of ENABLE_HARFBUZZ_FOR_EMOJI
endif
# end of ENABLE_EMOJI_DICT
diff --git a/bindings/vala/ibus-fontset-1.0.deps b/bindings/vala/ibus-fontset-1.0.deps
new file mode 100644
index 00000000..129fe166
--- /dev/null
+++ b/bindings/vala/ibus-fontset-1.0.deps
@@ -0,0 +1 @@
+cairo
diff --git a/configure.ac b/configure.ac
index 14556a3a..6ff8f4a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -653,6 +653,34 @@ https://github.com/fujiwarat/cldr-emoji-annotation)
enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
fi
+AC_ARG_ENABLE(harfbuzz-for-emoji,
+ AS_HELP_STRING([--enable-harfbuzz-for-emoji],
+ [Enable HarBuzz to draw emoji characters.
+ Current Pango has a problem to draw emoji variants and
+ this way enables to use HarfBuzz directly in GtkLabel.]),
+ [enable_harfbuzz_for_emoji=$enableval],
+ [enable_harfbuzz_for_emoji=no]
+)
+AM_CONDITIONAL([ENABLE_HARFBUZZ_FOR_EMOJI],
+ [test x"$enable_harfbuzz_for_emoji" = x"yes"])
+
+if test x"$enable_harfbuzz_for_emoji" = x"yes"; then
+ PKG_CHECK_MODULES(CAIRO, [
+ cairo
+ ])
+ PKG_CHECK_MODULES(FONTCONFIG, [
+ fontconfig
+ ])
+ PKG_CHECK_MODULES(HARFBUZZ, [
+ harfbuzz
+ ])
+ PKG_CHECK_MODULES(PANGO, [
+ pango
+ ])
+else
+ enable_harfbuzz_for_emoji="no (disabled, use --enable-harfbuzz-for-emoji to enable)"
+fi
+
# Check iso-codes.
PKG_CHECK_MODULES(ISOCODES, [
iso-codes
@@ -743,6 +771,7 @@ Build options:
Enable Emoji dict $enable_emoji_dict
Unicode Emoji directory $UNICODE_EMOJI_DIR
CLDR annotation directory $EMOJI_ANNOTATION_DIR
+ Enable HarfBuzz for Emoji $enable_harfbuzz_for_emoji
Run test cases $enable_tests
])
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 786b80e6..cd1e9c2c 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -156,6 +156,8 @@ EXTRA_DIST = \
$(man_seven_in_files) \
emojierapp.vala \
gtkpanel.xml.in \
+ ibusfontset.c \
+ ibusfontset.h \
notification-item.xml \
notification-watcher.xml \
$(NULL)
@@ -198,6 +200,36 @@ emojierapp.o: $(srcdir)/emojierapp.c
$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
$(NULL)
+if ENABLE_HARFBUZZ_FOR_EMOJI
+ibus_ui_gtk3_SOURCES += \
+ ibusfontset.c \
+ $(NULL)
+
+ibus_ui_emojier_SOURCES += \
+ ibusfontset.c \
+ $(NULL)
+
+AM_CFLAGS += \
+ @CAIRO_CFLAGS@ \
+ @FONTCONFIG_CFLAGS@ \
+ @HARFBUZZ_CFLAGS@ \
+ $(NULL)
+
+AM_LDADD += \
+ @CAIRO_LIBS@ \
+ @FONTCONFIG_LIBS@ \
+ @HARFBUZZ_LIBS@ \
+ $(NULL)
+
+AM_VALAFLAGS += \
+ -D ENABLE_HARFBUZZ_FOR_EMOJI \
+ --pkg=cairo \
+ --pkg=ibus-fontset-1.0 \
+ $(NULL)
+
+endif
+# end of ENABLE_HARFBUZZ_FOR_EMOJI
+
man_seven_files = $(man_seven_in_files:.7.in=.7)
man_seven_DATA =$(man_seven_files:.7=.7.gz)
man_sevendir = $(mandir)/man7
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 36ab4bab..7d5116fe 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -80,6 +80,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
}
}
private class EWhiteLabel : Gtk.Label {
+#if ENABLE_HARFBUZZ_FOR_EMOJI
+ IBus.RequisitionEx m_requisition;
+#endif
public EWhiteLabel(string text) {
GLib.Object(
name : "IBusEmojierWhiteLabel"
@@ -87,8 +90,78 @@ class IBusEmojier : Gtk.ApplicationWindow {
if (text != "")
set_label(text);
}
+#if ENABLE_HARFBUZZ_FOR_EMOJI
+ private void get_preferred_size_with_hb(out int minimum_width,
+ out int natural_width,
+ out int minimum_height,
+ out int natural_height) {
+ minimum_width = 0;
+ natural_width = 0;
+ minimum_height = 0;
+ natural_height = 0;
+ var text = this.get_text();
+ if (text == null || text == "")
+ return;
+ var context = this.get_pango_context();
+ var language = context.get_language();
+ update_fontset(language);
+ Cairo.RectangleInt widest = Cairo.RectangleInt();
+ m_requisition = m_fontset.get_preferred_size_hb(text, out widest);
+ minimum_width = widest.width;
+ natural_width = widest.width;
+ minimum_height = widest.height;
+ natural_height = widest.height;
+ }
+ public override void get_preferred_width(out int minimum_width,
+ out int natural_width) {
+ get_preferred_size_with_hb(out minimum_width,
+ out natural_width,
+ null, null);
+ }
+ public override void get_preferred_height(out int minimum_height,
+ out int natural_height) {
+ get_preferred_size_with_hb(null, null,
+ out minimum_height,
+ out natural_height);
+ }
+ public override bool draw(Cairo.Context cr) {
+ if (m_fontset == null)
+ return true;
+ if (m_requisition == null)
+ return true;
+ if (m_requisition.cairo_lines == null)
+ return true;
+ var style_context = get_style_context();
+ Gtk.Allocation allocation;
+ get_allocation(out allocation);
+ style_context.render_background(cr,
+ 0, 0,
+ allocation.width,
+ allocation.height);
+ Gdk.RGBA *normal_fg = null;
+ style_context.get(Gtk.StateFlags.NORMAL,
+ "color",
+ out normal_fg);
+ cr.set_operator(Cairo.Operator.OVER);
+ cr.set_source_rgba(normal_fg.red, normal_fg.green, normal_fg.blue,
+ normal_fg.alpha);
+ cr.save();
+ double x = 0.0;
+ double y = 0.0;
+ if (allocation.width > m_requisition.width)
+ x = (allocation.width - m_requisition.width) / 2.0;
+ if (allocation.height > m_requisition.height)
+ y = (allocation.height - m_requisition.height) / 2.0;
+ cr.translate(x, y);
+ m_fontset.draw_cairo_with_requisition_ex(cr, m_requisition);
+ cr.restore();
+ normal_fg.free();
+ normal_fg = null;
+ return true;
+ }
+#endif
}
- private class ESelectedLabel : Gtk.Label {
+ private class ESelectedLabel : EWhiteLabel {
public ESelectedLabel(string text) {
GLib.Object(
name : "IBusEmojierSelectedLabel"
@@ -97,7 +170,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
set_label(text);
}
}
- private class EGoldLabel : Gtk.Label {
+ private class EGoldLabel : EWhiteLabel {
public EGoldLabel(string text) {
GLib.Object(
name : "IBusEmojierGoldLabel"
@@ -212,6 +285,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
m_category_to_emojis_dict;
private static GLib.HashTable<string, GLib.SList<string>>?
m_emoji_to_emoji_variants_dict;
+#if ENABLE_HARFBUZZ_FOR_EMOJI
+ private static IBus.FontSet m_fontset;
+#endif
private ThemedRGBA m_rgba;
private Gtk.Box m_vbox;
@@ -1601,6 +1677,22 @@ class IBusEmojier : Gtk.ApplicationWindow {
}
+#if ENABLE_HARFBUZZ_FOR_EMOJI
+ private static void update_fontset(Pango.Language language) {
+ if (m_fontset != null) {
+ m_fontset.set_family(m_emoji_font_family);
+ m_fontset.set_size(m_emoji_font_size);
+ m_fontset.set_language(language.to_string());
+ m_fontset.update_fcfontset();
+ } else {
+ m_fontset = new IBus.FontSet.with_font(
+ m_emoji_font_family,
+ m_emoji_font_size,
+ language.to_string());
+ }
+ }
+#endif
+
public static bool has_loaded_emoji_dict() {
if (m_emoji_to_data_dict == null)
return false;
@@ -1631,6 +1723,10 @@ class IBusEmojier : Gtk.ApplicationWindow {
int font_size = font_desc.get_size() / Pango.SCALE;
if (font_size != 0)
m_emoji_font_size = font_size;
+#if ENABLE_HARFBUZZ_FOR_EMOJI
+ var widget = new Gtk.Label("");
+ update_fontset(widget.get_pango_context().get_language());
+#endif
}
diff --git a/ui/gtk3/ibusfontset.c b/ui/gtk3/ibusfontset.c
new file mode 100644
index 00000000..66090ecd
--- /dev/null
+++ b/ui/gtk3/ibusfontset.c
@@ -0,0 +1,932 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#include <cairo-ft.h>
+#include <fontconfig/fontconfig.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include <glib.h>
+#include <hb-ot.h>
+#include <pango/pango.h>
+
+#include "ibusfontset.h"
+
+#define XPAD 2
+#define YPAD 2
+#define UNKNOWN_FONT_SIZE 7
+#define IBUS_FONTSET_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_FONTSET, IBusFontSetPrivate))
+#define MONOSPACE "monospace"
+#define SERIF "serif"
+#define SANS "sans"
+
+
+static FT_Library m_ftlibrary;
+static FcFontSet *m_fcfontset;
+static gchar *m_family;
+static guint m_size;
+static gchar *m_language;
+static GHashTable *m_scaled_font_table;
+static GHashTable *m_hb_font_table;
+
+enum {
+ PROP_0,
+ PROP_FAMILY,
+ PROP_SIZE,
+ PROP_LANGUAGE
+};
+
+typedef struct {
+ gunichar ch;
+ FcPattern *fcfont;
+} FontPerChar;
+
+struct _IBusFontSetPrivate {
+ gchar *family;
+ guint size;
+ gchar *language;
+};
+
+static GObject * ibus_fontset_constructor (GType type,
+ guint n,
+ GObjectConstructParam *args);
+static void ibus_fontset_destroy (IBusFontSet *fontset);
+static void ibus_fontset_set_property (IBusFontSet *fontset,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ibus_fontset_get_property (IBusFontSet *fontset,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static cairo_scaled_font_t *
+ ibus_fontset_cairo_scaled_font_new_with_font
+ (const gchar *family,
+ guint size);
+
+G_DEFINE_BOXED_TYPE (IBusCairoLine,
+ ibus_cairo_line,
+ ibus_cairo_line_copy,
+ ibus_cairo_line_free);
+G_DEFINE_BOXED_TYPE (IBusRequisitionEx,
+ ibus_requisition_ex,
+ ibus_requisition_ex_copy,
+ ibus_requisition_ex_free);
+G_DEFINE_TYPE (IBusFontSet, ibus_fontset, IBUS_TYPE_OBJECT)
+
+static void
+ibus_fontset_class_init (IBusFontSetClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
+ cairo_glyph_t dummy;
+ IBusGlyph dummy2;
+
+ gobject_class->constructor = ibus_fontset_constructor;
+ gobject_class->get_property =
+ (GObjectGetPropertyFunc) ibus_fontset_get_property;
+ gobject_class->set_property =
+ (GObjectSetPropertyFunc) ibus_fontset_set_property;
+ object_class->destroy = (IBusObjectDestroyFunc) ibus_fontset_destroy;
+
+ /* install properties */
+ /**
+ * IBusFontSet:family:
+ *
+ * Font family of this IBusFontSet.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_FAMILY,
+ g_param_spec_string ("family",
+ "family",
+ "family",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ /**
+ * IBusFontSet:size:
+ *
+ * Font size of this IBusFontSet.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_SIZE,
+ g_param_spec_uint ("size",
+ "size",
+ "size",
+ 0, G_MAXUINT16, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ /**
+ * IBusFontSet:language:
+ *
+ * Font language of this IBusFontSet.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_LANGUAGE,
+ g_param_spec_string ("language",
+ "language",
+ "language",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (class, sizeof (IBusFontSetPrivate));
+ FT_Init_FreeType (&m_ftlibrary);
+ m_scaled_font_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ g_free,
+ (GDestroyNotify) cairo_scaled_font_destroy);
+ m_hb_font_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ g_free,
+ (GDestroyNotify) hb_font_destroy);
+
+ /* hb_glyph_t is not available in Vala so override it with IBusGlyph. */
+ g_assert (sizeof (dummy) == sizeof (dummy2));
+ g_assert (sizeof (dummy.index) == sizeof (dummy2.index));
+ g_assert (sizeof (dummy.x) == sizeof (dummy2.x));
+ g_assert (sizeof (dummy.y) == sizeof (dummy2.y));
+}
+
+static void
+ibus_fontset_init (IBusFontSet *fontset)
+{
+ fontset->priv = IBUS_FONTSET_GET_PRIVATE (fontset);
+}
+
+
+static GObject *
+ibus_fontset_constructor (GType type,
+ guint n,
+ GObjectConstructParam *args)
+{
+ GObject *object;
+ IBusFontSet *fontset;
+ const gchar *family;
+ guint size;
+
+ object = G_OBJECT_CLASS (ibus_fontset_parent_class)->constructor (
+ type, n ,args);
+ fontset = IBUS_FONTSET (object);
+ family = ibus_fontset_get_family (fontset);
+ size = ibus_fontset_get_size (fontset);
+ ibus_fontset_update_fcfontset (fontset);
+ if (family != NULL && size > 0) {
+ /* cache the font */
+ ibus_fontset_cairo_scaled_font_new_with_font (family,
+ size);
+ }
+ return object;
+}
+
+static void
+ibus_fontset_destroy (IBusFontSet *fontset)
+{
+ g_clear_pointer (&fontset->priv->family, g_free);
+ g_clear_pointer (&fontset->priv->language, g_free);
+}
+
+static void
+ibus_fontset_set_property (IBusFontSet *fontset,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id) {
+ case PROP_FAMILY:
+ ibus_fontset_set_family (fontset, g_value_get_string (value));
+ break;
+ case PROP_SIZE:
+ ibus_fontset_set_size (fontset, g_value_get_uint (value));
+ break;
+ case PROP_LANGUAGE:
+ ibus_fontset_set_language (fontset, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (fontset, prop_id, pspec);
+ }
+}
+
+static void
+ibus_fontset_get_property (IBusFontSet *fontset,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id) {
+ case PROP_FAMILY:
+ g_value_set_string (value, ibus_fontset_get_family (fontset));
+ break;
+ case PROP_SIZE:
+ g_value_set_uint (value, ibus_fontset_get_size (fontset));
+ break;
+ case PROP_LANGUAGE:
+ g_value_set_string (value, ibus_fontset_get_language (fontset));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (fontset, prop_id, pspec);
+ }
+}
+
+static cairo_scaled_font_t *
+ibus_fontset_cairo_scaled_font_new_with_font (const gchar *family,
+ guint size)
+{
+ gchar *font_name;
+ cairo_scaled_font_t *scaled_font = NULL;
+ FcPattern *pattern, *resolved;
+ FcResult result;
+ cairo_font_options_t *font_options;
+ double pixel_size = 0.;
+ FcMatrix fc_matrix, *fc_matrix_val;
+ cairo_font_face_t *cairo_face = NULL;
+ cairo_matrix_t font_matrix;
+ cairo_matrix_t ctm;
+ int i;
+
+ g_return_val_if_fail (family != NULL, NULL);
+ g_return_val_if_fail (m_scaled_font_table != NULL, NULL);
+
+ font_name = g_strdup_printf ("%s %u", family, size);
+ scaled_font = g_hash_table_lookup (m_scaled_font_table, font_name);
+ if (scaled_font != NULL) {
+ g_free (font_name);
+ return scaled_font;
+ }
+ pattern = FcPatternCreate ();
+ FcPatternAddString (pattern, FC_FAMILY, (FcChar8*) family);
+ FcPatternAddDouble (pattern, FC_SIZE, (double) size);
+ FcPatternAddDouble (pattern, FC_DPI, 96);
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ font_options = cairo_font_options_create ();
+ cairo_ft_font_options_substitute (font_options, pattern);
+ FcDefaultSubstitute (pattern);
+ resolved = FcFontMatch (NULL, pattern, &result);
+ FcPatternDestroy (pattern);
+ FcPatternGetDouble (resolved, FC_PIXEL_SIZE, 0, &pixel_size);
+ if (pixel_size == 0.)
+ g_warning ("Failed to scaled the font: %s %u", family, size);
+ cairo_face = cairo_ft_font_face_create_for_pattern (resolved);
+ FcMatrixInit (&fc_matrix);
+ for (i = 0;
+ FcPatternGetMatrix (resolved, FC_MATRIX, i, &fc_matrix_val)
+ == FcResultMatch;
+ i++) {
+ FcMatrixMultiply (&fc_matrix, &fc_matrix, fc_matrix_val);
+ }
+ FcPatternDestroy (resolved);
+ cairo_matrix_init (&font_matrix,
+ fc_matrix.xx, -fc_matrix.yx,
+ -fc_matrix.xy, fc_matrix.yy,
+ 0., 0.);
+ if (pixel_size != 0.)
+ cairo_matrix_scale (&font_matrix, pixel_size, pixel_size);
+ cairo_matrix_init_identity (&ctm);
+ scaled_font = cairo_scaled_font_create (cairo_face,
+ &font_matrix, &ctm,
+ font_options);
+ cairo_font_face_destroy (cairo_face);
+ if (font_name)
+ g_hash_table_insert(m_scaled_font_table, font_name, scaled_font);
+
+ return scaled_font;
+}
+
+static hb_font_t *
+ibus_fontset_hb_font_new_with_font_path (const gchar *font_path)
+{
+ hb_font_t *hb_font;
+ GError *error = NULL;
+ GMappedFile *mf;
+ char *font_data = NULL;
+ gsize len;
+ hb_blob_t *hb_blob;
+ hb_face_t *hb_face;
+
+ g_return_val_if_fail (font_path != NULL, NULL);
+ g_return_val_if_fail (m_hb_font_table != NULL, NULL);
+
+ hb_font = g_hash_table_lookup (m_hb_font_table, font_path);
+ if (hb_font != NULL)
+ return hb_font;
+
+ mf = g_mapped_file_new (font_path, FALSE, &error);
+ if (mf == NULL) {
+ g_warning ("Not found font %s", font_path);
+ return NULL;
+ }
+ font_data = g_mapped_file_get_contents (mf);
+ len = g_mapped_file_get_length (mf);
+ if (len == 0) {
+ g_warning ("zero size font %s", font_path);
+ g_mapped_file_unref (mf);
+ return NULL;
+ }
+ hb_blob = hb_blob_create (font_data, len,
+ HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE,
+ mf, (hb_destroy_func_t)g_mapped_file_unref);
+ hb_face = hb_face_create (hb_blob, 0);
+ hb_blob_destroy (hb_blob);
+ hb_font = hb_font_create (hb_face);
+ unsigned int upem = hb_face_get_upem (hb_face);
+ hb_font_set_scale (hb_font, upem, upem);
+ hb_face_destroy (hb_face);
+ hb_ot_font_set_funcs (hb_font);
+ g_hash_table_insert (m_hb_font_table, g_strdup (font_path), hb_font);
+
+ return hb_font;
+}
+
+static void
+get_font_extents_with_scaled_font (cairo_scaled_font_t *scaled_font,
+ PangoRectangle *font_rect)
+{
+ cairo_font_extents_t font_extents;
+
+ g_assert (scaled_font != NULL && font_rect != NULL);
+
+ cairo_scaled_font_extents (scaled_font, &font_extents);
+ font_rect->x = 0;
+ font_rect->y = - pango_units_from_double (font_extents.ascent);
+ font_rect->width = 0;
+ font_rect->height = pango_units_from_double (
+ font_extents.ascent + font_extents.descent);
+}
+
+static void
+get_glyph_extents_with_scaled_hb_font (const gchar *str,
+ cairo_scaled_font_t *scaled_font,
+ hb_font_t *hb_font,
+ PangoRectangle *font_rect,
+ IBusCairoLine **cairo_lines,
+ FcChar8 *fallback_family)
+{
+ gboolean has_unknown_glyph = FALSE;
+ hb_buffer_t *hb_buffer;
+ unsigned int len, n, i;
+ hb_glyph_info_t *info;
+ hb_glyph_position_t *pos;
+ double x;
+ cairo_glyph_t *glyph;
+ cairo_text_extents_t text_extents = { 0, };
+
+ g_return_if_fail (str != NULL);
+
+ hb_buffer = hb_buffer_create ();
+ hb_buffer_add_utf8 (hb_buffer, str, -1, 0, -1);
+ hb_buffer_guess_segment_properties (hb_buffer);
+ for (n = 0; *cairo_lines && (*cairo_lines)[n].scaled_font; n++);
+ if (n == 0)
+ *cairo_lines = g_new0 (IBusCairoLine, 2);
+ else
+ *cairo_lines = g_renew (IBusCairoLine, *cairo_lines, n + 2);
+ (*cairo_lines)[n + 1].scaled_font = NULL;
+ (*cairo_lines)[n + 1].num_glyphs = 0;
+ (*cairo_lines)[n + 1].glyphs = NULL;
+ hb_shape (hb_font, hb_buffer, NULL, 0);
+ len = hb_buffer_get_length (hb_buffer);
+ info = hb_buffer_get_glyph_infos (hb_buffer, NULL);
+ pos = hb_buffer_get_glyph_positions (hb_buffer, NULL);
+ (*cairo_lines)[n].scaled_font = scaled_font;
+ (*cairo_lines)[n].num_glyphs = len;
+ (*cairo_lines)[n].glyphs = (IBusGlyph*) cairo_glyph_allocate (len + 1);
+ x = 0.;
+ for (i = 0; i < len; i++) {
+ hb_codepoint_t c = info[i].codepoint;
+ if (c) {
+ (*cairo_lines)[n].glyphs[i].index = info[i].codepoint;
+ (*cairo_lines)[n].glyphs[i].x = x;
+ (*cairo_lines)[n].glyphs[i].y = -font_rect->y / PANGO_SCALE;
+ glyph = (cairo_glyph_t *) &((*cairo_lines)[n].glyphs[i]);
+ cairo_scaled_font_glyph_extents (scaled_font, glyph,
+ 1, &text_extents);
+ x += text_extents.width;
+ } else {
+ has_unknown_glyph = TRUE;
+ c = g_utf8_get_char (str);
+ (*cairo_lines)[n].glyphs[i].index = PANGO_GET_UNKNOWN_GLYPH (c);
+ (*cairo_lines)[n].glyphs[i].x = x;
+ (*cairo_lines)[n].glyphs[i].y = -font_rect->y / PANGO_SCALE;
+ glyph = (cairo_glyph_t *) &((*cairo_lines)[n].glyphs[i]);
+ cairo_scaled_font_glyph_extents (scaled_font, glyph,
+ 1, &text_extents);
+ x += 10;
+ }
+ }
+ (*cairo_lines)[n].glyphs[i].index = -1;
+ (*cairo_lines)[n].glyphs[i].x = 0;
+ (*cairo_lines)[n].glyphs[i].y = 0;
+ glyph = (cairo_glyph_t *) (*cairo_lines)[n].glyphs;
+ cairo_scaled_font_glyph_extents (scaled_font, glyph,
+ len, &text_extents);
+ if (text_extents.width) {
+ font_rect->width = pango_units_from_double (text_extents.width);
+ } else {
+ font_rect->width = font_rect->height;
+ }
+ if (has_unknown_glyph && fallback_family != NULL) {
+ cairo_scaled_font_t *unknown_font;
+ unknown_font = ibus_fontset_cairo_scaled_font_new_with_font (
+ (const gchar *) fallback_family,
+ UNKNOWN_FONT_SIZE);
+ (*cairo_lines)[n].scaled_font = unknown_font;
+ }
+ hb_buffer_destroy (hb_buffer);
+}
+
+static void
+get_string_extents_with_font (const gchar *str,
+ FontPerChar *buff,
+ cairo_rectangle_int_t *rect,
+ IBusCairoLine **cairo_lines)
+{
+ FcChar8 *family = NULL;
+ FcChar8 *font_path = NULL;
+ guint size = 0;
+ cairo_scaled_font_t *scaled_font = NULL;
+ PangoRectangle font_rect = { 0, };
+ hb_font_t *hb_font;
+
+ g_return_if_fail (str != NULL);
+ g_return_if_fail (buff != NULL && buff->fcfont != NULL);
+
+ FcPatternGetString (buff->fcfont, FC_FAMILY, 0, &family);
+ g_return_if_fail (family != NULL);
+ size = m_size;
+ if (size == 0) {
+ g_warning ("Font size is not right for font %s.", family);
+ size = 14;
+ }
+ scaled_font = ibus_fontset_cairo_scaled_font_new_with_font (
+ (const gchar *) family,
+ size);
+ g_return_if_fail (scaled_font != NULL);
+ get_font_extents_with_scaled_font (scaled_font, &font_rect);
+
+ FcPatternGetString (buff->fcfont, FC_FILE, 0, &font_path);
+ g_return_if_fail (font_path != NULL);
+ hb_font = ibus_fontset_hb_font_new_with_font_path (
+ (const gchar *) font_path);
+ if (hb_font == NULL)
+ return;
+ get_glyph_extents_with_scaled_hb_font (str,
+ scaled_font,
+ hb_font,
+ &font_rect,
+ cairo_lines,
+ family);
+ rect->width += font_rect.width / PANGO_SCALE;
+ rect->height += font_rect.height / PANGO_SCALE;
+}
+
+static FT_Face
+ibus_fontset_get_ftface_from_fcfont (IBusFontSet *fontset,
+ FcPattern *fcfont)
+{
+ FcChar8 *font_file = NULL;
+ FT_Face ft_face;
+ guint size = ibus_fontset_get_size (fontset);
+
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), NULL);
+ g_return_val_if_fail (fcfont != NULL, NULL);
+
+ size = ibus_fontset_get_size (fontset);
+ FcPatternGetString (fcfont, FC_FILE, 0, &font_file);
+ FT_New_Face (m_ftlibrary, (const gchar *) font_file, 0, &ft_face);
+ FT_Set_Pixel_Sizes (ft_face, size, size);
+ return ft_face;
+}
+
+void
+_cairo_show_unknown_glyphs (cairo_t *cr,
+ const cairo_glyph_t *glyphs,
+ guint num_glyphs,
+ guint width,
+ guint height)
+{
+ gunichar ch;
+ gboolean invalid_input;
+ int rows = 2;
+ int cols;
+ int row, col;
+ char buf[7];
+ double cx = 0.;
+ double cy;
+ const double box_descent = 3.;
+ double x0;
+ double y0;
+ const double digit_width = 5.;
+ const double digit_height= 6.;
+ char hexbox_string[2] = {0, 0};
+
+ g_assert (glyphs != NULL);
+ g_assert (num_glyphs > 0);
+
+ ch = glyphs[0].index & ~PANGO_GLYPH_UNKNOWN_FLAG;
+ invalid_input = G_UNLIKELY (glyphs[0].index == PANGO_GLYPH_INVALID_INPUT ||
+ ch > 0x10FFFF);
+ if (G_UNLIKELY (invalid_input)) {
+ g_warning ("Unsupported U+%06X", ch);
+ return;
+ }
+
+ cairo_save (cr);
+
+ cols = (ch > 0xffff ? 6 : 4) / rows;
+ g_snprintf (buf, sizeof(buf), (ch > 0xffff) ? "%06X" : "%04X", ch);
+ cy = (double) height;
+ x0 = cx + box_descent + XPAD / 2;
+ y0 = cy - box_descent - YPAD / 2;
+
+ for (row = 0; row < rows; row++) {
+ double y = y0 - (rows - 1 - row) * (digit_height + YPAD);
+ for (col = 0; col < cols; col++) {
+ double x = x0 + col * (digit_width + XPAD);
+ cairo_move_to (cr, x, y);
+ hexbox_string[0] = buf[row * cols + col];
+ cairo_show_text (cr, hexbox_string);
+ }
+ }
+ cairo_move_to (cr, XPAD, YPAD);
+ cairo_line_to (cr, width - XPAD, YPAD);
+ cairo_line_to (cr, width - XPAD,
+ height - YPAD);
+ cairo_line_to (cr, XPAD, height - YPAD);
+ cairo_line_to (cr, XPAD, YPAD);
+ cairo_set_line_width (cr, 1.);
+ cairo_stroke (cr);
+
+ cairo_restore (cr);
+}
+
+IBusCairoLine *
+ibus_cairo_line_copy (IBusCairoLine *cairo_lines)
+{
+ IBusCairoLine *ret;
+ guint n, i, j, num_glyphs;
+ if (!cairo_lines)
+ return NULL;
+
+ for (n = 0; cairo_lines[n].scaled_font; n++);
+ ret = g_new0 (IBusCairoLine, n + 1);
+ for (i = 0; i < n; i++) {
+ ret[i].scaled_font = cairo_lines[i].scaled_font;
+ num_glyphs = cairo_lines[i].num_glyphs;
+ ret[i].num_glyphs = num_glyphs;
+ ret[i].glyphs = (IBusGlyph *) cairo_glyph_allocate (num_glyphs + 1);
+ for (j = 0; j < num_glyphs; j++) {
+ ret[i].glyphs[j] = cairo_lines[i].glyphs[j];
+ }
+ ret[i].glyphs[j].index = -1;
+ ret[i].glyphs[j].x = 0;
+ ret[i].glyphs[j].y = 0;
+ }
+ ret[i].scaled_font = NULL;
+ ret[i].num_glyphs = 0;
+ ret[i].glyphs = NULL;
+ return ret;
+}
+
+void
+ibus_cairo_line_free (IBusCairoLine *cairo_lines)
+{
+ guint i;
+ if (!cairo_lines)
+ return;
+ for (i = 0; cairo_lines[i].scaled_font; i++) {
+ g_free (cairo_lines[i].glyphs);
+ }
+ g_free (cairo_lines);
+}
+
+IBusRequisitionEx *
+ibus_requisition_ex_copy (IBusRequisitionEx *req)
+{
+ IBusRequisitionEx *ret;
+ if (!req)
+ return NULL;
+ ret = g_new0 (IBusRequisitionEx, 1);
+ ret->width = req->width;
+ ret->height = req->height;
+ ret->cairo_lines = ibus_cairo_line_copy (req->cairo_lines);
+ return ret;
+}
+
+void
+ibus_requisition_ex_free (IBusRequisitionEx *req)
+{
+ if (!req)
+ return;
+ g_clear_pointer (&req->cairo_lines, ibus_cairo_line_free);
+ g_free (req);
+}
+
+IBusFontSet *
+ibus_fontset_new (const gchar *first_property_name, ...)
+{
+ va_list var_args;
+ IBusFontSet *fontset;
+
+ g_assert (first_property_name);
+
+ va_start (var_args, first_property_name);
+ fontset = (IBusFontSet *)g_object_new_valist (IBUS_TYPE_FONTSET,
+ first_property_name,
+ var_args);
+ va_end (var_args);
+ g_assert (fontset->priv->family);
+ g_assert (fontset->priv->language);
+ return fontset;
+}
+
+IBusFontSet *
+ibus_fontset_new_with_font (const gchar *family,
+ guint size,
+ const gchar *language)
+{
+ return ibus_fontset_new ("family", family,
+ "size", size,
+ "language", language,
+ NULL);
+}
+
+void
+ibus_fontset_exit ()
+{
+ g_clear_pointer (&m_ftlibrary, FT_Done_FreeType);
+}
+
+const gchar *
+ibus_fontset_get_family (IBusFontSet *fontset)
+{
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), NULL);
+ return fontset->priv->family;
+}
+
+void
+ibus_fontset_set_family (IBusFontSet *fontset,
+ const gchar *family)
+{
+ g_return_if_fail (IBUS_IS_FONTSET (fontset));
+ g_free (fontset->priv->family);
+ fontset->priv->family = g_strdup (family);
+}
+
+guint
+ibus_fontset_get_size (IBusFontSet *fontset)
+{
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), 0);
+ return fontset->priv->size;
+}
+
+void
+ibus_fontset_set_size (IBusFontSet *fontset,
+ guint size)
+{
+ g_return_if_fail (IBUS_IS_FONTSET (fontset));
+ fontset->priv->size = size;
+}
+
+const gchar *
+ibus_fontset_get_language (IBusFontSet *fontset)
+{
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), NULL);
+ return fontset->priv->language;
+}
+
+void
+ibus_fontset_set_language (IBusFontSet *fontset,
+ const gchar *language)
+{
+ g_return_if_fail (IBUS_IS_FONTSET (fontset));
+ g_free (fontset->priv->language);
+ fontset->priv->language = g_strdup (language);
+}
+
+gboolean
+ibus_fontset_update_fcfontset (IBusFontSet *fontset)
+{
+ FcPattern *pattern;
+ const gchar *family;
+ guint size;
+ const gchar *language;
+ gboolean update_fontset = FALSE;
+ FcResult result;
+
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), FALSE);
+
+ pattern = FcPatternCreate ();
+ family = fontset->priv->family;
+ size = fontset->priv->size;
+ language = fontset->priv->language;
+
+ if (g_strcmp0 (m_family, family)) {
+ g_free (m_family);
+ m_family = g_strdup (family);
+ update_fontset = TRUE;
+ }
+ if (m_size != size) {
+ m_size = size;
+ update_fontset = TRUE;
+ }
+ if (g_strcmp0 (m_language, language)) {
+ g_free (m_language);
+ m_language = g_strdup (language);
+ update_fontset = TRUE;
+ }
+ if (!update_fontset && m_fcfontset != NULL)
+ return FALSE;
+
+ if (m_fcfontset)
+ g_clear_pointer (&m_fcfontset, FcFontSetDestroy);
+
+ if (g_strcmp0 (family, ""))
+ FcPatternAddString (pattern, FC_FAMILY, (const FcChar8*) family);
+ if (size > 0)
+ FcPatternAddDouble (pattern, FC_SIZE, (double) size);
+ if (g_strcmp0 (language, ""))
+ FcPatternAddString (pattern, FC_LANG, (const FcChar8*) language);
+ FcPatternAddInteger (pattern, FC_WEIGHT, FC_WEIGHT_NORMAL);
+ FcPatternAddInteger (pattern, FC_WIDTH, FC_WIDTH_NORMAL);
+ FcPatternAddInteger (pattern, FC_DPI, 96);
+ if (FC_VERSION >= 21205 &&
+ (!g_ascii_strncasecmp (family, MONOSPACE, strlen (MONOSPACE)) ||
+ !g_ascii_strncasecmp (family, SERIF, strlen (SERIF)) ||
+ !g_ascii_strncasecmp (family, SANS, strlen (SANS)))) {
+ FcPatternAddBool (pattern, FC_COLOR, TRUE);
+ }
+ FcConfigSubstitute (NULL, pattern, FcMatchPattern);
+ FcConfigSubstitute (NULL, pattern, FcMatchFont);
+ FcDefaultSubstitute (pattern);
+ m_fcfontset = FcFontSort (NULL, pattern, FcTrue, NULL, &result);
+ FcPatternDestroy (pattern);
+ if (result == FcResultNoMatch || m_fcfontset->nfont == 0) {
+ g_warning ("No FcFontSet for %s", family ? family : "(null)");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void
+ibus_fontset_unref (IBusFontSet *fontset)
+{
+ g_object_unref (fontset);
+}
+
+IBusRequisitionEx *
+ibus_fontset_get_preferred_size_hb (IBusFontSet *fontset,
+ const gchar *text,
+ cairo_rectangle_int_t *widest)
+{
+ gchar *copied_text;
+ gchar *p;
+ FontPerChar *buff;
+ IBusCairoLine *cairo_lines = NULL;
+ IBusRequisitionEx *req = NULL;
+ GString *str = NULL;
+ int text_length;
+ int i, n = 0;
+
+ g_return_val_if_fail (IBUS_IS_FONTSET (fontset), NULL);
+ g_return_val_if_fail (m_fcfontset != NULL, NULL);
+
+ copied_text = g_strdup (text);
+ text_length = g_utf8_strlen (text, -1);
+ buff = g_slice_alloc0 (sizeof (FontPerChar) * text_length);
+ str = g_string_new (NULL);
+
+ for (p = copied_text; *p != '\0'; p = g_utf8_next_char (p)) {
+ gunichar c = g_utf8_get_char (p);
+ gboolean has_glyphs = FALSE;
+ buff[n].ch = c;
+ if ((c == 0xfe0eu || c == 0xfe0fu) && n > 0) {
+ buff[n].fcfont = buff[n-1].fcfont;
+ ++n;
+ continue;
+ }
+ for (i = 0; i < m_fcfontset->nfont; i++) {
+ if (g_unichar_iscntrl (c) && !g_unichar_isspace (c))
+ break;
+ FT_Face ft_face = ibus_fontset_get_ftface_from_fcfont (
+ fontset,
+ m_fcfontset->fonts[i]);
+ if (FT_Get_Char_Index (ft_face, c) != 0) {
+ buff[n].fcfont = m_fcfontset->fonts[i];
+ if (n > 0 && buff[n - 1].fcfont != buff[n].fcfont) {
+ get_string_extents_with_font (str->str,
+ &buff[n - 1],
+ widest,
+ &cairo_lines);
+ g_string_free (str, TRUE);
+ str = g_string_new (NULL);
+ g_string_append_unichar (str, c);
+ } else {
+ g_string_append_unichar (str, c);
+ }
+ ++n;
+ has_glyphs = TRUE;
+ FT_Done_Face (ft_face);
+ break;
+ }
+ FT_Done_Face (ft_face);
+ }
+ if (!has_glyphs) {
+ if (n > 0) {
+ buff[n].fcfont = buff[n - 1].fcfont;
+ } else {
+ /* Search a font for non-glyph char to draw the code points
+ * likes Pango.
+ */
+ for (i = 0; i < m_fcfontset->nfont; i++) {
+ FT_Face ft_face = ibus_fontset_get_ftface_from_fcfont (
+ fontset,
+ m_fcfontset->fonts[i]);
+ /* Check alphabets instead of space or digits
+ * because 'Noto Emoji Color' font's digits are
+ * white color and cannot change the font color.
+ * the font does not have alphabets.
+ */
+ if (FT_Get_Char_Index (ft_face, 'A') != 0) {
+ buff[n].fcfont = m_fcfontset->fonts[i];
+ FT_Done_Face (ft_face);
+ has_glyphs = TRUE;
+ break;
+ }
+ FT_Done_Face (ft_face);
+ }
+ if (!has_glyphs) {
+ buff[n].fcfont = m_fcfontset->fonts[0];
+ g_warning ("Not found fonts for unicode %04X at %d in %s",
+ c, n, text);
+ }
+ }
+ n++;
+ g_string_append_unichar (str, c);
+ }
+ }
+ if (str->str) {
+ get_string_extents_with_font (str->str,
+ &buff[n - 1],
+ widest,
+ &cairo_lines);
+ g_string_free (str, TRUE);
+ }
+ g_slice_free1 (sizeof (FontPerChar) * text_length, buff);
+ g_free (copied_text);
+ widest->width += XPAD * 2;
+ widest->height += YPAD * 2;
+ req = g_new0 (IBusRequisitionEx, 1);
+ req->width = widest->width;
+ req->height = widest->height;
+ req->cairo_lines = cairo_lines;
+ return req;
+}
+
+void
+ibus_fontset_draw_cairo_with_requisition_ex (IBusFontSet *fontset,
+ cairo_t *cr,
+ IBusRequisitionEx *ex)
+{
+ IBusCairoLine *cairo_lines;
+ int i;
+
+ g_return_if_fail (IBUS_IS_FONTSET (fontset));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (ex != NULL);
+
+ cairo_lines = ex->cairo_lines;
+ g_return_if_fail (cairo_lines != NULL);
+
+ for (i = 0; cairo_lines[i].scaled_font; i++) {
+ const cairo_glyph_t *glyphs = (cairo_glyph_t *) cairo_lines[i].glyphs;
+ guint num_glyphs = cairo_lines[i].num_glyphs;
+
+ cairo_ft_scaled_font_lock_face (cairo_lines[i].scaled_font);
+ cairo_set_scaled_font (cr, cairo_lines[i].scaled_font);
+ if (num_glyphs > 0 && glyphs[0].index & PANGO_GLYPH_UNKNOWN_FLAG) {
+ _cairo_show_unknown_glyphs (cr, glyphs, num_glyphs,
+ ex->width, ex->height);
+ } else {
+ cairo_show_glyphs (cr, glyphs, num_glyphs);
+ }
+ cairo_ft_scaled_font_unlock_face (cairo_lines[i].scaled_font);
+ }
+}
diff --git a/ui/gtk3/ibusfontset.h b/ui/gtk3/ibusfontset.h
new file mode 100644
index 00000000..efcaa286
--- /dev/null
+++ b/ui/gtk3/ibusfontset.h
@@ -0,0 +1,302 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#ifndef __IBUS_HARFBUZZ_H_
+#define __IBUS_HARFBUZZ_H_
+
+/**
+ * SECTION: ibusfontset
+ * @short_description: Object for HarfBuzz and Fontconfig.
+ * @title: IBusFontSet
+ * @stability: Unstable
+ *
+ * IBusFontSet offers FcFontSet, glyph info with HarfBuzz and rendering
+ * on Cairo context.
+ * Current Pango changes fonts by emoji variants and draws the separated
+ * glyphs [1] but actually the emoji characters with variants can be drawn
+ * as one glyph so this class manages Fontconfig fontsets to select a font,
+ * HarfBuzz to get glyphs for emoji variants, Cairo to draw glyphs.
+ *
+ * [1]: https://bugzilla.gnome.org/show_bug.cgi?id=780669
+ * https://bugzilla.gnome.org/show_bug.cgi?id=781123
+ */
+
+#include <ibus.h>
+#include <cairo.h>
+
+#define IBUS_TYPE_CAIRO_LINE (ibus_cairo_line_get_type ())
+#define IBUS_TYPE_REQUISITION_EX (ibus_requisition_ex_get_type ())
+#define IBUS_TYPE_FONTSET (ibus_fontset_get_type ())
+#define IBUS_FONTSET(obj) (G_TYPE_CHECK_INSTANCE_CAST (\
+ (obj), \
+ IBUS_TYPE_FONTSET, \
+ IBusFontSet))
+#define IBUS_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST (\
+ (klass), \
+ IBUS_TYPE_FONTSET, \
+ IBusFontSetClass))
+#define IBUS_IS_FONTSET(obj) (G_TYPE_CHECK_INSTANCE_TYPE (\
+ (obj), \
+ IBUS_TYPE_FONTSET))
+#define IBUS_IS_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE (\
+ (klass), \
+ IBUS_TYPE_FONTSET))
+#define IBUS_FONTSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS (\
+ (obj), \
+ IBUS_TYPE_FONTSET, \
+ IBusFontSetClass))
+
+G_BEGIN_DECLS
+
+typedef struct _IBusGlyph IBusGlyph;
+typedef struct _IBusCairoLine IBusCairoLine;
+typedef struct _IBusRequisitionEx IBusRequisitionEx;
+typedef struct _IBusFontSet IBusFontSet;
+typedef struct _IBusFontSetPrivate IBusFontSetPrivate;
+typedef struct _IBusFontSetClass IBusFontSetClass;
+
+struct _IBusGlyph {
+ unsigned long index;
+ double x;
+ double y;
+};
+
+struct _IBusCairoLine {
+ IBusGlyph *glyphs;
+ guint num_glyphs;
+ cairo_scaled_font_t *scaled_font;
+ gpointer pdummy[5];
+};
+
+struct _IBusRequisitionEx {
+ guint width;
+ guint height;
+ IBusCairoLine *cairo_lines;
+ gpointer pdummy[5];
+};
+
+struct _IBusFontSet {
+ IBusObject parent_instance;
+ IBusFontSetPrivate *priv;
+};
+
+struct _IBusFontSetClass {
+ IBusObjectClass parent_class;
+ /* signals */
+ /*< private >*/
+ /* padding */
+ gpointer pdummy[10];
+};
+
+GType ibus_cairo_line_get_type (void) G_GNUC_CONST;
+
+/**
+ * ibus_cairo_line_copy:
+ * @cairo_lines: #IBusCairoLine
+ *
+ * Creates a copy of @cairo_liens, which should be freed with
+ * ibus_cairo_line_free(). Primarily used by language bindings,
+ * not that useful otherwise (since @req can just be copied
+ * by assignment in C).
+ *
+ * Returns: the newly allocated #IBusCairoLine, which should
+ * be freed with ibus_cairo_line_free(), or %NULL
+ * if @cairo_lines was %NULL.
+ **/
+IBusCairoLine * ibus_cairo_line_copy (IBusCairoLine *cairo_lines);
+
+/**
+ * ibus_cairo_line_free:
+ * @cairo_lines: #IBusCairoLine
+ *
+ * Free an #IBusCairoLine.
+ */
+void ibus_cairo_line_free (IBusCairoLine *cairo_lines);
+
+
+GType ibus_requisition_ex_get_type (void) G_GNUC_CONST;
+
+/**
+ * ibus_requisition_ex_copy:
+ * @req: #IBusRequisitionEx
+ *
+ * Creates a copy of @req, which should be freed with
+ * ibus_requisition_ex_free(). Primarily used by language bindings,
+ * not that useful otherwise (since @req can just be copied
+ * by assignment in C).
+ *
+ * Returns: the newly allocated #IBusRequisitionEx, which should
+ * be freed with ibus_requisition_ex_free(), or %NULL
+ * if @req was %NULL.
+ **/
+IBusRequisitionEx *
+ ibus_requisition_ex_copy (IBusRequisitionEx *req);
+
+/**
+ * ibus_requisition_ex_free:
+ * @req: #IBusRequisitionEx
+ *
+ * Free an #IBusRequisitionEx.
+ */
+void ibus_requisition_ex_free (IBusRequisitionEx *req);
+
+
+GType ibus_fontset_get_type (void);
+
+/**
+ * ibus_fontset_new:
+ * @first_property_name:
+ *
+ * Creates a new #IBusFcFontSet.
+ *
+ * Returns: (transfer full): A newly allocated #IBusFontSet and includes
+ * #FcFontSet internally. E.g. ibus_fontset_new ("family",
+ * "Noto Emoji Color", "size", 16, "language", "ja-jp");
+ */
+IBusFontSet * ibus_fontset_new (const gchar
+ *first_property_name,
+ ...);
+
+/**
+ * ibus_fontset_new_with_font:
+ * @family: font family
+ * @size: font size
+ * @language: font language
+ *
+ * Creates a new #IBusFcFontSet.
+ *
+ * Returns: (transfer full): A newly allocated #IBusFcFontSet and includes
+ * #FcFontSet internally.
+ */
+IBusFontSet * ibus_fontset_new_with_font (const gchar *family,
+ guint size,
+ const gchar *language);
+/**
+ * ibus_fontset_get_family:
+ * @fontset: #IBusFcFontSet
+ *
+ * Return the base font family of #FcFontSet
+ *
+ * Returns: Base font family of #FcFontSet
+ */
+const gchar * ibus_fontset_get_family (IBusFontSet *fontset);
+
+/**
+ * ibus_fontset_set_family:
+ * @fontset: #IBusFcFontSet
+ * @family: base font family for #FcFontSet
+ *
+ * Set the base font family for #FcFontSet
+ */
+void ibus_fontset_set_family (IBusFontSet *fontset,
+ const gchar *family);
+/**
+ * ibus_fontset_get_size:
+ * @fontset: #IBusFcFontSet
+ *
+ * Return the font size of #FcFontSet
+ *
+ * Returns: Font size of #FcFontSet
+ */
+guint ibus_fontset_get_size (IBusFontSet *fontset);
+
+/**
+ * ibus_fontset_set_size:
+ * @fontset: #IBusFcFontSet
+ * @size: font size for #FcFontSet
+ *
+ * Set the font size for #FcFontSet
+ */
+void ibus_fontset_set_size (IBusFontSet *fontset,
+ guint size);
+/**
+ * ibus_fontset_get_language:
+ * @fontset: #IBusFcFontSet
+ *
+ * Return the font language of #FcFontSet
+ *
+ * Returns: Font language of #FcFontSet
+ */
+const gchar * ibus_fontset_get_language (IBusFontSet *fontset);
+
+/**
+ * ibus_fontset_set_language:
+ * @fontset: #IBusFcFontSet
+ * @language: font langauge for #FcFontSet
+ *
+ * Set the font language for #FcFontSet
+ */
+void ibus_fontset_set_language (IBusFontSet *fontset,
+ const gchar *language);
+
+/**
+ * ibus_fontset_update_fcfontset:
+ * @fontset: #IBusFcFontSet
+ *
+ * Update #FcFontSet from font family, size and langauge of @fontset.
+ * Returns: %TRUE if #FcFontSet is updated. %FALSE otherwise.
+ */
+gboolean ibus_fontset_update_fcfontset (IBusFontSet *fontset);
+
+/**
+ * ibus_fontset_get_preferred_size_hb:
+ * @fontset: #IBusFcFontSet
+ * @text: a string to be calculate the preferred rectangle size.
+ * @widest: (out): #cairo_rectangle_int_t is updated.
+ *
+ * Calculate @widest for @text.
+ *
+ * Returns: #IBusRequisitionEx which includes the glyphs and coordinates.
+ */
+IBusRequisitionEx *
+ ibus_fontset_get_preferred_size_hb
+ (IBusFontSet *fontset,
+ const gchar *text,
+ cairo_rectangle_int_t
+ *widest);
+
+/**
+ * ibus_fontset_draw_cairo_lines:
+ * @fontset: #IBusFcFontSet
+ * @cr: #cairo_t in #GtkWidget.draw().
+ * @ex: #IBusRequisitionEx which includes glyph, x, y values, char width
+ * and height.
+ *
+ * Draw glyphs in @ex using cairo @cr.
+ */
+void ibus_fontset_draw_cairo_with_requisition_ex
+ (IBusFontSet *fontset,
+ cairo_t *cr,
+ IBusRequisitionEx
+ *ex);
+
+/**
+ * ibus_fontset_unref:
+ * @fontset: #IBusFcFontSet
+ *
+ * Call g_object_unref().
+ * FIXME: Seems Vala needs this API.
+ */
+void ibus_fontset_unref (IBusFontSet *fontset);
+
+G_END_DECLS
+#endif
--
2.13.4