[pango/serializer2: 2/3] Add layout serialization api




commit aba01db213c28bfba9c31d022781a880684897ea
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Nov 11 21:44:56 2021 -0500

    Add layout serialization api
    
    Add api to serialize PangoLayout, for the benefit
    of testing and debugging. Currently, this uses
    json, but that is an implementation detail.
    
    Some tests included.

 meson.build              |   4 +
 pango/meson.build        |   1 +
 pango/pango-attributes.c |   5 +-
 pango/pango-layout.h     |  19 +
 pango/serializer.c       | 893 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/testserialize.c    | 163 +++++++++
 6 files changed, 1082 insertions(+), 3 deletions(-)
---
diff --git a/meson.build b/meson.build
index 37bcf6cb..4acb0654 100644
--- a/meson.build
+++ b/meson.build
@@ -232,6 +232,7 @@ harfbuzz_req_version = '>= 2.6.0'
 fontconfig_req_version = '>= 2.13.0'
 xft_req_version = '>= 2.0.0'
 cairo_req_version = '>= 1.12.10'
+json_glib_version = '>= 1.6.0'
 
 # libm
 mathlib_dep = cc.find_library('m', required: false)
@@ -251,6 +252,9 @@ fribidi_dep = dependency('fribidi', version: fribidi_req_version,
                          default_options: ['docs=false'])
 pango_deps += fribidi_dep
 
+json_glib_dep = dependency('json-glib-1.0', version: json_glib_version)
+pango_deps += json_glib_dep
+
 thai_dep = dependency('libthai', version: libthai_req_version, required: get_option('libthai'))
 if thai_dep.found()
   pango_conf.set('HAVE_LIBTHAI', 1)
diff --git a/pango/meson.build b/pango/meson.build
index 084de229..7c7bb280 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -27,6 +27,7 @@ pango_sources = [
   'pango-utils.c',
   'reorder-items.c',
   'shape.c',
+  'serializer.c',
 ]
 
 pango_headers = [
diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c
index 13468963..cfaf9b17 100644
--- a/pango/pango-attributes.c
+++ b/pango/pango-attributes.c
@@ -2802,7 +2802,7 @@ pango_attr_list_from_string (const char *text)
 
       p = endp + strspn (endp, " ");
 
-      endp = (char *)strpbrk (p, " ");
+      endp = (char *)p + strcspn (p, " ");
       attr_type = get_attr_type_by_nick (p, endp - p);
 
       p = endp + strspn (endp, " ");
@@ -2929,8 +2929,7 @@ pango_attr_list_from_string (const char *text)
           break;
 
         case PANGO_ATTR_SHAPE:
-          endp = (char *)strpbrk (p, ",\n");
-          p = endp  + strspn (endp, " ");
+          endp = (char *)p + strcspn (p, ",\n");
           continue; /* FIXME */
 
         case PANGO_ATTR_SCALE:
diff --git a/pango/pango-layout.h b/pango/pango-layout.h
index e28f9295..69d1e887 100644
--- a/pango/pango-layout.h
+++ b/pango/pango-layout.h
@@ -351,6 +351,25 @@ GSList *         pango_layout_get_lines            (PangoLayout    *layout);
 PANGO_AVAILABLE_IN_1_16
 GSList *         pango_layout_get_lines_readonly   (PangoLayout    *layout);
 
+#define PANGO_LAYOUT_SERIALIZE_ERROR (pango_layout_serialize_error_quark ())
+
+typedef enum {
+  PANGO_LAYOUT_SERIALIZE_INVALID,
+  PANGO_LAYOUT_SERIALIZE_INVALID_SYNTAX,
+  PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+  PANGO_LAYOUT_SERIALIZE_MISSING_VALUE,
+} PangoLayoutSerializeError;
+
+PANGO_AVAILABLE_IN_1_50
+GQuark          pango_layout_serialize_error_quark (void);
+
+PANGO_AVAILABLE_IN_1_50
+GBytes *        pango_layout_serialize             (PangoLayout    *layout);
+
+PANGO_AVAILABLE_IN_1_50
+PangoLayout *   pango_layout_deserialize           (PangoContext   *context,
+                                                    GBytes         *bytes,
+                                                    GError        **error);
 
 #define PANGO_TYPE_LAYOUT_LINE (pango_layout_line_get_type ())
 
diff --git a/pango/serializer.c b/pango/serializer.c
new file mode 100644
index 00000000..bb2a3801
--- /dev/null
+++ b/pango/serializer.c
@@ -0,0 +1,893 @@
+/* Pango
+ * serializer.c: Code to serialize various Pango objects
+ *
+ * Copyright (C) 2021 Red Hat, Inc
+ *
+ * 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 <pango/pango-layout.h>
+#include <pango/pango-layout-private.h>
+#include <pango/pango-enum-types.h>
+
+#include <json-glib/json-glib.h>
+
+/* {{{ Error handling */
+
+G_DEFINE_QUARK(pango-layout-serialize-error-quark, pango_layout_serialize_error)
+
+/* }}} */
+/* {{{ Serialization */
+
+static GType
+get_enum_type (PangoAttrType attr_type)
+{
+  switch ((int)attr_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
+add_enum_value (JsonBuilder   *builder,
+                GType          type,
+                int            value,
+                gboolean       allow_extra)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+
+  enum_class = g_type_class_ref (type);
+  enum_value = g_enum_get_value (enum_class, value);
+
+  if (enum_value)
+    json_builder_add_string_value (builder, enum_value->value_nick);
+  else if (allow_extra)
+    {
+      char buf[128];
+      g_snprintf (buf, 128, "%d", value);
+      json_builder_add_string_value (builder, buf);
+    }
+  else
+    json_builder_add_string_value (builder, "ERROR");
+
+}
+
+static void
+add_attribute (JsonBuilder    *builder,
+               PangoAttribute *attr)
+{
+  char *str;
+
+  json_builder_begin_object (builder);
+
+  if (attr->start_index != PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING)
+    {
+      json_builder_set_member_name (builder, "start");
+      json_builder_add_int_value (builder, (int)attr->start_index);
+    }
+  if (attr->end_index != PANGO_ATTR_INDEX_TO_TEXT_END)
+    {
+      json_builder_set_member_name (builder, "end");
+      json_builder_add_int_value (builder, (int)attr->end_index);
+    }
+  json_builder_set_member_name (builder, "type");
+  add_enum_value (builder, PANGO_TYPE_ATTR_TYPE, attr->klass->type, FALSE);
+
+  json_builder_set_member_name (builder, "value");
+  switch (attr->klass->type)
+    {
+    default:
+    case PANGO_ATTR_INVALID:
+      g_assert_not_reached ();
+    case PANGO_ATTR_LANGUAGE:
+      json_builder_add_string_value (builder, pango_language_to_string (((PangoAttrLanguage*)attr)->value));
+      break;
+    case PANGO_ATTR_FAMILY:
+    case PANGO_ATTR_FONT_FEATURES:
+      json_builder_add_string_value (builder, ((PangoAttrString*)attr)->value);
+      break;
+    case PANGO_ATTR_STYLE:
+    case PANGO_ATTR_VARIANT:
+    case PANGO_ATTR_STRETCH:
+    case PANGO_ATTR_UNDERLINE:
+    case PANGO_ATTR_OVERLINE:
+    case PANGO_ATTR_GRAVITY:
+    case PANGO_ATTR_GRAVITY_HINT:
+    case PANGO_ATTR_TEXT_TRANSFORM:
+    case PANGO_ATTR_FONT_SCALE:
+      add_enum_value (builder, get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, FALSE);
+      break;
+    case PANGO_ATTR_WEIGHT:
+    case PANGO_ATTR_BASELINE_SHIFT:
+      add_enum_value (builder, get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, TRUE);
+      break;
+    case PANGO_ATTR_SIZE:
+    case PANGO_ATTR_RISE:
+    case PANGO_ATTR_LETTER_SPACING:
+    case PANGO_ATTR_ABSOLUTE_SIZE:
+    case PANGO_ATTR_FOREGROUND_ALPHA:
+    case PANGO_ATTR_BACKGROUND_ALPHA:
+    case PANGO_ATTR_SHOW:
+    case PANGO_ATTR_WORD:
+    case PANGO_ATTR_SENTENCE:
+    case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
+      json_builder_add_int_value (builder, ((PangoAttrInt*)attr)->value);
+      break;
+    case PANGO_ATTR_FONT_DESC:
+      str = pango_font_description_to_string (((PangoAttrFontDesc*)attr)->desc);
+      json_builder_add_string_value (builder, str);
+      g_free (str);
+      break;
+    case PANGO_ATTR_FOREGROUND:
+    case PANGO_ATTR_BACKGROUND:
+    case PANGO_ATTR_UNDERLINE_COLOR:
+    case PANGO_ATTR_OVERLINE_COLOR:
+    case PANGO_ATTR_STRIKETHROUGH_COLOR:
+      str = pango_color_to_string (&((PangoAttrColor*)attr)->color);
+      json_builder_add_string_value (builder, str);
+      g_free (str);
+      break;
+    case PANGO_ATTR_STRIKETHROUGH:
+    case PANGO_ATTR_FALLBACK:
+    case PANGO_ATTR_ALLOW_BREAKS:
+    case PANGO_ATTR_INSERT_HYPHENS:
+      json_builder_add_boolean_value (builder, ((PangoAttrInt*)attr)->value != 0);
+      break;
+    case PANGO_ATTR_SHAPE:
+      json_builder_add_string_value (builder, "shape");
+      break;
+    case PANGO_ATTR_SCALE:
+    case PANGO_ATTR_LINE_HEIGHT:
+      json_builder_add_double_value (builder, ((PangoAttrFloat*)attr)->value);
+    }
+
+  json_builder_end_object (builder);
+}
+
+static void
+add_attr_list (JsonBuilder   *builder,
+               PangoAttrList *attrs)
+{
+  GSList *attributes, *l;
+
+  json_builder_begin_array (builder);
+
+  attributes = pango_attr_list_get_attributes (attrs);
+  for (l = attributes; l; l = l->next)
+    {
+      PangoAttribute *attr = l->data;
+      add_attribute (builder, attr);
+    }
+  g_slist_free_full (attributes, (GDestroyNotify) pango_attribute_destroy);
+
+  json_builder_end_array (builder);
+}
+
+static void
+add_tab_array (JsonBuilder   *builder,
+               PangoTabArray *tabs)
+{
+  json_builder_begin_object (builder);
+
+  json_builder_set_member_name (builder, "positions-in-pixels");
+  json_builder_add_boolean_value (builder, pango_tab_array_get_positions_in_pixels (tabs));
+  json_builder_set_member_name (builder, "positions");
+  json_builder_begin_array (builder);
+  for (int i = 0; i < pango_tab_array_get_size (tabs); i++)
+    {
+      int pos;
+      pango_tab_array_get_tab (tabs, i, NULL, &pos);
+      json_builder_add_int_value (builder, pos);
+    }
+  json_builder_end_array (builder);
+
+  json_builder_end_object (builder);
+}
+
+static JsonNode *
+layout_to_json (PangoLayout *layout)
+{
+  JsonBuilder *builder;
+  JsonNode *root;
+
+  builder = json_builder_new_immutable ();
+
+  json_builder_begin_object (builder);
+
+  json_builder_set_member_name (builder, "text");
+  json_builder_add_string_value (builder, layout->text);
+
+  if (layout->attrs)
+    {
+      json_builder_set_member_name (builder, "attributes");
+      add_attr_list (builder, layout->attrs);
+    }
+
+  if (layout->font_desc)
+    {
+      char *str = pango_font_description_to_string (layout->font_desc);
+      json_builder_set_member_name (builder, "font");
+      json_builder_add_string_value (builder, str);
+      g_free (str);
+    }
+
+  if (layout->tabs)
+    {
+      json_builder_set_member_name (builder, "tabs");
+      add_tab_array (builder, layout->tabs);
+    }
+
+  if (layout->justify)
+    {
+      json_builder_set_member_name (builder, "justify");
+      json_builder_add_boolean_value (builder, TRUE);
+    }
+
+  if (layout->justify_last_line)
+    {
+      json_builder_set_member_name (builder, "justify-last-line");
+      json_builder_add_boolean_value (builder, TRUE);
+    }
+
+  if (layout->single_paragraph)
+    {
+      json_builder_set_member_name (builder, "single-paragraph");
+      json_builder_add_boolean_value (builder, TRUE);
+    }
+
+  if (!layout->auto_dir)
+    {
+      json_builder_set_member_name (builder, "auto-dir");
+      json_builder_add_boolean_value (builder, FALSE);
+    }
+
+  if (layout->alignment != PANGO_ALIGN_LEFT)
+    {
+      json_builder_set_member_name (builder, "alignment");
+      add_enum_value (builder, PANGO_TYPE_ALIGNMENT, layout->alignment, FALSE);
+    }
+
+  if (layout->wrap != PANGO_WRAP_WORD)
+    {
+      json_builder_set_member_name (builder, "wrap");
+      add_enum_value (builder, PANGO_TYPE_WRAP_MODE, layout->wrap, FALSE);
+    }
+
+  if (layout->ellipsize != PANGO_ELLIPSIZE_NONE)
+    {
+      json_builder_set_member_name (builder, "ellipsize");
+      add_enum_value (builder, PANGO_TYPE_ELLIPSIZE_MODE, layout->ellipsize, FALSE);
+    }
+
+  if (layout->width != -1)
+    {
+      json_builder_set_member_name (builder, "width");
+      json_builder_add_int_value (builder, layout->width);
+    }
+
+  if (layout->height != -1)
+    {
+      json_builder_set_member_name (builder, "height");
+      json_builder_add_int_value (builder, layout->height);
+    }
+
+  if (layout->indent != 0)
+    {
+      json_builder_set_member_name (builder, "indent");
+      json_builder_add_int_value (builder, layout->indent);
+    }
+
+  if (layout->spacing != 0)
+    {
+      json_builder_set_member_name (builder, "spacing");
+      json_builder_add_int_value (builder, layout->spacing);
+    }
+
+  if (layout->line_spacing != 0.)
+    {
+      json_builder_set_member_name (builder, "line-spacing");
+      json_builder_add_double_value (builder, layout->line_spacing);
+    }
+
+  json_builder_end_object (builder);
+
+  root = json_builder_get_root (builder);
+  g_object_unref (builder);
+
+  return root;
+}
+
+/* }}} */
+/* {{{ Deserialization */
+
+static int
+get_enum_value (GType       type,
+                const char *str,
+                gboolean    allow_extra)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+
+  enum_class = g_type_class_ref (type);
+  enum_value = g_enum_get_value_by_nick (enum_class, str);
+
+  if (enum_value)
+    return enum_value->value;
+
+  if (allow_extra)
+    {
+      gint64 value;
+      char *endp;
+
+      value = g_ascii_strtoll (str, &endp, 10);
+      if (*endp == '\0')
+        return value;
+    }
+
+  return -1;
+}
+
+static PangoAttribute *
+json_to_attribute (JsonReader  *reader,
+                   GError     **error)
+{
+  PangoAttribute *attr = NULL;
+  PangoAttrType type = PANGO_ATTR_INVALID;
+  guint start = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
+  guint end = PANGO_ATTR_INDEX_TO_TEXT_END;
+  PangoFontDescription *desc;
+  PangoColor color;
+
+  if (!json_reader_is_object (reader))
+    {
+      g_set_error (error,
+                   PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_INVALID_SYNTAX,
+                   "Attribute must be a Json object");
+      return NULL;
+    }
+
+  if (json_reader_read_member (reader, "start"))
+    start = json_reader_get_int_value (reader);
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "end"))
+    end = json_reader_get_int_value (reader);
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "type"))
+    {
+      type = get_enum_value (PANGO_TYPE_ATTR_TYPE, json_reader_get_string_value (reader), FALSE);
+      if (type == -1 || type == PANGO_ATTR_INVALID)
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+                       "Attribute \"type\" invalid: %s",
+                       json_reader_get_string_value (reader));
+          return NULL;
+        }
+      json_reader_end_member (reader);
+    }
+  else
+    {
+      g_set_error (error,
+                   PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_MISSING_VALUE,
+                   "Attribute \"type\" missing");
+      json_reader_end_member (reader);
+      return NULL;
+    }
+
+  if (!json_reader_read_member (reader, "value"))
+    {
+      g_set_error (error,
+                   PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_MISSING_VALUE,
+                   "Attribute \"value\" missing");
+      json_reader_end_member (reader);
+      return NULL;
+    }
+
+  switch (type)
+    {
+    default:
+    case PANGO_ATTR_INVALID:
+      g_assert_not_reached ();
+
+    case PANGO_ATTR_LANGUAGE:
+      attr = pango_attr_language_new (pango_language_from_string (json_reader_get_string_value (reader)));
+      break;
+    case PANGO_ATTR_FAMILY:
+      attr = pango_attr_family_new (json_reader_get_string_value (reader));
+      break;
+    case PANGO_ATTR_STYLE:
+      attr = pango_attr_style_new (get_enum_value (PANGO_TYPE_STYLE, json_reader_get_string_value (reader), 
FALSE));
+      break;
+    case PANGO_ATTR_WEIGHT:
+      attr = pango_attr_weight_new (get_enum_value (PANGO_TYPE_WEIGHT, json_reader_get_string_value 
(reader), TRUE));
+      break;
+    case PANGO_ATTR_VARIANT:
+      attr = pango_attr_variant_new (get_enum_value (PANGO_TYPE_VARIANT, json_reader_get_string_value 
(reader), FALSE));
+      break;
+    case PANGO_ATTR_STRETCH:
+      attr = pango_attr_stretch_new (get_enum_value (PANGO_TYPE_STRETCH, json_reader_get_string_value 
(reader), FALSE));
+      break;
+    case PANGO_ATTR_SIZE:
+      attr = pango_attr_size_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_FONT_DESC:
+      desc = pango_font_description_from_string (json_reader_get_string_value (reader));
+      attr = pango_attr_font_desc_new (desc);
+      pango_font_description_free (desc);
+      break;
+    case PANGO_ATTR_FOREGROUND:
+      pango_color_parse (&color, json_reader_get_string_value (reader));
+      attr = pango_attr_foreground_new (color.red, color.green, color.blue);
+      break;
+    case PANGO_ATTR_BACKGROUND:
+      pango_color_parse (&color, json_reader_get_string_value (reader));
+      attr = pango_attr_background_new (color.red, color.green, color.blue);
+      break;
+    case PANGO_ATTR_UNDERLINE:
+      attr = pango_attr_underline_new (get_enum_value (PANGO_TYPE_UNDERLINE, json_reader_get_string_value 
(reader), FALSE));
+      break;
+    case PANGO_ATTR_STRIKETHROUGH:
+      attr = pango_attr_strikethrough_new (json_reader_get_boolean_value (reader));
+      break;
+    case PANGO_ATTR_RISE:
+      attr = pango_attr_rise_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_SHAPE:
+      /* FIXME */
+      attr = pango_attr_shape_new (&(PangoRectangle) { 0, 0, 0, 0}, &(PangoRectangle) { 0, 0, 0, 0});
+      break;
+    case PANGO_ATTR_SCALE:
+      attr = pango_attr_scale_new (json_reader_get_double_value (reader));
+      break;
+    case PANGO_ATTR_FALLBACK:
+      attr = pango_attr_fallback_new (json_reader_get_boolean_value (reader));
+      break;
+    case PANGO_ATTR_LETTER_SPACING:
+      attr = pango_attr_letter_spacing_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_UNDERLINE_COLOR:
+      pango_color_parse (&color, json_reader_get_string_value (reader));
+      attr = pango_attr_underline_color_new (color.red, color.green, color.blue);
+      break;
+    case PANGO_ATTR_STRIKETHROUGH_COLOR:
+      pango_color_parse (&color, json_reader_get_string_value (reader));
+      attr = pango_attr_strikethrough_color_new (color.red, color.green, color.blue);
+      break;
+    case PANGO_ATTR_ABSOLUTE_SIZE:
+      attr = pango_attr_size_new_absolute (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_GRAVITY:
+      attr = pango_attr_gravity_new (get_enum_value (PANGO_TYPE_GRAVITY, json_reader_get_string_value 
(reader), FALSE));
+      break;
+    case PANGO_ATTR_GRAVITY_HINT:
+      attr = pango_attr_gravity_hint_new (get_enum_value (PANGO_TYPE_GRAVITY_HINT, 
json_reader_get_string_value (reader), FALSE));
+      break;
+    case PANGO_ATTR_FONT_FEATURES:
+      attr = pango_attr_font_features_new (json_reader_get_string_value (reader));
+      break;
+    case PANGO_ATTR_FOREGROUND_ALPHA:
+      attr = pango_attr_foreground_alpha_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_BACKGROUND_ALPHA:
+      attr = pango_attr_background_alpha_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_ALLOW_BREAKS:
+      attr = pango_attr_allow_breaks_new (json_reader_get_boolean_value (reader));
+      break;
+    case PANGO_ATTR_SHOW:
+      attr = pango_attr_show_new (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_INSERT_HYPHENS:
+      attr = pango_attr_insert_hyphens_new (json_reader_get_boolean_value (reader));
+      break;
+    case PANGO_ATTR_OVERLINE:
+      attr = pango_attr_overline_new (get_enum_value (PANGO_TYPE_OVERLINE, json_reader_get_string_value 
(reader), FALSE));
+      break;
+    case PANGO_ATTR_OVERLINE_COLOR:
+      pango_color_parse (&color, json_reader_get_string_value (reader));
+      attr = pango_attr_overline_color_new (color.red, color.green, color.blue);
+      break;
+    case PANGO_ATTR_LINE_HEIGHT:
+      attr = pango_attr_line_height_new (json_reader_get_double_value (reader));
+      break;
+    case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
+      attr = pango_attr_line_height_new_absolute (json_reader_get_int_value (reader));
+      break;
+    case PANGO_ATTR_TEXT_TRANSFORM:
+      attr = pango_attr_text_transform_new (get_enum_value (PANGO_TYPE_TEXT_TRANSFORM, 
json_reader_get_string_value (reader), FALSE));
+      break;
+    case PANGO_ATTR_WORD:
+      attr = pango_attr_word_new ();
+      break;
+    case PANGO_ATTR_SENTENCE:
+      attr = pango_attr_sentence_new ();
+      break;
+    case PANGO_ATTR_BASELINE_SHIFT:
+      attr = pango_attr_baseline_shift_new (get_enum_value (PANGO_TYPE_BASELINE_SHIFT, 
json_reader_get_string_value (reader), TRUE));
+      break;
+    case PANGO_ATTR_FONT_SCALE:
+      attr = pango_attr_font_scale_new (get_enum_value (PANGO_TYPE_BASELINE_SHIFT, 
json_reader_get_string_value (reader), FALSE));
+      break;
+    }
+
+  attr->start_index = start;
+  attr->end_index = end;
+
+  json_reader_end_member (reader);
+
+  return attr;
+}
+
+static PangoAttrList *
+json_to_attr_list (JsonReader  *reader,
+                   GError     **error)
+{
+  PangoAttrList *attributes;
+
+  if (!json_reader_is_array (reader))
+    {
+      g_set_error (error,
+                   PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_INVALID_SYNTAX,
+                   "\"attributes\" must be a Json array");
+      goto fail;
+    }
+
+  attributes = pango_attr_list_new ();
+
+  for (int i = 0; i < json_reader_count_elements (reader); i++)
+    {
+      PangoAttribute *attr;
+      json_reader_read_element (reader, i);
+      attr = json_to_attribute (reader, error);
+      if (!attr)
+        goto fail;
+      pango_attr_list_insert (attributes, attr);
+      json_reader_end_element (reader);
+    }
+
+  return attributes;
+
+fail:
+  if (attributes)
+    pango_attr_list_unref (attributes);
+  return NULL;
+}
+
+static PangoTabArray *
+json_to_tab_array (JsonReader  *reader,
+                   GError     **error)
+{
+  PangoTabArray *tabs;
+  gboolean positions_in_pixels = FALSE;
+
+  if (json_reader_read_member (reader, "positions-in-pixels"))
+    positions_in_pixels = json_reader_get_boolean_value (reader);
+  json_reader_end_member (reader);
+
+  tabs = pango_tab_array_new (0, positions_in_pixels);
+
+  if (json_reader_read_member (reader, "positions"))
+    {
+      if (!json_reader_is_array (reader))
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR,
+                       PANGO_LAYOUT_SERIALIZE_INVALID_SYNTAX,
+                       "Tab \"positions\"  must be a Json array");
+          goto fail;
+        }
+
+      pango_tab_array_resize (tabs, json_reader_count_elements (reader));
+      for (int i = 0; i < json_reader_count_elements (reader); i++)
+        {
+          int pos;
+          json_reader_read_element (reader, i);
+          pos = json_reader_get_int_value (reader);
+          pango_tab_array_set_tab (tabs, i, PANGO_TAB_LEFT, pos);
+          json_reader_end_element (reader);
+        }
+    }
+  json_reader_end_member (reader);
+
+  return tabs;
+
+fail:
+  if (tabs)
+    pango_tab_array_free (tabs);
+  return NULL;
+}
+
+static PangoLayout *
+json_to_layout (PangoContext *context,
+                JsonNode     *node,
+                GError      **error)
+{
+  JsonReader *reader;
+  PangoLayout *layout;
+
+  layout = pango_layout_new (context);
+
+  reader = json_reader_new (node);
+  if (!json_reader_is_object (reader))
+    {
+      g_set_error (error,
+                   PANGO_LAYOUT_SERIALIZE_ERROR,
+                   PANGO_LAYOUT_SERIALIZE_INVALID_SYNTAX,
+                   "Layout must be a Json object");
+      goto fail;
+    }
+
+  if (json_reader_read_member (reader, "text"))
+    pango_layout_set_text (layout, json_reader_get_string_value (reader), -1);
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "attributes"))
+    {
+      PangoAttrList *attributes;
+
+      attributes = json_to_attr_list (reader, error);
+
+      if (!attributes)
+        goto fail;
+
+      pango_layout_set_attributes (layout, attributes);
+      pango_attr_list_unref (attributes);
+    }
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "font"))
+    {
+      PangoFontDescription *desc;
+
+      desc = pango_font_description_from_string ( json_reader_get_string_value (reader));
+      if (!desc)
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR,
+                       PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+                       "Could not parse \"font\" value: %s",
+                       json_reader_get_string_value (reader));
+          goto fail;
+        }
+      pango_layout_set_font_description (layout, desc);
+      pango_font_description_free (desc);
+    }
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "tabs"))
+    {
+      PangoTabArray *tabs;
+
+      tabs = json_to_tab_array (reader, error);
+
+      if (!tabs)
+        goto fail;
+
+      pango_layout_set_tabs (layout, tabs);
+      pango_tab_array_free (tabs);
+    }
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "justify"))
+    pango_layout_set_justify (layout, json_reader_get_boolean_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "justify-last-line"))
+    pango_layout_set_justify_last_line (layout, json_reader_get_boolean_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "single-paragraph"))
+    pango_layout_set_single_paragraph_mode (layout, json_reader_get_boolean_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "auto-dir"))
+    pango_layout_set_auto_dir (layout, json_reader_get_boolean_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "alignment"))
+    {
+      PangoAlignment align = get_enum_value (PANGO_TYPE_ALIGNMENT,
+                                             json_reader_get_string_value (reader),
+                                             FALSE);
+      if (align == -1)
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR,
+                       PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+                       "Could not parse \"alignment\" value: %s",
+                       json_reader_get_string_value (reader));
+          goto fail;
+        }
+
+      pango_layout_set_alignment (layout, align);
+    }
+
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "wrap"))
+    {
+      PangoWrapMode wrap = get_enum_value (PANGO_TYPE_WRAP_MODE,
+                                           json_reader_get_string_value (reader),
+                                           FALSE);
+
+      if (wrap == -1)
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR,
+                       PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+                       "Could not parse \"wrap\" value: %s",
+                       json_reader_get_string_value (reader));
+          goto fail;
+        }
+
+      pango_layout_set_wrap (layout, wrap);
+    }
+
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "ellipsize"))
+    {
+      PangoEllipsizeMode ellipsize = get_enum_value (PANGO_TYPE_ELLIPSIZE_MODE,
+                                                     json_reader_get_string_value (reader),
+                                                     FALSE);
+
+      if (ellipsize == -1)
+        {
+          g_set_error (error,
+                       PANGO_LAYOUT_SERIALIZE_ERROR,
+                       PANGO_LAYOUT_SERIALIZE_INVALID_VALUE,
+                       "Could not parse \"ellipsize\" value: %s",
+                       json_reader_get_string_value (reader));
+          goto fail;
+        }
+
+      pango_layout_set_ellipsize (layout, ellipsize);
+    }
+
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "width"))
+    pango_layout_set_width (layout, json_reader_get_int_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "height"))
+    pango_layout_set_height (layout, json_reader_get_int_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "indent"))
+    pango_layout_set_indent (layout, json_reader_get_int_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "spacing"))
+    pango_layout_set_spacing (layout, json_reader_get_int_value (reader));
+  json_reader_end_member (reader);
+
+  if (json_reader_read_member (reader, "line-spacing"))
+    pango_layout_set_line_spacing (layout, json_reader_get_double_value (reader));
+  json_reader_end_member (reader);
+
+  g_object_unref (reader);
+
+  return layout;
+
+fail:
+  g_object_unref (layout);
+  return NULL;
+}
+
+ /* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango_layout_serialize:
+ * @layout: a `PangoLayout`
+ *
+ * Serializes the @layout for later deserialization via [method@Pango.Layout.deserialize].
+ *
+ * There are no guarantees about the format of the output accross different
+ * versions of Pango and [method@Pango.Layout.deserialize] will reject data
+ * that it cannot parse.
+ *
+ * The intended use of this function is testing, benchmarking and debugging.
+ * The format is not meant as a permanent storage format.
+ *
+ * Returns: a `GBytes` containing the serialized form of @layout
+ *
+ * Since: 1.50
+ */
+GBytes *
+pango_layout_serialize (PangoLayout *layout)
+{
+  JsonGenerator *generator;
+  JsonNode *node;
+  char *data;
+  gsize size;
+
+  node = layout_to_json (layout);
+
+  generator = json_generator_new ();
+  json_generator_set_pretty (generator, TRUE);
+  json_generator_set_indent (generator, 2);
+
+  json_generator_set_root (generator, node);
+  data = json_generator_to_data (generator, &size);
+
+  json_node_free (node);
+  g_object_unref (generator);
+
+  return g_bytes_new_take (data, size);
+}
+
+/**
+ * pango_layout_deserialize:
+ * @context: a `PangoContext`
+ * @bytes: the bytes containing the data
+ * @error: return location for an error
+ *
+ * Loads data previously created via [method@Pango.Layout.serialize].
+ *
+ * For a discussion of the supported format, see that function.
+ *
+ * Returns: (nullable) (transfer full): a new `PangoLayout`
+ *
+ * Since: 1.50
+ */
+PangoLayout *
+pango_layout_deserialize (PangoContext  *context,
+                          GBytes        *bytes,
+                          GError       **error)
+{
+  JsonParser *parser;
+  JsonNode *node;
+  PangoLayout *layout;
+
+  parser = json_parser_new_immutable ();
+  if (!json_parser_load_from_data (parser,
+                                   g_bytes_get_data (bytes, NULL),
+                                   g_bytes_get_size (bytes),
+                                   error))
+    {
+      g_object_unref (parser);
+      return NULL;
+    }
+
+  node = json_parser_get_root (parser);
+  layout = json_to_layout (context, node, error);
+
+  g_object_unref (parser);
+
+  return layout;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/tests/testserialize.c b/tests/testserialize.c
index cc6c67bc..35c83504 100644
--- a/tests/testserialize.c
+++ b/tests/testserialize.c
@@ -21,6 +21,7 @@
 #include "config.h"
 #include <glib.h>
 #include <pango/pangocairo.h>
+#include <gio/gio.h>
 
 static void
 test_serialize_attr_list (void)
@@ -122,6 +123,165 @@ test_serialize_tab_array (void)
     }
 }
 
+static void
+test_serialize_layout_minimal (void)
+{
+  const char *test =
+    "{\n"
+    "  \"text\" : \"Almost nothing\"\n"
+    "}";
+
+  PangoContext *context;
+  GBytes *bytes;
+  PangoLayout *layout;
+  GError *error = NULL;
+  GBytes *out_bytes;
+  const char *str;
+
+  context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+
+  bytes = g_bytes_new_static (test, -1);
+
+  layout = pango_layout_deserialize (context, bytes, &error);
+  g_assert_no_error (error);
+  g_assert_true (PANGO_IS_LAYOUT (layout));
+  g_assert_cmpstr (pango_layout_get_text (layout), ==, "Almost nothing");
+  g_assert_null (pango_layout_get_attributes (layout));
+  g_assert_null (pango_layout_get_tabs (layout));
+  g_assert_null (pango_layout_get_font_description (layout));
+  g_assert_cmpint (pango_layout_get_alignment (layout), ==, PANGO_ALIGN_LEFT);
+  g_assert_cmpint (pango_layout_get_width (layout), ==, -1);
+
+  out_bytes = pango_layout_serialize (layout);
+  str = g_bytes_get_data (out_bytes, NULL);
+
+  g_assert_cmpstr (str, ==, test);
+
+  g_bytes_unref (out_bytes);
+
+  g_object_unref (layout);
+  g_bytes_unref (bytes);
+  g_object_unref (context);
+}
+
+static void
+test_serialize_layout_valid (void)
+{
+  const char *test =
+    "{\n"
+    "  \"text\" : \"Some fun with layouts!\",\n"
+    "  \"attributes\" : [\n"
+    "    {\n"
+    "      \"end\" : 4,\n"
+    "      \"type\" : \"foreground\",\n"
+    "      \"value\" : \"#ffff00000000\"\n"
+    "    },\n"
+    "    {\n"
+    "      \"start\" : 5,\n"
+    "      \"end\" : 8,\n"
+    "      \"type\" : \"foreground\",\n"
+    "      \"value\" : \"#000080800000\"\n"
+    "    },\n"
+    "    {\n"
+    "      \"start\" : 14,\n"
+    "      \"type\" : \"foreground\",\n"
+    "      \"value\" : \"#ffff0000ffff\"\n"
+    "    }\n"
+    "  ],\n"
+    "  \"font\" : \"Sans Bold 32\",\n"
+    "  \"tabs\" : {\n"
+    "    \"positions-in-pixels\" : true,\n"
+    "    \"positions\" : [\n"
+    "      0,\n"
+    "      50,\n"
+    "      100\n"
+    "    ]\n"
+    "  },\n"
+    "  \"alignment\" : \"center\",\n"
+    "  \"width\" : 350000,\n"
+    "  \"line-spacing\" : 1.5\n"
+    "}";
+
+  PangoContext *context;
+  GBytes *bytes;
+  PangoLayout *layout;
+  GError *error = NULL;
+  GBytes *out_bytes;
+  const char *str;
+  char *s;
+
+  context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+
+  bytes = g_bytes_new_static (test, -1);
+
+  layout = pango_layout_deserialize (context, bytes, &error);
+  g_assert_no_error (error);
+  g_assert_true (PANGO_IS_LAYOUT (layout));
+  g_assert_cmpstr (pango_layout_get_text (layout), ==, "Some fun with layouts!");
+  g_assert_nonnull (pango_layout_get_attributes (layout));
+  g_assert_nonnull (pango_layout_get_tabs (layout));
+  s = pango_font_description_to_string (pango_layout_get_font_description (layout));
+  g_assert_cmpstr (s, ==, "Sans Bold 32");
+  g_free (s);
+  g_assert_cmpint (pango_layout_get_alignment (layout), ==, PANGO_ALIGN_CENTER);
+  g_assert_cmpint (pango_layout_get_width (layout), ==, 350000);
+  g_assert_cmpfloat_with_epsilon (pango_layout_get_line_spacing (layout), 1.5, 0.0001);
+
+  out_bytes = pango_layout_serialize (layout);
+  str = g_bytes_get_data (out_bytes, NULL);
+
+  g_assert_cmpstr (str, ==, test);
+
+  g_bytes_unref (out_bytes);
+
+  g_object_unref (layout);
+  g_bytes_unref (bytes);
+  g_object_unref (context);
+}
+
+static void
+test_serialize_layout_invalid (void)
+{
+  const char *test1 =
+    "{\n"
+    "  \"attributes\" : [\n"
+    "    {\n"
+    "      \"type\" : \"caramba\"\n"
+    "    }\n"
+    "  ]\n"
+    "}";
+
+  const char *test2 =
+    "{\n"
+    "  \"attributes\" : [\n"
+    "    {\n"
+    "      \"type\" : \"weight\"\n"
+    "    }\n"
+    "  ]\n"
+    "}";
+
+  PangoContext *context;
+  GBytes *bytes;
+  PangoLayout *layout;
+  GError *error = NULL;
+
+  context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+
+  bytes = g_bytes_new_static (test1, -1);
+  layout = pango_layout_deserialize (context, bytes, &error);
+  g_assert_null (layout);
+  g_assert_error (error, PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_INVALID_VALUE);
+  g_bytes_unref (bytes);
+  g_clear_error (&error);
+
+  bytes = g_bytes_new_static (test2, -1);
+  layout = pango_layout_deserialize (context, bytes, &error);
+  g_assert_null (layout);
+  g_assert_error (error, PANGO_LAYOUT_SERIALIZE_ERROR, PANGO_LAYOUT_SERIALIZE_MISSING_VALUE);
+  g_bytes_unref (bytes);
+
+  g_object_unref (context);
+}
 int
 main (int argc, char *argv[])
 {
@@ -129,6 +289,9 @@ main (int argc, char *argv[])
 
   g_test_add_func ("/serialize/attr-list", test_serialize_attr_list);
   g_test_add_func ("/serialize/tab-array", test_serialize_tab_array);
+  g_test_add_func ("/serialize/layout/minimal", test_serialize_layout_minimal);
+  g_test_add_func ("/serialize/layout/valid", test_serialize_layout_valid);
+  g_test_add_func ("/serialize/layout/invalid", test_serialize_layout_invalid);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]