diff options
author | Matthias Clasen <mclasen@redhat.com> | 2022-01-20 23:59:44 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2022-01-25 15:29:16 -0500 |
commit | 45762d6b50b29447ee97c759c7a7cc55cb534703 (patch) | |
tree | b3f45c39f4a9d68f03130dd8527f51fc50ef719a /pango/pango-layout-iter.c | |
parent | 33eb81450f8572762a5b805cf0bb4740c86f45cf (diff) | |
download | pango-45762d6b50b29447ee97c759c7a7cc55cb534703.tar.gz |
The big rename of doom
simple layout -> layout
line iter -> layout iter
line -> layout line
This commit replaces the old PangoLayout implementation
with PangoSimpleLayout, and does all the necessary
cleanups.
Diffstat (limited to 'pango/pango-layout-iter.c')
-rw-r--r-- | pango/pango-layout-iter.c | 871 |
1 files changed, 871 insertions, 0 deletions
diff --git a/pango/pango-layout-iter.c b/pango/pango-layout-iter.c new file mode 100644 index 00000000..91d37b46 --- /dev/null +++ b/pango/pango-layout-iter.c @@ -0,0 +1,871 @@ +#include "config.h" + +#include "pango-layout-iter-private.h" +#include "pango-lines-private.h" +#include "pango-layout-run-private.h" +#include "pango-layout-line-private.h" +#include "pango-layout-run-private.h" + +/* {{{ PangoLayoutIter implementation */ + +struct _PangoLayoutIter +{ + PangoLines *lines; + guint serial; + + int line_no; + int line_x; + int line_y; + PangoLayoutLine *line; + GSList *run_link; + PangoLayoutRun *run; + int index; + + /* run handling */ + int run_x; + int run_width; + int end_x_offset; + gboolean ltr; + + /* cluster handling */ + int cluster_x; + int cluster_width; + int cluster_start; + int next_cluster_glyph; + int cluster_num_chars; + + int character_position; +}; + +G_DEFINE_BOXED_TYPE (PangoLayoutIter, pango_layout_iter, + pango_layout_iter_copy, pango_layout_iter_free); + + +/* }}} */ +/* {{{ Utilities */ + +#define ITER_IS_VALID(iter) ((iter)->serial == (iter)->lines->serial) + +static gboolean +line_is_terminated (PangoLayoutIter *iter) +{ + if (iter->line_no + 1 < pango_lines_get_line_count (iter->lines)) + return pango_layout_line_ends_paragraph (iter->line); + + return FALSE; + +} + +static int +next_cluster_start (PangoGlyphString *glyphs, + int cluster_start) +{ + int i; + + i = cluster_start + 1; + while (i < glyphs->num_glyphs) + { + if (glyphs->glyphs[i].attr.is_cluster_start) + return i; + + i++; + } + + return glyphs->num_glyphs; +} + +static int +cluster_width (PangoGlyphString *glyphs, + int cluster_start) +{ + int i; + int width; + + width = glyphs->glyphs[cluster_start].geometry.width; + i = cluster_start + 1; + while (i < glyphs->num_glyphs) + { + if (glyphs->glyphs[i].attr.is_cluster_start) + break; + + width += glyphs->glyphs[i].geometry.width; + i++; + } + + return width; +} + +/* Sets up the iter for the start of a new cluster. cluster_start_index + * is the byte index of the cluster start relative to the run. + */ +static void +update_cluster (PangoLayoutIter *iter, + int cluster_start_index) +{ + PangoGlyphItem *glyph_item; + char *cluster_text; + int cluster_length; + + glyph_item = pango_layout_run_get_glyph_item (iter->run); + + iter->character_position = 0; + + iter->cluster_width = cluster_width (glyph_item->glyphs, iter->cluster_start); + iter->next_cluster_glyph = next_cluster_start (glyph_item->glyphs, iter->cluster_start); + + if (iter->ltr) + { + /* For LTR text, finding the length of the cluster is easy + * since logical and visual runs are in the same direction. + */ + if (iter->next_cluster_glyph < glyph_item->glyphs->num_glyphs) + cluster_length = glyph_item->glyphs->log_clusters[iter->next_cluster_glyph] - cluster_start_index; + else + cluster_length = glyph_item->item->length - cluster_start_index; + } + else + { + /* For RTL text, we have to scan backwards to find the previous + * visual cluster which is the next logical cluster. + */ + int i = iter->cluster_start; + while (i > 0 && glyph_item->glyphs->log_clusters[i - 1] == cluster_start_index) + i--; + + if (i == 0) + cluster_length = glyph_item->item->length - cluster_start_index; + else + cluster_length = glyph_item->glyphs->log_clusters[i - 1] - cluster_start_index; + } + + cluster_text = iter->line->data->text + glyph_item->item->offset + cluster_start_index; + iter->cluster_num_chars = g_utf8_strlen (cluster_text, cluster_length); + + if (iter->ltr) + iter->index = cluster_text - iter->line->data->text; + else + iter->index = g_utf8_prev_char (cluster_text + cluster_length) - iter->line->data->text; +} + +/* Moves to the next non-empty line. If @include_terminators + * is set, a line with just an explicit paragraph separator + * is considered non-empty. + */ +static gboolean +next_nonempty_line (PangoLayoutIter *iter, + gboolean include_terminators) +{ + gboolean result; + + while (TRUE) + { + result = pango_layout_iter_next_line (iter); + if (!result) + break; + + if (iter->line->runs) + break; + + if (include_terminators && line_is_terminated (iter)) + break; + } + + return result; +} + +/* Moves to the next non-empty run. If @include_terminators + * is set, the trailing run at the end of a line with an explicit + * paragraph separator is considered non-empty. + */ +static gboolean +next_nonempty_run (PangoLayoutIter *iter, + gboolean include_terminators) +{ + gboolean result; + + while (TRUE) + { + result = pango_layout_iter_next_run (iter); + if (!result) + break; + + if (iter->run) + break; + + if (include_terminators && line_is_terminated (iter)) + break; + } + + return result; +} + +/* Like pango_layout_next_cluster(), but if @include_terminators + * is set, includes the fake runs/clusters for empty lines. + * (But not positions introduced by line wrapping). + */ +static gboolean +next_cluster_internal (PangoLayoutIter *iter, + gboolean include_terminators) +{ + PangoGlyphItem *glyph_item; + + if (iter->run == NULL) + return next_nonempty_line (iter, include_terminators); + + glyph_item = pango_layout_run_get_glyph_item (iter->run); + + if (iter->next_cluster_glyph == glyph_item->glyphs->num_glyphs) + { + return next_nonempty_run (iter, include_terminators); + } + else + { + iter->cluster_start = iter->next_cluster_glyph; + iter->cluster_x += iter->cluster_width; + update_cluster (iter, glyph_item->glyphs->log_clusters[iter->cluster_start]); + + return TRUE; + } +} + +static void +update_run (PangoLayoutIter *iter, + int start_index) +{ + PangoGlyphItem *glyph_item; + + if (iter->run) + glyph_item = pango_layout_run_get_glyph_item (iter->run); + + if (iter->run_link == iter->line->runs) + iter->run_x = 0; + else + { + iter->run_x += iter->end_x_offset + iter->run_width; + if (iter->run) + iter->run_x += glyph_item->start_x_offset; + } + + if (iter->run) + { + iter->run_width = pango_glyph_string_get_width (glyph_item->glyphs); + iter->end_x_offset = glyph_item->end_x_offset; + } + else + { + /* The empty run at the end of a line */ + iter->run_width = 0; + iter->end_x_offset = 0; + } + + if (iter->run) + iter->ltr = (glyph_item->item->analysis.level % 2) == 0; + else + iter->ltr = TRUE; + + iter->cluster_start = 0; + iter->cluster_x = iter->run_x; + + if (iter->run) + { + update_cluster (iter, glyph_item->glyphs->log_clusters[0]); + } + else + { + iter->cluster_width = 0; + iter->character_position = 0; + iter->cluster_num_chars = 0; + iter->index = start_index; + } +} + +static inline void +offset_line (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + if (ink_rect) + { + ink_rect->x += iter->line_x; + ink_rect->y += iter->line_y; + } + if (logical_rect) + { + logical_rect->x += iter->line_x; + logical_rect->y += iter->line_y; + } +} + +static inline void +offset_run (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + if (ink_rect) + ink_rect->x += iter->run_x; + if (logical_rect) + logical_rect->x += iter->run_x; +} + +/* }}} */ +/* {{{ Private API */ + +PangoLayoutIter * +pango_layout_iter_new (PangoLines *lines) +{ + PangoLayoutIter *iter; + int run_start_index; + + g_return_val_if_fail (PANGO_IS_LINES (lines), NULL); + + iter = g_new0 (PangoLayoutIter, 1); + + iter->lines = g_object_ref (lines); + iter->serial = pango_lines_get_serial (lines); + + iter->line_no = 0; + iter->line = pango_lines_get_line (iter->lines, 0, &iter->line_x, &iter->line_y); + iter->run_link = pango_layout_line_get_runs (iter->line); + if (iter->run_link) + { + iter->run = iter->run_link->data; + run_start_index = pango_layout_run_get_glyph_item (iter->run)->item->offset; + } + else + { + iter->run = NULL; + run_start_index = 0; + } + + update_run (iter, run_start_index); + + return iter; +} + +/* }}} */ +/* {{{ Public API */ + +/** + * pango_layout_iter_copy: + * @iter: (nullable): a `PangoLayoutIter` + * + * Copies a `PangoLayoutIter`. + * + * Return value: (nullable): the newly allocated `PangoLayoutIter` + */ +PangoLayoutIter * +pango_layout_iter_copy (PangoLayoutIter *iter) +{ + PangoLayoutIter *copy; + + if (iter == NULL) + return NULL; + + copy = g_new0 (PangoLayoutIter, 1); + memcpy (iter, copy, sizeof (PangoLayoutIter)); + g_object_ref (copy->lines); + + return copy; +} + +/** + * pango_layout_iter_free: + * @iter: (nullable): a `PangoLayoutIter` + * + * Frees an iterator that's no longer in use. + */ +void +pango_layout_iter_free (PangoLayoutIter *iter) +{ + if (iter == NULL) + return; + + g_object_unref (iter->lines); + g_free (iter); +} + +/** + * pango_layout_iter_get_lines: + * @iter: a `PangoLayoutIter` + * + * Gets the `PangoLines` object associated with a `PangoLayoutIter`. + * + * Return value: (transfer none): the lines associated with @iter + */ +PangoLines * +pango_layout_iter_get_lines (PangoLayoutIter *iter) +{ + return iter->lines; +} + +/** + * pango_layout_iter_get_line: + * @iter: a `PangoLayoutIter` + * + * Gets the current line. + * + * Return value: (transfer none): the current line + */ +PangoLayoutLine * +pango_layout_iter_get_line (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), NULL); + + return iter->line; +} + +/** + * pango_layout_iter_at_last_line: + * @iter: a `PangoLayoutIter` + * + * Determines whether @iter is on the last line. + * + * Return value: %TRUE if @iter is on the last line + */ +gboolean +pango_layout_iter_at_last_line (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), FALSE); + + return iter->line_no + 1 == pango_lines_get_line_count (iter->lines); +} + +/** + * pango_layout_iter_get_run: + * @iter: a `PangoLayoutIter` + * + * Gets the current run. + * + * When iterating by run, at the end of each line, there's a position + * with a %NULL run, so this function can return %NULL. The %NULL run + * at the end of each line ensures that all lines have at least one run, + * even lines consisting of only a newline. + * + * Return value: (transfer none) (nullable): the current run + */ +PangoLayoutRun * +pango_layout_iter_get_run (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), NULL); + + return iter->run; +} + +/** + * pango_layout_iter_get_index: + * @iter: a `PangoLayoutIter` + * + * Gets the current byte index. + * + * The byte index is relative to the text backing the current + * line. + * + * Note that iterating forward by char moves in visual order, + * not logical order, so indexes may not be sequential. Also, + * the index may be equal to the length of the text in the + * layout, if on the %NULL run (see [method@Pango.LayoutIter.get_run]). + * + * Return value: current byte index + */ +int +pango_layout_iter_get_index (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), 0); + + return iter->index; +} + +/** + * pango_layout_iter_next_line: + * @iter: a `PangoLayoutIter` + * + * Moves @iter forward to the start of the next line. + * + * If @iter is already on the last line, returns %FALSE. + * + * Return value: whether motion was possible + */ +gboolean +pango_layout_iter_next_line (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), FALSE); + + iter->line = pango_lines_get_line (iter->lines, iter->line_no + 1, &iter->line_x, &iter->line_y); + if (!iter->line) + return FALSE; + + iter->line_no++; + iter->run_link = pango_layout_line_get_runs (iter->line); + if (iter->run_link) + iter->run = iter->run_link->data; + else + iter->run = NULL; + + update_run (iter, iter->line->start_index); + + return TRUE; +} + +/** + * pango_layout_iter_next_run: + * @iter: a `PangoLayoutIter` + * + * Moves @iter forward to the next run in visual order. + * + * If @iter was already at the end, returns %FALSE. + * + * Return value: whether motion was possible + */ +gboolean +pango_layout_iter_next_run (PangoLayoutIter *iter) +{ + int run_start_index; + + g_return_val_if_fail (ITER_IS_VALID (iter), FALSE); + + if (iter->run == NULL) + return pango_layout_iter_next_line (iter); + + iter->run_link = iter->run_link->next; + if (iter->run_link == NULL) + { + PangoItem *item = pango_layout_run_get_glyph_item (iter->run)->item; + run_start_index = item->offset + item->length; + iter->run = NULL; + } + else + { + iter->run = iter->run_link->data; + run_start_index = pango_layout_run_get_glyph_item (iter->run)->item->offset; + } + + update_run (iter, run_start_index); + + return TRUE; +} + +/** + * pango_layout_iter_next_cluster: + * @iter: a `PangoLayoutIter` + * + * Moves @iter forward to the next cluster in visual order. + * + * If @iter was already at the end, returns %FALSE. + * + * Return value: whether motion was possible + */ +gboolean +pango_layout_iter_next_cluster (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), FALSE); + + return next_cluster_internal (iter, FALSE); +} + +/** + * pango_layout_iter_next_char: + * @iter: a `PangoLayoutIter` + * + * Moves @iter forward to the next character in visual order. + * + * If @iter was already at the end, returns %FALSE. + * + * Return value: whether motion was possible + */ +gboolean +pango_layout_iter_next_char (PangoLayoutIter *iter) +{ + const char *text; + + g_return_val_if_fail (ITER_IS_VALID (iter), FALSE); + + if (iter->run == NULL) + { + /* We need to fake an iterator position in the middle of a \r\n line terminator */ + if (line_is_terminated (iter) && + strncmp (iter->line->data->text + iter->line->start_index + iter->line->length, "\r\n", 2) == 0 && + iter->character_position == 0) + { + iter->character_position++; + + return TRUE; + } + + return next_nonempty_line (iter, TRUE); + } + + iter->character_position++; + + if (iter->character_position >= iter->cluster_num_chars) + return next_cluster_internal (iter, TRUE); + + text = iter->line->data->text; + if (iter->ltr) + iter->index = g_utf8_next_char (text + iter->index) - text; + else + iter->index = g_utf8_prev_char (text + iter->index) - text; + + return TRUE; +} + +/** + * pango_layout_iter_get_layout_extents: + * @iter: a `PangoLayoutIter` + * @ink_rect: (out) (optional): rectangle to fill with ink extents + * @logical_rect: (out) (optional): rectangle to fill with logical extents + * + * Obtains the extents of the `PangoLines` being iterated over. + */ +void +pango_layout_iter_get_layout_extents (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + g_return_if_fail (ITER_IS_VALID (iter)); + + pango_lines_get_extents (iter->lines, ink_rect, logical_rect); +} + +/** + * pango_layout_iter_get_line_extents: + * @iter: a `PangoLayoutIter` + * @ink_rect: (out) (optional): rectangle to fill with ink extents + * @logical_rect: (out) (optional): rectangle to fill with logical extents + * + * Obtains the extents of the current line. + * + * Extents are in layout coordinates (origin is the top-left corner of the + * entire `PangoLines`). Thus the extents returned by this function will be + * the same width/height but not at the same x/y as the extents returned + * from [method@Pango.LayoutLine.get_extents]. + * + * The logical extents returned by this function always have their leading + * trimmed according to paragraph boundaries: if the line starts a paragraph, + * it has its start leading trimmed; if it ends a paragraph, it has its end + * leading trimmed. If you need other trimming, use + * [method@Pango.LayoutLine.get_trimmed_extents]. + */ +void +pango_layout_iter_get_line_extents (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + g_return_if_fail (ITER_IS_VALID (iter)); + + pango_layout_line_get_extents (iter->line, ink_rect, logical_rect); + offset_line (iter, ink_rect, logical_rect); +} + +void +pango_layout_iter_get_trimmed_line_extents (PangoLayoutIter *iter, + PangoLeadingTrim trim, + PangoRectangle *logical_rect) +{ + g_return_if_fail (ITER_IS_VALID (iter)); + + pango_layout_line_get_trimmed_extents (iter->line, trim, logical_rect); + offset_line (iter, NULL, logical_rect); +} + +/** + * pango_layout_iter_get_run_extents: + * @iter: a `PangoLayoutIter` + * @ink_rect: (out) (optional): rectangle to fill with ink extents + * @logical_rect: (out) (optional): rectangle to fill with logical extents + * + * Gets the extents of the current run in layout coordinates. + * + * Layout coordinates have the origin at the top left of the entire `PangoLines`. + * + * The logical extents returned by this function always have their leading + * trimmed off. If you need extents that include leading, use + * [method@Pango.LayoutRun.get_extents]. + */ +void +pango_layout_iter_get_run_extents (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + g_return_if_fail (ITER_IS_VALID (iter)); + + if (iter->run) + { + pango_layout_run_get_extents (iter->run, PANGO_LEADING_TRIM_BOTH, ink_rect, logical_rect); + } + else + { + GSList *runs = pango_layout_line_get_runs (iter->line); + if (runs) + { + /* Virtual run at the end of a nonempty line */ + PangoLayoutRun *run = g_slist_last (runs)->data; + + pango_layout_run_get_extents (run, PANGO_LEADING_TRIM_BOTH, ink_rect, logical_rect); + if (ink_rect) + ink_rect->width = 0; + if (logical_rect) + logical_rect->width = 0; + } + else + { + /* Empty line */ + PangoRectangle r; + + pango_layout_line_get_empty_extents (iter->line, PANGO_LEADING_TRIM_BOTH, &r); + + if (ink_rect) + *ink_rect = r; + + if (logical_rect) + *logical_rect = r; + } + } + + offset_line (iter, ink_rect, logical_rect); + offset_run (iter, ink_rect, logical_rect); +} + +/** + * pango_layout_iter_get_cluster_extents: + * @iter: a `PangoLayoutIter` + * @ink_rect: (out) (optional): rectangle to fill with ink extents + * @logical_rect: (out) (optional): rectangle to fill with logical extents + * + * Gets the extents of the current cluster, in layout coordinates. + * + * Layout coordinates have the origin at the top left of the entire `PangoLines`. + */ +void +pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + PangoGlyphItem *glyph_item; + + g_return_if_fail (ITER_IS_VALID (iter)); + + if (iter->run == NULL) + { + /* When on the NULL run, all extents are the same */ + pango_layout_iter_get_run_extents (iter, ink_rect, logical_rect); + return; + } + + glyph_item = pango_layout_run_get_glyph_item (iter->run); + + pango_glyph_string_extents_range (glyph_item->glyphs, + iter->cluster_start, + iter->next_cluster_glyph, + glyph_item->item->analysis.font, + ink_rect, + logical_rect); + + offset_line (iter, ink_rect, logical_rect); + if (ink_rect) + { + ink_rect->x += iter->cluster_x + glyph_item->start_x_offset; + ink_rect->y -= glyph_item->y_offset; + } + + if (logical_rect) + { + g_assert (logical_rect->width == iter->cluster_width); + logical_rect->x += iter->cluster_x + glyph_item->start_x_offset; + logical_rect->y -= glyph_item->y_offset; + } +} + +/** + * pango_layout_iter_get_char_extents: + * @iter: a `PangoLayoutIter` + * @logical_rect: (out caller-allocates): rectangle to fill with logical extents + * + * Gets the extents of the current character, in layout coordinates. + * + * Layout coordinates have the origin at the top left of the entire `PangoLines`. + * + * Only logical extents can sensibly be obtained for characters; + * ink extents make sense only down to the level of clusters. + */ +void +pango_layout_iter_get_char_extents (PangoLayoutIter *iter, + PangoRectangle *logical_rect) +{ + PangoRectangle cluster_rect; + int x0, x1; + + g_return_if_fail (ITER_IS_VALID (iter)); + + if (logical_rect == NULL) + return; + + pango_layout_iter_get_cluster_extents (iter, NULL, &cluster_rect); + + if (iter->run == NULL) + { + /* When on the NULL run, all extents are the same */ + *logical_rect = cluster_rect; + return; + } + + if (iter->cluster_num_chars) + { + x0 = (iter->character_position * cluster_rect.width) / iter->cluster_num_chars; + x1 = ((iter->character_position + 1) * cluster_rect.width) / iter->cluster_num_chars; + } + else + { + x0 = x1 = 0; + } + + logical_rect->width = x1 - x0; + logical_rect->height = cluster_rect.height; + logical_rect->y = cluster_rect.y; + logical_rect->x = cluster_rect.x + x0; +} + +/** + * pango_layout_iter_get_line_baseline: + * @iter: a `PangoLayoutIter` + * + * Gets the Y position of the current line's baseline, in layout + * coordinates. + * + * Layout coordinates have the origin at the top left of the entire `PangoLines`. + * + * Return value: baseline of current line + */ +int +pango_layout_iter_get_line_baseline (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), 0); + + return iter->line_y; +} + +/** + * pango_layout_iter_get_run_baseline: + * @iter: a `PangoLayoutIter` + * + * Gets the Y position of the current run's baseline, in layout + * coordinates. + * + * Layout coordinates have the origin at the top left of the entire `PangoLines`. + * + * The run baseline can be different from the line baseline, for + * example due to superscript or subscript positioning. + */ +int +pango_layout_iter_get_run_baseline (PangoLayoutIter *iter) +{ + g_return_val_if_fail (ITER_IS_VALID (iter), 0); + + if (iter->run) + return pango_layout_iter_get_line_baseline (iter) - pango_layout_run_get_glyph_item (iter->run)->y_offset; + else + return pango_layout_iter_get_line_baseline (iter); +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ |