summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2021-11-29 13:59:31 +0000
committerMatthias Clasen <mclasen@redhat.com>2021-11-29 13:59:31 +0000
commit7b8d9efa987435a5214092157f032f9d65285d7e (patch)
tree10258cfd4975ec782c26942ca7bffbfedd396ec0
parentedebfaed39a41f815319fdc925fbf81c36a92f90 (diff)
parent52b1d2aa246e71642d2b3a1388899ce0e23bcbdd (diff)
downloadpango-7b8d9efa987435a5214092157f032f9d65285d7e.tar.gz
Merge branch 'tab-align' into 'main'
Support tab alignment Closes #34 See merge request GNOME/pango!527
-rw-r--r--pango/pango-layout-private.h1
-rw-r--r--pango/pango-layout.c291
-rw-r--r--pango/pango-tabs.c133
-rw-r--r--pango/pango-tabs.h35
-rw-r--r--pango/serializer.c40
-rw-r--r--tests/layouts/tabs.layout1836
-rw-r--r--tests/layouts/valid-12.layout30
-rw-r--r--tests/layouts/valid-13.layout30
-rw-r--r--tests/meson.build1
-rw-r--r--tests/test-layout.c7
-rw-r--r--tests/testserialize.c32
11 files changed, 2329 insertions, 107 deletions
diff --git a/pango/pango-layout-private.h b/pango/pango-layout-private.h
index b9f9b137..74c36126 100644
--- a/pango/pango-layout-private.h
+++ b/pango/pango-layout-private.h
@@ -74,6 +74,7 @@ struct _PangoLayout
PangoRectangle logical_rect;
PangoRectangle ink_rect;
int tab_width; /* Cached width of a tab. -1 == not yet calculated */
+ gunichar decimal;
int copy_end;
diff --git a/pango/pango-layout.c b/pango/pango-layout.c
index 7b32eeef..473d1785 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -82,6 +82,7 @@
#include "pango-glyph-item.h"
#include <string.h>
#include <math.h>
+#include <locale.h>
#include <hb-ot.h>
@@ -225,6 +226,7 @@ pango_layout_init (PangoLayout *layout)
layout->line_count = 0;
layout->tab_width = -1;
+ layout->decimal = 0;
layout->unknown_glyphs_count = -1;
layout->wrap = PANGO_WRAP_WORD;
@@ -1017,7 +1019,8 @@ pango_layout_get_alignment (PangoLayout *layout)
*
* Note that tabs and justification conflict with each other:
* Justification will move content away from its tab-aligned
- * positions.
+ * positions. The same is true for alignments other than
+ * %PANGO_ALIGN_LEFT.
*/
void
pango_layout_set_tabs (PangoLayout *layout,
@@ -3261,6 +3264,7 @@ pango_layout_line_leaked (PangoLayoutLine *line)
*****************/
static void shape_tab (PangoLayoutLine *line,
+ ParaBreakState *state,
PangoItem *item,
PangoGlyphString *glyphs);
@@ -3366,54 +3370,58 @@ ensure_tab_width (PangoLayout *layout)
}
}
-/* For now we only need the tab position, we assume
- * all tabs are left-aligned.
- */
-static int
-get_tab_pos (PangoLayout *layout,
- int index,
- gboolean *is_default)
+static void
+get_tab_pos (PangoLayoutLine *line,
+ int index,
+ int *tab_pos,
+ PangoTabAlign *alignment,
+ gunichar *decimal,
+ gboolean *is_default)
{
- gint n_tabs;
+ PangoLayout *layout = line->layout;
+ int n_tabs;
gboolean in_pixels;
+ int offset = 0;
+
+ if (layout->alignment != PANGO_ALIGN_CENTER)
+ {
+ if (line->is_paragraph_start && layout->indent >= 0)
+ offset = layout->indent;
+ else if (!line->is_paragraph_start && layout->indent < 0)
+ offset = - layout->indent;
+ }
if (layout->tabs)
{
n_tabs = pango_tab_array_get_size (layout->tabs);
in_pixels = pango_tab_array_get_positions_in_pixels (layout->tabs);
- if (is_default)
- *is_default = FALSE;
+ *is_default = FALSE;
}
else
{
n_tabs = 0;
in_pixels = FALSE;
- if (is_default)
- *is_default = TRUE;
+ *is_default = TRUE;
}
if (index < n_tabs)
{
- gint pos = 0;
-
- pango_tab_array_get_tab (layout->tabs, index, NULL, &pos);
+ pango_tab_array_get_tab (layout->tabs, index, alignment, tab_pos);
if (in_pixels)
- return pos * PANGO_SCALE;
- else
- return pos;
- }
+ *tab_pos *= PANGO_SCALE;
- if (n_tabs > 0)
+ *decimal = pango_tab_array_get_decimal_point (layout->tabs, index);
+ }
+ else if (n_tabs > 0)
{
- /* Extrapolate tab position, repeating the last tab gap to
- * infinity.
- */
+ /* Extrapolate tab position, repeating the last tab gap to infinity. */
int last_pos = 0;
int next_to_last_pos = 0;
int tab_width;
- pango_tab_array_get_tab (layout->tabs, n_tabs - 1, NULL, &last_pos);
+ pango_tab_array_get_tab (layout->tabs, n_tabs - 1, alignment, &last_pos);
+ *decimal = pango_tab_array_get_decimal_point (layout->tabs, n_tabs - 1);
if (n_tabs > 1)
pango_tab_array_get_tab (layout->tabs, n_tabs - 2, NULL, &next_to_last_pos);
@@ -3427,22 +3435,21 @@ get_tab_pos (PangoLayout *layout,
}
if (last_pos > next_to_last_pos)
- {
- tab_width = last_pos - next_to_last_pos;
- }
+ tab_width = last_pos - next_to_last_pos;
else
- {
- tab_width = layout->tab_width;
- }
+ tab_width = layout->tab_width;
- return last_pos + tab_width * (index - n_tabs + 1);
+ *tab_pos = last_pos + tab_width * (index - n_tabs + 1);
}
else
{
- /* No tab array set, so use default tab width
- */
- return layout->tab_width * index;
+ /* No tab array set, so use default tab width */
+ *tab_pos = layout->tab_width * index;
+ *alignment = PANGO_TAB_LEFT;
+ *decimal = 0;
}
+
+ *tab_pos -= offset;
}
static int
@@ -3459,7 +3466,7 @@ line_width (PangoLayoutLine *line)
{
PangoLayoutRun *run = l->data;
- for (i=0; i < run->glyphs->num_glyphs; i++)
+ for (i = 0; i < run->glyphs->num_glyphs; i++)
width += run->glyphs->glyphs[i].geometry.width;
}
@@ -3483,14 +3490,33 @@ showing_space (const PangoAnalysis *analysis)
return FALSE;
}
+static void break_state_set_last_tab (ParaBreakState *state,
+ PangoGlyphString *glyphs,
+ int width,
+ int tab_pos,
+ PangoTabAlign tab_align,
+ gunichar tab_decimal);
+
+static void
+ensure_decimal (PangoLayout *layout)
+{
+ if (layout->decimal == 0)
+ layout->decimal = g_utf8_get_char (localeconv ()->decimal_point);
+}
+
static void
shape_tab (PangoLayoutLine *line,
+ ParaBreakState *state,
PangoItem *item,
PangoGlyphString *glyphs)
{
int i, space_width;
+ int current_width;
+ int tab_pos;
+ PangoTabAlign tab_align;
+ gunichar tab_decimal;
- int current_width = line_width (line);
+ current_width = line_width (line);
pango_glyph_string_set_size (glyphs, 1);
@@ -3498,6 +3524,7 @@ shape_tab (PangoLayoutLine *line,
glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH ('\t');
else
glyphs->glyphs[0].glyph = PANGO_GLYPH_EMPTY;
+
glyphs->glyphs[0].geometry.x_offset = 0;
glyphs->glyphs[0].geometry.y_offset = 0;
glyphs->glyphs[0].attr.is_cluster_start = 1;
@@ -3508,15 +3535,17 @@ shape_tab (PangoLayoutLine *line,
ensure_tab_width (line->layout);
space_width = line->layout->tab_width / 8;
- for (i=0;;i++)
+ for (i = 0; ; i++)
{
gboolean is_default;
- int tab_pos = get_tab_pos (line->layout, i, &is_default);
+
+ get_tab_pos (line, i, &tab_pos, &tab_align, &tab_decimal, &is_default);
+
/* Make sure there is at least a space-width of space between
- * tab-aligned text and the text before it. However, only do
+ * tab-aligned text and the text before it. However, only do
* this if no tab array is set on the layout, ie. using default
- * tab positions. If use has set tab positions, respect it to
- * the pixel.
+ * tab positions. If the user has set tab positions, respect it
+ * to the pixel.
*/
if (tab_pos >= current_width + (is_default ? space_width : 1))
{
@@ -3524,6 +3553,14 @@ shape_tab (PangoLayoutLine *line,
break;
}
}
+
+ if (tab_decimal == 0)
+ {
+ ensure_decimal (line->layout);
+ tab_decimal = line->layout->decimal;
+ }
+
+ break_state_set_last_tab (state, glyphs, current_width, tab_pos, tab_align, tab_decimal);
}
static inline gboolean
@@ -3607,12 +3644,69 @@ struct _ParaBreakState
int hyphen_width; /* How much space a hyphen will take */
GList *baseline_shifts;
+
+ PangoGlyphString *last_tab;
+ int last_tab_width;
+ int last_tab_pos;
+ PangoTabAlign last_tab_align;
+ gunichar last_tab_decimal;
};
+static void
+break_state_set_last_tab (ParaBreakState *state,
+ PangoGlyphString *glyphs,
+ int width,
+ int tab_pos,
+ PangoTabAlign tab_align,
+ gunichar tab_decimal)
+{
+
+ state->last_tab = glyphs;
+ state->last_tab_width = width;
+ state->last_tab_pos = tab_pos;
+ state->last_tab_align = tab_align;
+ state->last_tab_decimal = tab_decimal;
+}
+
static gboolean
should_ellipsize_current_line (PangoLayout *layout,
ParaBreakState *state);
+static void
+get_decimal_prefix_width (PangoItem *item,
+ PangoGlyphString *glyphs,
+ const char *text,
+ gunichar decimal,
+ int *width,
+ gboolean *found)
+{
+ PangoGlyphItem glyph_item = { item, glyphs, 0, 0, 0 };
+ int *log_widths;
+ int i;
+ const char *p;
+
+ log_widths = g_new (int, item->num_chars);
+
+ pango_glyph_item_get_logical_widths (&glyph_item, text, log_widths);
+
+ *width = 0;
+ *found = FALSE;
+
+ for (i = 0, p = text + item->offset; i < item->num_chars; i++, p = g_utf8_next_char (p))
+ {
+ if (g_utf8_get_char (p) == decimal)
+ {
+ *width += log_widths[i] / 2;
+ *found = TRUE;
+ break;
+ }
+
+ *width += log_widths[i];
+ }
+
+ g_free (log_widths);
+}
+
static PangoGlyphString *
shape_run (PangoLayoutLine *line,
ParaBreakState *state,
@@ -3622,7 +3716,7 @@ shape_run (PangoLayoutLine *line,
PangoGlyphString *glyphs = pango_glyph_string_new ();
if (layout->text[item->offset] == '\t')
- shape_tab (line, item, glyphs);
+ shape_tab (line, state, item, glyphs);
else
{
PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
@@ -3660,6 +3754,33 @@ shape_run (PangoLayoutLine *line,
glyphs->glyphs[0].geometry.x_offset += space_left;
glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += space_right;
}
+
+ if (state->last_tab != NULL)
+ {
+ int w;
+
+ g_assert (state->last_tab->num_glyphs == 1);
+
+ /* Update the width of the current tab to position this run properly */
+
+ w = state->last_tab_pos - state->last_tab_width;
+
+ if (state->last_tab_align == PANGO_TAB_RIGHT)
+ w -= pango_glyph_string_get_width (glyphs);
+ else if (state->last_tab_align == PANGO_TAB_CENTER)
+ w -= pango_glyph_string_get_width (glyphs) / 2;
+ else if (state->last_tab_align == PANGO_TAB_DECIMAL)
+ {
+ int width;
+ gboolean found;
+
+ get_decimal_prefix_width (item, glyphs, layout->text, state->last_tab_decimal, &width, &found);
+
+ w -= width;
+ }
+
+ state->last_tab->glyphs[0].geometry.width = MAX (w, 0);
+ }
}
return glyphs;
@@ -3680,19 +3801,45 @@ insert_run (PangoLayoutLine *line,
run->glyphs = glyphs;
else if (last_run && state->log_widths_offset == 0 &&
!(run_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN))
- run->glyphs = state->glyphs;
+ {
+ run->glyphs = state->glyphs;
+ state->glyphs = NULL;
+ }
else
run->glyphs = shape_run (line, state, run_item);
- if (last_run)
+ if (last_run && state->glyphs)
{
- if (state->log_widths_offset > 0)
- pango_glyph_string_free (state->glyphs);
+ pango_glyph_string_free (state->glyphs);
state->glyphs = NULL;
}
line->runs = g_slist_prepend (line->runs, run);
line->length += run_item->length;
+
+ if (state->last_tab && run->glyphs != state->last_tab)
+ {
+ /* Adjust the tab position so placing further runs will continue to
+ * maintain the tab placement. In the case of decimal tabs, we are
+ * done once we've placed the run with the decimal point.
+ */
+
+ if (state->last_tab_align == PANGO_TAB_RIGHT)
+ state->last_tab_width += pango_glyph_string_get_width (run->glyphs);
+ else if (state->last_tab_align == PANGO_TAB_CENTER)
+ state->last_tab_width += pango_glyph_string_get_width (run->glyphs) / 2;
+ else if (state->last_tab_align == PANGO_TAB_DECIMAL)
+ {
+ int width;
+ gboolean found;
+
+ get_decimal_prefix_width (run->item, run->glyphs, line->layout->text, state->last_tab_decimal, &width, &found);
+
+ state->last_tab_width += width;
+ if (found)
+ state->last_tab = NULL;
+ }
+ }
}
static gboolean
@@ -3800,6 +3947,20 @@ compute_log_widths (PangoLayout *layout,
pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
}
+/* If last_tab is set, we've added a tab and remaining_width has been updated to
+ * account for its origin width, which is last_tab_pos - last_tab_width. shape_run
+ * updates the tab width, so we need to consider the delta when comparing
+ * against remaining_width.
+ */
+static int
+tab_width_change (ParaBreakState *state)
+{
+ if (state->last_tab)
+ return state->last_tab->glyphs[0].geometry.width - (state->last_tab_pos - state->last_tab_width);
+
+ return 0;
+}
+
/* Tries to insert as much as possible of the item at the head of
* state->items onto @line. Five results are possible:
*
@@ -3915,7 +4076,6 @@ process_item (PangoLayout *layout,
if (state->remaining_width < 0 && !no_break_at_end) /* Wrapping off */
{
insert_run (line, state, item, NULL, TRUE);
-
DEBUG1 ("no wrapping, all-fit");
return BREAK_ALL_FIT;
}
@@ -3931,6 +4091,16 @@ process_item (PangoLayout *layout,
width += state->log_widths[state->log_widths_offset + i];
}
+ if (layout->text[item->offset] == '\t')
+ {
+ insert_run (line, state, item, NULL, TRUE);
+ state->remaining_width -= width;
+ state->remaining_width = MAX (state->remaining_width, 0);
+
+ DEBUG1 ("tab run, all-fit");
+ return BREAK_ALL_FIT;
+ }
+
if (!no_break_at_end &&
can_break_at (layout, state->start_offset + item->num_chars, wrap))
{
@@ -3945,31 +4115,32 @@ process_item (PangoLayout *layout,
else
extra_width = 0;
- if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) &&
+ if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs) ||
+ (state->last_tab && state->last_tab_align != PANGO_TAB_LEFT)) &&
!no_break_at_end)
{
+ PangoGlyphString *glyphs;
+
DEBUG1 ("%d + %d <= %d", width, extra_width, state->remaining_width);
- insert_run (line, state, item, NULL, FALSE);
+ glyphs = shape_run (line, state, item);
- width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
+ width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
if (width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs))
{
+ insert_run (line, state, item, glyphs, TRUE);
+
state->remaining_width -= width;
state->remaining_width = MAX (state->remaining_width, 0);
- /* We passed last_run == FALSE to insert_run, so it did not do this */
- pango_glyph_string_free (state->glyphs);
- state->glyphs = NULL;
-
DEBUG1 ("early accept '%.*s', all-fit, remaining %d",
item->length, layout->text + item->offset,
state->remaining_width);
return BREAK_ALL_FIT;
}
- /* if it doesn't fit after shaping, revert and proceed to break the item */
- uninsert_run (line);
+ /* if it doesn't fit after shaping, discard and proceed to break the item */
+ pango_glyph_string_free (glyphs);
}
/*** From here on, we look for a way to break item ***/
@@ -4054,7 +4225,7 @@ retry_break:
glyphs = shape_run (line, state, new_item);
- new_break_width = pango_glyph_string_get_width (glyphs);
+ new_break_width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
if (num_chars > 0 &&
layout->log_attrs[state->start_offset + num_chars - 1].is_white)
@@ -4315,6 +4486,10 @@ process_line (PangoLayout *layout,
state->remaining_width = -1;
else
state->remaining_width = state->line_width;
+
+ state->last_tab = NULL;
+ state->last_tab_align = PANGO_TAB_LEFT;
+
DEBUG ("starting to fill line", line, state);
while (state->items)
diff --git a/pango/pango-tabs.c b/pango/pango-tabs.c
index c126ec91..27ae3be8 100644
--- a/pango/pango-tabs.c
+++ b/pango/pango-tabs.c
@@ -28,12 +28,9 @@ typedef struct _PangoTab PangoTab;
struct _PangoTab
{
- gint location; /* Offset in pixels of this tab stop
- * from the left margin of the text.
- */
- PangoTabAlign alignment; /* Where the tab stop appears relative
- * to the text.
- */
+ int location;
+ PangoTabAlign alignment;
+ gunichar decimal_point;
};
/**
@@ -42,7 +39,8 @@ struct _PangoTab
* A `PangoTabArray` contains an array of tab stops.
*
* `PangoTabArray` can be used to set tab stops in a `PangoLayout`.
- * Each tab stop has an alignment and a position.
+ * Each tab stop has an alignment, a position, and optionally
+ * a character to use as decimal point.
*/
struct _PangoTabArray
{
@@ -59,6 +57,7 @@ init_tabs (PangoTabArray *array, gint start, gint end)
{
array->tabs[start].location = 0;
array->tabs[start].alignment = PANGO_TAB_LEFT;
+ array->tabs[start].decimal_point = 0;
++start;
}
}
@@ -141,6 +140,7 @@ pango_tab_array_new_with_positions (gint size,
array->tabs[0].alignment = first_alignment;
array->tabs[0].location = first_position;
+ array->tabs[0].decimal_point = 0;
if (size == 1)
return array;
@@ -155,6 +155,7 @@ pango_tab_array_new_with_positions (gint size,
array->tabs[i].alignment = align;
array->tabs[i].location = pos;
+ array->tabs[i].decimal_point = 0;
++i;
}
@@ -266,9 +267,6 @@ pango_tab_array_resize (PangoTabArray *tab_array,
* @location: tab location in Pango units
*
* Sets the alignment and location of a tab stop.
- *
- * @alignment must always be %PANGO_TAB_LEFT in the current
- * implementation.
*/
void
pango_tab_array_set_tab (PangoTabArray *tab_array,
@@ -278,7 +276,6 @@ pango_tab_array_set_tab (PangoTabArray *tab_array,
{
g_return_if_fail (tab_array != NULL);
g_return_if_fail (tab_index >= 0);
- g_return_if_fail (alignment == PANGO_TAB_LEFT);
g_return_if_fail (location >= 0);
if (tab_index >= tab_array->size)
@@ -398,10 +395,21 @@ pango_tab_array_to_string (PangoTabArray *tab_array)
for (int i = 0; i < tab_array->size; i++)
{
if (i > 0)
- g_string_append_c (s, ' ');
+ g_string_append_c (s, '\n');
+
+ if (tab_array->tabs[i].alignment == PANGO_TAB_RIGHT)
+ g_string_append (s, "right:");
+ else if (tab_array->tabs[i].alignment == PANGO_TAB_CENTER)
+ g_string_append (s, "center:");
+ else if (tab_array->tabs[i].alignment == PANGO_TAB_DECIMAL)
+ g_string_append (s, "decimal:");
+
g_string_append_printf (s, "%d", tab_array->tabs[i].location);
if (tab_array->positions_in_pixels)
g_string_append (s, "px");
+
+ if (tab_array->tabs[i].decimal_point != 0)
+ g_string_append_printf (s, ":%d", tab_array->tabs[i].decimal_point);
}
return g_string_free (s, FALSE);
@@ -446,14 +454,39 @@ pango_tab_array_from_string (const char *text)
{
char *endp;
gint64 pos;
+ PangoTabAlign align;
+
+ if (g_str_has_prefix (p, "left:"))
+ {
+ align = PANGO_TAB_LEFT;
+ p += strlen ("left:");
+ }
+ else if (g_str_has_prefix (p, "right:"))
+ {
+ align = PANGO_TAB_RIGHT;
+ p += strlen ("right:");
+ }
+ else if (g_str_has_prefix (p, "center:"))
+ {
+ align = PANGO_TAB_CENTER;
+ p += strlen ("center:");
+ }
+ else if (g_str_has_prefix (p, "decimal:"))
+ {
+ align = PANGO_TAB_DECIMAL;
+ p += strlen ("decimal:");
+ }
+ else
+ {
+ align = PANGO_TAB_LEFT;
+ }
pos = g_ascii_strtoll (p, &endp, 10);
if (pos < 0 ||
(pixels && *endp != 'p') ||
- (!pixels && !g_ascii_isspace (*endp) && *endp != '\0')) goto fail;
+ (!pixels && !g_ascii_isspace (*endp) && *endp != ':' && *endp != '\0')) goto fail;
- pango_tab_array_set_tab (array, i, PANGO_TAB_LEFT, pos);
- i++;
+ pango_tab_array_set_tab (array, i, align, pos);
p = (const char *)endp;
if (pixels)
@@ -461,7 +494,23 @@ pango_tab_array_from_string (const char *text)
if (p[0] != 'p' || p[1] != 'x') goto fail;
p += 2;
}
+
+ if (p[0] == ':')
+ {
+ gunichar ch;
+
+ p++;
+ ch = g_ascii_strtoll (p, &endp, 10);
+ if (!g_ascii_isspace (*endp) && *endp != '\0') goto fail;
+
+ pango_tab_array_set_decimal_point (array, i, ch);
+
+ p = (const char *)endp;
+ }
+
p = skip_whitespace (p);
+
+ i++;
}
goto success;
@@ -473,3 +522,57 @@ fail:
success:
return array;
}
+
+/**
+ * pango_tab_array_set_decimal_point:
+ * @tab_array: a `PangoTabArray`
+ * @tab_index: the index of a tab stop
+ * @decimal_point: the decimal point to use
+ *
+ * Sets the decimal point to use.
+ *
+ * This is only relevant for %PANGO_TAB_DECIMAL.
+ *
+ * By default, Pango uses the decimal point according
+ * to the current locale.
+ *
+ * Since: 1.50
+ */
+void
+pango_tab_array_set_decimal_point (PangoTabArray *tab_array,
+ int tab_index,
+ gunichar decimal_point)
+{
+ g_return_if_fail (tab_array != NULL);
+ g_return_if_fail (tab_index >= 0);
+
+ if (tab_index >= tab_array->size)
+ pango_tab_array_resize (tab_array, tab_index + 1);
+
+ tab_array->tabs[tab_index].decimal_point = decimal_point;
+}
+
+/**
+ * pango_tab_array_get_decimal_point:
+ * @tab_array: a `PangoTabArray`
+ * @tab_index: the index of a tab stop
+ *
+ * Gets the decimal point to use.
+ *
+ * This is only relevant for %PANGO_TAB_DECIMAL.
+ *
+ * The default value of 0 means that Pango will use the
+ * decimal point according to the current locale.
+ *
+ * Since: 1.50
+ */
+gunichar
+pango_tab_array_get_decimal_point (PangoTabArray *tab_array,
+ int tab_index)
+{
+ g_return_val_if_fail (tab_array != NULL, 0);
+ g_return_val_if_fail (tab_index < tab_array->size, 0);
+ g_return_val_if_fail (tab_index >= 0, 0);
+
+ return tab_array->tabs[tab_index].decimal_point;
+}
diff --git a/pango/pango-tabs.h b/pango/pango-tabs.h
index d2a68d5a..0792a36a 100644
--- a/pango/pango-tabs.h
+++ b/pango/pango-tabs.h
@@ -30,21 +30,27 @@ typedef struct _PangoTabArray PangoTabArray;
/**
* PangoTabAlign:
- * @PANGO_TAB_LEFT: the tab stop appears to the left of the text.
+ * @PANGO_TAB_LEFT: the text appears to the right of the tab stop position
+ * @PANGO_TAB_RIGHT: the text appears to the left of the tab stop position
+ * until the available space is filled
+ * @PANGO_TAB_CENTER: the text is centered at the tab stop position
+ * until the available space is filled
+ * @PANGO_TAB_DECIMAL: text before the first '.' appears to the left of the
+ * tab stop position (until the available space is filled), the rest to
+ * the right
*
- * `PangoTabAlign` specifies where a tab stop appears relative to the text.
+ * `PangoTabAlign` specifies where the text appears relative to the tab stop
+ * position.
+ *
+ * Support for tab alignments other than %PANGO_TAB_LEFT was added
+ * in Pango 1.50.
*/
typedef enum
{
- PANGO_TAB_LEFT
-
- /* These are not supported now, but may be in the
- * future.
- *
- * PANGO_TAB_RIGHT,
- * PANGO_TAB_CENTER,
- * PANGO_TAB_NUMERIC
- */
+ PANGO_TAB_LEFT,
+ PANGO_TAB_RIGHT,
+ PANGO_TAB_CENTER,
+ PANGO_TAB_DECIMAL
} PangoTabAlign;
#define PANGO_TYPE_TAB_ARRAY (pango_tab_array_get_type ())
@@ -92,6 +98,13 @@ char * pango_tab_array_to_string (PangoTabArray *tab_array);
PANGO_AVAILABLE_IN_1_50
PangoTabArray * pango_tab_array_from_string (const char *text);
+PANGO_AVAILABLE_IN_1_50
+void pango_tab_array_set_decimal_point (PangoTabArray *tab_array,
+ int tab_index,
+ gunichar decimal_point);
+PANGO_AVAILABLE_IN_1_50
+gunichar pango_tab_array_get_decimal_point (PangoTabArray *tab_array,
+ int tab_index);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(PangoTabArray, pango_tab_array_free)
diff --git a/pango/serializer.c b/pango/serializer.c
index 6e394a1c..fe0fc25c 100644
--- a/pango/serializer.c
+++ b/pango/serializer.c
@@ -218,9 +218,17 @@ add_tab_array (JsonBuilder *builder,
json_builder_begin_array (builder);
for (int i = 0; i < pango_tab_array_get_size (tabs); i++)
{
+ PangoTabAlign align;
int pos;
- pango_tab_array_get_tab (tabs, i, NULL, &pos);
+ pango_tab_array_get_tab (tabs, i, &align, &pos);
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "position");
json_builder_add_int_value (builder, pos);
+ json_builder_set_member_name (builder, "alignment");
+ add_enum_value (builder, PANGO_TYPE_TAB_ALIGN, align, FALSE);
+ json_builder_set_member_name (builder, "decimal-point");
+ json_builder_add_int_value (builder, pango_tab_array_get_decimal_point (tabs, i));
+ json_builder_end_object (builder);
}
json_builder_end_array (builder);
@@ -1133,9 +1141,35 @@ json_to_tab_array (JsonReader *reader,
for (int i = 0; i < json_reader_count_elements (reader); i++)
{
int pos;
+ PangoTabAlign align = PANGO_TAB_LEFT;
+ gunichar ch = 0;
+
json_reader_read_element (reader, i);
- pos = json_reader_get_int_value (reader);
- pango_tab_array_set_tab (tabs, i, PANGO_TAB_LEFT, pos);
+ if (json_reader_is_object (reader))
+ {
+ json_reader_read_member (reader, "position");
+ pos = json_reader_get_int_value (reader);
+ json_reader_end_member (reader);
+ json_reader_read_member (reader, "alignment");
+
+ align = get_enum_value (PANGO_TYPE_TAB_ALIGN,
+ json_reader_get_string_value (reader),
+ FALSE,
+ error);
+ if (align == -1)
+ goto fail;
+ json_reader_end_member (reader);
+ json_reader_read_member (reader, "decimal-point");
+ ch = json_reader_get_int_value (reader);
+ json_reader_end_member (reader);
+ }
+ else
+ {
+ pos = json_reader_get_int_value (reader);
+ }
+
+ pango_tab_array_set_tab (tabs, i, align, pos);
+ pango_tab_array_set_decimal_point (tabs, i, ch);
json_reader_end_element (reader);
}
}
diff --git a/tests/layouts/tabs.layout b/tests/layouts/tabs.layout
new file mode 100644
index 00000000..deafe91e
--- /dev/null
+++ b/tests/layouts/tabs.layout
@@ -0,0 +1,1836 @@
+{
+ "context" : {
+ "font" : "serif 12",
+ "base-gravity" : "south",
+ "gravity-hint" : "natural",
+ "base-dir" : "weak-ltr",
+ "round-glyph-positions" : true,
+ "transform" : [
+ 1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "text" : "\t0.1\t100.5\tso\tmore\tso\n\t0.02\t20.25\tand\tand\tand\n\t0.003\t1.9\tmore\tso\tmore",
+ "font" : "Cantarell 14 @wght=400",
+ "tabs" : {
+ "positions-in-pixels" : true,
+ "positions" : [
+ {
+ "position" : 50,
+ "alignment" : "decimal",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 150,
+ "alignment" : "decimal",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 250,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 350,
+ "alignment" : "center",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 450,
+ "alignment" : "right",
+ "decimal-point" : 0
+ }
+ ]
+ },
+ "width" : 460800,
+ "output" : {
+ "is-wrapped" : false,
+ "is-ellipsized" : false,
+ "unknown-glyphs" : 0,
+ "width" : 460800,
+ "height" : 73728,
+ "log-attrs" : [
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "sentence-boundary" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-boundary" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "mandatory-break" : true,
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "sentence-boundary" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-boundary" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "mandatory-break" : true,
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "sentence-boundary" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "sentence-boundary" : true,
+ "sentence-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-end" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "word-boundary" : true
+ },
+ {
+ "line-break" : true,
+ "char-break" : true,
+ "cursor-position" : true,
+ "word-start" : true,
+ "backspace-deletes-character" : true,
+ "word-boundary" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "char-break" : true,
+ "cursor-position" : true,
+ "break-inserts-hyphen" : true
+ },
+ {
+ "line-break" : true,
+ "mandatory-break" : true,
+ "char-break" : true,
+ "white" : true,
+ "cursor-position" : true,
+ "word-end" : true,
+ "sentence-boundary" : true,
+ "sentence-end" : true,
+ "word-boundary" : true
+ }
+ ],
+ "lines" : [
+ {
+ "start-index" : 0,
+ "length" : 21,
+ "paragraph-start" : true,
+ "direction" : "ltr",
+ "runs" : [
+ {
+ "offset" : 0,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 37376,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 1,
+ "length" : 3,
+ "text" : "0.1",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 965,
+ "width" : 8192,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ }
+ ]
+ },
+ {
+ "offset" : 4,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 58368,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 5,
+ "length" : 5,
+ "text" : "100.5",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 965,
+ "width" : 8192,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ },
+ {
+ "glyph" : 969,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 4
+ }
+ ]
+ },
+ {
+ "offset" : 10,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 89600,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 11,
+ "length" : 2,
+ "text" : "so",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 417,
+ "width" : 9216,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ }
+ ]
+ },
+ {
+ "offset" : 13,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 58880,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 14,
+ "length" : 4,
+ "text" : "more",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 358,
+ "width" : 17408,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 409,
+ "width" : 7168,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 287,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ }
+ ]
+ },
+ {
+ "offset" : 18,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 58880,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 19,
+ "length" : 2,
+ "text" : "so",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 417,
+ "width" : 9216,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "start-index" : 22,
+ "length" : 23,
+ "paragraph-start" : true,
+ "direction" : "ltr",
+ "runs" : [
+ {
+ "offset" : 22,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 37376,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 23,
+ "length" : 4,
+ "text" : "0.02",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 966,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ }
+ ]
+ },
+ {
+ "offset" : 27,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 54272,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 28,
+ "length" : 5,
+ "text" : "20.25",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 966,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 966,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ },
+ {
+ "glyph" : 969,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 4
+ }
+ ]
+ },
+ {
+ "offset" : 33,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 79360,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 34,
+ "length" : 3,
+ "text" : "and",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 244,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 360,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 280,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ }
+ ]
+ },
+ {
+ "offset" : 37,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 53248,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 38,
+ "length" : 3,
+ "text" : "and",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 244,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 360,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 280,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ }
+ ]
+ },
+ {
+ "offset" : 41,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 53248,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 42,
+ "length" : 3,
+ "text" : "and",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 244,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 360,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 280,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "start-index" : 46,
+ "length" : 23,
+ "paragraph-start" : true,
+ "direction" : "ltr",
+ "runs" : [
+ {
+ "offset" : 46,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 37376,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 47,
+ "length" : 5,
+ "text" : "0.003",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 964,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ },
+ {
+ "glyph" : 967,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 4
+ }
+ ]
+ },
+ {
+ "offset" : 52,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 55296,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 53,
+ "length" : 3,
+ "text" : "1.9",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 965,
+ "width" : 8192,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 1058,
+ "width" : 5120,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 973,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ }
+ ]
+ },
+ {
+ "offset" : 56,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 88576,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 57,
+ "length" : 4,
+ "text" : "more",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 358,
+ "width" : 17408,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 409,
+ "width" : 7168,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 287,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ }
+ ]
+ },
+ {
+ "offset" : 61,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 46080,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 62,
+ "length" : 2,
+ "text" : "so",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 417,
+ "width" : 9216,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ }
+ ]
+ },
+ {
+ "offset" : 64,
+ "length" : 1,
+ "text" : "\t",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 268435455,
+ "width" : 46080,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ }
+ ]
+ },
+ {
+ "offset" : 65,
+ "length" : 4,
+ "text" : "more",
+ "bidi-level" : 0,
+ "gravity" : "south",
+ "language" : "en-us",
+ "script" : "latin",
+ "font" : {
+ "description" : "Cantarell 14 @wght=400",
+ "checksum" : "5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2",
+ "variations" : {
+ "wght" : 0
+ },
+ "matrix" : [
+ 1.0,
+ -0.0,
+ -0.0,
+ 1.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ "flags" : 0,
+ "y-offset" : 0,
+ "start-x-offset" : 0,
+ "end-x-offset" : 0,
+ "glyphs" : [
+ {
+ "glyph" : 358,
+ "width" : 17408,
+ "is-cluster-start" : true,
+ "log-cluster" : 0
+ },
+ {
+ "glyph" : 370,
+ "width" : 11264,
+ "is-cluster-start" : true,
+ "log-cluster" : 1
+ },
+ {
+ "glyph" : 409,
+ "width" : 7168,
+ "is-cluster-start" : true,
+ "log-cluster" : 2
+ },
+ {
+ "glyph" : 287,
+ "width" : 10240,
+ "is-cluster-start" : true,
+ "log-cluster" : 3
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/layouts/valid-12.layout b/tests/layouts/valid-12.layout
index cc647178..247b374e 100644
--- a/tests/layouts/valid-12.layout
+++ b/tests/layouts/valid-12.layout
@@ -21,11 +21,31 @@
"tabs" : {
"positions-in-pixels" : true,
"positions" : [
- 0,
- 50,
- 100,
- 150,
- 200
+ {
+ "position" : 0,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 50,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 100,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 150,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 200,
+ "alignment" : "left",
+ "decimal-point" : 0
+ }
]
},
"output" : {
diff --git a/tests/layouts/valid-13.layout b/tests/layouts/valid-13.layout
index 7b70a602..b557a278 100644
--- a/tests/layouts/valid-13.layout
+++ b/tests/layouts/valid-13.layout
@@ -21,11 +21,31 @@
"tabs" : {
"positions-in-pixels" : true,
"positions" : [
- 0,
- 50,
- 100,
- 150,
- 200
+ {
+ "position" : 0,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 50,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 100,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 150,
+ "alignment" : "left",
+ "decimal-point" : 0
+ },
+ {
+ "position" : 200,
+ "alignment" : "left",
+ "decimal-point" : 0
+ }
]
},
"single-paragraph" : true,
diff --git a/tests/meson.build b/tests/meson.build
index 3d47ba85..2658f0a2 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -95,6 +95,7 @@ installed_test_layouts_data = [
'layouts/bratwurst.layout',
'layouts/effigy.layout',
'layouts/kebab.layout',
+ 'layouts/tabs.layout',
'layouts/valid-1.layout',
'layouts/valid-2.layout',
'layouts/valid-3.layout',
diff --git a/tests/test-layout.c b/tests/test-layout.c
index 3ea3063d..1139e4fd 100644
--- a/tests/test-layout.c
+++ b/tests/test-layout.c
@@ -117,7 +117,7 @@ install_fonts (const char *dir)
config = FcConfigCreate ();
- path = g_test_build_filename (G_TEST_DIST, "fonts/fonts.conf", NULL);
+ path = g_build_filename (dir, "fonts.conf", NULL);
g_file_get_contents (path, &conf, &len, NULL);
if (!FcConfigParseAndLoadFromMemory (config, (const FcChar8 *) conf, TRUE))
@@ -161,7 +161,10 @@ main (int argc, char *argv[])
g_option_context_free (option_context);
if (opt_fonts)
- install_fonts (opt_fonts);
+ {
+ install_fonts (opt_fonts);
+ g_free (opt_fonts);
+ }
/* allow to easily generate expected output for new test cases */
if (argc > 1 && argv[1][0] != '-')
diff --git a/tests/testserialize.c b/tests/testserialize.c
index 4ea7425e..95fb4cd4 100644
--- a/tests/testserialize.c
+++ b/tests/testserialize.c
@@ -86,18 +86,22 @@ test_serialize_tab_array (void)
"0px 10px 100px 200px 400px",
" 0 10 ",
"20 10",
+ "left:10px right:20px center:30px decimal:40px",
+ "decimal:10240:94",
""
};
const char *roundtripped[] = {
- "0 10 100 200 400",
- "0px 10px 100px 200px 400px",
- "0 10",
- "20 10",
+ "0\n10\n100\n200\n400",
+ "0px\n10px\n100px\n200px\n400px",
+ "0\n10",
+ "20\n10",
+ "10px\nright:20px\ncenter:30px\ndecimal:40px",
+ "decimal:10240:94",
""
};
const char *invalid[] = {
"not a tabarray",
- "-10\n-20",
+ "-10:-20",
"10ps 20pu",
"10, 20",
"10 20px 30",
@@ -231,9 +235,21 @@ test_serialize_layout_valid (void)
" \"tabs\" : {\n"
" \"positions-in-pixels\" : true,\n"
" \"positions\" : [\n"
- " 0,\n"
- " 50,\n"
- " 100\n"
+ " {\n"
+ " \"position\" : 0,\n"
+ " \"alignment\" : \"left\",\n"
+ " \"decimal-point\" : 0\n"
+ " },\n"
+ " {\n"
+ " \"position\" : 50,\n"
+ " \"alignment\" : \"center\",\n"
+ " \"decimal-point\" : 0\n"
+ " },\n"
+ " {\n"
+ " \"position\" : 100,\n"
+ " \"alignment\" : \"decimal\",\n"
+ " \"decimal-point\" : 94\n"
+ " }\n"
" ]\n"
" },\n"
" \"alignment\" : \"center\",\n"