diff options
author | Matthias Clasen <mclasen@redhat.com> | 2022-07-04 11:16:05 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2022-07-04 11:17:21 -0400 |
commit | bcdce34c8ac0adffd589e9fb68b62ff09368bb0f (patch) | |
tree | 7b92ba875b22212d15ffb9d9620d517cfa427a1e /pango2/pango-attr-list.c | |
parent | b197cc08b18bc44a3e93f859ca7e61dfe335bbc5 (diff) | |
download | pango-bcdce34c8ac0adffd589e9fb68b62ff09368bb0f.tar.gz |
Move the code from pango/ to pango2/
Life is easier if the src path matches the installed
header path prefix, so make them match. Update all
users.
Diffstat (limited to 'pango2/pango-attr-list.c')
-rw-r--r-- | pango2/pango-attr-list.c | 1286 |
1 files changed, 1286 insertions, 0 deletions
diff --git a/pango2/pango-attr-list.c b/pango2/pango-attr-list.c new file mode 100644 index 00000000..6f0f47d0 --- /dev/null +++ b/pango2/pango-attr-list.c @@ -0,0 +1,1286 @@ +/* Pango2 + * pango-attr-list.c: Attribute lists + * + * 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-attr-list-private.h" +#include "pango-attr-private.h" +#include "pango-impl-utils.h" + + +G_DEFINE_BOXED_TYPE (Pango2AttrList, pango2_attr_list, + pango2_attr_list_copy, + pango2_attr_list_unref); + +/* {{{ Utilities */ + +static void +pango2_attr_list_insert_internal (Pango2AttrList *list, + Pango2Attribute *attr, + gboolean before) +{ + const guint start_index = attr->start_index; + Pango2Attribute *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++) + { + Pango2Attribute *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; + } + } + } +} + +/* }}} */ +/* {{{ Private API */ + +void +pango2_attr_list_init (Pango2AttrList *list) +{ + list->ref_count = 1; + list->attributes = NULL; +} + +void +pango2_attr_list_destroy (Pango2AttrList *list) +{ + if (!list->attributes) + return; + + g_ptr_array_free (list->attributes, TRUE); +} + +gboolean +pango2_attr_list_has_attributes (const Pango2AttrList *list) +{ + return list && list->attributes != NULL && list->attributes->len > 0; +} + +/* }}} */ +/* {{{ Public API */ + +/** + * pango2_attr_list_new: + * + * Create a new empty attribute list with a reference + * count of one. + * + * Return value: (transfer full): the newly allocated + * `Pango2AttrList`, which should be freed with + * [method@Pango2.AttrList.unref] + */ +Pango2AttrList * +pango2_attr_list_new (void) +{ + Pango2AttrList *list = g_slice_new (Pango2AttrList); + + pango2_attr_list_init (list); + + return list; +} + +/** + * pango2_attr_list_ref: + * @list: (nullable): a `Pango2AttrList` + * + * Increase the reference count of the given attribute + * list by one. + * + * Return value: The attribute list passed in + */ +Pango2AttrList * +pango2_attr_list_ref (Pango2AttrList *list) +{ + if (list == NULL) + return NULL; + + g_atomic_int_inc ((int *) &list->ref_count); + + return list; +} + +/** + * pango2_attr_list_unref: + * @list: (nullable): a `Pango2AttrList` + * + * 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 +pango2_attr_list_unref (Pango2AttrList *list) +{ + if (list == NULL) + return; + + g_return_if_fail (list->ref_count > 0); + + if (g_atomic_int_dec_and_test ((int *) &list->ref_count)) + { + pango2_attr_list_destroy (list); + g_slice_free (Pango2AttrList, list); + } +} + +/** + * pango2_attr_list_copy: + * @list: (nullable): a `Pango2AttrList` + * + * Copy @list and return an identical new list. + * + * Return value: (nullable): the newly allocated + * `Pango2AttrList`, with a reference count of one, + * which should be freed with [method@Pango2.AttrList.unref] + */ +Pango2AttrList * +pango2_attr_list_copy (Pango2AttrList *list) +{ + Pango2AttrList *new; + + if (list == NULL) + return NULL; + + new = pango2_attr_list_new (); + if (!list->attributes || list->attributes->len == 0) + return new; + + new->attributes = g_ptr_array_copy (list->attributes, (GCopyFunc)pango2_attribute_copy, NULL); + + return new; +} + +/** + * pango2_attr_list_insert: + * @list: a `Pango2AttrList` + * @attr: (transfer full): the attribute to insert + * + * Insert the given attribute into the `Pango2AttrList`. + * + * It will be inserted after all other attributes with a + * matching @start_index. + */ +void +pango2_attr_list_insert (Pango2AttrList *list, + Pango2Attribute *attr) +{ + g_return_if_fail (list != NULL); + g_return_if_fail (attr != NULL); + + pango2_attr_list_insert_internal (list, attr, FALSE); +} + +/** + * pango2_attr_list_insert_before: + * @list: a `Pango2AttrList` + * @attr: (transfer full): the attribute to insert + * + * Insert the given attribute into the `Pango2AttrList`. + * + * It will be inserted before all other attributes with a + * matching @start_index. + */ +void +pango2_attr_list_insert_before (Pango2AttrList *list, + Pango2Attribute *attr) +{ + g_return_if_fail (list != NULL); + g_return_if_fail (attr != NULL); + + pango2_attr_list_insert_internal (list, attr, TRUE); +} + +/** + * pango2_attr_list_change: + * @list: a `Pango2AttrList` + * @attr: (transfer full): the attribute to insert + * + * Insert the given attribute into the `Pango2AttrList`. + * + * 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@Pango2.AttrList.insert] + * for creating an attribute list in order (potentially + * much slower for large lists). However, + * [method@Pango2.AttrList.insert] is not suitable for + * continually changing a set of attributes since it + * never removes or combines existing attributes. + */ +void +pango2_attr_list_change (Pango2AttrList *list, + Pango2Attribute *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 */ + { + pango2_attribute_destroy (attr); + return; + } + + if (!list->attributes || list->attributes->len == 0) + { + pango2_attr_list_insert (list, attr); + return; + } + + inserted = FALSE; + for (i = 0, p = list->attributes->len; i < p; i++) + { + Pango2Attribute *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->type != attr->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 (pango2_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. + */ + pango2_attribute_destroy (attr); + return; + } + + tmp_attr->end_index = end_index; + pango2_attribute_destroy (attr); + + attr = tmp_attr; + inserted = TRUE; + break; + } + else + { + /* Split, truncate, or remove the old attribute + */ + if (tmp_attr->end_index > end_index) + { + Pango2Attribute *end_attr = pango2_attribute_copy (tmp_attr); + + end_attr->start_index = end_index; + pango2_attr_list_insert (list, end_attr); + } + + if (tmp_attr->start_index == start_index) + { + pango2_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 */ + pango2_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++) + { + Pango2Attribute *tmp_attr = g_ptr_array_index (list->attributes, i); + + if (tmp_attr->start_index > end_index) + break; + + if (tmp_attr->type != attr->type) + continue; + + if (tmp_attr == attr) + continue; + + if (tmp_attr->end_index <= attr->end_index || + pango2_attribute_equal (tmp_attr, attr)) + { + /* We can merge the new attribute with this attribute. */ + attr->end_index = MAX (end_index, tmp_attr->end_index); + pango2_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++) + { + Pango2Attribute *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; + } + } + } +} + +/** + * pango2_attr_list_update: + * @list: a `Pango2AttrList` + * @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. + */ +void +pango2_attr_list_update (Pango2AttrList *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++) + { + Pango2Attribute *attr = g_ptr_array_index (list->attributes, i); + + if (attr->start_index >= pos && + attr->end_index < pos + remove) + { + pango2_attribute_destroy (attr); + g_ptr_array_remove_index (list->attributes, i); + i--; /* Look at this index again */ + p--; + continue; + } + + if (attr->start_index != PANGO2_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 != PANGO2_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; + } + } + } +} + +/** + * pango2_attr_list_splice: + * @list: a `Pango2AttrList` + * @other: another `Pango2AttrList` + * @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@Pango2.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 +pango2_attr_list_splice (Pango2AttrList *list, + Pango2AttrList *other, + int pos, + int 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++) + { + Pango2Attribute *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++) + { + Pango2Attribute *attr = pango2_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 + * pango2_attr_list_change() will take care of deleting it. + */ + pango2_attr_list_change (list, attr); + } +#undef CLAMP_ADD +} + +/** + * pango2_attr_list_get_attributes: + * @list: a `Pango2AttrList` + * + * Gets a list of all attributes in @list. + * + * Return value: (element-type Pango2.Attribute) (transfer full): + * a list of all attributes in @list. To free this value, + * call [method@Pango2.Attribute.destroy] on each value and + * [func@GLib.SList.free] on the list + */ +GSList * +pango2_attr_list_get_attributes (Pango2AttrList *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++) + { + Pango2Attribute *attr = g_ptr_array_index (list->attributes, i); + + result = g_slist_prepend (result, pango2_attribute_copy (attr)); + } + + return g_slist_reverse (result); +} + +/** + * pango2_attr_list_equal: + * @list: a `Pango2AttrList` + * @other_list: the other `Pango2AttrList` + * + * 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 + */ +gboolean +pango2_attr_list_equal (Pango2AttrList *list, + Pango2AttrList *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++) + { + Pango2Attribute *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++) + { + Pango2Attribute *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 && + pango2_attribute_equal (attr, other_attr)) + { + skip_bitmask |= other_attr_bitmask; + attr_equal = TRUE; + break; + } + + } + + if (!attr_equal) + return FALSE; + } + + return TRUE; +} + +/** + * pango2_attr_list_filter: + * @list: a `Pango2AttrList` + * @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 `Pango2AttrList` 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 + * `Pango2AttrList` or %NULL if no attributes of the + * given types were found + */ +Pango2AttrList * +pango2_attr_list_filter (Pango2AttrList *list, + Pango2AttrFilterFunc func, + gpointer data) + +{ + Pango2AttrList *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++) + { + Pango2Attribute *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 = pango2_attr_list_new (); + new->attributes = g_ptr_array_new (); + } + + g_ptr_array_add (new->attributes, tmp_attr); + } + } + + return new; +} + +/* }}} */ +/* {{{ 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 GType +get_attr_value_type (Pango2AttrType type) +{ + switch ((int)type) + { + case PANGO2_ATTR_STYLE: return PANGO2_TYPE_STYLE; + case PANGO2_ATTR_WEIGHT: return PANGO2_TYPE_WEIGHT; + case PANGO2_ATTR_VARIANT: return PANGO2_TYPE_VARIANT; + case PANGO2_ATTR_STRETCH: return PANGO2_TYPE_STRETCH; + case PANGO2_ATTR_GRAVITY: return PANGO2_TYPE_GRAVITY; + case PANGO2_ATTR_GRAVITY_HINT: return PANGO2_TYPE_GRAVITY_HINT; + case PANGO2_ATTR_UNDERLINE: return PANGO2_TYPE_LINE_STYLE; + case PANGO2_ATTR_STRIKETHROUGH: return PANGO2_TYPE_LINE_STYLE; + case PANGO2_ATTR_OVERLINE: return PANGO2_TYPE_LINE_STYLE; + case PANGO2_ATTR_BASELINE_SHIFT: return PANGO2_TYPE_BASELINE_SHIFT; + case PANGO2_ATTR_FONT_SCALE: return PANGO2_TYPE_FONT_SCALE; + case PANGO2_ATTR_TEXT_TRANSFORM: return PANGO2_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, + Pango2Attribute *attr) +{ + const char *name; + + name = pango2_attr_type_get_name (attr->type); + if (!name) + return; + + g_string_append_printf (str, "%u %u %s", attr->start_index, attr->end_index, name); + + switch (PANGO2_ATTR_VALUE_TYPE (attr)) + { + case PANGO2_ATTR_VALUE_INT: + if (attr->type == PANGO2_ATTR_WEIGHT || + attr->type == PANGO2_ATTR_STYLE || + attr->type == PANGO2_ATTR_STRETCH || + attr->type == PANGO2_ATTR_VARIANT || + attr->type == PANGO2_ATTR_GRAVITY || + attr->type == PANGO2_ATTR_GRAVITY_HINT || + attr->type == PANGO2_ATTR_UNDERLINE || + attr->type == PANGO2_ATTR_OVERLINE || + attr->type == PANGO2_ATTR_BASELINE_SHIFT || + attr->type == PANGO2_ATTR_FONT_SCALE || + attr->type == PANGO2_ATTR_TEXT_TRANSFORM) + append_enum_value (str, get_attr_value_type (attr->type), attr->int_value); + else + g_string_append_printf (str, " %d", attr->int_value); + break; + + case PANGO2_ATTR_VALUE_BOOLEAN: + g_string_append (str, attr->int_value ? " true" : " false"); + break; + + case PANGO2_ATTR_VALUE_STRING: + g_string_append_printf (str, " \"%s\"", attr->str_value); + break; + + case PANGO2_ATTR_VALUE_LANGUAGE: + g_string_append_printf (str, " %s", pango2_language_to_string (attr->lang_value)); + break; + + case PANGO2_ATTR_VALUE_FLOAT: + { + char buf[20]; + g_ascii_formatd (buf, 20, "%f", attr->double_value); + g_string_append_printf (str, " %s", buf); + } + break; + + case PANGO2_ATTR_VALUE_FONT_DESC: + { + char *s = pango2_font_description_to_string (attr->font_value); + g_string_append_printf (str, " \"%s\"", s); + g_free (s); + } + break; + + case PANGO2_ATTR_VALUE_COLOR: + { + char *s = pango2_color_to_string (&attr->color_value); + g_string_append_printf (str, " %s", s); + g_free (s); + } + break; + + case PANGO2_ATTR_VALUE_POINTER: + { + char *s = pango2_attr_value_serialize (attr); + g_string_append_printf (str, " %s", s); + g_free (s); + } + break; + + default: + g_assert_not_reached (); + } +} + +/** + * pango2_attr_list_to_string: + * @list: a `Pango2AttrList` + * + * Serializes a `Pango2AttrList` to a string. + * + * No guarantees are made about the format of the string, + * it may change between Pango2 versions. + * + * The intended use of this function is testing and + * debugging. The format is not meant as a permanent + * storage format. + * + * Returns: (transfer full): a newly allocated string + */ +char * +pango2_attr_list_to_string (Pango2AttrList *list) +{ + GString *s; + + s = g_string_new (""); + + if (list->attributes) + for (int i = 0; i < list->attributes->len; i++) + { + Pango2Attribute *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 Pango2AttrType +get_attr_type_by_nick (const char *nick, + int len) +{ + GEnumClass *enum_class; + + enum_class = g_type_class_ref (pango2_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 (Pango2AttrType) ev->value; + } + } + + g_type_class_unref (enum_class); + return PANGO2_ATTR_INVALID; +} + +static int +get_attr_value (Pango2AttrType 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'; +} + +/** + * pango2_attr_list_from_string: + * @text: a string + * + * Deserializes a `Pango2AttrList` from a string. + * + * This is the counterpart to [method@Pango2.AttrList.to_string]. + * See that functions for details about the format. + * + * Returns: (transfer full) (nullable): a new `Pango2AttrList` + */ +Pango2AttrList * +pango2_attr_list_from_string (const char *text) +{ + Pango2AttrList *list; + const char *p; + + g_return_val_if_fail (text != NULL, NULL); + + list = pango2_attr_list_new (); + + if (*text == '\0') + return list; + + list->attributes = g_ptr_array_new (); + + p = text + strspn (text, " \t\n"); + while (*p) + { + char *endp; + gint64 start_index; + gint64 end_index; + char *str; + Pango2AttrType attr_type; + Pango2Attribute *attr; + Pango2Language *lang; + gint64 integer; + Pango2FontDescription *desc; + Pango2Color color; + double num; + + start_index = g_ascii_strtoll (p, &endp, 10); + if (*endp != ' ') + goto fail; + + p = endp + strspn (endp, " "); + if (!*p) + goto fail; + + end_index = g_ascii_strtoll (p, &endp, 10); + if (*endp != ' ') + goto fail; + + p = endp + strspn (endp, " "); + + 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,type) \ + integer = g_ascii_strtoll (p, &endp, 10); \ + if (!is_valid_end_char (*endp)) goto fail; \ + attr = pango2_attr_##name##_new ((type)integer); + +#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, &endp, 10); \ + if (!is_valid_end_char (*endp)) goto fail; \ + attr = pango2_attr_##name##_new ((type)integer); + +#define ENUM_ATTR(name, type, min, max) \ + endp = (char *)p + strcspn (p, ",\n"); \ + integer = get_attr_value (attr_type, p, endp - p); \ + attr = pango2_attr_##name##_new ((type) CLAMP (integer, min, max)); + +#define FLOAT_ATTR(name) \ + num = g_ascii_strtod (p, &endp); \ + if (!is_valid_end_char (*endp)) goto fail; \ + attr = pango2_attr_##name##_new ((float)num); + +#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 (!pango2_color_parse (&color, str)) \ + { \ + g_free (str); \ + goto fail; \ + } \ + attr = pango2_attr_##name##_new (&color); \ + g_free (str); + + switch (attr_type) + { + case PANGO2_ATTR_INVALID: + pango2_attr_list_unref (list); + return NULL; + + case PANGO2_ATTR_LANGUAGE: + endp = (char *)p + strcspn (p, ",\n"); + if (!is_valid_end_char (*endp)) goto fail; + str = g_strndup (p, endp - p); + lang = pango2_language_from_string (str); + attr = pango2_attr_language_new (lang); + g_free (str); + break; + + case PANGO2_ATTR_FAMILY: + p++; + endp = strchr (p, '"'); + if (!endp) goto fail; + str = g_strndup (p, endp - p); + attr = pango2_attr_family_new (str); + g_free (str); + endp++; + if (!is_valid_end_char (*endp)) goto fail; + break; + + case PANGO2_ATTR_STYLE: + ENUM_ATTR(style, Pango2Style, PANGO2_STYLE_NORMAL, PANGO2_STYLE_ITALIC); + break; + + case PANGO2_ATTR_WEIGHT: + ENUM_ATTR(weight, Pango2Weight, PANGO2_WEIGHT_THIN, PANGO2_WEIGHT_ULTRAHEAVY); + break; + + case PANGO2_ATTR_VARIANT: + ENUM_ATTR(variant, Pango2Variant, PANGO2_VARIANT_NORMAL, PANGO2_VARIANT_TITLE_CAPS); + break; + + case PANGO2_ATTR_STRETCH: + ENUM_ATTR(stretch, Pango2Stretch, PANGO2_STRETCH_ULTRA_CONDENSED, PANGO2_STRETCH_ULTRA_EXPANDED); + break; + + case PANGO2_ATTR_SIZE: + INT_ATTR(size, int); + break; + + case PANGO2_ATTR_FONT_DESC: + p++; + endp = strchr (p, '"'); + if (!endp) goto fail; + str = g_strndup (p, endp - p); + desc = pango2_font_description_from_string (str); + attr = pango2_attr_font_desc_new (desc); + pango2_font_description_free (desc); + g_free (str); + endp++; + if (!is_valid_end_char (*endp)) goto fail; + break; + + case PANGO2_ATTR_FOREGROUND: + COLOR_ATTR(foreground); + break; + + case PANGO2_ATTR_BACKGROUND: + COLOR_ATTR(background); + break; + + case PANGO2_ATTR_UNDERLINE: + ENUM_ATTR(underline, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY); + break; + + case PANGO2_ATTR_UNDERLINE_POSITION: + ENUM_ATTR(underline_position, Pango2UnderlinePosition, PANGO2_UNDERLINE_POSITION_NORMAL, PANGO2_UNDERLINE_POSITION_UNDER); + break; + + case PANGO2_ATTR_STRIKETHROUGH: + ENUM_ATTR(strikethrough, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY); + break; + + case PANGO2_ATTR_RISE: + INT_ATTR(rise, int); + break; + + case PANGO2_ATTR_SCALE: + FLOAT_ATTR(scale); + break; + + case PANGO2_ATTR_FALLBACK: + BOOLEAN_ATTR(fallback, gboolean); + break; + + case PANGO2_ATTR_LETTER_SPACING: + INT_ATTR(letter_spacing, int); + break; + + case PANGO2_ATTR_UNDERLINE_COLOR: + COLOR_ATTR(underline_color); + break; + + case PANGO2_ATTR_STRIKETHROUGH_COLOR: + COLOR_ATTR(strikethrough_color); + break; + + case PANGO2_ATTR_ABSOLUTE_SIZE: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango2_attr_size_new_absolute (integer); + break; + + case PANGO2_ATTR_GRAVITY: + ENUM_ATTR(gravity, Pango2Gravity, PANGO2_GRAVITY_SOUTH, PANGO2_GRAVITY_WEST); + break; + + case PANGO2_ATTR_FONT_FEATURES: + p++; + endp = strchr (p, '"'); + if (!endp) goto fail; + str = g_strndup (p, endp - p); + attr = pango2_attr_font_features_new (str); + g_free (str); + endp++; + if (!is_valid_end_char (*endp)) goto fail; + break; + + case PANGO2_ATTR_GRAVITY_HINT: + ENUM_ATTR(gravity_hint, Pango2GravityHint, PANGO2_GRAVITY_HINT_NATURAL, PANGO2_GRAVITY_HINT_LINE); + break; + + case PANGO2_ATTR_ALLOW_BREAKS: + BOOLEAN_ATTR(allow_breaks, gboolean); + break; + + case PANGO2_ATTR_SHOW: + INT_ATTR(show, Pango2ShowFlags); + break; + + case PANGO2_ATTR_INSERT_HYPHENS: + BOOLEAN_ATTR(insert_hyphens, gboolean); + break; + + case PANGO2_ATTR_OVERLINE: + ENUM_ATTR(overline, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY); + break; + + case PANGO2_ATTR_OVERLINE_COLOR: + COLOR_ATTR(overline_color); + break; + + case PANGO2_ATTR_LINE_HEIGHT: + FLOAT_ATTR(line_height); + break; + + case PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango2_attr_line_height_new_absolute (integer); + break; + + case PANGO2_ATTR_TEXT_TRANSFORM: + ENUM_ATTR(text_transform, Pango2TextTransform, PANGO2_TEXT_TRANSFORM_NONE, PANGO2_TEXT_TRANSFORM_CAPITALIZE); + break; + + case PANGO2_ATTR_WORD: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango2_attr_word_new (); + break; + + case PANGO2_ATTR_SENTENCE: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango2_attr_sentence_new (); + break; + + case PANGO2_ATTR_PARAGRAPH: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango2_attr_paragraph_new (); + break; + + case PANGO2_ATTR_BASELINE_SHIFT: + ENUM_ATTR(baseline_shift, Pango2BaselineShift, 0, G_MAXINT); + break; + + case PANGO2_ATTR_FONT_SCALE: + ENUM_ATTR(font_scale, Pango2FontScale, PANGO2_FONT_SCALE_NONE, PANGO2_FONT_SCALE_SMALL_CAPS); + break; + + case PANGO2_ATTR_LINE_SPACING: + INT_ATTR(line_spacing, int); + break; + + case PANGO2_ATTR_SHAPE: + 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: + pango2_attr_list_unref (list); + list = NULL; + +success: + return list; +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ |