summaryrefslogtreecommitdiff
path: root/pango/ellipsize.c
blob: d7b7fee01a13e4b752a1da523a9807fb487b4971 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
/* 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.h"
#include "pango-layout-private.h"
#include "pango-font-private.h"
#include "pango-attributes-private.h"
#include "pango-impl-utils.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 */

  PangoShapeFlags shape_flags;
};

/* Compute global information needed for the itemization process
 */
static void
init_state (EllipsizeState  *state,
            PangoLayoutLine *line,
            PangoAttrList   *attrs,
            PangoShapeFlags  shape_flags)
{
  GSList *l;
  int i;
  int start_offset;

  state->layout = line->layout;
  if (attrs)
    state->attrs = pango_attr_list_ref (attrs);
  else
    state->attrs = pango_attr_list_new ();

  state->shape_flags = shape_flags;

  state->n_runs = g_slist_length (line->runs);
  state->run_info = g_new (RunInfo, state->n_runs);

  start_offset = pango_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 = pango_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)
{
  pango_attr_list_unref (state->attrs);
  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 int
get_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 gboolean
line_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 gboolean
line_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 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->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 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->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 void
shape_ellipsis (EllipsizeState *state)
{
  PangoAttrList attrs;
  GSList *run_attrs;
  PangoItem *item;
  PangoGlyphString *glyphs;
  GSList *l;
  PangoAttribute *fallback;
  const char *ellipsis_text;
  int len;
  int i;

  _pango_attr_list_init (&attrs);

  /* Create/reset state->ellipsis_run
   */
  if (!state->ellipsis_run)
    {
      state->ellipsis_run = g_slice_new0 (PangoGlyphItem);
      state->ellipsis_run->glyphs = pango_glyph_string_new ();
    }

  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_font_has_char (item->analysis.font,
                            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_destroy (&attrs);

  state->ellipsis_run->item = item;

  /* Now shape
   */
  glyphs = state->ellipsis_run->glyphs;

  len = strlen (ellipsis_text);
  pango_shape_with_flags (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 PangoAttrIterator to a particular
 * byte index.
 */
static void
advance_iterator_to (PangoAttrIterator *iter,
		     int                new_index)
{
  int start, end;

  do
    {
      pango_attr_iterator_range (iter, &start, &end);
      if (end > new_index)
	break;
    }
  while (pango_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 PangoAttrIterator 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 = pango_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;

      pango_attr_iterator_range (state->gap_start_attr, &start, &end);

      if (state->gap_start_iter.run_iter.start_index < start)
	{
	  pango_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 = pango_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->layout->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)
{
  PangoGlyphItem *glyph_item;
  PangoGlyphItemIter *run_iter;
  gboolean have_cluster;
  int i;
  int x;
  int cluster_width;

  switch (state->layout->ellipsize)
    {
    case PANGO_ELLIPSIZE_NONE:
    default:
      g_assert_not_reached ();
    case PANGO_ELLIPSIZE_START:
      state->gap_center = 0;
      break;
    case PANGO_ELLIPSIZE_MIDDLE:
      state->gap_center = state->total_width / 2;
      break;
    case PANGO_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 = pango_glyph_item_iter_init_start (run_iter, glyph_item, state->layout->text);
       have_cluster;
       have_cluster = pango_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)
{
  PangoGlyphString *glyphs = state->ellipsis_run->glyphs;
  PangoItem *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 = pango_utf8_strlen (state->layout->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 |= PANGO_ANALYSIS_FLAG_IS_ELLIPSIS;
}

/* Computes the new list of runs for the line
 */
static GSList *
get_run_list (EllipsizeState *state)
{
  PangoGlyphItem *partial_start_run = NULL;
  PangoGlyphItem *partial_end_run = NULL;
  GSList *result = NULL;
  RunInfo *run_info;
  PangoGlyphItemIter *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 = pango_glyph_item_split (run_info->run, state->layout->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 = pango_glyph_item_split (run_info->run, state->layout->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++)
    pango_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;
}

/**
 * _pango_layout_line_ellipsize:
 * @line: a `PangoLayoutLine`
 * @attrs: Attributes being used for itemization/shaping
 * @shape_flags: Flags to use when shaping
 *
 * Given a `PangoLayoutLine` with the runs still in logical order, ellipsize
 * it according the layout's policy to fit within the set width of the layout.
 *
 * Return value: whether the line had to be ellipsized
 **/
gboolean
_pango_layout_line_ellipsize (PangoLayoutLine *line,
			      PangoAttrList   *attrs,
                              PangoShapeFlags  shape_flags,
			      int              goal_width)
{
  EllipsizeState state;
  gboolean is_ellipsized = FALSE;

  g_return_val_if_fail (line->layout->ellipsize != PANGO_ELLIPSIZE_NONE && goal_width >= 0, is_ellipsized);

  init_state (&state, line, 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);
  is_ellipsized = TRUE;

 out:
  free_state (&state);

  return is_ellipsized;
}