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