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
31 typedef struct _EllipsizeState EllipsizeState;
32 typedef struct _RunInfo RunInfo;
33 typedef 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 */
67 struct _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 */
75 struct _LineIter
76 {
77 PangoGlyphItemIter run_iter;
78 int run_index;
79 };
80
81 /* State of ellipsization process */
82 struct _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 */
115 static void
init_state(EllipsizeState * state,PangoLayoutLine * line,PangoAttrList * attrs,PangoShapeFlags shape_flags)116 init_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 (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 (line->runs);
134 state->run_info = g_new (RunInfo, state->n_runs);
135
136 start_offset = pango_utf8_strlen (line->layout->text,
137 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 (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 */
160 static void
free_state(EllipsizeState * state)161 free_state (EllipsizeState *state)
162 {
163 pango_attr_list_unref (state->attrs);
164 if (state->line_start_attr)
165 pango_attr_iterator_destroy (state->line_start_attr);
166 if (state->gap_start_attr)
167 pango_attr_iterator_destroy (state->gap_start_attr);
168 g_free (state->run_info);
169 }
170
171 /* Computes the width of a single cluster
172 */
173 static int
get_cluster_width(LineIter * iter)174 get_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 */
197 static gboolean
line_iter_next_cluster(EllipsizeState * state,LineIter * iter)198 line_iter_next_cluster (EllipsizeState *state,
199 LineIter *iter)
200 {
201 if (!pango_glyph_item_iter_next_cluster (&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->run_iter,
209 state->run_info[iter->run_index].run,
210 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 */
219 static gboolean
line_iter_prev_cluster(EllipsizeState * state,LineIter * iter)220 line_iter_prev_cluster (EllipsizeState *state,
221 LineIter *iter)
222 {
223 if (!pango_glyph_item_iter_prev_cluster (&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->run_iter,
231 state->run_info[iter->run_index].run,
232 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 */
252 static gboolean
starts_at_ellipsization_boundary(EllipsizeState * state,LineIter * iter)253 starts_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 */
266 static gboolean
ends_at_ellipsization_boundary(EllipsizeState * state,LineIter * iter)267 ends_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 */
280 static PangoItem *
itemize_text(EllipsizeState * state,const char * text,PangoAttrList * attrs)281 itemize_text (EllipsizeState *state,
282 const char *text,
283 PangoAttrList *attrs)
284 {
285 GList *items;
286 PangoItem *item;
287
288 items = pango_itemize (state->layout->context, text, 0, strlen (text), attrs, NULL);
289 g_assert (g_list_length (items) == 1);
290
291 item = items->data;
292 g_list_free (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 */
300 static void
shape_ellipsis(EllipsizeState * state)301 shape_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 (&attrs);
314
315 /* Create/reset state->ellipsis_run
316 */
317 if (!state->ellipsis_run)
318 {
319 state->ellipsis_run = g_slice_new (PangoGlyphItem);
320 state->ellipsis_run->glyphs = pango_glyph_string_new ();
321 state->ellipsis_run->item = NULL;
322 }
323
324 if (state->ellipsis_run->item)
325 {
326 pango_item_free (state->ellipsis_run->item);
327 state->ellipsis_run->item = NULL;
328 }
329
330 /* Create an attribute list
331 */
332 run_attrs = pango_attr_iterator_get_attrs (state->gap_start_attr);
333 for (l = run_attrs; l; l = l->next)
334 {
335 PangoAttribute *attr = l->data;
336 attr->start_index = 0;
337 attr->end_index = G_MAXINT;
338
339 pango_attr_list_insert (&attrs, attr);
340 }
341
342 g_slist_free (run_attrs);
343
344 fallback = pango_attr_fallback_new (FALSE);
345 fallback->start_index = 0;
346 fallback->end_index = G_MAXINT;
347 pango_attr_list_insert (&attrs, fallback);
348
349 /* First try using a specific ellipsis character in the best matching font
350 */
351 if (state->ellipsis_is_cjk)
352 ellipsis_text = "\342\213\257"; /* U+22EF: MIDLINE HORIZONTAL ELLIPSIS, used for CJK */
353 else
354 ellipsis_text = "\342\200\246"; /* U+2026: HORIZONTAL ELLIPSIS */
355
356 item = itemize_text (state, ellipsis_text, &attrs);
357
358 /* If that fails we use "..." in the first matching font
359 */
360 if (!item->analysis.font ||
361 !pango_font_has_char (item->analysis.font,
362 g_utf8_get_char (ellipsis_text)))
363 {
364 pango_item_free (item);
365
366 /* Modify the fallback iter while it is inside the PangoAttrList; Don't try this at home
367 */
368 ((PangoAttrInt *)fallback)->value = TRUE;
369
370 ellipsis_text = "...";
371 item = itemize_text (state, ellipsis_text, &attrs);
372 }
373
374 _pango_attr_list_destroy (&attrs);
375
376 state->ellipsis_run->item = item;
377
378 /* Now shape
379 */
380 glyphs = state->ellipsis_run->glyphs;
381
382 len = strlen (ellipsis_text);
383 pango_shape_with_flags (ellipsis_text, len,
384 ellipsis_text, len,
385 &item->analysis, glyphs,
386 state->shape_flags);
387
388 state->ellipsis_width = 0;
389 for (i = 0; i < glyphs->num_glyphs; i++)
390 state->ellipsis_width += glyphs->glyphs[i].geometry.width;
391 }
392
393 /* Helper function to advance a PangoAttrIterator to a particular
394 * byte index.
395 */
396 static void
advance_iterator_to(PangoAttrIterator * iter,int new_index)397 advance_iterator_to (PangoAttrIterator *iter,
398 int new_index)
399 {
400 int start, end;
401
402 do
403 {
404 pango_attr_iterator_range (iter, &start, &end);
405 if (end > new_index)
406 break;
407 }
408 while (pango_attr_iterator_next (iter));
409 }
410
411 /* Updates the shaping of the ellipsis if necessary when we move the
412 * position of the start of the gap.
413 *
414 * The shaping of the ellipsis is determined by two things:
415 *
416 * - The font attributes applied to the first character in the gap
417 * - Whether the first character in the gap is wide or not. If the
418 * first character is wide, then we assume that we are ellipsizing
419 * East-Asian text, so prefer a mid-line ellipsizes to a baseline
420 * ellipsis, since that's typical practice for Chinese/Japanese/Korean.
421 */
422 static void
update_ellipsis_shape(EllipsizeState * state)423 update_ellipsis_shape (EllipsizeState *state)
424 {
425 gboolean recompute = FALSE;
426 gunichar start_wc;
427 gboolean is_cjk;
428
429 /* Unfortunately, we can only advance PangoAttrIterator forward; so each
430 * time we back up we need to go forward to find the new position. To make
431 * this not utterly slow, we cache an iterator at the start of the line
432 */
433 if (!state->line_start_attr)
434 {
435 state->line_start_attr = pango_attr_list_get_iterator (state->attrs);
436 advance_iterator_to (state->line_start_attr, state->run_info[0].run->item->offset);
437 }
438
439 if (state->gap_start_attr)
440 {
441 /* See if the current attribute range contains the new start position
442 */
443 int start, end;
444
445 pango_attr_iterator_range (state->gap_start_attr, &start, &end);
446
447 if (state->gap_start_iter.run_iter.start_index < start)
448 {
449 pango_attr_iterator_destroy (state->gap_start_attr);
450 state->gap_start_attr = NULL;
451 }
452 }
453
454 /* Check whether we need to recompute the ellipsis because of new font attributes
455 */
456 if (!state->gap_start_attr)
457 {
458 state->gap_start_attr = pango_attr_iterator_copy (state->line_start_attr);
459 advance_iterator_to (state->gap_start_attr,
460 state->run_info[state->gap_start_iter.run_index].run->item->offset);
461
462 recompute = TRUE;
463 }
464
465 /* Check whether we need to recompute the ellipsis because we switch from CJK to not
466 * or vice-versa
467 */
468 start_wc = g_utf8_get_char (state->layout->text + state->gap_start_iter.run_iter.start_index);
469 is_cjk = g_unichar_iswide (start_wc);
470
471 if (is_cjk != state->ellipsis_is_cjk)
472 {
473 state->ellipsis_is_cjk = is_cjk;
474 recompute = TRUE;
475 }
476
477 if (recompute)
478 shape_ellipsis (state);
479 }
480
481 /* Computes the position of the gap center and finds the smallest span containing it
482 */
483 static void
find_initial_span(EllipsizeState * state)484 find_initial_span (EllipsizeState *state)
485 {
486 PangoGlyphItem *glyph_item;
487 PangoGlyphItemIter *run_iter;
488 gboolean have_cluster;
489 int i;
490 int x;
491 int cluster_width;
492
493 switch (state->layout->ellipsize)
494 {
495 case PANGO_ELLIPSIZE_NONE:
496 default:
497 g_assert_not_reached ();
498 case PANGO_ELLIPSIZE_START:
499 state->gap_center = 0;
500 break;
501 case PANGO_ELLIPSIZE_MIDDLE:
502 state->gap_center = state->total_width / 2;
503 break;
504 case PANGO_ELLIPSIZE_END:
505 state->gap_center = state->total_width;
506 break;
507 }
508
509 /* Find the run containing the gap center
510 */
511 x = 0;
512 for (i = 0; i < state->n_runs; i++)
513 {
514 if (x + state->run_info[i].width > state->gap_center)
515 break;
516
517 x += state->run_info[i].width;
518 }
519
520 if (i == state->n_runs) /* Last run is a closed interval, so back off one run */
521 {
522 i--;
523 x -= state->run_info[i].width;
524 }
525
526 /* Find the cluster containing the gap center
527 */
528 state->gap_start_iter.run_index = i;
529 run_iter = &state->gap_start_iter.run_iter;
530 glyph_item = state->run_info[i].run;
531
532 cluster_width = 0; /* Quiet GCC, the line must have at least one cluster */
533 for (have_cluster = pango_glyph_item_iter_init_start (run_iter, glyph_item, state->layout->text);
534 have_cluster;
535 have_cluster = pango_glyph_item_iter_next_cluster (run_iter))
536 {
537 cluster_width = get_cluster_width (&state->gap_start_iter);
538
539 if (x + cluster_width > state->gap_center)
540 break;
541
542 x += cluster_width;
543 }
544
545 if (!have_cluster) /* Last cluster is a closed interval, so back off one cluster */
546 x -= cluster_width;
547
548 state->gap_end_iter = state->gap_start_iter;
549
550 state->gap_start_x = x;
551 state->gap_end_x = x + cluster_width;
552
553 /* Expand the gap to a full span
554 */
555 while (!starts_at_ellipsization_boundary (state, &state->gap_start_iter))
556 {
557 line_iter_prev_cluster (state, &state->gap_start_iter);
558 state->gap_start_x -= get_cluster_width (&state->gap_start_iter);
559 }
560
561 while (!ends_at_ellipsization_boundary (state, &state->gap_end_iter))
562 {
563 line_iter_next_cluster (state, &state->gap_end_iter);
564 state->gap_end_x += get_cluster_width (&state->gap_end_iter);
565 }
566
567 update_ellipsis_shape (state);
568 }
569
570 /* Removes one run from the start or end of the gap. Returns FALSE
571 * if there's nothing left to remove in either direction.
572 */
573 static gboolean
remove_one_span(EllipsizeState * state)574 remove_one_span (EllipsizeState *state)
575 {
576 LineIter new_gap_start_iter;
577 LineIter new_gap_end_iter;
578 int new_gap_start_x;
579 int new_gap_end_x;
580 int width;
581
582 /* Find one span backwards and forward from the gap
583 */
584 new_gap_start_iter = state->gap_start_iter;
585 new_gap_start_x = state->gap_start_x;
586 do
587 {
588 if (!line_iter_prev_cluster (state, &new_gap_start_iter))
589 break;
590 width = get_cluster_width (&new_gap_start_iter);
591 new_gap_start_x -= width;
592 }
593 while (!starts_at_ellipsization_boundary (state, &new_gap_start_iter) ||
594 width == 0);
595
596 new_gap_end_iter = state->gap_end_iter;
597 new_gap_end_x = state->gap_end_x;
598 do
599 {
600 if (!line_iter_next_cluster (state, &new_gap_end_iter))
601 break;
602 width = get_cluster_width (&new_gap_end_iter);
603 new_gap_end_x += width;
604 }
605 while (!ends_at_ellipsization_boundary (state, &new_gap_end_iter) ||
606 width == 0);
607
608 if (state->gap_end_x == new_gap_end_x && state->gap_start_x == new_gap_start_x)
609 return FALSE;
610
611 /* In the case where we could remove a span from either end of the
612 * gap, we look at which causes the smaller increase in the
613 * MAX (gap_end - gap_center, gap_start - gap_center)
614 */
615 if (state->gap_end_x == new_gap_end_x ||
616 (state->gap_start_x != new_gap_start_x &&
617 state->gap_center - new_gap_start_x < new_gap_end_x - state->gap_center))
618 {
619 state->gap_start_iter = new_gap_start_iter;
620 state->gap_start_x = new_gap_start_x;
621
622 update_ellipsis_shape (state);
623 }
624 else
625 {
626 state->gap_end_iter = new_gap_end_iter;
627 state->gap_end_x = new_gap_end_x;
628 }
629
630 return TRUE;
631 }
632
633 /* Fixes up the properties of the ellipsis run once we've determined the final extents
634 * of the gap
635 */
636 static void
fixup_ellipsis_run(EllipsizeState * state,int extra_width)637 fixup_ellipsis_run (EllipsizeState *state,
638 int extra_width)
639 {
640 PangoGlyphString *glyphs = state->ellipsis_run->glyphs;
641 PangoItem *item = state->ellipsis_run->item;
642 int level;
643 int i;
644
645 /* Make the entire glyphstring into a single logical cluster */
646 for (i = 0; i < glyphs->num_glyphs; i++)
647 {
648 glyphs->log_clusters[i] = 0;
649 glyphs->glyphs[i].attr.is_cluster_start = FALSE;
650 }
651
652 glyphs->glyphs[0].attr.is_cluster_start = TRUE;
653
654 glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += extra_width;
655
656 /* Fix up the item to point to the entire elided text */
657 item->offset = state->gap_start_iter.run_iter.start_index;
658 item->length = state->gap_end_iter.run_iter.end_index - item->offset;
659 item->num_chars = pango_utf8_strlen (state->layout->text + item->offset, item->length);
660
661 /* The level for the item is the minimum level of the elided text */
662 level = G_MAXINT;
663 for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
664 level = MIN (level, state->run_info[i].run->item->analysis.level);
665
666 item->analysis.level = level;
667
668 item->analysis.flags |= PANGO_ANALYSIS_FLAG_IS_ELLIPSIS;
669 }
670
671 /* Computes the new list of runs for the line
672 */
673 static GSList *
get_run_list(EllipsizeState * state)674 get_run_list (EllipsizeState *state)
675 {
676 PangoGlyphItem *partial_start_run = NULL;
677 PangoGlyphItem *partial_end_run = NULL;
678 GSList *result = NULL;
679 RunInfo *run_info;
680 PangoGlyphItemIter *run_iter;
681 int i;
682
683 /* We first cut out the pieces of the starting and ending runs we want to
684 * preserve; we do the end first in case the end and the start are
685 * the same. Doing the start first would disturb the indices for the end.
686 */
687 run_info = &state->run_info[state->gap_end_iter.run_index];
688 run_iter = &state->gap_end_iter.run_iter;
689 if (run_iter->end_char != run_info->run->item->num_chars)
690 {
691 partial_end_run = run_info->run;
692 run_info->run = pango_glyph_item_split (run_info->run, state->layout->text,
693 run_iter->end_index - run_info->run->item->offset);
694 }
695
696 run_info = &state->run_info[state->gap_start_iter.run_index];
697 run_iter = &state->gap_start_iter.run_iter;
698 if (run_iter->start_char != 0)
699 {
700 partial_start_run = pango_glyph_item_split (run_info->run, state->layout->text,
701 run_iter->start_index - run_info->run->item->offset);
702 }
703
704 /* Now assemble the new list of runs
705 */
706 for (i = 0; i < state->gap_start_iter.run_index; i++)
707 result = g_slist_prepend (result, state->run_info[i].run);
708
709 if (partial_start_run)
710 result = g_slist_prepend (result, partial_start_run);
711
712 result = g_slist_prepend (result, state->ellipsis_run);
713
714 if (partial_end_run)
715 result = g_slist_prepend (result, partial_end_run);
716
717 for (i = state->gap_end_iter.run_index + 1; i < state->n_runs; i++)
718 result = g_slist_prepend (result, state->run_info[i].run);
719
720 /* And free the ones we didn't use
721 */
722 for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
723 pango_glyph_item_free (state->run_info[i].run);
724
725 return g_slist_reverse (result);
726 }
727
728 /* Computes the width of the line as currently ellipsized
729 */
730 static int
current_width(EllipsizeState * state)731 current_width (EllipsizeState *state)
732 {
733 return state->total_width - (state->gap_end_x - state->gap_start_x) + state->ellipsis_width;
734 }
735
736 /**
737 * _pango_layout_line_ellipsize:
738 * @line: a `PangoLayoutLine`
739 * @attrs: Attributes being used for itemization/shaping
740 * @shape_flags: Flags to use when shaping
741 *
742 * Given a `PangoLayoutLine` with the runs still in logical order, ellipsize
743 * it according the layout's policy to fit within the set width of the layout.
744 *
745 * Return value: whether the line had to be ellipsized
746 **/
747 gboolean
_pango_layout_line_ellipsize(PangoLayoutLine * line,PangoAttrList * attrs,PangoShapeFlags shape_flags,int goal_width)748 _pango_layout_line_ellipsize (PangoLayoutLine *line,
749 PangoAttrList *attrs,
750 PangoShapeFlags shape_flags,
751 int goal_width)
752 {
753 EllipsizeState state;
754 gboolean is_ellipsized = FALSE;
755
756 g_return_val_if_fail (line->layout->ellipsize != PANGO_ELLIPSIZE_NONE && goal_width >= 0, is_ellipsized);
757
758 init_state (&state, line, attrs, shape_flags);
759
760 if (state.total_width <= goal_width)
761 goto out;
762
763 find_initial_span (&state);
764
765 while (current_width (&state) > goal_width)
766 {
767 if (!remove_one_span (&state))
768 break;
769 }
770
771 fixup_ellipsis_run (&state, MAX (goal_width - current_width (&state), 0));
772
773 g_slist_free (line->runs);
774 line->runs = get_run_list (&state);
775 is_ellipsized = TRUE;
776
777 out:
778 free_state (&state);
779
780 return is_ellipsized;
781 }
782