summaryrefslogtreecommitdiff
path: root/pango
diff options
context:
space:
mode:
authorOwen Taylor <otaylor@redhat.com>2005-06-21 22:37:59 +0000
committerOwen Taylor <otaylor@src.gnome.org>2005-06-21 22:37:59 +0000
commit4ce097d475ac26a1b872baaaae925f0a71b4563d (patch)
treee51739791aea42bd5fc9f51cf8910b9598e43f0a /pango
parentb4989509d6a353a2efe4ce2af41cf0f6b0308b37 (diff)
downloadpango-4ce097d475ac26a1b872baaaae925f0a71b4563d.tar.gz
Fix up the operation of PangoLayoutIter, especially for Bidi (#89541,
2005-06-21 Owen Taylor <otaylor@redhat.com> Fix up the operation of PangoLayoutIter, especially for Bidi (#89541, based on a patch from Amit Aronovitch) * pango/pango-layout.c: Many changes to make iteration consistently in visual order. * pango/pango-layout.c (pango_layout_iter_next_char): Iterate through each character in the layout exactly once. (Including a hack to get two iterator positions for \r\n) * pango/pango-layout.c (pango_layout_iter_next_cluster): Only iterate through real clusters: that is, positions in the layout that have glyphs. * tests/testiter.c tests/Makefile.am: Add a (somewhat reworked) test from Amit for the operation of PangoLayoutIter.
Diffstat (limited to 'pango')
-rw-r--r--pango/pango-layout.c416
1 files changed, 243 insertions, 173 deletions
diff --git a/pango/pango-layout.c b/pango/pango-layout.c
index 1398e757..a3a5f840 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -82,19 +82,23 @@ struct _PangoLayoutIter
/* this run is left-to-right */
gboolean ltr;
- /* X position where cluster begins, where "begins" means left or
- * right side according to text direction
- */
+ /* X position of the left side of the current cluster */
int cluster_x;
- /* Byte index of the cluster start from the run start */
- int cluster_index;
-
- /* Glyph offset to the current cluster start */
+ /* The width of the current cluster */
+ int cluster_width;
+
+ /* glyph offset to the current cluster start */
int cluster_start;
- /* The next cluster_start */
- int next_cluster_start;
+ /* first glyph in the next cluster */
+ int next_cluster_glyph;
+
+ /* number of unicode chars in current cluster */
+ int cluster_num_chars;
+
+ /* visual position of current character within the cluster */
+ int character_position;
};
typedef struct _PangoLayoutLinePrivate PangoLayoutLinePrivate;
@@ -4309,31 +4313,29 @@ next_cluster_start (PangoGlyphString *gs,
if (gs->glyphs[i].attr.is_cluster_start)
return i;
- ++i;
+ i++;
}
return gs->num_glyphs;
}
static int
-cluster_end_index (PangoLayoutIter *iter)
+cluster_width (PangoGlyphString *gs,
+ int cluster_start)
{
- PangoGlyphString *gs;
-
- gs = iter->run->glyphs;
-
- if (iter->next_cluster_start == gs->num_glyphs)
- {
- /* Use the left or right end of the run */
- if (iter->ltr)
- return iter->run->item->length;
- else
- return 0;
- }
- else
+ int i;
+ int log_cluster;
+ int width = 0;
+
+ log_cluster = gs->log_clusters[cluster_start];
+ for (i = cluster_start; i < gs->num_glyphs; i++)
{
- return gs->log_clusters[iter->next_cluster_start];
+ if (gs->log_clusters[i] != log_cluster)
+ break;
+ width += gs->glyphs[i].geometry.width;
}
+
+ return width;
}
static inline void
@@ -4347,6 +4349,57 @@ offset_y (PangoLayoutIter *iter,
*y += line_ext->baseline;
}
+/* 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)
+{
+ char *cluster_text;
+ PangoGlyphString *gs;
+ int cluster_length;
+
+ iter->character_position = 0;
+
+ gs = iter->run->glyphs;
+ iter->cluster_width = cluster_width (gs, iter->cluster_start);
+ iter->next_cluster_glyph = next_cluster_start (gs, 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 < gs->num_glyphs)
+ cluster_length = gs->log_clusters[iter->next_cluster_glyph] - cluster_start_index;
+ else
+ cluster_length = iter->run->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 && gs->log_clusters[i - 1] == cluster_start_index)
+ i--;
+
+ if (i == 0)
+ cluster_length = iter->run->item->length - cluster_start_index;
+ else
+ cluster_length = gs->log_clusters[i - 1] - cluster_start_index;
+ }
+
+ cluster_text = iter->layout->text + iter->run->item->offset + cluster_start_index;
+ iter->cluster_num_chars = g_utf8_strlen (cluster_text, cluster_length);
+
+ if (iter->ltr)
+ iter->index = cluster_text - iter->layout->text;
+ else
+ iter->index = g_utf8_prev_char (cluster_text + cluster_length) - iter->layout->text;
+}
+
static void
update_run (PangoLayoutIter *iter,
int run_start_index)
@@ -4370,13 +4423,15 @@ update_run (PangoLayoutIter *iter,
NULL,
&iter->run_logical_rect);
- /* Fix coordinates of the run extents */
+ /* Fix coordinates of the run extents to be layout-relative*/
iter->run_logical_rect.x += iter->run_x;
offset_y (iter, &iter->run_logical_rect.y);
}
else
{
+ /* The empty run at the end of a line */
+
iter->run_logical_rect.x = iter->run_x;
iter->run_logical_rect.y = line_ext->logical_rect.y;
iter->run_logical_rect.width = 0;
@@ -4387,33 +4442,21 @@ update_run (PangoLayoutIter *iter,
iter->ltr = (iter->run->item->analysis.level % 2) == 0;
else
iter->ltr = TRUE;
-
- if (iter->ltr)
- iter->cluster_x = iter->run_logical_rect.x;
- else
- iter->cluster_x = iter->run_logical_rect.x +
- iter->run_logical_rect.width;
-
+
iter->cluster_start = 0;
+ iter->cluster_x = iter->run_logical_rect.x;
if (iter->run)
- iter->next_cluster_start = next_cluster_start (iter->run->glyphs,
- iter->cluster_start);
- else
- iter->next_cluster_start = 0;
-
- /* Index of the first cluster in the glyph string, relative
- * to the start of the run
- */
- if (iter->run)
- iter->cluster_index = iter->run->glyphs->log_clusters[0];
+ {
+ update_cluster (iter, iter->run->glyphs->log_clusters[0]);
+ }
else
- iter->cluster_index = 0;
-
- /* Get an overall index, leaving it unchanged for
- * a the NULL run
- */
- iter->index = run_start_index;
+ {
+ iter->cluster_width = 0;
+ iter->character_position = 0;
+ iter->cluster_num_chars = 0;
+ iter->index = run_start_index;
+ }
}
static PangoLayoutIter *
@@ -4453,9 +4496,13 @@ pango_layout_iter_copy (PangoLayoutIter *iter)
new->ltr = iter->ltr;
new->cluster_x = iter->cluster_x;
- new->cluster_index = iter->cluster_index;
+ new->cluster_width = iter->cluster_x;
+
new->cluster_start = iter->cluster_start;
- new->next_cluster_start = iter->next_cluster_start;
+ new->next_cluster_glyph = iter->next_cluster_glyph;
+
+ new->cluster_num_chars = iter->cluster_num_chars;
+ new->character_position = iter->character_position;
return new;
}
@@ -4484,6 +4531,7 @@ pango_layout_iter_get_type (void)
PangoLayoutIter*
pango_layout_get_iter (PangoLayout *layout)
{
+ int run_start_index;
PangoLayoutIter *iter;
g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
@@ -4499,10 +4547,14 @@ pango_layout_get_iter (PangoLayout *layout)
iter->line = iter->line_list_link->data;
pango_layout_line_ref (iter->line);
+ run_start_index = iter->line->start_index;
iter->run_list_link = iter->line->runs;
if (iter->run_list_link)
- iter->run = iter->run_list_link->data;
+ {
+ iter->run = iter->run_list_link->data;
+ run_start_index = iter->run->item->offset;
+ }
else
iter->run = NULL;
@@ -4514,7 +4566,7 @@ pango_layout_get_iter (PangoLayout *layout)
iter->line_extents_link = iter->line_extents;
- update_run (iter, 0);
+ update_run (iter, run_start_index);
return iter;
}
@@ -4611,6 +4663,108 @@ pango_layout_iter_at_last_line (PangoLayoutIter *iter)
return iter->line_extents_link->next == NULL;
}
+static gboolean
+line_is_terminated (PangoLayoutIter *iter)
+{
+ /* There is a real terminator at the end of each paragraph other
+ * than the last.
+ */
+ if (iter->line_list_link->next)
+ {
+ PangoLayoutLine *next_line = iter->line_list_link->next->data;
+ if (next_line->is_paragraph_start)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Moves to the next non-empty line. If @include_terminators
+ * is set, a line with just an explicit paragraph separator
+ * is considered non-empty.
+ */
+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 line. If @include_terminators
+ * is set, the trailing run at the end of a line with an explicit
+ * paragraph separator is considered non-empty.
+ */
+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).
+ */
+gboolean
+next_cluster_internal (PangoLayoutIter *iter,
+ gboolean include_terminators)
+{
+ PangoGlyphString *gs;
+ int next_start;
+
+ if (IS_INVALID (iter))
+ return FALSE;
+
+ if (iter->run == NULL)
+ return next_nonempty_line (iter, include_terminators);
+
+ gs = iter->run->glyphs;
+
+ next_start = iter->next_cluster_glyph;
+ if (next_start == gs->num_glyphs)
+ {
+ return next_nonempty_run (iter, include_terminators);
+ }
+ else
+ {
+ iter->cluster_start = next_start;
+ iter->cluster_x += iter->cluster_width;
+ update_cluster(iter, gs->log_clusters[iter->cluster_start]);
+
+ return TRUE;
+ }
+}
+
/**
* pango_layout_iter_next_char:
* @iter: a #PangoLayoutIter
@@ -4623,51 +4777,36 @@ pango_layout_iter_at_last_line (PangoLayoutIter *iter)
gboolean
pango_layout_iter_next_char (PangoLayoutIter *iter)
{
- const char *p;
- const char *oldp;
- const char *item_end;
- int new_index;
- int next_cluster_index;
- PangoGlyphString *gs;
-
+ const char *text;
+
if (IS_INVALID (iter))
return FALSE;
if (iter->run == NULL)
- return pango_layout_iter_next_line (iter);
-
- gs = iter->run->glyphs;
-
- next_cluster_index = iter->run->item->offset + cluster_end_index (iter);
-
- oldp = iter->layout->text + iter->index;
-
- if (iter->ltr)
{
- item_end = iter->layout->text +
- iter->run->item->offset +
- iter->run->item->length;
- p = g_utf8_next_char (oldp);
- }
- else
- {
- item_end = iter->layout->text + iter->run->item->offset;
- p = g_utf8_prev_char (oldp);
- }
+ /* We need to fake an iterator position in the middle of a \r\n line terminator */
+ if (line_is_terminated (iter) &&
+ strncmp (iter->layout->text + iter->line->start_index + iter->line->length, "\r\n", 2) == 0 &&
+ iter->character_position == 0)
+ {
+ iter->character_position++;
+ return TRUE;
+ }
- new_index = iter->index + (p - oldp);
+ return next_nonempty_line (iter, TRUE);
+ }
- /* Make sure we don't go past the next cluster index */
- g_assert ((iter->ltr && (new_index <= next_cluster_index)) ||
- (!iter->ltr && (new_index >= next_cluster_index)));
+ iter->character_position++;
+ if (iter->character_position >= iter->cluster_num_chars)
+ return next_cluster_internal (iter, TRUE);
- if (new_index == next_cluster_index)
- return pango_layout_iter_next_cluster (iter);
+ text = iter->layout->text;
+ if (iter->ltr)
+ iter->index = g_utf8_next_char (text + iter->index) - text;
else
- {
- iter->index = new_index;
- return TRUE;
- }
+ iter->index = g_utf8_prev_char (text + iter->index) - text;
+
+ return TRUE;
}
/**
@@ -4682,31 +4821,7 @@ pango_layout_iter_next_char (PangoLayoutIter *iter)
gboolean
pango_layout_iter_next_cluster (PangoLayoutIter *iter)
{
- PangoGlyphString *gs;
-
- if (IS_INVALID (iter))
- return FALSE;
-
- if (iter->run == NULL)
- return pango_layout_iter_next_line (iter);
-
- gs = iter->run->glyphs;
-
- if (iter->next_cluster_start == gs->num_glyphs)
- return pango_layout_iter_next_run (iter);
- else
- {
- if (iter->ltr)
- iter->cluster_x += gs->glyphs[iter->cluster_start].geometry.width;
- else
- iter->cluster_x -= gs->glyphs[iter->cluster_start].geometry.width;
-
- iter->cluster_start = iter->next_cluster_start;
- iter->next_cluster_start = next_cluster_start (gs, iter->cluster_start);
- iter->cluster_index = gs->log_clusters[iter->cluster_start];
- iter->index = iter->run->item->offset + iter->cluster_index;
- return TRUE;
- }
+ return next_cluster_internal (iter, FALSE);
}
/**
@@ -4721,7 +4836,7 @@ pango_layout_iter_next_cluster (PangoLayoutIter *iter)
gboolean
pango_layout_iter_next_run (PangoLayoutIter *iter)
{
- gint prev_run_end;
+ int next_run_start; /* byte index */
GSList *next_link;
if (IS_INVALID (iter))
@@ -4729,9 +4844,7 @@ pango_layout_iter_next_run (PangoLayoutIter *iter)
if (iter->run == NULL)
return pango_layout_iter_next_line (iter);
- else
- prev_run_end = iter->run->item->offset + iter->run->item->length;
-
+
next_link = iter->run_list_link->next;
if (next_link == NULL)
@@ -4739,6 +4852,7 @@ pango_layout_iter_next_run (PangoLayoutIter *iter)
/* Moving on to the zero-width "virtual run" at the end of each
* line
*/
+ next_run_start = iter->run->item->offset + iter->run->item->length;
iter->run = NULL;
iter->run_list_link = NULL;
}
@@ -4746,9 +4860,10 @@ pango_layout_iter_next_run (PangoLayoutIter *iter)
{
iter->run_list_link = next_link;
iter->run = iter->run_list_link->data;
+ next_run_start = iter->run->item->offset;
}
- update_run (iter, prev_run_end);
+ update_run (iter, next_run_start);
return TRUE;
}
@@ -4766,7 +4881,7 @@ gboolean
pango_layout_iter_next_line (PangoLayoutIter *iter)
{
GSList *next_link;
-
+
if (IS_INVALID (iter))
return FALSE;
@@ -4790,13 +4905,6 @@ pango_layout_iter_next_line (PangoLayoutIter *iter)
else
iter->run = NULL;
- /* FIXME isn't this broken? we can have \r\n etc. */
- /* If we move on to an empty line (no runs), it means the empty line
- * represents a '\n' in layout->text, so advance iter->index
- */
- if (iter->run == NULL)
- iter->index += 1; /* 1 is the length of '\n' in UTF-8 */
-
iter->line_extents_link = iter->line_extents_link->next;
g_assert (iter->line_extents_link != NULL);
@@ -4821,15 +4929,8 @@ pango_layout_iter_get_char_extents (PangoLayoutIter *iter,
PangoRectangle *logical_rect)
{
PangoRectangle cluster_rect;
- int end_index;
- int start_index;
- const char *p;
- const char *end;
- const char *current;
- int char_count;
- int cluster_offset;
- double char_width;
-
+ double x0, x1;
+
if (IS_INVALID (iter))
return;
@@ -4847,46 +4948,15 @@ pango_layout_iter_get_char_extents (PangoLayoutIter *iter,
return;
}
- /* count chars in the cluster */
- end_index = iter->run->item->offset + cluster_end_index (iter);
- start_index = iter->run->item->offset + iter->cluster_index;
+ g_assert (cluster_rect.width == iter->cluster_width);
- if (end_index < start_index)
- {
- int tmp = end_index;
- end_index = start_index;
- start_index = tmp;
- }
-
- g_assert (start_index < end_index);
-
- p = iter->layout->text + start_index;
- current = iter->layout->text + iter->index;
- end = iter->layout->text + end_index;
+ x0 = ((double)iter->character_position * cluster_rect.width) / iter->cluster_num_chars;
+ x1 = ((double)(iter->character_position + 1) * cluster_rect.width) / iter->cluster_num_chars;
- g_assert (p < end);
- g_assert (p <= current);
- g_assert (current < end);
-
- char_count = 0;
- cluster_offset = 0;
- while (p != end)
- {
- if (p < current)
- ++cluster_offset;
- ++char_count;
- p = g_utf8_next_char (p);
- }
-
- char_width = ((double)cluster_rect.width) / char_count;
- logical_rect->width = char_width;
+ logical_rect->width = (int)x1 - (int)x0;
logical_rect->height = cluster_rect.height;
logical_rect->y = cluster_rect.y;
-
- if (iter->ltr)
- logical_rect->x = cluster_rect.x + char_width * cluster_offset;
- else
- logical_rect->x = cluster_rect.x + cluster_rect.width - char_width * cluster_offset;
+ logical_rect->x = cluster_rect.x + (int)x0;
}
/**
@@ -4918,7 +4988,7 @@ pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter,
pango_glyph_string_extents_range (iter->run->glyphs,
iter->cluster_start,
- iter->next_cluster_start,
+ iter->next_cluster_glyph,
iter->run->item->analysis.font,
ink_rect,
logical_rect);