📄 ellipsize.c
字号:
/* 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 <config.h>#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 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{ 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 voidinit_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); 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->ellipsis_is_cjk = FALSE; state->line_start_attr = NULL; state->gap_start_attr = NULL;}/* Cleanup memory allocation */static voidfree_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 intget_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[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 gbooleanline_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 gbooleanline_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 gbooleanstarts_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 gbooleanends_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 voidshape_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_slice_new (PangoGlyphItem); 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 (!item->analysis.font || !_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 = TRUE; 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 voidadvance_iterator_to (PangoAttrIterator *iter,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -