From 744df757571eb00e6766b450d941ccfe9b70b68f Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Mon, 24 Apr 2000 03:37:20 +0000 Subject: New function for visual (left-right, not forward-back) cursor navigation Sun Apr 23 23:33:10 2000 Owen Taylor * pango/pango-layout.c (pango_layout_move_cursor_visually): New function for visual (left-right, not forward-back) cursor navigation within a layout. Thu Apr 20 22:16:39 2000 Owen Taylor * pango/pango-layout.c (pango_layout_line_x_to_index): As a hack, for x-positions at the edge of a line, fudge the position to be one before the line end (which is, for programs that don't properly handle the trailing flag), instead of at the end of the line. This works fine for whitespace-broken languages, but won't work right for languages that don't use whitespace to break lines. (GtkText* needs to keep track of whether a cursor is trailing or not) * pango/pango-layout.c (pango_layout_check_lines): Prohibit breaking a line at a non-whitespace -> space transition. (So that we never put wrap single character of whitespace at the beginning of a line) * pango/break.c (pango_break): Always allow a break before the first char. * pango/pango-layout.c (pango_layout_line_x_to_index): Remove the return value from the function, make it return the appropriate index depending on the base direction of the layoutt. --- pango/break.c | 4 +- pango/pango-layout.c | 420 ++++++++++++++++++++++++++++++++++++++++++++++----- pango/pango-layout.h | 38 +++-- 3 files changed, 407 insertions(+), 55 deletions(-) (limited to 'pango') diff --git a/pango/break.c b/pango/break.c index 811d5482..b171b087 100644 --- a/pango/break.c +++ b/pango/break.c @@ -49,7 +49,7 @@ void pango_break (const gchar *text, { next = unicode_get_utf8 (cur, &wc); if (!next) - return; /* FIXME: ERROR */ + break; /* FIXME: ERROR */ if (cur == next) break; if ((next - text) > length) @@ -57,7 +57,7 @@ void pango_break (const gchar *text, cur = next; attrs[i].is_white = (wc == ' ' || wc == '\t' || wc == '\n') ? 1 : 0; - attrs[i].is_break = (i > 0 && attrs[i-1].is_white) || attrs[i].is_white; + attrs[i].is_break = i == 0 || attrs[i-1].is_white || attrs[i].is_white; attrs[i].is_char_stop = 1; attrs[i].is_word_stop = (i == 0) || attrs[i-1].is_white; diff --git a/pango/pango-layout.c b/pango/pango-layout.c index bc031e89..db807f9b 100644 --- a/pango/pango-layout.c +++ b/pango/pango-layout.c @@ -58,8 +58,13 @@ struct _PangoLayoutLinePrivate static void pango_layout_clear_lines (PangoLayout *layout); static void pango_layout_check_lines (PangoLayout *layout); -static PangoLayoutLine * pango_layout_line_new (PangoLayout *layout); -static void pango_layout_line_reorder (PangoLayoutLine *line); +static PangoLayoutLine * pango_layout_line_new (PangoLayout *layout); +static void pango_layout_line_postprocess (PangoLayoutLine *line); + +static int *pango_layout_line_get_log2vis_map (PangoLayoutLine *line, + gboolean strong); +static int *pango_layout_line_get_vis2log_map (PangoLayoutLine *line, + gboolean strong); static void pango_layout_get_item_properties (PangoItem *item, PangoUnderline *uline); @@ -633,6 +638,160 @@ pango_layout_index_to_line_x (PangoLayout *layout, *x_pos = -1; } +/** + * pango_layout_move_cursor_visually: + * @layout: a #PangoLayout. + * @old_index: the old cursor byte index + * @old_trailing: + * @direction: direction to move cursor. A negative + * value indicates motion to the left. + * @new_index: 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: + * + * Computes a new cursor position from an old position and + * a count of positions to move visually. If @count is positive, + * then the new strong cursor position will be one position + * to the right of the old cursor position. If @count is position + * then the new strong cursor position will be one position + * to the left of the old cursor position. + * + * In the presence of bidirection 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. + **/ +void +pango_layout_move_cursor_visually (PangoLayout *layout, + int old_index, + int old_trailing, + int direction, + int *new_index, + int *new_trailing) +{ + int bytes_seen = 0; + PangoDirection base_dir; + PangoLayoutLine *line = NULL; + PangoLayoutLine *prev_line = NULL; + PangoLayoutLine *next_line; + GSList *tmp_list; + + int *log2vis_map; + int *vis2log_map; + int n_vis; + int vis_pos; + + g_return_if_fail (layout != NULL); + g_return_if_fail (old_index >= 0 && old_index <= layout->length); + g_return_if_fail (old_index < layout->length || old_trailing == 0); + g_return_if_fail (new_index != NULL); + g_return_if_fail (new_trailing != NULL); + + pango_layout_check_lines (layout); + + base_dir = pango_context_get_base_dir (layout->context); + + /* Find the line the old cursor is on + */ + tmp_list = layout->lines; + while (tmp_list) + { + line = tmp_list->data; + + if (bytes_seen + line->length > old_index || !tmp_list->next) + break; + + tmp_list = tmp_list->next; + prev_line = line; + bytes_seen += line->length; + } + + if (tmp_list->next) + next_line = tmp_list->next->data; + else + next_line = NULL; + + if (old_trailing) + old_index = unicode_next_utf8 (layout->text + old_index) - layout->text; + + log2vis_map = pango_layout_line_get_log2vis_map (line, TRUE); + n_vis = unicode_strlen (layout->text + bytes_seen, line->length); + + vis_pos = log2vis_map[old_index - bytes_seen]; + g_free (log2vis_map); + + if (vis_pos == 0 && count < 0) + { + if (base_dir == PANGO_DIRECTION_LTR) + { + if (!prev_line) + { + *new_index = -1; + *new_trailing = 0; + return; + } + line = prev_line; + bytes_seen -= line->length; + } + else + { + if (!next_line) + { + *new_index = G_MAXINT; + *new_trailing = 0; + return; + } + bytes_seen += line->length; + line = next_line; + } + + vis_pos = unicode_strlen (layout->text + bytes_seen, line->length); + } + else if (vis_pos == n_vis && count > 0) + { + if (base_dir == PANGO_DIRECTION_LTR) + { + if (!next_line) + { + *new_index = G_MAXINT; + *new_trailing = 0; + return; + } + bytes_seen += line->length; + line = next_line; + } + else + { + if (!prev_line) + { + *new_index = -1; + *new_trailing = 0; + return; + } + line = prev_line; + bytes_seen -= line->length; + } + + vis_pos = 0; + } + + vis_pos += (count > 0) ? 1 : -1; + + vis2log_map = pango_layout_line_get_vis2log_map (line, TRUE); + *new_index = bytes_seen + vis2log_map[vis_pos]; + g_free (vis2log_map); + + if (*new_index == bytes_seen + line->length) + { + *new_index = unicode_previous_utf8 (layout->text, layout->text + *new_index) - layout->text; + *new_trailing = 1; + } + else + *new_trailing = 0; +} + /** * pango_layout_xy_to_index: * @layout: a #PangoLayout @@ -694,7 +853,8 @@ pango_layout_xy_to_index (PangoLayout *layout, else x_offset = 0; - return pango_layout_line_x_to_index (line, x - x_offset, index, trailing); + pango_layout_line_x_to_index (line, x - x_offset, index, trailing); + return TRUE; } y_offset += logical_rect.height; @@ -779,6 +939,141 @@ pango_layout_index_to_pos (PangoLayout *layout, } } +static void +pango_layout_line_get_range (PangoLayoutLine *line, + char **start, + char **end) +{ + char *p; + GSList *tmp_list; + + p = line->layout->text; + tmp_list = line->layout->lines; + + while (tmp_list->data != line) + { + p += ((PangoLayoutLine *)tmp_list->data)->length; + tmp_list = tmp_list->next; + } + + if (start) + *start = p; + if (end) + *end = p + line->length; +} + +int * +pango_layout_line_get_vis2log_map (PangoLayoutLine *line, + gboolean strong) +{ + PangoLayout *layout = line->layout; + PangoDirection base_dir = pango_context_get_base_dir (layout->context); + PangoDirection prev_dir = base_dir; + GSList *tmp_list; + gchar *start, *end; + int *result; + int pos = 0; + int n_chars; + + pango_layout_line_get_range (line, &start, &end); + n_chars = unicode_strlen (start, end - start); + + result = g_new (int, n_chars + 1); + + if (strong) + { + if (base_dir == PANGO_DIRECTION_LTR) + { + result[0] = 0; + result[n_chars] = end - start; + } + else + { + result[0] = end - start; + result[n_chars] = 0; + } + } + + tmp_list = line->runs; + while (tmp_list) + { + PangoLayoutRun *run = tmp_list->data; + int run_n_chars = run->item->num_chars; + PangoDirection run_dir = (run->item->analysis.level % 2) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; + char *p = layout->text + run->item->offset; + int i; + + if (run_dir == PANGO_DIRECTION_LTR) + { + if ((strong && base_dir == run_dir) || + (!strong && base_dir != run_dir) || + (prev_dir == run_dir)) + result[pos] = p - start; + + p = unicode_next_utf8 (p); + + for (i = 1; i < run_n_chars; i++) + { + result[pos + i] = p - start; + p = unicode_next_utf8 (p); + } + + if ((strong && base_dir == run_dir) || + (!strong && base_dir != run_dir)) + result[pos + run_n_chars] = p - start; + } + else + { + if ((strong && base_dir == run_dir) || + (!strong && base_dir != run_dir)) + result[pos + run_n_chars] = p - start; + + p = unicode_next_utf8 (p); + + for (i = 1; i < run_n_chars; i++) + { + result[pos + run_n_chars - i] = p - start; + p = unicode_next_utf8 (p); + } + + if ((strong && base_dir == run_dir) || + (!strong && base_dir != run_dir) || + (prev_dir == run_dir)) + result[pos] = p - start; + } + + pos += run_n_chars; + prev_dir = run_dir; + tmp_list = tmp_list->next; + } + + return result; +} + +static int * +pango_layout_line_get_log2vis_map (PangoLayoutLine *line, + gboolean strong) +{ + gchar *start, *end; + int *reverse_map; + int *result; + int i; + int n_chars; + + pango_layout_line_get_range (line, &start, &end); + n_chars = unicode_strlen (start, end - start); + result = g_new0 (int, end - start + 1); + + reverse_map = pango_layout_line_get_vis2log_map (line, strong); + + for (i=0; i <= n_chars; i++) + result[reverse_map[i]] = i; + + g_free (reverse_map); + + return result; +} + static PangoDirection pango_layout_line_get_char_direction (PangoLayoutLine *layout_line, int index) @@ -1102,6 +1397,7 @@ process_item (PangoLayoutLine *line, return FALSE; pango_shape (text + item->offset, item->length, &item->analysis, glyphs); + if (*remaining_width < 0) { insert_run (line, item, glyphs); @@ -1126,39 +1422,28 @@ process_item (PangoLayoutLine *line, { int length; int num_chars = item->num_chars; - int new_width; PangoGlyphUnit *log_widths = g_new (PangoGlyphUnit, item->num_chars); pango_glyph_string_get_logical_widths (glyphs, text + item->offset, item->length, item->analysis.level, log_widths); - new_width = 0; + /* Shorten the item by one line break + */ while (--num_chars > 0) { - /* Shorten the item by one line break - */ width -= log_widths[num_chars]; + if (log_attrs[num_chars].is_break && width <= *remaining_width) break; } g_free (log_widths); - if (num_chars != 0) /* Succesfully broke the item */ + if (num_chars > 0) /* Succesfully broke the item */ { - const char *p; - gint n; - PangoItem *new_item = pango_item_copy (item); - - /* Determine utf8 length corresponding to num_chars. Slow? - */ - n = num_chars; - p = text + item->offset; - while (n-- > 0) - p = unicode_next_utf8 (p); - length = p - (text + item->offset); + length = unicode_offset_to_index (text + item->offset, num_chars); new_item->length = length; new_item->num_chars = num_chars; @@ -1166,9 +1451,9 @@ process_item (PangoLayoutLine *line, item->offset += length; item->length -= length; item->num_chars -= num_chars; - + pango_shape (text + new_item->offset, new_item->length, &new_item->analysis, glyphs); - + *remaining_width -= width; insert_run (line, new_item, glyphs); @@ -1305,7 +1590,14 @@ pango_layout_check_lines (PangoLayout *layout) tmp_list = tmp_list->next; start_offset += old_num_chars; - if (start_offset < layout->n_chars && !layout->log_attrs[start_offset].is_break) + /* We prohibit breaking a line between a non-whitespace char and the whitespace char afterwards + * because this would result in the possibility of normal wrapped paragraphs with leading + * white-space at the front of lines. We probably should have a mode where we treat all + * white-space as zero width - appropriate for typography but not for editing. + */ + if (start_offset < layout->n_chars && + (!layout->log_attrs[start_offset].is_break || + (!layout->log_attrs[start_offset-1].is_white && layout->log_attrs[start_offset].is_white))) last_cant_end = TRUE; else { @@ -1346,15 +1638,14 @@ pango_layout_check_lines (PangoLayout *layout) } else { - line->runs = g_slist_reverse (line->runs); - pango_layout_line_reorder (line); + start_offset += old_num_chars - item->num_chars; + + pango_layout_line_postprocess (line); layout->lines = g_slist_prepend (layout->lines, line); line = pango_layout_line_new (layout); remaining_width = (layout->indent >= 0) ? layout->width : layout->indent + layout->indent; - - start_offset += old_num_chars - item->num_chars; } last_cant_end = FALSE; @@ -1362,8 +1653,7 @@ pango_layout_check_lines (PangoLayout *layout) } } - line->runs = g_slist_reverse (line->runs); - pango_layout_line_reorder (line); + pango_layout_line_postprocess (line); layout->lines = g_slist_prepend (layout->lines, line); @@ -1447,11 +1737,12 @@ pango_layout_line_unref (PangoLayoutLine *line) * 0 represents the trailing edge of the cluster. * * Convert from x offset to the byte index of the corresponding - * character within the text of the layout. - * - * Return value: %TRUE if the x index was within the line + * character within the text of the layout. If the @x_pos + * is negative or greater than the length of the line, then + * then the result will be 0, or the last index in the line + * depending on the base direction of the layout. */ -gboolean +void pango_layout_line_x_to_index (PangoLayoutLine *line, int x_pos, int *index, @@ -1459,11 +1750,48 @@ pango_layout_line_x_to_index (PangoLayoutLine *line, { GSList *tmp_list; gint start_pos = 0; + gint first_index = 0, last_index; + PangoDirection base_dir; + gboolean last_trailing; - g_return_val_if_fail (LINE_IS_VALID (line), FALSE); + g_return_if_fail (line != NULL); + g_return_if_fail (LINE_IS_VALID (line)); if (!LINE_IS_VALID (line)) - return FALSE; + return; + + base_dir = pango_context_get_base_dir (line->layout->context); + + /* Find the last index in the line + */ + tmp_list = line->layout->lines; + while (tmp_list->data != line) + { + first_index += ((PangoLayoutLine *)tmp_list->data)->length; + tmp_list = tmp_list->next; + } + + last_index = first_index + line->length; + last_index = unicode_previous_utf8 (line->layout->text + first_index, line->layout->text + last_index) - line->layout->text; + + /* This is a HACK. If a program only keeps track if cursor (etc) + * indices and not the trailing flag, then the trailing index of the + * last character on a wrapped line is identical to the leading + * index of the next line. Actually, any program keeping cursor + * positions with wrapped lines should distinguish leading and + * trailing cursors. + */ + last_trailing = tmp_list->next ? 0 : 1; + + if (x_pos < 0) + { + if (index) + *index = (base_dir == PANGO_DIRECTION_LTR) ? first_index : last_index; + if (trailing) + *trailing = (base_dir == PANGO_DIRECTION_LTR) ? 0 : last_trailing; + + return; + } tmp_list = line->runs; while (tmp_list) @@ -1486,14 +1814,17 @@ pango_layout_line_x_to_index (PangoLayoutLine *line, if (index) *index = pos + run->item->offset; - return TRUE; + return; } start_pos += logical_rect.width; tmp_list = tmp_list->next; } - return FALSE; + if (index) + *index = (base_dir == PANGO_DIRECTION_LTR) ? last_index : first_index; + if (trailing) + *trailing = (base_dir == PANGO_DIRECTION_LTR) ? last_trailing : 0; } /** @@ -1780,7 +2111,7 @@ pango_layout_line_new (PangoLayout *layout) /* - * NB: The contents of the file implement the exact same algorithm + * NB: This implement the exact same algorithm as * reorder-items.c:pango_reorder_items(). */ @@ -1856,6 +2187,21 @@ pango_layout_line_reorder (PangoLayoutLine *line) g_slist_free (logical_runs); } +static void +pango_layout_line_postprocess (PangoLayoutLine *line) +{ + /* NB: the runs are in reverse order at this point, since we prepended them to the list + */ + + /* Reverse the runs + */ + line->runs = g_slist_reverse (line->runs); + + /* Now convert logical to visual order + */ + pango_layout_line_reorder (line); +} + /* This utility function is duplicated here and in pango-layout.c; should it be * public? Trouble is - what is the appropriate set of properties? */ diff --git a/pango/pango-layout.h b/pango/pango-layout.h index 02fef3b0..2a01055d 100644 --- a/pango/pango-layout.h +++ b/pango/pango-layout.h @@ -88,21 +88,27 @@ void pango_layout_get_log_attrs (PangoLayout *layout, PangoLogAttr **attrs, gint *n_attrs); -void pango_layout_index_to_pos (PangoLayout *layout, - int index, - PangoRectangle *pos); -void pango_layout_get_cursor_pos (PangoLayout *layout, - int index, - PangoRectangle *strong_pos, - PangoRectangle *weak_pos); -gboolean pango_layout_xy_to_index (PangoLayout *layout, - int x, - int y, - int *index, - gboolean *trailing); -void pango_layout_get_extents (PangoLayout *layout, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect); +void pango_layout_index_to_pos (PangoLayout *layout, + int index, + PangoRectangle *pos); +void pango_layout_get_cursor_pos (PangoLayout *layout, + int index, + PangoRectangle *strong_pos, + PangoRectangle *weak_pos); +void pango_layout_move_cursor_visually (PangoLayout *layout, + int old_index, + int old_trailing, + int direction, + int *new_index, + int *new_trailing); +gboolean pango_layout_xy_to_index (PangoLayout *layout, + int x, + int y, + int *index, + gboolean *trailing); +void pango_layout_get_extents (PangoLayout *layout, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect); int pango_layout_get_line_count (PangoLayout *layout); PangoLayoutLine *pango_layout_get_line (PangoLayout *layout, @@ -111,7 +117,7 @@ GSList * pango_layout_get_lines (PangoLayout *layout); void pango_layout_line_ref (PangoLayoutLine *line); void pango_layout_line_unref (PangoLayoutLine *line); -gboolean pango_layout_line_x_to_index (PangoLayoutLine *line, +void pango_layout_line_x_to_index (PangoLayoutLine *line, int x_pos, int *index, int *trailing); -- cgit v1.2.1