[pango/pango2: 104/195] Add PangoLineIter




commit 576cd91bb0663877a0127fc1d1e4a56e9bdd990d
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Jan 17 00:08:57 2022 -0500

    Add PangoLineIter

 pango/meson.build               |   2 +
 pango/pango-line-iter-private.h |  25 ++
 pango/pango-line-iter.c         | 882 ++++++++++++++++++++++++++++++++++++++++
 pango/pango-line-iter.h         | 103 +++++
 pango/pango-lines.c             |  21 +
 pango/pango-lines.h             |   5 +
 pango/pango-simple-layout.c     |  21 +
 pango/pango-simple-layout.h     |   3 +
 pango/pango.h                   |   1 +
 9 files changed, 1063 insertions(+)
---
diff --git a/pango/meson.build b/pango/meson.build
index ed86d712..1478bfda 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -37,6 +37,7 @@ pango_sources = [
   'pango-line.c',
   'pango-line-breaker.c',
   'pango-lines.c',
+  'pango-line-iter.c',
   'pango-simple-layout.c',
 ]
 
@@ -64,6 +65,7 @@ pango_headers = [
   'pango-layout-run.h',
   'pango-line.h',
   'pango-line-breaker.h',
+  'pango-line-iter.h',
   'pango-lines.h',
   'pango-layout.h',
   'pango-matrix.h',
diff --git a/pango/pango-line-iter-private.h b/pango/pango-line-iter-private.h
new file mode 100644
index 00000000..f5f4b9a6
--- /dev/null
+++ b/pango/pango-line-iter-private.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-line-iter.h"
+
+
+PangoLineIter * pango_line_iter_new (PangoLines *lines);
diff --git a/pango/pango-line-iter.c b/pango/pango-line-iter.c
new file mode 100644
index 00000000..1a72be31
--- /dev/null
+++ b/pango/pango-line-iter.c
@@ -0,0 +1,882 @@
+#include "config.h"
+
+#include "pango-line-iter-private.h"
+#include "pango-lines-private.h"
+#include "pango-line-private.h"
+#include "pango-layout-run-private.h"
+
+/**
+ * PangoLineIter:
+ *
+ * A `PangoLineIter` can be used to iterate over the visual
+ * extents of a `PangoLayout` or `PangoLines`.
+ *
+ * To obtain a `PangoLineIter`, use [method@Pango.Layout.get_iter]
+ * or [method@Pango.Lines.get_iter].
+ *
+ * The `PangoLineIter` structure is opaque, and has no user-visible
+ * fields.
+ */
+
+/* {{{ PangoLineIter implementation */
+
+struct _PangoLineIter
+{
+  PangoLines *lines;
+  guint serial;
+
+  int line_no;
+  int line_x;
+  int line_y;
+  PangoLine *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 (PangoLineIter, pango_line_iter,
+                     pango_line_iter_copy, pango_line_iter_free);
+
+
+/* }}} */
+/* {{{ Utilities */
+
+#define ITER_IS_VALID(iter) ((iter)->serial == (iter)->lines->serial)
+
+static gboolean
+line_is_terminated (PangoLineIter *iter)
+{
+  if (iter->line_no + 1 < pango_lines_get_line_count (iter->lines))
+    return pango_line_is_paragraph_end (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 (PangoLineIter *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 (PangoLineIter *iter,
+                    gboolean       include_terminators)
+{
+  gboolean result;
+
+  while (TRUE)
+    {
+      result = pango_line_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 (PangoLineIter *iter,
+                   gboolean       include_terminators)
+{
+  gboolean result;
+
+  while (TRUE)
+    {
+      result = pango_line_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 (PangoLineIter *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 (PangoLineIter *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 (PangoLineIter  *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 (PangoLineIter  *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 */
+
+PangoLineIter *
+pango_line_iter_new (PangoLines *lines)
+{
+  PangoLineIter *iter;
+  int run_start_index;
+
+  g_return_val_if_fail (PANGO_IS_LINES (lines), NULL);
+
+  iter = g_new0 (PangoLineIter, 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_line_get_runs (iter->line);
+  if (iter->run_link)
+    {
+      iter->run = iter->run_link->data;
+      run_start_index = iter->run->item->offset;
+    }
+  else
+    {
+      iter->run = NULL;
+      run_start_index = 0;
+    }
+
+  update_run (iter, run_start_index);
+
+  return iter;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango_line_iter_copy:
+ * @iter: (nullable): a `PangoLineIter`
+ *
+ * Copies a `PangoLineIter`.
+ *
+ * Return value: (nullable): the newly allocated `PangoLineIter`
+ */
+PangoLineIter *
+pango_line_iter_copy (PangoLineIter *iter)
+{
+  PangoLineIter *copy;
+
+  if (iter == NULL)
+    return NULL;
+
+  copy = g_new0 (PangoLineIter, 1);
+  memcpy (iter, copy, sizeof (PangoLineIter));
+  g_object_ref (copy->lines);
+
+  return copy;
+}
+
+/**
+ * pango_line_iter_free:
+ * @iter: (nullable): a `PangoLineIter`
+ *
+ * Frees an iterator that's no longer in use.
+ */
+void
+pango_line_iter_free (PangoLineIter *iter)
+{
+  if (iter == NULL)
+    return;
+
+  g_object_unref (iter->lines);
+  g_free (iter);
+}
+
+/**
+ * pango_line_iter_get_lines:
+ * @iter: a `PangoLineIter`
+ *
+ * Gets the `PangoLines` object associated with a `PangoLineIter`.
+ *
+ * Return value: (transfer none): the lines associated with @iter
+ */
+PangoLines *
+pango_line_iter_get_lines (PangoLineIter *iter)
+{
+  return iter->lines;
+}
+
+/**
+ * pango_line_iter_get_line:
+ * @iter: a `PangoLineIter`
+ *
+ * Gets the current line.
+ *
+ * Return value: (transfer none): the current line
+ */
+PangoLine *
+pango_line_iter_get_line (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), NULL);
+
+  return iter->line;
+}
+
+/**
+ * pango_line_iter_at_last_line:
+ * @iter: a `PangoLineIter`
+ *
+ * Determines whether @iter is on the last line.
+ *
+ * Return value: %TRUE if @iter is on the last line
+ */
+gboolean
+pango_line_iter_at_last_line (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+  return iter->line_no + 1 == pango_lines_get_line_count (iter->lines);
+}
+
+/**
+ * pango_line_iter_get_run:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_get_run (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), NULL);
+
+  return iter->run;
+}
+
+/**
+ * pango_line_iter_get_index:
+ * @iter: a `PangoLineIter`
+ *
+ * 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.LineIter.get_run]).
+ *
+ * Return value: current byte index
+ */
+int
+pango_line_iter_get_index (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+  return iter->index;
+}
+
+/**
+ * pango_line_iter_next_line:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_next_line (PangoLineIter *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_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_line_iter_next_run:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_next_run (PangoLineIter *iter)
+{
+  int run_start_index;
+
+  g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+  if (iter->run == NULL)
+    return pango_line_iter_next_line (iter);
+
+  iter->run_link = iter->run_link->next;
+  if (iter->run_link == NULL)
+    {
+      run_start_index = iter->run->item->offset + iter->run->item->length;
+      iter->run = NULL;
+    }
+  else
+    {
+      iter->run = iter->run_link->data;
+      run_start_index = iter->run->item->offset;
+    }
+
+  update_run (iter, run_start_index);
+
+  return TRUE;
+}
+
+/**
+ * pango_line_iter_next_cluster:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_next_cluster (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+  return next_cluster_internal (iter, FALSE);
+}
+
+/**
+ * pango_line_iter_next_char:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_next_char (PangoLineIter *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_line_iter_get_layout_extents:
+ * @iter: a `PangoLineIter`
+ * @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_line_iter_get_layout_extents (PangoLineIter  *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_line_iter_get_line_extents:
+ * @iter: a `PangoLineIter`
+ * @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 Line.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 Line.get_trimmed_extents].
+ */
+void
+pango_line_iter_get_line_extents (PangoLineIter  *iter,
+                                  PangoRectangle *ink_rect,
+                                  PangoRectangle *logical_rect)
+{
+  g_return_if_fail (ITER_IS_VALID (iter));
+
+  pango_line_get_extents (iter->line, ink_rect, logical_rect);
+  offset_line (iter, ink_rect, logical_rect);
+}
+
+void
+pango_line_iter_get_trimmed_line_extents (PangoLineIter    *iter,
+                                          PangoLeadingTrim  trim,
+                                          PangoRectangle   *logical_rect)
+{
+  g_return_if_fail (ITER_IS_VALID (iter));
+
+  pango_line_get_trimmed_extents (iter->line, trim, logical_rect);
+  offset_line (iter, NULL, logical_rect);
+}
+
+/**
+ * pango_line_iter_get_run_extents:
+ * @iter: a `PangoLineIter`
+ * @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_line_iter_get_run_extents (PangoLineIter  *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_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_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_line_iter_get_cluster_extents:
+ * @iter: a `PangoLineIter`
+ * @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_line_iter_get_cluster_extents (PangoLineIter  *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_line_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 -= iter->run->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 -= iter->run->y_offset;
+    }
+}
+
+/**
+ * pango_line_iter_get_char_extents:
+ * @iter: a `PangoLineIter`
+ * @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_line_iter_get_char_extents (PangoLineIter  *iter,
+                                  PangoRectangle *logical_rect)
+{
+  PangoRectangle cluster_rect;
+  int            x0, x1;
+
+  g_return_if_fail (ITER_IS_VALID (iter));
+
+  if (logical_rect == NULL)
+    return;
+
+  pango_line_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_line_iter_get_line_baseline:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_get_line_baseline (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+  return iter->line_y;
+}
+
+/**
+ * pango_line_iter_get_run_baseline:
+ * @iter: a `PangoLineIter`
+ *
+ * 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_line_iter_get_run_baseline (PangoLineIter *iter)
+{
+  g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+  if (iter->run)
+    return pango_line_iter_get_line_baseline (iter) - pango_layout_run_get_glyph_item (iter->run)->y_offset;
+  else
+    return pango_line_iter_get_line_baseline (iter);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango/pango-line-iter.h b/pango/pango-line-iter.h
new file mode 100644
index 00000000..7bdbfb23
--- /dev/null
+++ b/pango/pango-line-iter.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango/pango-types.h>
+#include <pango/pango-lines.h>
+#include <pango/pango-glyph-item.h>
+
+G_BEGIN_DECLS
+
+PANGO_AVAILABLE_IN_ALL
+GType                   pango_line_iter_get_type           (void) G_GNUC_CONST;
+
+PANGO_AVAILABLE_IN_ALL
+PangoLineIter *         pango_line_iter_copy                (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_free                (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+PangoLines *            pango_line_iter_get_lines           (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+PangoLine *             pango_line_iter_get_line            (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean                pango_line_iter_at_last_line        (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+PangoLayoutRun *        pango_line_iter_get_run             (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+int                     pango_line_iter_get_index           (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean                pango_line_iter_next_line           (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean                pango_line_iter_next_run            (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean                pango_line_iter_next_cluster        (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+gboolean                pango_line_iter_next_char           (PangoLineIter *iter);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_layout_extents  (PangoLineIter  *iter,
+                                                             PangoRectangle *ink_rect,
+                                                             PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_line_extents    (PangoLineIter  *iter,
+                                                             PangoRectangle *ink_rect,
+                                                             PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_trimmed_line_extents
+                                                            (PangoLineIter    *iter,
+                                                             PangoLeadingTrim  trim,
+                                                             PangoRectangle   *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_run_extents     (PangoLineIter  *iter,
+                                                             PangoRectangle *ink_rect,
+                                                             PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_cluster_extents (PangoLineIter  *iter,
+                                                             PangoRectangle *ink_rect,
+                                                             PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+void                    pango_line_iter_get_char_extents    (PangoLineIter  *iter,
+                                                             PangoRectangle *logical_rect);
+
+PANGO_AVAILABLE_IN_ALL
+int                     pango_line_iter_get_line_baseline   (PangoLineIter  *iter);
+
+PANGO_AVAILABLE_IN_ALL
+int                     pango_line_iter_get_run_baseline    (PangoLineIter  *iter);
+
+
+G_END_DECLS
diff --git a/pango/pango-lines.c b/pango/pango-lines.c
index 25f045b7..9063301b 100644
--- a/pango/pango-lines.c
+++ b/pango/pango-lines.c
@@ -3,6 +3,7 @@
 #include "pango-lines-private.h"
 #include "pango-line-private.h"
 #include "pango-item-private.h"
+#include "pango-line-iter-private.h"
 
 /**
  * PangoLines:
@@ -229,6 +230,26 @@ pango_lines_add_line (PangoLines *lines,
     lines->serial++;
 }
 
+/**
+ * pango_lines_get_iter:
+ * @lines: a `PangoLines`
+ *
+ * Returns an iterator to iterate over the visual extents of the lines.
+ *
+ * The returned iterator will be invaliated when more
+ * lines are added to @lines, and can't be used anymore
+ * after that point.
+ *
+ * Note that the iter holds a reference to @lines.
+ *
+ * Return value: the new `PangoLineIter`
+ */
+PangoLineIter *
+pango_lines_get_iter (PangoLines *lines)
+{
+  return pango_line_iter_new (lines);
+}
+
 /**
  * pango_lines_get_line_count:
  * @lines: a `PangoLines`
diff --git a/pango/pango-lines.h b/pango/pango-lines.h
index d15802cc..d89e5a73 100644
--- a/pango/pango-lines.h
+++ b/pango/pango-lines.h
@@ -31,6 +31,8 @@ G_BEGIN_DECLS
 PANGO_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (PangoLines, pango_lines, PANGO, LINES, GObject);
 
+typedef struct _PangoLineIter PangoLineIter;
+
 PANGO_AVAILABLE_IN_ALL
 PangoLines *            pango_lines_new             (void);
 
@@ -52,6 +54,9 @@ PangoLine *             pango_lines_get_line        (PangoLines        *lines,
                                                      int               *line_x,
                                                      int               *line_y);
 
+PANGO_AVAILABLE_IN_ALL
+PangoLineIter *         pango_lines_get_iter        (PangoLines        *lines);
+
 PANGO_AVAILABLE_IN_ALL
 void                    pango_lines_get_extents     (PangoLines        *lines,
                                                      PangoRectangle    *ink_rect,
diff --git a/pango/pango-simple-layout.c b/pango/pango-simple-layout.c
index 0faa8e50..1df807e4 100644
--- a/pango/pango-simple-layout.c
+++ b/pango/pango-simple-layout.c
@@ -1528,6 +1528,27 @@ pango_simple_layout_get_log_attrs (PangoSimpleLayout *layout,
   return line->data->log_attrs;
 }
 
+/**
+ * pango_simple_layout_get_iter:
+ * @layout: a `PangoSimpleLayout`
+ *
+ * Returns an iterator to iterate over the visual extents
+ * of the layout.
+ *
+ * This is a convenience wrapper for [method@Pango.Lines.get_iter].
+ *
+ * Returns: the new `PangoLineIter`
+ */
+PangoLineIter *
+pango_simple_layout_get_iter (PangoSimpleLayout *layout)
+{
+  g_return_val_if_fail (PANGO_IS_SIMPLE_LAYOUT (layout), NULL);
+
+  ensure_lines (layout);
+
+  return pango_lines_get_iter (layout->lines);
+}
+
 /* }}} */
 /* }}} */
 
diff --git a/pango/pango-simple-layout.h b/pango/pango-simple-layout.h
index 2a746be7..852b8b19 100644
--- a/pango/pango-simple-layout.h
+++ b/pango/pango-simple-layout.h
@@ -135,6 +135,9 @@ gboolean                pango_simple_layout_get_auto_dir   (PangoSimpleLayout  *
 PANGO_AVAILABLE_IN_ALL
 PangoLines *            pango_simple_layout_get_lines      (PangoSimpleLayout  *layout);
 
+PANGO_AVAILABLE_IN_ALL
+PangoLineIter *         pango_simple_layout_get_iter       (PangoSimpleLayout  *layout);
+
 PANGO_AVAILABLE_IN_ALL
 const PangoLogAttr *    pango_simple_layout_get_log_attrs  (PangoSimpleLayout  *layout,
                                                             int                *n_attrs);
diff --git a/pango/pango.h b/pango/pango.h
index bb48ab30..5727e768 100644
--- a/pango/pango.h
+++ b/pango/pango.h
@@ -47,6 +47,7 @@
 #include <pango/pango-layout-run.h>
 #include <pango/pango-line.h>
 #include <pango/pango-line-breaker.h>
+#include <pango/pango-line-iter.h>
 #include <pango/pango-lines.h>
 #include <pango/pango-matrix.h>
 #include <pango/pango-markup.h>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]