[pango/line-breaker: 8/16] Add PangoLines
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pango/line-breaker: 8/16] Add PangoLines
- Date: Tue, 18 Jan 2022 04:02:20 +0000 (UTC)
commit 2b1c98a06efbcf04d6aeb25e39d5ec90b635a2e2
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Jan 14 22:09:57 2022 -0500
Add PangoLines
This is an array of positioned PangoLine
objects.
pango/meson.build | 2 +
pango/pango-lines-private.h | 11 +
pango/pango-lines.c | 840 ++++++++++++++++++++++++++++++++++++++++++++
pango/pango-lines.h | 92 +++++
pango/pango.h | 1 +
tests/meson.build | 1 +
tests/test-simple-layout.c | 228 ++++++++++++
7 files changed, 1175 insertions(+)
---
diff --git a/pango/meson.build b/pango/meson.build
index 78af534e..3802c51a 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -33,6 +33,7 @@ pango_sources = [
'json/gtkjsonprinter.c',
'pango-line.c',
'pango-line-breaker.c',
+ 'pango-lines.c',
]
pango_headers = [
@@ -56,6 +57,7 @@ pango_headers = [
'pango-language.h',
'pango-line.h',
'pango-line-breaker.h',
+ 'pango-lines.h',
'pango-layout.h',
'pango-matrix.h',
'pango-markup.h',
diff --git a/pango/pango-lines-private.h b/pango/pango-lines-private.h
new file mode 100644
index 00000000..46ef06bf
--- /dev/null
+++ b/pango/pango-lines-private.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "pango-lines.h"
+
+struct _PangoLines
+{
+ GObject parent_instance;
+
+ GArray *lines;
+ guint serial;
+};
diff --git a/pango/pango-lines.c b/pango/pango-lines.c
new file mode 100644
index 00000000..5fe080be
--- /dev/null
+++ b/pango/pango-lines.c
@@ -0,0 +1,840 @@
+#include "config.h"
+
+#include "pango-lines-private.h"
+#include "pango-line-private.h"
+
+/* {{{ PangoLines implementation */
+
+typedef struct _Line Line;
+struct _Line
+{
+ PangoLine *line;
+ int x, y;
+};
+
+struct _PangoLinesClass
+{
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (PangoLines, pango_lines, G_TYPE_OBJECT)
+
+static void
+pango_lines_init (PangoLines *lines)
+{
+ lines->serial = 1;
+ lines->lines = g_array_new (FALSE, FALSE, sizeof (Line));
+}
+
+static void
+pango_lines_finalize (GObject *object)
+{
+ PangoLines *lines = PANGO_LINES (object);
+
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Line *line = &g_array_index (lines->lines, Line, i);
+ g_object_unref (line->line);
+ }
+
+ g_array_free (lines->lines, TRUE);
+
+ G_OBJECT_CLASS (pango_lines_parent_class)->finalize (object);
+}
+
+static void
+pango_lines_class_init (PangoLinesClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango_lines_finalize;
+}
+
+/* }}} */
+ /* {{{ Utilities*/
+
+/*< private >
+ * pango_lines_index_to_line:
+ * @lines: a `PangoLines`
+ * @idx: index in @lines
+ * @line: (inout): if *@line is `NULL`, the found line will be returned here
+ * @line_no: (out) (optional): return location for line number
+ * @x_offset: (out) (optional): return location for X offset of line
+ * @y_offset: (out) (optional): return location for Y offset of line
+ *
+ * Given an index (and possibly line), determine the line number,
+ * and offset for the line.
+ *
+ * @idx may refer to any byte position inside @lines, as well
+ * as the position after the last character (i.e.
+ * line->start_index + line->length, for the last line).
+ *
+ * If @lines contains lines with different backing data (i.e.
+ * more than one line with line->start_index of zero), then
+ * *@line must be specified to disambiguate. If all lines
+ * are backed by the same data, it is save to pass `NULL`
+ * as *@line and use this function to find the line at @idx.
+ */
+static void
+pango_lines_index_to_line (PangoLines *lines,
+ int idx,
+ PangoLine **line,
+ int *line_no,
+ int *x_offset,
+ int *y_offset)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Line *l = &g_array_index (lines->lines, Line, i);
+
+ if ((*line == l->line || *line == NULL) &&
+ ((l->line->start_index <= idx && idx < l->line->start_index + l->line->length) ||
+ (i == lines->lines->len - 1 && idx == l->line->start_index + l->line->length)))
+ {
+ *line = l->line;
+ if (line_no)
+ *line_no = i;
+ if (x_offset)
+ *x_offset = l->x;
+ if (y_offset)
+ *y_offset = l->y;
+ return;
+ }
+ }
+
+ *line = NULL;
+}
+
+typedef struct {
+ int x;
+ int pos;
+} CursorPos;
+
+static int
+compare_cursor (gconstpointer v1,
+ gconstpointer v2)
+{
+ const CursorPos *c1 = v1;
+ const CursorPos *c2 = v2;
+
+ return c1->x - c2->x;
+}
+
+static void
+pango_line_get_cursors (PangoLine *line,
+ gboolean last_line,
+ gboolean strong,
+ GArray *cursors)
+{
+ const char *start, *end;
+ int start_offset;
+ int j;
+ const char *p;
+ PangoRectangle pos;
+
+ g_assert (g_array_get_element_size (cursors) == sizeof (CursorPos));
+ g_assert (cursors->len == 0);
+
+ start = line->data->text + line->start_index;
+ end = start + line->length;
+ start_offset = line->start_offset;
+
+ if (last_line)
+ end++;
+
+ for (j = start_offset, p = start; p < end; j++, p = g_utf8_next_char (p))
+ {
+ int idx = p - start;
+
+ if (line->data->log_attrs[j].is_cursor_position)
+ {
+ CursorPos cursor;
+
+ pango_line_get_cursor_pos (line, idx,
+ strong ? &pos : NULL,
+ strong ? NULL : &pos);
+
+ cursor.x = pos.x;
+ cursor.pos = idx;
+ g_array_append_val (cursors, cursor);
+ }
+ }
+
+ g_array_sort (cursors, compare_cursor);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango_lines_new:
+ *
+ * Creates an empty `PangoLines` object.
+ *
+ * Returns: a newly allocated `PangoLines`
+ */
+PangoLines *
+pango_lines_new (void)
+{
+ return g_object_new (PANGO_TYPE_LINES, NULL);
+}
+
+/**
+ * pango_lines_get_serial:
+ * @lines: a `PangoLines`
+ *
+ * Returns the current serial number of @lines.
+ *
+ * The serial number is initialized to an small number larger than zero
+ * when a new layout is created and is increased whenever the @lines
+ * object is changed (i.e. more lines are added).
+ *
+ * The serial may wrap, but will never have the value 0. Since it can
+ * wrap, never compare it with "less than", always use "not equals".
+ *
+ * This can be used to automatically detect changes to a `PangoLines`,
+ * and is useful for example to decide whether a layout needs redrawing.
+ *
+ * Return value: The current serial number of @lines
+ */
+guint
+pango_lines_get_serial (PangoLines *lines)
+{
+ return lines->serial;
+}
+
+/**
+ * pango_lines_add_line:
+ * @lines: a `PangoLines`
+ * @line: the `PangoLine` to add
+ * @line_x: X coordinate of the position
+ * @line_y: Y coordinate of the position
+ *
+ * Adds a line to the `PangoLines`.
+ *
+ * The coordinates are the position
+ * at which @line is placed.
+ */
+void
+pango_lines_add_line (PangoLines *lines,
+ PangoLine *line,
+ int x_line,
+ int y_line)
+{
+ Line l;
+
+ l.line = g_object_ref (line);
+ l.x = x_line;
+ l.y = y_line;
+
+ g_array_append_val (lines->lines, l);
+
+ lines->serial++;
+ if (lines->serial == 0)
+ lines->serial++;
+}
+
+/**
+ * pango_lines_get_n_lines:
+ * @lines: a `PangoLines`
+ *
+ * Gets the number of lines in @lines.
+ *
+ * Returns: the number of lines'
+ */
+int
+pango_lines_get_n_lines (PangoLines *lines)
+{
+ return lines->lines->len;
+}
+
+/**
+ * pango_lines_get_line:
+ * @lines: a `PangoLines`
+ * @num: the position of the line to get
+ * @line: (out): return location for the `PangoLine`
+ * @line_x: (out): return location for the X coordinate
+ * @line_y: (out): return location for the Y coordinate
+ *
+ * Gets the @num-th line of @lines.
+ *
+ * Returns: `TRUE` if @line was set, `FALSE` otherwise
+ */
+gboolean
+pango_lines_get_line (PangoLines *lines,
+ int num,
+ PangoLine **line,
+ int *line_x,
+ int *line_y)
+{
+ Line *l;
+
+ if (num >= lines->lines->len)
+ return FALSE;
+
+ l = &g_array_index (lines->lines, Line, num);
+
+ *line = l->line;
+ *line_x = l->x;
+ *line_y = l->y;
+
+ return TRUE;
+}
+
+/* {{{ Extents */
+
+/**
+ * pango_lines_get_extents:
+ * @lines: a `PangoLines` object
+ * @ink_rect: (out) (optional): return location for the ink extents
+ * @logical_rect: (out) (optional): return location for the logical extents
+ *
+ * Computs the extents of @lines.
+ *
+ * Logical extents are usually what you want for positioning things. Note
+ * that the extents may have non-zero x and y. You may want to use those
+ * to offset where you render the layout. Not doing that is a very typical
+ * bug that shows up as right-to-left layouts not being correctly positioned
+ * in a layout with a set width.
+ *
+ * The extents are given in layout coordinates and in Pango units; layout
+ * coordinates begin at the top left corner.
+ */
+void
+pango_lines_get_extents (PangoLines *lines,
+ PangoRectangle *ink_rect,
+ PangoRectangle *logical_rect)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Line *l = &g_array_index (lines->lines, Line, i);
+ PangoRectangle line_ink;
+ PangoRectangle line_logical;
+
+ pango_line_get_extents (l->line, &line_ink, &line_logical);
+ line_ink.x += l->x;
+ line_ink.y += l->y;
+ line_logical.x += l->x;
+ line_logical.y += l->y;
+
+ if (i == 0)
+ {
+ if (ink_rect)
+ *ink_rect = line_ink;
+ if (logical_rect)
+ *logical_rect = line_logical;
+ }
+ else
+ {
+ int new_pos;
+
+ if (ink_rect)
+ {
+ new_pos = MIN (ink_rect->x, line_ink.x);
+ ink_rect->width = MAX (ink_rect->x + ink_rect->width, line_ink.x + line_ink.width) - new_pos;
+ ink_rect->x = new_pos;
+
+ new_pos = MIN (ink_rect->y, line_ink.y);
+ ink_rect->height = MAX (ink_rect->y + ink_rect->height, line_ink.y + line_ink.height) -
new_pos;
+ ink_rect->y = new_pos;
+ }
+ if (logical_rect)
+ {
+ new_pos = MIN (logical_rect->x, line_logical.x);
+ logical_rect->width = MAX (logical_rect->x + logical_rect->width, line_logical.x +
line_logical.width) - new_pos;
+ logical_rect->x = new_pos;
+
+ new_pos = MIN (logical_rect->y, line_logical.y);
+ logical_rect->height = MAX (logical_rect->y + logical_rect->height, line_logical.y +
line_logical.height) - new_pos;
+ logical_rect->y = new_pos;
+ }
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Editing API */
+
+/**
+ * pango_lines_pos_to_line:
+ * @lines: a `PangoLines` object
+ * @x: the X position (in Pango units)
+ * @y: the Y position (in Pango units)
+ * @line: (out): return location for the line
+ * @line_x: (out): return location for the X offset of the line
+ * @line_y: (out): return location for the Y offset of the line
+ *
+ * Finds the line at the given position.
+ *
+ * If either the X or Y positions were not inside the layout, then the
+ * function returns `FALSE`; on an exact hit, it returns `TRUE`.
+
+ * Return value: `TRUE` if the coordinates were inside a line, `FALSE` otherwise
+ */
+gboolean
+pango_lines_pos_to_line (PangoLines *lines,
+ int x,
+ int y,
+ PangoLine **line,
+ int *line_x,
+ int *line_y)
+{
+ g_return_val_if_fail (PANGO_IS_LINES (lines), FALSE);
+ g_return_val_if_fail (line != NULL, FALSE);
+ g_return_val_if_fail (line_x != NULL, FALSE);
+ g_return_val_if_fail (line_y != NULL, FALSE);
+
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Line *l = &g_array_index (lines->lines, Line, i);
+ PangoRectangle ext;
+
+ pango_line_get_extents (l->line, NULL, &ext);
+
+ ext.x += l->x;
+ ext.y += l->y;
+
+ if (ext.x <= x && x <= ext.x + ext.width &&
+ ext.y <= y && y <= ext.y + ext.height)
+ {
+ *line = l->line;
+ *line_x = l->x;
+ *line_y = l->y;
+
+ return TRUE;
+ }
+ }
+
+ *line = NULL;
+ *line_x = 0;
+ *line_y = 0;
+
+ return FALSE;
+}
+
+/**
+ * pango_lines_index_to_pos:
+ * @lines: a `PangoLines` object
+ * @line: (nullable): `PangoLine` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: byte index within @line
+ * @pos: (out): rectangle in which to store the position of the grapheme
+ *
+ * Converts from an index within a `PangoLine` to the
+ * position corresponding to the grapheme at that index.
+ *
+ * The return value is represented as rectangle. Note that `pos->x`
+ * is always the leading edge of the grapheme and `pos->x + pos->width`
+ * the trailing edge of the grapheme. If the directionality of the
+ * grapheme is right-to-left, then `pos->width` will be negative.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ */
+void
+pango_lines_index_to_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *pos)
+{
+ int x_offset, y_offset;
+
+ g_return_if_fail (PANGO_IS_LINES (lines));
+ g_return_if_fail (line == NULL || PANGO_IS_LINE (line));
+ g_return_if_fail (idx >= 0);
+ g_return_if_fail (pos != NULL);
+
+ pango_lines_index_to_line (lines, idx, &line, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ pango_line_index_to_pos (line, idx, pos);
+ pos->x += x_offset;
+ pos->y += y_offset;
+}
+
+/**
+ * pango_lines_pos_to_index:
+ * @lines: a `PangoLines` object
+ * @x: the X offset (in Pango units) from the left edge of the layout
+ * @y: the Y offset (in Pango units) from the top edge of the layout
+ * @line: (out): location to store the found line
+ * @idx: (out): location to store calculated byte index
+ * @trailing: (out): location to store a integer indicating where
+ * in the grapheme the user clicked. It will either be zero, or the
+ * number of characters in the grapheme. 0 represents the leading edge
+ * of the grapheme.
+ *
+ * Converts from X and Y position within lines to the byte index of the
+ * character at that position.
+ *
+ * Return value: %TRUE if the coordinates were inside text, %FALSE otherwise
+ */
+gboolean
+pango_lines_pos_to_index (PangoLines *lines,
+ int x,
+ int y,
+ PangoLine **line,
+ int *idx,
+ int *trailing)
+{
+ int x_offset, y_offset;
+
+ g_return_val_if_fail (PANGO_IS_LINES (lines), FALSE);
+ g_return_val_if_fail (line != NULL, FALSE);
+ g_return_val_if_fail (idx != NULL, FALSE);
+ g_return_val_if_fail (trailing != NULL, FALSE);
+
+ if (!pango_lines_pos_to_line (lines, x, y, line, &x_offset, &y_offset))
+ return FALSE;
+
+ return pango_line_x_to_index (*line, x - x_offset, idx, trailing);
+}
+
+/* }}} */
+/* {{{ Cursor positioning */
+
+/**
+ * pango_lines_get_cursor_pos:
+ * @lines: a `PangoLines` object
+ * @line: (nullable): `PangoLine` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: the byte index of the cursor
+ * @strong_pos: (out) (optional): location to store the strong cursor position
+ * @weak_pos: (out) (optional): location to store the weak cursor position
+ *
+ * Given an index within lines, determines the positions that of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ *
+ * The position of each cursor is stored as a zero-width rectangle
+ * with the height of the run extents.
+ *
+ * <picture>
+ * <source srcset="cursor-positions-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Cursor positions" src="cursor-positions-light.png">
+ * </picture>
+ *
+ * The strong cursor location is the location where characters of the
+ * directionality equal to the base direction of the layout are inserted.
+ * The weak cursor location is the location where characters of the
+ * directionality opposite to the base direction of the layout are inserted.
+ *
+ * The following example shows text with both a strong and a weak cursor.
+ *
+ * <picture>
+ * <source srcset="split-cursor-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Strong and weak cursors" src="split-cursor-light.png">
+ * </picture>
+ *
+ * The strong cursor has a little arrow pointing to the right, the weak
+ * cursor to the left. Typing a 'c' in this situation will insert the
+ * character after the 'b', and typing another Hebrew character, like 'ג',
+ * will insert it at the end.
+ */
+void
+pango_lines_get_cursor_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *strong_pos,
+ PangoRectangle *weak_pos)
+{
+ int x_offset, y_offset;
+
+ g_return_if_fail (PANGO_IS_LINES (lines));
+ g_return_if_fail (line == NULL || PANGO_IS_LINE (line));
+
+ pango_lines_index_to_line (lines, idx, &line, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ pango_line_get_cursor_pos (line, idx, strong_pos, weak_pos);
+
+ if (strong_pos)
+ {
+ strong_pos->x += x_offset;
+ strong_pos->y += y_offset;
+ }
+ if (weak_pos)
+ {
+ weak_pos->x += x_offset;
+ weak_pos->y += y_offset;
+ }
+}
+
+/**
+ * pango_lines_get_caret_pos:
+ * @lines: a `PangoLines` object
+ * @line: (nullable): `PangoLine` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: the byte index of the cursor
+ * @strong_pos: (out) (optional): location to store the strong cursor position
+ * @weak_pos: (out) (optional): location to store the weak cursor position
+ *
+ * Given an index within a layout, determines the positions of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ *
+ * This is a variant of [method@Pango.Layout.get_cursor_pos] that applies
+ * font metric information about caret slope and offset to the positions
+ * it returns.
+ *
+ * <picture>
+ * <source srcset="caret-metrics-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Caret metrics" src="caret-metrics-light.png">
+ * </picture>
+ */
+void
+pango_lines_get_caret_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *strong_pos,
+ PangoRectangle *weak_pos)
+{
+ int x_offset, y_offset;
+
+ g_return_if_fail (PANGO_IS_LINES (lines));
+ g_return_if_fail (line == NULL || PANGO_IS_LINE (line));
+
+ pango_lines_index_to_line (lines, idx, &line, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ pango_line_get_caret_pos (line, idx, strong_pos, weak_pos);
+
+ if (strong_pos)
+ {
+ strong_pos->x += x_offset;
+ strong_pos->y += y_offset;
+ }
+ if (weak_pos)
+ {
+ weak_pos->x += x_offset;
+ weak_pos->y += y_offset;
+ }
+}
+
+/**
+ * pango_lines_move_cursor:
+ * @lines: a `PangoLines` object
+ * @strong: whether the moving cursor is the strong cursor or the
+ * weak cursor. The strong cursor is the cursor corresponding
+ * to text insertion in the base direction for the layout.
+ * @line: (nullable): `PangoLine` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: the byte index of the current cursor position
+ * @trailing: if 0, the cursor was at the leading edge of the
+ * grapheme indicated by @old_index, if > 0, the cursor
+ * was at the trailing edge.
+ * @direction: direction to move cursor. A negative
+ * value indicates motion to the left
+ * @new_line: `PangoLine` wrt to which @new_idx is interpreted
+ * @new_idx: (out): location to store the new cursor byte index
+ * A value of -1 indicates that the cursor has been moved off the
+ * beginning of the layout. A value of %G_MAXINT indicates that
+ * the cursor has been moved off the end of the layout.
+ * @new_trailing: (out): number of characters to move forward from
+ * the location returned for @new_idx to get the position where
+ * the cursor should be displayed. This allows distinguishing the
+ * position at the beginning of one line from the position at the
+ * end of the preceding line. @new_idx is always on the line where
+ * the cursor should be displayed.
+ *
+ * Computes a new cursor position from an old position and a direction.
+ *
+ * If @direction is positive, then the new position will cause the strong
+ * or weak cursor to be displayed one position to right of where it was
+ * with the old cursor position. If @direction is negative, it will be
+ * moved to the left.
+ *
+ * In the presence of bidirectional text, the correspondence between
+ * logical and visual order will depend on the direction of the current
+ * run, and there may be jumps when the cursor is moved off of the end
+ * of a run.
+ *
+ * Motion here is in cursor positions, not in characters, so a single
+ * call to this function may move the cursor over multiple characters
+ * when multiple characters combine to form a single grapheme.
+ */
+void
+pango_lines_move_cursor (PangoLines *lines,
+ gboolean strong,
+ PangoLine *line,
+ int idx,
+ int trailing,
+ int direction,
+ PangoLine **new_line,
+ int *new_idx,
+ int *new_trailing)
+{
+ int line_no;
+ int x_offset, y_offset;
+ GArray *cursors;
+ int n_vis;
+ int vis_pos;
+ int start_offset;
+ gboolean off_start = FALSE;
+ gboolean off_end = FALSE;
+ PangoRectangle pos;
+ int j;
+
+ g_return_if_fail (PANGO_IS_LINES (lines));
+ g_return_if_fail (idx >= 0);
+ g_return_if_fail (trailing >= 0);
+ g_return_if_fail (new_line != NULL);
+ g_return_if_fail (new_idx != NULL);
+ g_return_if_fail (new_trailing != NULL);
+
+ direction = (direction >= 0 ? 1 : -1);
+
+ pango_lines_index_to_line (lines, idx, &line, &line_no, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ while (trailing--)
+ idx = g_utf8_next_char (line->data->text + idx) - line->data->text;
+
+ /* Clamp old_index to fit on the line */
+ if (idx > (line->start_index + line->length))
+ idx = line->start_index + line->length;
+
+ cursors = g_array_new (FALSE, FALSE, sizeof (CursorPos));
+ pango_line_get_cursors (line, line_no == lines->lines->len - 1, strong, cursors);
+
+ pango_lines_get_cursor_pos (lines, line, idx, strong ? &pos : NULL, strong ? NULL : &pos);
+
+ vis_pos = -1;
+ for (j = 0; j < cursors->len; j++)
+ {
+ CursorPos *cursor = &g_array_index (cursors, CursorPos, j);
+ if (cursor->x == pos.x)
+ {
+ vis_pos = j;
+
+ /* If moving left, we pick the leftmost match, otherwise
+ * the rightmost one. Without this, we can get stuck
+ */
+ if (direction < 0)
+ break;
+ }
+ }
+
+ if (vis_pos == -1 &&
+ idx == line->start_index + line->length)
+ {
+ if (line->direction == PANGO_DIRECTION_LTR)
+ vis_pos = cursors->len;
+ else
+ vis_pos = 0;
+ }
+
+ /* Handling movement between lines */
+ if (line->direction == PANGO_DIRECTION_LTR)
+ {
+ if (idx == line->start_index && direction < 0)
+ off_start = TRUE;
+ if (idx == line->start_index + line->length && direction > 0)
+ off_end = TRUE;
+ }
+ else
+ {
+ if (idx == line->start_index + line->length && direction < 0)
+ off_start = TRUE;
+ if (idx == line->start_index && direction > 0)
+ off_end = TRUE;
+ }
+ if (off_start || off_end)
+ {
+ /* If we move over a paragraph boundary, count that as
+ * an extra position in the motion
+ */
+ gboolean paragraph_boundary;
+
+ if (off_start)
+ {
+ PangoLine *prev_line;
+
+ if (!pango_lines_get_line (lines, line_no - 1, &prev_line, &x_offset, &y_offset))
+ {
+ *new_idx = -1;
+ *new_trailing = 0;
+ g_array_unref (cursors);
+ return;
+ }
+ line = prev_line;
+ line_no--;
+ paragraph_boundary = (line->start_index + line->length != idx);
+ }
+ else
+ {
+ PangoLine *next_line;
+
+ if (!pango_lines_get_line (lines, line_no + 1, &next_line, &x_offset, &y_offset))
+ {
+ *new_idx = G_MAXINT;
+ *new_trailing = 0;
+ g_array_unref (cursors);
+ return;
+ }
+ line = next_line;
+ line_no++;
+ paragraph_boundary = (line->start_index != idx);
+ }
+
+ g_array_set_size (cursors, 0);
+ pango_line_get_cursors (line, line_no == lines->lines->len - 1, strong, cursors);
+
+ n_vis = cursors->len;
+
+ if (off_start && direction < 0)
+ {
+ vis_pos = n_vis;
+ if (paragraph_boundary)
+ vis_pos++;
+ }
+ else if (off_end && direction > 0)
+ {
+ vis_pos = 0;
+ if (paragraph_boundary)
+ vis_pos--;
+ }
+ }
+
+ if (direction < 0)
+ vis_pos--;
+ else
+ vis_pos++;
+
+ if (0 <= vis_pos && vis_pos < cursors->len)
+ *new_idx = g_array_index (cursors, CursorPos, vis_pos).pos;
+ else if (vis_pos >= cursors->len - 1)
+ *new_idx = line->start_index + line->length;
+
+ *new_trailing = 0;
+
+ if (*new_idx == line->start_index + line->length && line->length > 0)
+ {
+ int log_pos;
+
+ start_offset = g_utf8_pointer_to_offset (line->data->text, line->data->text + line->start_index);
+ log_pos = start_offset + g_utf8_strlen (line->data->text + line->start_index, line->length);
+ do
+ {
+ log_pos--;
+ *new_idx = g_utf8_prev_char (line->data->text + *new_idx) - line->data->text;
+ (*new_trailing)++;
+ }
+ while (log_pos > start_offset && !line->data->log_attrs[log_pos].is_cursor_position);
+ }
+
+ g_array_unref (cursors);
+}
+
+/* }}} */
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango/pango-lines.h b/pango/pango-lines.h
new file mode 100644
index 00000000..3a7553fb
--- /dev/null
+++ b/pango/pango-lines.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango/pango-types.h>
+#include <pango/pango-line.h>
+
+G_BEGIN_DECLS
+
+#define PANGO_TYPE_LINES pango_lines_get_type ()
+
+PANGO_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (PangoLines, pango_lines, PANGO, LINES, GObject);
+
+PANGO_AVAILABLE_IN_ALL
+PangoLines * pango_lines_new (void);
+
+PANGO_AVAILABLE_IN_ALL
+guint pango_lines_get_serial (PangoLines *lines);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_add_line (PangoLines *lines,
+ PangoLine *line,
+ int line_x,
+ int line_y);
+
+PANGO_AVAILABLE_IN_ALL
+int pango_lines_get_n_lines (PangoLines *lines);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean pango_lines_get_line (PangoLines *lines,
+ int num,
+ PangoLine **line,
+ int *line_x,
+ int *line_y);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_get_extents (PangoLines *lines,
+ PangoRectangle *ink_rect,
+ PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean pango_lines_pos_to_line (PangoLines *lines,
+ int x,
+ int y,
+ PangoLine **line,
+ int *line_x,
+ int *line_y);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_index_to_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *pos);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean pango_lines_pos_to_index (PangoLines *lines,
+ int x,
+ int y,
+ PangoLine **line,
+ int *idx,
+ int *trailing);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_get_cursor_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *strong_pos,
+ PangoRectangle *weak_pos);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_get_caret_pos (PangoLines *lines,
+ PangoLine *line,
+ int idx,
+ PangoRectangle *strong_pos,
+ PangoRectangle *weak_pos);
+
+PANGO_AVAILABLE_IN_ALL
+void pango_lines_move_cursor (PangoLines *lines,
+ gboolean strong,
+ PangoLine *line,
+ int idx,
+ int trailing,
+ int direction,
+ PangoLine **new_line,
+ int *new_idx,
+ int *new_trailing);
+
+PANGO_AVAILABLE_IN_ALL
+GBytes * pango_lines_serialize (PangoLines *lines);
+
+G_END_DECLS
diff --git a/pango/pango.h b/pango/pango.h
index 6c8ef805..9890d57f 100644
--- a/pango/pango.h
+++ b/pango/pango.h
@@ -44,6 +44,7 @@
#include <pango/pango-layout.h>
#include <pango/pango-line.h>
#include <pango/pango-line-breaker.h>
+#include <pango/pango-lines.h>
#include <pango/pango-matrix.h>
#include <pango/pango-markup.h>
#include <pango/pango-renderer.h>
diff --git a/tests/meson.build b/tests/meson.build
index 83732b5a..ba71b2d6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -62,6 +62,7 @@ if cairo_dep.found()
if host_system != 'darwin'
tests += [
[ 'test-layout', [ 'test-layout.c', 'test-common.c' ], [ libpangocairo_dep, libpangoft2_dep ] ],
+ [ 'test-simple-layout', [ 'test-simple-layout.c', 'test-common.c' ], [ libpangocairo_dep,
libpangoft2_dep ] ],
]
endif
endif
diff --git a/tests/test-simple-layout.c b/tests/test-simple-layout.c
new file mode 100644
index 00000000..b7f5e00d
--- /dev/null
+++ b/tests/test-simple-layout.c
@@ -0,0 +1,228 @@
+/* Pango
+ * test-layout.c: Test Pango Simple Layout
+ *
+ * Copyright (C) 2022 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 <glib.h>
+#include <string.h>
+#include <locale.h>
+
+#ifndef G_OS_WIN32
+#include <unistd.h>
+#endif
+
+#include "config.h"
+#include <pango/pangocairo.h>
+#include <pango/pangocairo-fc.h>
+#include <pango/pangofc-fontmap.h>
+#include "test-common.h"
+
+
+static void
+test_layout (gconstpointer d)
+{
+ const char *filename = d;
+ GError *error = NULL;
+ char *diff;
+ GBytes *bytes;
+ char *contents;
+ gsize length;
+ GBytes *orig;
+ PangoContext *context;
+ PangoSimpleLayout *layout;
+
+ if (!PANGO_IS_FC_FONT_MAP (pango_cairo_font_map_get_default ()))
+ {
+ g_test_skip ("Not an fc fontmap. Skipping...");
+ return;
+ }
+
+ char *old_locale = g_strdup (setlocale (LC_ALL, NULL));
+ setlocale (LC_ALL, "en_US.UTF-8");
+ if (strstr (setlocale (LC_ALL, NULL), "en_US") == NULL)
+ {
+ char *msg = g_strdup_printf ("Locale en_US.UTF-8 not available, skipping layout %s", filename);
+ g_test_skip (msg);
+ g_free (msg);
+ g_free (old_locale);
+ return;
+ }
+
+ g_file_get_contents (filename, &contents, &length, &error);
+ g_assert_no_error (error);
+ orig = g_bytes_new_take (contents, length);
+
+ context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+ layout = pango_simple_layout_deserialize (context, orig, PANGO_SIMPLE_LAYOUT_DESERIALIZE_CONTEXT, &error);
+ g_assert_no_error (error);
+
+ bytes = pango_simple_layout_serialize (layout, PANGO_SIMPLE_LAYOUT_SERIALIZE_CONTEXT |
+ PANGO_SIMPLE_LAYOUT_SERIALIZE_OUTPUT);
+
+ g_object_unref (layout);
+ g_object_unref (context);
+
+ diff = diff_bytes (orig, bytes, &error);
+ g_assert_no_error (error);
+
+ g_bytes_unref (bytes);
+ g_bytes_unref (orig);
+
+ setlocale (LC_ALL, old_locale);
+ g_free (old_locale);
+
+ if (diff && diff[0])
+ {
+ char **lines = g_strsplit (diff, "\n", -1);
+ const char *line;
+ int i = 0;
+
+ g_test_message ("Contents don't match expected contents");
+
+ for (line = lines[0]; line != NULL; line = lines[++i])
+ g_test_message ("%s", line);
+
+ g_test_fail ();
+ g_strfreev (lines);
+ }
+
+ g_free (diff);
+}
+
+static void
+install_fonts (const char *dir)
+{
+ FcConfig *config;
+ PangoFontMap *map;
+ char *path;
+ gsize len;
+ char *conf;
+
+ map = g_object_new (PANGO_TYPE_CAIRO_FC_FONT_MAP, NULL);
+
+ config = FcConfigCreate ();
+
+ path = g_build_filename (dir, "fonts.conf", NULL);
+ g_file_get_contents (path, &conf, &len, NULL);
+
+ if (!FcConfigParseAndLoadFromMemory (config, (const FcChar8 *) conf, TRUE))
+ g_error ("Failed to parse fontconfig configuration");
+
+ g_free (conf);
+ g_free (path);
+
+ FcConfigAppFontAddDir (config, (const FcChar8 *) dir);
+ pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (map), config);
+ FcConfigDestroy (config);
+
+ pango_cairo_font_map_set_default (PANGO_CAIRO_FONT_MAP (map));
+
+ g_object_unref (map);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GDir *dir;
+ GError *error = NULL;
+ char *opt_fonts = NULL;
+ const gchar *name;
+ char *path;
+ GOptionContext *option_context;
+ GOptionEntry entries[] = {
+ { "fonts", 0, 0, G_OPTION_ARG_FILENAME, &opt_fonts, "Fonts to use", "DIR" },
+ { NULL, 0 },
+ };
+
+ setlocale (LC_ALL, "");
+ option_context = g_option_context_new ("");
+ g_option_context_add_main_entries (option_context, entries, NULL);
+ g_option_context_set_ignore_unknown_options (option_context, TRUE);
+ if (!g_option_context_parse (option_context, &argc, &argv, &error))
+ {
+ g_error ("failed to parse options: %s", error->message);
+ return 1;
+ }
+ g_option_context_free (option_context);
+
+ if (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] != '-')
+ {
+ char *contents;
+ gsize length;
+ GError *error = NULL;
+ GBytes *orig;
+ GBytes *bytes;
+ PangoContext *context;
+ PangoSimpleLayout *layout;
+
+ g_file_get_contents (argv[1], &contents, &length, &error);
+ g_assert_no_error (error);
+ orig = g_bytes_new_take (contents, length);
+ context = pango_font_map_create_context (pango_cairo_font_map_get_default ());
+ layout = pango_simple_layout_deserialize (context, orig, PANGO_LAYOUT_DESERIALIZE_CONTEXT, &error);
+ g_assert_no_error (error);
+
+ bytes = pango_simple_layout_serialize (layout, PANGO_SIMPLE_LAYOUT_SERIALIZE_CONTEXT |
+ PANGO_SIMPLE_LAYOUT_SERIALIZE_OUTPUT);
+
+ g_object_unref (layout);
+ g_object_unref (context);
+
+ g_print ("%s", (const char *)g_bytes_get_data (bytes, NULL));
+
+ g_bytes_unref (bytes);
+ g_bytes_unref (orig);
+
+ return 0;
+ }
+
+ g_test_init (&argc, &argv, NULL);
+
+ if (!opt_fonts)
+ {
+ path = g_test_build_filename (G_TEST_DIST, "fonts", NULL);
+ install_fonts (path);
+ g_free (path);
+ }
+
+ path = g_test_build_filename (G_TEST_DIST, "layouts", NULL);
+ dir = g_dir_open (path, 0, &error);
+ g_free (path);
+ g_assert_no_error (error);
+ while ((name = g_dir_read_name (dir)) != NULL)
+ {
+ if (!g_str_has_suffix (name, ".layout"))
+ continue;
+
+ path = g_strdup_printf ("/layout/%s", name);
+ g_test_add_data_func_full (path, g_test_build_filename (G_TEST_DIST, "layouts", name, NULL),
+ test_layout, g_free);
+ g_free (path);
+ }
+ g_dir_close (dir);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]