[gnome-characters/wip/dueno/font-features] Respect OpenType font features
- From: Daiki Ueno <dueno src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-characters/wip/dueno/font-features] Respect OpenType font features
- Date: Wed, 19 Aug 2015 08:07:29 +0000 (UTC)
commit a1dc36ef40e18d3290fd23f510ad46cdd01498f5
Author: Daiki Ueno <dueno src gnome org>
Date: Mon Aug 17 18:23:59 2015 +0900
Respect OpenType font features
configure.ac | 3 +-
data/application.css | 2 +-
data/character.ui | 6 +-
lib/gc.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++
lib/gc.h | 9 +++
src/character.js | 33 +++++++++-
src/characterList.js | 17 ++++--
src/window.js | 3 +-
8 files changed, 228 insertions(+), 15 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 72e30a6..c3be601 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,7 +40,8 @@ PKG_CHECK_MODULES([DEPS], [gdk-3.0
glib-2.0
gobject-2.0
gtk+-3.0
- gjs-1.0 >= $GJS_MIN_VERSION])
+ gjs-1.0 >= $GJS_MIN_VERSION
+ harfbuzz >= 1.0.0])
AC_PATH_PROG([GJS],[gjs])
diff --git a/data/application.css b/data/application.css
index 8047ea3..cde8c87 100644
--- a/data/application.css
+++ b/data/application.css
@@ -28,7 +28,7 @@ Gjs_MenuPopover .list-row {
background-color: rgb(255, 255, 255);
}
-.character-label {
+.character-button {
font-size: 7em;
font-weight: bold;
}
diff --git a/data/character.ui b/data/character.ui
index b123d76..861ef37 100644
--- a/data/character.ui
+++ b/data/character.ui
@@ -17,14 +17,14 @@
<property name="orientation">vertical</property>
<property name="row_spacing">50</property>
<child>
- <object class="GtkLabel" id="character-label">
+ <object class="GtkButton" id="character-button">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="ellipsize">end</property>
<property name="halign">center</property>
<property name="valign">center</property>
+ <property name="relief">none</property>
<style>
- <class name="character-label"/>
+ <class name="character-button"/>
</style>
</object>
<packing>
diff --git a/lib/gc.c b/lib/gc.c
index 9f4e3be..fc4eace 100644
--- a/lib/gc.c
+++ b/lib/gc.c
@@ -17,6 +17,8 @@
#define PANGO_ENABLE_ENGINE 1
#include <pango/pangofc-font.h>
+#include <harfbuzz/hb-ft.h>
+#include <harfbuzz/hb-ot.h>
static const uc_block_t *all_blocks;
static size_t all_block_count;
@@ -926,6 +928,174 @@ gc_pango_context_font_has_glyph (PangoContext *context,
return retval == 0;
}
+void
+gc_pango_layout_set_font_features (PangoLayout *layout, gchar *features)
+{
+ PangoAttrList *attr_list;
+
+ attr_list = pango_layout_get_attributes (layout);
+ if (!attr_list)
+ {
+ attr_list = pango_attr_list_new ();
+ pango_layout_set_attributes (layout, attr_list);
+ }
+ pango_attr_list_insert (attr_list, pango_attr_font_features_new (features));
+}
+
+/**
+ * gc_pango_list_font_features:
+ * @font: a #PangoFont
+ *
+ * Returns: (transfer full) (nullable) (array zero-terminated=1): A
+ * list of OpenType feature tags.
+ */
+gchar **
+gc_pango_list_font_features (PangoFont *font)
+{
+#ifdef HAVE_PANGOFT2
+ if (PANGO_IS_FC_FONT (font))
+ {
+ FT_Face ftface = pango_fc_font_lock_face (PANGO_FC_FONT (font));
+ hb_face_t *hbface = hb_ft_face_create_cached (ftface);
+ unsigned int count, i;
+ hb_tag_t *features;
+ gchar buf[5];
+ gchar **result;
+
+ count = hb_ot_layout_table_get_feature_tags (hbface, HB_OT_TAG_GSUB, 0,
+ NULL, NULL);
+ features = g_new (hb_tag_t, count + 1);
+ hb_ot_layout_table_get_feature_tags (hbface, HB_OT_TAG_GSUB, 0,
+ &count, features);
+ result = g_new0 (gchar *, count + 1);
+ for (i = 0; i < count; i++)
+ {
+ buf[4] = '\0';
+ hb_tag_to_string (features[i], buf);
+ result[i] = g_strdup (buf);
+ }
+ g_free (features);
+ pango_fc_font_unlock_face (PANGO_FC_FONT (font));
+ return result;
+ }
+#endif
+ return NULL;
+}
+
+static void
+collect_features (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GArray *result = user_data;
+ g_array_append_val (result, value);
+}
+
+/**
+ * gc_pango_list_effective_font_features:
+ * @font: a #PangoFont
+ * @font_features: (array zero-terminated=1) (element-type utf8): a
+ * list of font features
+ * @uc: a #gunichar
+ *
+ * Returns: (transfer full) (nullable) (array zero-terminated=1): A
+ * list of OpenType feature tags followed by a space and the index.
+ */
+gchar **
+gc_pango_list_effective_font_features (PangoFont *font,
+ gchar **font_features,
+ gunichar uc)
+{
+ GHashTable *table = g_hash_table_new (g_direct_hash, g_direct_equal);
+ GArray *result = g_array_sized_new (TRUE, FALSE,
+ sizeof (gchar *),
+ g_strv_length (font_features));
+
+#ifdef HAVE_PANGOFT2
+ if (PANGO_IS_FC_FONT (font))
+ {
+ FT_Face ftface = pango_fc_font_lock_face (PANGO_FC_FONT (font));
+ hb_face_t *hbface = hb_ft_face_create_cached (ftface);
+ hb_buffer_t *buffer;
+ hb_font_t *hbfont = NULL;
+ hb_glyph_info_t *infos;
+ unsigned int length;
+ hb_feature_t features[1];
+ hb_codepoint_t base_gid;
+ gchar **p, *last;
+ gint index;
+ uint8_t utf8[6];
+ size_t utf8_length = G_N_ELEMENTS (utf8);
+
+ u32_to_u8 (&uc, 1, utf8, &utf8_length);
+
+ hbfont = hb_font_create (hbface);
+ hb_ft_font_set_funcs (hbfont);
+ hb_font_set_scale (hbfont, 10, 10);
+
+ buffer = hb_buffer_create ();
+ hb_buffer_set_direction (buffer, HB_DIRECTION_LTR);
+ hb_buffer_add_utf8 (buffer, (const char *) utf8, utf8_length, 0, 1);
+
+ hb_shape (hbfont, buffer, NULL, 0);
+ infos = hb_buffer_get_glyph_infos (buffer, &length);
+ hb_buffer_destroy (buffer);
+ if (length == 0)
+ goto out;
+ base_gid = infos[0].codepoint;
+
+ last = NULL;
+ index = 0;
+ for (p = font_features; *p; p++)
+ {
+ gchar *feature_string;
+
+ /* 'salt' or 'ss00-20' */
+ if (!(strcmp (*p, "salt") == 0
+ || (strncmp (*p, "ss", 2) == 0
+ && (strcmp (*p + 2, "00") >= 0
+ && strcmp (*p + 2, "20") <= 0))))
+ continue;
+
+ if (last != NULL && strcmp (*p, last) == 0)
+ index++;
+ else
+ index = 0;
+ last = *p;
+
+ feature_string = g_strdup_printf ("%s %d", *p, index);
+
+ hb_feature_from_string (feature_string, 6, &features[0]);
+
+ buffer = hb_buffer_create ();
+ hb_buffer_set_direction (buffer, HB_DIRECTION_LTR);
+ hb_buffer_add_utf8 (buffer, (const char *) utf8, utf8_length, 0, 1);
+ hb_shape (hbfont, buffer, features, 1);
+ infos = hb_buffer_get_glyph_infos (buffer, &length);
+ hb_buffer_destroy (buffer);
+
+ if (length > 0
+ && infos[0].codepoint != base_gid
+ && !g_hash_table_contains (table,
+ GINT_TO_POINTER (infos[0].codepoint)))
+ {
+ g_hash_table_insert (table,
+ GINT_TO_POINTER (infos[0].codepoint),
+ feature_string);
+ }
+ else
+ g_free (feature_string);
+ }
+ out:
+ hb_font_destroy (hbfont);
+ pango_fc_font_unlock_face (PANGO_FC_FONT (font));
+ }
+#endif
+ g_hash_table_foreach (table, collect_features, result);
+ g_hash_table_unref (table);
+ return (gchar **) g_array_free (result, FALSE);
+}
+
/**
* gc_get_current_language:
*
diff --git a/lib/gc.h b/lib/gc.h
index 688c3a3..a68716d 100644
--- a/lib/gc.h
+++ b/lib/gc.h
@@ -72,6 +72,15 @@ GtkClipboard *gc_gtk_clipboard_get (void);
/* Pango support. PangoAttrFallback is not accessible from GI. */
void gc_pango_layout_disable_fallback
(PangoLayout *layout);
+void gc_pango_layout_set_font_features
+ (PangoLayout *layout,
+ gchar *features);
+gchar **gc_pango_list_font_features
+ (PangoFont *font);
+gchar **gc_pango_list_effective_font_features
+ (PangoFont *font,
+ gchar **font_features,
+ gunichar uc);
gboolean gc_pango_context_font_has_glyph
(PangoContext *context,
diff --git a/src/character.js b/src/character.js
index d39e663..876d588 100644
--- a/src/character.js
+++ b/src/character.js
@@ -21,6 +21,7 @@ const Params = imports.params;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
+const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
const Gc = imports.gi.Gc;
@@ -31,12 +32,13 @@ const CharacterDialog = new Lang.Class({
Name: 'CharacterDialog',
Extends: Gtk.Dialog,
Template: 'resource:///org/gnome/Characters/character.ui',
- InternalChildren: ['main-stack', 'character-label', 'detail-label',
+ InternalChildren: ['main-stack', 'character-button', 'detail-label',
'copy-button', 'related-listbox'],
_init: function(params) {
let filtered = Params.filter(params, { character: null,
- fontDescription: null });
+ fontDescription: null,
+ fontFeatures: null });
params = Params.fill(params, { use_header_bar: true,
width_request: 400,
height_request: 400 });
@@ -62,8 +64,31 @@ const CharacterDialog = new Lang.Class({
this._main_stack.visible_child_name = 'character';
}));
- this._character_label.override_font(filtered.fontDescription);
+ this._character_button.override_font(filtered.fontDescription);
this._setCharacter(filtered.character);
+
+ let context = this.get_pango_context();
+ let font = context.load_font(filtered.fontDescription);
+ this._fontFeatures =
+ Gc.pango_list_effective_font_features (font,
+ filtered.fontFeatures,
+ filtered.character);
+ this._fontFeaturesIndex = 0;
+
+ this._character_button.connect('clicked', Lang.bind(this, this._characterButtonClicked));
+ },
+
+ _characterButtonClicked: function(event) {
+ if (this._fontFeatures.length == 0)
+ return;
+
+ this._fontFeaturesIndex =
+ (this._fontFeaturesIndex + 1) % this._fontFeatures.length;
+ let feature = this._fontFeatures[this._fontFeaturesIndex];
+ let label = this._character_button.get_child();
+ label.set_attributes(null);
+ let layout = label.get_layout();
+ Gc.pango_layout_set_font_features(layout, feature);
},
_finishSearch: function(result) {
@@ -107,7 +132,7 @@ const CharacterDialog = new Lang.Class({
this._character = uc;
this._character = this._character;
- this._character_label.label = this._character;
+ this._character_button.label = this._character;
let codePoint = Util.toCodePoint(this._character);
let codePointHex = codePoint.toString(16).toUpperCase();
diff --git a/src/characterList.js b/src/characterList.js
index 2f79f43..ba1a5af 100644
--- a/src/characterList.js
+++ b/src/characterList.js
@@ -317,20 +317,27 @@ const CharacterListView = new Lang.Class({
return this._fontDescription;
},
+ getFontFeatures: function() {
+ return this._fontFeatures;
+ },
+
updateCharacterList: function() {
let characters = this._characters;
- let fontDescription = this._fontDescription;
+ let fontDescription = this._filterFontDescription ?
+ this._filterFontDescription : this._fontDescription;
+
+ let context = this.get_pango_context();
+ let font = context.load_font(fontDescription);
+ this._fontFeatures = Gc.pango_list_font_features(font);
+
if (this._filterFontDescription) {
- let context = this.get_pango_context();
- let filterFont = context.load_font(this._filterFontDescription);
let filteredCharacters = [];
for (let index = 0; index < characters.length; index++) {
let uc = characters[index];
- if (Gc.pango_context_font_has_glyph(context, filterFont, uc))
+ if (Gc.pango_context_font_has_glyph(context, font, uc))
filteredCharacters.push(uc);
}
characters = filteredCharacters;
- fontDescription = this._filterFontDescription;
}
this._characterList.setFontDescription(fontDescription);
diff --git a/src/window.js b/src/window.js
index 4d4b8a9..b61fc23 100644
--- a/src/window.js
+++ b/src/window.js
@@ -349,7 +349,8 @@ const MainView = new Lang.Class({
character: uc,
modal: true,
transient_for: this.get_toplevel(),
- fontDescription: this.visible_child.getFontDescription()
+ fontDescription: this.visible_child.getFontDescription(),
+ fontFeatures: this.visible_child.getFontFeatures()
});
dialog.show();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]