/* Pango
 * pangocoretext-fontmap.c
 *
 * Copyright (C) 2000-2003 Red Hat, Inc.
 * Copyright (C) 2005-2007 Imendio AB
 * Copyright (C) 2010  Kristian Rietveld  <kris@gtk.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include "pango-fontmap.h"
#include "pangocoretext-private.h"
#include "pango-impl-utils.h"
#include "modules.h"

#include <Carbon/Carbon.h>

typedef struct _FontHashKey      FontHashKey;

typedef struct _PangoCoreTextFontset PangoCoreTextFontset;

#define PANGO_TYPE_CORE_TEXT_FAMILY              (pango_core_text_family_get_type ())
#define PANGO_CORE_TEXT_FAMILY(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_TYPE_CORE_TEXT_FAMILY, PangoCoreTextFamily))
#define PANGO_IS_CORE_TEXT_FAMILY(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_TYPE_CORE_TEXT_FAMILY))
#define PANGO_CORE_TEXT_FAMILY_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_CORE_TEXT_FAMILY, PangoCoreTextFamilyClass))
#define PANGO_IS_CORE_TEXT_FAMILY_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_CORE_TEXT_FAMILY))
#define PANGO_CORE_TEXT_FAMILY_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), PANGO_CORE_TEXT_FAMILY, PangoCoreTextFamilyClass))

#define PANGO_TYPE_CORE_TEXT_FONTSET           (pango_core_text_fontset_get_type ())
#define PANGO_CORE_TEXT_FONTSET(object)        (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_TYPE_CORE_TEXT_FONTSET, PangoCoreTextFontset))
#define PANGO_IS_CORE_TEXT_FONTSET(object)     (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_TYPE_CORE_TEXT_FONTSET))


struct _PangoCoreTextFamily
{
  PangoFontFamily parent_instance;

  char *family_name;

  guint is_monospace : 1;

  PangoFontFace **faces;
  gint n_faces;
};

struct _PangoCoreTextFamilyClass
{
  PangoFontFamilyClass parent_class;
};

typedef struct _PangoCoreTextFamilyClass PangoCoreTextFamilyClass;

#define PANGO_TYPE_CORE_TEXT_FACE              (pango_core_text_face_get_type ())
#define PANGO_CORE_TEXT_FACE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_TYPE_CORE_TEXT_FACE, PangoCoreTextFace))
#define PANGO_IS_CORE_TEXT_FACE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_TYPE_CORE_TEXT_FACE))
#define PANGO_CORE_TEXT_FACE_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_CORE_TEXT_FACE, PangoCoreTextFaceClass))
#define PANGO_IS_CORE_TEXT_FACE_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_CORE_TEXT_FACE))
#define PANGO_CORE_TEXT_FACE_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), PANGO_CORE_TEXT_FACE, PangoCoreTextFaceClass))

struct _PangoCoreTextFace
{
  PangoFontFace parent_instance;

  PangoCoreTextFamily *family;

  CTFontDescriptorRef ctfontdescriptor;

  char *style_name;
  PangoWeight weight;
  CTFontSymbolicTraits traits;
  guint synthetic_italic : 1;
};

struct _PangoCoreTextFaceClass
{
  PangoFontFaceClass parent_class;
};

typedef struct _PangoCoreTextFaceClass PangoCoreTextFaceClass;

static GType pango_core_text_family_get_type (void);
static GType pango_core_text_face_get_type (void);
static GType pango_core_text_fontset_get_type (void);

static PangoCoreTextFontset    *pango_core_text_fontset_new     (PangoCoreTextFontsetKey    *key,
                                                                 const PangoFontDescription *description);
static PangoCoreTextFontsetKey *pango_core_text_fontset_get_key (PangoCoreTextFontset       *fontset);

/*
 * Helper functions to translate CoreText data to Pango
 */

typedef struct
{
    float bound;
    PangoWeight weight;
} PangoCTWeight;

const float ct_weight_min = -1.00f;
const float ct_weight_max = 1.00f;

static const PangoCTWeight ct_weight_limits[] = {
    { -0.70, PANGO_WEIGHT_THIN},
    { -0.50, PANGO_WEIGHT_ULTRALIGHT },
    { -0.35, PANGO_WEIGHT_LIGHT },
    { -0.10, PANGO_WEIGHT_BOOK },
    {  0.10, PANGO_WEIGHT_NORMAL },
    {  0.24, PANGO_WEIGHT_MEDIUM },
    {  0.36, PANGO_WEIGHT_SEMIBOLD },
    {  0.50, PANGO_WEIGHT_BOLD },
    {  0.75, PANGO_WEIGHT_ULTRABOLD },
    {  1.00, PANGO_WEIGHT_HEAVY }
};

static const char *
get_real_family (const char *family_name)
{
  switch (family_name[0])
    {
    case 'm':
    case 'M':
      if (g_ascii_strcasecmp (family_name, "monospace") == 0)
	return "Courier";
      break;
    case 's':
    case 'S':
      if (g_ascii_strcasecmp (family_name, "sans") == 0)
	return "Helvetica";
      else if (g_ascii_strcasecmp (family_name, "serif") == 0)
	return "Times";
      break;
    }

  return family_name;
}

static gchar *
gchar_from_cf_string (CFStringRef str)
{
  CFIndex len;
  gchar *buffer;

  /* GetLength returns the number of UTF-16 pairs, so this number
   * times 2 should definitely gives us enough space for UTF8.
   * We add one for the terminating zero.
   */
  len = CFStringGetLength (str) * 2 + 1;
  buffer = g_new0 (char, len);
  CFStringGetCString (str, buffer, len, kCFStringEncodingUTF8);

  return buffer;
}

static char *
ct_font_descriptor_get_family_name (CTFontDescriptorRef desc,
                                    gboolean            may_fail)
{
  CFStringRef cf_str;
  char *buffer;

  cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontFamilyNameAttribute);
  if (!cf_str)
    {
      int i;

      /* No font family name is set, try to retrieve font name and deduce
       * the family name from that instead.
       */
      cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontNameAttribute);
      if (!cf_str)
        {
          if (may_fail)
            return NULL;

          /* This font is likely broken, return a default family name ... */
          return g_strdup ("Sans");
        }

      buffer = gchar_from_cf_string (cf_str);
      CFRelease (cf_str);

      for (i = 0; i < strlen (buffer); i++)
        if (buffer[i] == '-')
          break;

      if (i < strlen (buffer))
        {
          char *ret;

          ret = g_strndup (buffer, i);
          g_free (buffer);

          return ret;
        }
      else
        return buffer;
    }
  /* else */

  buffer = gchar_from_cf_string (cf_str);
  CFRelease (cf_str);

  return buffer;
}

static char *
ct_font_descriptor_get_style_name (CTFontDescriptorRef desc)
{
  CFStringRef cf_str;
  char *buffer;

  cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontStyleNameAttribute);
  if (!cf_str)
    return NULL;

  buffer = gchar_from_cf_string (cf_str);
  CFRelease (cf_str);

  return buffer;
}

static CTFontSymbolicTraits
ct_font_descriptor_get_traits (CTFontDescriptorRef desc)
{
  CFDictionaryRef dict;
  CFNumberRef cf_number;
  SInt64 traits;

  /* This is interesting, the value stored is a CTFontSymbolicTraits which
   * is defined as uint32_t.  CFNumber does not have an obvious type which
   * deals with unsigned values.  Upon inspection with CFNumberGetType,
   * it turns out this value is stored as SInt64, so we use that to
   * obtain the value from the CFNumber.
   */
  dict = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
  cf_number = (CFNumberRef)CFDictionaryGetValue (dict, kCTFontSymbolicTrait);
  if (!CFNumberGetValue (cf_number, kCFNumberSInt64Type, &traits))
    traits = 0;
  CFRelease (dict);

  return (CTFontSymbolicTraits)traits;
}

static CTFontDescriptorRef
cf_font_descriptor_copy_with_traits (CTFontDescriptorRef        desc,
                                     const CTFontSymbolicTraits traits)
{
  CFMutableDictionaryRef dict, traits_dict;
  CFDictionaryRef tmp;
  CTFontDescriptorRef new_desc;
  SInt64 tmp_traits;

  tmp = CTFontDescriptorCopyAttributes (desc);
  dict = CFDictionaryCreateMutableCopy (kCFAllocatorDefault, 0, tmp);
  CFRelease (tmp);

  tmp = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
  traits_dict = CFDictionaryCreateMutableCopy (kCFAllocatorDefault, 0, tmp);
  CFRelease (tmp);

  tmp_traits = traits;
  CFDictionarySetValue (traits_dict, (CFTypeRef) kCTFontSymbolicTrait,
                        CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &tmp_traits));

  CFDictionarySetValue (dict, (CFTypeRef)kCTFontTraitsAttribute, traits_dict);

  new_desc = CTFontDescriptorCreateCopyWithAttributes (desc, dict);
  CFRelease (dict);

  return new_desc;
}

static PangoWeight
ct_font_descriptor_get_weight (CTFontDescriptorRef desc)
{
  CFDictionaryRef dict;
  CFNumberRef cf_number;
  CGFloat value;
  PangoWeight weight;

  dict = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
  cf_number = (CFNumberRef)CFDictionaryGetValue (dict,
                                                 kCTFontWeightTrait);

  if (CFNumberGetValue (cf_number, kCFNumberCGFloatType, &value))
    {
      if (value < ct_weight_min || value > ct_weight_max)
	{
          /* This is really an error */
          weight = PANGO_WEIGHT_NORMAL;
	}
      else
	{
	  guint i;
	  for (i = 0; i < G_N_ELEMENTS(ct_weight_limits); i++)
	    if (value < ct_weight_limits[i].bound)
	      {
                weight = ct_weight_limits[i].weight;
                break;
	      }
	}
    }
 else
   weight = PANGO_WEIGHT_NORMAL;

  CFRelease (dict);

  return weight;
}

static gboolean
ct_font_descriptor_is_small_caps (CTFontDescriptorRef desc)
{
  CFIndex i, count;
  CFArrayRef array;
  CFStringRef str;
  gboolean retval = FALSE;

  /* See http://stackoverflow.com/a/4811371 for why this works and an
   * explanation of the magic number "3" used below.
   */
  array = CTFontDescriptorCopyAttribute (desc, kCTFontFeaturesAttribute);
  if (!array)
    return FALSE;

  str = CFStringCreateWithCString (NULL, "CTFeatureTypeIdentifier",
                                   kCFStringEncodingASCII);

  count = CFArrayGetCount (array);
  for (i = 0; i < count; i++)
    {
      CFDictionaryRef dict = CFArrayGetValueAtIndex (array, i);
      CFNumberRef num;

      num = (CFNumberRef)CFDictionaryGetValue (dict, str);
      if (num)
        {
          int value = 0;

          if (CFNumberGetValue (num, kCFNumberSInt32Type, &value) &&
              value == 3)
            {
              /* This font supports small caps. */
              retval = TRUE;
              break;
            }
        }
    }

  CFRelease (str);
  CFRelease (array);

  return retval;
}

static inline gboolean
pango_core_text_style_name_is_oblique (const char *style_name)
{
  if (!style_name)
    return FALSE;

  return g_strrstr (style_name, "Oblique") != NULL;
}

PangoFontDescription *
_pango_core_text_font_description_from_ct_font_descriptor (CTFontDescriptorRef desc)
{
  SInt64 font_traits;
  char *family_name;
  char *style_name;
  PangoFontDescription *font_desc;

  font_desc = pango_font_description_new ();

  /* Family name */

  /* FIXME: Should we actually retrieve the family name from the list of
   * families in a font map?
   */
  family_name = ct_font_descriptor_get_family_name (desc, FALSE);
  pango_font_description_set_family (font_desc, family_name);
  g_free (family_name);

  /* Weight */
  pango_font_description_set_weight (font_desc,
                                     ct_font_descriptor_get_weight (desc));

  /* Font traits, style name; from this we deduce style and variant */
  font_traits = ct_font_descriptor_get_traits (desc);
  style_name = ct_font_descriptor_get_style_name (desc);

  if ((font_traits & kCTFontItalicTrait) == kCTFontItalicTrait)
    pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
  else if (pango_core_text_style_name_is_oblique (style_name))
    pango_font_description_set_style (font_desc, PANGO_STYLE_OBLIQUE);
  else
    pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);

  if (ct_font_descriptor_is_small_caps (desc))
    pango_font_description_set_variant (font_desc, PANGO_VARIANT_SMALL_CAPS);
  else
    pango_font_description_set_variant (font_desc, PANGO_VARIANT_NORMAL);

  g_free (style_name);

  return font_desc;
}

/*
 * PangoCoreTextFace
 */

static inline gboolean
pango_core_text_face_is_oblique (PangoCoreTextFace *face)
{
  return pango_core_text_style_name_is_oblique (face->style_name);
}

static void
pango_core_text_face_make_italic (PangoCoreTextFace *ctface,
                                  gboolean           synthetic_italic)
{
  CTFontDescriptorRef new_desc;

  ctface->traits |= kCTFontItalicTrait;
  if (synthetic_italic)
    ctface->synthetic_italic = TRUE;

  /* Update the font descriptor */
  new_desc = cf_font_descriptor_copy_with_traits (ctface->ctfontdescriptor,
                                                  ctface->traits);
  CFRelease (ctface->ctfontdescriptor);
  ctface->ctfontdescriptor = new_desc;
}

static inline PangoCoreTextFace *
pango_core_text_face_copy (const PangoCoreTextFace *old)
{
  PangoCoreTextFace *face;

  face = g_object_new (PANGO_TYPE_CORE_TEXT_FACE, NULL);
  face->family = old->family;
  face->ctfontdescriptor = CFRetain (old->ctfontdescriptor);
  face->style_name = g_strdup (old->style_name);
  face->weight = old->weight;
  face->traits = old->traits;
  face->synthetic_italic = old->synthetic_italic;

  return face;
}

static inline PangoCoreTextFace *
pango_core_text_face_from_ct_font_descriptor (CTFontDescriptorRef desc)
{
  PangoCoreTextFace *face = g_object_new (PANGO_TYPE_CORE_TEXT_FACE,
                                          NULL);

  face->synthetic_italic = FALSE;

  face->ctfontdescriptor = CFRetain (desc);

  face->style_name = ct_font_descriptor_get_style_name (desc);
  face->traits = ct_font_descriptor_get_traits (desc);
  face->weight = ct_font_descriptor_get_weight (desc);

  return face;
}

static PangoFontDescription *
pango_core_text_face_describe (PangoFontFace *face)
{
  PangoCoreTextFace *ctface = PANGO_CORE_TEXT_FACE (face);

  return _pango_core_text_font_description_from_ct_font_descriptor (ctface->ctfontdescriptor);
}

static const char *
pango_core_text_face_get_face_name (PangoFontFace *face)
{
  PangoCoreTextFace *ctface = PANGO_CORE_TEXT_FACE (face);

  return ctface->style_name;
}

static void
pango_core_text_face_list_sizes (PangoFontFace  *face,
                                 int           **sizes,
                                 int            *n_sizes)
{
  *n_sizes = 0;
  *sizes = NULL;
}

G_DEFINE_TYPE (PangoCoreTextFace, pango_core_text_face, PANGO_TYPE_FONT_FACE);

static void
pango_core_text_face_init (PangoCoreTextFace *face)
{
  face->family = NULL;
}

static void
pango_core_text_face_finalize (GObject *object)
{
  PangoCoreTextFace *ctface = PANGO_CORE_TEXT_FACE (object);

  g_free (ctface->style_name);
  CFRelease (ctface->ctfontdescriptor);

  G_OBJECT_CLASS (pango_core_text_face_parent_class)->finalize (object);
}

static gboolean
pango_core_text_face_is_synthesized (PangoFontFace *face)
{
  PangoCoreTextFace *cface = PANGO_CORE_TEXT_FACE (face);

  return cface->synthetic_italic;
}

static void
pango_core_text_face_class_init (PangoCoreTextFaceClass *klass)
{
  GObjectClass *object_class = (GObjectClass *)klass;
  PangoFontFaceClass *pfclass = PANGO_FONT_FACE_CLASS(klass);

  object_class->finalize = pango_core_text_face_finalize;

  pfclass->describe = pango_core_text_face_describe;
  pfclass->get_face_name = pango_core_text_face_get_face_name;
  pfclass->list_sizes = pango_core_text_face_list_sizes;
  pfclass->is_synthesized = pango_core_text_face_is_synthesized;
}

/*
 * PangoCoreTextFamily
 */

static void
pango_core_text_family_list_faces (PangoFontFamily  *family,
                                   PangoFontFace  ***faces,
                                   int              *n_faces)
{
  PangoCoreTextFamily *ctfamily = PANGO_CORE_TEXT_FAMILY (family);

  if (ctfamily->n_faces < 0)
    {
      GList *l;
      GList *faces = NULL;
      GList *synthetic_faces = NULL;
      GHashTable *italic_faces;
      const char *real_family = get_real_family (ctfamily->family_name);
      CTFontCollectionRef collection;
      CFArrayRef ctfaces;
      CFArrayRef font_descriptors;
      CFDictionaryRef attributes;
      CFIndex i, count;

      CFTypeRef keys[] = {
          (CFTypeRef) kCTFontFamilyNameAttribute
      };

      CFStringRef values[] = {
          CFStringCreateWithCString (kCFAllocatorDefault,
                                     real_family,
                                     kCFStringEncodingUTF8)
      };

      CTFontDescriptorRef descriptors[1];

      attributes = CFDictionaryCreate (kCFAllocatorDefault,
                                       (const void **)keys,
                                       (const void **)values,
                                       1,
                                       &kCFTypeDictionaryKeyCallBacks,
                                       &kCFTypeDictionaryValueCallBacks);
      descriptors[0] = CTFontDescriptorCreateWithAttributes (attributes);
      font_descriptors = CFArrayCreate (kCFAllocatorDefault,
                                        (const void **)descriptors,
                                        1,
                                        &kCFTypeArrayCallBacks);
      collection = CTFontCollectionCreateWithFontDescriptors (font_descriptors,
                                                              NULL);

      ctfaces = CTFontCollectionCreateMatchingFontDescriptors (collection);

      italic_faces = g_hash_table_new (g_direct_hash, g_direct_equal);

      count = CFArrayGetCount (ctfaces);
      for (i = 0; i < count; i++)
        {
          PangoCoreTextFace *face;
          CTFontDescriptorRef desc = CFArrayGetValueAtIndex (ctfaces, i);

          face = pango_core_text_face_from_ct_font_descriptor (desc);
          face->family = ctfamily;

          faces = g_list_prepend (faces, face);

          if ((face->traits & kCTFontItalicTrait) == kCTFontItalicTrait ||
              pango_core_text_face_is_oblique (face))
            g_hash_table_insert (italic_faces,
				 GINT_TO_POINTER ((gint)face->weight),
                                 face);
        }

      CFRelease (font_descriptors);
      CFRelease (attributes);
      CFRelease (ctfaces);

      /* For all fonts for which a non-synthetic italic variant does
       * not exist on the system, we create synthesized versions here.
       */
      for (l = faces; l; l = l->next)
        {
          PangoCoreTextFace *face = l->data;

          if (!g_hash_table_lookup (italic_faces,
                                    GINT_TO_POINTER ((gint)face->weight)))
            {
              PangoCoreTextFace *italic_face;

              italic_face = pango_core_text_face_copy (face);

              italic_face->family = ctfamily;
              pango_core_text_face_make_italic (italic_face, TRUE);

              /* Try to create a sensible face name. */
              g_free (italic_face->style_name);
              if (strcasecmp (face->style_name, "regular") == 0)
                italic_face->style_name = g_strdup ("Oblique");
              else
                italic_face->style_name = g_strdup_printf ("%s Oblique",
                                                           face->style_name);

              synthetic_faces = g_list_prepend (synthetic_faces, italic_face);
            }
        }

      faces = g_list_concat (faces, synthetic_faces);

      ctfamily->n_faces = g_list_length (faces);
      ctfamily->faces = g_new (PangoFontFace *, ctfamily->n_faces);

      for (l = faces, i = 0; l; l = l->next, i++)
	ctfamily->faces[i] = l->data;

      g_list_free (faces);
      g_hash_table_destroy (italic_faces);
    }

  if (n_faces)
    *n_faces = ctfamily->n_faces;

  if (faces)
    *faces = g_memdup (ctfamily->faces, ctfamily->n_faces * sizeof (PangoFontFace *));
}

static const char *
pango_core_text_family_get_name (PangoFontFamily *family)
{
  PangoCoreTextFamily *ctfamily = PANGO_CORE_TEXT_FAMILY (family);

  return ctfamily->family_name;
}

static gboolean
pango_core_text_family_is_monospace (PangoFontFamily *family)
{
  PangoCoreTextFamily *ctfamily = PANGO_CORE_TEXT_FAMILY (family);

  return ctfamily->is_monospace;
}

G_DEFINE_TYPE (PangoCoreTextFamily, pango_core_text_family, PANGO_TYPE_FONT_FAMILY);

static void
pango_core_text_family_finalize (GObject *object)
{
  PangoCoreTextFamily *family = PANGO_CORE_TEXT_FAMILY (object);
  int i;

  g_free (family->family_name);

  if (family->n_faces != -1)
    {
      for (i = 0; i < family->n_faces; i++)
	g_object_unref (family->faces[i]);

      g_free (family->faces);
    }

  G_OBJECT_CLASS (pango_core_text_family_parent_class)->finalize (object);
}


static void
pango_core_text_family_class_init (PangoCoreTextFamilyClass *klass)
{
  GObjectClass *object_class = (GObjectClass *)klass;
  int i;
  PangoFontFamilyClass *pfclass = PANGO_FONT_FAMILY_CLASS(klass);

  object_class->finalize = pango_core_text_family_finalize;

  pfclass->list_faces = pango_core_text_family_list_faces;
  pfclass->get_name = pango_core_text_family_get_name;
  pfclass->is_monospace = pango_core_text_family_is_monospace;

  for (i = 0; _pango_included_core_text_modules[i].list; i++)
    pango_module_register (&_pango_included_core_text_modules[i]);
}

static void
pango_core_text_family_init (PangoCoreTextFamily *family)
{
  family->n_faces = -1;
}



static void pango_core_text_font_map_class_init (PangoCoreTextFontMapClass *class);
static void pango_core_text_font_map_init (PangoCoreTextFontMap *ctfontmap);


G_DEFINE_TYPE (PangoCoreTextFontMap, pango_core_text_font_map, PANGO_TYPE_FONT_MAP);

static void
pango_core_text_font_map_finalize (GObject *object)
{
  PangoCoreTextFontMap *fontmap = PANGO_CORE_TEXT_FONT_MAP (object);

  g_hash_table_destroy (fontmap->fontset_hash);
  g_hash_table_destroy (fontmap->font_hash);
  g_hash_table_destroy (fontmap->families);

  G_OBJECT_CLASS (pango_core_text_font_map_parent_class)->finalize (object);
}

/* Fowler / Noll / Vo (FNV) Hash (http://www.isthe.com/chongo/tech/comp/fnv/)
 *
 * Not necessarily better than a lot of other hashes, but should be OK, and
 * well tested with binary data.
 */

#define FNV_32_PRIME ((guint32)0x01000193)
#define FNV1_32_INIT ((guint32)0x811c9dc5)

static guint32
hash_bytes_fnv (unsigned char *buffer,
		int            len,
		guint32        hval)
{
  while (len--)
    {
      hval *= FNV_32_PRIME;
      hval ^= *buffer++;
    }

  return hval;
}

static void
get_context_matrix (PangoContext *context,
		    PangoMatrix *matrix)
{
  const PangoMatrix *set_matrix;
  const PangoMatrix identity = PANGO_MATRIX_INIT;

  if (context)
    set_matrix = pango_context_get_matrix (context);
  else
    set_matrix = NULL;

  if (set_matrix)
    *matrix = *set_matrix;
  else
    *matrix = identity;
}

/*
 * Helper functions for PangoCoreTextFontsetKey
 */

static double
pango_core_text_font_map_get_resolution (PangoCoreTextFontMap *fontmap,
                                         PangoContext         *context)
{
  if (PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (fontmap)->get_resolution)
    return PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (fontmap)->get_resolution (fontmap, context);

  /* FIXME: acquire DPI from CoreText using some deafault font */
  g_warning ("FIXME: returning default DPI");

  return 72.0;
}

static int
get_scaled_size (PangoCoreTextFontMap       *fontmap,
                 PangoContext               *context,
                 const PangoFontDescription *desc)
{
  double size = pango_font_description_get_size (desc);

  if (!pango_font_description_get_size_is_absolute (desc))
    {
      double dpi = pango_core_text_font_map_get_resolution (fontmap, context);
      size = size * dpi / 72.;
    }

  return .5 + pango_matrix_get_font_scale_factor (pango_context_get_matrix (context)) * size;
}


/*
 * PangoCoreTextFontsetKey
 */
struct _PangoCoreTextFontsetKey
{
  PangoCoreTextFontMap *fontmap;
  PangoLanguage *language;
  PangoFontDescription *desc;
  PangoMatrix matrix;
  int pixelsize;
  double resolution;
  PangoGravity gravity;
  gpointer context_key;
};

static void
pango_core_text_fontset_key_init (PangoCoreTextFontsetKey    *key,
                                  PangoCoreTextFontMap       *fontmap,
                                  PangoContext               *context,
                                  const PangoFontDescription *desc,
                                  PangoLanguage              *language)
{
  if (!language && context)
    language = pango_context_get_language (context);

  key->fontmap = fontmap;
  get_context_matrix (context, &key->matrix);
  key->language = language;
  key->pixelsize = get_scaled_size (fontmap, context, desc);
  key->resolution = pango_core_text_font_map_get_resolution (fontmap, context);
  key->gravity = pango_context_get_gravity (context);
  key->desc = pango_font_description_copy_static (desc);
  pango_font_description_unset_fields (key->desc, PANGO_FONT_MASK_SIZE);

  if (context && PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (fontmap)->context_key_get)
    key->context_key = (gpointer)PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (fontmap)->context_key_get (fontmap, context);
  else
    key->context_key = NULL;
}

static PangoCoreTextFontsetKey *
pango_core_text_fontset_key_copy (const PangoCoreTextFontsetKey *old)
{
  PangoCoreTextFontsetKey *key = g_slice_new (PangoCoreTextFontsetKey);

  key->fontmap = old->fontmap;
  key->matrix = old->matrix;
  key->language = old->language;
  key->pixelsize = old->pixelsize;
  key->resolution = old->resolution;
  key->gravity = old->gravity;
  key->desc = pango_font_description_copy (old->desc);
  if (old->context_key)
    key->context_key = PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_copy (key->fontmap, old->context_key);
  else
    key->context_key = NULL;

  return key;
}

static void
pango_core_text_fontset_key_free (PangoCoreTextFontsetKey *key)
{
  pango_font_description_free (key->desc);

  if (key->context_key)
    PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_free (key->fontmap, key->context_key);

  g_slice_free (PangoCoreTextFontsetKey, key);
}

static guint
pango_core_text_fontset_key_hash (const PangoCoreTextFontsetKey *key)
{
  guint32 hash = FNV1_32_INIT;

  hash = hash_bytes_fnv ((unsigned char *)(&key->matrix), sizeof (double) * 4, hash);
  hash ^= hash_bytes_fnv ((unsigned char *)(&key->resolution), sizeof (double), hash);

  if (key->context_key)
    hash ^= PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_hash (key->fontmap, key->context_key);

  return (hash ^
          GPOINTER_TO_UINT (key->language) ^
          pango_font_description_hash (key->desc));
}

static gboolean
pango_core_text_fontset_key_equal (const PangoCoreTextFontsetKey *key_a,
                                   const PangoCoreTextFontsetKey *key_b)
{
  if (key_a->language == key_b->language &&
      key_a->pixelsize == key_b->pixelsize &&
      key_a->resolution == key_b->resolution &&
      key_a->gravity == key_b->gravity &&
      pango_font_description_equal (key_a->desc, key_b->desc) &&
      memcmp ((void *)&key_a->matrix, (void *)&key_b->matrix, 4 * sizeof (double)) == 0)
    {
      if (key_a->context_key)
        return PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key_a->fontmap)->context_key_equal (key_a->fontmap,
                                                                                       key_a->context_key,
                                                                                       key_b->context_key);
      else
        return key_a->context_key == key_b->context_key;
    }
  /* else */
  return FALSE;
}

static PangoLanguage *
pango_core_text_fontset_key_get_language (const PangoCoreTextFontsetKey *key)
{
  return key->language;
}

static const PangoMatrix *
pango_core_text_fontset_key_get_matrix (const PangoCoreTextFontsetKey *key)
{
  return &key->matrix;
}

static PangoGravity
pango_core_text_fontset_key_get_gravity (const PangoCoreTextFontsetKey *key)
{
  return key->gravity;
}

static gpointer
pango_core_text_fontset_key_get_context_key (const PangoCoreTextFontsetKey *key)
{
  return key->context_key;
}

/*
 * PangoCoreTextFontKey
 */
struct _PangoCoreTextFontKey
{
  PangoCoreTextFontMap *fontmap;
  CTFontDescriptorRef ctfontdescriptor;
  PangoMatrix matrix;
  PangoGravity gravity;
  int pixelsize;
  double resolution;
  gboolean synthetic_italic;
  gpointer context_key;
};

static void
pango_core_text_font_key_init (PangoCoreTextFontKey    *key,
                               PangoCoreTextFontMap    *ctfontmap,
                               PangoCoreTextFontsetKey *fontset_key,
                               CTFontDescriptorRef      ctdescriptor,
                               gboolean                 synthetic_italic)
{
  key->fontmap = ctfontmap;
  key->ctfontdescriptor = ctdescriptor;
  key->matrix = *pango_core_text_fontset_key_get_matrix (fontset_key);
  key->pixelsize = fontset_key->pixelsize;
  key->resolution = fontset_key->resolution;
  key->synthetic_italic = synthetic_italic;
  key->gravity = pango_core_text_fontset_key_get_gravity (fontset_key);
  key->context_key = pango_core_text_fontset_key_get_context_key (fontset_key);
}

static PangoCoreTextFontKey *
pango_core_text_font_key_copy (const PangoCoreTextFontKey *old)
{
  PangoCoreTextFontKey *key = g_slice_new (PangoCoreTextFontKey);

  key->fontmap = old->fontmap;
  key->ctfontdescriptor = old->ctfontdescriptor;
  CFRetain (key->ctfontdescriptor);
  key->matrix = old->matrix;
  key->pixelsize = old->pixelsize;
  key->resolution = old->resolution;
  key->synthetic_italic = old->synthetic_italic;
  key->gravity = old->gravity;
  if (old->context_key)
    key->context_key = PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_copy (key->fontmap, old->context_key);
  else
    key->context_key = NULL;

  return key;
}

static void
pango_core_text_font_key_free (PangoCoreTextFontKey *key)
{
  if (key->ctfontdescriptor)
    CFRelease (key->ctfontdescriptor);

  if (key->context_key)
    PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_free (key->fontmap, key->context_key);

  g_slice_free (PangoCoreTextFontKey, key);
}

static guint
pango_core_text_font_key_hash (const PangoCoreTextFontKey *key)
{
  guint32 hash = FNV1_32_INIT;

  /* Not everything is included here, probably good enough for a hash */

  hash = hash_bytes_fnv ((unsigned char *)(&key->matrix), sizeof (double) * 4, hash);

  if (key->context_key)
    hash ^= PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key->fontmap)->context_key_hash (key->fontmap, key->context_key);

  return (hash ^ CFHash (key->ctfontdescriptor));
}

static gboolean
pango_core_text_font_key_equal (const PangoCoreTextFontKey *key_a,
                                const PangoCoreTextFontKey *key_b)
{
  if (CFEqual (key_a->ctfontdescriptor, key_b->ctfontdescriptor) &&
      memcmp (&key_a->matrix, &key_b->matrix, 4 * sizeof (double)) == 0 &&
      key_a->gravity == key_b->gravity &&
      key_a->pixelsize == key_b->pixelsize &&
      key_a->resolution == key_b->resolution &&
      key_a->synthetic_italic == key_b->synthetic_italic)
    {
      if (key_a->context_key && key_b->context_key)
        return PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (key_a->fontmap)->context_key_equal (key_a->fontmap,
                                                                                       key_a->context_key,
                                                                                       key_b->context_key);
      else
        return key_a->context_key == key_b->context_key;
    }
  else
    return FALSE;
}

int
pango_core_text_font_key_get_absolute_size (const PangoCoreTextFontKey *key)
{
  return key->pixelsize;
}

double
pango_core_text_font_key_get_resolution (const PangoCoreTextFontKey *key)
{
  return key->resolution;
}

gboolean
pango_core_text_font_key_get_synthetic_italic (const PangoCoreTextFontKey *key)
{
  return key->synthetic_italic;
}

gpointer
pango_core_text_font_key_get_context_key (const PangoCoreTextFontKey *key)
{
  return key->context_key;
}

const PangoMatrix *
pango_core_text_font_key_get_matrix (const PangoCoreTextFontKey *key)
{
  return &key->matrix;
}

PangoGravity
pango_core_text_font_key_get_gravity (const PangoCoreTextFontKey *key)
{
  return key->gravity;
}

CTFontDescriptorRef
pango_core_text_font_key_get_ctfontdescriptor (const PangoCoreTextFontKey *key)
{
  return key->ctfontdescriptor;
}



static void
pango_core_text_font_map_add (PangoCoreTextFontMap *ctfontmap,
                              PangoCoreTextFontKey *key,
                              PangoCoreTextFont    *ctfont)
{
  PangoCoreTextFontKey *key_copy;

  _pango_core_text_font_set_font_map (ctfont, ctfontmap);

  key_copy = pango_core_text_font_key_copy (key);
  _pango_core_text_font_set_font_key (ctfont, key_copy);
  g_hash_table_insert (ctfontmap->font_hash, key_copy, ctfont);
}

static PangoCoreTextFont *
pango_core_text_font_map_new_font (PangoCoreTextFontMap    *fontmap,
                                   PangoCoreTextFontsetKey *fontset_key,
                                   CTFontDescriptorRef      ctfontdescriptor,
                                   gboolean                 synthetic_italic)
{
  PangoCoreTextFontMapClass *klass;
  PangoCoreTextFont *font;
  PangoCoreTextFontKey key;

  pango_core_text_font_key_init (&key, fontmap, fontset_key, ctfontdescriptor,
                                 synthetic_italic);

  font = g_hash_table_lookup (fontmap->font_hash, &key);
  if (font)
    return g_object_ref (font);

  /* Call create_font */
  klass = PANGO_CORE_TEXT_FONT_MAP_GET_CLASS (fontmap);
  font = klass->create_font (fontmap, &key);

  if (!font)
    return NULL;

  pango_core_text_font_map_add (fontmap, &key, font);

  return font;
}

static gboolean
find_best_match (PangoCoreTextFamily         *font_family,
                 const PangoFontDescription  *description,
                 PangoFontDescription       **best_description,
                 PangoCoreTextFace          **best_face)
{
  PangoFontDescription *new_desc;
  int i;

  *best_description = NULL;
  *best_face = NULL;

  for (i = 0; i < font_family->n_faces; i++)
    {
      new_desc = pango_font_face_describe (font_family->faces[i]);

      if (pango_font_description_better_match (description, *best_description, new_desc))
	{
	  pango_font_description_free (*best_description);
	  *best_description = new_desc;
	  *best_face = (PangoCoreTextFace *)font_family->faces[i];
	}
      else
	pango_font_description_free (new_desc);
    }

  if (*best_description)
    return TRUE;

  return FALSE;
}

static gboolean
get_first_font (PangoFontset *fontset G_GNUC_UNUSED,
                PangoFont    *font,
                gpointer      data)
{
  *(PangoFont **)data = font;

  return TRUE;
}

static guint
pango_core_text_font_map_get_serial (PangoFontMap *fontmap)
{
  PangoCoreTextFontMap *ctfontmap = PANGO_CORE_TEXT_FONT_MAP (fontmap);

  return ctfontmap->serial;
}

static void
pango_core_text_font_map_changed (PangoFontMap *fontmap)
{
  PangoCoreTextFontMap *ctfontmap = PANGO_CORE_TEXT_FONT_MAP (fontmap);

  ctfontmap->serial++;
  if (ctfontmap->serial == 0)
    ctfontmap->serial++;
}

static PangoFont *
pango_core_text_font_map_load_font (PangoFontMap               *fontmap,
                                    PangoContext               *context,
                                    const PangoFontDescription *description)
{
  PangoLanguage *language;
  PangoFontset *fontset;
  PangoFont *font = NULL;

  if (context)
    language = pango_context_get_language (context);
  else
    language = NULL;

  fontset = pango_font_map_load_fontset (fontmap, context,
                                         description, language);

  if (fontset)
    {
      pango_fontset_foreach (fontset, get_first_font, &font);

      if (font)
        g_object_ref (font);

      g_object_unref (fontset);
    }

  return font;
}

static void
list_families_foreach (gpointer key,
		       gpointer value,
		       gpointer user_data)
{
  GSList **list = user_data;

  *list = g_slist_prepend (*list, value);
}

static void
pango_core_text_font_map_list_families (PangoFontMap      *fontmap,
                                        PangoFontFamily ***families,
                                        int               *n_families)
{
  GSList *family_list = NULL;
  GSList *tmp_list;
  PangoCoreTextFontMap *ctfontmap = (PangoCoreTextFontMap *)fontmap;

  if (!n_families)
    return;

  g_hash_table_foreach (ctfontmap->families,
                        list_families_foreach, &family_list);

  *n_families = g_slist_length (family_list);

  if (families)
    {
      int i = 0;

      *families = g_new (PangoFontFamily *, *n_families);

      tmp_list = family_list;
      while (tmp_list)
	{
	  (*families)[i] = tmp_list->data;
	  i++;
	  tmp_list = tmp_list->next;
	}
    }

  g_slist_free (family_list);
}

static PangoFontset *
pango_core_text_font_map_load_fontset (PangoFontMap               *fontmap,
                                       PangoContext               *context,
                                       const PangoFontDescription *desc,
                                       PangoLanguage              *language)
{
  PangoCoreTextFontset *fontset;
  PangoCoreTextFontsetKey key;
  PangoCoreTextFontMap *ctfontmap = PANGO_CORE_TEXT_FONT_MAP (fontmap);
  static gboolean warned_full_fallback = FALSE; /* MT-safe */

  pango_core_text_fontset_key_init (&key, ctfontmap,
                                    context, desc, language);

  fontset = g_hash_table_lookup (ctfontmap->fontset_hash, &key);

  if (G_UNLIKELY (!fontset))
    {
      gboolean insert_in_hash = TRUE;

      fontset = pango_core_text_fontset_new (&key, desc);

      if (G_UNLIKELY (!fontset))
        {
          /* If no font(set) could be loaded, we fallback to "Sans",
           * which should always work on Mac. We try to adhere to the
           * requested style at first.
           */
          PangoFontDescription *tmp_desc;

          /* Cannot use pango_core_text_fontset_key_free() here */
          pango_font_description_free (key.desc);

          tmp_desc = pango_font_description_copy_static (desc);
          pango_font_description_set_family_static (tmp_desc, "Sans");

          pango_core_text_fontset_key_init (&key, ctfontmap, context, tmp_desc,
                                            language);

          fontset = g_hash_table_lookup (ctfontmap->fontset_hash, &key);
          if (G_LIKELY (fontset))
            insert_in_hash = FALSE;
          else
            fontset = pango_core_text_fontset_new (&key, tmp_desc);

          if (G_UNLIKELY (!fontset))
            {
              /* We could not load Sans in the requested style; reset
               * variant, weight and stretch to sensible defaults (we should
               * be able to adhere the PangoStyle with "Sans").
               */
              pango_font_description_set_variant (tmp_desc, PANGO_VARIANT_NORMAL);
              pango_font_description_set_weight (tmp_desc, PANGO_WEIGHT_NORMAL);
              pango_font_description_set_stretch (tmp_desc, PANGO_STRETCH_NORMAL);

              if (!warned_full_fallback)
                {
                  char *ctmp;

                  warned_full_fallback = TRUE;

                  ctmp = pango_font_description_to_string (desc);
                  g_warning ("couldn't load font \"%s\", modified variant/"
                             "weight/stretch as fallback, expect ugly output.",
                             ctmp);
                  g_free (ctmp);
                }

              fontset = g_hash_table_lookup (ctfontmap->fontset_hash, &key);
              if (G_LIKELY (fontset))
                insert_in_hash = FALSE;
              else
                fontset = pango_core_text_fontset_new (&key, tmp_desc);

              if (G_UNLIKELY (!fontset))
                {
                  /* If even that failed, display a sensible error message
                   * and bail out, in contrast to failing randomly.
                   */
                  g_error ("Could not load fallback font, bailing out.");
                }
            }
        }

      if (insert_in_hash)
        g_hash_table_insert (ctfontmap->fontset_hash,
                             pango_core_text_fontset_get_key (fontset),
                             fontset);
    }

  /* Cannot use pango_core_text_fontset_key_free() here */
  pango_font_description_free (key.desc);

  return g_object_ref (fontset);
}

static void
pango_core_text_font_map_init (PangoCoreTextFontMap *ctfontmap)
{
  PangoCoreTextFamily *family;
  CTFontCollectionRef collection;
  CFArrayRef ctfaces;
  CFIndex i, count;

  ctfontmap->serial = 1;
  ctfontmap->families = g_hash_table_new_full (g_str_hash, g_str_equal,
                                               g_free, g_object_unref);


  ctfontmap->font_hash = g_hash_table_new_full ((GHashFunc)pango_core_text_font_key_hash,
                                                (GEqualFunc)pango_core_text_font_key_equal,
                                                (GDestroyNotify)pango_core_text_font_key_free,
                                                NULL);

  ctfontmap->fontset_hash = g_hash_table_new_full ((GHashFunc)pango_core_text_fontset_key_hash,
                                                   (GEqualFunc)pango_core_text_fontset_key_equal,
                                                   NULL,
                                                   (GDestroyNotify)g_object_unref);

  collection = CTFontCollectionCreateFromAvailableFonts (0);
  ctfaces = CTFontCollectionCreateMatchingFontDescriptors (collection);
  count = CFArrayGetCount (ctfaces);

  for (i = 0; i < count; i++)
    {
      SInt64 font_traits;
      char *buffer;
      char *family_name;
      CFNumberRef number;
      CFDictionaryRef dict;
      CTFontDescriptorRef desc = CFArrayGetValueAtIndex (ctfaces, i);

      buffer = ct_font_descriptor_get_family_name (desc, TRUE);
      if (!buffer)
        continue;

      family_name = g_utf8_casefold (buffer, -1);

      family = g_hash_table_lookup (ctfontmap->families, family_name);
      if (!family)
        {
          family = g_object_new (PANGO_TYPE_CORE_TEXT_FAMILY, NULL);
          g_hash_table_insert (ctfontmap->families, g_strdup (family_name),
                               family);

          family->family_name = g_strdup (buffer);
        }

      g_free (buffer);

      g_free (family_name);

      /* We assume that all faces in the family are monospaced or none. */
      dict = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
      number = (CFNumberRef)CFDictionaryGetValue (dict,
                                                  kCTFontSymbolicTrait);

      if (CFNumberGetValue (number, kCFNumberSInt64Type, &font_traits))
        {
          if ((font_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait)
            family->is_monospace = TRUE;
        }

      CFRelease (dict);
    }

  /* Insert aliases */
  family = g_object_new (PANGO_TYPE_CORE_TEXT_FAMILY, NULL);
  family->family_name = g_strdup ("Sans");
  g_hash_table_insert (ctfontmap->families,
                       g_utf8_casefold (family->family_name, -1), family);

  family = g_object_new (PANGO_TYPE_CORE_TEXT_FAMILY, NULL);
  family->family_name = g_strdup ("Serif");
  g_hash_table_insert (ctfontmap->families,
                       g_utf8_casefold (family->family_name, -1), family);

  family = g_object_new (PANGO_TYPE_CORE_TEXT_FAMILY, NULL);
  family->family_name = g_strdup ("Monospace");
  family->is_monospace = TRUE;
  g_hash_table_insert (ctfontmap->families,
                       g_utf8_casefold (family->family_name, -1), family);
}

static void
pango_core_text_font_map_class_init (PangoCoreTextFontMapClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  PangoFontMapClass *fontmap_class = PANGO_FONT_MAP_CLASS (class);

  object_class->finalize = pango_core_text_font_map_finalize;

  fontmap_class->load_font = pango_core_text_font_map_load_font;
  fontmap_class->list_families = pango_core_text_font_map_list_families;
  fontmap_class->load_fontset = pango_core_text_font_map_load_fontset;
  fontmap_class->shape_engine_type = PANGO_RENDER_TYPE_CORE_TEXT;
  fontmap_class->get_serial = pango_core_text_font_map_get_serial;
  fontmap_class->changed = pango_core_text_font_map_changed;
}

/*
 * PangoCoreTextFontSet
 */

static void              pango_core_text_fontset_finalize     (GObject                   *object);
static void              pango_core_text_fontset_init         (PangoCoreTextFontset      *fontset);
static PangoLanguage *   pango_core_text_fontset_get_language (PangoFontset              *fontset);
static  PangoFont *      pango_core_text_fontset_get_font     (PangoFontset              *fontset,
                                                               guint                      wc);
static void              pango_core_text_fontset_foreach      (PangoFontset              *fontset,
                                                               PangoFontsetForeachFunc    func,
                                                               gpointer                   data);

struct _PangoCoreTextFontset
{
  PangoFontset parent_instance;

  const gchar *orig_family;
  PangoFontDescription *orig_description;

  PangoCoreTextFontsetKey *key;
  CFArrayRef cascade_list;

  GPtrArray *fonts;
  GPtrArray *coverages;
};

struct _PangoCoreTextFontsetClass
{
  PangoFontsetClass parent_instance;
};

typedef struct _PangoCoreTextFontsetClass PangoCoreTextFontsetClass;

G_DEFINE_TYPE (PangoCoreTextFontset,
               pango_core_text_fontset,
               PANGO_TYPE_FONTSET);

/* This symbol does exist in the CoreText library shipped with Snow
 * Leopard and Lion, however, it is not found in the public header files.
 */
CFArrayRef CTFontCopyDefaultCascadeList (CTFontRef font_ref);

static PangoCoreTextFontset *
pango_core_text_fontset_new (PangoCoreTextFontsetKey    *key,
                             const PangoFontDescription *description)
{
  PangoCoreTextFamily *font_family;
  PangoCoreTextFontset *fontset;
  PangoCoreTextFont *best_font = NULL;
  const gchar *family;
  gchar *name;

  family = pango_font_description_get_family (description);
  family = family ? family : "";
  name = g_utf8_casefold (family, -1);
  font_family = g_hash_table_lookup (key->fontmap->families, name);
  g_free (name);

  if (font_family)
    {
      PangoFontDescription *best_description;
      PangoCoreTextFace *best_face;
      gint size;
      gboolean is_absolute;

      /* Force a listing of the available faces */
      pango_font_family_list_faces ((PangoFontFamily *)font_family, NULL, NULL);

      if (!find_best_match (font_family, description, &best_description, &best_face))
	return NULL;

      size = pango_font_description_get_size (description);
      if (size < 0)
        return NULL;

      is_absolute = pango_font_description_get_size_is_absolute (description);
      if (is_absolute)
        pango_font_description_set_absolute_size (best_description, size);
      else
        pango_font_description_set_size (best_description, size);

      best_font = pango_core_text_font_map_new_font (key->fontmap,
                                                     key,
                                                     best_face->ctfontdescriptor,
                                                     best_face->synthetic_italic);

      pango_font_description_free (best_description);
    }
  else
    return NULL;

  if (!best_font)
    return NULL;

  /* Create a font set with best font */
  fontset = g_object_new (PANGO_TYPE_CORE_TEXT_FONTSET, NULL);
  fontset->key = pango_core_text_fontset_key_copy (key);
  fontset->orig_description = pango_font_description_copy (description);

  fontset->fonts = g_ptr_array_new ();
  g_ptr_array_add (fontset->fonts, best_font);
  fontset->coverages = g_ptr_array_new ();

  /* Add the cascade list for this language */
  fontset->cascade_list = CTFontCopyDefaultCascadeList (pango_core_text_font_get_ctfont (best_font));

  /* length of cascade list + 1 for the "real" font at the front */
  g_ptr_array_set_size (fontset->fonts, CFArrayGetCount (fontset->cascade_list) + 1);
  g_ptr_array_set_size (fontset->coverages, CFArrayGetCount (fontset->cascade_list) + 1);

  return fontset;
}

static PangoFont *
pango_core_text_fontset_load_font (PangoCoreTextFontset *ctfontset,
                                   CTFontDescriptorRef   ctdescriptor)
{
  PangoCoreTextFontsetKey *key;
  PangoCoreTextFont *font;

  key = pango_core_text_fontset_get_key (ctfontset);

  /* For now, we will default the fallbacks to not have synthetic italic,
   * in the future this may be improved.
   */
  font = pango_core_text_font_map_new_font (ctfontset->key->fontmap,
                                            ctfontset->key,
                                            ctdescriptor,
                                            FALSE);

  return PANGO_FONT (font);
}

static PangoFont *
pango_core_text_fontset_get_font_at (PangoCoreTextFontset *ctfontset,
                                     unsigned int          i)
{
  /* The first font is loaded as soon as the fontset is created */
  if (i == 0)
    return g_ptr_array_index (ctfontset->fonts, i);

  if (i >= ctfontset->fonts->len)
    return NULL;

  if (g_ptr_array_index (ctfontset->fonts, i) == NULL)
    {
      CTFontDescriptorRef ctdescriptor = CFArrayGetValueAtIndex (ctfontset->cascade_list, i - 1);
      PangoFont *font = pango_core_text_fontset_load_font (ctfontset, ctdescriptor);
      g_ptr_array_index (ctfontset->fonts, i) = font;
      g_ptr_array_index (ctfontset->coverages, i) = NULL;
    }

  return g_ptr_array_index (ctfontset->fonts, i);
}

static void
pango_core_text_fontset_class_init (PangoCoreTextFontsetClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  PangoFontsetClass *fontset_class = PANGO_FONTSET_CLASS (klass);

  object_class->finalize = pango_core_text_fontset_finalize;

  fontset_class->get_font = pango_core_text_fontset_get_font;
  fontset_class->get_language = pango_core_text_fontset_get_language;
  fontset_class->foreach = pango_core_text_fontset_foreach;
}

static void
pango_core_text_fontset_init (PangoCoreTextFontset *ctfontset)
{
  ctfontset->key = NULL;
  ctfontset->cascade_list = NULL;
  ctfontset->fonts = g_ptr_array_new ();
  ctfontset->coverages = g_ptr_array_new ();
}

static void
pango_core_text_fontset_finalize (GObject *object)
{
  PangoCoreTextFontset *ctfontset = PANGO_CORE_TEXT_FONTSET (object);
  unsigned int i;

  for (i = 0; i < ctfontset->fonts->len; i++)
    {
      PangoFont *font = g_ptr_array_index (ctfontset->fonts, i);
      if (font)
        g_object_unref (font);
    }
  g_ptr_array_free (ctfontset->fonts, TRUE);

  for (i = 0; i < ctfontset->coverages->len; i++)
    {
      PangoCoverage *coverage = g_ptr_array_index (ctfontset->coverages, i);
      if (coverage)
        pango_coverage_unref (coverage);
    }
  g_ptr_array_free (ctfontset->coverages, TRUE);

  CFRelease (ctfontset->cascade_list);

  pango_font_description_free (ctfontset->orig_description);

  if (ctfontset->key)
    pango_core_text_fontset_key_free (ctfontset->key);

  G_OBJECT_CLASS (pango_core_text_fontset_parent_class)->finalize (object);
}

static PangoCoreTextFontsetKey *
pango_core_text_fontset_get_key (PangoCoreTextFontset *fontset)
{
  return fontset->key;
}

static PangoLanguage *
pango_core_text_fontset_get_language (PangoFontset *fontset)
{
  PangoCoreTextFontset *ctfontset = PANGO_CORE_TEXT_FONTSET (fontset);

  return pango_core_text_fontset_key_get_language (pango_core_text_fontset_get_key (ctfontset));
}

static PangoFont *
pango_core_text_fontset_get_font (PangoFontset *fontset,
                                  guint         wc)
{
  PangoCoreTextFontset *ctfontset = PANGO_CORE_TEXT_FONTSET (fontset);
  PangoCoverageLevel best_level = PANGO_COVERAGE_NONE;
  PangoCoverageLevel level;
  PangoFont *font;
  PangoCoverage *coverage;
  int result = -1;
  unsigned int i;

  for (i = 0; i < ctfontset->fonts->len; i++)
    {
      PangoFont *font = pango_core_text_fontset_get_font_at (ctfontset, i);
      if (!font)
        continue;

      coverage = g_ptr_array_index (ctfontset->coverages, i);

      if (coverage == NULL)
        {
          font = g_ptr_array_index (ctfontset->fonts, i);

          coverage = pango_font_get_coverage (font, ctfontset->key->language);
          g_ptr_array_index (ctfontset->coverages, i) = coverage;
        }

      level = pango_coverage_get (coverage, wc);

      if (result == -1 || level > best_level)
        {
          result = i;
          best_level = level;
          if (level == PANGO_COVERAGE_EXACT)
            break;
        }
    }

  if (G_UNLIKELY (result == -1))
    return NULL;

  font = g_ptr_array_index (ctfontset->fonts, result);
  return g_object_ref (font);
}

static void
pango_core_text_fontset_foreach (PangoFontset *fontset,
                                 PangoFontsetForeachFunc func,
                                 gpointer data)
{
  PangoCoreTextFontset *ctfontset = PANGO_CORE_TEXT_FONTSET (fontset);
  unsigned int i;

  for (i = 0; i < ctfontset->fonts->len; i++)
    {
      PangoFont *font = pango_core_text_fontset_get_font_at (ctfontset, i);
      if (!font)
        continue;

      if ((* func) (fontset, font, data))
        return;
    }
}

