Commit 3f6f2887 authored by Matthias Clasen's avatar Matthias Clasen
Browse files

Optionally serialize output

If requested, serialize lines, runs, and log attrs.
This will let us use the serialization format to
record not just the test inputs, but outputs as
well.
parent c4fe95cc
......@@ -355,6 +355,7 @@ GSList * pango_layout_get_lines_readonly (PangoLayout *layout);
* PangoLayoutSerializeFlags:
* @PANGO_LAYOUT_SERIALIZE_DEFAULT: Default behavior
* @PANGO_LAYOUT_SERIALIZE_CONTEXT: Include context information
* @PANGO_LAYOUT_SERIALIZE_OUTPUT: Include information about the formatted output
*
* Flags that influence the behavior of [method@Pango.Layout.serialize].
*
......@@ -363,6 +364,7 @@ GSList * pango_layout_get_lines_readonly (PangoLayout *layout);
typedef enum {
PANGO_LAYOUT_SERIALIZE_DEFAULT = 0,
PANGO_LAYOUT_SERIALIZE_CONTEXT = 1 << 0,
PANGO_LAYOUT_SERIALIZE_OUTPUT = 1 << 1,
} PangoLayoutSerializeFlags;
PANGO_AVAILABLE_IN_1_50
......
......@@ -25,7 +25,9 @@
#include <pango/pango-layout-private.h>
#include <pango/pango-context-private.h>
#include <pango/pango-enum-types.h>
#include <pango/pango-font-private.h>
#include <hb-ot.h>
#include <json-glib/json-glib.h>
/* {{{ Error handling */
......@@ -257,7 +259,7 @@ add_context (JsonBuilder *builder,
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, "direction");
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");
......@@ -280,6 +282,379 @@ add_context (JsonBuilder *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));
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)
......@@ -384,6 +759,12 @@ layout_to_json (PangoLayout *layout,
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);
......
This is a test of the automatic emergency brake!
--- parameters
wrapped: 0
ellipsized: 1
lines: 2
width: 225280
--- attributes
range 0 22
range 22 41
22 41 foreground #00000000ffff
22 41 underline single
range 41 2147483647
--- directions
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
--- cursor positions
0(0) 1(0) 2(0) 3(0) 4(0) 5(0) 6(0) 7(0) 8(0) 9(0) 10(0) 11(0) 12(0) 13(0) 14(0) 15(0) 16(0) 17(0) 18(0) 19(0) 20(0) 21(0) 22(0) 23(0) 24(0) 25(0) 26(0) 27(0) 28(0) 29(0) 30(0) 31(0) 32(0) 33(0) 34(0) 35(0) 36(0) 37(0) 38(0) 39(0) 40(0) 41(0) 42(0) 43(0) 44(0) 45(0) 46(0) 47(0) 47(1) 49(0)
--- lines
i=1, index=0, paragraph-start=1, dir=ltr 'This is a test of the automatic emergency brake!
'
i=2, index=49, paragraph-start=1, dir=ltr ''
--- runs
i=1, index=0, chars=22, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'This is a test of the '
i=2, index=22, chars=11, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'automatic e'
22 41 foreground #00000000ffff
22 41 underline single
i=3, index=33, chars=15, level=0, gravity=south, flags=2, font=OMITTED, script=common, language=en-us, 'mergency brake!'
0 2147483647 fallback false
22 41 foreground #00000000ffff
22 41 underline single
i=4, index=48, no run, line end
i=5, index=49, no run, line end
Hello שלום Γειά σας
--- parameters
wrapped: 0
ellipsized: 1
lines: 2
width: 102400
--- attributes
range 0 2147483647
--- directions
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0
--- cursor positions
0(0) 1(0) 2(0) 3(0) 4(0) 5(0) 6(0) 12(0) 10(0) 8(0) 14(0) 15(0) 17(0) 19(0) 21(0) 23(0) 24(0) 26(0) 28(0) 28(1) 31(0)
--- lines
i=1, index=0, paragraph-start=1, dir=ltr 'Hello שלום Γειά σας
'
i=2, index=31, paragraph-start=1, dir=ltr ''
--- runs
i=1, index=0, chars=6, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'Hello '
i=2, index=12, chars=4, level=1, gravity=south, flags=0, font=OMITTED, script=hebrew, language=he, 'שלום'
i=3, index=14, chars=1, level=0, gravity=south, flags=0, font=OMITTED, script=hebrew, language=he, ' '
i=4, index=15, chars=1, level=0, gravity=south, flags=0, font=OMITTED, script=greek, language=el, 'Γ'
i=5, index=17, chars=7, level=0, gravity=south, flags=2, font=OMITTED, script=common, language=en-us, 'ειά σας'
0 2147483647 fallback false
i=6, index=30, no run, line end
i=7, index=31, no run, line end
double low error
--- parameters
wrapped: 0
ellipsized: 0
lines: 2
width: 225280
--- attributes
range 0 6
0 6 underline double
0 6 overline single
range 6 7
range 7 10
7 10 underline low
7 10 strikethrough true
range 10 11
range 11 16
11 16 underline error
11 16 rise 1024
range 16 2147483647
--- directions
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
--- cursor positions
0(0) 1(0) 2(0) 3(0) 4(0) 5(0) 6(0) 7(0) 8(0) 9(0) 10(0) 11(0) 12(0) 13(0) 14(0) 15(0) 15(1) 17(0)
--- lines
i=1, index=0, paragraph-start=1, dir=ltr 'double low error
'
i=2, index=17, paragraph-start=1, dir=ltr ''
--- runs
i=1, index=0, chars=6, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'double'
0 6 underline double
0 6 overline single
i=2, index=6, chars=1, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, ' '
i=3, index=7, chars=3, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'low'
7 10 underline low
7 10 strikethrough true
i=4, index=10, chars=1, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, ' '
i=5, index=11, chars=5, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'error'
11 16 rise 1024
11 16 underline error
i=6, index=16, no run, line end
i=7, index=17, no run, line end
a b c d
e f g h
--- parameters
wrapped: 1
ellipsized: 0
lines: 3
--- attributes
range 0 2147483647
--- directions
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
--- cursor positions
0(0) 1(0) 2(0) 3(0) 4(0) 5(0) 6(0) 7(0) 8(0) 9(0) 10(0) 11(0) 12(0) 13(0) 14(0) 15(0) 16(0) 17(0) 18(0) 19(0) 20(0) 21(0) 22(0) 23(0) 24(0) 25(0) 25(1) 29(0) 30(0) 31(0) 32(0) 33(0) 34(0) 35(0) 36(0) 37(0) 38(0) 39(0) 40(0) 41(0) 42(0) 43(0) 44(0) 45(0) 46(0) 47(0) 48(0) 49(0) 50(0) 50(1) 52(0)
--- lines
i=1, index=0, paragraph-start=1, dir=ltr 'a b c d
'
i=2, index=28, paragraph-start=0, dir=ltr 'e f g h
'
i=3, index=52, paragraph-start=1, dir=ltr ''
--- runs
i=1, index=0, chars=25, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'a b c d'
i=2, index=25, chars=1, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, '
'
i=3, index=28, no run, line end
i=4, index=28, chars=23, level=0, gravity=south, flags=0, font=OMITTED, script=latin, language=en-us, 'e f g h'
i=5, index=51, no run, line end
i=6, index=52, no run, line end
a b c d
e f g h
--- parameters
wrapped: 0
ellipsized: 0
lines: 1
--- attributes
range 0 2147483647
--- directions
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
--- cursor positions
0(0) 1(0) 2(0) 3(0) 4(0) 5(0) 6(0) 7(0) 8(0) 9(0) 10(0) 11(0) 12(0) 13(0) 14(0) 15(0) 16(0) 17(0) 18(0) 19(0) 20(0) 21(0) 22(0) 23(0) 24(0) 25(0) 28(0) 29(0) 30(0) 31(0) 32(0) 33(0) 34(0) 35(0) 36(0) 37(0) 38(0) 39(0) 40(0) 41(0) 42(0) 43(0) 44(0) 45(0) 46(0) 47(0) 48(0) 49(0) 50(0) 51(0) 51(1)
--- lines
i=1, index=0, paragraph-start=1, dir=ltr 'a b c d
e f g h
'