Commit c58dfbfa authored by Matthias Clasen's avatar Matthias Clasen
Browse files

Add pango_attr_list_to/from_string

Add an api to serialize PangoAttrList.

This will be useful in testing and debugging.
parent 9278c4f0
......@@ -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 */
......
......@@ -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;
......
......@@ -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'
......
/* 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",