/* Pango
 * pango-attributes.c: Attributed text
 *
 * Copyright (C) 2000-2002 Red Hat Software
 *
 * 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 <string.h>

#include "pango-attributes.h"
#include "pango-attributes-private.h"
#include "pango-impl-utils.h"


/* {{{ Generic attribute code */

G_LOCK_DEFINE_STATIC (attr_type);
static GHashTable *name_map = NULL; /* MT-safe */

/**
 * pango_attr_type_register:
 * @name: an identifier for the type
 *
 * Allocate a new attribute type ID.
 *
 * The attribute type name can be accessed later
 * by using [func@Pango.AttrType.get_name].
 *
 * Return value: the new type ID.
 */
PangoAttrType
pango_attr_type_register (const gchar *name)
{
  static guint current_type = 0x1000000; /* MT-safe */
  guint type;

  G_LOCK (attr_type);

  type = current_type++;

  if (name)
    {
      if (G_UNLIKELY (!name_map))
        name_map = g_hash_table_new (NULL, NULL);

      g_hash_table_insert (name_map, GUINT_TO_POINTER (type), (gpointer) g_intern_string (name));
    }

  G_UNLOCK (attr_type);

  return type;
}

/**
 * pango_attr_type_get_name:
 * @type: an attribute type ID to fetch the name for
 *
 * Fetches the attribute type name.
 *
 * The attribute type name is the string passed in
 * when registering the type using
 * [func@Pango.AttrType.register].
 *
 * The returned value is an interned string (see
 * g_intern_string() for what that means) that should
 * not be modified or freed.
 *
 * Return value: (nullable): the type ID name (which
 *   may be %NULL), or %NULL if @type is a built-in Pango
 *   attribute type or invalid.
 *
 * Since: 1.22
 */
const char *
pango_attr_type_get_name (PangoAttrType type)
{
  const char *result = NULL;

  G_LOCK (attr_type);

  if (name_map)
    result = g_hash_table_lookup (name_map, GUINT_TO_POINTER ((guint) type));

  G_UNLOCK (attr_type);

  return result;
}

/**
 * pango_attribute_init:
 * @attr: a `PangoAttribute`
 * @klass: a `PangoAttrClass`
 *
 * Initializes @attr's klass to @klass, it's start_index to
 * %PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING and end_index to
 * %PANGO_ATTR_INDEX_TO_TEXT_END such that the attribute applies
 * to the entire text by default.
 *
 * Since: 1.20
 */
void
pango_attribute_init (PangoAttribute       *attr,
                      const PangoAttrClass *klass)
{
  g_return_if_fail (attr != NULL);
  g_return_if_fail (klass != NULL);

  attr->klass = klass;
  attr->start_index = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
  attr->end_index   = PANGO_ATTR_INDEX_TO_TEXT_END;
}

/**
 * pango_attribute_copy:
 * @attr: a `PangoAttribute`
 *
 * Make a copy of an attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy].
 */
PangoAttribute *
pango_attribute_copy (const PangoAttribute *attr)
{
  PangoAttribute *result;

  g_return_val_if_fail (attr != NULL, NULL);

  result = attr->klass->copy (attr);
  result->start_index = attr->start_index;
  result->end_index = attr->end_index;

  return result;
}

/**
 * pango_attribute_destroy:
 * @attr: a `PangoAttribute`.
 *
 * Destroy a `PangoAttribute` and free all associated memory.
 */
void
pango_attribute_destroy (PangoAttribute *attr)
{
  g_return_if_fail (attr != NULL);

  attr->klass->destroy (attr);
}

G_DEFINE_BOXED_TYPE (PangoAttribute, pango_attribute,
                     pango_attribute_copy,
                     pango_attribute_destroy);

/**
 * pango_attribute_equal:
 * @attr1: a `PangoAttribute`
 * @attr2: another `PangoAttribute`
 *
 * Compare two attributes for equality.
 *
 * This compares only the actual value of the two
 * attributes and not the ranges that the attributes
 * apply to.
 *
 * Return value: %TRUE if the two attributes have the same value
 */
gboolean
pango_attribute_equal (const PangoAttribute *attr1,
                       const PangoAttribute *attr2)
{
  g_return_val_if_fail (attr1 != NULL, FALSE);
  g_return_val_if_fail (attr2 != NULL, FALSE);

  if (attr1->klass->type != attr2->klass->type)
    return FALSE;

  return attr1->klass->equal (attr1, attr2);
}

/* }}} */
/* {{{ Attribute types */
/* {{{ String attribute */
static PangoAttribute *pango_attr_string_new (const PangoAttrClass *klass,
                                              const char           *str);

static PangoAttribute *
pango_attr_string_copy (const PangoAttribute *attr)
{
  return pango_attr_string_new (attr->klass, ((PangoAttrString *)attr)->value);
}

static void
pango_attr_string_destroy (PangoAttribute *attr)
{
  PangoAttrString *sattr = (PangoAttrString *)attr;

  g_free (sattr->value);
  g_slice_free (PangoAttrString, sattr);
}

static gboolean
pango_attr_string_equal (const PangoAttribute *attr1,
                         const PangoAttribute *attr2)
{
  return strcmp (((PangoAttrString *)attr1)->value, ((PangoAttrString *)attr2)->value) == 0;
}

static PangoAttribute *
pango_attr_string_new (const PangoAttrClass *klass,
                       const char           *str)
{
  PangoAttrString *result = g_slice_new (PangoAttrString);
  pango_attribute_init (&result->attr, klass);
  result->value = g_strdup (str);

  return (PangoAttribute *)result;
}
 /* }}} */
/* {{{ Language attribute */
static PangoAttribute *
pango_attr_language_copy (const PangoAttribute *attr)
{
  return pango_attr_language_new (((PangoAttrLanguage *)attr)->value);
}

static void
pango_attr_language_destroy (PangoAttribute *attr)
{
  PangoAttrLanguage *lattr = (PangoAttrLanguage *)attr;

  g_slice_free (PangoAttrLanguage, lattr);
}

static gboolean
pango_attr_language_equal (const PangoAttribute *attr1,
                           const PangoAttribute *attr2)
{
  return ((PangoAttrLanguage *)attr1)->value == ((PangoAttrLanguage *)attr2)->value;
}
/* }}}} */
/* {{{ Color attribute */
static PangoAttribute *pango_attr_color_new (const PangoAttrClass *klass,
                                             guint16               red,
                                             guint16               green,
                                             guint16               blue);

static PangoAttribute *
pango_attr_color_copy (const PangoAttribute *attr)
{
  const PangoAttrColor *color_attr = (PangoAttrColor *)attr;

  return pango_attr_color_new (attr->klass,
                               color_attr->color.red,
                               color_attr->color.green,
                               color_attr->color.blue);
}

static void
pango_attr_color_destroy (PangoAttribute *attr)
{
  PangoAttrColor *cattr = (PangoAttrColor *)attr;

  g_slice_free (PangoAttrColor, cattr);
}

static gboolean
pango_attr_color_equal (const PangoAttribute *attr1,
                        const PangoAttribute *attr2)
{
  const PangoAttrColor *color_attr1 = (const PangoAttrColor *)attr1;
  const PangoAttrColor *color_attr2 = (const PangoAttrColor *)attr2;

  return (color_attr1->color.red == color_attr2->color.red &&
          color_attr1->color.blue == color_attr2->color.blue &&
          color_attr1->color.green == color_attr2->color.green);
}

static PangoAttribute *
pango_attr_color_new (const PangoAttrClass *klass,
                      guint16               red,
                      guint16               green,
                      guint16               blue)
{
  PangoAttrColor *result = g_slice_new (PangoAttrColor);
  pango_attribute_init (&result->attr, klass);
  result->color.red = red;
  result->color.green = green;
  result->color.blue = blue;

  return (PangoAttribute *)result;
}
/* }}}} */
/* {{{ Integer attribute */
static PangoAttribute *pango_attr_int_new (const PangoAttrClass *klass,
                                           int                   value);

static PangoAttribute *
pango_attr_int_copy (const PangoAttribute *attr)
{
  const PangoAttrInt *int_attr = (PangoAttrInt *)attr;

  return pango_attr_int_new (attr->klass, int_attr->value);
}

static void
pango_attr_int_destroy (PangoAttribute *attr)
{
  PangoAttrInt *iattr = (PangoAttrInt *)attr;

  g_slice_free (PangoAttrInt, iattr);
}

static gboolean
pango_attr_int_equal (const PangoAttribute *attr1,
                      const PangoAttribute *attr2)
{
  const PangoAttrInt *int_attr1 = (const PangoAttrInt *)attr1;
  const PangoAttrInt *int_attr2 = (const PangoAttrInt *)attr2;

  return (int_attr1->value == int_attr2->value);
}

static PangoAttribute *
pango_attr_int_new (const PangoAttrClass *klass,
                    int                   value)
{
  PangoAttrInt *result = g_slice_new (PangoAttrInt);
  pango_attribute_init (&result->attr, klass);
  result->value = value;

  return (PangoAttribute *)result;
}
/* }}} */
/* {{{ Float attribute */
static PangoAttribute *pango_attr_float_new (const PangoAttrClass *klass,
                                             double                value);

static PangoAttribute *
pango_attr_float_copy (const PangoAttribute *attr)
{
  const PangoAttrFloat *float_attr = (PangoAttrFloat *)attr;

  return pango_attr_float_new (attr->klass, float_attr->value);
}

static void
pango_attr_float_destroy (PangoAttribute *attr)
{
  PangoAttrFloat *fattr = (PangoAttrFloat *)attr;

  g_slice_free (PangoAttrFloat, fattr);
}

static gboolean
pango_attr_float_equal (const PangoAttribute *attr1,
                        const PangoAttribute *attr2)
{
  const PangoAttrFloat *float_attr1 = (const PangoAttrFloat *)attr1;
  const PangoAttrFloat *float_attr2 = (const PangoAttrFloat *)attr2;

  return (float_attr1->value == float_attr2->value);
}

static PangoAttribute *
pango_attr_float_new  (const PangoAttrClass *klass,
                       double                value)
{
  PangoAttrFloat *result = g_slice_new (PangoAttrFloat);
  pango_attribute_init (&result->attr, klass);
  result->value = value;

  return (PangoAttribute *)result;
}
/* }}} */
/* {{{ Size attribute */
static PangoAttribute *pango_attr_size_new_internal (int      size,
                                                     gboolean absolute);

static PangoAttribute *
pango_attr_size_copy (const PangoAttribute *attr)
{
  const PangoAttrSize *size_attr = (PangoAttrSize *)attr;

  if (attr->klass->type == PANGO_ATTR_ABSOLUTE_SIZE)
    return pango_attr_size_new_absolute (size_attr->size);
  else
    return pango_attr_size_new (size_attr->size);
}

static void
pango_attr_size_destroy (PangoAttribute *attr)
{
  PangoAttrSize *sattr = (PangoAttrSize *)attr;

  g_slice_free (PangoAttrSize, sattr);
}

static gboolean
pango_attr_size_equal (const PangoAttribute *attr1,
                       const PangoAttribute *attr2)
{
  const PangoAttrSize *size_attr1 = (const PangoAttrSize *)attr1;
  const PangoAttrSize *size_attr2 = (const PangoAttrSize *)attr2;

  return size_attr1->size == size_attr2->size;
}

static PangoAttribute *
pango_attr_size_new_internal (int size,
                              gboolean absolute)
{
  PangoAttrSize *result;

  static const PangoAttrClass klass = {
    PANGO_ATTR_SIZE,
    pango_attr_size_copy,
    pango_attr_size_destroy,
    pango_attr_size_equal
  };
  static const PangoAttrClass absolute_klass = {
    PANGO_ATTR_ABSOLUTE_SIZE,
    pango_attr_size_copy,
    pango_attr_size_destroy,
    pango_attr_size_equal
  };

  result = g_slice_new (PangoAttrSize);
  pango_attribute_init (&result->attr, absolute ? &absolute_klass : &klass);
  result->size = size;
  result->absolute = absolute;

  return (PangoAttribute *)result;
}
/* }}} */
/* {{{ Font description attribute */
static PangoAttribute *
pango_attr_font_desc_copy (const PangoAttribute *attr)
{
  const PangoAttrFontDesc *desc_attr = (const PangoAttrFontDesc *)attr;

  return pango_attr_font_desc_new (desc_attr->desc);
}

static void
pango_attr_font_desc_destroy (PangoAttribute *attr)
{
  PangoAttrFontDesc *desc_attr = (PangoAttrFontDesc *)attr;

  pango_font_description_free (desc_attr->desc);
  g_slice_free (PangoAttrFontDesc, desc_attr);
}

static gboolean
pango_attr_font_desc_equal (const PangoAttribute *attr1,
                            const PangoAttribute *attr2)
{
  const PangoAttrFontDesc *desc_attr1 = (const PangoAttrFontDesc *)attr1;
  const PangoAttrFontDesc *desc_attr2 = (const PangoAttrFontDesc *)attr2;

  return pango_font_description_get_set_fields (desc_attr1->desc) ==
         pango_font_description_get_set_fields (desc_attr2->desc) &&
         pango_font_description_equal (desc_attr1->desc, desc_attr2->desc);
}
/* }}} */
/* {{{ Shape attribute */
static PangoAttribute *
pango_attr_shape_copy (const PangoAttribute *attr)
{
  const PangoAttrShape *shape_attr = (PangoAttrShape *)attr;
  gpointer data;

  if (shape_attr->copy_func)
    data = shape_attr->copy_func (shape_attr->data);
  else
    data = shape_attr->data;

  return pango_attr_shape_new_with_data (&shape_attr->ink_rect, &shape_attr->logical_rect,
                                         data, shape_attr->copy_func, shape_attr->destroy_func);
}

static void
pango_attr_shape_destroy (PangoAttribute *attr)
{
  PangoAttrShape *shape_attr = (PangoAttrShape *)attr;

  if (shape_attr->destroy_func)
    shape_attr->destroy_func (shape_attr->data);

  g_slice_free (PangoAttrShape, shape_attr);
}

static gboolean
pango_attr_shape_equal (const PangoAttribute *attr1,
                        const PangoAttribute *attr2)
{
  const PangoAttrShape *shape_attr1 = (const PangoAttrShape *)attr1;
  const PangoAttrShape *shape_attr2 = (const PangoAttrShape *)attr2;

  return (shape_attr1->logical_rect.x == shape_attr2->logical_rect.x &&
          shape_attr1->logical_rect.y == shape_attr2->logical_rect.y &&
          shape_attr1->logical_rect.width == shape_attr2->logical_rect.width &&
          shape_attr1->logical_rect.height == shape_attr2->logical_rect.height &&
          shape_attr1->ink_rect.x == shape_attr2->ink_rect.x &&
          shape_attr1->ink_rect.y == shape_attr2->ink_rect.y &&
          shape_attr1->ink_rect.width == shape_attr2->ink_rect.width &&
          shape_attr1->ink_rect.height == shape_attr2->ink_rect.height &&
          shape_attr1->data == shape_attr2->data);
}
/* }}} */
/* }}} */
/* {{{ Public API */

/**
 * pango_attr_family_new:
 * @family: the family or comma-separated list of families
 *
 * Create a new font family attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_family_new (const char *family)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FAMILY,
    pango_attr_string_copy,
    pango_attr_string_destroy,
    pango_attr_string_equal
  };

  g_return_val_if_fail (family != NULL, NULL);

  return pango_attr_string_new (&klass, family);
}

/**
 * pango_attr_language_new:
 * @language: language tag
 *
 * Create a new language tag attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_language_new (PangoLanguage *language)
{
  PangoAttrLanguage *result;

  static const PangoAttrClass klass = {
    PANGO_ATTR_LANGUAGE,
    pango_attr_language_copy,
    pango_attr_language_destroy,
    pango_attr_language_equal
  };

  result = g_slice_new (PangoAttrLanguage);
  pango_attribute_init (&result->attr, &klass);
  result->value = language;

  return (PangoAttribute *)result;
}

/**
 * pango_attr_foreground_new:
 * @red: the red value (ranging from 0 to 65535)
 * @green: the green value
 * @blue: the blue value
 *
 * Create a new foreground color attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_foreground_new (guint16 red,
                           guint16 green,
                           guint16 blue)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FOREGROUND,
    pango_attr_color_copy,
    pango_attr_color_destroy,
    pango_attr_color_equal
  };

  return pango_attr_color_new (&klass, red, green, blue);
}

/**
 * pango_attr_background_new:
 * @red: the red value (ranging from 0 to 65535)
 * @green: the green value
 * @blue: the blue value
 *
 * Create a new background color attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_background_new (guint16 red,
                           guint16 green,
                           guint16 blue)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_BACKGROUND,
    pango_attr_color_copy,
    pango_attr_color_destroy,
    pango_attr_color_equal
  };

  return pango_attr_color_new (&klass, red, green, blue);
}

/**
 * pango_attr_size_new:
 * @size: the font size, in %PANGO_SCALE-ths of a point
 *
 * Create a new font-size attribute in fractional points.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_size_new (int size)
{
  return pango_attr_size_new_internal (size, FALSE);
}

/**
 * pango_attr_size_new_absolute:
 * @size: the font size, in %PANGO_SCALE-ths of a device unit
 *
 * Create a new font-size attribute in device units.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.8
 */
PangoAttribute *
pango_attr_size_new_absolute (int size)
{
  return pango_attr_size_new_internal (size, TRUE);
}

/**
 * pango_attr_style_new:
 * @style: the slant style
 *
 * Create a new font slant style attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_style_new (PangoStyle style)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_STYLE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)style);
}

/**
 * pango_attr_weight_new:
 * @weight: the weight
 *
 * Create a new font weight attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_weight_new (PangoWeight weight)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_WEIGHT,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)weight);
}

/**
 * pango_attr_variant_new:
 * @variant: the variant
 *
 * Create a new font variant attribute (normal or small caps).
 *
 * Return value: (transfer full): the newly allocated `PangoAttribute`,
 *   which should be freed with [method@Pango.Attribute.destroy].
 */
PangoAttribute *
pango_attr_variant_new (PangoVariant variant)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_VARIANT,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)variant);
}

/**
 * pango_attr_stretch_new:
 * @stretch: the stretch
 *
 * Create a new font stretch attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_stretch_new (PangoStretch  stretch)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_STRETCH,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)stretch);
}

/**
 * pango_attr_font_desc_new:
 * @desc: the font description
 *
 * Create a new font description attribute.
 *
 * This attribute allows setting family, style, weight, variant,
 * stretch, and size simultaneously.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_font_desc_new (const PangoFontDescription *desc)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FONT_DESC,
    pango_attr_font_desc_copy,
    pango_attr_font_desc_destroy,
    pango_attr_font_desc_equal
  };

  PangoAttrFontDesc *result = g_slice_new (PangoAttrFontDesc);
  pango_attribute_init (&result->attr, &klass);
  result->desc = pango_font_description_copy (desc);

  return (PangoAttribute *)result;
}

/* Helper for the deserialization code below */
static PangoAttribute *
pango_attr_font_desc_from_string_new (const char *string)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FONT_DESC,
    pango_attr_font_desc_copy,
    pango_attr_font_desc_destroy,
    pango_attr_font_desc_equal
  };

  PangoAttrFontDesc *result = g_slice_new (PangoAttrFontDesc);
  pango_attribute_init (&result->attr, &klass);
  result->desc = pango_font_description_from_string (string);

  return (PangoAttribute *)result;
}

/**
 * pango_attr_underline_new:
 * @underline: the underline style
 *
 * Create a new underline-style attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_underline_new (PangoUnderline underline)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_UNDERLINE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)underline);
}

/**
 * pango_attr_underline_color_new:
 * @red: the red value (ranging from 0 to 65535)
 * @green: the green value
 * @blue: the blue value
 *
 * Create a new underline color attribute.
 *
 * This attribute modifies the color of underlines.
 * If not set, underlines will use the foreground color.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.8
 */
PangoAttribute *
pango_attr_underline_color_new (guint16 red,
                                guint16 green,
                                guint16 blue)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_UNDERLINE_COLOR,
    pango_attr_color_copy,
    pango_attr_color_destroy,
    pango_attr_color_equal
  };

  return pango_attr_color_new (&klass, red, green, blue);
}

/**
 * pango_attr_strikethrough_new:
 * @strikethrough: %TRUE if the text should be struck-through
 *
 * Create a new strike-through attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_strikethrough_new (gboolean strikethrough)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_STRIKETHROUGH,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)strikethrough);
}

/**
 * pango_attr_strikethrough_color_new:
 * @red: the red value (ranging from 0 to 65535)
 * @green: the green value
 * @blue: the blue value
 *
 * Create a new strikethrough color attribute.
 *
 * This attribute modifies the color of strikethrough lines.
 * If not set, strikethrough lines will use the foreground color.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.8
 */
PangoAttribute *
pango_attr_strikethrough_color_new (guint16 red,
                                    guint16 green,
                                    guint16 blue)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_STRIKETHROUGH_COLOR,
    pango_attr_color_copy,
    pango_attr_color_destroy,
    pango_attr_color_equal
  };

  return pango_attr_color_new (&klass, red, green, blue);
}

/**
 * pango_attr_rise_new:
 * @rise: the amount that the text should be displaced vertically,
 *   in Pango units. Positive values displace the text upwards.
 *
 * Create a new baseline displacement attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_rise_new (int rise)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_RISE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)rise);
}

/**
 * pango_attr_baseline_shift_new:
 * @shift: either a `PangoBaselineShift` enumeration value or an absolute value (> 1024)
 *   in Pango units, relative to the baseline of the previous run.
 *   Positive values displace the text upwards.
 *
 * Create a new baseline displacement attribute.
 *
 * The effect of this attribute is to shift the baseline of a run,
 * relative to the run of preceding run.
 *
 * <picture>
 *   <source srcset="baseline-shift-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Baseline Shift" src="baseline-shift-light.png">
 * </picture>

 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_baseline_shift_new (int rise)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_BASELINE_SHIFT,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)rise);
}

/**
 * pango_attr_font_scale_new:
 * @scale: a `PangoFontScale` value, which indicates font size change relative
 *   to the size of the previous run.
 *
 *
 * Create a new font scale attribute.
 *
 * The effect of this attribute is to change the font size of a run,
 * relative to the size of preceding run.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_font_scale_new (PangoFontScale scale)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FONT_SCALE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)scale);
}

/**
 * pango_attr_scale_new:
 * @scale_factor: factor to scale the font
 *
 * Create a new font size scale attribute.
 *
 * The base font for the affected text will have
 * its size multiplied by @scale_factor.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute*
pango_attr_scale_new (double scale_factor)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_SCALE,
    pango_attr_float_copy,
    pango_attr_float_destroy,
    pango_attr_float_equal
  };

  return pango_attr_float_new (&klass, scale_factor);
}

/**
 * pango_attr_fallback_new:
 * @enable_fallback: %TRUE if we should fall back on other fonts
 *   for characters the active font is missing
 *
 * Create a new font fallback attribute.
 *
 * If fallback is disabled, characters will only be
 * used from the closest matching font on the system.
 * No fallback will be done to other fonts on the system
 * that might contain the characters in the text.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.4
 */
PangoAttribute *
pango_attr_fallback_new (gboolean enable_fallback)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FALLBACK,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, (int)enable_fallback);
}

/**
 * pango_attr_letter_spacing_new:
 * @letter_spacing: amount of extra space to add between
 *   graphemes of the text, in Pango units
 *
 * Create a new letter-spacing attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.6
 */
PangoAttribute *
pango_attr_letter_spacing_new (int letter_spacing)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_LETTER_SPACING,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, letter_spacing);
}

/**
 * pango_attr_shape_new_with_data:
 * @ink_rect: ink rectangle to assign to each character
 * @logical_rect: logical rectangle to assign to each character
 * @data: user data pointer
 * @copy_func: (nullable): function to copy @data when the
 *   attribute is copied. If %NULL, @data is simply copied
 *   as a pointer
 * @destroy_func: (nullable): function to free @data when the
 *   attribute is freed
 *
 * Creates a new shape attribute.
 *
 * Like [func@Pango.AttrShape.new], but a user data pointer
 * is also provided; this pointer can be accessed when later
 * rendering the glyph.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.8
 */
PangoAttribute *
pango_attr_shape_new_with_data (const PangoRectangle  *ink_rect,
                                const PangoRectangle  *logical_rect,
                                gpointer               data,
                                PangoAttrDataCopyFunc  copy_func,
                                GDestroyNotify         destroy_func)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_SHAPE,
    pango_attr_shape_copy,
    pango_attr_shape_destroy,
    pango_attr_shape_equal
  };

  PangoAttrShape *result;

  g_return_val_if_fail (ink_rect != NULL, NULL);
  g_return_val_if_fail (logical_rect != NULL, NULL);

  result = g_slice_new (PangoAttrShape);
  pango_attribute_init (&result->attr, &klass);
  result->ink_rect = *ink_rect;
  result->logical_rect = *logical_rect;
  result->data = data;
  result->copy_func = copy_func;
  result->destroy_func =  destroy_func;

  return (PangoAttribute *)result;
}

/**
 * pango_attr_shape_new:
 * @ink_rect: ink rectangle to assign to each character
 * @logical_rect: logical rectangle to assign to each character
 *
 * Create a new shape attribute.
 *
 * A shape is used to impose a particular ink and logical
 * rectangle on the result of shaping a particular glyph.
 * This might be used, for instance, for embedding a picture
 * or a widget inside a `PangoLayout`.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 */
PangoAttribute *
pango_attr_shape_new (const PangoRectangle *ink_rect,
                      const PangoRectangle *logical_rect)
{
  g_return_val_if_fail (ink_rect != NULL, NULL);
  g_return_val_if_fail (logical_rect != NULL, NULL);

  return pango_attr_shape_new_with_data (ink_rect, logical_rect,
                                         NULL, NULL, NULL);
}

/**
 * pango_attr_gravity_new:
 * @gravity: the gravity value; should not be %PANGO_GRAVITY_AUTO
 *
 * Create a new gravity attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.16
 */
PangoAttribute *
pango_attr_gravity_new (PangoGravity gravity)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_GRAVITY,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  g_return_val_if_fail (gravity != PANGO_GRAVITY_AUTO, NULL);

  return pango_attr_int_new (&klass, (int)gravity);
}

/**
 * pango_attr_gravity_hint_new:
 * @hint: the gravity hint value
 *
 * Create a new gravity hint attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.16
 */
PangoAttribute *
pango_attr_gravity_hint_new (PangoGravityHint hint)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_GRAVITY_HINT,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)hint);
}

/**
 * pango_attr_font_features_new:
 * @features: a string with OpenType font features, with the syntax of the [CSS
 * font-feature-settings property](https://www.w3.org/TR/css-fonts-4/#font-rend-desc)
 *
 * Create a new font features tag attribute.
 *
 * You can use this attribute to select OpenType font features like small-caps,
 * alternative glyphs, ligatures, etc. for fonts that support them.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.38
 */
PangoAttribute *
pango_attr_font_features_new (const gchar *features)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FONT_FEATURES,
    pango_attr_string_copy,
    pango_attr_string_destroy,
    pango_attr_string_equal
  };

  g_return_val_if_fail (features != NULL, NULL);

  return pango_attr_string_new (&klass, features);
}

/**
 * pango_attr_foreground_alpha_new:
 * @alpha: the alpha value, between 1 and 65536
 *
 * Create a new foreground alpha attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.38
 */
PangoAttribute *
pango_attr_foreground_alpha_new (guint16 alpha)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_FOREGROUND_ALPHA,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)alpha);
}

/**
 * pango_attr_background_alpha_new:
 * @alpha: the alpha value, between 1 and 65536
 *
 * Create a new background alpha attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.38
 */
PangoAttribute *
pango_attr_background_alpha_new (guint16 alpha)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_BACKGROUND_ALPHA,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)alpha);
}

/**
 * pango_attr_allow_breaks_new:
 * @allow_breaks: %TRUE if we line breaks are allowed
 *
 * Create a new allow-breaks attribute.
 *
 * If breaks are disabled, the range will be kept in a
 * single run, as far as possible.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.44
 */
PangoAttribute *
pango_attr_allow_breaks_new (gboolean allow_breaks)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_ALLOW_BREAKS,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, (int)allow_breaks);
}

/**
 * pango_attr_insert_hyphens_new:
 * @insert_hyphens: %TRUE if hyphens should be inserted
 *
 * Create a new insert-hyphens attribute.
 *
 * Pango will insert hyphens when breaking lines in
 * the middle of a word. This attribute can be used
 * to suppress the hyphen.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.44
 */
PangoAttribute *
pango_attr_insert_hyphens_new (gboolean insert_hyphens)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_INSERT_HYPHENS,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, (int)insert_hyphens);
}

/**
 * pango_attr_show_new:
 * @flags: `PangoShowFlags` to apply
 *
 * Create a new attribute that influences how invisible
 * characters are rendered.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.44
 **/
PangoAttribute *
pango_attr_show_new (PangoShowFlags flags)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_SHOW,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, (int)flags);
}

/**
 * pango_attr_word_new:
 *
 * Marks the range of the attribute as a single word.
 *
 * Note that this may require adjustments to word and
 * sentence classification around the range.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_word_new (void)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_WORD,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, 1);
}

/**
 * pango_attr_sentence_new:
 *
 * Marks the range of the attribute as a single sentence.
 *
 * Note that this may require adjustments to word and
 * sentence classification around the range.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_sentence_new (void)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_SENTENCE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal,
  };

  return pango_attr_int_new (&klass, 1);
}

/**
 * pango_attr_overline_new:
 * @overline: the overline style
 *
 * Create a new overline-style attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.46
 */
PangoAttribute *
pango_attr_overline_new (PangoOverline overline)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_OVERLINE,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int)overline);
}

/**
 * pango_attr_overline_color_new:
 * @red: the red value (ranging from 0 to 65535)
 * @green: the green value
 * @blue: the blue value
 *
 * Create a new overline color attribute.
 *
 * This attribute modifies the color of overlines.
 * If not set, overlines will use the foreground color.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.46
 */
PangoAttribute *
pango_attr_overline_color_new (guint16 red,
                               guint16 green,
                               guint16 blue)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_OVERLINE_COLOR,
    pango_attr_color_copy,
    pango_attr_color_destroy,
    pango_attr_color_equal
  };

  return pango_attr_color_new (&klass, red, green, blue);
}

/**
 * pango_attr_line_height_new:
 * @factor: the scaling factor to apply to the logical height
 *
 * Modify the height of logical line extents by a factor.
 *
 * This affects the values returned by
 * [method@Pango.LayoutLine.get_extents],
 * [method@Pango.LayoutLine.get_pixel_extents] and
 * [method@Pango.LayoutIter.get_line_extents].
 *
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_line_height_new (double factor)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_LINE_HEIGHT,
    pango_attr_float_copy,
    pango_attr_float_destroy,
    pango_attr_float_equal
  };

  return pango_attr_float_new (&klass, factor);
}

/**
 * pango_attr_line_height_new_absolute:
 * @height: the line height, in %PANGO_SCALE-ths of a point
 *
 * Override the height of logical line extents to be @height.
 *
 * This affects the values returned by
 * [method@Pango.LayoutLine.get_extents],
 * [method@Pango.LayoutLine.get_pixel_extents] and
 * [method@Pango.LayoutIter.get_line_extents].
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_line_height_new_absolute (int height)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_ABSOLUTE_LINE_HEIGHT,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, height);
}

/**
 * pango_attr_text_transform_new:
 * @transform: `PangoTextTransform` to apply
 *
 * Create a new attribute that influences how characters
 * are transformed during shaping.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.50
 */
PangoAttribute *
pango_attr_text_transform_new (PangoTextTransform transform)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_TEXT_TRANSFORM,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, transform);
}

/**
 * pango_attr_width_new:
 * @width: the width
 *
 * Create a new font width attribute.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttribute`, which should be freed with
 *   [method@Pango.Attribute.destroy]
 *
 * Since: 1.58
 */
PangoAttribute *
pango_attr_width_new (PangoWidth width)
{
  static const PangoAttrClass klass = {
    PANGO_ATTR_WIDTH,
    pango_attr_int_copy,
    pango_attr_int_destroy,
    pango_attr_int_equal
  };

  return pango_attr_int_new (&klass, (int) width);
}

/* }}} */
/* {{{ Binding helpers */

/**
 * pango_attribute_as_int:
 * @attr: A `PangoAttribute` such as weight
 *
 * Returns the attribute cast to `PangoAttrInt`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrInt`,
 *   or %NULL if it's not an integer attribute
 *
 * Since: 1.50
 */
PangoAttrInt *
pango_attribute_as_int (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_STYLE:
    case PANGO_ATTR_WEIGHT:
    case PANGO_ATTR_VARIANT:
    case PANGO_ATTR_STRETCH:
    case PANGO_ATTR_WIDTH:
    case PANGO_ATTR_UNDERLINE:
    case PANGO_ATTR_STRIKETHROUGH:
    case PANGO_ATTR_RISE:
    case PANGO_ATTR_FALLBACK:
    case PANGO_ATTR_LETTER_SPACING:
    case PANGO_ATTR_GRAVITY:
    case PANGO_ATTR_GRAVITY_HINT:
    case PANGO_ATTR_FOREGROUND_ALPHA:
    case PANGO_ATTR_BACKGROUND_ALPHA:
    case PANGO_ATTR_ALLOW_BREAKS:
    case PANGO_ATTR_SHOW:
    case PANGO_ATTR_INSERT_HYPHENS:
    case PANGO_ATTR_OVERLINE:
    case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
    case PANGO_ATTR_TEXT_TRANSFORM:
    case PANGO_ATTR_WORD:
    case PANGO_ATTR_SENTENCE:
    case PANGO_ATTR_BASELINE_SHIFT:
    case PANGO_ATTR_FONT_SCALE:
      return (PangoAttrInt *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_float:
 * @attr: A `PangoAttribute` such as scale
 *
 * Returns the attribute cast to `PangoAttrFloat`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrFloat`,
 *   or %NULL if it's not a floating point attribute
 *
 * Since: 1.50
 */
PangoAttrFloat *
pango_attribute_as_float (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_SCALE:
    case PANGO_ATTR_LINE_HEIGHT:
      return (PangoAttrFloat *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_string:
 * @attr: A `PangoAttribute` such as family
 *
 * Returns the attribute cast to `PangoAttrString`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrString`,
 *   or %NULL if it's not a string attribute
 *
 * Since: 1.50
 */
PangoAttrString *
pango_attribute_as_string (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_FAMILY:
      return (PangoAttrString *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_size:
 * @attr: A `PangoAttribute` representing a size
 *
 * Returns the attribute cast to `PangoAttrSize`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrSize`,
 *   or NULL if it's not a size attribute
 *
 * Since: 1.50
 */
PangoAttrSize *
pango_attribute_as_size (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_SIZE:
    case PANGO_ATTR_ABSOLUTE_SIZE:
      return (PangoAttrSize *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_color:
 * @attr: A `PangoAttribute` such as foreground
 *
 * Returns the attribute cast to `PangoAttrColor`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrColor`,
 *   or %NULL if it's not a color attribute
 *
 * Since: 1.50
 */
PangoAttrColor *
pango_attribute_as_color (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_FOREGROUND:
    case PANGO_ATTR_BACKGROUND:
    case PANGO_ATTR_UNDERLINE_COLOR:
    case PANGO_ATTR_STRIKETHROUGH_COLOR:
    case PANGO_ATTR_OVERLINE_COLOR:
      return (PangoAttrColor *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_font_desc:
 * @attr: A `PangoAttribute` representing a font description
 *
 * Returns the attribute cast to `PangoAttrFontDesc`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrFontDesc`,
 *   or %NULL if it's not a font description attribute
 *
 * Since: 1.50
 */
PangoAttrFontDesc *
pango_attribute_as_font_desc (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_FONT_DESC:
      return (PangoAttrFontDesc *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_font_features:
 * @attr: A `PangoAttribute` representing font features
 *
 * Returns the attribute cast to `PangoAttrFontFeatures`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrFontFeatures`,
 *   or %NULL if it's not a font features attribute
 *
 * Since: 1.50
 */
PangoAttrFontFeatures *
pango_attribute_as_font_features (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_FONT_FEATURES:
      return (PangoAttrFontFeatures *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_language:
 * @attr: A `PangoAttribute` representing a language
 *
 * Returns the attribute cast to `PangoAttrLanguage`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrLanguage`,
 *   or %NULL if it's not a language attribute
 *
 * Since: 1.50
 */
PangoAttrLanguage *
pango_attribute_as_language (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_LANGUAGE:
      return (PangoAttrLanguage *)attr;

    default:
      return NULL;
    }
}

/**
 * pango_attribute_as_shape:
 * @attr: A `PangoAttribute` representing a shape
 *
 * Returns the attribute cast to `PangoAttrShape`.
 *
 * This is mainly useful for language bindings.
 *
 * Returns: (nullable) (transfer none): The attribute as `PangoAttrShape`,
 *   or %NULL if it's not a shape attribute
 *
 * Since: 1.50
 */
PangoAttrShape *
pango_attribute_as_shape (PangoAttribute *attr)
{
  switch ((int)attr->klass->type)
    {
    case PANGO_ATTR_SHAPE:
      return (PangoAttrShape *)attr;

    default:
      return NULL;
    }
}

/* }}} */
/* {{{ Attribute List */

G_DEFINE_BOXED_TYPE (PangoAttrList, pango_attr_list,
                     pango_attr_list_copy,
                     pango_attr_list_unref);

void
_pango_attr_list_init (PangoAttrList *list)
{
  list->ref_count = 1;
  list->attributes = NULL;
}

/**
 * pango_attr_list_new:
 *
 * Create a new empty attribute list with a reference
 * count of one.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttrList`, which should be freed with
 *   [method@Pango.AttrList.unref]
 */
PangoAttrList *
pango_attr_list_new (void)
{
  PangoAttrList *list = g_slice_new (PangoAttrList);

  _pango_attr_list_init (list);

  return list;
}

/**
 * pango_attr_list_ref:
 * @list: (nullable): a `PangoAttrList`
 *
 * Increase the reference count of the given attribute
 * list by one.
 *
 * Return value: The attribute list passed in
 *
 * Since: 1.10
 */
PangoAttrList *
pango_attr_list_ref (PangoAttrList *list)
{
  if (list == NULL)
    return NULL;

  g_atomic_int_inc ((int *) &list->ref_count);

  return list;
}

void
_pango_attr_list_destroy (PangoAttrList *list)
{
  guint i, p;

  if (!list->attributes)
    return;

  for (i = 0, p = list->attributes->len; i < p; i++)
    {
      PangoAttribute *attr = g_ptr_array_index (list->attributes, i);

      attr->klass->destroy (attr);
    }

  g_ptr_array_free (list->attributes, TRUE);
}

/**
 * pango_attr_list_unref:
 * @list: (nullable): a `PangoAttrList`
 *
 * Decrease the reference count of the given attribute
 * list by one.
 *
 * If the result is zero, free the attribute list
 * and the attributes it contains.
 */
void
pango_attr_list_unref (PangoAttrList *list)
{
  if (list == NULL)
    return;

  g_return_if_fail (list->ref_count > 0);

  if (g_atomic_int_dec_and_test ((int *) &list->ref_count))
    {
      _pango_attr_list_destroy (list);
      g_slice_free (PangoAttrList, list);
    }
}

/**
 * pango_attr_list_copy:
 * @list: (nullable): a `PangoAttrList`
 *
 * Copy @list and return an identical new list.
 *
 * Return value: (nullable): the newly allocated
 *   `PangoAttrList`, with a reference count of one,
 *   which should be freed with [method@Pango.AttrList.unref].
 *   Returns %NULL if @list was %NULL.
 */
PangoAttrList *
pango_attr_list_copy (PangoAttrList *list)
{
  PangoAttrList *new;

  if (list == NULL)
    return NULL;

  new = pango_attr_list_new ();
  if (!list->attributes || list->attributes->len == 0)
    return new;

  new->attributes = g_ptr_array_copy (list->attributes, (GCopyFunc)pango_attribute_copy, NULL);

  return new;
}

static void
pango_attr_list_insert_internal (PangoAttrList  *list,
                                 PangoAttribute *attr,
                                 gboolean        before)
{
  const guint start_index = attr->start_index;
  PangoAttribute *last_attr;

  if (G_UNLIKELY (!list->attributes))
    list->attributes = g_ptr_array_new ();

  if (list->attributes->len == 0)
    {
      g_ptr_array_add (list->attributes, attr);
      return;
    }

  g_assert (list->attributes->len > 0);

  last_attr = g_ptr_array_index (list->attributes, list->attributes->len - 1);

  if (last_attr->start_index < start_index ||
      (!before && last_attr->start_index == start_index))
    {
      g_ptr_array_add (list->attributes, attr);
    }
  else
    {
      guint i, p;

      for (i = 0, p = list->attributes->len; i < p; i++)
        {
          PangoAttribute *cur = g_ptr_array_index (list->attributes, i);

          if (cur->start_index > start_index ||
              (before && cur->start_index == start_index))
            {
              g_ptr_array_insert (list->attributes, i, attr);
              break;
            }
        }
    }
}

/**
 * pango_attr_list_insert:
 * @list: a `PangoAttrList`
 * @attr: (transfer full): the attribute to insert
 *
 * Insert the given attribute into the `PangoAttrList`.
 *
 * It will be inserted after all other attributes with a
 * matching @start_index.
 */
void
pango_attr_list_insert (PangoAttrList  *list,
                        PangoAttribute *attr)
{
  g_return_if_fail (list != NULL);
  g_return_if_fail (attr != NULL);

  pango_attr_list_insert_internal (list, attr, FALSE);
}

/**
 * pango_attr_list_insert_before:
 * @list: a `PangoAttrList`
 * @attr: (transfer full): the attribute to insert
 *
 * Insert the given attribute into the `PangoAttrList`.
 *
 * It will be inserted before all other attributes with a
 * matching @start_index.
 */
void
pango_attr_list_insert_before (PangoAttrList  *list,
                               PangoAttribute *attr)
{
  g_return_if_fail (list != NULL);
  g_return_if_fail (attr != NULL);

  pango_attr_list_insert_internal (list, attr, TRUE);
}

/**
 * pango_attr_list_change:
 * @list: a `PangoAttrList`
 * @attr: (transfer full): the attribute to insert
 *
 * Insert the given attribute into the `PangoAttrList`.
 *
 * It will replace any attributes of the same type
 * on that segment and be merged with any adjoining
 * attributes that are identical.
 *
 * This function is slower than [method@Pango.AttrList.insert]
 * for creating an attribute list in order (potentially
 * much slower for large lists). However,
 * [method@Pango.AttrList.insert] is not suitable for
 * continually changing a set of attributes since it
 * never removes or combines existing attributes.
 */
void
pango_attr_list_change (PangoAttrList  *list,
                        PangoAttribute *attr)
{
  guint i, p;
  guint start_index = attr->start_index;
  guint end_index = attr->end_index;
  gboolean inserted;

  g_return_if_fail (list != NULL);

  if (start_index == end_index) /* empty, nothing to do */
    {
      pango_attribute_destroy (attr);
      return;
    }

  if (!list->attributes || list->attributes->len == 0)
    {
      pango_attr_list_insert (list, attr);
      return;
    }

  inserted = FALSE;
  for (i = 0, p = list->attributes->len; i < p; i++)
    {
      PangoAttribute *tmp_attr = g_ptr_array_index (list->attributes, i);

      if (tmp_attr->start_index > start_index)
        {
          g_ptr_array_insert (list->attributes, i, attr);
          inserted = TRUE;
          break;
        }

      if (tmp_attr->klass->type != attr->klass->type)
        continue;

      if (tmp_attr->end_index < start_index)
        continue; /* This attr does not overlap with the new one */

      g_assert (tmp_attr->start_index <= start_index);
      g_assert (tmp_attr->end_index >= start_index);

      if (pango_attribute_equal (tmp_attr, attr))
        {
          /* We can merge the new attribute with this attribute
           */
          if (tmp_attr->end_index >= end_index)
            {
              /* We are totally overlapping the previous attribute.
               * No action is needed.
               */
              pango_attribute_destroy (attr);
              return;
            }

          tmp_attr->end_index = end_index;
          pango_attribute_destroy (attr);

          attr = tmp_attr;
          inserted = TRUE;
          break;
        }
      else
        {
          /* Split, truncate, or remove the old attribute
           */
          if (tmp_attr->end_index > end_index)
            {
              PangoAttribute *end_attr = pango_attribute_copy (tmp_attr);

              end_attr->start_index = end_index;
              pango_attr_list_insert (list, end_attr);
            }

          if (tmp_attr->start_index == start_index)
            {
              pango_attribute_destroy (tmp_attr);
              g_ptr_array_remove_index (list->attributes, i);
              break;
            }
          else
            {
              tmp_attr->end_index = start_index;
            }
        }
    }

  if (!inserted)
    /* we didn't insert attr yet */
    pango_attr_list_insert (list, attr);

  /* We now have the range inserted into the list one way or the
   * other. Fix up the remainder
   */
  /* Attention: No i = 0 here. */
  for (i = i + 1, p = list->attributes->len; i < p; i++)
    {
      PangoAttribute *tmp_attr = g_ptr_array_index (list->attributes, i);

      if (tmp_attr->start_index > end_index)
        break;

      if (tmp_attr->klass->type != attr->klass->type)
        continue;

      if (tmp_attr == attr)
        continue;

      if (tmp_attr->end_index <= attr->end_index ||
          pango_attribute_equal (tmp_attr, attr))
        {
          /* We can merge the new attribute with this attribute. */
          attr->end_index = MAX (end_index, tmp_attr->end_index);
          pango_attribute_destroy (tmp_attr);
          g_ptr_array_remove_index (list->attributes, i);
          i--;
          p--;
          continue;
        }
      else
        {
          /* Trim the start of this attribute that it begins at the end
           * of the new attribute. This may involve moving it in the list
           * to maintain the required non-decreasing order of start indices.
           */
          int k, m;

          tmp_attr->start_index = attr->end_index;

          for (k = i + 1, m = list->attributes->len; k < m; k++)
            {
              PangoAttribute *tmp_attr2 = g_ptr_array_index (list->attributes, k);

              if (tmp_attr2->start_index >= tmp_attr->start_index)
                break;

              g_ptr_array_index (list->attributes, k - 1) = tmp_attr2;
              g_ptr_array_index (list->attributes, k) = tmp_attr;
            }
        }
    }
}

/**
 * pango_attr_list_update:
 * @list: a `PangoAttrList`
 * @pos: the position of the change
 * @remove: the number of removed bytes
 * @add: the number of added bytes
 *
 * Update indices of attributes in @list for a change in the
 * text they refer to.
 *
 * The change that this function applies is removing @remove
 * bytes at position @pos and inserting @add bytes instead.
 *
 * Attributes that fall entirely in the (@pos, @pos + @remove)
 * range are removed.
 *
 * Attributes that start or end inside the (@pos, @pos + @remove)
 * range are shortened to reflect the removal.
 *
 * Attributes start and end positions are updated if they are
 * behind @pos + @remove.
 *
 * Since: 1.44
 */
void
pango_attr_list_update (PangoAttrList *list,
                        int             pos,
                        int             remove,
                        int             add)
{
  guint i, p;

  g_return_if_fail (pos >= 0);
  g_return_if_fail (remove >= 0);
  g_return_if_fail (add >= 0);

  if (list->attributes)
    for (i = 0, p = list->attributes->len; i < p; i++)
      {
        PangoAttribute *attr = g_ptr_array_index (list->attributes, i);

        if (attr->start_index >= pos &&
          attr->end_index < pos + remove)
          {
            pango_attribute_destroy (attr);
            g_ptr_array_remove_index (list->attributes, i);
            i--; /* Look at this index again */
            p--;
            continue;
          }

        if (attr->start_index != PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING)
          {
            if (attr->start_index >= pos &&
                attr->start_index < pos + remove)
              {
                attr->start_index = pos + add;
              }
            else if (attr->start_index >= pos + remove)
              {
                attr->start_index += add - remove;
              }
          }

        if (attr->end_index != PANGO_ATTR_INDEX_TO_TEXT_END)
          {
            if (attr->end_index >= pos &&
                attr->end_index < pos + remove)
              {
                attr->end_index = pos;
              }
            else if (attr->end_index >= pos + remove)
              {
                if (add > remove &&
                    G_MAXUINT - attr->end_index < add - remove)
                  attr->end_index = G_MAXUINT;
                else
                  attr->end_index += add - remove;
              }
          }
      }
}

/**
 * pango_attr_list_splice:
 * @list: a `PangoAttrList`
 * @other: another `PangoAttrList`
 * @pos: the position in @list at which to insert @other
 * @len: the length of the spliced segment. (Note that this
 *   must be specified since the attributes in @other may only
 *   be present at some subsection of this range)
 *
 * This function opens up a hole in @list, fills it
 * in with attributes from the left, and then merges
 * @other on top of the hole.
 *
 * This operation is equivalent to stretching every attribute
 * that applies at position @pos in @list by an amount @len,
 * and then calling [method@Pango.AttrList.change] with a copy
 * of each attribute in @other in sequence (offset in position
 * by @pos, and limited in length to @len).
 *
 * This operation proves useful for, for instance, inserting
 * a pre-edit string in the middle of an edit buffer.
 *
 * For backwards compatibility, the function behaves differently
 * when @len is 0. In this case, the attributes from @other are
 * not imited to @len, and are just overlayed on top of @list.
 *
 * This mode is useful for merging two lists of attributes together.
 */
void
pango_attr_list_splice (PangoAttrList *list,
                        PangoAttrList *other,
                        gint           pos,
                        gint           len)
{
  guint i, p;
  guint upos, ulen;
  guint end;

  g_return_if_fail (list != NULL);
  g_return_if_fail (other != NULL);
  g_return_if_fail (pos >= 0);
  g_return_if_fail (len >= 0);

  upos = (guint)pos;
  ulen = (guint)len;

/* This definition only works when a and b are unsigned; overflow
 * isn't defined in the C standard for signed integers
 */
#define CLAMP_ADD(a,b) (((a) + (b) < (a)) ? G_MAXUINT : (a) + (b))

  end = CLAMP_ADD (upos, ulen);

  if (list->attributes)
    for (i = 0, p = list->attributes->len; i < p; i++)
      {
        PangoAttribute *attr = g_ptr_array_index (list->attributes, i);;

        if (attr->start_index <= upos)
          {
            if (attr->end_index > upos)
              attr->end_index = CLAMP_ADD (attr->end_index, ulen);
          }
        else
          {
            /* This could result in a zero length attribute if it
             * gets squashed up against G_MAXUINT, but deleting such
             * an element could (in theory) suprise the caller, so
             * we don't delete it.
             */
            attr->start_index = CLAMP_ADD (attr->start_index, ulen);
            attr->end_index = CLAMP_ADD (attr->end_index, ulen);
         }
      }

  if (!other->attributes || other->attributes->len == 0)
    return;

  for (i = 0, p = other->attributes->len; i < p; i++)
    {
      PangoAttribute *attr = pango_attribute_copy (g_ptr_array_index (other->attributes, i));
      if (ulen > 0)
        {
          attr->start_index = MIN (CLAMP_ADD (attr->start_index, upos), end);
          attr->end_index = MIN (CLAMP_ADD (attr->end_index, upos), end);
        }
      else
        {
          attr->start_index = CLAMP_ADD (attr->start_index, upos);
          attr->end_index = CLAMP_ADD (attr->end_index, upos);
        }

      /* Same as above, the attribute could be squashed to zero-length; here
       * pango_attr_list_change() will take care of deleting it.
       */
      pango_attr_list_change (list, attr);
    }
#undef CLAMP_ADD
}

/**
 * pango_attr_list_get_attributes:
 * @list: a `PangoAttrList`
 *
 * Gets a list of all attributes in @list.
 *
 * Return value: (element-type Pango.Attribute) (transfer full):
 *   a list of all attributes in @list. To free this value,
 *   call [method@Pango.Attribute.destroy] on each value and
 *   g_slist_free() on the list.
 *
 * Since: 1.44
 */
GSList *
pango_attr_list_get_attributes (PangoAttrList *list)
{
  GSList *result = NULL;
  guint i, p;

  g_return_val_if_fail (list != NULL, NULL);

  if (!list->attributes || list->attributes->len == 0)
    return NULL;

  for (i = 0, p = list->attributes->len; i < p; i++)
    {
      PangoAttribute *attr = g_ptr_array_index (list->attributes, i);

      result = g_slist_prepend (result, pango_attribute_copy (attr));
    }

  return g_slist_reverse (result);
}

/**
 * pango_attr_list_equal:
 * @list: a `PangoAttrList`
 * @other_list: the other `PangoAttrList`
 *
 * Checks whether @list and @other_list contain the same
 * attributes and whether those attributes apply to the
 * same ranges.
 *
 * Beware that this will return wrong values if any list
 * contains duplicates.
 *
 * Return value: %TRUE if the lists are equal, %FALSE if
 *   they aren't
 *
 * Since: 1.46
 */
gboolean
pango_attr_list_equal (PangoAttrList *list,
                       PangoAttrList *other_list)
{
  GPtrArray *attrs, *other_attrs;
  guint64 skip_bitmask = 0;
  guint i;

  if (list == other_list)
    return TRUE;

  if (list == NULL || other_list == NULL)
    return FALSE;

  if (list->attributes == NULL || other_list->attributes == NULL)
    return list->attributes == other_list->attributes;

  attrs = list->attributes;
  other_attrs = other_list->attributes;

  if (attrs->len != other_attrs->len)
    return FALSE;

  for (i = 0; i < attrs->len; i++)
    {
      PangoAttribute *attr = g_ptr_array_index (attrs, i);
      gboolean attr_equal = FALSE;
      guint other_attr_index;

      for (other_attr_index = 0; other_attr_index < other_attrs->len; other_attr_index++)
        {
          PangoAttribute *other_attr = g_ptr_array_index (other_attrs, other_attr_index);
          guint64 other_attr_bitmask = other_attr_index < 64 ? 1 << other_attr_index : 0;

          if ((skip_bitmask & other_attr_bitmask) != 0)
            continue;

          if (attr->start_index == other_attr->start_index &&
              attr->end_index == other_attr->end_index &&
              pango_attribute_equal (attr, other_attr))
            {
              skip_bitmask |= other_attr_bitmask;
              attr_equal = TRUE;
              break;
            }

        }

      if (!attr_equal)
        return FALSE;
    }

  return TRUE;
}

gboolean
_pango_attr_list_has_attributes (const PangoAttrList *list)
{
  return list && list->attributes != NULL && list->attributes->len > 0;
}

/**
 * pango_attr_list_filter:
 * @list: a `PangoAttrList`
 * @func: (scope call) (closure data): callback function;
 *   returns %TRUE if an attribute should be filtered out
 * @data: Data to be passed to @func
 *
 * Given a `PangoAttrList` and callback function, removes
 * any elements of @list for which @func returns %TRUE and
 * inserts them into a new list.
 *
 * Return value: (transfer full) (nullable): the new
 *   `PangoAttrList` or %NULL if no attributes of the
 *   given types were found
 *
 * Since: 1.2
 */
PangoAttrList *
pango_attr_list_filter (PangoAttrList       *list,
                        PangoAttrFilterFunc  func,
                        gpointer             data)

{
  PangoAttrList *new = NULL;
  guint i, p;

  g_return_val_if_fail (list != NULL, NULL);

  if (!list->attributes || list->attributes->len == 0)
    return NULL;

  for (i = 0, p = list->attributes->len; i < p; i++)
    {
      PangoAttribute *tmp_attr = g_ptr_array_index (list->attributes, i);

      if ((*func) (tmp_attr, data))
        {
          g_ptr_array_remove_index (list->attributes, i);
          i--; /* Need to look at this index again */
          p--;

          if (G_UNLIKELY (!new))
            {
              new = pango_attr_list_new ();
              new->attributes = g_ptr_array_new ();
            }

          g_ptr_array_add (new->attributes, tmp_attr);
        }
    }

  return new;
}

/* {{{ PangoAttrList serialization */

/* We serialize attribute lists to strings. The format
 * is a comma-separated list of the attributes in the order
 * in which they are in the list, with each attribute having
 * this format:
 *
 * START END NICK VALUE
 *
 * Values that can contain a comma, such as font descriptions
 * are quoted with "".
 */

static const char *
get_attr_type_nick (PangoAttrType attr_type)
{
  GEnumClass *enum_class;
  GEnumValue *enum_value;

  enum_class = g_type_class_ref (pango_attr_type_get_type ());
  enum_value = g_enum_get_value (enum_class, attr_type);
  g_type_class_unref (enum_class);

  return enum_value->value_nick;
}

static GType
get_attr_value_type (PangoAttrType type)
{
  switch ((int)type)
    {
    case PANGO_ATTR_STYLE: return PANGO_TYPE_STYLE;
    case PANGO_ATTR_WEIGHT: return PANGO_TYPE_WEIGHT;
    case PANGO_ATTR_VARIANT: return PANGO_TYPE_VARIANT;
    case PANGO_ATTR_STRETCH: return PANGO_TYPE_STRETCH;
    case PANGO_ATTR_WIDTH: return PANGO_TYPE_WIDTH;
    case PANGO_ATTR_GRAVITY: return PANGO_TYPE_GRAVITY;
    case PANGO_ATTR_GRAVITY_HINT: return PANGO_TYPE_GRAVITY_HINT;
    case PANGO_ATTR_UNDERLINE: return PANGO_TYPE_UNDERLINE;
    case PANGO_ATTR_OVERLINE: return PANGO_TYPE_OVERLINE;
    case PANGO_ATTR_BASELINE_SHIFT: return PANGO_TYPE_BASELINE_SHIFT;
    case PANGO_ATTR_FONT_SCALE: return PANGO_TYPE_FONT_SCALE;
    case PANGO_ATTR_TEXT_TRANSFORM: return PANGO_TYPE_TEXT_TRANSFORM;
    default: return G_TYPE_INVALID;
    }
}

static void
append_enum_value (GString *str,
                   GType    type,
                   int      value)
{
  GEnumClass *enum_class;
  GEnumValue *enum_value;

  enum_class = g_type_class_ref (type);
  enum_value = g_enum_get_value (enum_class, value);
  g_type_class_unref (enum_class);

  if (enum_value)
    g_string_append_printf (str, " %s", enum_value->value_nick);
  else
    g_string_append_printf (str, " %d", value);
}

static void
attr_print (GString        *str,
            PangoAttribute *attr)
{
  PangoAttrString *string;
  PangoAttrLanguage *lang;
  PangoAttrInt *integer;
  PangoAttrFloat *flt;
  PangoAttrFontDesc *font;
  PangoAttrColor *color;
  PangoAttrShape *shape;
  PangoAttrSize *size;
  PangoAttrFontFeatures *features;

  g_string_append_printf (str, "%u %u ", attr->start_index, attr->end_index);

  g_string_append (str, get_attr_type_nick (attr->klass->type));

  if (attr->klass->type == PANGO_ATTR_WEIGHT ||
      attr->klass->type == PANGO_ATTR_STYLE ||
      attr->klass->type == PANGO_ATTR_STRETCH ||
      attr->klass->type == PANGO_ATTR_WIDTH ||
      attr->klass->type == PANGO_ATTR_VARIANT ||
      attr->klass->type == PANGO_ATTR_GRAVITY ||
      attr->klass->type == PANGO_ATTR_GRAVITY_HINT ||
      attr->klass->type == PANGO_ATTR_UNDERLINE ||
      attr->klass->type == PANGO_ATTR_OVERLINE ||
      attr->klass->type == PANGO_ATTR_BASELINE_SHIFT ||
      attr->klass->type == PANGO_ATTR_FONT_SCALE ||
      attr->klass->type == PANGO_ATTR_TEXT_TRANSFORM)
    append_enum_value (str, get_attr_value_type (attr->klass->type), ((PangoAttrInt *)attr)->value);
  else if (attr->klass->type == PANGO_ATTR_STRIKETHROUGH ||
           attr->klass->type == PANGO_ATTR_ALLOW_BREAKS ||
           attr->klass->type == PANGO_ATTR_INSERT_HYPHENS ||
           attr->klass->type == PANGO_ATTR_FALLBACK)
    g_string_append (str, ((PangoAttrInt *)attr)->value ? " true" : " false");
  else if ((string = pango_attribute_as_string (attr)) != NULL)
    {
      char *s = g_strescape (string->value, NULL);
      g_string_append_printf (str, " \"%s\"", s);
      g_free (s);
    }
  else if ((lang = pango_attribute_as_language (attr)) != NULL)
    g_string_append_printf (str, " %s", pango_language_to_string (lang->value));
  else if ((integer = pango_attribute_as_int (attr)) != NULL)
    g_string_append_printf (str, " %d", integer->value);
  else if ((flt = pango_attribute_as_float (attr)) != NULL)
    {
      char buf[20];
      g_ascii_formatd (buf, 20, "%f", flt->value);
      g_string_append_printf (str, " %s", buf);
    }
  else if ((font = pango_attribute_as_font_desc (attr)) != NULL)
    {
      char *s = pango_font_description_to_string (font->desc);
      char *s2 = g_strescape (s, NULL);
      g_string_append_printf (str, " \"%s\"", s2);
      g_free (s2);
      g_free (s);
    }
  else if ((color = pango_attribute_as_color (attr)) != NULL)
    {
      char *s = pango_color_to_string (&color->color);
      g_string_append_printf (str, " %s", s);
      g_free (s);
    }
  else if ((shape = pango_attribute_as_shape (attr)) != NULL)
    g_string_append (str, "shape"); /* FIXME */
  else if ((size = pango_attribute_as_size (attr)) != NULL)
    g_string_append_printf (str, " %d", size->size);
  else if ((features = pango_attribute_as_font_features (attr)) != NULL)
    g_string_append_printf (str, " \"%s\"", features->features);
  else
    g_assert_not_reached ();
}

/**
 * pango_attr_list_to_string:
 * @list: a `PangoAttrList`
 *
 * Serializes a `PangoAttrList` to a string.
 *
 * In the resulting string, serialized attributes are separated by newlines or commas.
 * Individual attributes are serialized to a string of the form
 *
 *     [START END] TYPE VALUE
 *
 * Where START and END are the indices (with -1 being accepted in place
 * of MAXUINT), TYPE is the nickname of the attribute value type, e.g.
 * _weight_ or _stretch_, and the value is serialized according to its type:
 *
 * Optionally, START and END can be omitted to indicate unlimited extent.
 *
 * - enum values as nick or numeric value
 * - boolean values as _true_ or _false_
 * - integers and floats as numbers
 * - strings as string, optionally quoted
 * - font features as quoted string
 * - PangoLanguage as string
 * - PangoFontDescription as serialized by [method@Pango.FontDescription.to_string], quoted
 * - PangoColor as serialized by [method@Pango.Color.to_string]
 *
 * Examples:
 *
 *     0 10 foreground red, 5 15 weight bold, 0 200 font-desc "Sans 10"
 *
 *     0 -1 weight 700
 *     0 100 family Times
 *
 *     weight bold
 *
 * To parse the returned value, use [func@Pango.AttrList.from_string].
 *
 * Note that shape attributes can not be serialized.
 *
 * Returns: (transfer full): a newly allocated string
 * Since: 1.50
 */
char *
pango_attr_list_to_string (PangoAttrList *list)
{
  GString *s;

  s = g_string_new ("");

  if (list->attributes)
    for (int i = 0; i < list->attributes->len; i++)
      {
        PangoAttribute *attr = g_ptr_array_index (list->attributes, i);

        if (i > 0)
          g_string_append (s, "\n");
        attr_print (s, attr);
      }

  return g_string_free (s, FALSE);
}

static PangoAttrType
get_attr_type_by_nick (const char *nick,
                       int         len)
{
  GEnumClass *enum_class;

  enum_class = g_type_class_ref (pango_attr_type_get_type ());
  for (GEnumValue *ev = enum_class->values; ev->value_name; ev++)
    {
      if (ev->value_nick && strncmp (ev->value_nick, nick, len) == 0)
        {
          g_type_class_unref (enum_class);
          return (PangoAttrType) ev->value;
        }
    }

  g_type_class_unref (enum_class);
  return PANGO_ATTR_INVALID;
}

static int
get_attr_value (PangoAttrType  type,
                const char    *str,
                int            len)
{
  GEnumClass *enum_class;
  char *endp;
  int value;

  enum_class = g_type_class_ref (get_attr_value_type (type));
  for (GEnumValue *ev = enum_class->values; ev->value_name; ev++)
    {
      if (ev->value_nick && strncmp (ev->value_nick, str, len) == 0)
        {
          g_type_class_unref (enum_class);
          return ev->value;
        }
    }
  g_type_class_unref (enum_class);

  value = g_ascii_strtoll (str, &endp, 10);
  if (endp - str == len)
    return value;

  return -1;
}

static gboolean
is_valid_end_char (char c)
{
  return c == ',' || c == '\n' || c == '\0';
}

/**
 * pango_attr_list_from_string:
 * @text: a string
 *
 * Deserializes a `PangoAttrList` from a string.
 *
 * This is the counterpart to [method@Pango.AttrList.to_string].
 * See that functions for details about the format.
 *
 * Returns: (transfer full) (nullable): a new `PangoAttrList`
 * Since: 1.50
 */
PangoAttrList *
pango_attr_list_from_string (const char *text)
{
  PangoAttrList *list;
  const char *p;

  g_return_val_if_fail (text != NULL, NULL);

  list = pango_attr_list_new ();

  if (*text == '\0')
    return list;

  list->attributes = g_ptr_array_new ();

  p = text + strspn (text, " \t\n");
  while (*p)
    {
      const char *endp;
      char *endtok;
      gint64 start_index;
      gint64 end_index;
      char *str;
      PangoAttrType attr_type;
      PangoAttribute *attr;
      PangoLanguage *lang;
      gint64 integer;
      PangoColor color;
      double num;
      int len;

      if g_ascii_isdigit (p[0])
        {
          start_index = g_ascii_strtoll (p, &endtok, 10);
          if (*endtok != ' ')
            goto fail;
          endp = endtok;

          p = endp + strspn (endp, " ");
          if (!*p)
            goto fail;
          endp = endtok;

          end_index = g_ascii_strtoll (p, &endtok, 10);
          if (*endtok != ' ')
            goto fail;
          endp = endtok;

          p = endp + strspn (endp, " ");
        }
      else
        {
          /* START and END are omitted */
          start_index = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
          end_index = PANGO_ATTR_INDEX_TO_TEXT_END;
        }

      endp = (char *)p + strcspn (p, " ");
      attr_type = get_attr_type_by_nick (p, endp - p);

      p = endp + strspn (endp, " ");
      if (*p == '\0')
        goto fail;

#define INT_ATTR(name) \
          integer = g_ascii_strtoll (p, &endtok, 10); \
          if (!is_valid_end_char (*endtok)) goto fail; \
          attr = pango_attr_##name##_new ((int)integer); \
          endp = endtok;

#define INT_ATTR_ABS(name) \
          integer = g_ascii_strtoll (p, &endtok, 10); \
          if (!is_valid_end_char (*endtok)) goto fail; \
          attr = pango_attr_##name##_new_absolute ((int)integer); \
          endp = endtok;

#define MARK_ATTR(name) \
          integer = g_ascii_strtoll (p, &endtok, 10); \
          if (!is_valid_end_char (*endtok)) goto fail; \
          attr = pango_attr_##name##_new (); \
          endp = endtok;

#define BOOLEAN_ATTR(name,type) \
          if (strncmp (p, "true", strlen ("true")) == 0) \
            { \
              integer = 1; \
              endp = (char *)(p + strlen ("true")); \
            } \
          else if (strncmp (p, "false", strlen ("false")) == 0) \
            { \
              integer = 0; \
              endp = (char *)(p + strlen ("false")); \
            } \
          else \
            { \
              integer = g_ascii_strtoll (p, &endtok, 10); \
              endp = endtok; \
            } \
          if (!is_valid_end_char (*endp)) goto fail; \
          attr = pango_attr_##name##_new ((type)integer);

#define ENUM_ATTR(name, type, min, max) \
          endp = (char *)p + strcspn (p, ",\n"); \
          len = endp - p; \
          while (len > 0 && p[len - 1] == ' ') \
            len--; \
          integer = get_attr_value (attr_type, p, len); \
          attr = pango_attr_##name##_new ((type) CLAMP (integer, min, max));

#define FLAGS_ATTR(name,type) \
          integer = g_ascii_strtoll (p, &endtok, 10); \
          if (!is_valid_end_char (*endtok)) goto fail; \
          attr = pango_attr_##name##_new ((type)integer); \
          endp = endtok;

#define FLOAT_ATTR(name) \
          num = g_ascii_strtod (p, &endtok); \
          if (!is_valid_end_char (*endtok)) goto fail; \
          attr = pango_attr_##name##_new ((float)num); \
          endp = endtok;

#define COLOR_ATTR(name) \
          endp = (char *)p + strcspn (p, ",\n"); \
          if (!is_valid_end_char (*endp)) goto fail; \
          str = g_strndup (p, endp - p); \
          if (!pango_color_parse (&color, str)) \
            { \
              g_free (str); \
              goto fail; \
            } \
          attr = pango_attr_##name##_new (color.red, color.green, color.blue); \
          g_free (str);

#define STRING_ATTR(name) \
          if (*p != '"') goto fail; \
          p++; \
          endp = strchr (p, '"'); \
          if (!endp) goto fail; \
          /* check the next character like other attributes */ \
          if (!is_valid_end_char (*(endp + 1))) goto fail; \
          str = g_strndup (p, endp - p); \
          attr = pango_attr_##name##_new (str); \
          g_free (str); \
          endp++;

      switch (attr_type)
        {
        case PANGO_ATTR_INVALID:
          pango_attr_list_unref (list);
          return NULL;

        case PANGO_ATTR_LANGUAGE:
          endp = (char *)p + strcspn (p, ",\n");
          if (!is_valid_end_char (*endp)) goto fail;
          str = g_strndup (p, endp - p);
          lang = pango_language_from_string (str);
          attr = pango_attr_language_new (lang);
          g_free (str);
          break;

        case PANGO_ATTR_FAMILY:
          endp = (char *)p + strcspn (p, ",\n");
          if (!is_valid_end_char (*endp)) goto fail;
          if (p[0] == '"')
            {
              char *str2;

              len = endp - p;
              while (len > 0 && p[len - 1] == ' ')
                len--;

              if (p[len - 1] != '"') goto fail;

              str2 = g_strndup (p + 1, len - 2);
              str = g_strcompress (str2);
              g_free (str2);
            }
          else
            str = g_strndup (p, endp - p);
          attr = pango_attr_family_new (str);
          g_free (str);
          break;

        case PANGO_ATTR_STYLE:
          ENUM_ATTR(style, PangoStyle, PANGO_STYLE_NORMAL, PANGO_STYLE_ITALIC);
          break;

        case PANGO_ATTR_WEIGHT:
          ENUM_ATTR(weight, PangoWeight, PANGO_WEIGHT_THIN, PANGO_WEIGHT_ULTRAHEAVY);
          break;

        case PANGO_ATTR_VARIANT:
          ENUM_ATTR(variant, PangoVariant, PANGO_VARIANT_NORMAL, PANGO_VARIANT_TITLE_CAPS);
          break;

        case PANGO_ATTR_STRETCH:
          ENUM_ATTR(stretch, PangoStretch, PANGO_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_EXPANDED);
          break;

        case PANGO_ATTR_WIDTH:
          ENUM_ATTR(width, PangoWidth, PANGO_WIDTH_ULTRA_CONDENSED, PANGO_WIDTH_ULTRA_EXPANDED);
          break;

        case PANGO_ATTR_SIZE:
          INT_ATTR(size);
          break;

        case PANGO_ATTR_FONT_DESC:
          STRING_ATTR(font_desc_from_string);
          break;

        case PANGO_ATTR_FOREGROUND:
          COLOR_ATTR(foreground);
          break;

        case PANGO_ATTR_BACKGROUND:
          COLOR_ATTR(background);
          break;

        case PANGO_ATTR_UNDERLINE:
          ENUM_ATTR(underline, PangoUnderline, PANGO_UNDERLINE_NONE, PANGO_UNDERLINE_ERROR_LINE);
          break;

        case PANGO_ATTR_STRIKETHROUGH:
          BOOLEAN_ATTR(strikethrough, gboolean);
          break;

        case PANGO_ATTR_RISE:
          INT_ATTR(rise);
          break;

        case PANGO_ATTR_SHAPE:
          endp = (char *)p + strcspn (p, ",\n");
          continue; /* FIXME */

        case PANGO_ATTR_SCALE:
          FLOAT_ATTR(scale);
          break;

        case PANGO_ATTR_FALLBACK:
          BOOLEAN_ATTR(fallback, gboolean);
          break;

        case PANGO_ATTR_LETTER_SPACING:
          INT_ATTR(letter_spacing);
          break;

        case PANGO_ATTR_UNDERLINE_COLOR:
          COLOR_ATTR(underline_color);
          break;

        case PANGO_ATTR_STRIKETHROUGH_COLOR:
          COLOR_ATTR(strikethrough_color);
          break;

        case PANGO_ATTR_ABSOLUTE_SIZE:
          INT_ATTR_ABS(size);
          break;

        case PANGO_ATTR_GRAVITY:
          ENUM_ATTR(gravity, PangoGravity, PANGO_GRAVITY_SOUTH, PANGO_GRAVITY_WEST);
          break;

        case PANGO_ATTR_GRAVITY_HINT:
          ENUM_ATTR(gravity_hint, PangoGravityHint, PANGO_GRAVITY_HINT_NATURAL, PANGO_GRAVITY_HINT_LINE);
          break;

        case PANGO_ATTR_FONT_FEATURES:
          STRING_ATTR(font_features);
          break;

        case PANGO_ATTR_FOREGROUND_ALPHA:
          INT_ATTR(foreground_alpha);
          break;

        case PANGO_ATTR_BACKGROUND_ALPHA:
          INT_ATTR(background_alpha);
          break;

        case PANGO_ATTR_ALLOW_BREAKS:
          BOOLEAN_ATTR(allow_breaks, gboolean);
          break;

        case PANGO_ATTR_SHOW:
          FLAGS_ATTR(show, PangoShowFlags);
          break;

        case PANGO_ATTR_INSERT_HYPHENS:
          BOOLEAN_ATTR(insert_hyphens, gboolean);
          break;

        case PANGO_ATTR_OVERLINE:
          ENUM_ATTR(overline, PangoOverline, PANGO_OVERLINE_NONE, PANGO_OVERLINE_SINGLE);
          break;

        case PANGO_ATTR_OVERLINE_COLOR:
          COLOR_ATTR(overline_color);
          break;

        case PANGO_ATTR_LINE_HEIGHT:
          FLOAT_ATTR(line_height);
          break;

        case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
          INT_ATTR_ABS(line_height);
          break;

        case PANGO_ATTR_TEXT_TRANSFORM:
          ENUM_ATTR(text_transform, PangoTextTransform, PANGO_TEXT_TRANSFORM_NONE, PANGO_TEXT_TRANSFORM_CAPITALIZE);
          break;

        case PANGO_ATTR_WORD:
          MARK_ATTR(word);
          break;

        case PANGO_ATTR_SENTENCE:
          MARK_ATTR(sentence);
          break;

        case PANGO_ATTR_BASELINE_SHIFT:
          ENUM_ATTR(baseline_shift, PangoBaselineShift, 0, G_MAXINT);
          break;

        case PANGO_ATTR_FONT_SCALE:
          ENUM_ATTR(font_scale, PangoFontScale, PANGO_FONT_SCALE_NONE, PANGO_FONT_SCALE_SMALL_CAPS);
          break;

        default:
          g_assert_not_reached ();
        }

      attr->start_index = (guint)start_index;
      attr->end_index = (guint)end_index;
      g_ptr_array_add (list->attributes, attr);

      p = endp;
      if (*p)
        {
          if (*p == ',')
            p++;
          p += strspn (p, " \n");
        }
    }

  goto success;

fail:
  pango_attr_list_unref (list);
  list = NULL;

success:
  return list;
}

/* }}} */
/* {{{ Attribute Iterator */

G_DEFINE_BOXED_TYPE (PangoAttrIterator,
                     pango_attr_iterator,
                     pango_attr_iterator_copy,
                     pango_attr_iterator_destroy)

void
_pango_attr_list_get_iterator (PangoAttrList     *list,
                               PangoAttrIterator *iterator)
{
  iterator->attribute_stack = NULL;
  iterator->attrs = list->attributes;
  iterator->n_attrs = iterator->attrs ? iterator->attrs->len : 0;

  iterator->attr_index = 0;
  iterator->start_index = 0;
  iterator->end_index = 0;

  if (!pango_attr_iterator_next (iterator))
    iterator->end_index = G_MAXUINT;
}

/**
 * pango_attr_list_get_iterator:
 * @list: a `PangoAttrList`
 *
 * Create a iterator initialized to the beginning of the list.
 *
 * @list must not be modified until this iterator is freed.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttrIterator`, which should be freed with
 *   [method@Pango.AttrIterator.destroy]
 */
PangoAttrIterator *
pango_attr_list_get_iterator (PangoAttrList  *list)
{
  PangoAttrIterator *iterator;

  g_return_val_if_fail (list != NULL, NULL);

  iterator = g_slice_new (PangoAttrIterator);
  _pango_attr_list_get_iterator (list, iterator);

  return iterator;
}

/**
 * pango_attr_iterator_range:
 * @iterator: a PangoAttrIterator
 * @start: (out): location to store the start of the range
 * @end: (out): location to store the end of the range
 *
 * Get the range of the current segment.
 *
 * Note that the stored return values are signed, not unsigned
 * like the values in `PangoAttribute`. To deal with this API
 * oversight, stored return values that wouldn't fit into
 * a signed integer are clamped to %G_MAXINT.
 */
void
pango_attr_iterator_range (PangoAttrIterator *iterator,
                           gint              *start,
                           gint              *end)
{
  g_return_if_fail (iterator != NULL);

  if (start)
    *start = MIN (iterator->start_index, G_MAXINT);
  if (end)
    *end = MIN (iterator->end_index, G_MAXINT);
}

/**
 * pango_attr_iterator_next:
 * @iterator: a `PangoAttrIterator`
 *
 * Advance the iterator until the next change of style.
 *
 * Return value: %FALSE if the iterator is at the end
 *   of the list, otherwise %TRUE
 */
gboolean
pango_attr_iterator_next (PangoAttrIterator *iterator)
{
  int i;

  g_return_val_if_fail (iterator != NULL, FALSE);

  if (iterator->attr_index >= iterator->n_attrs &&
      (!iterator->attribute_stack || iterator->attribute_stack->len == 0))
    return FALSE;

  iterator->start_index = iterator->end_index;
  iterator->end_index = G_MAXUINT;

  if (iterator->attribute_stack)
    {
      for (i = iterator->attribute_stack->len - 1; i>= 0; i--)
        {
          const PangoAttribute *attr = g_ptr_array_index (iterator->attribute_stack, i);

          if (attr->end_index == iterator->start_index)
            g_ptr_array_remove_index (iterator->attribute_stack, i); /* Can't use index_fast :( */
          else
            iterator->end_index = MIN (iterator->end_index, attr->end_index);
        }
    }

  while (1)
    {
      PangoAttribute *attr;

      if (iterator->attr_index >= iterator->n_attrs)
        break;

      attr = g_ptr_array_index (iterator->attrs, iterator->attr_index);

      if (attr->start_index != iterator->start_index)
        break;

      if (attr->end_index > iterator->start_index)
        {
          if (G_UNLIKELY (!iterator->attribute_stack))
            iterator->attribute_stack = g_ptr_array_new ();

          g_ptr_array_add (iterator->attribute_stack, attr);

          iterator->end_index = MIN (iterator->end_index, attr->end_index);
        }

      iterator->attr_index++; /* NEXT! */
    }

  if (iterator->attr_index < iterator->n_attrs)
      {
      PangoAttribute *attr = g_ptr_array_index (iterator->attrs, iterator->attr_index);

      iterator->end_index = MIN (iterator->end_index, attr->start_index);
    }

  return TRUE;
}

/**
 * pango_attr_iterator_copy:
 * @iterator: a `PangoAttrIterator`
 *
 * Copy a `PangoAttrIterator`.
 *
 * Return value: (transfer full): the newly allocated
 *   `PangoAttrIterator`, which should be freed with
 *   [method@Pango.AttrIterator.destroy]
 */
PangoAttrIterator *
pango_attr_iterator_copy (PangoAttrIterator *iterator)
{
  PangoAttrIterator *copy;

  g_return_val_if_fail (iterator != NULL, NULL);

  copy = g_slice_new (PangoAttrIterator);

  *copy = *iterator;

  if (iterator->attribute_stack)
    copy->attribute_stack = g_ptr_array_copy (iterator->attribute_stack, NULL, NULL);
  else
    copy->attribute_stack = NULL;

  return copy;
}

void
_pango_attr_iterator_destroy (PangoAttrIterator *iterator)
{
  if (iterator->attribute_stack)
    g_ptr_array_free (iterator->attribute_stack, TRUE);
}

/**
 * pango_attr_iterator_destroy:
 * @iterator: a `PangoAttrIterator`
 *
 * Destroy a `PangoAttrIterator` and free all associated memory.
 */
void
pango_attr_iterator_destroy (PangoAttrIterator *iterator)
{
  g_return_if_fail (iterator != NULL);

  _pango_attr_iterator_destroy (iterator);
  g_slice_free (PangoAttrIterator, iterator);
}

/**
 * pango_attr_iterator_get:
 * @iterator: a `PangoAttrIterator`
 * @type: the type of attribute to find
 *
 * Find the current attribute of a particular type
 * at the iterator location.
 *
 * When multiple attributes of the same type overlap,
 * the attribute whose range starts closest to the
 * current location is used.
 *
 * Return value: (nullable) (transfer none): the current
 *   attribute of the given type, or %NULL if no attribute
 *   of that type applies to the current location.
 */
PangoAttribute *
pango_attr_iterator_get (PangoAttrIterator *iterator,
                         PangoAttrType      type)
{
  int i;

  g_return_val_if_fail (iterator != NULL, NULL);

  if (!iterator->attribute_stack)
    return NULL;

  for (i = iterator->attribute_stack->len - 1; i>= 0; i--)
    {
      PangoAttribute *attr = g_ptr_array_index (iterator->attribute_stack, i);

      if (attr->klass->type == type)
        return attr;
    }

  return NULL;
}

/**
 * pango_attr_iterator_get_font:
 * @iterator: a `PangoAttrIterator`
 * @desc: a `PangoFontDescription` to fill in with the current
 *   values. The family name in this structure will be set using
 *   [method@Pango.FontDescription.set_family_static] using
 *   values from an attribute in the `PangoAttrList` associated
 *   with the iterator, so if you plan to keep it around, you
 *   must call:
 *   `pango_font_description_set_family (desc, pango_font_description_get_family (desc))`.
 * @language: (out) (optional): location to store language tag
 *   for item, or %NULL if none is found.
 * @extra_attrs: (out) (optional) (element-type Pango.Attribute) (transfer full):
 *   location in which to store a list of non-font attributes
 *   at the the current position; only the highest priority
 *   value of each attribute will be added to this list. In
 *   order to free this value, you must call
 *   [method@Pango.Attribute.destroy] on each member.
 *
 * Get the font and other attributes at the current
 * iterator position.
 */
void
pango_attr_iterator_get_font (PangoAttrIterator     *iterator,
                              PangoFontDescription  *desc,
                              PangoLanguage        **language,
                              GSList               **extra_attrs)
{
  PangoFontMask mask = 0;
  gboolean have_language = FALSE;
  gdouble scale = 0;
  gboolean have_scale = FALSE;
  int i;

  g_return_if_fail (iterator != NULL);
  g_return_if_fail (desc != NULL);

  if (language)
    *language = NULL;

  if (extra_attrs)
    *extra_attrs = NULL;

  if (!iterator->attribute_stack)
    return;

  for (i = iterator->attribute_stack->len - 1; i >= 0; i--)
    {
      const PangoAttribute *attr = g_ptr_array_index (iterator->attribute_stack, i);

      switch ((int) attr->klass->type)
        {
        case PANGO_ATTR_FONT_DESC:
          {
            PangoFontMask new_mask = pango_font_description_get_set_fields (((PangoAttrFontDesc *)attr)->desc) & ~mask;
            mask |= new_mask;
            pango_font_description_unset_fields (desc, new_mask);
            pango_font_description_merge_static (desc, ((PangoAttrFontDesc *)attr)->desc, FALSE);

            break;
          }
        case PANGO_ATTR_FAMILY:
          if (!(mask & PANGO_FONT_MASK_FAMILY))
            {
              mask |= PANGO_FONT_MASK_FAMILY;
              pango_font_description_set_family (desc, ((PangoAttrString *)attr)->value);
            }
          break;
        case PANGO_ATTR_STYLE:
          if (!(mask & PANGO_FONT_MASK_STYLE))
            {
              mask |= PANGO_FONT_MASK_STYLE;
              pango_font_description_set_style (desc, ((PangoAttrInt *)attr)->value);
            }
          break;
        case PANGO_ATTR_VARIANT:
          if (!(mask & PANGO_FONT_MASK_VARIANT))
            {
              mask |= PANGO_FONT_MASK_VARIANT;
              pango_font_description_set_variant (desc, ((PangoAttrInt *)attr)->value);
            }
          break;
        case PANGO_ATTR_WEIGHT:
          if (!(mask & PANGO_FONT_MASK_WEIGHT))
            {
              mask |= PANGO_FONT_MASK_WEIGHT;
              pango_font_description_set_weight (desc, ((PangoAttrInt *)attr)->value);
            }
          break;
        case PANGO_ATTR_STRETCH:
          if (!(mask & PANGO_FONT_MASK_STRETCH))
            {
              mask |= PANGO_FONT_MASK_STRETCH;
              pango_font_description_set_stretch (desc, ((PangoAttrInt *)attr)->value);
            }
          break;
        case PANGO_ATTR_WIDTH:
          if (!(mask & PANGO_FONT_MASK_WIDTH))
            {
              mask |= PANGO_FONT_MASK_WIDTH;
              pango_font_description_set_width (desc, ((PangoAttrInt *)attr)->value);
            }
          break;
        case PANGO_ATTR_SIZE:
          if (!(mask & PANGO_FONT_MASK_SIZE))
            {
              mask |= PANGO_FONT_MASK_SIZE;
              pango_font_description_set_size (desc, ((PangoAttrSize *)attr)->size);
            }
          break;
        case PANGO_ATTR_ABSOLUTE_SIZE:
          if (!(mask & PANGO_FONT_MASK_SIZE))
            {
              mask |= PANGO_FONT_MASK_SIZE;
              pango_font_description_set_absolute_size (desc, ((PangoAttrSize *)attr)->size);
            }
          break;
        case PANGO_ATTR_SCALE:
          if (!have_scale)
            {
              have_scale = TRUE;
              scale = ((PangoAttrFloat *)attr)->value;
            }
          break;
        case PANGO_ATTR_LANGUAGE:
          if (language)
            {
              if (!have_language)
                {
                  have_language = TRUE;
                  *language = ((PangoAttrLanguage *)attr)->value;
                }
            }
          break;
        default:
          if (extra_attrs)
            {
              gboolean found = FALSE;

              /* Hack: special-case FONT_FEATURES, BASELINE_SHIFT and FONT_SCALE.
               * We don't want these to accumulate, not override each other,
               * so we never merge them.
               * This needs to be handled more systematically.
               */
              if (attr->klass->type != PANGO_ATTR_FONT_FEATURES &&
                  attr->klass->type != PANGO_ATTR_BASELINE_SHIFT &&
                  attr->klass->type != PANGO_ATTR_FONT_SCALE)
                {
                  GSList *tmp_list = *extra_attrs;
                  while (tmp_list)
                    {
                      PangoAttribute *old_attr = tmp_list->data;
                      if (attr->klass->type == old_attr->klass->type)
                        {
                          found = TRUE;
                          break;
                        }

                      tmp_list = tmp_list->next;
                    }
                }

              if (!found)
                *extra_attrs = g_slist_prepend (*extra_attrs, pango_attribute_copy (attr));
            }
        }
    }

  if (have_scale)
    {
      /* We need to use a local variable to ensure that the compiler won't
       * implicitly cast it to integer while the result is kept in registers,
       * leading to a wrong approximation in i386 (with 387 FPU)
       */
      volatile double size = scale * pango_font_description_get_size (desc);

      if (pango_font_description_get_size_is_absolute (desc))
        pango_font_description_set_absolute_size (desc, size);
      else
        pango_font_description_set_size (desc, size);
    }
}

/**
 * pango_attr_iterator_get_attrs:
 * @iterator: a `PangoAttrIterator`
 *
 * Gets a list of all attributes at the current position of the
 * iterator.
 *
 * Return value: (element-type Pango.Attribute) (transfer full):
 *   a list of all attributes for the current range. To free
 *   this value, call [method@Pango.Attribute.destroy] on each
 *   value and g_slist_free() on the list.
 *
 * Since: 1.2
 */
GSList *
pango_attr_iterator_get_attrs (PangoAttrIterator *iterator)
{
  GSList *attrs = NULL;
  int i;

  if (!iterator->attribute_stack ||
      iterator->attribute_stack->len == 0)
    return NULL;

  for (i = iterator->attribute_stack->len - 1; i >= 0; i--)
    {
      PangoAttribute *attr = g_ptr_array_index (iterator->attribute_stack, i);
      GSList *tmp_list2;
      gboolean found = FALSE;

      if (attr->klass->type != PANGO_ATTR_FONT_DESC &&
          attr->klass->type != PANGO_ATTR_BASELINE_SHIFT &&
          attr->klass->type != PANGO_ATTR_FONT_SCALE)
        for (tmp_list2 = attrs; tmp_list2; tmp_list2 = tmp_list2->next)
          {
            PangoAttribute *old_attr = tmp_list2->data;
            if (attr->klass->type == old_attr->klass->type)
              {
                found = TRUE;
                break;
              }
           }

      if (!found)
        attrs = g_slist_prepend (attrs, pango_attribute_copy (attr));
    }

  return attrs;
}

gboolean
pango_attr_iterator_advance (PangoAttrIterator *iterator,
                             int                index)
{
  int start_range, end_range;

  pango_attr_iterator_range (iterator, &start_range, &end_range);

  while (index >= end_range)
    {
      if (!pango_attr_iterator_next (iterator))
        return FALSE;
      pango_attr_iterator_range (iterator, &start_range, &end_range);
    }

  if (start_range > index)
    g_warning ("pango_attr_iterator_advance(): iterator had already "
               "moved beyond the index");

  return TRUE;
}
/* }}} */

/* vim:set foldmethod=marker expandtab: */
