Blob Blame History Raw
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * ezfc-font.c
 * Copyright (C) 2011-2015 Akira TAGOH
 * 
 * Authors:
 *   Akira TAGOH  <akira@tagoh.org>
 * 
 * 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 3 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <fontconfig/fontconfig.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
#include <hb-ft.h>
#include <hb-ot.h>
#include <libeasyfc/ezfc-error.h>
#include "ezfc-mem.h"
#include <libeasyfc/ezfc-font.h>
#include "ezfc-font-private.h"


/**
 * SECTION: ezfc-font
 * @Short_Description: A class for managing the font properties
 * @Title: ezfc_font_t
 *
 * This class provides an easy access to the font properties
 *
 * Since: 0.7
 */
typedef enum {
	EZFC_FONT_UNKNOWN   = 0,
	EZFC_FONT_SANS      = 1 << 0,
	EZFC_FONT_SERIF     = 1 << 1,
	EZFC_FONT_MONOSPACE = 1 << 2,
	EZFC_FONT_CURSIVE   = 1 << 3,
	EZFC_FONT_FANTASY   = 1 << 4,
	EZFC_FONT_EMOJI     = 1 << 5,
	EZFC_FONT_MATH      = 1 << 6,
	EZFC_FONT_END
} ezfc_font_variant_t;
typedef GList * (ezfc_proc_t) (GList *, const FcPattern *);

extern FT_Library _ezfc_get_freetype(void);

static gchar *__ezfc_font_alias_names[] = {
	"sans",
	"sans-serif",
	"serif",
	"monospace",
	"cursive",
	"fantasy",
	"emoji",
	"math",
	NULL
};

/*< private >*/
static FT_Face
_ezfc_font_get_face(const FcPattern *pattern)
{
	FcChar8 *file;
	int idx;
	FT_Face face = NULL;

	FcPatternGetString(pattern, FC_FILE, 0, &file);
	FcPatternGetInteger(pattern, FC_INDEX, 0, &idx);

	if (FT_New_Face(_ezfc_get_freetype(), (char *)file, idx, &face)) {
		g_warning("Unable to open the font file '%s' index %d",
			  file, idx);
	}

	return face;
}

static ezfc_font_variant_t
_ezfc_font_get_font_type(const gchar *font)
{
	gint i;

	for (i = 0; __ezfc_font_alias_names[i] != NULL; i++) {
		if (g_ascii_strcasecmp(font, __ezfc_font_alias_names[i]) == 0) {
			if (i == 0)
				i++;
			return 1 << (i - 1);
		}
	}

	return EZFC_FONT_UNKNOWN;
}

static ezfc_font_variant_t
_ezfc_font_get_font_type_from_pattern(const FcPattern *pattern)
{
	int spacing = 0;
	ezfc_font_variant_t retval = EZFC_FONT_UNKNOWN;
	TT_OS2 *os2;
	FT_Face face = NULL;
	FT_Byte cls_id, subcls_id;
	FcLangSet *ls = NULL;

	FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing);
	if (spacing == FC_MONO ||
	    spacing == FC_DUAL ||
	    spacing == FC_CHARCELL) {
		retval = EZFC_FONT_MONOSPACE;
	}
	FcPatternGetLangSet(pattern, FC_LANG, 0, &ls);
	if (ls) {
		if (FcLangSetHasLang(ls, (const FcChar8 *)"und-zsye") == FcLangEqual)
			retval |= EZFC_FONT_EMOJI;
		if (FcLangSetHasLang(ls, (const FcChar8 *)"und-zmth") == FcLangEqual)
			retval |= EZFC_FONT_MATH;
	}

	face = _ezfc_font_get_face(pattern);
	os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
	if (!os2)
		goto bail;
	/* See http://www.microsoft.com/typography/otspec/os2.htm#fc
	 * and http://www.microsoft.com/typography/otspec/ibmfc.htm
	 * for the reference of sFamilyClass.
	 */
	cls_id = (os2->sFamilyClass >> 8) & 0xff;
	subcls_id = os2->sFamilyClass & 0xff;
	switch (cls_id) {
	    case 0: /* No Classification */
	    case 6: /* reserved for future use */
	    case 11: /* reserved for future use */
	    case 13: /* Reserved */
	    case 14: /* Reserved */
		    break;
	    case 1: /* Oldstyle Serifs */
		    retval |= EZFC_FONT_SERIF;
		    if (subcls_id == 8) /* Calligraphic */
			    retval |= EZFC_FONT_CURSIVE;
		    break;
	    case 2: /* Transitional Serifs */
	    case 3: /* Modern Serifs */
		    retval |= EZFC_FONT_SERIF;
		    if (subcls_id == 2) /* Script */
			    retval |= EZFC_FONT_CURSIVE;
		    break;
	    case 4: /* Clarendon Serifs */
	    case 5: /* Slab Serifs */
	    case 7: /* Freeform Serifs */
		    retval |= EZFC_FONT_SERIF;
		    break;
	    case 8: /* Sans Serif */
		    retval |= EZFC_FONT_SANS;
		    break;
	    case 9: /* Ornamentals */
	    case 12: /* Symbolic */
		    retval |= EZFC_FONT_FANTASY;
		    break;
	    case 10: /* Scripts */
		    retval |= EZFC_FONT_CURSIVE;
		    break;
	    default:
		    g_warning("Unknown sFamilyClass class ID: %d", cls_id);
		    break;
	}
	/* See http://www.monotypeimaging.com/ProductsServices/pan1.aspx
	 * for the reference of Panose
	 */
	if (os2->panose[0] == 3) {
		/* Latin Hand Written */
		retval |= EZFC_FONT_CURSIVE;
	} else if (os2->panose[0] >= 4 &&
		   os2->panose[0] <= 5) {
		/* 4...Latin Decorative
		 * 5...Latin Symbol
		 */
		retval |= EZFC_FONT_FANTASY;
	} else if (os2->panose[0] == 2 &&
		   ((os2->panose[1] >= 11 &&
		     os2->panose[1] <= 13) ||
		    os2->panose[1] == 15)) {
		/* 2..Latin Text */
		/*   11...Normal Sans
		 *   12...Obtuse Sans
		 *   13...Perpendicular Sans
		 *   15...Pounded
		 */
		retval |= EZFC_FONT_SANS;
	} else if (os2->panose[0] == 1) {
		/* "Not Fit" in the family kind should be ignored. */
	} else if (os2->panose[0] == 0 &&
		   os2->panose[1] == 1) {
		/* Unable to determine the alias type */
	} else {
		retval |= EZFC_FONT_SERIF;
	}
	if ((os2->panose[0] == 2 &&
	     os2->panose[3] == 9) ||
	    (os2->panose[0] == 3 &&
	     os2->panose[3] == 3) ||
	    (os2->panose[0] == 4 &&
	     os2->panose[3] == 9) ||
	    (os2->panose[0] == 5 &&
	     os2->panose[3] == 3)) {
		retval |= EZFC_FONT_MONOSPACE;
	}
  bail:
	FT_Done_Face(face);

	return retval;
}

static gboolean
_ezfc_font_is_font_a(const FcPattern *pattern,
		     const gchar     *alias_name)
{
	ezfc_font_variant_t type = _ezfc_font_get_font_type(alias_name);
	ezfc_font_variant_t ptype = _ezfc_font_get_font_type_from_pattern(pattern);

	return (type & ptype) == type;
}

static GList *
_ezfc_font_get_fonts_proc(const gchar *language,
			  const gchar *alias_name,
			  gboolean     localized_font_name,
			  ezfc_proc_t  func)
{
	FcPattern *pat;
	FcFontSet *fs;
	FcObjectSet *os;
	GList *retval = NULL;
	FcLangSet *ls = NULL;

	g_return_val_if_fail (func != NULL, NULL);

	if (alias_name) {
		g_return_val_if_fail (ezfc_font_is_alias_font(alias_name), NULL);
	}
	pat = FcPatternCreate();
	if (language && language[0] != 0) {
		ls = FcLangSetCreate();
		FcLangSetAdd(ls, (const FcChar8 *)language);
		FcPatternAddLangSet(pat, FC_LANG, ls);
	}
	if (!localized_font_name)
		FcPatternAddString(pat, FC_NAMELANG, (const FcChar8 *)"en");
	os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, FC_SPACING, FC_LANG, NULL);
	fs = FcFontList(NULL, pat, os);
	FcObjectSetDestroy(os);
	FcPatternDestroy(pat);
	if (ls)
		FcLangSetDestroy(ls);
	if (fs) {
		gint i;

		for (i = 0; i < fs->nfont; i++) {
			if (!alias_name || _ezfc_font_is_font_a(fs->fonts[i], alias_name)) {
				retval = func(retval, fs->fonts[i]);
			}
		}
		FcFontSetDestroy(fs);
	}

	return retval;
}

static GList *
_ezfc_font_get_fonts_cb_family(GList           *list,
			       const FcPattern *pattern)
{
	FcChar8 *family;
	GList *l;
	gboolean added = FALSE;

	FcPatternGetString(pattern, FC_FAMILY, 0, &family);
	for (l = list; l != NULL; l = g_list_next(l)) {
		gint r = g_strcmp0(l->data, (const gchar *)family);
		GList *ll;

		if (r == 0) {
			/* do not add it */
			added = TRUE;
			break;
		} else if (r > 0) {
			ll = g_list_prepend(l, g_strdup((const gchar *)family));

			if (list == l)
				list = ll;
			added = TRUE;
			break;
		}
	}
	if (!added)
		list = g_list_append(list, g_strdup((const gchar *)family));

	return list;
}

static GList *
_ezfc_font_get_fonts_cb_pattern(GList           *list,
				const FcPattern *pattern)
{
	return g_list_append(list, FcPatternDuplicate(pattern));
}

static GList *
_ezfc_font_get_available_features(GList     *retval,
				  hb_face_t *hbface,
				  hb_tag_t   table_tag)
{
	unsigned int count;
	hb_tag_t *p;
	int i;

	count = hb_ot_layout_table_get_feature_tags(hbface, table_tag, 0, NULL, NULL);
	p = g_new0(hb_tag_t, count + 1);
	hb_ot_layout_table_get_feature_tags(hbface, table_tag, 0, &count, p);
	p[count] = 0;
	for (i = 0; i < count; i++) {
		gchar *tag = g_malloc(sizeof (char) * 5);

		tag[0] = (p[i] >> 24) & 0xff;
		tag[1] = (p[i] >> 16) & 0xff;
		tag[2] = (p[i] >>  8) & 0xff;
		tag[3] = (p[i]      ) & 0xff;
		tag[4] = 0;
		if (!g_list_find_custom(retval, tag, (GCompareFunc)strcmp))
			retval = g_list_append(retval, tag);
		else
			g_free(tag);
	}

	return retval;
}

static gchar *
_ezfc_font_escape_fontname(const gchar *font_name)
{
	GString *retval = g_string_new(NULL);
	size_t i, n = strlen(font_name);

	/* fontconfig uses -,: as a separator to operate FcNameParse().
	 * if the font name contains any of them, it may causes
	 * unexpected result. so those has to be escaped.
	 */
	for (i = 0; i < n; i++) {
		if (font_name[i] == '-' ||
		    font_name[i] == ',' ||
		    font_name[i] == ':')
			g_string_append_c(retval, '\\');
		g_string_append_c(retval, font_name[i]);
	}

	return g_string_free(retval, FALSE);
}

/*< protected >*/
void
ezfc_font_init(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->check_font_existence = TRUE;
}

/*< public >*/

/**
 * ezfc_font_is_alias_font:
 * @alias_name: the alias font name
 *
 * Checks if @alias_name is one of sans-serif, serif, monospace, cursive, fantasy,
 * emoji or math.
 *
 * Returns: %TRUE if @alias_name is an alias font name, otherwise %FALSE.
 */
gboolean
ezfc_font_is_alias_font(const gchar *alias_name)
{
	ezfc_font_variant_t t = _ezfc_font_get_font_type(alias_name);

	return t != EZFC_FONT_UNKNOWN;
}

/**
 * ezfc_font_get_list:
 * @language: (allow-none): the language name fontconfig can deal with.
 * @alias_name: (allow-none): the alias name to obtain the fonts list for.
 * @localized_font_name: %TRUE to include the localized font name if available,
 *                       %FALSE for English font name only.
 *
 * Obtains the fonts list being assigned to @alias_name for @language.
 *
 * Note that @localized_font_name doesn't take effect yet. this is just
 * a reservation for future improvement.
 *
 * Returns: (element-type utf8) (transfer full): a #GList contains the font family name.
 *          if no valid families, %NULL then.
 */
GList *
ezfc_font_get_list(const gchar *language,
		   const gchar *alias_name,
		   gboolean     localized_font_name)
{
	return _ezfc_font_get_fonts_proc(language, alias_name,
					 localized_font_name,
					 _ezfc_font_get_fonts_cb_family);
}

/**
 * ezfc_font_get_pattern_list:
 * @language: (allow-none): the language name fontconfig can deal with.
 * @alias_name: (allow-none): the alias name to obtain the fonts pettern list for.
 *
 * Obtains #FcPattern list being assigned to @alias_name for @language.
 *
 * Returns: (element-type FcPattern) (transfer full): a #GList contains #FcPattern, otherwise %NULL.
 */
GList *
ezfc_font_get_pattern_list(const gchar *language,
			   const gchar *alias_name)
{
	return _ezfc_font_get_fonts_proc(language, alias_name, TRUE,
					 _ezfc_font_get_fonts_cb_pattern);
}

/**
 * ezfc_font_get_alias_name_from_pattern:
 * @pattern: a #FcPattern.
 *
 * Analize @pattern and returns a alias name string according to the result.
 *
 * Returns: (transfer container) (element-type utf8): a #GList containing
 *          a static string for the alias name.
 */
GList *
ezfc_font_get_alias_name_from_pattern(const FcPattern *pattern)
{
	GList *retval = NULL;
	ezfc_font_variant_t type = _ezfc_font_get_font_type_from_pattern(pattern);
	gint i, j;

	if (type == EZFC_FONT_UNKNOWN)
		return NULL;

	for (i = EZFC_FONT_SANS, j = 1; i < EZFC_FONT_END; i <<= 1, j++) {
		if ((type & i) == i)
			retval = g_list_append(retval, __ezfc_font_alias_names[j]);
	}

	return retval;
}

/**
 * ezfc_font_new:
 *
 * Create an instance of #ezfc_font_t.
 *
 * Returns: a #ezfc_font_t.
 */
ezfc_font_t *
ezfc_font_new(void)
{
	ezfc_font_private_t *retval = ezfc_mem_alloc_object(sizeof (ezfc_font_private_t));

	if (retval) {
		ezfc_font_init((ezfc_font_t *)retval);
	}

	return (ezfc_font_t *)retval;
}

/**
 * ezfc_font_ref:
 * @font: a #ezfc_font_t.
 *
 * Increases the reference count of @font.
 *
 * Returns: (transfer none): the same @font object.
 */
ezfc_font_t *
ezfc_font_ref(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	return ezfc_mem_ref(&priv->parent);
}

/**
 * ezfc_font_unref:
 * @font: a #ezfc_font_t.
 *
 * Decreases the reference count of @font. when its reference count
 * drops to 0, the object is finalized (i.e. its memory is freed).
 */
void
ezfc_font_unref(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	if (priv)
		ezfc_mem_unref(&priv->parent);
}

/**
 * ezfc_font_get_pattern:
 * @font: a #ezfc_font_t.
 *
 * Obtains #FcPattern in #ezfc_font_t.
 *
 * Returns: a duplicate of #FcPattern in the instance. it has to be freed.
 *          %NULL if @font doesn't have any font pattern.
 */
FcPattern *
ezfc_font_get_pattern(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern)
		return FcPatternDuplicate(priv->pattern);

	return NULL;
}

/**
 * ezfc_font_set_pattern:
 * @font: a #ezfc_font_t.
 * @pattern: a #FcPattern.
 * @error: (allow-none): a #GError.
 *
 * Set @pattern as the font pattern. @font keeps a duplicate instance of
 * @pattern.
 *
 * Returns: %TRUE if it successfully is set. otherwise %FALSE.
 */
gboolean
ezfc_font_set_pattern(ezfc_font_t      *font,
		      const FcPattern  *pattern,
		      GError          **error)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	FcPattern *pat;
	gboolean retval = TRUE;
	GError *err = NULL;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (pattern != NULL, FALSE);

	pat = FcPatternDuplicate(pattern);
	if (!pat) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_OOM,
			    "Unable to make a duplicate of FcPattern");
		retval = FALSE;
		goto bail;
	}
	if (priv->pattern)
		ezfc_mem_remove_ref(&priv->parent, priv->pattern);
	priv->pattern = pat;
	ezfc_mem_add_ref(&priv->parent, pat, (ezfc_destroy_func_t)FcPatternDestroy);

  bail:
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}

	return retval;
}

/**
 * ezfc_font_get_family:
 * @font: a #ezfc_font_t.
 *
 * Obtains the font family name in first place in @font.
 *
 * Returns: the font name.
 */
const gchar *
ezfc_font_get_family(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	gchar *family = NULL;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern &&
	    FcPatternGetString(priv->pattern, FC_FAMILY, 0, (FcChar8 **)&family) != FcResultMatch)
		return NULL;

	return family;
}

/**
 * ezfc_font_get_families:
 * @font: a #ezfc_font_t.
 *
 * Obtains font family names in @font.
 *
 * Returns: (transfer container) (element-type utf8): a #GList containing
 *          the static string of font family names
 *
 * Since: 0.11
 */
GList *
ezfc_font_get_families(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	GList *retval = NULL;
	FcChar8 *s;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern) {
		int i;

		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern,
					       FC_FAMILY, i, &s) != FcResultMatch)
				break;
			retval = g_list_append(retval, s);
		}
	}

	return retval;
}

/**
 * ezfc_font_find:
 * @font: a #ezfc_font_t.
 * @font_name: a font name.
 *
 * Check if @font contains @font_name.
 *
 * Returns: %TRUE if it contains, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_font_find(ezfc_font_t *font,
	       const gchar *font_name)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	gboolean retval = FALSE;
	FcChar8 *s;
	int i;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (font_name != NULL, FALSE);

	if (priv->pattern) {
		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern, FC_FAMILY, i, &s) != FcResultMatch)
				break;
			if (g_ascii_strcasecmp(font_name, (const gchar *)s) == 0) {
				retval = TRUE;
				break;
			}
		}
	}

	return retval;
}

/**
 * ezfc_font_set_family:
 * @font: a #ezfc_font_t.
 * @font_name: a font name.
 * @error: (allow-none): a #GError.
 *
 * Set @font_name as the font family name used for the font font.
 *
 * Returns: %TRUE if it successfully is set. otherwise %FALSE.
 *
 * Deprecated: 0.11. Use ezfc_font_add_family().
 */
gboolean
ezfc_font_set_family(ezfc_font_t   *font,
		     const gchar   *font_name,
		     GError       **error)
{
	return ezfc_font_add_family(font, font_name, error);
}

/**
 * ezfc_font_add_family:
 * @font: a #ezfc_font_t.
 * @font_name: a font name.
 * @error: (allow-none): a #GError.
 *
 * Add @font_name as the font family name used for the font font.
 *
 * Returns: %TRUE if it successfully is set. otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_font_add_family(ezfc_font_t   *font,
		     const gchar   *font_name,
		     GError       **error)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	FcPattern *pat, *match = NULL;
	FcResult result;
	gboolean retval = TRUE;
	GError *err = NULL;
	gchar *escaped_font_name;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (font_name != NULL, FALSE);

	escaped_font_name = _ezfc_font_escape_fontname(font_name);
	pat = FcNameParse((FcChar8 *)escaped_font_name);
	if (!pat) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_FC,
			    "Unable to parse `%s'",
			    font_name);
		retval = FALSE;
		goto bail;
	}
	if (!priv->check_font_existence) {
		retval = ezfc_font_set_pattern(font, pat, &err);
		goto bail;
	}
	if ((match = FcFontMatch(NULL, pat, &result))) {
		FcChar8 *v1, *v2;
		int i;

		if (FcPatternGetString(pat, FC_FAMILY, 0, &v1) != FcResultMatch) {
			g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_FAMILY,
				    "Pattern doesn't contain any family name");
			retval = FALSE;
			goto bail1;
		}
		/* may need to check all of values assigned to the specific object
		 * because it might contains multiple values. e.g. family name in
		 * different language.
		 */
		for (i = 0; ; i++) {
			if (FcPatternGetString(match, FC_FAMILY, i, &v2) != FcResultMatch) {
				if (i == 0)
					g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_FAMILY,
						    "Pattern doesn't contain any family name");
				else
					g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_VALID_FONT,
						    "No such fonts available: %s",
						    font_name);
				retval = FALSE;
				goto bail1;
			}
			if (FcStrCmpIgnoreCase(v1, v2) == 0) {
				retval = ezfc_font_set_pattern(font, pat, &err);
				break;
			}
		}
	  bail1:
		FcPatternDestroy(match);
	} else {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_VALID_FONT,
			    "No such fonts available: %s",
			    font_name);
		retval = FALSE;
		goto bail;
	}
  bail:
	if (pat)
		FcPatternDestroy(pat);
	g_free(escaped_font_name);

	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}

	return retval;
}

/**
 * ezfc_font_remove:
 * @font: a #ezfc_font_t.
 * @error: (allow-none): a #GError.
 *
 * Removes all of families in @font.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_font_remove(ezfc_font_t  *font,
		 GError      **error)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	gboolean retval = FALSE;
	GError *err = NULL;

	g_return_val_if_fail (font != NULL, FALSE);

	if (priv->pattern) {
		if (FcPatternDel(priv->pattern, FC_FAMILY)) {
			retval = TRUE;
		} else {
			g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_FC,
				    "Pattern doesn't contain any family name");
		}
	} else {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_FAMILY,
			    "No patterns available");
	}
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}

	return retval;
}

/**
 * ezfc_font_remove_family:
 * @font: a #ezfc_font_t.
 * @font_name: a font name to be removed.
 * @error: (allow-none): a #GError.
 *
 * Removes @font_name from @font.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_font_remove_family(ezfc_font_t  *font,
			const gchar  *font_name,
			GError      **error)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	gboolean retval = FALSE;
	GError *err = NULL;
	int i;
	FcChar8 *s;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (font_name != NULL, FALSE);

	if (priv->pattern) {
		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern, FC_FAMILY, i, &s) != FcResultMatch)
				break;
			if (g_ascii_strcasecmp(font_name, (const gchar *)s) == 0) {
				if (FcPatternRemove(priv->pattern, FC_FAMILY, i)) {
					retval = TRUE;
				} else {
					g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_FC,
						    "Unable to remove a family: %s",
						    font_name);
				}
				break;
			}
		}
	}

	return retval;
}

/**
 * ezfc_font_check_existence:
 * @font: a #ezfc_font_t.
 * @flag: a boolean value.
 *
 * Set a flag whether checking the font existence when invoking
 * ezfc_font_set_family().
 */
void
ezfc_font_check_existence(ezfc_font_t *font,
			  gboolean     flag)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->check_font_existence = (flag == TRUE);
}

/**
 * ezfc_font_get_check_existence:
 * @font: a #ezfc_font_t
 *
 * Obtain a flag in @font if it's supposed to check the font existence when invoking
 *
 * Returns: %TRUE if it does otherwise %FALSE.
 */
gboolean
ezfc_font_get_check_existence(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);

	return priv->check_font_existence;
}

/**
 * ezfc_font_set_hinting:
 * @font: a #ezfc_font_t.
 * @flag: a boolean value.
 *
 * Set a flag whether the font use the own hints for rendering
 */
void
ezfc_font_set_hinting(ezfc_font_t *font,
		      gboolean     flag)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->masks |= EZFC_FONT_MASK_HINTING;
	priv->hinting = (flag == TRUE);
}

/**
 * ezfc_font_get_hinting:
 * @font: a #ezfc_font_t.
 *
 * Obtain a boolean value about the hinting usage in @font.
 *
 * Returns: %TRUE if the hinting is enabled. otherwise %FALSE.
 */
gboolean
ezfc_font_get_hinting(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);

	return priv->hinting;
}

/**
 * ezfc_font_set_autohinting:
 * @font: a #ezfc_font_t.
 * @flag: a boolean value.
 *
 * Set a flag whether the font use the auto-hinting for rendering
 */
void
ezfc_font_set_autohinting(ezfc_font_t *font,
			  gboolean     flag)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->masks |= EZFC_FONT_MASK_AUTOHINT;
	priv->autohinting = (flag == TRUE);
}

/**
 * ezfc_font_get_autohinting:
 * @font: a #ezfc_font_t.
 *
 * Obtain a boolean value about the auto-hinting usage in @font.
 *
 * Returns: %TRUE if the auto-hinting is enabled. otherwise %FALSE.
 */
gboolean
ezfc_font_get_autohinting(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);

	return priv->autohinting;
}

/**
 * ezfc_font_set_hintstyle:
 * @font: a #ezfc_font_t.
 * @hintstyle: a #ezfc_font_hintstyle_t.
 *
 * Set a hintstyle for @font.
 */
void
ezfc_font_set_hintstyle(ezfc_font_t           *font,
			ezfc_font_hintstyle_t  hintstyle)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);
	g_return_if_fail (hintstyle > EZFC_FONT_HINTSTYLE_UNKNOWN && hintstyle < EZFC_FONT_HINTSTYLE_END);

	priv->masks |= EZFC_FONT_MASK_HINTSTYLE;
	priv->hintstyle = hintstyle;
}

/**
 * ezfc_font_get_hintstyle:
 * @font: a #ezfc_font_t.
 *
 * Obtain the hintstyle in @font.
 *
 * Returns: a #ezfc_font_hintstyle_t.
 */
ezfc_font_hintstyle_t
ezfc_font_get_hintstyle(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, EZFC_FONT_HINTSTYLE_UNKNOWN);

	return priv->hintstyle;
}

/**
 * ezfc_font_set_antialiasing:
 * @font: a #ezfc_font_t.
 * @flag: a boolean value.
 *
 * Set a flag whether the font use the antialiasing.
 */
void
ezfc_font_set_antialiasing(ezfc_font_t *font,
			   gboolean     flag)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->masks |= EZFC_FONT_MASK_ANTIALIAS;
	priv->antialiasing = (flag == TRUE);
}

/**
 * ezfc_font_get_antialiasing:
 * @font: a #ezfc_font_t.
 *
 * Obtain a boolean value about the anti-aliasing usage in @font.
 *
 * Returns: %TRUE if the antialiasing is enabled. otherwise %FALSE.
 */
gboolean
ezfc_font_get_antialiasing(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);

	return priv->antialiasing;
}

/**
 * ezfc_font_set_embedded_bitmap:
 * @font: a #ezfc_font_t.
 * @flag: a boolean value.
 *
 * Set a flag whether the font use the embedded bitmap.
 * Note that Enabling the embedded bitmap may causes disabling the antialias.
 */
void
ezfc_font_set_embedded_bitmap(ezfc_font_t *font,
			      gboolean     flag)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->masks |= EZFC_FONT_MASK_EMBEDDED_BITMAP;
	priv->embedded_bitmap = (flag == TRUE);
}

/**
 * ezfc_font_get_embedded_bitmap:
 * @font: a #ezfc_font_t.
 *
 * Obtain a boolean value about the embedded bitmap usage in @font.
 *
 * Returns: %TRUE if the embedded bitmap is enabled, otherwise %FALSE.
 */
gboolean
ezfc_font_get_embedded_bitmap(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);

	return priv->embedded_bitmap;
}

/**
 * ezfc_font_set_rgba:
 * @font: a #ezfc_font_t.
 * @val: an integer value corresponding to FC_RGBA_*.
 *
 * Set @val as the sub-pixel ordering
 */
void
ezfc_font_set_rgba(ezfc_font_t *font,
		   gint         val)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_if_fail (font != NULL);

	priv->masks |= EZFC_FONT_MASK_RGBA;
	priv->rgba = val;
}

/**
 * ezfc_font_get_rgba:
 * @font: a #ezfc_font_t.
 *
 * Obtains current sub-pixel ordering in @font.
 *
 * Returns: the sub-pixel ordering value in the integer.
 */
gint
ezfc_font_get_rgba(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FC_RGBA_UNKNOWN);

	return priv->rgba;
}

/**
 * ezfc_font_get_boolean_masks:
 * @font: a #ezfc_font_t.
 *
 * Obtain masks that any boolean values are set to @font.
 * This is set when ezfc_font_set_hinting(), ezfc_font_set_autohinting()
 * and ezfc_font_set_antialiasing() is called.
 *
 * Returns: an integer value
 */
gint
ezfc_font_get_boolean_masks(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, 0);

	return priv->masks;
}

/**
 * ezfc_font_set_subpixel_rendering:
 * @font: a #ezfc_font_t.
 * @mode: a #ezfc_font_subpixel_render_t.
 *
 * This is just convenient to change the several configuration for subpixel
 * rendering.
 *
 * Returns: %TRUE if the sub-pixel rendering is enabled. otherwise %FALSE.
 */
gboolean
ezfc_font_set_subpixel_rendering(ezfc_font_t                 *font,
				 ezfc_font_subpixel_render_t  mode)
{
	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (mode < EZFC_FONT_ANTIALIAS_END, FALSE);

	switch (mode) {
	    case EZFC_FONT_ANTIALIAS_NONE:
		    ezfc_font_set_antialiasing(font, FALSE);
		    ezfc_font_set_rgba(font, FC_RGBA_NONE);
		    break;
	    case EZFC_FONT_ANTIALIAS_GRAY:
		    ezfc_font_set_antialiasing(font, TRUE);
		    ezfc_font_set_rgba(font, FC_RGBA_NONE);
		    break;
	    case EZFC_FONT_ANTIALIAS_RGB:
		    ezfc_font_set_antialiasing(font, TRUE);
		    ezfc_font_set_rgba(font, FC_RGBA_RGB);
		    break;
	    case EZFC_FONT_ANTIALIAS_BGR:
		    ezfc_font_set_antialiasing(font, TRUE);
		    ezfc_font_set_rgba(font, FC_RGBA_BGR);
		    break;
	    case EZFC_FONT_ANTIALIAS_VRGB:
		    ezfc_font_set_antialiasing(font, TRUE);
		    ezfc_font_set_rgba(font, FC_RGBA_VRGB);
		    break;
	    case EZFC_FONT_ANTIALIAS_VBGR:
		    ezfc_font_set_antialiasing(font, TRUE);
		    ezfc_font_set_rgba(font, FC_RGBA_VBGR);
		    break;
	    default:
		    g_return_val_if_reached (FALSE);
	}

	return TRUE;
}

/**
 * ezfc_font_get_subpixel_rendering:
 * @font: a #ezfc_font_t.
 *
 * Obtain current status about the sub-pixel rendering in @font.
 *
 * Returns: current mode in the sub-pixel rendering.
 */
ezfc_font_subpixel_render_t
ezfc_font_get_subpixel_rendering(ezfc_font_t *font)
{
	gboolean antialias;
	gint rgba;
	ezfc_font_subpixel_render_t retval;

	g_return_val_if_fail (font != NULL, EZFC_FONT_ANTIALIAS_UNKNOWN);

	antialias = ezfc_font_get_antialiasing(font);
	rgba = ezfc_font_get_rgba(font);

	if (antialias) {
		switch (rgba) {
		    case FC_RGBA_NONE:
			    retval = EZFC_FONT_ANTIALIAS_GRAY;
			    break;
		    case FC_RGBA_RGB:
			    retval = EZFC_FONT_ANTIALIAS_RGB;
			    break;
		    case FC_RGBA_BGR:
			    retval = EZFC_FONT_ANTIALIAS_BGR;
			    break;
		    case FC_RGBA_VRGB:
			    retval = EZFC_FONT_ANTIALIAS_VRGB;
			    break;
		    case FC_RGBA_VBGR:
			    retval = EZFC_FONT_ANTIALIAS_VBGR;
			    break;
		    default:
			    retval = EZFC_FONT_ANTIALIAS_UNKNOWN;
			    break;
		}
	} else {
		/* no matter what rgba is, if antialias is false,
		 * it should be None.
		 */
		retval = EZFC_FONT_ANTIALIAS_NONE;
	}

	return retval;
}

/**
 * ezfc_font_add_feature:
 * @font: a #ezfc_font_t.
 * @feature: feature name to be added
 *
 * Add @feature font feature to @font.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.12
 */
gboolean
ezfc_font_add_feature(ezfc_font_t *font,
		      const gchar *feature)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	GList *l;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (feature != NULL && feature[0] != 0, FALSE);

	if (!priv->pattern)
		priv->pattern = FcPatternCreate();

	l = ezfc_font_get_features(font);
	if (g_list_find_custom(l, feature, (GCompareFunc)strcmp)) {
		g_list_free(l);
		return TRUE;
	}
	g_list_free(l);

	return FcPatternAddString(priv->pattern, FC_FONT_FEATURES, (const FcChar8 *)feature);
}

/**
 * ezfc_font_remove_feature:
 * @font: a #ezfc_font_t.
 * @feature: feature name to be removed
 *
 * Remove @feature from @font if available.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.12
 */
gboolean
ezfc_font_remove_feature(ezfc_font_t *font,
			 const gchar *feature)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;

	g_return_val_if_fail (font != NULL, FALSE);
	g_return_val_if_fail (feature != NULL && feature[0] != 0, FALSE);

	if (priv->pattern) {
		int i;
		FcChar8 *s;

		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern,
					       FC_FONT_FEATURES, i,
					       &s) == FcResultMatch) {
				return FcPatternRemove(priv->pattern,
						       FC_FONT_FEATURES,
						       i);
			}
		}
	}

	return FALSE;
}

/**
 * ezfc_font_get_features:
 * @font: a #ezfc_font_t.
 *
 * Obtains font features list that @font has.
 *
 * Returns: (transfer container) (element-type utf8): a #GList containing
 *          the static string of feature name.
 *
 * Since: 0.12
 */
GList *
ezfc_font_get_features(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	GList *retval = NULL;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern) {
		int i;
		FcChar8 *s;

		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern,
					       FC_FONT_FEATURES, i,
					       &s) != FcResultMatch)
				break;
			retval = g_list_append(retval, s);
		}
	}

	return retval;
}

/**
 * ezfc_font_get_available_features:
 * @font: a #ezfc_font_t.
 *
 * Obtains available font features in @font.
 *
 * Returns: (transfer full) (element-type utf8): a #GList containing
 *          memory-allocated string of feature name that is available
 *          in @font. strings in #GList has to be freed when it isn't
 *          needed anymore.
 *
 * Since: 0.12
 */
GList *
ezfc_font_get_available_features(ezfc_font_t *font)
{
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	GList *retval = NULL;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern) {
		FcPattern *match = NULL;
		FcResult result;
		FT_Library ftlib;

		if (FT_Init_FreeType(&ftlib) != FT_Err_Ok)
			return NULL;
		if ((match = FcFontMatch(NULL, priv->pattern, &result))) {
			FcChar8 *file;
			FT_Face face;
			int idx;
			hb_face_t *hbface;

			if (FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch) {
				goto bail;
			}
			if (FcPatternGetInteger(match, FC_INDEX, 0, &idx) != FcResultMatch)
				idx = 0;
			if (FT_New_Face(ftlib, (const char *)file, idx, &face) != FT_Err_Ok)
				goto bail;
			hbface = hb_ft_face_create_cached(face);
			retval = _ezfc_font_get_available_features(retval, hbface, HB_OT_TAG_GDEF);
			retval = _ezfc_font_get_available_features(retval, hbface, HB_OT_TAG_GSUB);
			retval = _ezfc_font_get_available_features(retval, hbface, HB_OT_TAG_GPOS);
			hb_face_destroy(hbface);
			FT_Done_Face(face);
		}
	  bail:
		FcPatternDestroy(match);
		FT_Done_FreeType(ftlib);
	}

	return retval;
}

/**
 * ezfc_font_canonicalize:
 * @font: a #ezfc_font_t.
 * @error: (allow-none): a #GError.
 *
 * Split up @font to #ezfc_font_t that has one family name only.
 *
 * Returns: (transfer full) (element-type ezfc_font_t): a #GList contains
 *          #ezfc_font_t, otherwise %NULL.
 *
 * Since: 0.11
 */
GList *
ezfc_font_canonicalize(ezfc_font_t  *font,
		       GError      **error)
{
	GList *retval = NULL;
	ezfc_font_private_t *priv = (ezfc_font_private_t *)font;
	GError *err = NULL;

	g_return_val_if_fail (font != NULL, NULL);

	if (priv->pattern) {
		int i;
		FcChar8 *s;
		ezfc_font_t *f;
		FcPattern *pat;

		for (i = 0; ; i++) {
			if (FcPatternGetString(priv->pattern, FC_FAMILY, i, &s) != FcResultMatch)
				break;
			f = ezfc_font_new();
			if (!f) {
				g_set_error(&err, EZFC_ERROR, EZFC_ERR_OOM,
					    "Unable to create a canonicalized font instance");
				goto bail;
			}
			memcpy(&((ezfc_font_private_t *)f)->masks,
			       &priv->masks, sizeof (ezfc_font_private_t) - G_STRUCT_OFFSET (ezfc_font_private_t, masks));
			pat = FcPatternDuplicate(priv->pattern);
			FcPatternDel(pat, FC_FAMILY);
			ezfc_font_set_pattern(f, pat, &err);
			FcPatternDestroy(pat);
			if (!ezfc_font_add_family(f, (const gchar *)s, &err)) {
				ezfc_font_unref(f);
				goto bail;
			}
			retval = g_list_append(retval, f);
		}
	}
  bail:
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}

	return retval;
}