From e6bab7ab78c69d238a70a64e60963dd5a6711ffe Mon Sep 17 00:00:00 2001 From: Felix Yan 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 . --- 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 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 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 @@ ibus-keyboard 1 + + xkb:my::msa + ms + GPL + Peng Huang <shawn.p.huang@gmail.com> + my + Malay (Jawi) + Malay (Jawi) + ibus-keyboard + 1 + + + xkb:mn::mon + mn + GPL + Peng Huang <shawn.p.huang@gmail.com> + mn + Mongolian + Mongolian + ibus-keyboard + 1 + -- 2.9.3 From 081d09f1a927f459dacda3bcc59a1678ca2f9a95 Mon Sep 17 00:00:00 2001 From: fujiwarat 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 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 +#endif + #include #ifdef HAVE_JSON_GLIB1 #include #endif +#ifdef HAVE_LOCALE_H +#include +#endif + #include #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 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 annotations = data.get_annotations(); foreach (string annotation in annotations) { bool has_emoji = false; - unowned GLib.SList hits = - m_annotation_to_emojis_dict.lookup(annotation); + GLib.SList 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 annotations = data.get_annotations(); + GLib.SList 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? variants = - m_emoji_to_emoji_variants_dict.lookup(base_emoji); + GLib.SList? 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 hits = - m_category_to_emojis_dict.lookup(category); + GLib.SList 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 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 -# Copyright (c) 2007-2016 Red Hat, Inc. +# Copyright (c) 2017 Takao Fujiwara +# 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>? 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 + * Copyright (C) 2017 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include +#include +#include FT_FREETYPE_H +#include +#include +#include + +#include "ibusfontset.h" + +#define XPAD 2 +#define YPAD 2 +#define UNKNOWN_FONT_SIZE 7 +#define IBUS_FONTSET_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_FONTSET, IBusFontSetPrivate)) + + +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 + * Copyright (C) 2017 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef __IBUS_HARFBUZZ_H_ +#define __IBUS_HARFBUZZ_H_ + +/** + * SECTION: ibusfontset + * @short_description: Object for HarfBuzz and Fontconfig. + * @title: IBusFontSet + * @stability: Unstable + * + * IBusFontSet offers FcFontSet, glyph info with HarfBuzz and rendering + * on Cairo context. + * Current Pango changes fonts by emoji variants and draws the separated + * glyphs [1] but actually the emoji characters with variants can be drawn + * as one glyph so this class manages Fontconfig fontsets to select a font, + * HarfBuzz to get glyphs for emoji variants, Cairo to draw glyphs. + * + * [1]: https://bugzilla.gnome.org/show_bug.cgi?id=780669 + * https://bugzilla.gnome.org/show_bug.cgi?id=781123 + */ + +#include +#include + +#define IBUS_TYPE_CAIRO_LINE (ibus_cairo_line_get_type ()) +#define IBUS_TYPE_REQUISITION_EX (ibus_requisition_ex_get_type ()) +#define IBUS_TYPE_FONTSET (ibus_fontset_get_type ()) +#define IBUS_FONTSET(obj) (G_TYPE_CHECK_INSTANCE_CAST (\ + (obj), \ + IBUS_TYPE_FONTSET, \ + IBusFontSet)) +#define IBUS_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST (\ + (klass), \ + IBUS_TYPE_FONTSET, \ + IBusFontSetClass)) +#define IBUS_IS_FONTSET(obj) (G_TYPE_CHECK_INSTANCE_TYPE (\ + (obj), \ + IBUS_TYPE_FONTSET)) +#define IBUS_IS_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE (\ + (klass), \ + IBUS_TYPE_FONTSET)) +#define IBUS_FONTSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS (\ + (obj), \ + IBUS_TYPE_FONTSET, \ + IBusFontSetClass)) + +G_BEGIN_DECLS + +typedef struct _IBusGlyph IBusGlyph; +typedef struct _IBusCairoLine IBusCairoLine; +typedef struct _IBusRequisitionEx IBusRequisitionEx; +typedef struct _IBusFontSet IBusFontSet; +typedef struct _IBusFontSetPrivate IBusFontSetPrivate; +typedef struct _IBusFontSetClass IBusFontSetClass; + +struct _IBusGlyph { + unsigned long index; + double x; + double y; +}; + +struct _IBusCairoLine { + IBusGlyph *glyphs; + guint num_glyphs; + cairo_scaled_font_t *scaled_font; + gpointer pdummy[5]; +}; + +struct _IBusRequisitionEx { + guint width; + guint height; + IBusCairoLine *cairo_lines; + gpointer pdummy[5]; +}; + +struct _IBusFontSet { + IBusObject parent_instance; + IBusFontSetPrivate *priv; +}; + +struct _IBusFontSetClass { + IBusObjectClass parent_class; + /* signals */ + /*< private >*/ + /* padding */ + gpointer pdummy[10]; +}; + +GType ibus_cairo_line_get_type (void) G_GNUC_CONST; + +/** + * ibus_cairo_line_copy: + * @cairo_lines: #IBusCairoLine + * + * Creates a copy of @cairo_liens, which should be freed with + * ibus_cairo_line_free(). Primarily used by language bindings, + * not that useful otherwise (since @req can just be copied + * by assignment in C). + * + * Returns: the newly allocated #IBusCairoLine, which should + * be freed with ibus_cairo_line_free(), or %NULL + * if @cairo_lines was %NULL. + **/ +IBusCairoLine * ibus_cairo_line_copy (IBusCairoLine *cairo_lines); + +/** + * ibus_cairo_line_free: + * @cairo_lines: #IBusCairoLine + * + * Free an #IBusCairoLine. + */ +void ibus_cairo_line_free (IBusCairoLine *cairo_lines); + + +GType ibus_requisition_ex_get_type (void) G_GNUC_CONST; + +/** + * ibus_requisition_ex_copy: + * @req: #IBusRequisitionEx + * + * Creates a copy of @req, which should be freed with + * ibus_requisition_ex_free(). Primarily used by language bindings, + * not that useful otherwise (since @req can just be copied + * by assignment in C). + * + * Returns: the newly allocated #IBusRequisitionEx, which should + * be freed with ibus_requisition_ex_free(), or %NULL + * if @req was %NULL. + **/ +IBusRequisitionEx * + ibus_requisition_ex_copy (IBusRequisitionEx *req); + +/** + * ibus_requisition_ex_free: + * @req: #IBusRequisitionEx + * + * Free an #IBusRequisitionEx. + */ +void ibus_requisition_ex_free (IBusRequisitionEx *req); + + +GType ibus_fontset_get_type (void); + +/** + * ibus_fontset_new: + * @first_property_name: + * + * Creates a new #IBusFcFontSet. + * + * Returns: (transfer full): A newly allocated #IBusFontSet and includes + * #FcFontSet internally. E.g. ibus_fontset_new ("family", + * "Noto Emoji Color", "size", 16, "language", "ja-jp"); + */ +IBusFontSet * ibus_fontset_new (const gchar + *first_property_name, + ...); + +/** + * ibus_fontset_new_with_font: + * @family: font family + * @size: font size + * @language: font language + * + * Creates a new #IBusFcFontSet. + * + * Returns: (transfer full): A newly allocated #IBusFcFontSet and includes + * #FcFontSet internally. + */ +IBusFontSet * ibus_fontset_new_with_font (const gchar *family, + guint size, + const gchar *language); +/** + * ibus_fontset_get_family: + * @fontset: #IBusFcFontSet + * + * Return the base font family of #FcFontSet + * + * Returns: Base font family of #FcFontSet + */ +const gchar * ibus_fontset_get_family (IBusFontSet *fontset); + +/** + * ibus_fontset_set_family: + * @fontset: #IBusFcFontSet + * @family: base font family for #FcFontSet + * + * Set the base font family for #FcFontSet + */ +void ibus_fontset_set_family (IBusFontSet *fontset, + const gchar *family); +/** + * ibus_fontset_get_size: + * @fontset: #IBusFcFontSet + * + * Return the font size of #FcFontSet + * + * Returns: Font size of #FcFontSet + */ +guint ibus_fontset_get_size (IBusFontSet *fontset); + +/** + * ibus_fontset_set_size: + * @fontset: #IBusFcFontSet + * @size: font size for #FcFontSet + * + * Set the font size for #FcFontSet + */ +void ibus_fontset_set_size (IBusFontSet *fontset, + guint size); +/** + * ibus_fontset_get_language: + * @fontset: #IBusFcFontSet + * + * Return the font language of #FcFontSet + * + * Returns: Font language of #FcFontSet + */ +const gchar * ibus_fontset_get_language (IBusFontSet *fontset); + +/** + * ibus_fontset_set_language: + * @fontset: #IBusFcFontSet + * @language: font langauge for #FcFontSet + * + * Set the font language for #FcFontSet + */ +void ibus_fontset_set_language (IBusFontSet *fontset, + const gchar *language); + +/** + * ibus_fontset_update_fcfontset: + * @fontset: #IBusFcFontSet + * + * Update #FcFontSet from font family, size and langauge of @fontset. + * Returns: %TRUE if #FcFontSet is updated. %FALSE otherwise. + */ +gboolean ibus_fontset_update_fcfontset (IBusFontSet *fontset); + +/** + * ibus_fontset_get_preferred_size_hb: + * @fontset: #IBusFcFontSet + * @text: a string to be calculate the preferred rectangle size. + * @widest: (out): #cairo_rectangle_int_t is updated. + * + * Calculate @widest for @text. + * + * Returns: #IBusRequisitionEx which includes the glyphs and coordinates. + */ +IBusRequisitionEx * + ibus_fontset_get_preferred_size_hb + (IBusFontSet *fontset, + const gchar *text, + cairo_rectangle_int_t + *widest); + +/** + * ibus_fontset_draw_cairo_lines: + * @fontset: #IBusFcFontSet + * @cr: #cairo_t in #GtkWidget.draw(). + * @ex: #IBusRequisitionEx which includes glyph, x, y values, char width + * and height. + * + * Draw glyphs in @ex using cairo @cr. + */ +void ibus_fontset_draw_cairo_with_requisition_ex + (IBusFontSet *fontset, + cairo_t *cr, + IBusRequisitionEx + *ex); + +/** + * ibus_fontset_unref: + * @fontset: #IBusFcFontSet + * + * Call g_object_unref(). + * FIXME: Seems Vala needs this API. + */ +void ibus_fontset_unref (IBusFontSet *fontset); + +G_END_DECLS +#endif -- 2.9.3