/* 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 #include #include #include #include #include #include /* {{{ Error handling */ G_DEFINE_QUARK(pango-layout-deserialize-error-quark, pango_layout_deserialize_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; if (!attrs) return; attributes = pango_attr_list_get_attributes (attrs); if (!attributes) return; json_builder_set_member_name (builder, "attributes"); json_builder_begin_array (builder); 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) { if (!tabs || pango_tab_array_get_size (tabs) == 0) return; json_builder_set_member_name (builder, "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 void add_context (JsonBuilder *builder, PangoContext *context) { char *str; const PangoMatrix *matrix; PangoMatrix identity = PANGO_MATRIX_INIT; json_builder_begin_object (builder); /* Note: since we don't create the context when deserializing, * we don't strip out default values here to ensure that the * context gets updated as expected. */ str = pango_font_description_to_string (context->font_desc); json_builder_set_member_name (builder, "font"); json_builder_add_string_value (builder, str); g_free (str); if (context->set_language) { json_builder_set_member_name (builder, "language"); json_builder_add_string_value (builder, pango_language_to_string (context->set_language)); } json_builder_set_member_name (builder, "base-gravity"); add_enum_value (builder, PANGO_TYPE_GRAVITY, context->base_gravity, FALSE); json_builder_set_member_name (builder, "gravity-hint"); add_enum_value (builder, PANGO_TYPE_GRAVITY_HINT, context->gravity_hint, FALSE); json_builder_set_member_name (builder, "base-dir"); add_enum_value (builder, PANGO_TYPE_DIRECTION, context->base_dir, FALSE); json_builder_set_member_name (builder, "round-glyph-positions"); json_builder_add_boolean_value (builder, context->round_glyph_positions); json_builder_set_member_name (builder, "transform"); matrix = pango_context_get_matrix (context); if (!matrix) matrix = &identity; json_builder_begin_array (builder); json_builder_add_double_value (builder, matrix->xx); json_builder_add_double_value (builder, matrix->xy); json_builder_add_double_value (builder, matrix->yx); json_builder_add_double_value (builder, matrix->yy); json_builder_add_double_value (builder, matrix->x0); json_builder_add_double_value (builder, matrix->y0); json_builder_end_array (builder); json_builder_end_object (builder); } static void add_log_attrs (JsonBuilder *builder, PangoLayout *layout) { const PangoLogAttr *log_attrs; int n_attrs; json_builder_set_member_name (builder, "log-attrs"); json_builder_begin_array (builder); log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); for (int i = 0; i < n_attrs; i++) { json_builder_begin_object (builder); if (log_attrs[i].is_line_break) { json_builder_set_member_name (builder, "line-break"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_mandatory_break) { json_builder_set_member_name (builder, "mandatory-break"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_char_break) { json_builder_set_member_name (builder, "char-break"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_white) { json_builder_set_member_name (builder, "white"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_cursor_position) { json_builder_set_member_name (builder, "cursor-position"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_word_start) { json_builder_set_member_name (builder, "word-start"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_word_end) { json_builder_set_member_name (builder, "word-end"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_sentence_boundary) { json_builder_set_member_name (builder, "sentence-boundary"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_sentence_start) { json_builder_set_member_name (builder, "sentence-start"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_sentence_end) { json_builder_set_member_name (builder, "sentence-end"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].backspace_deletes_character) { json_builder_set_member_name (builder, "backspace-deletes-character"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_expandable_space) { json_builder_set_member_name (builder, "expandable-space"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].is_word_boundary) { json_builder_set_member_name (builder, "word-boundary"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].break_inserts_hyphen) { json_builder_set_member_name (builder, "break-inserts-hyphen"); json_builder_add_boolean_value (builder, TRUE); } if (log_attrs[i].break_removes_preceding) { json_builder_set_member_name (builder, "break-removes_preceding"); json_builder_add_boolean_value (builder, TRUE); } json_builder_end_object (builder); } json_builder_end_array (builder); } static void add_font (JsonBuilder *builder, PangoFont *font) { PangoFontDescription *desc; char *str; hb_font_t *hb_font; hb_face_t *face; hb_blob_t *blob; const char *data; guint length; const int *coords; hb_feature_t features[32]; PangoMatrix matrix; json_builder_begin_object (builder); json_builder_set_member_name (builder, "description"); desc = pango_font_describe (font); str = pango_font_description_to_string (desc); json_builder_add_string_value (builder, str); g_free (str); pango_font_description_free (desc); hb_font = pango_font_get_hb_font (font); face = hb_font_get_face (hb_font); blob = hb_face_reference_blob (face); data = hb_blob_get_data (blob, &length); str = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (const guchar *)data, length); json_builder_set_member_name (builder, "checksum"); json_builder_add_string_value (builder, str); g_free (str); hb_blob_destroy (blob); coords = hb_font_get_var_coords_normalized (hb_font, &length); if (length > 0) { guint count; hb_ot_var_axis_info_t *axes; count = hb_ot_var_get_axis_count (face); g_assert (count == length); axes = g_alloca (count * sizeof (hb_ot_var_axis_info_t)); hb_ot_var_get_axis_infos (face, 0, &count, axes); json_builder_set_member_name (builder, "variations"); json_builder_begin_object (builder); for (int i = 0; i < length; i++) { char buf[5] = { 0, }; hb_tag_to_string (axes[i].tag, buf); json_builder_set_member_name (builder, buf); json_builder_add_int_value (builder, coords[i]); } json_builder_end_object (builder); } length = 0; pango_font_get_features (font, features, G_N_ELEMENTS (features), &length); if (length > 0) { json_builder_set_member_name (builder, "features"); json_builder_begin_object (builder); for (int i = 0; i < length; i++) { char buf[5] = { 0, }; hb_tag_to_string (features[i].tag, buf); json_builder_set_member_name (builder, buf); json_builder_add_int_value (builder, features[i].value); } json_builder_end_object (builder); } pango_font_get_matrix (font, &matrix); if (memcmp (&matrix, &(PangoMatrix)PANGO_MATRIX_INIT, sizeof (PangoMatrix)) != 0) { json_builder_set_member_name (builder, "matrix"); json_builder_begin_array (builder); json_builder_add_double_value (builder, matrix.xx); json_builder_add_double_value (builder, matrix.xy); json_builder_add_double_value (builder, matrix.yx); json_builder_add_double_value (builder, matrix.yy); json_builder_add_double_value (builder, matrix.x0); json_builder_add_double_value (builder, matrix.y0); json_builder_end_array (builder); } json_builder_end_object (builder); } #define ANALYSIS_FLAGS (PANGO_ANALYSIS_FLAG_CENTERED_BASELINE | \ PANGO_ANALYSIS_FLAG_IS_ELLIPSIS | \ PANGO_ANALYSIS_FLAG_NEED_HYPHEN) static void add_run (JsonBuilder *builder, PangoLayout *layout, PangoLayoutRun *run) { json_builder_begin_object (builder); char *str; json_builder_set_member_name (builder, "offset"); json_builder_add_int_value (builder, run->item->offset); json_builder_set_member_name (builder, "length"); json_builder_add_int_value (builder, run->item->length); str = g_strndup (layout->text + run->item->offset, run->item->length); json_builder_set_member_name (builder, "text"); json_builder_add_string_value (builder, str); g_free (str); json_builder_set_member_name (builder, "bidi-level"); json_builder_add_int_value (builder, run->item->analysis.level); json_builder_set_member_name (builder, "gravity"); add_enum_value (builder, PANGO_TYPE_GRAVITY, run->item->analysis.gravity, FALSE); json_builder_set_member_name (builder, "language"); json_builder_add_string_value (builder, pango_language_to_string (run->item->analysis.language)); json_builder_set_member_name (builder, "script"); add_enum_value (builder, PANGO_TYPE_SCRIPT, run->item->analysis.script, FALSE); json_builder_set_member_name (builder, "font"); add_font (builder, run->item->analysis.font); json_builder_set_member_name (builder, "flags"); json_builder_add_int_value (builder, run->item->analysis.flags & ANALYSIS_FLAGS); if (run->item->analysis.extra_attrs) { GSList *l; json_builder_set_member_name (builder, "extra-attributes"); json_builder_begin_array (builder); for (l = run->item->analysis.extra_attrs; l; l = l->next) { PangoAttribute *attr = l->data; add_attribute (builder, attr); } json_builder_end_array (builder); } json_builder_set_member_name (builder, "y-offset"); json_builder_add_int_value (builder, run->y_offset); json_builder_set_member_name (builder, "start-x-offset"); json_builder_add_int_value (builder, run->start_x_offset); json_builder_set_member_name (builder, "end-x-offset"); json_builder_add_int_value (builder, run->end_x_offset); json_builder_set_member_name (builder, "glyphs"); json_builder_begin_array (builder); for (int i = 0; i < run->glyphs->num_glyphs; i++) { json_builder_begin_object (builder); json_builder_set_member_name (builder, "glyph"); json_builder_add_int_value (builder, run->glyphs->glyphs[i].glyph); json_builder_set_member_name (builder, "width"); json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.width); if (run->glyphs->glyphs[i].geometry.x_offset != 0) { json_builder_set_member_name (builder, "x-offset"); json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.x_offset); } if (run->glyphs->glyphs[i].geometry.y_offset != 0) { json_builder_set_member_name (builder, "y-offset"); json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.y_offset); } if (run->glyphs->glyphs[i].attr.is_cluster_start) { json_builder_set_member_name (builder, "is-cluster-start"); json_builder_add_boolean_value (builder, TRUE); } if (run->glyphs->glyphs[i].attr.is_color) { json_builder_set_member_name (builder, "is-color"); json_builder_add_boolean_value (builder, TRUE); } json_builder_set_member_name (builder, "log-cluster"); json_builder_add_int_value (builder, run->glyphs->log_clusters[i]); json_builder_end_object (builder); } json_builder_end_array (builder); json_builder_end_object (builder); } #undef ANALYSIS_FLAGS static void add_line (JsonBuilder *builder, PangoLayoutLine *line) { json_builder_begin_object (builder); json_builder_set_member_name (builder, "start-index"); json_builder_add_int_value (builder, line->start_index); json_builder_set_member_name (builder, "length"); json_builder_add_int_value (builder, line->length); json_builder_set_member_name (builder, "paragraph-start"); json_builder_add_boolean_value (builder, line->is_paragraph_start); json_builder_set_member_name (builder, "direction"); add_enum_value (builder, PANGO_TYPE_DIRECTION, line->resolved_dir, FALSE); json_builder_set_member_name (builder, "runs"); json_builder_begin_array (builder); for (GSList *l = line->runs; l; l = l->next) { PangoLayoutRun *run = l->data; add_run (builder, line->layout, run); } json_builder_end_array (builder); json_builder_end_object (builder); } static void add_output (JsonBuilder *builder, PangoLayout *layout) { int width, height; json_builder_begin_object (builder); json_builder_set_member_name (builder, "is-wrapped"); json_builder_add_boolean_value (builder, pango_layout_is_wrapped (layout)); json_builder_set_member_name (builder, "is-ellipsized"); json_builder_add_boolean_value (builder, pango_layout_is_ellipsized (layout)); json_builder_set_member_name (builder, "unknown-glyphs"); json_builder_add_int_value (builder, pango_layout_get_unknown_glyphs_count (layout)); pango_layout_get_size (layout, &width, &height); json_builder_set_member_name (builder, "width"); json_builder_add_int_value (builder, width); json_builder_set_member_name (builder, "height"); json_builder_add_int_value (builder, height); add_log_attrs (builder, layout); json_builder_set_member_name (builder, "lines"); json_builder_begin_array (builder); for (GSList *l = layout->lines; l; l = l->next) { PangoLayoutLine *line = l->data; add_line (builder, line); } json_builder_end_array (builder); json_builder_end_object (builder); } static JsonNode * layout_to_json (PangoLayout *layout, PangoLayoutSerializeFlags flags) { JsonBuilder *builder; JsonNode *root; const char *str; builder = json_builder_new_immutable (); json_builder_begin_object (builder); if (flags & PANGO_LAYOUT_SERIALIZE_CONTEXT) { json_builder_set_member_name (builder, "context"); add_context (builder, layout->context); } str = (const char *) g_object_get_data (G_OBJECT (layout), "comment"); if (str) { json_builder_set_member_name (builder, "comment"); if (strstr (str, "\n") != NULL) { char **strs = g_strsplit (str, "\n", -1); json_builder_begin_array (builder); for (int i = 0; strs[i]; i++) json_builder_add_string_value (builder, strs[i]); json_builder_end_array (builder); g_strfreev (strs); } else json_builder_add_string_value (builder, str); } json_builder_set_member_name (builder, "text"); json_builder_add_string_value (builder, layout->text); 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); } 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); } if (flags & PANGO_LAYOUT_SERIALIZE_OUTPUT) { json_builder_set_member_name (builder, "output"); add_output (builder, layout); } json_builder_end_object (builder); root = json_builder_get_root (builder); g_object_unref (builder); return root; } static JsonNode * font_to_json (PangoFont *font) { JsonBuilder *builder; JsonNode *root; builder = json_builder_new_immutable (); add_font (builder, font); 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, GError **error) { 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; } g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE, "Could not parse enum value of type %s: %s", g_type_name (type), str); 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; int value; if (!json_reader_is_object (reader)) { g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_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, error); if (type == -1) return NULL; } else { g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_MISSING_VALUE, "Attribute \"type\" missing"); return NULL; } json_reader_end_member (reader); if (!json_reader_read_member (reader, "value")) { g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_MISSING_VALUE, "Attribute \"value\" missing"); 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: value = get_enum_value (PANGO_TYPE_STYLE, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_style_new ((PangoStyle)value); break; case PANGO_ATTR_WEIGHT: value = get_enum_value (PANGO_TYPE_WEIGHT, json_reader_get_string_value (reader), TRUE, error); if (value == -1) return NULL; attr = pango_attr_weight_new (value); break; case PANGO_ATTR_VARIANT: value = get_enum_value (PANGO_TYPE_VARIANT, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_variant_new ((PangoVariant)value); break; case PANGO_ATTR_STRETCH: value = get_enum_value (PANGO_TYPE_STRETCH, json_reader_get_string_value (reader), FALSE, error); if (value == -1) attr = pango_attr_stretch_new ((PangoStretch)value); 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: value = get_enum_value (PANGO_TYPE_UNDERLINE, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_underline_new ((PangoUnderline)value); 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: value = get_enum_value (PANGO_TYPE_GRAVITY, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_gravity_new ((PangoGravity)value); break; case PANGO_ATTR_GRAVITY_HINT: value = get_enum_value (PANGO_TYPE_GRAVITY_HINT, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_gravity_hint_new ((PangoGravityHint)value); 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: value = get_enum_value (PANGO_TYPE_OVERLINE, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_overline_new ((PangoOverline)value); 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: value = get_enum_value (PANGO_TYPE_TEXT_TRANSFORM, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_text_transform_new ((PangoTextTransform)value); 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: value = get_enum_value (PANGO_TYPE_BASELINE_SHIFT, json_reader_get_string_value (reader), TRUE, error); if (value == -1) return NULL; attr = pango_attr_baseline_shift_new (value); break; case PANGO_ATTR_FONT_SCALE: value = get_enum_value (PANGO_TYPE_FONT_SCALE, json_reader_get_string_value (reader), FALSE, error); if (value == -1) return NULL; attr = pango_attr_font_scale_new ((PangoFontScale)value); 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; attributes = pango_attr_list_new (); if (!json_reader_is_array (reader)) { g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX, "\"attributes\" must be a Json array"); goto fail; } 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_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_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 gboolean apply_json_to_context (JsonReader *reader, PangoContext *context, GError **error) { if (json_reader_read_member (reader, "language")) { const char *value; PangoLanguage *language; value = json_reader_get_string_value (reader); language = pango_language_from_string (value); pango_context_set_language (context, language); } 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)); pango_context_set_font_description (context, desc); pango_font_description_free (desc); } json_reader_end_member (reader); if (json_reader_read_member (reader, "base-gravity")) { PangoGravity gravity = get_enum_value (PANGO_TYPE_GRAVITY, json_reader_get_string_value (reader), FALSE, error); if (gravity == -1) return FALSE; pango_context_set_base_gravity (context, gravity); } json_reader_end_member (reader); if (json_reader_read_member (reader, "gravity-hint")) { PangoGravityHint gravity_hint = get_enum_value (PANGO_TYPE_GRAVITY_HINT, json_reader_get_string_value (reader), FALSE, error); if (gravity_hint == -1) return FALSE; pango_context_set_gravity_hint (context, gravity_hint); } json_reader_end_member (reader); if (json_reader_read_member (reader, "base-dir")) { PangoDirection direction = get_enum_value (PANGO_TYPE_DIRECTION, json_reader_get_string_value (reader), FALSE, error); if (direction == -1) return FALSE; pango_context_set_base_dir (context, direction); } json_reader_end_member (reader); if (json_reader_read_member (reader, "round-glyph-positions")) { pango_context_set_round_glyph_positions (context, json_reader_get_boolean_value (reader)); } json_reader_end_member (reader); if (json_reader_read_member (reader, "transform")) { PangoMatrix m; json_reader_read_element (reader, 0); m.xx = json_reader_get_double_value (reader); json_reader_end_element (reader); json_reader_read_element (reader, 1); m.xy = json_reader_get_double_value (reader); json_reader_end_element (reader); json_reader_read_element (reader, 2); m.yx = json_reader_get_double_value (reader); json_reader_end_element (reader); json_reader_read_element (reader, 3); m.yy = json_reader_get_double_value (reader); json_reader_end_element (reader); json_reader_read_element (reader, 4); m.x0 = json_reader_get_double_value (reader); json_reader_end_element (reader); json_reader_read_element (reader, 5); m.y0 = json_reader_get_double_value (reader); json_reader_end_element (reader); pango_context_set_matrix (context, &m); } json_reader_end_member (reader); return TRUE; } static PangoLayout * json_to_layout (PangoContext *context, JsonNode *node, PangoLayoutDeserializeFlags flags, GError **error) { JsonReader *reader; PangoLayout *layout; reader = json_reader_new (node); if (!json_reader_is_object (reader)) { g_set_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX, "Layout must be a Json object"); goto fail; } if (flags & PANGO_LAYOUT_DESERIALIZE_CONTEXT) { if (json_reader_read_member (reader, "context")) { if (!apply_json_to_context (reader, context, error)) goto fail; } json_reader_end_member (reader); } layout = pango_layout_new (context); if (json_reader_read_member (reader, "comment")) { if (json_reader_is_array (reader)) { GString *s; s = g_string_new (""); for (int i = 0; i < json_reader_count_elements (reader); i++) { json_reader_read_element (reader, i); if (s->len > 0) g_string_append_c (s, '\n'); g_string_append (s, json_reader_get_string_value (reader)); json_reader_end_element (reader); } g_object_set_data_full (G_OBJECT (layout), "comment", g_string_free (s, FALSE), g_free); } else g_object_set_data_full (G_OBJECT (layout), "comment", g_strdup (json_reader_get_string_value (reader)), g_free); } json_reader_end_member (reader); 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_DESERIALIZE_ERROR, PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE, "Could not parse \"%s\" value: %s", "font", 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, error); if (align == -1) 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, error); if (wrap == -1) 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, error); if (ellipsize == -1) 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 (reader); if (layout) g_object_unref (layout); return NULL; } /* }}} */ /* {{{ Public API */ /** * pango_layout_serialize: * @layout: a `PangoLayout` * @flags: `PangoLayoutSerializeFlags` * * Serializes the @layout for later deserialization via [method@Pango.Layout.deserialize]. * * There are no guarantees about the format of the output across 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, PangoLayoutSerializeFlags flags) { JsonGenerator *generator; JsonNode *node; GString *str; char *data; gsize size; g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); node = layout_to_json (layout, flags); generator = json_generator_new (); json_generator_set_pretty (generator, TRUE); json_generator_set_indent (generator, 2); json_generator_set_root (generator, node); str = g_string_new (""); g_string_append_c (json_generator_to_gstring (generator, str), '\n'); size = str->len; data = g_string_free (str, FALSE); json_node_free (node); g_object_unref (generator); return g_bytes_new_take (data, size); } /** * pango_layout_write_to_file: * @layout: a `PangoLayout` * @flags: `PangoLayoutSerializeFlags` * @filename: (type filename): the file to save it to * @error: Return location for a potential error * * This function is equivalent to calling [method@Pango.Layout.serialize] * followed by g_file_set_contents(). * * See those two functions for details on the arguments. * * It is mostly intended for use inside a debugger to quickly dump * a layout to a file for later inspection. * * Returns: %TRUE if saving was successful * * Since: 1.50 */ gboolean pango_layout_write_to_file (PangoLayout *layout, PangoLayoutSerializeFlags flags, const char *filename, GError **error) { GBytes *bytes; gboolean result; g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE); g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); bytes = pango_layout_serialize (layout, flags); result = g_file_set_contents (filename, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), error); g_bytes_unref (bytes); return result; } /** * pango_layout_deserialize: * @context: a `PangoContext` * @flags: `PangoLayoutDeserializeFlags` * @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, PangoLayoutDeserializeFlags flags, GError **error) { JsonParser *parser; JsonNode *node; PangoLayout *layout; g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL); 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, flags, error); g_object_unref (parser); return layout; } /** * pango_font_serialize: * @font: a `PangoFont` * * Serializes the @font in a way that can be uniquely identified. * * There are no guarantees about the format of the output across different * versions of Pango. * * 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 @font * * Since: 1.50 */ GBytes * pango_font_serialize (PangoFont *font) { JsonGenerator *generator; JsonNode *node; char *data; gsize size; g_return_val_if_fail (PANGO_IS_FONT (font), NULL); node = font_to_json (font); 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); } /* }}} */ /* vim:set foldmethod=marker expandtab: */