1/* Pango
2 * ellipsize.c: Routine to ellipsize layout lines
3 *
4 * Copyright (C) 2004 Red Hat Software
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
21
22#include "config.h"
23#include <string.h>
24
25#include "pango-glyph-item.h"
26#include "pango-layout-private.h"
27#include "pango-font-private.h"
28#include "pango-attributes-private.h"
29#include "pango-impl-utils.h"
30
31typedef struct _EllipsizeState EllipsizeState;
32typedef struct _RunInfo RunInfo;
33typedef struct _LineIter LineIter;
34
35
36/* Overall, the way we ellipsize is we grow a "gap" out from an original
37 * gap center position until:
38 *
39 * line_width - gap_width + ellipsize_width <= goal_width
40 *
41 * Line: [-------------------------------------------]
42 * Runs: [------)[---------------)[------------------]
43 * Gap center: *
44 * Gap: [----------------------]
45 *
46 * The gap center may be at the start or end in which case the gap grows
47 * in only one direction.
48 *
49 * Note the line and last run are logically closed at the end; this allows
50 * us to use a gap position at x=line_width and still have it be part of
51 * of a run.
52 *
53 * We grow the gap out one "span" at a time, where a span is simply a
54 * consecutive run of clusters that we can't interrupt with an ellipsis.
55 *
56 * When choosing whether to grow the gap at the start or the end, we
57 * calculate the next span to remove in both directions and see which
58 * causes the smaller increase in:
59 *
60 * MAX (gap_end - gap_center, gap_start - gap_center)
61 *
62 * All computations are done using logical order; the ellipsization
63 * process occurs before the runs are ordered into visual order.
64 */
65
66/* Keeps information about a single run */
67struct _RunInfo
68{
69 PangoGlyphItem *run;
70 int start_offset; /* Character offset of run start */
71 int width; /* Width of run in Pango units */
72};
73
74/* Iterator to a position within the ellipsized line */
75struct _LineIter
76{
77 PangoGlyphItemIter run_iter;
78 int run_index;
79};
80
81/* State of ellipsization process */
82struct _EllipsizeState
83{
84 PangoLayout *layout; /* Layout being ellipsized */
85 PangoAttrList *attrs; /* Attributes used for itemization/shaping */
86
87 RunInfo *run_info; /* Array of information about each run */
88 int n_runs;
89
90 int total_width; /* Original width of line in Pango units */
91 int gap_center; /* Goal for center of gap */
92
93 PangoGlyphItem *ellipsis_run; /* Run created to hold ellipsis */
94 int ellipsis_width; /* Width of ellipsis, in Pango units */
95 int ellipsis_is_cjk; /* Whether the first character in the ellipsized
96 * is wide; this triggers us to try to use a
97 * mid-line ellipsis instead of a baseline
98 */
99
100 PangoAttrIterator *line_start_attr; /* Cached PangoAttrIterator for the start of the run */
101
102 LineIter gap_start_iter; /* Iteratator pointig to the first cluster in gap */
103 int gap_start_x; /* x position of start of gap, in Pango units */
104 PangoAttrIterator *gap_start_attr; /* Attribute iterator pointing to a range containing
105 * the first character in gap */
106
107 LineIter gap_end_iter; /* Iterator pointing to last cluster in gap */
108 int gap_end_x; /* x position of end of gap, in Pango units */
109
110 PangoShapeFlags shape_flags;
111};
112
113/* Compute global information needed for the itemization process
114 */
115static void
116init_state (EllipsizeState *state,
117 PangoLayoutLine *line,
118 PangoAttrList *attrs,
119 PangoShapeFlags shape_flags)
120{
121 GSList *l;
122 int i;
123 int start_offset;
124
125 state->layout = line->layout;
126 if (attrs)
127 state->attrs = pango_attr_list_ref (list: attrs);
128 else
129 state->attrs = pango_attr_list_new ();
130
131 state->shape_flags = shape_flags;
132
133 state->n_runs = g_slist_length (list: line->runs);
134 state->run_info = g_new (RunInfo, state->n_runs);
135
136 start_offset = pango_utf8_strlen (p: line->layout->text,
137 max: line->start_index);
138
139 state->total_width = 0;
140 for (l = line->runs, i = 0; l; l = l->next, i++)
141 {
142 PangoGlyphItem *run = l->data;
143 int width = pango_glyph_string_get_width (glyphs: run->glyphs);
144 state->run_info[i].run = run;
145 state->run_info[i].width = width;
146 state->run_info[i].start_offset = start_offset;
147 state->total_width += width;
148
149 start_offset += run->item->num_chars;
150 }
151
152 state->ellipsis_run = NULL;
153 state->ellipsis_is_cjk = FALSE;
154 state->line_start_attr = NULL;
155 state->gap_start_attr = NULL;
156}
157
158/* Cleanup memory allocation
159 */
160static void
161free_state (EllipsizeState *state)
162{
163 pango_attr_list_unref (list: state->attrs);
164 if (state->line_start_attr)
165 pango_attr_iterator_destroy (iterator: state->line_start_attr);
166 if (state->gap_start_attr)
167 pango_attr_iterator_destroy (iterator: state->gap_start_attr);
168 g_free (mem: state->run_info);
169}
170
171/* Computes the width of a single cluster
172 */
173static int
174get_cluster_width (LineIter *iter)
175{
176 PangoGlyphItemIter *run_iter = &iter->run_iter;
177 PangoGlyphString *glyphs = run_iter->glyph_item->glyphs;
178 int width = 0;
179 int i;
180
181 if (run_iter->start_glyph < run_iter->end_glyph) /* LTR */
182 {
183 for (i = run_iter->start_glyph; i < run_iter->end_glyph; i++)
184 width += glyphs->glyphs[i].geometry.width;
185 }
186 else /* RTL */
187 {
188 for (i = run_iter->start_glyph; i > run_iter->end_glyph; i--)
189 width += glyphs->glyphs[i].geometry.width;
190 }
191
192 return width;
193}
194
195/* Move forward one cluster. Returns %FALSE if we were already at the end
196 */
197static gboolean
198line_iter_next_cluster (EllipsizeState *state,
199 LineIter *iter)
200{
201 if (!pango_glyph_item_iter_next_cluster (iter: &iter->run_iter))
202 {
203 if (iter->run_index == state->n_runs - 1)
204 return FALSE;
205 else
206 {
207 iter->run_index++;
208 pango_glyph_item_iter_init_start (iter: &iter->run_iter,
209 glyph_item: state->run_info[iter->run_index].run,
210 text: state->layout->text);
211 }
212 }
213
214 return TRUE;
215}
216
217/* Move backward one cluster. Returns %FALSE if we were already at the end
218 */
219static gboolean
220line_iter_prev_cluster (EllipsizeState *state,
221 LineIter *iter)
222{
223 if (!pango_glyph_item_iter_prev_cluster (iter: &iter->run_iter))
224 {
225 if (iter->run_index == 0)
226 return FALSE;
227 else
228 {
229 iter->run_index--;
230 pango_glyph_item_iter_init_end (iter: &iter->run_iter,
231 glyph_item: state->run_info[iter->run_index].run,
232 text: state->layout->text);
233 }
234 }
235
236 return TRUE;
237}
238
239/*
240 * An ellipsization boundary is defined by two things
241 *
242 * - Starts a cluster - forced by structure of code
243 * - Starts a grapheme - checked here
244 *
245 * In the future we'd also like to add a check for cursive connectivity here.
246 * This should be an addition to `PangoGlyphVisAttr`
247 *
248 */
249
250/* Checks if there is a ellipsization boundary before the cluster @iter points to
251 */
252static gboolean
253starts_at_ellipsization_boundary (EllipsizeState *state,
254 LineIter *iter)
255{
256 RunInfo *run_info = &state->run_info[iter->run_index];
257
258 if (iter->run_iter.start_char == 0 && iter->run_index == 0)
259 return TRUE;
260
261 return state->layout->log_attrs[run_info->start_offset + iter->run_iter.start_char].is_cursor_position;
262}
263
264/* Checks if there is a ellipsization boundary after the cluster @iter points to
265 */
266static gboolean
267ends_at_ellipsization_boundary (EllipsizeState *state,
268 LineIter *iter)
269{
270 RunInfo *run_info = &state->run_info[iter->run_index];
271
272 if (iter->run_iter.end_char == run_info->run->item->num_chars && iter->run_index == state->n_runs - 1)
273 return TRUE;
274
275 return state->layout->log_attrs[run_info->start_offset + iter->run_iter.end_char + 1].is_cursor_position;
276}
277
278/* Helper function to re-itemize a string of text
279 */
280static PangoItem *
281itemize_text (EllipsizeState *state,
282 const char *text,
283 PangoAttrList *attrs)
284{
285 GList *items;
286 PangoItem *item;
287
288 items = pango_itemize (context: state->layout->context, text, start_index: 0, length: strlen (s: text), attrs, NULL);
289 g_assert (g_list_length (items) == 1);
290
291 item = items->data;
292 g_list_free (list: items);
293
294 return item;
295}
296
297/* Shapes the ellipsis using the font and is_cjk information computed by
298 * update_ellipsis_shape() from the first character in the gap.
299 */
300static void
301shape_ellipsis (EllipsizeState *state)
302{
303 PangoAttrList attrs;
304 GSList *run_attrs;
305 PangoItem *item;
306 PangoGlyphString *glyphs;
307 GSList *l;
308 PangoAttribute *fallback;
309 const char *ellipsis_text;
310 int len;
311 int i;
312
313 _pango_attr_list_init (list: &attrs);
314
315 /* Create/reset state->ellipsis_run
316 */
317 if (!state->ellipsis_run)
318 {
319 state->ellipsis_run = g_slice_new0 (PangoGlyphItem);
320 state->ellipsis_run->glyphs = pango_glyph_string_new ();
321 }
322
323 if (state->ellipsis_run->item)
324 {
325 pango_item_free (item: state->ellipsis_run->item);
326 state->ellipsis_run->item = NULL;
327 }
328
329 /* Create an attribute list
330 */
331 run_attrs = pango_attr_iterator_get_attrs (iterator: state->gap_start_attr);
332 for (l = run_attrs; l; l = l->next)
333 {
334 PangoAttribute *attr = l->data;
335 attr->start_index = 0;
336 attr->end_index = G_MAXINT;
337
338 pango_attr_list_insert (list: &attrs, attr);
339 }
340
341 g_slist_free (list: run_attrs);
342
343 fallback = pango_attr_fallback_new (FALSE);
344 fallback->start_index = 0;
345 fallback->end_index = G_MAXINT;
346 pango_attr_list_insert (list: &attrs, attr: fallback);
347
348 /* First try using a specific ellipsis character in the best matching font
349 */
350 if (state->ellipsis_is_cjk)
351 ellipsis_text = "\342\213\257"; /* U+22EF: MIDLINE HORIZONTAL ELLIPSIS, used for CJK */
352 else
353 ellipsis_text = "\342\200\246"; /* U+2026: HORIZONTAL ELLIPSIS */
354
355 item = itemize_text (state, text: ellipsis_text, attrs: &attrs);
356
357 /* If that fails we use "..." in the first matching font
358 */
359 if (!item->analysis.font ||
360 !pango_font_has_char (font: item->analysis.font,
361 wc: g_utf8_get_char (p: ellipsis_text)))
362 {
363 pango_item_free (item);
364
365 /* Modify the fallback iter while it is inside the PangoAttrList; Don't try this at home
366 */
367 ((PangoAttrInt *)fallback)->value = TRUE;
368
369 ellipsis_text = "...";
370 item = itemize_text (state, text: ellipsis_text, attrs: &attrs);
371 }
372
373 _pango_attr_list_destroy (list: &attrs);
374
375 state->ellipsis_run->item = item;
376
377 /* Now shape
378 */
379 glyphs = state->ellipsis_run->glyphs;
380
381 len = strlen (s: ellipsis_text);
382 pango_shape_with_flags (item_text: ellipsis_text, item_length: len,
383 paragraph_text: ellipsis_text, paragraph_length: len,
384 analysis: &item->analysis, glyphs,
385 flags: state->shape_flags);
386
387 state->ellipsis_width = 0;
388 for (i = 0; i < glyphs->num_glyphs; i++)
389 state->ellipsis_width += glyphs->glyphs[i].geometry.width;
390}
391
392/* Helper function to advance a PangoAttrIterator to a particular
393 * byte index.
394 */
395static void
396advance_iterator_to (PangoAttrIterator *iter,
397 int new_index)
398{
399 int start, end;
400
401 do
402 {
403 pango_attr_iterator_range (iterator: iter, start: &start, end: &end);
404 if (end > new_index)
405 break;
406 }
407 while (pango_attr_iterator_next (iterator: iter));
408}
409
410/* Updates the shaping of the ellipsis if necessary when we move the
411 * position of the start of the gap.
412 *
413 * The shaping of the ellipsis is determined by two things:
414 *
415 * - The font attributes applied to the first character in the gap
416 * - Whether the first character in the gap is wide or not. If the
417 * first character is wide, then we assume that we are ellipsizing
418 * East-Asian text, so prefer a mid-line ellipsizes to a baseline
419 * ellipsis, since that's typical practice for Chinese/Japanese/Korean.
420 */
421static void
422update_ellipsis_shape (EllipsizeState *state)
423{
424 gboolean recompute = FALSE;
425 gunichar start_wc;
426 gboolean is_cjk;
427
428 /* Unfortunately, we can only advance PangoAttrIterator forward; so each
429 * time we back up we need to go forward to find the new position. To make
430 * this not utterly slow, we cache an iterator at the start of the line
431 */
432 if (!state->line_start_attr)
433 {
434 state->line_start_attr = pango_attr_list_get_iterator (list: state->attrs);
435 advance_iterator_to (iter: state->line_start_attr, new_index: state->run_info[0].run->item->offset);
436 }
437
438 if (state->gap_start_attr)
439 {
440 /* See if the current attribute range contains the new start position
441 */
442 int start, end;
443
444 pango_attr_iterator_range (iterator: state->gap_start_attr, start: &start, end: &end);
445
446 if (state->gap_start_iter.run_iter.start_index < start)
447 {
448 pango_attr_iterator_destroy (iterator: state->gap_start_attr);
449 state->gap_start_attr = NULL;
450 }
451 }
452
453 /* Check whether we need to recompute the ellipsis because of new font attributes
454 */
455 if (!state->gap_start_attr)
456 {
457 state->gap_start_attr = pango_attr_iterator_copy (iterator: state->line_start_attr);
458 advance_iterator_to (iter: state->gap_start_attr,
459 new_index: state->run_info[state->gap_start_iter.run_index].run->item->offset);
460
461 recompute = TRUE;
462 }
463
464 /* Check whether we need to recompute the ellipsis because we switch from CJK to not
465 * or vice-versa
466 */
467 start_wc = g_utf8_get_char (p: state->layout->text + state->gap_start_iter.run_iter.start_index);
468 is_cjk = g_unichar_iswide (c: start_wc);
469
470 if (is_cjk != state->ellipsis_is_cjk)
471 {
472 state->ellipsis_is_cjk = is_cjk;
473 recompute = TRUE;
474 }
475
476 if (recompute)
477 shape_ellipsis (state);
478}
479
480/* Computes the position of the gap center and finds the smallest span containing it
481 */
482static void
483find_initial_span (EllipsizeState *state)
484{
485 PangoGlyphItem *glyph_item;
486 PangoGlyphItemIter *run_iter;
487 gboolean have_cluster;
488 int i;
489 int x;
490 int cluster_width;
491
492 switch (state->layout->ellipsize)
493 {
494 case PANGO_ELLIPSIZE_NONE:
495 default:
496 g_assert_not_reached ();
497 case PANGO_ELLIPSIZE_START:
498 state->gap_center = 0;
499 break;
500 case PANGO_ELLIPSIZE_MIDDLE:
501 state->gap_center = state->total_width / 2;
502 break;
503 case PANGO_ELLIPSIZE_END:
504 state->gap_center = state->total_width;
505 break;
506 }
507
508 /* Find the run containing the gap center
509 */
510 x = 0;
511 for (i = 0; i < state->n_runs; i++)
512 {
513 if (x + state->run_info[i].width > state->gap_center)
514 break;
515
516 x += state->run_info[i].width;
517 }
518
519 if (i == state->n_runs) /* Last run is a closed interval, so back off one run */
520 {
521 i--;
522 x -= state->run_info[i].width;
523 }
524
525 /* Find the cluster containing the gap center
526 */
527 state->gap_start_iter.run_index = i;
528 run_iter = &state->gap_start_iter.run_iter;
529 glyph_item = state->run_info[i].run;
530
531 cluster_width = 0; /* Quiet GCC, the line must have at least one cluster */
532 for (have_cluster = pango_glyph_item_iter_init_start (iter: run_iter, glyph_item, text: state->layout->text);
533 have_cluster;
534 have_cluster = pango_glyph_item_iter_next_cluster (iter: run_iter))
535 {
536 cluster_width = get_cluster_width (iter: &state->gap_start_iter);
537
538 if (x + cluster_width > state->gap_center)
539 break;
540
541 x += cluster_width;
542 }
543
544 if (!have_cluster) /* Last cluster is a closed interval, so back off one cluster */
545 x -= cluster_width;
546
547 state->gap_end_iter = state->gap_start_iter;
548
549 state->gap_start_x = x;
550 state->gap_end_x = x + cluster_width;
551
552 /* Expand the gap to a full span
553 */
554 while (!starts_at_ellipsization_boundary (state, iter: &state->gap_start_iter))
555 {
556 line_iter_prev_cluster (state, iter: &state->gap_start_iter);
557 state->gap_start_x -= get_cluster_width (iter: &state->gap_start_iter);
558 }
559
560 while (!ends_at_ellipsization_boundary (state, iter: &state->gap_end_iter))
561 {
562 line_iter_next_cluster (state, iter: &state->gap_end_iter);
563 state->gap_end_x += get_cluster_width (iter: &state->gap_end_iter);
564 }
565
566 update_ellipsis_shape (state);
567}
568
569/* Removes one run from the start or end of the gap. Returns FALSE
570 * if there's nothing left to remove in either direction.
571 */
572static gboolean
573remove_one_span (EllipsizeState *state)
574{
575 LineIter new_gap_start_iter;
576 LineIter new_gap_end_iter;
577 int new_gap_start_x;
578 int new_gap_end_x;
579 int width;
580
581 /* Find one span backwards and forward from the gap
582 */
583 new_gap_start_iter = state->gap_start_iter;
584 new_gap_start_x = state->gap_start_x;
585 do
586 {
587 if (!line_iter_prev_cluster (state, iter: &new_gap_start_iter))
588 break;
589 width = get_cluster_width (iter: &new_gap_start_iter);
590 new_gap_start_x -= width;
591 }
592 while (!starts_at_ellipsization_boundary (state, iter: &new_gap_start_iter) ||
593 width == 0);
594
595 new_gap_end_iter = state->gap_end_iter;
596 new_gap_end_x = state->gap_end_x;
597 do
598 {
599 if (!line_iter_next_cluster (state, iter: &new_gap_end_iter))
600 break;
601 width = get_cluster_width (iter: &new_gap_end_iter);
602 new_gap_end_x += width;
603 }
604 while (!ends_at_ellipsization_boundary (state, iter: &new_gap_end_iter) ||
605 width == 0);
606
607 if (state->gap_end_x == new_gap_end_x && state->gap_start_x == new_gap_start_x)
608 return FALSE;
609
610 /* In the case where we could remove a span from either end of the
611 * gap, we look at which causes the smaller increase in the
612 * MAX (gap_end - gap_center, gap_start - gap_center)
613 */
614 if (state->gap_end_x == new_gap_end_x ||
615 (state->gap_start_x != new_gap_start_x &&
616 state->gap_center - new_gap_start_x < new_gap_end_x - state->gap_center))
617 {
618 state->gap_start_iter = new_gap_start_iter;
619 state->gap_start_x = new_gap_start_x;
620
621 update_ellipsis_shape (state);
622 }
623 else
624 {
625 state->gap_end_iter = new_gap_end_iter;
626 state->gap_end_x = new_gap_end_x;
627 }
628
629 return TRUE;
630}
631
632/* Fixes up the properties of the ellipsis run once we've determined the final extents
633 * of the gap
634 */
635static void
636fixup_ellipsis_run (EllipsizeState *state,
637 int extra_width)
638{
639 PangoGlyphString *glyphs = state->ellipsis_run->glyphs;
640 PangoItem *item = state->ellipsis_run->item;
641 int level;
642 int i;
643
644 /* Make the entire glyphstring into a single logical cluster */
645 for (i = 0; i < glyphs->num_glyphs; i++)
646 {
647 glyphs->log_clusters[i] = 0;
648 glyphs->glyphs[i].attr.is_cluster_start = FALSE;
649 }
650
651 glyphs->glyphs[0].attr.is_cluster_start = TRUE;
652
653 glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += extra_width;
654
655 /* Fix up the item to point to the entire elided text */
656 item->offset = state->gap_start_iter.run_iter.start_index;
657 item->length = state->gap_end_iter.run_iter.end_index - item->offset;
658 item->num_chars = pango_utf8_strlen (p: state->layout->text + item->offset, max: item->length);
659
660 /* The level for the item is the minimum level of the elided text */
661 level = G_MAXINT;
662 for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
663 level = MIN (level, state->run_info[i].run->item->analysis.level);
664
665 item->analysis.level = level;
666
667 item->analysis.flags |= PANGO_ANALYSIS_FLAG_IS_ELLIPSIS;
668}
669
670/* Computes the new list of runs for the line
671 */
672static GSList *
673get_run_list (EllipsizeState *state)
674{
675 PangoGlyphItem *partial_start_run = NULL;
676 PangoGlyphItem *partial_end_run = NULL;
677 GSList *result = NULL;
678 RunInfo *run_info;
679 PangoGlyphItemIter *run_iter;
680 int i;
681
682 /* We first cut out the pieces of the starting and ending runs we want to
683 * preserve; we do the end first in case the end and the start are
684 * the same. Doing the start first would disturb the indices for the end.
685 */
686 run_info = &state->run_info[state->gap_end_iter.run_index];
687 run_iter = &state->gap_end_iter.run_iter;
688 if (run_iter->end_char != run_info->run->item->num_chars)
689 {
690 partial_end_run = run_info->run;
691 run_info->run = pango_glyph_item_split (orig: run_info->run, text: state->layout->text,
692 split_index: run_iter->end_index - run_info->run->item->offset);
693 }
694
695 run_info = &state->run_info[state->gap_start_iter.run_index];
696 run_iter = &state->gap_start_iter.run_iter;
697 if (run_iter->start_char != 0)
698 {
699 partial_start_run = pango_glyph_item_split (orig: run_info->run, text: state->layout->text,
700 split_index: run_iter->start_index - run_info->run->item->offset);
701 }
702
703 /* Now assemble the new list of runs
704 */
705 for (i = 0; i < state->gap_start_iter.run_index; i++)
706 result = g_slist_prepend (list: result, data: state->run_info[i].run);
707
708 if (partial_start_run)
709 result = g_slist_prepend (list: result, data: partial_start_run);
710
711 result = g_slist_prepend (list: result, data: state->ellipsis_run);
712
713 if (partial_end_run)
714 result = g_slist_prepend (list: result, data: partial_end_run);
715
716 for (i = state->gap_end_iter.run_index + 1; i < state->n_runs; i++)
717 result = g_slist_prepend (list: result, data: state->run_info[i].run);
718
719 /* And free the ones we didn't use
720 */
721 for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
722 pango_glyph_item_free (glyph_item: state->run_info[i].run);
723
724 return g_slist_reverse (list: result);
725}
726
727/* Computes the width of the line as currently ellipsized
728 */
729static int
730current_width (EllipsizeState *state)
731{
732 return state->total_width - (state->gap_end_x - state->gap_start_x) + state->ellipsis_width;
733}
734
735/**
736 * _pango_layout_line_ellipsize:
737 * @line: a `PangoLayoutLine`
738 * @attrs: Attributes being used for itemization/shaping
739 * @shape_flags: Flags to use when shaping
740 *
741 * Given a `PangoLayoutLine` with the runs still in logical order, ellipsize
742 * it according the layout's policy to fit within the set width of the layout.
743 *
744 * Return value: whether the line had to be ellipsized
745 **/
746gboolean
747_pango_layout_line_ellipsize (PangoLayoutLine *line,
748 PangoAttrList *attrs,
749 PangoShapeFlags shape_flags,
750 int goal_width)
751{
752 EllipsizeState state;
753 gboolean is_ellipsized = FALSE;
754
755 g_return_val_if_fail (line->layout->ellipsize != PANGO_ELLIPSIZE_NONE && goal_width >= 0, is_ellipsized);
756
757 init_state (state: &state, line, attrs, shape_flags);
758
759 if (state.total_width <= goal_width)
760 goto out;
761
762 find_initial_span (state: &state);
763
764 while (current_width (state: &state) > goal_width)
765 {
766 if (!remove_one_span (state: &state))
767 break;
768 }
769
770 fixup_ellipsis_run (state: &state, MAX (goal_width - current_width (&state), 0));
771
772 g_slist_free (list: line->runs);
773 line->runs = get_run_list (state: &state);
774 is_ellipsized = TRUE;
775
776 out:
777 free_state (state: &state);
778
779 return is_ellipsized;
780}
781

source code of gtk/subprojects/pango/pango/ellipsize.c