Blob Blame History Raw
/* 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.
 */

/**
 * SECTION:text-attributes
 * @short_description:Font and other attributes for annotating text
 * @title:Text Attributes
 *
 * Attributed text is used in a number of places in Pango. It
 * is used as the input to the itemization process and also when
 * creating a #PangoLayout. The data types and functions in
 * this section are used to represent and manipulate sets
 * of attributes applied to a portion of text.
 */
#include "config.h"
#include <string.h>

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

struct _PangoAttrList
{
  guint ref_count;
  GSList *attributes;
  GSList *attributes_tail;
};

struct _PangoAttrIterator
{
  GSList *next_attribute;
  GList *attribute_stack;
  guint start_index;
  guint end_index;
};

static PangoAttribute *pango_attr_color_new         (const PangoAttrClass *klass,
						     guint16               red,
						     guint16               green,
						     guint16               blue);
static PangoAttribute *pango_attr_string_new        (const PangoAttrClass *klass,
						     const char           *str);
static PangoAttribute *pango_attr_int_new           (const PangoAttrClass *klass,
						     int                   value);
static PangoAttribute *pango_attr_float_new         (const PangoAttrClass *klass,
						     double                value);
static PangoAttribute *pango_attr_size_new_internal (int                   size,
						     gboolean              absolute);


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 pango_attr_type_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 passed in when registering the type using
 * pango_attr_type_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 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);
}

/**
 * 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);
}

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;
}

/**
 * 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 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);
}

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;
}

/**
 * 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 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;
}

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;
}

/**
 * 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 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 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);
}

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;
}

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;
}

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;
}

/**
 * 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 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: the newly allocated #PangoAttribute, which should be
 *               freed with 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 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 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 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 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);
}

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);
}

/**
 * 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 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;
}


/**
 * 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 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 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 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 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 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_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 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 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 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);
}

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);
}

/**
 * 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: (allow-none): function to copy @data when the
 *                attribute is copied. If %NULL, @data is simply
 *                copied as a pointer.
 * @destroy_func: (allow-none): function to free @data when the
 *                attribute is freed, or %NULL
 *
 * Like pango_attr_shape_new(), but a user data pointer is also
 * provided; this pointer can be accessed when later
 * rendering the glyph.
 *
 * Return value: the newly allocated #PangoAttribute, which should be
 *               freed with 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 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 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 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, in CSS syntax
 *
 * Create a new font features tag attribute.
 *
 * Return value: (transfer full): the newly allocated #PangoAttribute,
 *               which should be freed with 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 new allocated #PangoAttribute,
 *               which should be freed with 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 new allocated #PangoAttribute,
 *               which should be freed with 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);
}

/*
 * Attribute List
 */

G_DEFINE_BOXED_TYPE (PangoAttrList, pango_attr_list,
                     pango_attr_list_copy,
                     pango_attr_list_unref);

/**
 * 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 pango_attr_list_unref().
 **/
PangoAttrList *
pango_attr_list_new (void)
{
  PangoAttrList *list = g_slice_new (PangoAttrList);

  list->ref_count = 1;
  list->attributes = NULL;
  list->attributes_tail = NULL;

  return list;
}

/**
 * pango_attr_list_ref:
 * @list: (nullable): a #PangoAttrList, may be %NULL
 *
 * 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;
}

/**
 * pango_attr_list_unref:
 * @list: (nullable): a #PangoAttrList, may be %NULL
 *
 * 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)
{
  GSList *tmp_list;

  if (list == NULL)
    return;

  g_return_if_fail (list->ref_count > 0);

  if (g_atomic_int_dec_and_test ((int *) &list->ref_count))
    {
      tmp_list = list->attributes;
      while (tmp_list)
	{
	  PangoAttribute *attr = tmp_list->data;
	  tmp_list = tmp_list->next;

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

      g_slist_free (list->attributes);

      g_slice_free (PangoAttrList, list);
    }
}

/**
 * pango_attr_list_copy:
 * @list: (nullable): a #PangoAttrList, may be %NULL
 *
 * 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 pango_attr_list_unref().
 *               Returns %NULL if @list was %NULL.
 **/
PangoAttrList *
pango_attr_list_copy (PangoAttrList *list)
{
  PangoAttrList *new;
  GSList *iter;
  GSList *new_attrs;

  if (list == NULL)
    return NULL;

  new = pango_attr_list_new ();

  iter = list->attributes;
  new_attrs = NULL;
  while (iter != NULL)
    {
      new_attrs = g_slist_prepend (new_attrs,
				   pango_attribute_copy (iter->data));

      iter = g_slist_next (iter);
    }

  /* we're going to reverse the nodes, so head becomes tail */
  new->attributes_tail = new_attrs;
  new->attributes = g_slist_reverse (new_attrs);

  return new;
}

static void
pango_attr_list_insert_internal (PangoAttrList  *list,
				 PangoAttribute *attr,
				 gboolean        before)
{
  GSList *tmp_list, *prev, *link;
  guint start_index = attr->start_index;

  if (!list->attributes)
    {
      list->attributes = g_slist_prepend (NULL, attr);
      list->attributes_tail = list->attributes;
    }
  else if (((PangoAttribute *)list->attributes_tail->data)->start_index < start_index ||
	   (!before && ((PangoAttribute *)list->attributes_tail->data)->start_index == start_index))
    {
      list->attributes_tail = g_slist_append (list->attributes_tail, attr);
      list->attributes_tail = list->attributes_tail->next;
      g_assert (list->attributes_tail);
    }
  else
    {
      prev = NULL;
      tmp_list = list->attributes;
      while (1)
	{
	  PangoAttribute *tmp_attr = tmp_list->data;

	  if (tmp_attr->start_index > start_index ||
	      (before && tmp_attr->start_index == start_index))
	    {
	      link = g_slist_alloc ();
	      link->next = tmp_list;
	      link->data = attr;

	      if (prev)
		prev->next = link;
	      else
		list->attributes = link;

	      break;
	    }

	  prev = tmp_list;
	  tmp_list = tmp_list->next;
	}
    }
}

/**
 * pango_attr_list_insert:
 * @list: a #PangoAttrList
 * @attr: (transfer full): the attribute to insert. Ownership of this
 *        value is assumed by the list.
 *
 * 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. Ownership of this
 *        value is assumed by the list.
 *
 * 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. Ownership of this
 *        value is assumed by the list.
 *
 * 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 pango_attr_list_insert() for
 * creating a attribute list in order (potentially much slower
 * for large lists). However, pango_attr_list_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)
{
  GSList *tmp_list, *prev, *link;
  guint start_index = attr->start_index;
  guint end_index = attr->end_index;

  g_return_if_fail (list != NULL);

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

  tmp_list = list->attributes;
  prev = NULL;
  while (1)
    {
      PangoAttribute *tmp_attr;

      if (!tmp_list ||
	  ((PangoAttribute *)tmp_list->data)->start_index > start_index)
	{
	  /* We need to insert a new attribute
	   */
	  link = g_slist_alloc ();
	  link->next = tmp_list;
	  link->data = attr;

	  if (prev)
	    prev->next = link;
	  else
	    list->attributes = link;

	  if (!tmp_list)
	    list->attributes_tail = link;

	  prev = link;
	  tmp_list = prev->next;
	  break;
	}

      tmp_attr = tmp_list->data;

      if (tmp_attr->klass->type == attr->klass->type &&
	  tmp_attr->end_index >= start_index)
	{
	  /* We overlap with an existing attribute */
	  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;

	      prev = tmp_list;
	      tmp_list = tmp_list->next;

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

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

	      if (tmp_attr->start_index == attr->start_index)
		{
		  pango_attribute_destroy (tmp_attr);
		  tmp_list->data = attr;

		  prev = tmp_list;
		  tmp_list = tmp_list->next;
		  break;
		}
	      else
		{
		  tmp_attr->end_index = attr->start_index;
		}
	    }
	}
      prev = tmp_list;
      tmp_list = tmp_list->next;
    }
  /* At this point, prev points to the list node with attr in it,
   * tmp_list points to prev->next.
   */

  g_assert (prev->data == attr);
  g_assert (prev->next == tmp_list);

  /* We now have the range inserted into the list one way or the
   * other. Fix up the remainder
   */
  while (tmp_list)
    {
      PangoAttribute *tmp_attr = tmp_list->data;

      if (tmp_attr->start_index > end_index)
	break;
      else if (tmp_attr->klass->type == attr->klass->type)
	{
	  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);
	      prev->next = tmp_list->next;

	      if (!prev->next)
		list->attributes_tail = prev;

	      g_slist_free_1 (tmp_list);
	      tmp_list = prev->next;

	      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
	       */
	      GSList *tmp_list2;
	      GSList *prev2;

	      tmp_attr->start_index = attr->end_index;

	      tmp_list2 = tmp_list->next;
	      prev2 = tmp_list;

	      while (tmp_list2)
		{
		  PangoAttribute *tmp_attr2 = tmp_list2->data;

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

		  prev2 = tmp_list2;
		  tmp_list2 = tmp_list2->next;
		}

	      /* Now remove and insert before tmp_list2. We'll
	       * hit this attribute again later, but that's harmless.
	       */
	      if (prev2 != tmp_list)
		{
		  GSList *old_next = tmp_list->next;

		  prev->next = old_next;
		  prev2->next = tmp_list;
		  tmp_list->next = tmp_list2;

		  if (!tmp_list->next)
		    list->attributes_tail = tmp_list;

		  tmp_list = old_next;

		  continue;
		}
	    }
	}

      prev = tmp_list;
      tmp_list = tmp_list->next;
    }
}

/**
 * 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 pango_attr_list_change() with a copy
 * of each attribute in @other in sequence (offset in position by @pos).
 *
 * This operation proves useful for, for instance, inserting
 * a pre-edit string in the middle of an edit buffer.
 **/
void
pango_attr_list_splice (PangoAttrList *list,
			PangoAttrList *other,
			gint           pos,
			gint           len)
{
  GSList *tmp_list;
  guint upos, ulen;

  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))

  tmp_list = list->attributes;
  while (tmp_list)
    {
      PangoAttribute *attr = tmp_list->data;

      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);
	}

      tmp_list = tmp_list->next;
    }

  tmp_list = other->attributes;
  while (tmp_list)
    {
      PangoAttribute *attr = pango_attribute_copy (tmp_list->data);
      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);

      tmp_list = tmp_list->next;
    }
#undef CLAMP_ADD
}

/**
 * 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 pango_attr_iterator_destroy().
 **/
PangoAttrIterator *
pango_attr_list_get_iterator (PangoAttrList  *list)
{
  PangoAttrIterator *iterator;

  g_return_val_if_fail (list != NULL, NULL);

  iterator = g_slice_new (PangoAttrIterator);
  iterator->next_attribute = list->attributes;
  iterator->attribute_stack = NULL;

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

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

  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)
{
  GList *tmp_list;

  g_return_val_if_fail (iterator != NULL, FALSE);

  if (!iterator->next_attribute && !iterator->attribute_stack)
    return FALSE;

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

  tmp_list = iterator->attribute_stack;
  while (tmp_list)
    {
      GList *next = tmp_list->next;
      PangoAttribute *attr = tmp_list->data;

      if (attr->end_index == iterator->start_index)
	{
	  iterator->attribute_stack = g_list_remove_link (iterator->attribute_stack, tmp_list);
	  g_list_free_1 (tmp_list);
	}
      else
	{
	  iterator->end_index = MIN (iterator->end_index, attr->end_index);
	}

      tmp_list = next;
    }

  while (iterator->next_attribute &&
	 ((PangoAttribute *)iterator->next_attribute->data)->start_index == iterator->start_index)
    {
      if (((PangoAttribute *)iterator->next_attribute->data)->end_index > iterator->start_index)
	{
	  iterator->attribute_stack = g_list_prepend (iterator->attribute_stack, iterator->next_attribute->data);
	  iterator->end_index = MIN (iterator->end_index, ((PangoAttribute *)iterator->next_attribute->data)->end_index);
	}
      iterator->next_attribute = iterator->next_attribute->next;
    }

  if (iterator->next_attribute)
    iterator->end_index = MIN (iterator->end_index, ((PangoAttribute *)iterator->next_attribute->data)->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
 *               pango_attr_iterator_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;

  copy->attribute_stack = g_list_copy (iterator->attribute_stack);

  return copy;
}

/**
 * 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);

  g_list_free (iterator->attribute_stack);
  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): 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)
{
  GList *tmp_list;

  g_return_val_if_fail (iterator != NULL, NULL);

  tmp_list = iterator->attribute_stack;
  while (tmp_list)
    {
      PangoAttribute *attr = tmp_list->data;

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

      tmp_list = tmp_list->next;
    }

  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
 *        pango_font_description_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:
 *        <literal>pango_font_description_set_family (desc, pango_font_description_get_family (desc))</literal>.
 * @language: (allow-none): if non-%NULL, location to store language tag for item, or %NULL
 *            if none is found.
 * @extra_attrs: (allow-none) (element-type Pango.Attribute) (transfer full): if non-%NULL,
 *           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 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)
{
  GList *tmp_list1;
  GSList *tmp_list2;

  PangoFontMask mask = 0;
  gboolean have_language = FALSE;
  gdouble scale = 0;
  gboolean have_scale = FALSE;

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

  if (language)
    *language = NULL;

  if (extra_attrs)
    *extra_attrs = NULL;

  tmp_list1 = iterator->attribute_stack;
  while (tmp_list1)
    {
      PangoAttribute *attr = tmp_list1->data;
      tmp_list1 = tmp_list1->next;

      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_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;

	      tmp_list2 = *extra_attrs;
	      /* Hack: special-case FONT_FEATURES.  We don't want them to
	       * override each other, so we never merge them.  This should
	       * be fixed when we implement attr-merging. */
	      if (attr->klass->type != PANGO_ATTR_FONT_FEATURES)
		while (tmp_list2)
		  {
		    PangoAttribute *old_attr = tmp_list2->data;
		    if (attr->klass->type == old_attr->klass->type)
		      {
			found = TRUE;
			break;
		      }

		    tmp_list2 = tmp_list2->next;
		  }

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

  if (have_scale)
    {
      if (pango_font_description_get_size_is_absolute (desc))
        pango_font_description_set_absolute_size (desc, scale * pango_font_description_get_size (desc));
      else
        pango_font_description_set_size (desc, scale * pango_font_description_get_size (desc));
    }
}

/**
 * pango_attr_list_filter:
 * @list: a #PangoAttrList
 * @func: (scope call) (closure data): callback function; returns %TRUE
 *        if an attribute should be filtered out.
 * @data: (closure): 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;
  GSList *tmp_list;
  GSList *prev;

  g_return_val_if_fail (list != NULL, NULL);

  tmp_list = list->attributes;
  prev = NULL;
  while (tmp_list)
    {
      GSList *next = tmp_list->next;
      PangoAttribute *tmp_attr = tmp_list->data;

      if ((*func) (tmp_attr, data))
	{
	  if (!tmp_list->next)
	    list->attributes_tail = prev;

	  if (prev)
	    prev->next = tmp_list->next;
	  else
	    list->attributes = tmp_list->next;

	  tmp_list->next = NULL;

	  if (!new)
	    {
	      new = pango_attr_list_new ();
	      new->attributes = new->attributes_tail = tmp_list;
	    }
	  else
	    {
	      new->attributes_tail->next = tmp_list;
	      new->attributes_tail = tmp_list;
	    }

	  goto next_attr;
	}

      prev = tmp_list;

    next_attr:
      tmp_list = next;
    }

  return new;
}

/**
 * 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 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;
  GList *tmp_list;

  for (tmp_list = iterator->attribute_stack; tmp_list; tmp_list = tmp_list->next)
    {
      PangoAttribute *attr = tmp_list->data;
      GSList *tmp_list2;
      gboolean found = FALSE;

      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;
}