diff options
author | Matthias Clasen <mclasen@redhat.com> | 2022-07-04 11:16:05 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2022-07-04 11:17:21 -0400 |
commit | bcdce34c8ac0adffd589e9fb68b62ff09368bb0f (patch) | |
tree | 7b92ba875b22212d15ffb9d9620d517cfa427a1e /pango/ellipsize.c | |
parent | b197cc08b18bc44a3e93f859ca7e61dfe335bbc5 (diff) | |
download | pango-bcdce34c8ac0adffd589e9fb68b62ff09368bb0f.tar.gz |
Move the code from pango/ to pango2/
Life is easier if the src path matches the installed
header path prefix, so make them match. Update all
users.
Diffstat (limited to 'pango/ellipsize.c')
-rw-r--r-- | pango/ellipsize.c | 797 |
1 files changed, 0 insertions, 797 deletions
diff --git a/pango/ellipsize.c b/pango/ellipsize.c deleted file mode 100644 index a5f74131..00000000 --- a/pango/ellipsize.c +++ /dev/null @@ -1,797 +0,0 @@ -/* Pango2 - * 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 "config.h" -#include <string.h> - -#include "pango-item-private.h" -#include "pango-glyph-item-private.h" -#include "pango-glyph-iter-private.h" -#include "pango-font-private.h" -#include "pango-attributes-private.h" -#include "pango-attr-private.h" -#include "pango-attr-list-private.h" -#include "pango-attr-iterator-private.h" -#include "pango-impl-utils.h" -#include "pango-line-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 gap 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 -{ - Pango2GlyphItem *run; - int start_offset; /* Character offset of run start */ - int width; /* Width of run in Pango2 units */ -}; - -/* Iterator to a position within the ellipsized line */ -struct _LineIter -{ - Pango2GlyphItemIter run_iter; - int run_index; -}; - -/* State of ellipsization process */ -struct _EllipsizeState -{ - Pango2Context *context; - const char *text; - Pango2LogAttr *log_attrs; - Pango2EllipsizeMode ellipsize; - Pango2AttrList *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 Pango2 units */ - int gap_center; /* Goal for center of gap */ - - Pango2GlyphItem *ellipsis_run; /* Run created to hold ellipsis */ - int ellipsis_width; /* Width of ellipsis, in Pango2 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 - */ - - Pango2AttrIterator *line_start_attr; /* Cached Pango2AttrIterator 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 Pango2 units */ - Pango2AttrIterator *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 Pango2 units */ - - Pango2ShapeFlags shape_flags; -}; - -static void -init_state (EllipsizeState *state, - Pango2Context *context, - const char *text, - int start_index, - Pango2LogAttr *log_attrs, - Pango2EllipsizeMode ellipsize, - GSList *runs, - Pango2AttrList *attrs, - Pango2ShapeFlags shape_flags) -{ - GSList *l; - int i; - int start_offset; - - state->context = context; - state->text = text; - state->log_attrs = log_attrs; - state->ellipsize = ellipsize; - - if (attrs) - state->attrs = pango2_attr_list_ref (attrs); - else - state->attrs = pango2_attr_list_new (); - - state->shape_flags = shape_flags; - - state->n_runs = g_slist_length (runs); - state->run_info = g_new (RunInfo, state->n_runs); - - start_offset = pango2_utf8_strlen (state->text, start_index); - - state->total_width = 0; - for (l = runs, i = 0; l; l = l->next, i++) - { - Pango2GlyphItem *run = l->data; - int width = pango2_glyph_string_get_width (run->glyphs); - 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->ellipsis_is_cjk = FALSE; - state->line_start_attr = NULL; - state->gap_start_attr = NULL; -} - -/* Cleanup memory allocation - */ -static void -free_state (EllipsizeState *state) -{ - pango2_attr_list_unref (state->attrs); - if (state->line_start_attr) - pango2_attr_iterator_destroy (state->line_start_attr); - if (state->gap_start_attr) - pango2_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) -{ - Pango2GlyphItemIter *run_iter = &iter->run_iter; - Pango2GlyphString *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[i].geometry.width; - } - else /* RTL */ - { - for (i = run_iter->start_glyph; i > run_iter->end_glyph; i--) - width += glyphs->glyphs[i].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 (!pango2_glyph_item_iter_next_cluster (&iter->run_iter)) - { - if (iter->run_index == state->n_runs - 1) - return FALSE; - else - { - iter->run_index++; - pango2_glyph_item_iter_init_start (&iter->run_iter, - state->run_info[iter->run_index].run, - state->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 (!pango2_glyph_item_iter_prev_cluster (&iter->run_iter)) - { - if (iter->run_index == 0) - return FALSE; - else - { - iter->run_index--; - pango2_glyph_item_iter_init_end (&iter->run_iter, - state->run_info[iter->run_index].run, - state->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 `Pango2GlyphVisAttr` - * - */ - -/* 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->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->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 Pango2Item * -itemize_text (EllipsizeState *state, - const char *text, - Pango2AttrList *attrs) -{ - GList *items; - Pango2Item *item; - Pango2Direction dir; - - dir = pango2_context_get_base_dir (state->context); - items = pango2_itemize (state->context, dir, text, 0, strlen (text), attrs); - 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) -{ - Pango2AttrList attrs; - GSList *run_attrs; - Pango2Item *item; - Pango2GlyphString *glyphs; - GSList *l; - Pango2Attribute *fallback; - const char *ellipsis_text; - int len; - int i; - - pango2_attr_list_init (&attrs); - - /* Create/reset state->ellipsis_run - */ - if (!state->ellipsis_run) - { - state->ellipsis_run = g_slice_new0 (Pango2GlyphItem); - state->ellipsis_run->glyphs = pango2_glyph_string_new (); - } - - if (state->ellipsis_run->item) - { - pango2_item_free (state->ellipsis_run->item); - state->ellipsis_run->item = NULL; - } - - /* Create an attribute list - */ - run_attrs = pango2_attr_iterator_get_attrs (state->gap_start_attr); - int s, e; - pango2_attr_iterator_range (state->gap_start_attr, &s, &e); - for (l = run_attrs; l; l = l->next) - { - Pango2Attribute *attr = l->data; - attr->start_index = PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING; - attr->end_index = PANGO2_ATTR_INDEX_TO_TEXT_END; - - if (pango2_attribute_affects_itemization (attr, NULL) || - pango2_attribute_affects_break_or_shape (attr, NULL)) - pango2_attr_list_insert (&attrs, attr); - else - pango2_attribute_destroy (attr); - } - - g_slist_free (run_attrs); - - fallback = pango2_attr_fallback_new (FALSE); - fallback->start_index = 0; - fallback->end_index = G_MAXINT; - pango2_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 (!item->analysis.font || - !pango2_font_face_has_char (item->analysis.font->face, - g_utf8_get_char (ellipsis_text))) - { - pango2_item_free (item); - - /* Modify the fallback iter while it is inside the Pango2AttrList; Don't try this at home - */ - fallback->int_value = TRUE; - - ellipsis_text = "..."; - item = itemize_text (state, ellipsis_text, &attrs); - } - - pango2_attr_list_destroy (&attrs); - - state->ellipsis_run->item = item; - - /* Now shape - */ - glyphs = state->ellipsis_run->glyphs; - - len = strlen (ellipsis_text); - pango2_shape (ellipsis_text, len, - ellipsis_text, len, - &item->analysis, glyphs, - state->shape_flags); - - 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 Pango2AttrIterator to a particular - * byte index. - */ -static void -advance_iterator_to (Pango2AttrIterator *iter, - int new_index) -{ - int start, end; - - do - { - pango2_attr_iterator_range (iter, &start, &end); - if (end > new_index) - break; - } - while (pango2_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 Pango2AttrIterator 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 = pango2_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; - - pango2_attr_iterator_range (state->gap_start_attr, &start, &end); - - if (state->gap_start_iter.run_iter.start_index < start) - { - pango2_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 = pango2_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->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) -{ - Pango2GlyphItem *glyph_item; - Pango2GlyphItemIter *run_iter; - gboolean have_cluster; - int i; - int x; - int cluster_width; - - switch (state->ellipsize) - { - case PANGO2_ELLIPSIZE_NONE: - default: - g_assert_not_reached (); - case PANGO2_ELLIPSIZE_START: - state->gap_center = 0; - break; - case PANGO2_ELLIPSIZE_MIDDLE: - state->gap_center = state->total_width / 2; - break; - case PANGO2_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 = pango2_glyph_item_iter_init_start (run_iter, glyph_item, state->text); - have_cluster; - have_cluster = pango2_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; - int width; - - /* 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; - width = get_cluster_width (&new_gap_start_iter); - new_gap_start_x -= width; - } - while (!starts_at_ellipsization_boundary (state, &new_gap_start_iter) || - width == 0); - - 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; - width = get_cluster_width (&new_gap_end_iter); - new_gap_end_x += width; - } - while (!ends_at_ellipsization_boundary (state, &new_gap_end_iter) || - width == 0); - - 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, - int extra_width) -{ - Pango2GlyphString *glyphs = state->ellipsis_run->glyphs; - Pango2Item *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; - - glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += extra_width; - - /* 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 = pango2_utf8_strlen (state->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; - - item->analysis.flags |= PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS; -} - -/* Computes the new list of runs for the line - */ -static GSList * -get_run_list (EllipsizeState *state) -{ - Pango2GlyphItem *partial_start_run = NULL; - Pango2GlyphItem *partial_end_run = NULL; - GSList *result = NULL; - RunInfo *run_info; - Pango2GlyphItemIter *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 = pango2_glyph_item_split (run_info->run, state->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 = pango2_glyph_item_split (run_info->run, state->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++) - pango2_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; -} - -void -pango2_line_ellipsize (Pango2Line *line, - Pango2Context *context, - Pango2EllipsizeMode ellipsize, - int goal_width) -{ - EllipsizeState state; - const char *text = line->data->text; - Pango2AttrList *attrs = line->data->attrs; - Pango2LogAttr *log_attrs = line->data->log_attrs; - Pango2ShapeFlags shape_flags; - - g_return_if_fail (ellipsize != PANGO2_ELLIPSIZE_NONE && goal_width >= 0); - - if (pango2_context_get_round_glyph_positions (context)) - shape_flags = PANGO2_SHAPE_ROUND_POSITIONS; - else - shape_flags = PANGO2_SHAPE_NONE; - - init_state (&state, context, text, line->start_index, log_attrs, ellipsize, line->runs, attrs, shape_flags); - - if (state.total_width <= goal_width) - goto out; - - find_initial_span (&state); - - while (current_width (&state) > goal_width) - { - if (!remove_one_span (&state)) - break; - } - - fixup_ellipsis_run (&state, MAX (goal_width - current_width (&state), 0)); - - g_slist_free (line->runs); - line->runs = get_run_list (&state); - line->ellipsized = TRUE; - - out: - free_state (&state); -} |