From f85ce71361d3d55eccc0bcc4fba1ccfb2a6c670f Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Wed, 20 Sep 2017 13:04:55 +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 | 950 +++++++++++++++++++++++++++++++++ ui/gtk3/ibusfontset.h | 302 +++++++++++ 8 files changed, 1496 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 9cd98140..c581671e 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>? 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; @@ -1609,6 +1685,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; @@ -1639,6 +1731,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..d637d034 --- /dev/null +++ b/ui/gtk3/ibusfontset.c @@ -0,0 +1,950 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et sts=4: */ +/* ibus - The Input Bus + * Copyright (C) 2017 Takao Fujiwara + * 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 +#include +#include +#include FT_FREETYPE_H +#include +#include +#include + +#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 gboolean m_color_supported; +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, + gboolean has_color); + +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; + + m_color_supported = (FcGetVersion () >= 21205); + 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, + TRUE); + } + 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, + gboolean has_color) +{ + 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); + + if (m_color_supported) { + font_name = g_strdup_printf ("%s %u:color=%s", + family, size, + has_color ? "TRUE" : "FALSE"); + } else { + 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); + if (m_color_supported) + FcPatternAddBool (pattern, FC_COLOR, has_color); + 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, + FALSE); + (*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; + gboolean has_color = TRUE; + 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); + if (m_color_supported) + FcPatternGetBool (buff->fcfont, FC_COLOR, 0, &has_color); + 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, + has_color); + 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 (m_color_supported && + (!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 + * 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 +#include + +#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