diff options
-rw-r--r-- | pango/pango-attributes.c | 543 | ||||
-rw-r--r-- | pango/pango-attributes.h | 5 | ||||
-rw-r--r-- | tests/meson.build | 1 | ||||
-rw-r--r-- | tests/testserialize.c | 86 |
4 files changed, 635 insertions, 0 deletions
diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c index c929a3d5..13468963 100644 --- a/pango/pango-attributes.c +++ b/pango/pango-attributes.c @@ -2524,6 +2524,549 @@ pango_attr_list_filter (PangoAttrList *list, 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_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_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) + g_string_append_printf (str, " %s", string->value); + 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); + g_string_append_printf (str, " \"%s\"", s); + 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. + * + * No guarantees are made about the format of the string, + * it may change between Pango 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 + * 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 [func@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) + { + char *endp; + gint64 start_index; + gint64 end_index; + char *str; + PangoAttrType attr_type; + PangoAttribute *attr; + PangoLanguage *lang; + gint64 integer; + PangoFontDescription *desc; + PangoColor 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 *)strpbrk (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 = pango_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 = pango_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 = pango_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 = pango_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 (!pango_color_parse (&color, str)) \ + { \ + g_free (str); \ + goto fail; \ + } \ + attr = pango_attr_##name##_new (color.red, color.green, color.blue); \ + g_free (str); + + 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; + 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_SIZE: + INT_ATTR(size, int); + break; + + case PANGO_ATTR_FONT_DESC: + p++; + endp = strchr (p, '"'); + if (!endp) goto fail; + str = g_strndup (p, endp - p); + desc = pango_font_description_from_string (str); + attr = pango_attr_font_desc_new (desc); + pango_font_description_free (desc); + g_free (str); + endp++; + if (!is_valid_end_char (*endp)) goto fail; + 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, int); + break; + + case PANGO_ATTR_SHAPE: + endp = (char *)strpbrk (p, ",\n"); + p = endp + strspn (endp, " "); + 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, int); + 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: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango_attr_size_new_absolute (integer); + break; + + case PANGO_ATTR_GRAVITY: + ENUM_ATTR(gravity, PangoGravity, PANGO_GRAVITY_SOUTH, PANGO_GRAVITY_WEST); + break; + + case PANGO_ATTR_FONT_FEATURES: + p++; + endp = strchr (p, '"'); + if (!endp) goto fail; + str = g_strndup (p, endp - p); + attr = pango_attr_font_features_new (str); + g_free (str); + endp++; + if (!is_valid_end_char (*endp)) goto fail; + break; + + case PANGO_ATTR_GRAVITY_HINT: + ENUM_ATTR(gravity_hint, PangoGravityHint, PANGO_GRAVITY_HINT_NATURAL, PANGO_GRAVITY_HINT_LINE); + break; + + case PANGO_ATTR_FOREGROUND_ALPHA: + INT_ATTR(foreground_alpha, int); + break; + + case PANGO_ATTR_BACKGROUND_ALPHA: + INT_ATTR(background_alpha, int); + break; + + case PANGO_ATTR_ALLOW_BREAKS: + BOOLEAN_ATTR(allow_breaks, gboolean); + break; + + case PANGO_ATTR_SHOW: + INT_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: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango_attr_line_height_new_absolute (integer); + break; + + case PANGO_ATTR_TEXT_TRANSFORM: + ENUM_ATTR(text_transform, PangoTextTransform, PANGO_TEXT_TRANSFORM_NONE, PANGO_TEXT_TRANSFORM_CAPITALIZE); + break; + + case PANGO_ATTR_WORD: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango_attr_word_new (); + break; + + case PANGO_ATTR_SENTENCE: + integer = g_ascii_strtoll (p, &endp, 10); + if (!is_valid_end_char (*endp)) goto fail; + attr = pango_attr_sentence_new (); + 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 */ diff --git a/pango/pango-attributes.h b/pango/pango-attributes.h index 018417d5..5ea6bd9e 100644 --- a/pango/pango-attributes.h +++ b/pango/pango-attributes.h @@ -707,6 +707,11 @@ PANGO_AVAILABLE_IN_1_46 gboolean pango_attr_list_equal (PangoAttrList *list, PangoAttrList *other_list); +PANGO_AVAILABLE_IN_1_50 +char * pango_attr_list_to_string (PangoAttrList *list); +PANGO_AVAILABLE_IN_1_50 +PangoAttrList * pango_attr_list_from_string (const char *text); + PANGO_AVAILABLE_IN_1_44 GType pango_attr_iterator_get_type (void) G_GNUC_CONST; diff --git a/tests/meson.build b/tests/meson.build index b5eda3e7..00741a14 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -53,6 +53,7 @@ if cairo_dep.found() [ 'cxx-test', [ 'cxx-test.cpp' ], [ libpangocairo_dep, gobject_dep, harfbuzz_dep ] ], [ 'test-harfbuzz', [ 'test-harfbuzz.c' ], [ libpangocairo_dep, gobject_dep, harfbuzz_dep ] ], [ 'test-break', [ 'test-break.c', 'test-common.c', 'validate-log-attrs.c' ], [libpangocairo_dep, glib_dep, harfbuzz_dep ] ], + [ 'testserialize', [ 'testserialize.c' ], [ libpangocairo_dep ] ], ] if host_system != 'darwin' diff --git a/tests/testserialize.c b/tests/testserialize.c new file mode 100644 index 00000000..cb5b27fd --- /dev/null +++ b/tests/testserialize.c @@ -0,0 +1,86 @@ +/* Pango + * + * Copyright (C) 2021 Matthias Clasen + * + * 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 <glib.h> +#include <pango/pangocairo.h> + +static void +test_serialize_attr_list (void) +{ + const char *valid[] = { + "5 16 style italic", + "0 10 foreground red, 5 15 weight bold, 0 200 font-desc \"Sans Small-Caps 10\"", + "0 10 foreground red\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"", + " 0 10 fallback false,\n 5 15 weight semilight\n\n \n \n", + "0 100 font-desc \"Cantarell, Sans, Italic Ultra-Light 64\", 10 11 weight 100", + "0 -1 size 10", + "0 1 weight 700, 2 4 weight book", + "0 200 rise 100\n5 15 family Times\n10 11 size 10240\n11 100 fallback 0\n30 60 stretch 2\n", + "" + }; + const char *roundtripped[] = { + "5 16 style italic", + "0 10 foreground #ffff00000000\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"", + "0 10 foreground #ffff00000000\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"", + "0 10 fallback false\n5 15 weight semilight", + "0 100 font-desc \"Cantarell,Sans Ultra-Light Italic 64\"\n10 11 weight thin", + "0 4294967295 size 10", + "0 1 weight bold\n2 4 weight book", + "0 200 rise 100\n5 15 family Times\n10 11 size 10240\n11 100 fallback false\n30 60 stretch condensed", + "" + }; + const char *invalid[] = { + "not an attr list", + "0 -1 FOREGROUND xyz", + ",,bla.wewq", + }; + + for (int i = 0; i < G_N_ELEMENTS (valid); i++) + { + PangoAttrList *attrs; + char *str; + + attrs = pango_attr_list_from_string (valid[i]); + g_assert_nonnull (attrs); + str = pango_attr_list_to_string (attrs); + g_assert_cmpstr (str, ==, roundtripped[i]); + g_free (str); + pango_attr_list_unref (attrs); + } + + for (int i = 0; i < G_N_ELEMENTS (invalid); i++) + { + PangoAttrList *attrs; + + attrs = pango_attr_list_from_string (invalid[i]); + g_assert_null (attrs); + } +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/serialize/attr-list", test_serialize_attr_list); + + return g_test_run (); +} |