diff options
author | Owen Taylor <otaylor@redhat.com> | 2004-07-14 22:17:36 +0000 |
---|---|---|
committer | Owen Taylor <otaylor@src.gnome.org> | 2004-07-14 22:17:36 +0000 |
commit | e8451d0463303bbaa3ba3d840d7985f9120ba58a (patch) | |
tree | 9ce3e64b7191ea3dc352e101ccd552231e9bbc59 /pango | |
parent | 731bd56653de86e2298cd8b04c320fca82bb2f9f (diff) | |
download | pango-e8451d0463303bbaa3ba3d840d7985f9120ba58a.tar.gz |
Add PangoEllipsizeMode, pango_layout_set_ellipsize(), implement. (#59071)
Wed Jul 14 17:47:38 2004 Owen Taylor <otaylor@redhat.com>
* pango/pango-layout.[ch] pango/ellipsize.c pango/Makefile.am:
Add PangoEllipsizeMode, pango_layout_set_ellipsize(), implement.
(#59071)
* pango/pango-layout-private.h pango/pango-layout.c:
Move PangoLayout structure into a separate header file.
* pango/pango-glyph-item.[ch]: Add pango_glyph_item_free().
* pango/pango-glyph-item-private.h pango/pango-glyph-item.c:
Internally export the PangoGlyphItemIter functionality.
* examples/renderdemo.[ch]: Add --ellipsize option.
Diffstat (limited to 'pango')
-rw-r--r-- | pango/Makefile.am | 3 | ||||
-rw-r--r-- | pango/ellipsize.c | 750 | ||||
-rw-r--r-- | pango/pango-glyph-item-private.h | 64 | ||||
-rw-r--r-- | pango/pango-glyph-item.c | 215 | ||||
-rw-r--r-- | pango/pango-glyph-item.h | 1 | ||||
-rw-r--r-- | pango/pango-layout-private.h | 71 | ||||
-rw-r--r-- | pango/pango-layout.c | 159 | ||||
-rw-r--r-- | pango/pango-layout.h | 24 |
8 files changed, 1184 insertions, 103 deletions
diff --git a/pango/Makefile.am b/pango/Makefile.am index 66081e8a..cc5fdb02 100644 --- a/pango/Makefile.am +++ b/pango/Makefile.am @@ -53,6 +53,7 @@ pango-win32res.lo: pango.rc libpango_1_0_la_SOURCES = \ break.c \ + ellipsize.c \ fonts.c \ glyphstring.c \ mapping.c \ @@ -66,9 +67,11 @@ libpango_1_0_la_SOURCES = \ pango-fontmap.c \ pango-fontset.c \ pango-glyph-item.c \ + pango-glyph-item-private.h \ pango-impl-utils.h \ pango-item.c \ pango-layout.c \ + pango-layout-private.h \ pango-markup.c \ pango-script.c \ pango-script-lang-table.h \ diff --git a/pango/ellipsize.c b/pango/ellipsize.c new file mode 100644 index 00000000..8a7164d1 --- /dev/null +++ b/pango/ellipsize.c @@ -0,0 +1,750 @@ +/* Pango + * ellipsize.c: Routine to ellipsize layout lines + * + * Copyright (C) 2004 Red Hat Software + * + * 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 <string.h> + +#include "pango-glyph-item-private.h" +#include "pango-layout-private.h" +#include "pango-engine-private.h" + +typedef struct _EllipsizeState EllipsizeState; +typedef struct _RunInfo RunInfo; +typedef struct _LineIter LineIter; + + +/* Overall, the way we ellipsize is we grow a "gap" out from an original + * gap center position until: + * + * line_width - gap_width + ellipsize_width <= goal_width + * + * Line: [-------------------------------------------] + * Runs: [------)[---------------)[------------------] + * Gap center: * + * Gap: [----------------------] + * + * The gap center may be at the start or end in which case the gap grows + * in only one direction. + * + * Note the line and last run are logically closed at the end; this allows + * us to use a gap position at x=line_width and still have it be part of + * of a run. + * + * We grow the grap out one "span" at a time, where a span is simply a + * consecutive run of clusters that we can't interrupt with an ellipsis. + * + * When choosing whether to grow the gap at the start or the end, we + * calculate the next span to remove in both directions and see which + * causes the smaller increase in: + * + * MAX (gap_end - gap_center, gap_start - gap_center) + * + * All computations are done using logical order; the ellipsization + * process occurs before the runs are ordered into visual order. + */ + +/* Keeps information about a single run */ +struct _RunInfo +{ + PangoGlyphItem *run; + int start_offset; /* Character offset of run start */ + int width; /* Width of run in Pango units */ +}; + +/* Iterator to a position within the ellipsized line */ +struct _LineIter +{ + PangoGlyphItemIter run_iter; + int run_index; +}; + +/* State of ellipsization process */ +struct _EllipsizeState +{ + PangoLayout *layout; /* Layout being ellipsized */ + PangoAttrList *attrs; /* Attributes used for itemization/shaping */ + + RunInfo *run_info; /* Array of information about each run */ + int n_runs; + + int total_width; /* Original width of line in pango units */ + int gap_center; /* Goal for center of gap */ + + PangoGlyphItem *ellipsis_run; /* Run created to hold ellipsis */ + int ellipsis_width; /* Width of ellipsis, in pango units */ + int ellipsis_is_cjk; /* Whether the first character in the ellipsized + * is wide; this triggers us to try to use a + * mid-line ellipsis instead of a baseline + */ + + PangoAttrIterator *line_start_attr; /* Cached PangoAttrIterator for the start of the run */ + + LineIter gap_start_iter; /* Iteratator pointig to the first cluster in gap */ + int gap_start_x; /* x position of start of gap, in pango units */ + PangoAttrIterator *gap_start_attr; /* Attribute iterator pointing to a range containing + * the first character in gap */ + + LineIter gap_end_iter; /* Iterator pointing to last cluster in gap */ + int gap_end_x; /* x position of end of gap, in pango units */ +}; + +/* Compute global information needed for the itemization process + */ +static void +init_state (EllipsizeState *state, + PangoLayoutLine *line, + PangoAttrList *attrs) +{ + GSList *l; + int i, j; + int start_offset; + + state->layout = line->layout; + state->attrs = attrs; + + state->n_runs = g_slist_length (line->runs); + state->run_info = g_new (RunInfo, state->n_runs); + + start_offset = g_utf8_strlen (line->layout->text, + line->start_index); + + start_offset = 0; + state->total_width = 0; + for (l = line->runs, i = 0; l; l = l->next, i++) + { + PangoGlyphItem *run = l->data; + int width = 0; + + for (j = 0; j < run->glyphs->num_glyphs; j++) + width += run->glyphs->glyphs[j].geometry.width; + + state->run_info[i].run = run; + state->run_info[i].width = width; + state->run_info[i].start_offset = start_offset; + state->total_width += width; + + start_offset += run->item->num_chars; + } + + state->ellipsis_run = NULL; + state->line_start_attr = NULL; + state->gap_start_attr = NULL; +} + +/* Cleanup memory allocation + */ +static void +free_state (EllipsizeState *state) +{ + if (state->line_start_attr) + pango_attr_iterator_destroy (state->line_start_attr); + if (state->gap_start_attr) + pango_attr_iterator_destroy (state->gap_start_attr); + g_free (state->run_info); +} + +/* Computes the width of a single cluster + */ +static int +get_cluster_width (LineIter *iter) +{ + PangoGlyphItemIter *run_iter = &iter->run_iter; + PangoGlyphString *glyphs = run_iter->glyph_item->glyphs; + int width = 0; + int i; + + if (run_iter->start_glyph < run_iter->end_glyph) /* LTR */ + { + for (i = run_iter->start_glyph; i < run_iter->end_glyph; i++) + width += glyphs->glyphs[run_iter->start_glyph].geometry.width; + } + else /* RTL */ + { + for (i = run_iter->start_glyph; i > run_iter->end_glyph; i--) + width += glyphs->glyphs[run_iter->start_glyph].geometry.width; + } + + return width; +} + +/* Move forward one cluster. Returns %FALSE if we were already at the end + */ +static gboolean +line_iter_next_cluster (EllipsizeState *state, + LineIter *iter) +{ + if (!_pango_glyph_item_iter_next_cluster (&iter->run_iter)) + { + if (iter->run_index == state->n_runs - 1) + return FALSE; + else + { + iter->run_index++; + _pango_glyph_item_iter_init_start (&iter->run_iter, + state->run_info[iter->run_index].run, + state->layout->text); + } + } + + return TRUE; +} + +/* Move backward one cluster. Returns %FALSE if we were already at the end + */ +static gboolean +line_iter_prev_cluster (EllipsizeState *state, + LineIter *iter) +{ + if (!_pango_glyph_item_iter_prev_cluster (&iter->run_iter)) + { + if (iter->run_index == 0) + return FALSE; + else + { + iter->run_index--; + _pango_glyph_item_iter_init_end (&iter->run_iter, + state->run_info[iter->run_index].run, + state->layout->text); + } + } + + return TRUE; +} + +/* + * An ellipsization boundary is defined by two things + * + * - Starts a cluster - forced by structure of code + * - Starts a grapheme - checked here + * + * In the future we'd also like to add a check for cursive connectivity here. + * This should be an addition to PangoGlyphVisAttr + * + */ + +/* Checks if there is a ellipsization boundary before the cluster @iter points to + */ +static gboolean +starts_at_ellipsization_boundary (EllipsizeState *state, + LineIter *iter) +{ + RunInfo *run_info = &state->run_info[iter->run_index]; + + if (iter->run_iter.start_char == 0 && iter->run_index == 0) + return TRUE; + + return state->layout->log_attrs[run_info->start_offset + iter->run_iter.start_char].is_cursor_position; +} + +/* Checks if there is a ellipsization boundary after the cluster @iter points to + */ +static gboolean +ends_at_ellipsization_boundary (EllipsizeState *state, + LineIter *iter) +{ + RunInfo *run_info = &state->run_info[iter->run_index]; + + if (iter->run_iter.end_char == run_info->run->item->num_chars && iter->run_index == state->n_runs - 1) + return TRUE; + + return state->layout->log_attrs[run_info->start_offset + iter->run_iter.end_char + 1].is_cursor_position; +} + +/* Helper function to re-itemize a string of text + */ +static PangoItem * +itemize_text (EllipsizeState *state, + const char *text, + PangoAttrList *attrs) +{ + GList *items; + PangoItem *item; + + items = pango_itemize (state->layout->context, text, 0, strlen (text), attrs, NULL); + g_assert (g_list_length (items) == 1); + + item = items->data; + g_list_free (items); + + return item; +} + +/* Shapes the ellipsis using the font and is_cjk information computed by + * update_ellipsis_shape() from the first character in the gap. + */ +static void +shape_ellipsis (EllipsizeState *state) +{ + PangoAttrList *attrs = pango_attr_list_new (); + GSList *run_attrs; + PangoItem *item; + PangoGlyphString *glyphs; + GSList *l; + PangoAttribute *fallback; + const char *ellipsis_text; + int i; + + /* Create/reset state->ellipsis_run + */ + if (!state->ellipsis_run) + { + state->ellipsis_run = g_new (PangoGlyphItem, 1); + state->ellipsis_run->glyphs = pango_glyph_string_new (); + state->ellipsis_run->item = NULL; + } + + if (state->ellipsis_run->item) + { + pango_item_free (state->ellipsis_run->item); + state->ellipsis_run->item = NULL; + } + + /* Create an attribute list + */ + run_attrs = pango_attr_iterator_get_attrs (state->gap_start_attr); + for (l = run_attrs; l; l = l->next) + { + PangoAttribute *attr = l->data; + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attrs, attr); + } + + g_slist_free (run_attrs); + + fallback = pango_attr_fallback_new (FALSE); + fallback->start_index = 0; + fallback->end_index = G_MAXINT; + pango_attr_list_insert (attrs, fallback); + + /* First try using a specific ellipsis character in the best matching font + */ + if (state->ellipsis_is_cjk) + ellipsis_text = "\342\213\257"; /* U+22EF: MIDLINE HORIZONTAL ELLIPSIS, used for CJK */ + else + ellipsis_text = "\342\200\246"; /* U+2026: HORIZONTAL ELLIPSIS */ + + item = itemize_text (state, ellipsis_text, attrs); + + /* If that fails we use "..." in the first matching font + */ + if (!_pango_engine_shape_covers (item->analysis.shape_engine, item->analysis.font, + item->analysis.language, g_utf8_get_char (ellipsis_text))) + { + pango_item_free (item); + + /* Modify the fallback iter while it is inside the PangoAttrList; Don't try this at home + */ + ((PangoAttrInt *)fallback)->value = FALSE; + + ellipsis_text = "..."; + item = itemize_text (state, ellipsis_text, attrs); + } + + pango_attr_list_unref (attrs); + + state->ellipsis_run->item = item; + + /* Now shape + */ + glyphs = state->ellipsis_run->glyphs; + + pango_shape (ellipsis_text, strlen (ellipsis_text), + &item->analysis, glyphs); + + state->ellipsis_width = 0; + for (i = 0; i < glyphs->num_glyphs; i++) + state->ellipsis_width += glyphs->glyphs[i].geometry.width; +} + +/* Helper function to advance a PangoAttrIterator to a particular + * byte index. + */ +static void +advance_iterator_to (PangoAttrIterator *iter, + int new_index) +{ + int start, end; + + while (TRUE) + { + pango_attr_iterator_range (iter, &start, &end); + if (end > new_index) + break; + + pango_attr_iterator_next (iter); + } +} + +/* Updates the shaping of the ellipsis if necessary when we move the + * position of the start of the gap. + * + * The shaping of the ellipsis is determined by two things: + * + * - The font attributes applied to the first character in the gap + * - Whether the first character in the gap is wide or not. If the + * first character is wide, then we assume that we are ellipsizing + * East-Asian text, so prefer a mid-line ellipsizes to a baseline + * ellipsis, since that's typical practice for Chinese/Japanese/Korean. + */ +static void +update_ellipsis_shape (EllipsizeState *state) +{ + gboolean recompute = FALSE; + gunichar start_wc; + gboolean is_cjk; + + /* Unfortunately, we can only advance PangoAttrIterator forward; so each + * time we back up we need to go forward to find the new position. To make + * this not utterly slow, we cache an iterator at the start of the line + */ + if (!state->line_start_attr) + { + state->line_start_attr = pango_attr_list_get_iterator (state->attrs); + advance_iterator_to (state->line_start_attr, state->run_info[0].run->item->offset); + } + + if (state->gap_start_attr) + { + /* See if the current attribute range contains the new start position + */ + int start, end; + + pango_attr_iterator_range (state->gap_start_attr, &start, &end); + + if (state->gap_start_iter.run_iter.start_index < start) + { + pango_attr_iterator_destroy (state->gap_start_attr); + state->gap_start_attr = NULL; + } + } + + /* Check whether we need to recompute the ellipsis because of new font attributes + */ + if (!state->gap_start_attr) + { + state->gap_start_attr = pango_attr_iterator_copy (state->line_start_attr); + advance_iterator_to (state->gap_start_attr, + state->run_info[state->gap_start_iter.run_index].run->item->offset); + + recompute = TRUE; + } + + /* Check whether we need to recompute the ellipsis because we switch from CJK to not + * or vice-versa + */ + start_wc = g_utf8_get_char (state->layout->text + state->gap_start_iter.run_iter.start_index); + is_cjk = g_unichar_iswide (start_wc); + + if (is_cjk != state->ellipsis_is_cjk) + { + state->ellipsis_is_cjk = is_cjk; + recompute = TRUE; + } + + if (recompute) + shape_ellipsis (state); +} + +/* Computes the position of the gap center and finds the smallest span containing it + */ +static void +find_initial_span (EllipsizeState *state) +{ + PangoGlyphItem *glyph_item; + PangoGlyphItemIter *run_iter; + gboolean have_cluster; + int i; + int x; + int cluster_width; + + switch (state->layout->ellipsize) + { + case PANGO_ELLIPSIZE_NONE: + default: + g_assert_not_reached (); + case PANGO_ELLIPSIZE_START: + state->gap_center = 0; + break; + case PANGO_ELLIPSIZE_MIDDLE: + state->gap_center = state->total_width / 2; + break; + case PANGO_ELLIPSIZE_END: + state->gap_center = state->total_width; + break; + } + + /* Find the run containing the gap center + */ + x = 0; + for (i = 0; i < state->n_runs; i++) + { + if (x + state->run_info[i].width > state->gap_center) + break; + + x += state->run_info[i].width; + } + + if (i == state->n_runs) /* Last run is a closed interval, so back off one run */ + { + i--; + x -= state->run_info[i].width; + } + + /* Find the cluster containing the gap center + */ + state->gap_start_iter.run_index = i; + run_iter = &state->gap_start_iter.run_iter; + glyph_item = state->run_info[i].run; + + cluster_width = 0; /* Quiet GCC, the line must have at least one cluster */ + for (have_cluster = _pango_glyph_item_iter_init_start (run_iter, glyph_item, state->layout->text); + have_cluster; + have_cluster = _pango_glyph_item_iter_next_cluster (run_iter)) + { + cluster_width = get_cluster_width (&state->gap_start_iter); + + if (x + cluster_width > state->gap_center) + break; + + x += cluster_width; + } + + if (!have_cluster) /* Last cluster is a closed interval, so back off one cluster */ + x -= cluster_width; + + state->gap_end_iter = state->gap_start_iter; + + state->gap_start_x = x; + state->gap_end_x = x + cluster_width; + + /* Expand the gap to a full span + */ + while (!starts_at_ellipsization_boundary (state, &state->gap_start_iter)) + { + line_iter_prev_cluster (state, &state->gap_start_iter); + state->gap_start_x -= get_cluster_width (&state->gap_start_iter); + } + + while (!ends_at_ellipsization_boundary (state, &state->gap_end_iter)) + { + line_iter_next_cluster (state, &state->gap_end_iter); + state->gap_end_x += get_cluster_width (&state->gap_end_iter); + } + + update_ellipsis_shape (state); +} + +/* Removes one run from the start or end of the gap. Returns FALSE + * if there's nothing left to remove in either direction. + */ +static gboolean +remove_one_span (EllipsizeState *state) +{ + LineIter new_gap_start_iter; + LineIter new_gap_end_iter; + int new_gap_start_x; + int new_gap_end_x; + + /* Find one span backwards and forward from the gap + */ + new_gap_start_iter = state->gap_start_iter; + new_gap_start_x = state->gap_start_x; + do + { + if (!line_iter_prev_cluster (state, &new_gap_start_iter)) + break; + new_gap_start_x -= get_cluster_width (&new_gap_start_iter); + } + while (!starts_at_ellipsization_boundary (state, &new_gap_start_iter)); + + new_gap_end_iter = state->gap_end_iter; + new_gap_end_x = state->gap_end_x; + do + { + if (!line_iter_next_cluster (state, &new_gap_end_iter)) + break; + new_gap_end_x += get_cluster_width (&new_gap_end_iter); + } + while (!ends_at_ellipsization_boundary (state, &new_gap_end_iter)); + + if (state->gap_end_x == new_gap_end_x && state->gap_start_x == new_gap_start_x) + return FALSE; + + /* In the case where we could remove a span from either end of the + * gap, we look at which causes the smaller increase in the + * MAX (gap_end - gap_center, gap_start - gap_center) + */ + if (state->gap_end_x == new_gap_end_x || + (state->gap_start_x != new_gap_start_x && + state->gap_center - new_gap_start_x < new_gap_end_x - state->gap_center)) + { + state->gap_start_iter = new_gap_start_iter; + state->gap_start_x = new_gap_start_x; + + update_ellipsis_shape (state); + } + else + { + state->gap_end_iter = new_gap_end_iter; + state->gap_end_x = new_gap_end_x; + } + + return TRUE; +} + +/* Fixes up the properties of the ellipsis run once we've determined the final extents + * of the gap + */ +static void +fixup_ellipsis_run (EllipsizeState *state) +{ + PangoGlyphString *glyphs = state->ellipsis_run->glyphs; + PangoItem *item = state->ellipsis_run->item; + int level; + int i; + + /* Make the entire glyphstring into a single logical cluster */ + for (i = 0; i < glyphs->num_glyphs; i++) + { + glyphs->log_clusters[i] = 0; + glyphs->glyphs[i].attr.is_cluster_start = FALSE; + } + + glyphs->glyphs[0].attr.is_cluster_start = TRUE; + + /* Fix up the item to point to the entire elided text */ + item->offset = state->gap_start_iter.run_iter.start_index; + item->length = state->gap_end_iter.run_iter.end_index - item->offset; + item->num_chars = g_utf8_strlen (state->layout->text + item->offset, item->length); + + /* The level for the item is the minimum level of the elided text */ + level = G_MAXINT; + for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++) + level = MIN (level, state->run_info[i].run->item->analysis.level); + + item->analysis.level = level; +} + +/* Computes the new list of runs for the line + */ +static GSList * +get_run_list (EllipsizeState *state) +{ + PangoGlyphItem *partial_start_run = NULL; + PangoGlyphItem *partial_end_run = NULL; + GSList *result = NULL; + RunInfo *run_info; + PangoGlyphItemIter *run_iter; + int i; + + /* We first cut out the pieces of the starting and ending runs we want to + * preserve; we do the end first in case the end and the start are + * the same. Doing the start first would disturb the indices for the end. + */ + run_info = &state->run_info[state->gap_end_iter.run_index]; + run_iter = &state->gap_end_iter.run_iter; + if (run_iter->end_char != run_info->run->item->num_chars) + { + partial_end_run = run_info->run; + run_info->run = pango_glyph_item_split (run_info->run, state->layout->text, + run_iter->end_index - run_info->run->item->offset); + } + + run_info = &state->run_info[state->gap_start_iter.run_index]; + run_iter = &state->gap_start_iter.run_iter; + if (run_iter->start_char != 0) + { + partial_start_run = pango_glyph_item_split (run_info->run, state->layout->text, + run_iter->start_index - run_info->run->item->offset); + } + + /* Now assemble the new list of runs + */ + for (i = 0; i < state->gap_start_iter.run_index; i++) + result = g_slist_prepend (result, state->run_info[i].run); + + if (partial_start_run) + result = g_slist_prepend (result, partial_start_run); + + result = g_slist_prepend (result, state->ellipsis_run); + + if (partial_end_run) + result = g_slist_prepend (result, partial_end_run); + + for (i = state->gap_end_iter.run_index + 1; i < state->n_runs; i++) + result = g_slist_prepend (result, state->run_info[i].run); + + /* And free the ones we didn't use + */ + for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++) + pango_glyph_item_free (state->run_info[i].run); + + return g_slist_reverse (result); +} + +/* Computes the width of the line as currently ellipsized + */ +static int +current_width (EllipsizeState *state) +{ + return state->total_width - (state->gap_end_x - state->gap_start_x) + state->ellipsis_width; +} + +/** + * _pango_layout_line_ellipsize: + * @line: a #PangoLayoutLine + * @attrs: Attributes being used for itemization/shaping + * + * Given a PangoLayoutLine with the runs still in logical order, ellipsize + * it according the layout's policy to fit within the set width of the layout. + **/ +void +_pango_layout_line_ellipsize (PangoLayoutLine *line, + PangoAttrList *attrs) +{ + EllipsizeState state; + + if (line->layout->ellipsize == PANGO_ELLIPSIZE_NONE || + line->layout->width < 0) + return; + + init_state (&state, line, attrs); + + if (state.total_width <= state.layout->width) + goto out; + + find_initial_span (&state); + + while (current_width (&state) > state.layout->width) + { + if (!remove_one_span (&state)) + break; + } + + fixup_ellipsis_run (&state); + + g_slist_free (line->runs); + line->runs = get_run_list (&state); + + out: + free_state (&state); +} diff --git a/pango/pango-glyph-item-private.h b/pango/pango-glyph-item-private.h new file mode 100644 index 00000000..245706ad --- /dev/null +++ b/pango/pango-glyph-item-private.h @@ -0,0 +1,64 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* Pango + * pango-glyph-item-private.h: Pair of PangoItem and a glyph string; private + * functionality + * + * Copyright (C) 2004 Red Hat Software + * + * 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. + */ + +#ifndef __PANGO_GLYPH_ITEM_PRIVATE_H__ +#define __PANGO_GLYPH_ITEM_PRIVATE_H__ + +#include <pango-glyph-item.h> + +G_BEGIN_DECLS + +/* Structure holding state when we're iterating over a GlyphItem. + * start_index/cluster_end (and range_start/range_end in + * apply_attrs()) are offsets into the text, so note the difference + * of glyph_item->item->offset between them and clusters in the + * log_clusters[] array. + */ +typedef struct _PangoGlyphItemIter PangoGlyphItemIter; + +struct _PangoGlyphItemIter +{ + PangoGlyphItem *glyph_item; + const gchar *text; + + int start_glyph; + int start_index; + int start_char; + + int end_glyph; + int end_index; + int end_char; +}; + +gboolean _pango_glyph_item_iter_init_start (PangoGlyphItemIter *iter, + PangoGlyphItem *glyph_item, + const char *text); +gboolean _pango_glyph_item_iter_init_end (PangoGlyphItemIter *iter, + PangoGlyphItem *glyph_item, + const char *text); +gboolean _pango_glyph_item_iter_next_cluster (PangoGlyphItemIter *iter); +gboolean _pango_glyph_item_iter_prev_cluster (PangoGlyphItemIter *iter); + +G_END_DECLS + +#endif /* __PANGO_GLYPH_ITEM_PRIVATE_H__ */ diff --git a/pango/pango-glyph-item.c b/pango/pango-glyph-item.c index af242b1e..1245ca8c 100644 --- a/pango/pango-glyph-item.c +++ b/pango/pango-glyph-item.c @@ -23,6 +23,7 @@ #include <string.h> #include "pango-glyph-item.h" +#include "pango-glyph-item-private.h" #define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0) @@ -126,45 +127,56 @@ pango_glyph_item_split (PangoGlyphItem *orig, return new; } -/* Structure holding state when we're iterating over a GlyphItem. - start_index/cluster_end (and range_start/range_end in - apply_attrs()) are offsets into the text, so note the difference - of glyph_item->item->offset between them and clusters in the - log_clusters[] array. - */ -typedef struct +/** + * pango_glyph_item_free: + * @glyph_item: a #PangoGlyphItem + * + * Frees a #PangoGlyphItem and memory to which it points. + **/ +void +pango_glyph_item_free (PangoGlyphItem *glyph_item) { - PangoGlyphItem *glyph_item; - const gchar *text; - - int start_glyph; - int start_index; - int start_char; + if (glyph_item->item) + pango_item_free (glyph_item->item); + if (glyph_item->glyphs) + pango_glyph_string_free (glyph_item->glyphs); - int end_glyph; - int end_index; - int end_char; -} GlyphItemIter; + g_free (glyph_item); +} -/* Advance to the next cluster, returns FALSE if - * we were already on the last cluster - */ -static gboolean -glyph_item_iter_next_cluster (GlyphItemIter *iter) +/** + * _pango_glyph_item_iter_next_cluster: + * @iter: a #PangoGlyphItemIter + * + * Advances the iterator to the next cluster in the glyph item. + * + * Return value: %TRUE if the iterator was advanced, %FALSE if we were already on the + * last cluster. + **/ +gboolean +_pango_glyph_item_iter_next_cluster (PangoGlyphItemIter *iter) { int glyph_index = iter->end_glyph; PangoGlyphString *glyphs = iter->glyph_item->glyphs; PangoItem *item = iter->glyph_item->item; - + + if (LTR (iter->glyph_item)) + { + if (glyph_index == glyphs->num_glyphs) + return FALSE; + } + else + { + if (glyph_index < 0) + return FALSE; + } + iter->start_glyph = iter->end_glyph; iter->start_index = iter->end_index; iter->start_char = iter->end_char; if (LTR (iter->glyph_item)) { - if (glyph_index == glyphs->num_glyphs) - return FALSE; - while (TRUE) { glyph_index++; @@ -187,9 +199,6 @@ glyph_item_iter_next_cluster (GlyphItemIter *iter) } else /* RTL */ { - if (glyph_index < 0) - return FALSE; - while (TRUE) { glyph_index--; @@ -215,11 +224,103 @@ glyph_item_iter_next_cluster (GlyphItemIter *iter) return TRUE; } -/* Returns FALSE if there are no clusters in the glyph item */ -static gboolean -glyph_item_iter_init (GlyphItemIter *iter, - PangoGlyphItem *glyph_item, - const char *text) +/** + * _pango_glyph_item_iter_prev_cluster: + * @iter: a #PangoGlyphItemIter + * + * Moves the iterator to the preceding cluster in the glyph item. + * + * Return value: %TRUE if the iterator was movedn, %FALSE if we were already on the + * first cluster. + **/ +gboolean +_pango_glyph_item_iter_prev_cluster (PangoGlyphItemIter *iter) +{ + int glyph_index = iter->start_glyph; + PangoGlyphString *glyphs = iter->glyph_item->glyphs; + PangoItem *item = iter->glyph_item->item; + + if (LTR (iter->glyph_item)) + { + if (glyph_index == 0) + return FALSE; + } + else + { + if (glyph_index == glyphs->num_glyphs - 1) + return FALSE; + + } + + iter->end_glyph = iter->start_glyph; + iter->end_index = iter->start_index; + iter->end_char = iter->start_char; + + if (LTR (iter->glyph_item)) + { + while (TRUE) + { + glyph_index--; + + if (glyph_index == 0) + { + iter->start_index = item->offset; + iter->start_char = 0; + break; + } + + if (item->offset + glyphs->log_clusters[glyph_index] < iter->end_index) + { + iter->start_index = item->offset + glyphs->log_clusters[glyph_index]; + iter->start_char -= g_utf8_strlen (iter->text + iter->start_index, + iter->end_index - iter->start_index); + break; + } + } + } + else /* RTL */ + { + while (TRUE) + { + glyph_index++; + + if (glyph_index == glyphs->num_glyphs - 1) + { + iter->start_index = item->offset; + iter->start_char = 0; + break; + } + + if (item->offset + glyphs->log_clusters[glyph_index] < iter->start_index) + { + iter->start_index = item->offset + glyphs->log_clusters[glyph_index]; + iter->start_char -= g_utf8_strlen (iter->text + iter->start_index, + iter->end_index - iter->start_index); + break; + } + } + } + + iter->start_glyph = glyph_index; + return TRUE; +} + +/** + * _pango_glyph_item_iter_init_start: + * @iter: pointer to a #PangoGlyphItemIter structure + * @glyph_item: the glyph item that the iter points into + * @text: text corresponding to the glyph item + * + * Initializes a #PangoGlyphItemIter structure to point to the + * first cluster in a glyph item. + * + * Return value: %FALSE if there are no clusters in the glyph item; + * in this case, the state of the iter is undefined. + **/ +gboolean +_pango_glyph_item_iter_init_start (PangoGlyphItemIter *iter, + PangoGlyphItem *glyph_item, + const char *text) { iter->glyph_item = glyph_item; iter->text = text; @@ -233,12 +334,44 @@ glyph_item_iter_init (GlyphItemIter *iter, iter->end_char = 0; /* Advance onto the first cluster of the glyph item */ - return glyph_item_iter_next_cluster (iter); + return _pango_glyph_item_iter_next_cluster (iter); +} + +/** + * _pango_glyph_item_iter_init_start: + * @iter: pointer to a #PangoGlyphItemIter structure + * @glyph_item: the glyph item that the iter points into + * @text: text corresponding to the glyph item + * + * Initializes a #PangoGlyphItemIter structure to point to the + * last cluster in a glyph item. + * + * Return value: %FALSE if there are no clusters in the glyph item; + * in this case, the state of the iter is undefined. + **/ +gboolean +_pango_glyph_item_iter_init_end (PangoGlyphItemIter *iter, + PangoGlyphItem *glyph_item, + const char *text) +{ + iter->glyph_item = glyph_item; + iter->text = text; + + if (LTR (glyph_item)) + iter->start_glyph = glyph_item->glyphs->num_glyphs; + else + iter->start_glyph = -1; + + iter->start_index = glyph_item->item->offset + glyph_item->item->length; + iter->start_char = glyph_item->item->num_chars; + + /* Advance onto the first cluster of the glyph item */ + return _pango_glyph_item_iter_prev_cluster (iter); } typedef struct { - GlyphItemIter iter; + PangoGlyphItemIter iter; GSList *segment_attrs; } ApplyAttrsState; @@ -368,9 +501,9 @@ pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item, range_end >= glyph_item->item->offset + glyph_item->item->length) goto out; - for (have_cluster = glyph_item_iter_init (&state.iter, glyph_item, text); + for (have_cluster = _pango_glyph_item_iter_init_start (&state.iter, glyph_item, text); have_cluster; - have_cluster = glyph_item_iter_next_cluster (&state.iter)) + have_cluster = _pango_glyph_item_iter_next_cluster (&state.iter)) { /* [range_start,range_end] is the first range that intersects @@ -471,12 +604,12 @@ pango_glyph_item_letter_space (PangoGlyphItem *glyph_item, PangoLogAttr *log_attrs, int letter_spacing) { - GlyphItemIter iter; + PangoGlyphItemIter iter; gboolean have_cluster; - for (have_cluster = glyph_item_iter_init (&iter, glyph_item, text); + for (have_cluster = _pango_glyph_item_iter_init_start (&iter, glyph_item, text); have_cluster; - have_cluster = glyph_item_iter_next_cluster (&iter)) + have_cluster = _pango_glyph_item_iter_next_cluster (&iter)) { if (iter.start_char > 0 && log_attrs[iter.start_char].is_cursor_position) diff --git a/pango/pango-glyph-item.h b/pango/pango-glyph-item.h index 01a57489..3be2b9e6 100644 --- a/pango/pango-glyph-item.h +++ b/pango/pango-glyph-item.h @@ -41,6 +41,7 @@ struct _PangoGlyphItem PangoGlyphItem *pango_glyph_item_split (PangoGlyphItem *orig, const char *text, int split_index); +void pango_glyph_item_free (PangoGlyphItem *glyph_item); GSList * pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item, const char *text, PangoAttrList *list); diff --git a/pango/pango-layout-private.h b/pango/pango-layout-private.h new file mode 100644 index 00000000..fa4f7b5d --- /dev/null +++ b/pango/pango-layout-private.h @@ -0,0 +1,71 @@ +/* Pango + * pango-layout-private.h: Internal structures of PangoLayout + * + * Copyright (C) 2004 Red Hat Software + * + * 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. + */ + +#ifndef __PANGO_LAYOUT_PRIVATE_H__ +#define __PANGO_LAYOUT_PRIVATE_H__ + +#include <pango/pango-layout.h> + +G_BEGIN_DECLS + +struct _PangoLayout +{ + GObject parent_instance; + + /* If you add fields to PangoLayout, be sure to update both + * the _copy function + */ + + PangoContext *context; + PangoAttrList *attrs; + PangoFontDescription *font_desc; + + gchar *text; + int length; /* length of text in bytes */ + int width; /* wrap width, in device units */ + int indent; /* amount by which first line should be shorter */ + int spacing; /* spacing between lines */ + + guint justify : 1; + guint alignment : 2; + + guint single_paragraph : 1; + guint auto_dir : 1; + + guint wrap : 2; /* PangoWrapMode */ + guint ellipsize : 2; /* PangoEllipsizeMode */ + + gint n_chars; /* Total number of characters in layout */ + PangoLogAttr *log_attrs; /* Logical attributes for layout's text */ + + int tab_width; /* Cached width of a tab. -1 == not yet calculated */ + + PangoTabArray *tabs; + + GSList *lines; +}; + +G_END_DECLS + +void _pango_layout_line_ellipsize (PangoLayoutLine *line, + PangoAttrList *attrs); + +#endif /* __PANGO_LAYOUT_PRIVATE_H__ */ diff --git a/pango/pango-layout.c b/pango/pango-layout.c index 533cf833..fe8ef25d 100644 --- a/pango/pango-layout.c +++ b/pango/pango-layout.c @@ -20,16 +20,18 @@ */ #include <pango/pango-glyph.h> /* For pango_shape() */ -#include <pango/pango-layout.h> #include <pango/pango-break.h> #include <pango/pango-item.h> #include <pango/pango-engine.h> #include <string.h> +#include "pango-layout-private.h" + #define LINE_IS_VALID(line) ((line)->layout != NULL) typedef struct _Extents Extents; typedef struct _ItemProperties ItemProperties; +typedef struct _ParaBreakState ParaBreakState; struct _Extents { @@ -103,42 +105,6 @@ struct _PangoLayoutLinePrivate guint ref_count; }; -struct _PangoLayout -{ - GObject parent_instance; - - /* If you add fields to PangoLayout, be sure to update both - * the _copy function - */ - - PangoContext *context; - PangoAttrList *attrs; - PangoFontDescription *font_desc; - - gchar *text; - int length; /* length of text in bytes */ - int width; /* wrap width, in device units */ - int indent; /* amount by which first line should be shorter */ - int spacing; /* spacing between lines */ - - guint justify : 1; - guint alignment : 2; - - guint single_paragraph : 1; - guint auto_dir : 1; - - gint n_chars; /* Total number of characters in layout */ - PangoLogAttr *log_attrs; /* Logical attributes for layout's text */ - - int tab_width; /* Cached width of a tab. -1 == not yet calculated */ - - PangoTabArray *tabs; - - GSList *lines; - - PangoWrapMode wrap; -}; - struct _PangoLayoutClass { GObjectClass parent_class; @@ -152,7 +118,8 @@ static void pango_layout_check_lines (PangoLayout *layout); static PangoAttrList *pango_layout_get_effective_attributes (PangoLayout *layout); static PangoLayoutLine * pango_layout_line_new (PangoLayout *layout); -static void pango_layout_line_postprocess (PangoLayoutLine *line); +static void pango_layout_line_postprocess (PangoLayoutLine *line, + ParaBreakState *state); static int *pango_layout_line_get_log2vis_map (PangoLayoutLine *line, gboolean strong); @@ -186,6 +153,7 @@ pango_layout_init (PangoLayout *layout) layout->tab_width = -1; layout->wrap = PANGO_WRAP_WORD; + layout->ellipsize = PANGO_ELLIPSIZE_NONE; } static void @@ -285,6 +253,7 @@ pango_layout_copy (PangoLayout *src) if (src->tabs) layout->tabs = pango_tab_array_copy (src->tabs); layout->wrap = src->wrap; + layout->ellipsize = src->ellipsize; /* log_attrs, lines fields are updated by check_lines */ @@ -715,11 +684,17 @@ pango_layout_set_single_paragraph_mode (PangoLayout *layout, gboolean setting) { g_return_if_fail (PANGO_IS_LAYOUT (layout)); - - layout->single_paragraph = setting; - pango_layout_clear_lines (layout); + setting = setting != FALSE; + + if (layout->single_paragraph != setting) + { + layout->single_paragraph = setting; + + pango_layout_clear_lines (layout); + } } + /** * pango_layout_get_single_paragraph_mode: * @layout: a #PangoLayout @@ -729,7 +704,6 @@ pango_layout_set_single_paragraph_mode (PangoLayout *layout, * Return value: %TRUE if the layout does not break paragraphs at * paragraph separator characters **/ - gboolean pango_layout_get_single_paragraph_mode (PangoLayout *layout) { @@ -739,6 +713,53 @@ pango_layout_get_single_paragraph_mode (PangoLayout *layout) } /** + * pango_layout_get_ellipsize: + * @layout: a #PangoLayout + * @ellipsize: the new ellipsization mode for @layout + * + * Sets the type of ellipsization being performed for @layout. + * Depending on the ellipsization mode @ellipsize text is + * removed from the start, middle, or end of lines so they + * fit within the width of layout set with pango_layout_set_width (). + * + * If the layout contains characters such as newlines that + * force it to be layed out in multiple lines, then each line + * is ellipsized separately. + * + * Return value: Gets the current ellipsization mode for the layout. + **/ +void +pango_layout_set_ellipsize (PangoLayout *layout, + PangoEllipsizeMode ellipsize) +{ + g_return_if_fail (PANGO_IS_LAYOUT (layout)); + + if (ellipsize != layout->ellipsize) + { + layout->ellipsize = ellipsize; + + pango_layout_clear_lines (layout); + } +} + +/** + * pango_layout_get_ellipsize: + * @layout: a #PangoLayout + * + * Gets the type of ellipsization being performed for @layout. + * See pango_layout_get_ellipsize() + * + * Return value: the current ellipsization mode for @layout + **/ +PangoEllipsizeMode +pango_layout_get_ellipsize (PangoLayout *layout) +{ + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_ELLIPSIZE_NONE); + + return layout->ellipsize; +} + +/** * pango_layout_set_text: * @layout: a #PangoLayout * @text: a UTF8-string @@ -2473,28 +2494,35 @@ get_tab_pos (PangoLayout *layout, int index) } } -static void -shape_tab (PangoLayoutLine *line, - PangoGlyphString *glyphs) +static int +line_width (PangoLayoutLine *line) { + GSList *l; int i; - GSList *tmp_list; - - int current_width = 0; - + int width = 0; + /* Compute the width of the line currently - inefficient, but easier * than keeping the current width of the line up to date everywhere */ - tmp_list = line->runs; - while (tmp_list) + for (l = line->runs; l; l = l->next) { - PangoLayoutRun *run = tmp_list->data; - for (i=0; i < run->glyphs->num_glyphs; i++) - current_width += run->glyphs->glyphs[i].geometry.width; + PangoLayoutRun *run = l->data; - tmp_list = tmp_list->next; + for (i=0; i < run->glyphs->num_glyphs; i++) + width += run->glyphs->glyphs[i].geometry.width; } - + + return width; +} + +static void +shape_tab (PangoLayoutLine *line, + PangoGlyphString *glyphs) +{ + int i; + + int current_width = line_width (line); + pango_glyph_string_set_size (glyphs, 1); glyphs->glyphs[0].glyph = 0; @@ -2567,10 +2595,9 @@ typedef enum BREAK_LINE_SEPARATOR } BreakResult; -typedef struct _ParaBreakState ParaBreakState; - struct _ParaBreakState { + PangoAttrList *attrs; /* Attributes being used for itemization */ GList *items; /* This paragraph turned into items */ PangoDirection base_dir; /* Current resolved base direction */ gboolean first_line; /* TRUE if this is the first line of the paragraph */ @@ -2853,13 +2880,15 @@ process_line (PangoLayout *layout, int break_remaining_width = 0; /* Remaining width before adding run with break */ int break_start_offset = 0; /* Start width before adding run with break */ GSList *break_link = NULL; /* Link holding run before break */ - + line = pango_layout_line_new (layout); line->start_index = state->line_start_index; line->is_paragraph_start = state->first_line; line->resolved_dir = state->base_dir; - if (state->first_line) + if (layout->ellipsize != PANGO_ELLIPSIZE_NONE) + state->remaining_width = -1; + else if (state->first_line) state->remaining_width = (layout->indent >= 0) ? layout->width - layout->indent : layout->width; else state->remaining_width = (layout->indent >= 0) ? layout->width : layout->width + layout->indent; @@ -2928,7 +2957,7 @@ process_line (PangoLayout *layout, } done: - pango_layout_line_postprocess (line); + pango_layout_line_postprocess (line, state); layout->lines = g_slist_prepend (layout->lines, line); state->first_line = FALSE; state->line_start_index += line->length; @@ -3149,6 +3178,7 @@ pango_layout_check_lines (PangoLayout *layout) g_assert (delim_len < 4); /* PS is 3 bytes */ g_assert (delim_len >= 0); + state.attrs = attrs; state.items = pango_itemize_with_base_dir (layout->context, base_dir, layout->text, @@ -4116,7 +4146,8 @@ adjust_line_letter_spacing (PangoLayoutLine *line) } static void -pango_layout_line_postprocess (PangoLayoutLine *line) +pango_layout_line_postprocess (PangoLayoutLine *line, + ParaBreakState *state) { /* NB: the runs are in reverse order at this point, since we prepended them to the list */ @@ -4125,6 +4156,10 @@ pango_layout_line_postprocess (PangoLayoutLine *line) */ line->runs = g_slist_reverse (line->runs); + /* Ellipsize the line if necessary + */ + _pango_layout_line_ellipsize (line, state->attrs); + /* Now convert logical to visual order */ pango_layout_line_reorder (line); diff --git a/pango/pango-layout.h b/pango/pango-layout.h index d9447fca..ac501fe1 100644 --- a/pango/pango-layout.h +++ b/pango/pango-layout.h @@ -48,6 +48,26 @@ typedef enum { PANGO_WRAP_WORD_CHAR } PangoWrapMode; +/** + * PangoEllipsizeMode + * @PANGO_ELLIPSIZE_NONE: No ellipsization + * @PANGO_ELLIPSIZE_START: Omit characters at the start of the text + * @PANGO_ELLIPSIZE_MIDDLE: Omit characters in the middle of the text + * @PANGO_ELLIPSIZE_END: Omit characters at the end of the text + * + * The #PangoEllipsizeMode type describes what sort of (if any) + * ellipsization should be applied to a line of text. In + * the ellipsization process characters are removed from the + * text in order to make it fit to a given width and replaced + * with an ellipsis. + */ +typedef enum { + PANGO_ELLIPSIZE_NONE, + PANGO_ELLIPSIZE_START, + PANGO_ELLIPSIZE_MIDDLE, + PANGO_ELLIPSIZE_END +} PangoEllipsizeMode; + struct _PangoLayoutLine { PangoLayout *layout; @@ -127,6 +147,10 @@ void pango_layout_set_single_paragraph_mode (PangoLayout gboolean setting); gboolean pango_layout_get_single_paragraph_mode (PangoLayout *layout); +void pango_layout_set_ellipsize (PangoLayout *layout, + PangoEllipsizeMode ellipsize); +PangoEllipsizeMode pango_layout_get_ellipsize (PangoLayout *layout); + void pango_layout_context_changed (PangoLayout *layout); void pango_layout_get_log_attrs (PangoLayout *layout, |