Blob Blame History Raw
From e6bab7ab78c69d238a70a64e60963dd5a6711ffe Mon Sep 17 00:00:00 2001
From: Felix Yan <felixonmars@archlinux.org>
Date: Fri, 19 May 2017 12:13:04 +0900
Subject: [PATCH] Fix a typo in configure.ac

BUG=https://github.com/ibus/ibus/pull/1927
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/317640043

Patch from Felix Yan <felixonmars@archlinux.org>.
---
 configure.ac | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 219b89d..2cc96d1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -727,7 +727,7 @@ Build options:
   Enable surrounding-text       $enable_surrounding_text
   Enable libnotify              $enable_libnotify
   Enable Emoji dict             $enable_emoji_dict
-  Uicode Emoji directory        $UNICODE_EMOJI_DIR
+  Unicode Emoji directory       $UNICODE_EMOJI_DIR
   CLDR annotation directory     $EMOJI_ANNOTATION_DIR
   Run test cases                $enable_tests
 ])
-- 
2.9.3

From 4fe3050efa7335f82870fb1d5a1d170d20afc160 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 22 May 2017 12:04:28 +0900
Subject: [PATCH] configure: Change relative paths to absolute ones

BUG=https://github.com/ibus/ibus/issues/1926
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/322990043
---
 configure.ac | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/configure.ac b/configure.ac
index 2cc96d1..cb48ad4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -634,10 +634,21 @@ if test x"$enable_emoji_dict" = x"yes"; then
     if test ! -f $UNICODE_EMOJI_DIR/emoji-test.txt ; then
         AC_MSG_ERROR(Not found $UNICODE_EMOJI_DIR/emoji-test.txt. You can get \
 the emoji files from http://www.unicode.org/Public/emoji/4.0/)
+    else
+        # POSIX SHELL has no ${FOO:0:1}
+        head=`echo "$UNICODE_EMOJI_DIR" | cut -c1`;
+        if test $head != "/" ; then
+            UNICODE_EMOJI_DIR=`realpath "$UNICODE_EMOJI_DIR"`
+        fi
     fi
     if test ! -f $EMOJI_ANNOTATION_DIR/en.xml ; then
         AC_MSG_ERROR(Not found $EMOJI_ANNOTATION_DIR/en.xml. You can get \
 https://github.com/fujiwarat/cldr-emoji-annotation)
+    else
+        head=`echo "$EMOJI_ANNOTATION_DIR" | cut -c1`;
+        if test $head != "/" ; then
+            EMOJI_ANNOTATION_DIR=`realpath "$EMOJI_ANNOTATION_DIR"`
+        fi
     fi
     enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
 fi
-- 
2.9.3

From 44d053577a6ac115f3fd3b7beb7bdd65da81aa64 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 24 May 2017 11:52:19 +0900
Subject: [PATCH] engine: Add Malay and Mongolian keymaps

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/325790043
---
 engine/simple.xml.in | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/engine/simple.xml.in b/engine/simple.xml.in
index c08000f..f35d7a5 100644
--- a/engine/simple.xml.in
+++ b/engine/simple.xml.in
@@ -706,5 +706,27 @@
                         <icon>ibus-keyboard</icon>
 			<rank>1</rank>
 		</engine>
+                <engine>
+                        <name>xkb:my::msa</name>
+                        <language>ms</language>
+                        <license>GPL</license>
+                        <author>Peng Huang &lt;shawn.p.huang@gmail.com&gt;</author>
+                        <layout>my</layout>
+                        <longname>Malay (Jawi)</longname>
+                        <description>Malay (Jawi)</description>
+                        <icon>ibus-keyboard</icon>
+                        <rank>1</rank>
+                </engine>
+                <engine>
+                        <name>xkb:mn::mon</name>
+                        <language>mn</language>
+                        <license>GPL</license>
+                        <author>Peng Huang &lt;shawn.p.huang@gmail.com&gt;</author>
+                        <layout>mn</layout>
+                        <longname>Mongolian</longname>
+                        <description>Mongolian</description>
+                        <icon>ibus-keyboard</icon>
+                        <rank>1</rank>
+                </engine>
 	</engines>
 </component>
-- 
2.9.3

From 081d09f1a927f459dacda3bcc59a1678ca2f9a95 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 29 May 2017 11:54:31 +0900
Subject: [PATCH] ui/gtk3: Emojier supports Ctrl-c,v,x and Ctrl-Shift-c

Ctrl-[c|v|x] copy, paste, or cut the emoji annotatons.
Ctrl-Shift-c copies the selected emoji.
Also Ctrl-Backspace is implemented to delete an annotation word.
Also updated ibus-emoji.7.in man page.

R=penghuang@google.com

Review URL: https://codereview.appspot.com/316650043
---
 ui/gtk3/emojier.vala    | 58 +++++++++++++++++++++++++++++++++++++++++++++++--
 ui/gtk3/ibus-emoji.7.in | 11 ++++++++++
 2 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index d0d69ed..1d105fd 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -1392,7 +1392,26 @@ class IBusEmojier : Gtk.ApplicationWindow {
             return true;
         case Gdk.Key.BackSpace:
             if (m_entry.get_text().len() > 0) {
-                GLib.Signal.emit_by_name(m_entry, "backspace");
+                if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
+                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
+                                             Gtk.DeleteType.WORD_ENDS, -1);
+                } else {
+                    GLib.Signal.emit_by_name(m_entry, "backspace");
+                }
+                return true;
+            }
+            break;
+        case Gdk.Key.Delete:
+        case Gdk.Key.KP_Delete:
+            if (m_entry.get_text().len() > 0) {
+                if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
+                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
+                                             Gtk.DeleteType.WORD_ENDS, 1);
+                } else {
+                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
+                                             Gtk.DeleteType.CHARS, 1);
+                }
+                return true;
             }
             break;
         case Gdk.Key.space:
@@ -1445,6 +1464,10 @@ class IBusEmojier : Gtk.ApplicationWindow {
             if (key_press_cursor_home_end(keyval, modifiers))
                 return true;
             break;
+        case Gdk.Key.Insert:
+        case Gdk.Key.KP_Insert:
+            GLib.Signal.emit_by_name(m_entry, "toggle-overwrite");
+            return true;
         }
 
         if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
@@ -1470,8 +1493,13 @@ class IBusEmojier : Gtk.ApplicationWindow {
                     return true;
                 break;
             case Gdk.Key.u:
-                if (key_press_escape())
+                if (m_entry.get_text().len() > 0) {
+                    GLib.Signal.emit_by_name(m_entry,
+                                             "delete-from-cursor",
+                                             Gtk.DeleteType.PARAGRAPH_ENDS,
+                                             -1);
                     return true;
+                }
                 break;
             case Gdk.Key.a:
                 if (m_entry.get_text().len() > 0) {
@@ -1479,6 +1507,32 @@ class IBusEmojier : Gtk.ApplicationWindow {
                     return true;
                 }
                 break;
+            case Gdk.Key.x:
+                if (m_entry.get_text().len() > 0) {
+                    GLib.Signal.emit_by_name(m_entry, "cut-clipboard");
+                    return true;
+                }
+                break;
+            case Gdk.Key.C:
+            case Gdk.Key.c:
+                if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
+                    if (m_candidate_panel_is_visible) {
+                        uint index = m_lookup_table.get_cursor_pos();
+                        var text = m_lookup_table.get_candidate(index).text;
+                        Gtk.Clipboard clipboard =
+                                Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
+                        clipboard.set_text(text, -1);
+                        clipboard.store();
+                        return true;
+                    }
+                } else if (m_entry.get_text().len() > 0) {
+                    GLib.Signal.emit_by_name(m_entry, "copy-clipboard");
+                    return true;
+                }
+                break;
+            case Gdk.Key.v:
+                GLib.Signal.emit_by_name(m_entry, "paste-clipboard");
+                return true;
             }
             return false;
         }
diff --git a/ui/gtk3/ibus-emoji.7.in b/ui/gtk3/ibus-emoji.7.in
index a5045f6..4ee8636 100644
--- a/ui/gtk3/ibus-emoji.7.in
+++ b/ui/gtk3/ibus-emoji.7.in
@@ -83,6 +83,17 @@ Move to the next or previous page in the emoji list.
 \fBHead, End, Control-h or Control-e\fR
 Select the first or last emoji on the list if an annotation is not typed.
 Otherwise move the cursor to the head or end in the typed annotation.
+.TP
+\fBControl-u\fR
+Erase the typed annotation.
+.TP
+\fBControl-x or Control-v or Control-c\fR
+Cut the selected annotation to the clipboard with Control-x. Paste
+the contents of the clipboard into the annotation entry with Control-v.
+Copy the selected annotation to the clipboard with Control-c.
+.TP
+\fBControl-Shift-c\fR
+Copy the selected emoji to the clipboard.
 
 .SH BUGS
 If you find a bug, please report it at https://github.com/ibus/ibus/issues
-- 
2.9.3

From ad80999f5a10faee1a665a2232e1cf60be901cc8 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 29 May 2017 12:03:41 +0900
Subject: [PATCH] Make all emoji dicts for fully qualified

Currently only emoji-en.dict enables fully qualified since it imports
emoji-test.txt and it causes to hardly compare emojis between
emoji-en.dict and emoji-$lang.dict when m_show_emoji_variant
is enabled. E.g. U+1F3CC-FE0F-200D-2642-FE0F
Now emoji-$lang.dict also import emoji-test.txt and enables
fully qualified.

R=penghuang@google.com

Review URL: https://codereview.appspot.com/323860043
---
 src/Makefile.am      |   1 +
 src/emoji-parser.c   | 167 +++++++++++++++++++++++++++++++++++++++++++++------
 src/ibusemoji.c      |   2 +-
 ui/gtk3/emojier.vala |  34 +++++------
 4 files changed, 169 insertions(+), 35 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 27cd168..e7bc8be 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -263,6 +263,7 @@ dicts/emoji-en.dict: emoji-parser
 	            --out $@; \
 	    else \
 	        $(builddir)/emoji-parser \
+	            --unicode-emoji-dir $(UNICODE_EMOJI_DIR) \
 	            --xml $(EMOJI_ANNOTATION_DIR)/$$f.xml \
 	            $$xml_derived_option \
 	            --out dicts/emoji-$$f.dict; \
diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index 5e6155b..fe3e4ef 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -31,12 +31,20 @@
  * ASCII emoji annotations are saved in ../data/annotations/en_ascii.xml
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #include <glib.h>
 
 #ifdef HAVE_JSON_GLIB1
 #include <json-glib/json-glib.h>
 #endif
 
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
 #include <string.h>
 
 #include "ibusemoji.h"
@@ -65,8 +73,73 @@ struct _EmojiData {
     EmojiDataSearchType search_type;
 };
 
+typedef struct _NoTransData NoTransData;
+struct _NoTransData {
+    const gchar *xml_file;
+    const gchar *xml_derived_file;
+    GSList      *emoji_list;
+};
+
 static gchar *unicode_emoji_version;
 
+
+static void
+init_annotations (IBusEmojiData *emoji,
+                  gpointer       user_data)
+{
+    g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
+    ibus_emoji_data_set_annotations (emoji, NULL);
+    ibus_emoji_data_set_description (emoji, "");
+}
+
+static void
+check_no_trans (IBusEmojiData *emoji,
+                NoTransData   *no_trans_data)
+{
+    const gchar *str = NULL;
+    g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
+    if (ibus_emoji_data_get_annotations (emoji) != NULL)
+        return;
+    str = ibus_emoji_data_get_emoji (emoji);
+    if (g_getenv ("IBUS_EMOJI_PARSER_DEBUG") != NULL) {
+        gchar *basename = NULL;
+        if (no_trans_data->xml_file)
+            basename = g_path_get_basename (no_trans_data->xml_file);
+        else if (no_trans_data->xml_derived_file)
+            basename = g_path_get_basename (no_trans_data->xml_derived_file);
+        else
+            basename = g_strdup ("WRONG FILE");
+        g_warning ("Not found emoji %s in the file %s", str, basename);
+        g_free (basename);
+    }
+    no_trans_data->emoji_list =
+            g_slist_append (no_trans_data->emoji_list, g_strdup (str));
+}
+
+int
+strcmp_ibus_emoji_data_str (IBusEmojiData *emoji,
+                            const gchar   *str)
+{
+    g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), -1);
+    return g_strcmp0 (ibus_emoji_data_get_emoji (emoji), str);
+}
+
+static void
+delete_emoji_from_list (const gchar  *str,
+                        GSList      **list)
+{
+    IBusEmojiData *emoji;
+
+    g_return_if_fail (list != NULL);
+    GSList *p = g_slist_find_custom (*list,
+                                     str,
+                                     (GCompareFunc)strcmp_ibus_emoji_data_str);
+    g_return_if_fail (p != NULL);
+    emoji = p->data;
+    *list = g_slist_remove (*list, emoji);
+    g_object_unref (emoji);
+}
+
 static void
 reset_emoji_element (EmojiData *data)
 {
@@ -79,6 +152,13 @@ reset_emoji_element (EmojiData *data)
     g_clear_pointer (&data->description, g_free);
 }
 
+/**
+ * strcmp_novariant:
+ *
+ * Return 0 between non-fully-qualified and fully-qualified emojis.
+ * E.g. U+1F3CC-200D-2642 and U+1F3CC-FE0F-200D-2642-FE0F
+ * in case @a_variant or @b_variant == U+FE0F
+ */
 gint
 strcmp_novariant (const gchar *a,
                   const gchar *b,
@@ -86,40 +166,54 @@ strcmp_novariant (const gchar *a,
                   gunichar     b_variant)
 {
     gint retval;
-    gchar *p = NULL;
     GString *buff = NULL;;
+    gchar *head = NULL;
+    gchar *p;
+    gchar *variant = NULL;
     gchar *substr = NULL;
 
     if (a_variant > 0) {
-        if ((p = g_utf8_strchr (a, -1, a_variant)) != NULL) {
+        if (g_utf8_strchr (a, -1, a_variant) != NULL) {
             buff = g_string_new (NULL);
-            if (a != p) {
-                substr = g_strndup (a, p - a);
-                g_string_append (buff, substr);
-                g_free (substr);
+            p = head = g_strdup (a);
+            while (*p != '\0') {
+                if ((variant = g_utf8_strchr (p, -1, a_variant)) == NULL) {
+                    g_string_append (buff, p);
+                    break;
+                }
+                if (p != variant) {
+                    substr = g_strndup (p, variant - p);
+                    g_string_append (buff, substr);
+                    g_free (substr);
+                }
+                p = g_utf8_next_char (variant);
             }
-            p = g_utf8_next_char (p);
-            if (*p != '\0')
-                g_string_append (buff, p);
             retval = g_strcmp0 (buff->str, b);
             g_string_free (buff, TRUE);
+            g_free (head);
             return retval;
         } else {
             return -1;
         }
     } else if (b_variant > 0) {
-        if ((p = g_utf8_strchr (b, -1, b_variant)) != NULL) {
+        if (g_utf8_strchr (b, -1, b_variant) != NULL) {
             buff = g_string_new (NULL);
-            if (b != p) {
-                substr = g_strndup (b, p - b);
-                g_string_append (buff, substr);
-                g_free (substr);
+            p = head = g_strdup (b);
+            while (*p != '\0') {
+                if ((variant = g_utf8_strchr (p, -1, b_variant)) == NULL) {
+                    g_string_append (buff, p);
+                    break;
+                }
+                if (p != variant) {
+                    substr = g_strndup (p, variant - p);
+                    g_string_append (buff, substr);
+                    g_free (substr);
+                }
+                p = g_utf8_next_char (variant);
             }
-            p = g_utf8_next_char (p);
-            if (*p != '\0')
-                g_string_append (buff, p);
             retval = g_strcmp0 (a, buff->str);
             g_string_free (buff, TRUE);
+            g_free (head);
             return retval;
         } else {
             return -1;
@@ -1117,6 +1211,12 @@ main (int argc, char *argv[])
     GOptionContext *context;
     GError *error = NULL;
     GSList *list = NULL;
+    gboolean is_en = TRUE;
+
+#ifdef HAVE_LOCALE_H
+    /* To output emoji warnings. */
+    setlocale (LC_ALL, "");
+#endif
 
     prgname = g_path_get_basename (argv[0]);
     g_set_prgname (prgname);
@@ -1144,12 +1244,45 @@ main (int argc, char *argv[])
 #endif
     if (emoji_dir)
         unicode_emoji_parse_dir (emoji_dir, &list);
+    if (list) {
+#define CHECK_IS_EN(file) if ((file)) {                                     \
+    gchar *basename = g_path_get_basename ((file));                         \
+    is_en = (g_ascii_strncasecmp (basename, "en.", 3) == 0) ?               \
+            TRUE : FALSE;                                                   \
+    g_free (basename);                                                      \
+}
+
+        CHECK_IS_EN(xml_derived_file);
+        CHECK_IS_EN(xml_file);
+#undef CHECK_IS_EN
+
+        /* Use English emoji-test.txt to get fully-qualified. */
+        if (!is_en)
+            g_slist_foreach (list, (GFunc)init_annotations, NULL);
+    }
     if (xml_file)
         unicode_annotations_parse_xml_file (xml_file, &list, FALSE);
     if (xml_derived_file)
         unicode_annotations_parse_xml_file (xml_derived_file, &list, TRUE);
     if (xml_ascii_file)
         unicode_annotations_parse_xml_file (xml_ascii_file, &list, FALSE);
+    if (list != NULL && !is_en) {
+        /* If emoji-test.txt has an emoji but $lang.xml does not, clear it
+         * since the language dicts do not want English annotations.
+         */
+        NoTransData no_trans_data = {
+            xml_file,
+            xml_derived_file,
+            NULL
+        };
+        g_slist_foreach (list, (GFunc)check_no_trans, &no_trans_data);
+        if (no_trans_data.emoji_list) {
+            g_slist_foreach (no_trans_data.emoji_list,
+                             (GFunc)delete_emoji_from_list,
+                             &list);
+            g_slist_free_full (no_trans_data.emoji_list, g_free);
+        }
+    }
     if (list != NULL && output)
         ibus_emoji_data_save (output, list);
     if (list != NULL && output_category)
diff --git a/src/ibusemoji.c b/src/ibusemoji.c
index d2e16c5..3d38c2a 100644
--- a/src/ibusemoji.c
+++ b/src/ibusemoji.c
@@ -29,7 +29,7 @@
 #include "ibusinternal.h"
 
 #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
-#define IBUS_EMOJI_DATA_VERSION (4)
+#define IBUS_EMOJI_DATA_VERSION (5)
 
 enum {
     PROP_0 = 0,
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 1d105fd..95912bf 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -190,9 +190,6 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private const string EMOJI_CATEGORY_OTHERS = N_("Others");
     private const unichar[] EMOJI_VARIANT_LIST = {
             0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff, 0x200d };
-    private const GLib.ActionEntry[] m_action_entries = {
-        { "variant", check_action_variant_cb, null, "false", null }
-    };
 
     // Set the actual default values in the constructor
     // because these fields are used for class_init() and static functions,
@@ -253,7 +250,13 @@ class IBusEmojier : Gtk.ApplicationWindow {
             focus_visible : true
         );
 
-        add_action_entries(m_action_entries, this);
+        // GLib.ActionEntry accepts const variables only.
+        var action = new GLib.SimpleAction.stateful(
+                "variant",
+                null,
+                new GLib.Variant.boolean(m_show_emoji_variant));
+        action.activate.connect(check_action_variant_cb);
+        add_action(action);
         if (m_current_lang_id == null)
             m_current_lang_id = "en";
         if (m_emoji_font_family == null)
@@ -521,18 +524,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             m_emoji_to_data_dict.replace(emoji, data);
         } else {
             unowned IBus.EmojiData? en_data = null;
-            // If emoji presentation (+= 0xfe0f) is already saved in dict,
-            // update it instead of no presentation.
-            // emoji-test.txt has all emoji presentations but $lang.xml has
-            // some no emoji presentations.
-            if (emoji.chr(-1, 0xfe0f) == null) {
-                var buff = new GLib.StringBuilder();
-                buff.append(emoji);
-                buff.append_unichar(0xfe0f);
-                en_data = m_emoji_to_data_dict.lookup(buff.str);
-            }
-            if (en_data == null)
-                en_data = m_emoji_to_data_dict.lookup(emoji);
+            en_data = m_emoji_to_data_dict.lookup(emoji);
             if (en_data == null) {
                 m_emoji_to_data_dict.insert(emoji, data);
                 return;
@@ -923,7 +915,12 @@ class IBusEmojier : Gtk.ApplicationWindow {
             m_vbox.add(button);
             button.show_all();
             button.button_press_event.connect((w, e) => {
-                hide_candidate_panel();
+                // Bring back to emoji candidate panel in case
+                // m_show_emoji_variant is enabled and shows variants.
+                if (m_backward_index >= 0 && m_backward != null)
+                    show_emoji_for_category(m_backward);
+                else
+                    hide_candidate_panel();
                 return true;
             });
         }
@@ -1269,6 +1266,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
                                          GLib.Variant?     parameter) {
         m_show_emoji_variant = !action.get_state().get_boolean();
         action.set_state(new GLib.Variant.boolean(m_show_emoji_variant));
+        // Redraw emoji candidate panel for m_show_emoji_variant
+        if (m_candidate_panel_is_visible)
+            show_candidate_panel();
     }
 
 
-- 
2.9.3

From 76a83df1ab2aca4063968b2dd5300c64979e9496 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 19 Jul 2017 20:48:17 +0900
Subject: [PATCH] ui/gtk3: Fix SEGV of IBusEmojier on de_DE.UTF-8

de's decimal_point is ',' instead of '.' and failed to load the
CSS data in Gtk.CssProvider.load_from_data(), launched null
window of emojis and finally caused a SEGV due to the null window.
This also fixes some memory leaks.
---
 src/ibusemoji.c      |  1 +
 ui/gtk3/emojier.vala | 33 ++++++++++++++++++++++++++-------
 2 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/src/ibusemoji.c b/src/ibusemoji.c
index 3d38c2a..d56c48a 100644
--- a/src/ibusemoji.c
+++ b/src/ibusemoji.c
@@ -591,6 +591,7 @@ out_load_cache:
         g_variant_unref (variant);
     if (variant_table)
         g_variant_unref (variant_table);
+    g_free (contents);
 
     return retval;
 }
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 95912bf..9df59ac 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -276,6 +276,17 @@ class IBusEmojier : Gtk.ApplicationWindow {
             warning("Could not open display.");
             return;
         }
+        // Set en locale because de_DE's decimal_point is ',' instead of '.'
+        string? backup_locale =
+            Intl.setlocale(LocaleCategory.NUMERIC, null).dup();
+        if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) {
+          if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) {
+              if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) {
+                  warning("You don't install either en_US.UTF-8 or C.UTF-8 " +
+                          "or C locale");
+              }
+          }
+        }
         m_rgba = new ThemedRGBA(this);
         uint bg_red = (uint)(m_rgba.normal_bg.red * 255);
         uint bg_green = (uint)(m_rgba.normal_bg.green * 255);
@@ -321,6 +332,10 @@ class IBusEmojier : Gtk.ApplicationWindow {
             warning("Failed css_provider_from_data: %s", e.message);
             return;
         }
+        if (backup_locale != null)
+            Intl.setlocale(LocaleCategory.NUMERIC, backup_locale);
+        else
+            Intl.setlocale(LocaleCategory.NUMERIC, "");
 
         Gtk.StyleContext.add_provider_for_screen(
                 screen,
@@ -424,8 +439,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
         unowned GLib.SList<string> annotations = data.get_annotations();
         foreach (string annotation in annotations) {
             bool has_emoji = false;
-            unowned GLib.SList<string> hits =
-                    m_annotation_to_emojis_dict.lookup(annotation);
+            GLib.SList<string> hits =
+                    m_annotation_to_emojis_dict.lookup(annotation).copy_deep(
+                            GLib.strdup);
             foreach (string hit_emoji in hits) {
                 if (hit_emoji == emoji) {
                     has_emoji = true;
@@ -485,7 +501,8 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private static void
     update_annotations_with_description (IBus.EmojiData data,
                                          string         description) {
-        unowned GLib.SList<string> annotations = data.get_annotations();
+        GLib.SList<string> annotations =
+                data.get_annotations().copy_deep(GLib.strdup);
         bool update_annotations = false;
         string former = null;
         string later = null;
@@ -574,8 +591,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 buff.append_unichar(0xfe0f);
                 if (m_emoji_to_data_dict.lookup(buff.str) != null)
                     base_emoji = buff.str;
-                unowned GLib.SList<string>? variants =
-                    m_emoji_to_emoji_variants_dict.lookup(base_emoji);
+                GLib.SList<string>? variants =
+                        m_emoji_to_emoji_variants_dict.lookup(
+                                base_emoji).copy_deep(GLib.strdup);
                 if (variants.find_custom(emoji, GLib.strcmp) == null) {
                     if (variants == null)
                         variants.append(base_emoji);
@@ -587,8 +605,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 return;
             }
             bool has_emoji = false;
-            unowned GLib.SList<string> hits =
-                    m_category_to_emojis_dict.lookup(category);
+            GLib.SList<string> hits =
+                    m_category_to_emojis_dict.lookup(category).copy_deep(
+                            GLib.strdup);
             foreach (string hit_emoji in hits) {
                 if (hit_emoji == emoji) {
                     has_emoji = true;
-- 
2.9.3

From 2686b46b29e12b4408033568a898949a731b7938 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 19 Jul 2017 21:02:20 +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
---
 .../vala}/IBusEmojiDialog-1.0.metadata             |   0
 bindings/vala/IBusFontSet-1.0.metadata             |   1 +
 bindings/vala/Makefile.am                          | 244 +++++-
 .../vala}/ibus-emoji-dialog-1.0.deps               |   0
 bindings/vala/ibus-fontset-1.0.deps                |   1 +
 configure.ac                                       |  29 +
 po/POTFILES.skip                                   |   5 +
 ui/gtk3/Makefile.am                                | 131 ++-
 ui/gtk3/emojier.vala                               | 119 ++-
 ui/gtk3/ibusemojidialog.h                          |  26 +
 ui/gtk3/ibusfontset.c                              | 923 +++++++++++++++++++++
 ui/gtk3/ibusfontset.h                              | 302 +++++++
 12 files changed, 1675 insertions(+), 106 deletions(-)
 rename {ui/gtk3 => bindings/vala}/IBusEmojiDialog-1.0.metadata (100%)
 create mode 100644 bindings/vala/IBusFontSet-1.0.metadata
 rename {ui/gtk3 => bindings/vala}/ibus-emoji-dialog-1.0.deps (100%)
 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/ui/gtk3/IBusEmojiDialog-1.0.metadata b/bindings/vala/IBusEmojiDialog-1.0.metadata
similarity index 100%
rename from ui/gtk3/IBusEmojiDialog-1.0.metadata
rename to bindings/vala/IBusEmojiDialog-1.0.metadata
diff --git a/bindings/vala/IBusFontSet-1.0.metadata b/bindings/vala/IBusFontSet-1.0.metadata
new file mode 100644
index 0000000..73037d7
--- /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 4e34afc..261e1f3 100644
--- a/bindings/vala/Makefile.am
+++ b/bindings/vala/Makefile.am
@@ -3,7 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2007-2016 Red Hat, Inc.
+# Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-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
@@ -22,15 +23,47 @@
 
 -include $(VAPIGEN_MAKEFILE)
 
+libibus = $(top_builddir)/src/libibus-@IBUS_API_VERSION@.la
+
+noinst_LTLIBRARIES =
+noinst_DATA =
+INTROSPECTION_GIRS =
+girdir = $(datadir)/gir-1.0
+
+AM_CPPFLAGS = \
+    -I$(top_srcdir)/src \
+    -I$(top_builddir)/src \
+    -include $(CONFIG_HEADER) \
+    $(NULL)
+AM_CFLAGS = \
+    -DG_LOG_DOMAIN=\"IBUS\" \
+    -DPKGDATADIR=\"$(pkgdatadir)\" \
+    -DIBUS_DISABLE_DEPRECATED \
+    -Wno-unused-variable \
+    -Wno-unused-but-set-variable \
+    -Wno-unused-function \
+    $(NULL)
+AM_VALAFLAGS = \
+    --vapidir=$(builddir) \
+    --vapidir=$(srcdir) \
+    --pkg=posix \
+    --pkg=gtk+-3.0 \
+    --pkg=gdk-x11-3.0 \
+    --pkg=ibus-1.0 \
+    --pkg=config \
+    --pkg=xi \
+    --target-glib="$(VALA_TARGET_GLIB_VERSION)" \
+    $(NULL)
+
 vapi_deps = \
 	IBus-1.0.metadata \
-	IBus-1.0-custom.vala \
 	$(top_builddir)/src/IBus-1.0.gir \
 	$(NULL)
 
 ibus-1.0.vapi: $(vapi_deps)
 
-VAPIGEN_VAPIS = ibus-1.0.vapi
+ibus_vapi = ibus-1.0.vapi
+VAPIGEN_VAPIS = $(ibus_vapi)
 
 ibus_1_0_vapi_DEPS = gio-2.0
 ibus_1_0_vapi_METADATADIRS = $(srcdir)
@@ -40,18 +73,201 @@ ibus_1_0_vapi_FILES = \
 	$(NULL)
 
 vapidir = $(datadir)/vala/vapi
-vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+vapi_DATA = $(ibus_vapi) $(ibus_vapi:.vapi=.deps)
 
-MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS)
-DISTCLEANFILES = $(VAPIGEN_VAPIS)
+MAINTAINERCLEANFILES = $(ibus_vapi)
+DISTCLEANFILES = $(ibus_vapi)
 
-EXTRA_DIST = \
-	$(VAPIGEN_VAPIS) \
-	IBus-1.0.metadata \
-	IBus-1.0-custom.vala \
-	ibus-1.0.deps \
-	config.vapi \
-	xi.vapi \
-	$(NULL)
+EXTRA_DIST =                                    \
+    $(ibus_vapi)                                \
+    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)
+
+if ENABLE_EMOJI_DICT
+AM_VALAFLAGS += --define=EMOJI_DICT
+
+libibus_emoji_dialog = libibus-emoji-dialog-1.0.la
+noinst_LTLIBRARIES += $(libibus_emoji_dialog)
+
+libibus_emoji_dialog_1_0_la_SOURCES =           \
+    candidatearea.vala                          \
+    emojier.vala                                \
+    iconwidget.vala                             \
+    pango.vala                                  \
+    separator.vala                              \
+    $(NULL)
+libibus_emoji_dialog_1_0_la_CFLAGS =            \
+    $(AM_CFLAGS)                                \
+    @GLIB2_CFLAGS@                              \
+    @GIO2_CFLAGS@                               \
+    @GTHREAD2_CFLAGS@                           \
+    @GTK3_CFLAGS@                               \
+    @X11_CFLAGS@                                \
+    -DBINDIR=\"$(bindir)\"                      \
+    $(NULL)
+libibus_emoji_dialog_1_0_la_LIBADD =            \
+    @GLIB2_LIBS@                                \
+    @GIO2_LIBS@                                 \
+    @GTHREAD2_LIBS@                             \
+    @GTK3_LIBS@                                 \
+    @X11_LIBS@                                  \
+    -lXi                                        \
+    $(libibus)                                  \
+    $(NULL)
+libibus_emoji_dialog_1_0_la_LDFLAGS =           \
+    -no-undefined                               \
+    -export-symbols-regex "ibus_.*"             \
+    $(NULL)
+
+# per file setting is needed to avoid conflicting LN_S by calling
+# duplicated times in parallel make
+%.vala: $(ibus_vapi)
+	if test ! -f $@ ; then                                              \
+	    $(LN_S) $(top_srcdir)/ui/gtk3/$@ .;                             \
+	fi;
+ibusfontset.c: $(ibus_vapi)
+	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)
+
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_SCANNER_ARGS =
+INTROSPECTION_COMPILER_ARGS =                   \
+    --includedir=$(srcdir)                      \
+    --includedir=.                              \
+    --includedir=$(top_srcdir)/src              \
+    $(NULL)
+
+
+emoji_headers =                                 \
+    $(top_srcdir)/ui/gtk3/ibusemojidialog.h     \
+    $(NULL)
+
+IBusEmojiDialog-1.0.gir: $(libibus_emoji_dialog) Makefile
+IBusEmojiDialog_1_0_gir_SCANNERFLAGS =          \
+    --pkg-export=ibus-1.0                       \
+    --pkg=gtk+-3.0                              \
+    $(IBUS_GIR_SCANNERFLAGS)                    \
+    $(NULL)
+IBusEmojiDialog_1_0_gir_INCLUDES = Gtk-3.0 GLib-2.0 GObject-2.0 Gio-2.0
+IBusEmojiDialog_1_0_gir_LIBS = $(libibus_emoji_dialog) $(libibus)
+IBusEmojiDialog_1_0_gir_FILES = $(emoji_headers)
+IBusEmojiDialog_1_0_gir_CFLAGS =                \
+    -I$(srcdir)                                 \
+    -I$(builddir)                               \
+    -I$(top_srcdir)/src                         \
+    $(NULL)
+
+ibus_emoji_dialog_gir = IBusEmojiDialog-1.0.gir
+INTROSPECTION_GIRS += $(ibus_emoji_dialog_gir)
+noinst_DATA += $(ibus_emoji_dialog_gir)
+EXTRA_DIST += $(ibus_emoji_dialog_gir)
+MAINTAINERCLEANFILES += $(ibus_emoji_dialog_gir)
+DISTCLEANFILES += $(ibus_emoji_dialog_gir)
+
+ibus-emoji-dialog-1.0.vapi: $(ibus_emoji_dialog_gir) IBusEmojiDialog-1.0.metadata
+ibus_emoji_dialog_vapi = ibus-emoji-dialog-1.0.vapi
+ibus_emoji_dialog_1_0_vapi_DEPS = gtk+-3.0 gio-2.0
+ibus_emoji_dialog_1_0_vapi_METADATADIRS = $(srcdir)
+ibus_emoji_dialog_1_0_vapi_FILES = IBusEmojiDialog-1.0.gir
+VAPIGEN_VAPIS += $(ibus_emoji_dialog_vapi)
+noinst_DATA += $(ibus_emoji_dialog_vapi)
+EXTRA_DIST += $(ibus_emoji_dialog_vapi)
+MAINTAINERCLEANFILES += $(ibus_emoji_dialog_vapi)
+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
 
 -include $(top_srcdir)/git.mk
diff --git a/ui/gtk3/ibus-emoji-dialog-1.0.deps b/bindings/vala/ibus-emoji-dialog-1.0.deps
similarity index 100%
rename from ui/gtk3/ibus-emoji-dialog-1.0.deps
rename to bindings/vala/ibus-emoji-dialog-1.0.deps
diff --git a/bindings/vala/ibus-fontset-1.0.deps b/bindings/vala/ibus-fontset-1.0.deps
new file mode 100644
index 0000000..129fe16
--- /dev/null
+++ b/bindings/vala/ibus-fontset-1.0.deps
@@ -0,0 +1 @@
+cairo
diff --git a/configure.ac b/configure.ac
index cb48ad4..d2aa222 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
@@ -740,6 +768,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/po/POTFILES.skip b/po/POTFILES.skip
index 7190221..10b8829 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -2,6 +2,11 @@
 # Please keep this file in alphabetical order.
 # Files under ui/gtk2/ are not shipped in the distribution, but kept
 # in the git repository for reference.
+bindings/vala/candidatearea.c
+bindings/vala/emojier.c
+bindings/vala/iconwidget.c
+bindings/vala/pango.c
+bindings/vala/separator.c
 ibus/_config.py
 tools/main.c
 ui/gtk2/candidatepanel.py
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index c79641a..cd1e9c2 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -81,10 +81,6 @@ AM_VALAFLAGS = \
 	--target-glib="$(VALA_TARGET_GLIB_VERSION)" \
 	$(NULL)
 
-MAINTAINERCLEANFILES =
-DISTCLEANFILES =
-noinst_DATA =
-
 if ENABLE_LIBNOTIFY
 AM_CFLAGS += \
 	@LIBNOTIFY_CFLAGS@ \
@@ -158,9 +154,10 @@ man_seven_in_files = ibus-emoji.7.in
 EXTRA_DIST =                            \
     $(emoji_headers)                    \
     $(man_seven_in_files)               \
-    IBusEmojiDialog-1.0.metadata        \
+    emojierapp.vala                     \
     gtkpanel.xml.in                     \
-    ibus-emoji-dialog-1.0.deps          \
+    ibusfontset.c                       \
+    ibusfontset.h                       \
     notification-item.xml               \
     notification-watcher.xml            \
     $(NULL)
@@ -168,98 +165,70 @@ EXTRA_DIST =                            \
 if ENABLE_EMOJI_DICT
 AM_VALAFLAGS += --define=EMOJI_DICT
 
-libibus_emoji_dialog = libibus-emoji-dialog-1.0.la
-
-noinst_LTLIBRARIES = $(libibus_emoji_dialog)
-
-libibus_emoji_dialog_1_0_la_CFLAGS = $(AM_CFLAGS)
-libibus_emoji_dialog_1_0_la_LDFLAGS =   \
-    -no-undefined                       \
-    -export-symbols-regex "ibus_.*"     \
-    -version-info @LT_VERSION_INFO@     \
-    $(NULL)
-libibus_emoji_dialog_1_0_la_SOURCES =   \
-    candidatearea.vala                  \
-    emojier.vala                        \
-    iconwidget.vala                     \
-    pango.vala                          \
-    separator.vala                      \
-    $(NULL)
-
 libexec_PROGRAMS += ibus-ui-emojier
 
-ibus_ui_emojier_SOURCES =                       \
-    $(libibus_emoji_dialog_1_0_la_SOURCES)      \
+ibus_ui_emojier_VALASOURCES =                   \
     emojierapp.vala                             \
+    candidatearea.vala                          \
+    emojier.vala                                \
+    iconwidget.vala                             \
+    pango.vala                                  \
+    separator.vala                              \
+    $(NULL)
+ibus_ui_emojier_SOURCES =                       \
+    $(ibus_ui_emojier_VALASOURCES:.vala=.c)     \
     $(NULL)
 
 ibus_ui_emojier_LDADD =                         \
     $(AM_LDADD)                                 \
     $(NULL)
 
--include $(INTROSPECTION_MAKEFILE)
-INTROSPECTION_SCANNER_ARGS =
-INTROSPECTION_COMPILER_ARGS =      \
-    --includedir=$(srcdir)         \
-    --includedir=.                 \
-    --includedir=$(top_srcdir)/src \
-    $(NULL)
-
-if HAVE_INTROSPECTION
-introspection_sources =                \
-    $(emoji_headers)                   \
-    $(NULL)
-IBusEmojiDialog-1.0.gir: $(libibus_emoji_dialog) Makefile
-IBusEmojiDialog_1_0_gir_SCANNERFLAGS = \
-    --pkg-export=ibus-1.0              \
-    --pkg=gtk+-3.0                     \
-    $(IBUS_GIR_SCANNERFLAGS)           \
+ibus_ui_emojier_VALAFLAGS =                     \
+    $(AM_VALAFLAGS)                             \
     $(NULL)
-IBusEmojiDialog-1.0.gir: $(libibus_emoji_dialog) Makefile
-IBusEmojiDialog_1_0_gir_INCLUDES = Gtk-3.0 GLib-2.0 GObject-2.0 Gio-2.0
-IBusEmojiDialog_1_0_gir_LIBS = $(libibus_emoji_dialog) $(libibus)
-IBusEmojiDialog_1_0_gir_FILES =                      \
-    $(addprefix $(srcdir)/,$(introspection_sources)) \
-    $(NULL)
-IBusEmojiDialog_1_0_gir_CFLAGS =       \
-    -DIBUS_COMPILATION                 \
-    -I$(srcdir)                        \
-    -I$(builddir)                      \
-    -I$(top_srcdir)/src                \
-    $(NULL)
-INTROSPECTION_GIRS = IBusEmojiDialog-1.0.gir
-
-girdir = $(datadir)/gir-1.0
-noinst_DATA += $(INTROSPECTION_GIRS)
-CLEANFILES += $(INTROSPECTION_GIRS)
 
-typelibsdir = $(libdir)/girepository-1.0
-noinst_DATA += $(INTROSPECTION_GIRS:.gir=.typelib)
-CLEANFILES += $(INTROSPECTION_GIRS:.gir=.typelib)
-
-
-if ENABLE_VAPIGEN
--include $(VAPIGEN_MAKEFILE)
+# This line and foo_VALASOURCES line can delete the duplicated entries
+# of emojier.c: emojier.vala
+emojierapp.c: $(ibus_ui_emojier_VALASOURCES)
+	$(AM_V_VALAC)$(am__cd) $(srcdir) && $(VALAC) $(AM_VALAFLAGS) \
+$(VALAFLAGS) -C $(ibus_ui_emojier_VALASOURCES)
+	$(NULL)
+# make dist creates .c files in a different srcdir
+emojierapp.o: $(srcdir)/emojierapp.c
+	$(AM_V_CC)source='$<' object='$@' libtool=no \
+	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \
+	$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
+	$(NULL)
 
-ibus-emoji-dialog-1.0.vapi: $(INTROSPECTION_GIRS) IBusEmojiDialog-1.0.metadata
+if ENABLE_HARFBUZZ_FOR_EMOJI
+ibus_ui_gtk3_SOURCES +=                         \
+    ibusfontset.c                               \
+    $(NULL)
 
-VAPIGEN_VAPIS = ibus-emoji-dialog-1.0.vapi
+ibus_ui_emojier_SOURCES +=                      \
+    ibusfontset.c                               \
+    $(NULL)
 
-ibus_emoji_dialog_1_0_vapi_DEPS = gtk+-3.0 gio-2.0
-ibus_emoji_dialog_1_0_vapi_METADATADIRS = $(srcdir)
-ibus_emoji_dialog_1_0_vapi_FILES = $(INTROSPECTION_GIRS)
+AM_CFLAGS += \
+    @CAIRO_CFLAGS@                              \
+    @FONTCONFIG_CFLAGS@                         \
+    @HARFBUZZ_CFLAGS@                           \
+    $(NULL)
 
-vapidir = $(datadir)/vala/vapi
-noinst_DATA += $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+AM_LDADD += \
+    @CAIRO_LIBS@                                \
+    @FONTCONFIG_LIBS@                           \
+    @HARFBUZZ_LIBS@                             \
+    $(NULL)
 
-MAINTAINERCLEANFILES += $(VAPIGEN_VAPIS)
-DISTCLEANFILES += $(VAPIGEN_VAPIS)
-EXTRA_DIST += $(VAPIGEN_VAPIS)
+AM_VALAFLAGS += \
+    -D ENABLE_HARFBUZZ_FOR_EMOJI                \
+    --pkg=cairo                                 \
+    --pkg=ibus-fontset-1.0                      \
+    $(NULL)
 
-# end of ENABLE_VAPIGEN
-endif
-# end of HAVE_INTROSPECTION
 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)
@@ -276,7 +245,7 @@ CLEANFILES += \
     $(man_seven_files) \
     $(NULL)
 
-# end of ENABLE_EMOJI_DICT
 endif
+# end of ENABLE_EMOJI_DICT
 
 -include $(top_srcdir)/git.mk
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 9df59ac..492a42f 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;
@@ -1139,6 +1215,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
                     m_category_active_index = (int)list.length();
         }
         Gtk.Adjustment adjustment = m_list_box.get_adjustment();
+        m_scrolled_window.set_hadjustment(new Gtk.Adjustment(0, 0, 0, 0, 0, 0));
         m_scrolled_window.set_vadjustment(adjustment);
         show_category_list();
     }
@@ -1156,7 +1233,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             else if (keyval == Gdk.Key.Right)
                 m_lookup_table.cursor_down();
             show_candidate_panel();
-        } else if (m_entry.get_text().len() > 0) {
+        } else if (m_entry.get_text().length > 0) {
             int step = 0;
             if (keyval == Gdk.Key.Left)
                 step = -1;
@@ -1211,7 +1288,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             show_candidate_panel();
             return true;
         }
-        if (m_entry.get_text().len() > 0) {
+        if (m_entry.get_text().length > 0) {
             int step = 0;
             if (keyval == Gdk.Key.Home)
                 step = -1;
@@ -1410,7 +1487,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             key_press_enter();
             return true;
         case Gdk.Key.BackSpace:
-            if (m_entry.get_text().len() > 0) {
+            if (m_entry.get_text().length > 0) {
                 if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
                     GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
                                              Gtk.DeleteType.WORD_ENDS, -1);
@@ -1422,7 +1499,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             break;
         case Gdk.Key.Delete:
         case Gdk.Key.KP_Delete:
-            if (m_entry.get_text().len() > 0) {
+            if (m_entry.get_text().length > 0) {
                 if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
                     GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
                                              Gtk.DeleteType.WORD_ENDS, 1);
@@ -1436,7 +1513,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
         case Gdk.Key.space:
         case Gdk.Key.KP_Space:
             if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
-                if (m_entry.get_text().len() > 0)
+                if (m_entry.get_text().length > 0)
                     entry_enter_keyval(keyval);
             } else if (m_candidate_panel_is_visible) {
                 enter_notify_disable_with_timer();
@@ -1512,7 +1589,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
                     return true;
                 break;
             case Gdk.Key.u:
-                if (m_entry.get_text().len() > 0) {
+                if (m_entry.get_text().length > 0) {
                     GLib.Signal.emit_by_name(m_entry,
                                              "delete-from-cursor",
                                              Gtk.DeleteType.PARAGRAPH_ENDS,
@@ -1521,13 +1598,13 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 }
                 break;
             case Gdk.Key.a:
-                if (m_entry.get_text().len() > 0) {
+                if (m_entry.get_text().length > 0) {
                     m_entry.select_region(0, -1);
                     return true;
                 }
                 break;
             case Gdk.Key.x:
-                if (m_entry.get_text().len() > 0) {
+                if (m_entry.get_text().length > 0) {
                     GLib.Signal.emit_by_name(m_entry, "cut-clipboard");
                     return true;
                 }
@@ -1544,7 +1621,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
                         clipboard.store();
                         return true;
                     }
-                } else if (m_entry.get_text().len() > 0) {
+                } else if (m_entry.get_text().length > 0) {
                     GLib.Signal.emit_by_name(m_entry, "copy-clipboard");
                     return true;
                 }
@@ -1600,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;
@@ -1630,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/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h
index 24d195c..ed8886a 100644
--- a/ui/gtk3/ibusemojidialog.h
+++ b/ui/gtk3/ibusemojidialog.h
@@ -170,5 +170,31 @@ void          ibus_emojier_set_favorites          (gchar**      favorites,
                                                    favorite_annotations,
                                                    int
                                                    favorite_annotations_length);
+
+/**
+ * ibus_emojier_set_partial_match:
+ * @has_partial_match: Enable the partial match if %TRUE. Otherwise if %FALSE.
+ *
+ * Set partial match for emoji annotations.
+ */
+void          ibus_emojier_set_partial_match      (gboolean  has_partial_match);
+
+/**
+ * ibus_emojier_set_partial_match_length:
+ * @length: minimum lenght to match partially.
+ *
+ * Set the minimum lenght to match partially.
+ */
+void          ibus_emojier_set_partial_match_length
+                                                  (gint         length);
+
+/**
+ * ibus_emojier_set_partial_match_condition:
+ * @condition: condition id between 0 and 2.
+ *
+ * Set the partial match condition with the integer.
+ */
+void          ibus_emojier_set_partial_match_condition
+                                                  (gint         condition);
 G_END_DECLS
 #endif
diff --git a/ui/gtk3/ibusfontset.c b/ui/gtk3/ibusfontset.c
new file mode 100644
index 0000000..7864a64
--- /dev/null
+++ b/ui/gtk3/ibusfontset.c
@@ -0,0 +1,923 @@
+/* -*- 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))
+
+
+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);
+    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 0000000..efcaa28
--- /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.9.3