1 /* Pango
2  * pango-layout.c: High-level layout driver
3  *
4  * Copyright (C) 2000, 2001, 2006 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 /**
23  * PangoLayout:
24  *
25  * A `PangoLayout` structure represents an entire paragraph of text.
26  *
27  * While complete access to the layout capabilities of Pango is provided
28  * using the detailed interfaces for itemization and shaping, using
29  * that functionality directly involves writing a fairly large amount
30  * of code. `PangoLayout` provides a high-level driver for formatting
31  * entire paragraphs of text at once. This includes paragraph-level
32  * functionality such as line breaking, justification, alignment and
33  * ellipsization.
34  *
35  * A `PangoLayout` is initialized with a `PangoContext`, UTF-8 string
36  * and set of attributes for that string. Once that is done, the set of
37  * formatted lines can be extracted from the object, the layout can be
38  * rendered, and conversion between logical character positions within
39  * the layout's text, and the physical position of the resulting glyphs
40  * can be made.
41  *
42  * There are a number of parameters to adjust the formatting of a
43  * `PangoLayout`. The following image shows adjustable parameters
44  * (on the left) and font metrics (on the right):
45  *
46  * ![Pango Layout Parameters](layout.png)
47  *
48  * It is possible, as well, to ignore the 2-D setup,
49  * and simply treat the results of a `PangoLayout` as a list of lines.
50  */
51 
52 /**
53  * PangoLayoutIter:
54  *
55  * A `PangoLayoutIter` can be used to iterate over the visual
56  * extents of a `PangoLayout`.
57  *
58  * To obtain a `PangoLayoutIter`, use [method@Pango.Layout.get_iter].
59  *
60  * The `PangoLayoutIter` structure is opaque, and has no user-visible fields.
61  */
62 
63 #include "config.h"
64 #include "pango-glyph.h"                /* For pango_shape() */
65 #include "pango-break.h"
66 #include "pango-item.h"
67 #include "pango-engine.h"
68 #include "pango-impl-utils.h"
69 #include "pango-glyph-item.h"
70 #include <string.h>
71 
72 #include "pango-layout-private.h"
73 #include "pango-attributes-private.h"
74 
75 
76 typedef struct _ItemProperties ItemProperties;
77 typedef struct _ParaBreakState ParaBreakState;
78 
79 /* Note that rise, letter_spacing, shape are constant across items,
80  * since we pass them into itemization.
81  *
82  * uline and strikethrough can vary across an item, so we collect
83  * all the values that we find.
84  *
85  * See pango_layout_get_item_properties for details.
86  */
87 struct _ItemProperties
88 {
89   guint uline_single   : 1;
90   guint uline_double   : 1;
91   guint uline_low      : 1;
92   guint uline_error    : 1;
93   guint strikethrough  : 1;
94   guint oline_single   : 1;
95   gint            rise;
96   gint            letter_spacing;
97   gboolean        shape_set;
98   PangoRectangle *shape_ink_rect;
99   PangoRectangle *shape_logical_rect;
100 };
101 
102 typedef struct _PangoLayoutLinePrivate PangoLayoutLinePrivate;
103 
104 struct _PangoLayoutLinePrivate
105 {
106   PangoLayoutLine line;
107   guint ref_count;
108 
109   /* Extents cache status:
110    *
111    * LEAKED means that the user has access to this line structure or a
112    * run included in this line, and so can change the glyphs/glyph-widths.
113    * If this is true, extents caching will be disabled.
114    */
115   enum {
116     NOT_CACHED,
117     CACHED,
118     LEAKED
119   } cache_status;
120   PangoRectangle ink_rect;
121   PangoRectangle logical_rect;
122   int height;
123 };
124 
125 struct _PangoLayoutClass
126 {
127   GObjectClass parent_class;
128 
129 
130 };
131 
132 #define LINE_IS_VALID(line) ((line) && (line)->layout != NULL)
133 
134 #ifdef G_DISABLE_CHECKS
135 #define ITER_IS_INVALID(iter) FALSE
136 #else
137 #define ITER_IS_INVALID(iter) G_UNLIKELY (check_invalid ((iter), G_STRLOC))
138 static gboolean
check_invalid(PangoLayoutIter * iter,const char * loc)139 check_invalid (PangoLayoutIter *iter,
140                const char      *loc)
141 {
142   if (iter->line->layout == NULL)
143     {
144       g_warning ("%s: PangoLayout changed since PangoLayoutIter was created, iterator invalid", loc);
145       return TRUE;
146     }
147   else
148     {
149       return FALSE;
150     }
151 }
152 #endif
153 
154 static void check_context_changed  (PangoLayout *layout);
155 static void layout_changed  (PangoLayout *layout);
156 
157 static void pango_layout_clear_lines (PangoLayout *layout);
158 static void pango_layout_check_lines (PangoLayout *layout);
159 
160 static PangoAttrList *pango_layout_get_effective_attributes (PangoLayout *layout);
161 
162 static PangoLayoutLine * pango_layout_line_new         (PangoLayout     *layout);
163 static void              pango_layout_line_postprocess (PangoLayoutLine *line,
164                                                         ParaBreakState  *state,
165                                                         gboolean         wrapped);
166 
167 static int *pango_layout_line_get_log2vis_map (PangoLayoutLine  *line,
168                                                gboolean          strong);
169 static int *pango_layout_line_get_vis2log_map (PangoLayoutLine  *line,
170                                                gboolean          strong);
171 static void pango_layout_line_leaked (PangoLayoutLine *line);
172 
173 /* doesn't leak line */
174 static PangoLayoutLine* _pango_layout_iter_get_line (PangoLayoutIter *iter);
175 
176 static void pango_layout_get_item_properties (PangoItem      *item,
177                                               ItemProperties *properties);
178 
179 static void pango_layout_get_empty_extents_and_height_at_index (PangoLayout    *layout,
180                                                                 int             index,
181                                                                 PangoRectangle *logical_rect,
182                                                                 int            *height);
183 
184 static void pango_layout_finalize    (GObject          *object);
185 
G_DEFINE_TYPE(PangoLayout,pango_layout,G_TYPE_OBJECT)186 G_DEFINE_TYPE (PangoLayout, pango_layout, G_TYPE_OBJECT)
187 
188 static void
189 pango_layout_init (PangoLayout *layout)
190 {
191   layout->serial = 1;
192   layout->attrs = NULL;
193   layout->font_desc = NULL;
194   layout->text = NULL;
195   layout->length = 0;
196   layout->width = -1;
197   layout->height = -1;
198   layout->indent = 0;
199   layout->spacing = 0;
200   layout->line_spacing = 0.0;
201 
202   layout->alignment = PANGO_ALIGN_LEFT;
203   layout->justify = FALSE;
204   layout->auto_dir = TRUE;
205   layout->single_paragraph = FALSE;
206 
207   layout->log_attrs = NULL;
208   layout->lines = NULL;
209   layout->line_count = 0;
210 
211   layout->tab_width = -1;
212   layout->unknown_glyphs_count = -1;
213 
214   layout->wrap = PANGO_WRAP_WORD;
215   layout->is_wrapped = FALSE;
216   layout->ellipsize = PANGO_ELLIPSIZE_NONE;
217   layout->is_ellipsized = FALSE;
218 }
219 
220 static void
pango_layout_class_init(PangoLayoutClass * klass)221 pango_layout_class_init (PangoLayoutClass *klass)
222 {
223   GObjectClass *object_class = G_OBJECT_CLASS (klass);
224 
225   object_class->finalize = pango_layout_finalize;
226 }
227 
228 static void
pango_layout_finalize(GObject * object)229 pango_layout_finalize (GObject *object)
230 {
231   PangoLayout *layout;
232 
233   layout = PANGO_LAYOUT (object);
234 
235   pango_layout_clear_lines (layout);
236 
237   if (layout->context)
238     g_object_unref (layout->context);
239 
240   if (layout->attrs)
241     pango_attr_list_unref (layout->attrs);
242 
243   g_free (layout->text);
244 
245   if (layout->font_desc)
246     pango_font_description_free (layout->font_desc);
247 
248   if (layout->tabs)
249     pango_tab_array_free (layout->tabs);
250 
251   G_OBJECT_CLASS (pango_layout_parent_class)->finalize (object);
252 }
253 
254 /**
255  * pango_layout_new:
256  * @context: a `PangoContext`
257  *
258  * Create a new `PangoLayout` object with attributes initialized to
259  * default values for a particular `PangoContext`.
260  *
261  * Return value: the newly allocated `PangoLayout`
262  */
263 PangoLayout *
pango_layout_new(PangoContext * context)264 pango_layout_new (PangoContext *context)
265 {
266   PangoLayout *layout;
267 
268   g_return_val_if_fail (context != NULL, NULL);
269 
270   layout = g_object_new (PANGO_TYPE_LAYOUT, NULL);
271 
272   layout->context = context;
273   layout->context_serial = pango_context_get_serial (context);
274   g_object_ref (context);
275 
276   return layout;
277 }
278 
279 /**
280  * pango_layout_copy:
281  * @src: a `PangoLayout`
282  *
283  * Creates a deep copy-by-value of the layout.
284  *
285  * The attribute list, tab array, and text from the original layout
286  * are all copied by value.
287  *
288  * Return value: (transfer full): the newly allocated `PangoLayout`
289  */
290 PangoLayout*
pango_layout_copy(PangoLayout * src)291 pango_layout_copy (PangoLayout *src)
292 {
293   PangoLayout *layout;
294 
295   g_return_val_if_fail (PANGO_IS_LAYOUT (src), NULL);
296 
297   /* Copy referenced members */
298 
299   layout = pango_layout_new (src->context);
300   if (src->attrs)
301     layout->attrs = pango_attr_list_copy (src->attrs);
302   if (src->font_desc)
303     layout->font_desc = pango_font_description_copy (src->font_desc);
304   if (src->tabs)
305     layout->tabs = pango_tab_array_copy (src->tabs);
306 
307   /* Dupped */
308   layout->text = g_strdup (src->text);
309 
310   /* Value fields */
311   memcpy (&layout->copy_begin, &src->copy_begin,
312           G_STRUCT_OFFSET (PangoLayout, copy_end) - G_STRUCT_OFFSET (PangoLayout, copy_begin));
313 
314   return layout;
315 }
316 
317 /**
318  * pango_layout_get_context:
319  * @layout: a `PangoLayout`
320  *
321  * Retrieves the `PangoContext` used for this layout.
322  *
323  * Return value: (transfer none): the `PangoContext` for the layout
324  */
325 PangoContext *
pango_layout_get_context(PangoLayout * layout)326 pango_layout_get_context (PangoLayout *layout)
327 {
328   g_return_val_if_fail (layout != NULL, NULL);
329 
330   return layout->context;
331 }
332 
333 /**
334  * pango_layout_set_width:
335  * @layout: a `PangoLayout`.
336  * @width: the desired width in Pango units, or -1 to indicate that no
337  *   wrapping or ellipsization should be performed.
338  *
339  * Sets the width to which the lines of the `PangoLayout` should wrap or
340  * ellipsized.
341  *
342  * The default value is -1: no width set.
343  */
344 void
pango_layout_set_width(PangoLayout * layout,int width)345 pango_layout_set_width (PangoLayout *layout,
346                         int          width)
347 {
348   g_return_if_fail (layout != NULL);
349 
350   if (width < 0)
351     width = -1;
352 
353   if (width != layout->width)
354     {
355       layout->width = width;
356       layout_changed (layout);
357     }
358 }
359 
360 /**
361  * pango_layout_get_width:
362  * @layout: a `PangoLayout`
363  *
364  * Gets the width to which the lines of the `PangoLayout` should wrap.
365  *
366  * Return value: the width in Pango units, or -1 if no width set.
367  */
368 int
pango_layout_get_width(PangoLayout * layout)369 pango_layout_get_width (PangoLayout    *layout)
370 {
371   g_return_val_if_fail (layout != NULL, 0);
372   return layout->width;
373 }
374 
375 /**
376  * pango_layout_set_height:
377  * @layout: a `PangoLayout`.
378  * @height: the desired height of the layout in Pango units if positive,
379  *   or desired number of lines if negative.
380  *
381  * Sets the height to which the `PangoLayout` should be ellipsized at.
382  *
383  * There are two different behaviors, based on whether @height is positive
384  * or negative.
385  *
386  * If @height is positive, it will be the maximum height of the layout. Only
387  * lines would be shown that would fit, and if there is any text omitted,
388  * an ellipsis added. At least one line is included in each paragraph regardless
389  * of how small the height value is. A value of zero will render exactly one
390  * line for the entire layout.
391  *
392  * If @height is negative, it will be the (negative of) maximum number of lines
393  * per paragraph. That is, the total number of lines shown may well be more than
394  * this value if the layout contains multiple paragraphs of text.
395  * The default value of -1 means that the first line of each paragraph is ellipsized.
396  * This behavior may be changed in the future to act per layout instead of per
397  * paragraph. File a bug against pango at
398  * [https://gitlab.gnome.org/gnome/pango](https://gitlab.gnome.org/gnome/pango)
399  * if your code relies on this behavior.
400  *
401  * Height setting only has effect if a positive width is set on
402  * @layout and ellipsization mode of @layout is not %PANGO_ELLIPSIZE_NONE.
403  * The behavior is undefined if a height other than -1 is set and
404  * ellipsization mode is set to %PANGO_ELLIPSIZE_NONE, and may change in the
405  * future.
406  *
407  * Since: 1.20
408  */
409 void
pango_layout_set_height(PangoLayout * layout,int height)410 pango_layout_set_height (PangoLayout *layout,
411                          int          height)
412 {
413   g_return_if_fail (layout != NULL);
414 
415   if (height != layout->height)
416     {
417       layout->height = height;
418 
419       /* Do not invalidate if the number of lines requested is
420        * larger than the total number of lines in layout.
421        * Bug 549003
422        */
423       if (layout->ellipsize != PANGO_ELLIPSIZE_NONE &&
424           !(layout->lines && layout->is_ellipsized == FALSE &&
425             height < 0 && layout->line_count <= (guint) -height))
426         layout_changed (layout);
427     }
428 }
429 
430 /**
431  * pango_layout_get_height:
432  * @layout: a `PangoLayout`
433  *
434  * Gets the height of layout used for ellipsization.
435  *
436  * See [method@Pango.Layout.set_height] for details.
437  *
438  * Return value: the height, in Pango units if positive,
439  *   or number of lines if negative.
440  *
441  * Since: 1.20
442  */
443 int
pango_layout_get_height(PangoLayout * layout)444 pango_layout_get_height (PangoLayout *layout)
445 {
446   g_return_val_if_fail (layout != NULL, 0);
447   return layout->height;
448 }
449 
450 /**
451  * pango_layout_set_wrap:
452  * @layout: a `PangoLayout`
453  * @wrap: the wrap mode
454  *
455  * Sets the wrap mode.
456  *
457  * The wrap mode only has effect if a width is set on the layout
458  * with [method@Pango.Layout.set_width]. To turn off wrapping,
459  * set the width to -1.
460  *
461  * The default value is %PANGO_WRAP_WORD.
462  */
463 void
pango_layout_set_wrap(PangoLayout * layout,PangoWrapMode wrap)464 pango_layout_set_wrap (PangoLayout   *layout,
465                        PangoWrapMode  wrap)
466 {
467   g_return_if_fail (PANGO_IS_LAYOUT (layout));
468 
469   if (layout->wrap != wrap)
470     {
471       layout->wrap = wrap;
472 
473       if (layout->width != -1)
474         layout_changed (layout);
475     }
476 }
477 
478 /**
479  * pango_layout_get_wrap:
480  * @layout: a `PangoLayout`
481  *
482  * Gets the wrap mode for the layout.
483  *
484  * Use [method@Pango.Layout.is_wrapped] to query whether
485  * any paragraphs were actually wrapped.
486  *
487  * Return value: active wrap mode.
488  */
489 PangoWrapMode
pango_layout_get_wrap(PangoLayout * layout)490 pango_layout_get_wrap (PangoLayout *layout)
491 {
492   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0);
493 
494   return layout->wrap;
495 }
496 
497 /**
498  * pango_layout_is_wrapped:
499  * @layout: a `PangoLayout`
500  *
501  * Queries whether the layout had to wrap any paragraphs.
502  *
503  * This returns %TRUE if a positive width is set on @layout,
504  * ellipsization mode of @layout is set to %PANGO_ELLIPSIZE_NONE,
505  * and there are paragraphs exceeding the layout width that have
506  * to be wrapped.
507  *
508  * Return value: %TRUE if any paragraphs had to be wrapped, %FALSE
509  *   otherwise
510  *
511  * Since: 1.16
512  */
513 gboolean
pango_layout_is_wrapped(PangoLayout * layout)514 pango_layout_is_wrapped (PangoLayout *layout)
515 {
516   g_return_val_if_fail (layout != NULL, FALSE);
517 
518   pango_layout_check_lines (layout);
519 
520   return layout->is_wrapped;
521 }
522 
523 /**
524  * pango_layout_set_indent:
525  * @layout: a `PangoLayout`
526  * @indent: the amount by which to indent
527  *
528  * Sets the width in Pango units to indent each paragraph.
529  *
530  * A negative value of @indent will produce a hanging indentation.
531  * That is, the first line will have the full width, and subsequent
532  * lines will be indented by the absolute value of @indent.
533  *
534  * The indent setting is ignored if layout alignment is set to
535  * %PANGO_ALIGN_CENTER.
536  *
537  * The default value is 0.
538  */
539 void
pango_layout_set_indent(PangoLayout * layout,int indent)540 pango_layout_set_indent (PangoLayout *layout,
541                          int          indent)
542 {
543   g_return_if_fail (layout != NULL);
544 
545   if (indent != layout->indent)
546     {
547       layout->indent = indent;
548       layout_changed (layout);
549     }
550 }
551 
552 /**
553  * pango_layout_get_indent:
554  * @layout: a `PangoLayout`
555  *
556  * Gets the paragraph indent width in Pango units.
557  *
558  * A negative value indicates a hanging indentation.
559  *
560  * Return value: the indent in Pango units
561  */
562 int
pango_layout_get_indent(PangoLayout * layout)563 pango_layout_get_indent (PangoLayout *layout)
564 {
565   g_return_val_if_fail (layout != NULL, 0);
566   return layout->indent;
567 }
568 
569 /**
570  * pango_layout_set_spacing:
571  * @layout: a `PangoLayout`
572  * @spacing: the amount of spacing
573  *
574  * Sets the amount of spacing in Pango units between
575  * the lines of the layout.
576  *
577  * When placing lines with spacing, Pango arranges things so that
578  *
579  *     line2.top = line1.bottom + spacing
580  *
581  * The default value is 0.
582  *
583  * Note: Since 1.44, Pango is using the line height (as determined
584  * by the font) for placing lines when the line height factor is set
585  * to a non-zero value with [method@Pango.Layout.set_line_spacing].
586  * In that case, the @spacing set with this function is ignored.
587  */
588 void
pango_layout_set_spacing(PangoLayout * layout,int spacing)589 pango_layout_set_spacing (PangoLayout *layout,
590                           int          spacing)
591 {
592   g_return_if_fail (layout != NULL);
593 
594   if (spacing != layout->spacing)
595     {
596       layout->spacing = spacing;
597       layout_changed (layout);
598     }
599 }
600 
601 /**
602  * pango_layout_get_spacing:
603  * @layout: a `PangoLayout`
604  *
605  * Gets the amount of spacing between the lines of the layout.
606  *
607  * Return value: the spacing in Pango units
608  */
609 int
pango_layout_get_spacing(PangoLayout * layout)610 pango_layout_get_spacing (PangoLayout *layout)
611 {
612   g_return_val_if_fail (layout != NULL, 0);
613   return layout->spacing;
614 }
615 
616 /**
617  * pango_layout_set_line_spacing:
618  * @layout: a `PangoLayout`
619  * @factor: the new line spacing factor
620  *
621  * Sets a factor for line spacing.
622  *
623  * Typical values are: 0, 1, 1.5, 2. The default values is 0.
624  *
625  * If @factor is non-zero, lines are placed so that
626  *
627  *     baseline2 = baseline1 + factor * height2
628  *
629  * where height2 is the line height of the second line
630  * (as determined by the font(s)). In this case, the spacing
631  * set with [method@Pango.Layout.set_spacing] is ignored.
632  *
633  * If @factor is zero (the default), spacing is applied as before.
634  *
635  * Since: 1.44
636  */
637 void
pango_layout_set_line_spacing(PangoLayout * layout,float factor)638 pango_layout_set_line_spacing (PangoLayout *layout,
639                                float        factor)
640 {
641   g_return_if_fail (layout != NULL);
642 
643   if (layout->line_spacing != factor)
644     {
645       layout->line_spacing = factor;
646       layout_changed (layout);
647     }
648 }
649 
650 /**
651  * pango_layout_get_line_spacing:
652  * @layout: a `PangoLayout`
653  *
654  * Gets the line spacing factor of @layout.
655  *
656  * See [method@Pango.Layout.set_line_spacing].
657  *
658  * Since: 1.44
659  */
660 float
pango_layout_get_line_spacing(PangoLayout * layout)661 pango_layout_get_line_spacing (PangoLayout *layout)
662 {
663   g_return_val_if_fail (layout != NULL, 1.0);
664   return layout->line_spacing;
665 }
666 
667 /**
668  * pango_layout_set_attributes:
669  * @layout: a `PangoLayout`
670  * @attrs: (nullable) (transfer none): a `PangoAttrList`
671  *
672  * Sets the text attributes for a layout object.
673  *
674  * References @attrs, so the caller can unref its reference.
675  */
676 void
pango_layout_set_attributes(PangoLayout * layout,PangoAttrList * attrs)677 pango_layout_set_attributes (PangoLayout   *layout,
678                              PangoAttrList *attrs)
679 {
680   PangoAttrList *old_attrs;
681 
682   g_return_if_fail (layout != NULL);
683 
684   /* Both empty */
685   if (!attrs && !layout->attrs)
686     return;
687 
688   if (layout->attrs &&
689       pango_attr_list_equal (layout->attrs, attrs))
690     return;
691 
692   old_attrs = layout->attrs;
693 
694   /* We always clear lines such that this function can be called
695    * whenever attrs changes.
696    */
697   layout->attrs = attrs;
698   if (layout->attrs)
699     pango_attr_list_ref (layout->attrs);
700 
701   layout_changed (layout);
702 
703   if (old_attrs)
704     pango_attr_list_unref (old_attrs);
705   layout->tab_width = -1;
706 }
707 
708 /**
709  * pango_layout_get_attributes:
710  * @layout: a `PangoLayout`
711  *
712  * Gets the attribute list for the layout, if any.
713  *
714  * Return value: (transfer none) (nullable): a `PangoAttrList`
715  */
716 PangoAttrList*
pango_layout_get_attributes(PangoLayout * layout)717 pango_layout_get_attributes (PangoLayout *layout)
718 {
719   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
720 
721   return layout->attrs;
722 }
723 
724 /**
725  * pango_layout_set_font_description:
726  * @layout: a `PangoLayout`
727  * @desc: (nullable): the new `PangoFontDescription`
728  *   to unset the current font description
729  *
730  * Sets the default font description for the layout.
731  *
732  * If no font description is set on the layout, the
733  * font description from the layout's context is used.
734  */
735 void
pango_layout_set_font_description(PangoLayout * layout,const PangoFontDescription * desc)736 pango_layout_set_font_description (PangoLayout                *layout,
737                                    const PangoFontDescription *desc)
738 {
739   g_return_if_fail (layout != NULL);
740 
741   if (desc != layout->font_desc &&
742       (!desc || !layout->font_desc || !pango_font_description_equal(desc, layout->font_desc)))
743     {
744       if (layout->font_desc)
745         pango_font_description_free (layout->font_desc);
746 
747       layout->font_desc = desc ? pango_font_description_copy (desc) : NULL;
748 
749       layout_changed (layout);
750       layout->tab_width = -1;
751     }
752 }
753 
754 /**
755  * pango_layout_get_font_description:
756  * @layout: a `PangoLayout`
757  *
758  * Gets the font description for the layout, if any.
759  *
760  * Return value: (transfer none) (nullable): a pointer to the
761  *   layout's font description, or %NULL if the font description
762  *   from the layout's context is inherited.
763  *
764  * Since: 1.8
765  */
766 const PangoFontDescription *
pango_layout_get_font_description(PangoLayout * layout)767 pango_layout_get_font_description (PangoLayout *layout)
768 {
769   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
770 
771   return layout->font_desc;
772 }
773 
774 /**
775  * pango_layout_set_justify:
776  * @layout: a `PangoLayout`
777  * @justify: whether the lines in the layout should be justified
778  *
779  * Sets whether each complete line should be stretched to fill the
780  * entire width of the layout.
781  *
782  * Stretching is typically done by adding whitespace, but for some scripts
783  * (such as Arabic), the justification may be done in more complex ways,
784  * like extending the characters.
785  *
786  * Note that this setting is not implemented and so is ignored in
787  * Pango older than 1.18.
788  *
789  * The default value is %FALSE.
790  */
791 void
pango_layout_set_justify(PangoLayout * layout,gboolean justify)792 pango_layout_set_justify (PangoLayout *layout,
793                           gboolean     justify)
794 {
795   g_return_if_fail (layout != NULL);
796 
797   if (justify != layout->justify)
798     {
799       layout->justify = justify;
800 
801       if (layout->is_ellipsized || layout->is_wrapped)
802         layout_changed (layout);
803     }
804 }
805 
806 /**
807  * pango_layout_get_justify:
808  * @layout: a `PangoLayout`
809  *
810  * Gets whether each complete line should be stretched to fill the entire
811  * width of the layout.
812  *
813  * Return value: the justify value
814  */
815 gboolean
pango_layout_get_justify(PangoLayout * layout)816 pango_layout_get_justify (PangoLayout *layout)
817 {
818   g_return_val_if_fail (layout != NULL, FALSE);
819   return layout->justify;
820 }
821 
822 /**
823  * pango_layout_set_auto_dir:
824  * @layout: a `PangoLayout`
825  * @auto_dir: if %TRUE, compute the bidirectional base direction
826  *   from the layout's contents
827  *
828  * Sets whether to calculate the base direction
829  * for the layout according to its contents.
830  *
831  * When this flag is on (the default), then paragraphs in @layout that
832  * begin with strong right-to-left characters (Arabic and Hebrew principally),
833  * will have right-to-left layout, paragraphs with letters from other scripts
834  * will have left-to-right layout. Paragraphs with only neutral characters
835  * get their direction from the surrounding paragraphs.
836  *
837  * When %FALSE, the choice between left-to-right and right-to-left
838  * layout is done according to the base direction of the layout's
839  * `PangoContext`. (See [method@Pango.Context.set_base_dir]).
840  *
841  * When the auto-computed direction of a paragraph differs from the
842  * base direction of the context, the interpretation of
843  * %PANGO_ALIGN_LEFT and %PANGO_ALIGN_RIGHT are swapped.
844  *
845  * Since: 1.4
846  */
847 void
pango_layout_set_auto_dir(PangoLayout * layout,gboolean auto_dir)848 pango_layout_set_auto_dir (PangoLayout *layout,
849                            gboolean     auto_dir)
850 {
851   g_return_if_fail (PANGO_IS_LAYOUT (layout));
852 
853   auto_dir = auto_dir != FALSE;
854 
855   if (auto_dir != layout->auto_dir)
856     {
857       layout->auto_dir = auto_dir;
858       layout_changed (layout);
859     }
860 }
861 
862 /**
863  * pango_layout_get_auto_dir:
864  * @layout: a `PangoLayout`
865  *
866  * Gets whether to calculate the base direction for the layout
867  * according to its contents.
868  *
869  * See [method@Pango.Layout.set_auto_dir].
870  *
871  * Return value: %TRUE if the bidirectional base direction
872  *   is computed from the layout's contents, %FALSE otherwise
873  *
874  * Since: 1.4
875  */
876 gboolean
pango_layout_get_auto_dir(PangoLayout * layout)877 pango_layout_get_auto_dir (PangoLayout *layout)
878 {
879   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), TRUE);
880 
881   return layout->auto_dir;
882 }
883 
884 /**
885  * pango_layout_set_alignment:
886  * @layout: a `PangoLayout`
887  * @alignment: the alignment
888  *
889  * Sets the alignment for the layout: how partial lines are
890  * positioned within the horizontal space available.
891  *
892  * The default alignment is %PANGO_ALIGN_LEFT.
893  */
894 void
pango_layout_set_alignment(PangoLayout * layout,PangoAlignment alignment)895 pango_layout_set_alignment (PangoLayout   *layout,
896                             PangoAlignment alignment)
897 {
898   g_return_if_fail (layout != NULL);
899 
900   if (alignment != layout->alignment)
901     {
902       layout->alignment = alignment;
903       layout_changed (layout);
904     }
905 }
906 
907 /**
908  * pango_layout_get_alignment:
909  * @layout: a `PangoLayout`
910  *
911  * Gets the alignment for the layout: how partial lines are
912  * positioned within the horizontal space available.
913  *
914  * Return value: the alignment
915  */
916 PangoAlignment
pango_layout_get_alignment(PangoLayout * layout)917 pango_layout_get_alignment (PangoLayout *layout)
918 {
919   g_return_val_if_fail (layout != NULL, PANGO_ALIGN_LEFT);
920   return layout->alignment;
921 }
922 
923 
924 /**
925  * pango_layout_set_tabs:
926  * @layout: a `PangoLayout`
927  * @tabs: (nullable): a `PangoTabArray`
928  *
929  * Sets the tabs to use for @layout, overriding the default tabs.
930  *
931  * By default, tabs are every 8 spaces. If @tabs is %NULL, the
932  * default tabs are reinstated. @tabs is copied into the layout;
933  * you must free your copy of @tabs yourself.
934  */
935 void
pango_layout_set_tabs(PangoLayout * layout,PangoTabArray * tabs)936 pango_layout_set_tabs (PangoLayout   *layout,
937                        PangoTabArray *tabs)
938 {
939   g_return_if_fail (PANGO_IS_LAYOUT (layout));
940 
941 
942   if (tabs != layout->tabs)
943     {
944       if (layout->tabs)
945         pango_tab_array_free (layout->tabs);
946 
947       layout->tabs = tabs ? pango_tab_array_copy (tabs) : NULL;
948 
949       layout_changed (layout);
950     }
951 }
952 
953 /**
954  * pango_layout_get_tabs:
955  * @layout: a `PangoLayout`
956  *
957  * Gets the current `PangoTabArray` used by this layout.
958  *
959  * If no `PangoTabArray` has been set, then the default tabs are
960  * in use and %NULL is returned. Default tabs are every 8 spaces.
961  *
962  * The return value should be freed with [method@Pango.TabArray.free].
963  *
964  * Return value: (transfer full) (nullable): a copy of the tabs for this layout
965  */
966 PangoTabArray*
pango_layout_get_tabs(PangoLayout * layout)967 pango_layout_get_tabs (PangoLayout *layout)
968 {
969   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
970 
971   if (layout->tabs)
972     return pango_tab_array_copy (layout->tabs);
973   else
974     return NULL;
975 }
976 
977 /**
978  * pango_layout_set_single_paragraph_mode:
979  * @layout: a `PangoLayout`
980  * @setting: new setting
981  *
982  * Sets the single paragraph mode of @layout.
983  *
984  * If @setting is %TRUE, do not treat newlines and similar characters
985  * as paragraph separators; instead, keep all text in a single paragraph,
986  * and display a glyph for paragraph separator characters. Used when
987  * you want to allow editing of newlines on a single text line.
988  *
989  * The default value is %FALSE.
990  */
991 void
pango_layout_set_single_paragraph_mode(PangoLayout * layout,gboolean setting)992 pango_layout_set_single_paragraph_mode (PangoLayout *layout,
993                                         gboolean     setting)
994 {
995   g_return_if_fail (PANGO_IS_LAYOUT (layout));
996 
997   setting = setting != FALSE;
998 
999   if (layout->single_paragraph != setting)
1000     {
1001       layout->single_paragraph = setting;
1002       layout_changed (layout);
1003     }
1004 }
1005 
1006 /**
1007  * pango_layout_get_single_paragraph_mode:
1008  * @layout: a `PangoLayout`
1009  *
1010  * Obtains whether @layout is in single paragraph mode.
1011  *
1012  * See [method@Pango.Layout.set_single_paragraph_mode].
1013  *
1014  * Return value: %TRUE if the layout does not break paragraphs
1015  *   at paragraph separator characters, %FALSE otherwise
1016  */
1017 gboolean
pango_layout_get_single_paragraph_mode(PangoLayout * layout)1018 pango_layout_get_single_paragraph_mode (PangoLayout *layout)
1019 {
1020   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE);
1021 
1022   return layout->single_paragraph;
1023 }
1024 
1025 /**
1026  * pango_layout_set_ellipsize:
1027  * @layout: a `PangoLayout`
1028  * @ellipsize: the new ellipsization mode for @layout
1029  *
1030  * Sets the type of ellipsization being performed for @layout.
1031  *
1032  * Depending on the ellipsization mode @ellipsize text is
1033  * removed from the start, middle, or end of text so they
1034  * fit within the width and height of layout set with
1035  * [method@Pango.Layout.set_width] and [method@Pango.Layout.set_height].
1036  *
1037  * If the layout contains characters such as newlines that
1038  * force it to be layed out in multiple paragraphs, then whether
1039  * each paragraph is ellipsized separately or the entire layout
1040  * is ellipsized as a whole depends on the set height of the layout.
1041  *
1042  * The default value is %PANGO_ELLIPSIZE_NONE.
1043  *
1044  * See [method@Pango.Layout.set_height] for details.
1045  *
1046  * Since: 1.6
1047  */
1048 void
pango_layout_set_ellipsize(PangoLayout * layout,PangoEllipsizeMode ellipsize)1049 pango_layout_set_ellipsize (PangoLayout        *layout,
1050                             PangoEllipsizeMode  ellipsize)
1051 {
1052   g_return_if_fail (PANGO_IS_LAYOUT (layout));
1053 
1054   if (ellipsize != layout->ellipsize)
1055     {
1056       layout->ellipsize = ellipsize;
1057 
1058       if (layout->is_ellipsized || layout->is_wrapped)
1059         layout_changed (layout);
1060     }
1061 }
1062 
1063 /**
1064  * pango_layout_get_ellipsize:
1065  * @layout: a `PangoLayout`
1066  *
1067  * Gets the type of ellipsization being performed for @layout.
1068  *
1069  * See [method@Pango.Layout.set_ellipsize].
1070  *
1071  * Use [method@Pango.Layout.is_ellipsized] to query whether any
1072  * paragraphs were actually ellipsized.
1073  *
1074  * Return value: the current ellipsization mode for @layout
1075  *
1076  * Since: 1.6
1077  */
1078 PangoEllipsizeMode
pango_layout_get_ellipsize(PangoLayout * layout)1079 pango_layout_get_ellipsize (PangoLayout *layout)
1080 {
1081   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_ELLIPSIZE_NONE);
1082 
1083   return layout->ellipsize;
1084 }
1085 
1086 /**
1087  * pango_layout_is_ellipsized:
1088  * @layout: a `PangoLayout`
1089  *
1090  * Queries whether the layout had to ellipsize any paragraphs.
1091  *
1092  * This returns %TRUE if the ellipsization mode for @layout
1093  * is not %PANGO_ELLIPSIZE_NONE, a positive width is set on @layout,
1094  * and there are paragraphs exceeding that width that have to be
1095  * ellipsized.
1096  *
1097  * Return value: %TRUE if any paragraphs had to be ellipsized,
1098  *   %FALSE otherwise
1099  *
1100  * Since: 1.16
1101  */
1102 gboolean
pango_layout_is_ellipsized(PangoLayout * layout)1103 pango_layout_is_ellipsized (PangoLayout *layout)
1104 {
1105   g_return_val_if_fail (layout != NULL, FALSE);
1106 
1107   pango_layout_check_lines (layout);
1108 
1109   return layout->is_ellipsized;
1110 }
1111 
1112 /**
1113  * pango_layout_set_text:
1114  * @layout: a `PangoLayout`
1115  * @text: the text
1116  * @length: maximum length of @text, in bytes. -1 indicates that
1117  *   the string is nul-terminated and the length should be calculated.
1118  *   The text will also be truncated on encountering a nul-termination
1119  *   even when @length is positive.
1120  *
1121  * Sets the text of the layout.
1122  *
1123  * This function validates @text and renders invalid UTF-8
1124  * with a placeholder glyph.
1125  *
1126  * Note that if you have used [method@Pango.Layout.set_markup] or
1127  * [method@Pango.Layout.set_markup_with_accel] on @layout before, you
1128  * may want to call [method@Pango.Layout.set_attributes] to clear the
1129  * attributes set on the layout from the markup as this function does
1130  * not clear attributes.
1131  */
1132 void
pango_layout_set_text(PangoLayout * layout,const char * text,int length)1133 pango_layout_set_text (PangoLayout *layout,
1134                        const char  *text,
1135                        int          length)
1136 {
1137   char *old_text, *start, *end;
1138 
1139   g_return_if_fail (layout != NULL);
1140   g_return_if_fail (length == 0 || text != NULL);
1141 
1142   old_text = layout->text;
1143 
1144   if (length < 0)
1145     {
1146       layout->length = strlen (text);
1147       layout->text = g_strndup (text, layout->length);
1148     }
1149   else if (length > 0)
1150     {
1151       /* This is not exactly what we want.  We don't need the padding...
1152        */
1153       layout->length = length;
1154       layout->text = g_strndup (text, length);
1155     }
1156   else
1157     {
1158       layout->length = 0;
1159       layout->text = g_malloc0 (1);
1160     }
1161 
1162   /* validate it, and replace invalid bytes with -1 */
1163   start = layout->text;
1164   for (;;) {
1165     gboolean valid;
1166 
1167     valid = g_utf8_validate (start, -1, (const char **)&end);
1168 
1169     if (!*end)
1170       break;
1171 
1172     /* Replace invalid bytes with -1.  The -1 will be converted to
1173      * ((gunichar) -1) by glib, and that in turn yields a glyph value of
1174      * ((PangoGlyph) -1) by PANGO_GET_UNKNOWN_GLYPH(-1),
1175      * and that's PANGO_GLYPH_INVALID_INPUT.
1176      */
1177     if (!valid)
1178       *end++ = -1;
1179 
1180     start = end;
1181   }
1182 
1183   if (start != layout->text)
1184     /* TODO: Write out the beginning excerpt of text? */
1185     g_warning ("Invalid UTF-8 string passed to pango_layout_set_text()");
1186 
1187   layout->n_chars = pango_utf8_strlen (layout->text, -1);
1188   layout->length = strlen (layout->text);
1189 
1190   layout_changed (layout);
1191 
1192   g_free (old_text);
1193 }
1194 
1195 /**
1196  * pango_layout_get_text:
1197  * @layout: a `PangoLayout`
1198  *
1199  * Gets the text in the layout.
1200  *
1201  * The returned text should not be freed or modified.
1202  *
1203  * Return value: (transfer none): the text in the @layout
1204  */
1205 const char*
pango_layout_get_text(PangoLayout * layout)1206 pango_layout_get_text (PangoLayout *layout)
1207 {
1208   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
1209 
1210   /* We don't ever want to return NULL as the text.
1211    */
1212   if (G_UNLIKELY (!layout->text))
1213     return "";
1214 
1215   return layout->text;
1216 }
1217 
1218 /**
1219  * pango_layout_get_character_count:
1220  * @layout: a `PangoLayout`
1221  *
1222  * Returns the number of Unicode characters in the
1223  * the text of @layout.
1224  *
1225  * Return value: the number of Unicode characters
1226  *   in the text of @layout
1227  *
1228  * Since: 1.30
1229  */
1230 gint
pango_layout_get_character_count(PangoLayout * layout)1231 pango_layout_get_character_count (PangoLayout *layout)
1232 {
1233   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0);
1234 
1235   return layout->n_chars;
1236 }
1237 
1238 /**
1239  * pango_layout_set_markup:
1240  * @layout: a `PangoLayout`
1241  * @markup: marked-up text
1242  * @length: length of marked-up text in bytes, or -1 if @markup is
1243  *   `NUL`-terminated
1244  *
1245  * Sets the layout text and attribute list from marked-up text.
1246  *
1247  * See [Pango Markup](pango_markup.html)).
1248  *
1249  * Replaces the current text and attribute list.
1250  *
1251  * This is the same as [method@Pango.Layout.set_markup_with_accel],
1252  * but the markup text isn't scanned for accelerators.
1253  */
1254 void
pango_layout_set_markup(PangoLayout * layout,const char * markup,int length)1255 pango_layout_set_markup (PangoLayout *layout,
1256                          const char  *markup,
1257                          int          length)
1258 {
1259   pango_layout_set_markup_with_accel (layout, markup, length, 0, NULL);
1260 }
1261 
1262 /**
1263  * pango_layout_set_markup_with_accel:
1264  * @layout: a `PangoLayout`
1265  * @markup: marked-up text (see [Pango Markup](pango_markup.html))
1266  * @length: length of marked-up text in bytes, or -1 if @markup is
1267  *   `NUL`-terminated
1268  * @accel_marker: marker for accelerators in the text
1269  * @accel_char: (out caller-allocates) (optional): return location
1270  *   for first located accelerator
1271  *
1272  * Sets the layout text and attribute list from marked-up text.
1273  *
1274  * See [Pango Markup](pango_markup.html)).
1275  *
1276  * Replaces the current text and attribute list.
1277  *
1278  * If @accel_marker is nonzero, the given character will mark the
1279  * character following it as an accelerator. For example, @accel_marker
1280  * might be an ampersand or underscore. All characters marked
1281  * as an accelerator will receive a %PANGO_UNDERLINE_LOW attribute,
1282  * and the first character so marked will be returned in @accel_char.
1283  * Two @accel_marker characters following each other produce a single
1284  * literal @accel_marker character.
1285  */
1286 void
pango_layout_set_markup_with_accel(PangoLayout * layout,const char * markup,int length,gunichar accel_marker,gunichar * accel_char)1287 pango_layout_set_markup_with_accel (PangoLayout *layout,
1288                                     const char  *markup,
1289                                     int          length,
1290                                     gunichar     accel_marker,
1291                                     gunichar    *accel_char)
1292 {
1293   PangoAttrList *list = NULL;
1294   char *text = NULL;
1295   GError *error;
1296 
1297   g_return_if_fail (PANGO_IS_LAYOUT (layout));
1298   g_return_if_fail (markup != NULL);
1299 
1300   error = NULL;
1301   if (!pango_parse_markup (markup, length,
1302                            accel_marker,
1303                            &list, &text,
1304                            accel_char,
1305                            &error))
1306     {
1307       g_warning ("pango_layout_set_markup_with_accel: %s", error->message);
1308       g_error_free (error);
1309       return;
1310     }
1311 
1312   pango_layout_set_text (layout, text, -1);
1313   pango_layout_set_attributes (layout, list);
1314   pango_attr_list_unref (list);
1315   g_free (text);
1316 }
1317 
1318 /**
1319  * pango_layout_get_unknown_glyphs_count:
1320  * @layout: a `PangoLayout`
1321  *
1322  * Counts the number of unknown glyphs in @layout.
1323  *
1324  * This function can be used to determine if there are any fonts
1325  * available to render all characters in a certain string, or when
1326  * used in combination with %PANGO_ATTR_FALLBACK, to check if a
1327  * certain font supports all the characters in the string.
1328  *
1329  * Return value: The number of unknown glyphs in @layout
1330  *
1331  * Since: 1.16
1332  */
1333 int
pango_layout_get_unknown_glyphs_count(PangoLayout * layout)1334 pango_layout_get_unknown_glyphs_count (PangoLayout *layout)
1335 {
1336     PangoLayoutLine *line;
1337     PangoLayoutRun *run;
1338     GSList *lines_list;
1339     GSList *runs_list;
1340     int i, count = 0;
1341 
1342     g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0);
1343 
1344     pango_layout_check_lines (layout);
1345 
1346     if (layout->unknown_glyphs_count >= 0)
1347       return layout->unknown_glyphs_count;
1348 
1349     lines_list = layout->lines;
1350     while (lines_list)
1351       {
1352         line = lines_list->data;
1353         runs_list = line->runs;
1354 
1355         while (runs_list)
1356           {
1357             run = runs_list->data;
1358 
1359             for (i = 0; i < run->glyphs->num_glyphs; i++)
1360               {
1361                 if (run->glyphs->glyphs[i].glyph & PANGO_GLYPH_UNKNOWN_FLAG)
1362                     count++;
1363               }
1364 
1365             runs_list = runs_list->next;
1366           }
1367         lines_list = lines_list->next;
1368       }
1369 
1370     layout->unknown_glyphs_count = count;
1371     return count;
1372 }
1373 
1374 static void
check_context_changed(PangoLayout * layout)1375 check_context_changed (PangoLayout *layout)
1376 {
1377   guint old_serial = layout->context_serial;
1378 
1379   layout->context_serial = pango_context_get_serial (layout->context);
1380 
1381   if (old_serial != layout->context_serial)
1382     pango_layout_context_changed (layout);
1383 }
1384 
1385 static void
layout_changed(PangoLayout * layout)1386 layout_changed (PangoLayout *layout)
1387 {
1388   layout->serial++;
1389   if (layout->serial == 0)
1390     layout->serial++;
1391   pango_layout_clear_lines (layout);
1392 }
1393 
1394 /**
1395  * pango_layout_context_changed:
1396  * @layout: a `PangoLayout`
1397  *
1398  * Forces recomputation of any state in the `PangoLayout` that
1399  * might depend on the layout's context.
1400  *
1401  * This function should be called if you make changes to the context
1402  * subsequent to creating the layout.
1403  */
1404 void
pango_layout_context_changed(PangoLayout * layout)1405 pango_layout_context_changed (PangoLayout *layout)
1406 {
1407   g_return_if_fail (PANGO_IS_LAYOUT (layout));
1408 
1409   layout_changed (layout);
1410   layout->tab_width = -1;
1411 }
1412 
1413 /**
1414  * pango_layout_get_serial:
1415  * @layout: a `PangoLayout`
1416  *
1417  * Returns the current serial number of @layout.
1418  *
1419  * The serial number is initialized to an small number larger than zero
1420  * when a new layout is created and is increased whenever the layout is
1421  * changed using any of the setter functions, or the `PangoContext` it
1422  * uses has changed. The serial may wrap, but will never have the value 0.
1423  * Since it can wrap, never compare it with "less than", always use "not equals".
1424  *
1425  * This can be used to automatically detect changes to a `PangoLayout`,
1426  * and is useful for example to decide whether a layout needs redrawing.
1427  * To force the serial to be increased, use
1428  * [method@Pango.Layout.context_changed].
1429  *
1430  * Return value: The current serial number of @layout.
1431  *
1432  * Since: 1.32.4
1433  */
1434 guint
pango_layout_get_serial(PangoLayout * layout)1435 pango_layout_get_serial (PangoLayout *layout)
1436 {
1437   check_context_changed (layout);
1438   return layout->serial;
1439 }
1440 
1441 /**
1442  * pango_layout_get_log_attrs:
1443  * @layout: a `PangoLayout`
1444  * @attrs: (out)(array length=n_attrs)(transfer container):
1445  *   location to store a pointer to an array of logical attributes.
1446  *   This value must be freed with g_free().
1447  * @n_attrs: (out): location to store the number of the attributes in the
1448  *   array. (The stored value will be one more than the total number
1449  *   of characters in the layout, since there need to be attributes
1450  *   corresponding to both the position before the first character
1451  *   and the position after the last character.)
1452  *
1453  * Retrieves an array of logical attributes for each character in
1454  * the @layout.
1455  */
1456 void
pango_layout_get_log_attrs(PangoLayout * layout,PangoLogAttr ** attrs,gint * n_attrs)1457 pango_layout_get_log_attrs (PangoLayout   *layout,
1458                             PangoLogAttr **attrs,
1459                             gint          *n_attrs)
1460 {
1461   g_return_if_fail (layout != NULL);
1462 
1463   pango_layout_check_lines (layout);
1464 
1465   if (attrs)
1466     {
1467       *attrs = g_new (PangoLogAttr, layout->n_chars + 1);
1468       memcpy (*attrs, layout->log_attrs, sizeof(PangoLogAttr) * (layout->n_chars + 1));
1469     }
1470 
1471   if (n_attrs)
1472     *n_attrs = layout->n_chars + 1;
1473 }
1474 
1475 /**
1476  * pango_layout_get_log_attrs_readonly:
1477  * @layout: a `PangoLayout`
1478  * @n_attrs: (out): location to store the number of the attributes in
1479  *   the array
1480  *
1481  * Retrieves an array of logical attributes for each character in
1482  * the @layout.
1483  *
1484  * This is a faster alternative to [method@Pango.Layout.get_log_attrs].
1485  * The returned array is part of @layout and must not be modified.
1486  * Modifying the layout will invalidate the returned array.
1487  *
1488  * The number of attributes returned in @n_attrs will be one more
1489  * than the total number of characters in the layout, since there
1490  * need to be attributes corresponding to both the position before
1491  * the first character and the position after the last character.
1492  *
1493  * Returns: (array length=n_attrs): an array of logical attributes
1494  *
1495  * Since: 1.30
1496  */
1497 const PangoLogAttr *
pango_layout_get_log_attrs_readonly(PangoLayout * layout,gint * n_attrs)1498 pango_layout_get_log_attrs_readonly (PangoLayout *layout,
1499                                      gint        *n_attrs)
1500 {
1501   if (n_attrs)
1502     *n_attrs = 0;
1503   g_return_val_if_fail (layout != NULL, NULL);
1504 
1505   pango_layout_check_lines (layout);
1506 
1507   if (n_attrs)
1508     *n_attrs = layout->n_chars + 1;
1509 
1510   return layout->log_attrs;
1511 }
1512 
1513 
1514 /**
1515  * pango_layout_get_line_count:
1516  * @layout: `PangoLayout`
1517  *
1518  * Retrieves the count of lines for the @layout.
1519  *
1520  * Return value: the line count
1521  */
1522 int
pango_layout_get_line_count(PangoLayout * layout)1523 pango_layout_get_line_count (PangoLayout   *layout)
1524 {
1525   g_return_val_if_fail (layout != NULL, 0);
1526 
1527   pango_layout_check_lines (layout);
1528   return layout->line_count;
1529 }
1530 
1531 /**
1532  * pango_layout_get_lines:
1533  * @layout: a `PangoLayout`
1534  *
1535  * Returns the lines of the @layout as a list.
1536  *
1537  * Use the faster [method@Pango.Layout.get_lines_readonly] if you do not
1538  * plan to modify the contents of the lines (glyphs, glyph widths, etc.).
1539  *
1540  * Return value: (element-type Pango.LayoutLine) (transfer none): a `GSList`
1541  *   containing the lines in the layout. This points to internal data of the
1542  *   `PangoLayout` and must be used with care. It will become invalid on any
1543  *   change to the layout's text or properties.
1544  */
1545 GSList *
pango_layout_get_lines(PangoLayout * layout)1546 pango_layout_get_lines (PangoLayout *layout)
1547 {
1548   pango_layout_check_lines (layout);
1549 
1550   if (layout->lines)
1551     {
1552       GSList *tmp_list = layout->lines;
1553       while (tmp_list)
1554         {
1555           PangoLayoutLine *line = tmp_list->data;
1556           tmp_list = tmp_list->next;
1557 
1558           pango_layout_line_leaked (line);
1559         }
1560     }
1561 
1562   return layout->lines;
1563 }
1564 
1565 /**
1566  * pango_layout_get_lines_readonly:
1567  * @layout: a `PangoLayout`
1568  *
1569  * Returns the lines of the @layout as a list.
1570  *
1571  * This is a faster alternative to [method@Pango.Layout.get_lines],
1572  * but the user is not expected to modify the contents of the lines
1573  * (glyphs, glyph widths, etc.).
1574  *
1575  * Return value: (element-type Pango.LayoutLine) (transfer none): a `GSList`
1576  *   containing the lines in the layout. This points to internal data of the
1577  *   `PangoLayout` and must be used with care. It will become invalid on any
1578  *   change to the layout's text or properties. No changes should be made to
1579  *   the lines.
1580  *
1581  * Since: 1.16
1582  */
1583 GSList *
pango_layout_get_lines_readonly(PangoLayout * layout)1584 pango_layout_get_lines_readonly (PangoLayout *layout)
1585 {
1586   pango_layout_check_lines (layout);
1587 
1588   return layout->lines;
1589 }
1590 
1591 /**
1592  * pango_layout_get_line:
1593  * @layout: a `PangoLayout`
1594  * @line: the index of a line, which must be between 0 and
1595  *   `pango_layout_get_line_count(layout) - 1`, inclusive.
1596  *
1597  * Retrieves a particular line from a `PangoLayout`.
1598  *
1599  * Use the faster [method@Pango.Layout.get_line_readonly] if you do not
1600  * plan to modify the contents of the line (glyphs, glyph widths, etc.).
1601  *
1602  * Return value: (transfer none) (nullable): the requested `PangoLayoutLine`,
1603  *   or %NULL if the index is out of range. This layout line can be ref'ed
1604  *   and retained, but will become invalid if changes are made to the
1605  *   `PangoLayout`.
1606  */
1607 PangoLayoutLine *
pango_layout_get_line(PangoLayout * layout,int line)1608 pango_layout_get_line (PangoLayout *layout,
1609                        int          line)
1610 {
1611   GSList *list_item;
1612   g_return_val_if_fail (layout != NULL, NULL);
1613 
1614   if (line < 0)
1615     return NULL;
1616 
1617   pango_layout_check_lines (layout);
1618 
1619   list_item = g_slist_nth (layout->lines, line);
1620 
1621   if (list_item)
1622     {
1623       PangoLayoutLine *line = list_item->data;
1624 
1625       pango_layout_line_leaked (line);
1626       return line;
1627     }
1628 
1629   return NULL;
1630 }
1631 
1632 /**
1633  * pango_layout_get_line_readonly:
1634  * @layout: a `PangoLayout`
1635  * @line: the index of a line, which must be between 0 and
1636  *   `pango_layout_get_line_count(layout) - 1`, inclusive.
1637  *
1638  * Retrieves a particular line from a `PangoLayout`.
1639  *
1640  * This is a faster alternative to [method@Pango.Layout.get_line],
1641  * but the user is not expected to modify the contents of the line
1642  * (glyphs, glyph widths, etc.).
1643  *
1644  * Return value: (transfer none) (nullable): the requested `PangoLayoutLine`,
1645  *   or %NULL if the index is out of range. This layout line can be ref'ed
1646  *   and retained, but will become invalid if changes are made to the
1647  *   `PangoLayout`. No changes should be made to the line.
1648  *
1649  * Since: 1.16
1650  */
1651 PangoLayoutLine *
pango_layout_get_line_readonly(PangoLayout * layout,int line)1652 pango_layout_get_line_readonly (PangoLayout *layout,
1653                                 int          line)
1654 {
1655   GSList *list_item;
1656   g_return_val_if_fail (layout != NULL, NULL);
1657 
1658   if (line < 0)
1659     return NULL;
1660 
1661   pango_layout_check_lines (layout);
1662 
1663   list_item = g_slist_nth (layout->lines, line);
1664 
1665   if (list_item)
1666     {
1667       PangoLayoutLine *line = list_item->data;
1668       return line;
1669     }
1670 
1671   return NULL;
1672 }
1673 
1674 /**
1675  * pango_layout_line_index_to_x:
1676  * @line: a `PangoLayoutLine`
1677  * @index_: byte offset of a grapheme within the layout
1678  * @trailing: an integer indicating the edge of the grapheme to retrieve
1679  *   the position of. If > 0, the trailing edge of the grapheme,
1680  *   if 0, the leading of the grapheme
1681  * @x_pos: (out): location to store the x_offset (in Pango units)
1682  *
1683  * Converts an index within a line to a X position.
1684  */
1685 void
pango_layout_line_index_to_x(PangoLayoutLine * line,int index,int trailing,int * x_pos)1686 pango_layout_line_index_to_x (PangoLayoutLine *line,
1687                               int              index,
1688                               int              trailing,
1689                               int             *x_pos)
1690 {
1691   PangoLayout *layout = line->layout;
1692   GSList *run_list = line->runs;
1693   int width = 0;
1694 
1695   while (run_list)
1696     {
1697       PangoLayoutRun *run = run_list->data;
1698 
1699       if (run->item->offset <= index && run->item->offset + run->item->length > index)
1700         {
1701           int offset = g_utf8_pointer_to_offset (layout->text, layout->text + index);
1702           if (trailing)
1703             {
1704               while (index < line->start_index + line->length &&
1705                      offset + 1 < layout->n_chars &&
1706                      !layout->log_attrs[offset + 1].is_cursor_position)
1707                 {
1708                   offset++;
1709                   index = g_utf8_next_char (layout->text + index) - layout->text;
1710                 }
1711             }
1712           else
1713             {
1714               while (index > line->start_index &&
1715                      !layout->log_attrs[offset].is_cursor_position)
1716                 {
1717                   offset--;
1718                   index = g_utf8_prev_char (layout->text + index) - layout->text;
1719                 }
1720 
1721             }
1722 
1723           pango_glyph_string_index_to_x (run->glyphs,
1724                                          layout->text + run->item->offset,
1725                                          run->item->length,
1726                                          &run->item->analysis,
1727                                          index - run->item->offset, trailing, x_pos);
1728           if (x_pos)
1729             *x_pos += width;
1730 
1731           return;
1732         }
1733 
1734       width += pango_glyph_string_get_width (run->glyphs);
1735 
1736       run_list = run_list->next;
1737     }
1738 
1739   if (x_pos)
1740     *x_pos = width;
1741 }
1742 
1743 static PangoLayoutLine *
pango_layout_index_to_line(PangoLayout * layout,int index,int * line_nr,PangoLayoutLine ** line_before,PangoLayoutLine ** line_after)1744 pango_layout_index_to_line (PangoLayout      *layout,
1745                             int               index,
1746                             int              *line_nr,
1747                             PangoLayoutLine **line_before,
1748                             PangoLayoutLine **line_after)
1749 {
1750   GSList *tmp_list;
1751   GSList *line_list;
1752   PangoLayoutLine *line = NULL;
1753   PangoLayoutLine *prev_line = NULL;
1754   int i = -1;
1755 
1756   line_list = tmp_list = layout->lines;
1757   while (tmp_list)
1758     {
1759       PangoLayoutLine *tmp_line = tmp_list->data;
1760 
1761       if (tmp_line->start_index > index)
1762         break; /* index was in paragraph delimiters */
1763 
1764       prev_line = line;
1765       line = tmp_line;
1766       line_list = tmp_list;
1767       i++;
1768 
1769       if (line->start_index + line->length > index)
1770         break;
1771 
1772       tmp_list = tmp_list->next;
1773     }
1774 
1775   if (line_nr)
1776     *line_nr = i;
1777 
1778   if (line_before)
1779     *line_before = prev_line;
1780 
1781   if (line_after)
1782     *line_after = (line_list && line_list->next) ? line_list->next->data : NULL;
1783 
1784   return line;
1785 }
1786 
1787 static PangoLayoutLine *
pango_layout_index_to_line_and_extents(PangoLayout * layout,int index,PangoRectangle * line_rect)1788 pango_layout_index_to_line_and_extents (PangoLayout     *layout,
1789                                         int              index,
1790                                         PangoRectangle  *line_rect)
1791 {
1792   PangoLayoutIter iter;
1793   PangoLayoutLine *line = NULL;
1794 
1795   _pango_layout_get_iter (layout, &iter);
1796 
1797   if (!ITER_IS_INVALID (&iter))
1798     while (TRUE)
1799       {
1800         PangoLayoutLine *tmp_line = _pango_layout_iter_get_line (&iter);
1801 
1802         if (tmp_line->start_index > index)
1803             break; /* index was in paragraph delimiters */
1804 
1805         line = tmp_line;
1806 
1807         pango_layout_iter_get_line_extents (&iter, NULL, line_rect);
1808 
1809         if (line->start_index + line->length > index)
1810           break;
1811 
1812         if (!pango_layout_iter_next_line (&iter))
1813           break; /* Use end of last line */
1814       }
1815 
1816   _pango_layout_iter_destroy (&iter);
1817 
1818   return line;
1819 }
1820 
1821 /**
1822  * pango_layout_index_to_line_x:
1823  * @layout: a `PangoLayout`
1824  * @index_: the byte index of a grapheme within the layout
1825  * @trailing: an integer indicating the edge of the grapheme to retrieve the
1826  *   position of. If > 0, the trailing edge of the grapheme, if 0,
1827  *   the leading of the grapheme
1828  * @line: (out) (optional): location to store resulting line index. (which will
1829  *   between 0 and pango_layout_get_line_count(layout) - 1)
1830  * @x_pos: (out) (optional): location to store resulting position within line
1831  *   (%PANGO_SCALE units per device unit)
1832  *
1833  * Converts from byte @index_ within the @layout to line and X position.
1834  *
1835  * The X position is measured from the left edge of the line.
1836  */
1837 void
pango_layout_index_to_line_x(PangoLayout * layout,int index,gboolean trailing,int * line,int * x_pos)1838 pango_layout_index_to_line_x (PangoLayout *layout,
1839                               int          index,
1840                               gboolean     trailing,
1841                               int         *line,
1842                               int         *x_pos)
1843 {
1844   int line_num;
1845   PangoLayoutLine *layout_line = NULL;
1846 
1847   g_return_if_fail (layout != NULL);
1848   g_return_if_fail (index >= 0);
1849   g_return_if_fail (index <= layout->length);
1850 
1851   pango_layout_check_lines (layout);
1852 
1853   layout_line = pango_layout_index_to_line (layout, index,
1854                                             &line_num, NULL, NULL);
1855 
1856   if (layout_line)
1857     {
1858       /* use end of line if index was in the paragraph delimiters */
1859       if (index > layout_line->start_index + layout_line->length)
1860         index = layout_line->start_index + layout_line->length;
1861 
1862       if (line)
1863         *line = line_num;
1864 
1865       pango_layout_line_index_to_x (layout_line, index, trailing, x_pos);
1866     }
1867   else
1868     {
1869       if (line)
1870         *line = -1;
1871       if (x_pos)
1872         *x_pos = -1;
1873     }
1874 }
1875 
1876 /**
1877  * pango_layout_move_cursor_visually:
1878  * @layout: a `PangoLayout`
1879  * @strong: whether the moving cursor is the strong cursor or the
1880  *   weak cursor. The strong cursor is the cursor corresponding
1881  *   to text insertion in the base direction for the layout.
1882  * @old_index: the byte index of the grapheme for the old index
1883  * @old_trailing: if 0, the cursor was at the leading edge of the
1884  *   grapheme indicated by @old_index, if > 0, the cursor
1885  *   was at the trailing edge.
1886  * @direction: direction to move cursor. A negative
1887  *   value indicates motion to the left
1888  * @new_index: (out): location to store the new cursor byte index.
1889  *   A value of -1 indicates that the cursor has been moved off the
1890  *   beginning of the layout. A value of %G_MAXINT indicates that
1891  *   the cursor has been moved off the end of the layout.
1892  * @new_trailing: (out): number of characters to move forward from
1893  *   the location returned for @new_index to get the position where
1894  *   the cursor should be displayed. This allows distinguishing the
1895  *   position at the beginning of one line from the position at the
1896  *   end of the preceding line. @new_index is always on the line where
1897  *   the cursor should be displayed.
1898  *
1899  * Computes a new cursor position from an old position and a count of
1900  * positions to move visually.
1901  *
1902  * If @direction is positive, then the new strong cursor position will be
1903  * one position to the right of the old cursor position. If @direction is
1904  * negative, then the new strong cursor position will be one position to
1905  * the left of the old cursor position.
1906  *
1907  * In the presence of bidirectional text, the correspondence between
1908  * logical and visual order will depend on the direction of the current
1909  * run, and there may be jumps when the cursor is moved off of the end
1910  * of a run.
1911  *
1912  * Motion here is in cursor positions, not in characters, so a single
1913  * call to [method@Pango.Layout.move_cursor_visually] may move the cursor
1914  * over multiple characters when multiple characters combine to form a
1915  * single grapheme.
1916  */
1917 void
pango_layout_move_cursor_visually(PangoLayout * layout,gboolean strong,int old_index,int old_trailing,int direction,int * new_index,int * new_trailing)1918 pango_layout_move_cursor_visually (PangoLayout *layout,
1919                                    gboolean     strong,
1920                                    int          old_index,
1921                                    int          old_trailing,
1922                                    int          direction,
1923                                    int         *new_index,
1924                                    int         *new_trailing)
1925 {
1926   PangoLayoutLine *line = NULL;
1927   PangoLayoutLine *prev_line;
1928   PangoLayoutLine *next_line;
1929 
1930   int *log2vis_map;
1931   int *vis2log_map;
1932   int n_vis;
1933   int vis_pos, vis_pos_old, log_pos;
1934   int start_offset;
1935   gboolean off_start = FALSE;
1936   gboolean off_end = FALSE;
1937 
1938   g_return_if_fail (layout != NULL);
1939   g_return_if_fail (old_index >= 0 && old_index <= layout->length);
1940   g_return_if_fail (old_index < layout->length || old_trailing == 0);
1941   g_return_if_fail (new_index != NULL);
1942   g_return_if_fail (new_trailing != NULL);
1943 
1944   direction = (direction >= 0 ? 1 : -1);
1945 
1946   pango_layout_check_lines (layout);
1947 
1948   /* Find the line the old cursor is on */
1949   line = pango_layout_index_to_line (layout, old_index,
1950                                      NULL, &prev_line, &next_line);
1951 
1952   start_offset = g_utf8_pointer_to_offset (layout->text, layout->text + line->start_index);
1953 
1954   while (old_trailing--)
1955     old_index = g_utf8_next_char (layout->text + old_index) - layout->text;
1956 
1957   log2vis_map = pango_layout_line_get_log2vis_map (line, strong);
1958   n_vis = pango_utf8_strlen (layout->text + line->start_index, line->length);
1959 
1960   /* Clamp old_index to fit on the line */
1961   if (old_index > (line->start_index + line->length))
1962     old_index = line->start_index + line->length;
1963 
1964   vis_pos = log2vis_map[old_index - line->start_index];
1965 
1966   g_free (log2vis_map);
1967 
1968   /* Handling movement between lines */
1969   if (vis_pos == 0 && direction < 0)
1970     {
1971       if (line->resolved_dir == PANGO_DIRECTION_LTR)
1972         off_start = TRUE;
1973       else
1974         off_end = TRUE;
1975     }
1976   else if (vis_pos == n_vis && direction > 0)
1977     {
1978       if (line->resolved_dir == PANGO_DIRECTION_LTR)
1979         off_end = TRUE;
1980       else
1981         off_start = TRUE;
1982     }
1983 
1984   if (off_start || off_end)
1985     {
1986       /* If we move over a paragraph boundary, count that as
1987        * an extra position in the motion
1988        */
1989       gboolean paragraph_boundary;
1990 
1991       if (off_start)
1992         {
1993           if (!prev_line)
1994             {
1995               *new_index = -1;
1996               *new_trailing = 0;
1997               return;
1998             }
1999           line = prev_line;
2000           paragraph_boundary = (line->start_index + line->length != old_index);
2001         }
2002       else
2003         {
2004           if (!next_line)
2005             {
2006               *new_index = G_MAXINT;
2007               *new_trailing = 0;
2008               return;
2009             }
2010           line = next_line;
2011           paragraph_boundary = (line->start_index != old_index);
2012         }
2013 
2014       n_vis = pango_utf8_strlen (layout->text + line->start_index, line->length);
2015       start_offset = g_utf8_pointer_to_offset (layout->text, layout->text + line->start_index);
2016 
2017       if (vis_pos == 0 && direction < 0)
2018         {
2019           vis_pos = n_vis;
2020           if (paragraph_boundary)
2021             vis_pos++;
2022         }
2023       else /* (vis_pos == n_vis && direction > 0) */
2024         {
2025           vis_pos = 0;
2026           if (paragraph_boundary)
2027             vis_pos--;
2028         }
2029     }
2030 
2031   vis2log_map = pango_layout_line_get_vis2log_map (line, strong);
2032 
2033   vis_pos_old = vis_pos + direction;
2034   log_pos = g_utf8_pointer_to_offset (layout->text + line->start_index,
2035                                       layout->text + line->start_index + vis2log_map[vis_pos_old]);
2036   do
2037     {
2038       vis_pos += direction;
2039       log_pos += g_utf8_pointer_to_offset (layout->text + line->start_index + vis2log_map[vis_pos_old],
2040                                            layout->text + line->start_index + vis2log_map[vis_pos]);
2041       vis_pos_old = vis_pos;
2042     }
2043   while (vis_pos > 0 && vis_pos < n_vis &&
2044          !layout->log_attrs[start_offset + log_pos].is_cursor_position);
2045 
2046   *new_index = line->start_index + vis2log_map[vis_pos];
2047   g_free (vis2log_map);
2048 
2049   *new_trailing = 0;
2050 
2051   if (*new_index == line->start_index + line->length && line->length > 0)
2052     {
2053       do
2054         {
2055           log_pos--;
2056           *new_index = g_utf8_prev_char (layout->text + *new_index) - layout->text;
2057           (*new_trailing)++;
2058         }
2059       while (log_pos > 0 && !layout->log_attrs[start_offset + log_pos].is_cursor_position);
2060     }
2061 }
2062 
2063 /**
2064  * pango_layout_xy_to_index:
2065  * @layout: a `PangoLayout`
2066  * @x: the X offset (in Pango units) from the left edge of the layout
2067  * @y: the Y offset (in Pango units) from the top edge of the layout
2068  * @index_: (out): location to store calculated byte index
2069  * @trailing: (out): location to store a integer indicating where
2070  *   in the grapheme the user clicked. It will either be zero, or the
2071  *   number of characters in the grapheme. 0 represents the leading edge
2072  *   of the grapheme.
2073  *
2074  * Converts from X and Y position within a layout to the byte index to the
2075  * character at that logical position.
2076  *
2077  * If the Y position is not inside the layout, the closest position is
2078  * chosen (the position will be clamped inside the layout). If the X position
2079  * is not within the layout, then the start or the end of the line is
2080  * chosen as described for [method@Pango.LayoutLine.x_to_index]. If either
2081  * the X or Y positions were not inside the layout, then the function returns
2082  * %FALSE; on an exact hit, it returns %TRUE.
2083  *
2084  * Return value: %TRUE if the coordinates were inside text, %FALSE otherwise
2085  */
2086 gboolean
pango_layout_xy_to_index(PangoLayout * layout,int x,int y,int * index,gint * trailing)2087 pango_layout_xy_to_index (PangoLayout *layout,
2088                           int          x,
2089                           int          y,
2090                           int         *index,
2091                           gint        *trailing)
2092 {
2093   PangoLayoutIter iter;
2094   PangoLayoutLine *prev_line = NULL;
2095   PangoLayoutLine *found = NULL;
2096   int found_line_x = 0;
2097   int prev_last = 0;
2098   int prev_line_x = 0;
2099   gboolean retval = FALSE;
2100   gboolean outside = FALSE;
2101 
2102   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE);
2103 
2104   _pango_layout_get_iter (layout, &iter);
2105 
2106   do
2107     {
2108       PangoRectangle line_logical;
2109       int first_y, last_y;
2110 
2111       g_assert (!ITER_IS_INVALID (&iter));
2112 
2113       pango_layout_iter_get_line_extents (&iter, NULL, &line_logical);
2114       pango_layout_iter_get_line_yrange (&iter, &first_y, &last_y);
2115 
2116       if (y < first_y)
2117         {
2118           if (prev_line && y < (prev_last + (first_y - prev_last) / 2))
2119             {
2120               found = prev_line;
2121               found_line_x = prev_line_x;
2122             }
2123           else
2124             {
2125               if (prev_line == NULL)
2126                 outside = TRUE; /* off the top */
2127 
2128               found = _pango_layout_iter_get_line (&iter);
2129               found_line_x = x - line_logical.x;
2130             }
2131         }
2132       else if (y >= first_y &&
2133                y < last_y)
2134         {
2135           found = _pango_layout_iter_get_line (&iter);
2136           found_line_x = x - line_logical.x;
2137         }
2138 
2139       prev_line = _pango_layout_iter_get_line (&iter);
2140       prev_last = last_y;
2141       prev_line_x = x - line_logical.x;
2142 
2143       if (found != NULL)
2144         break;
2145     }
2146   while (pango_layout_iter_next_line (&iter));
2147 
2148   _pango_layout_iter_destroy (&iter);
2149 
2150   if (found == NULL)
2151     {
2152       /* Off the bottom of the layout */
2153       outside = TRUE;
2154 
2155       found = prev_line;
2156       found_line_x = prev_line_x;
2157     }
2158 
2159   retval = pango_layout_line_x_to_index (found,
2160                                          found_line_x,
2161                                          index, trailing);
2162 
2163   if (outside)
2164     retval = FALSE;
2165 
2166   return retval;
2167 }
2168 
2169 /**
2170  * pango_layout_index_to_pos:
2171  * @layout: a `PangoLayout`
2172  * @index_: byte index within @layout
2173  * @pos: (out): rectangle in which to store the position of the grapheme
2174  *
2175  * Converts from an index within a `PangoLayout` to the onscreen position
2176  * corresponding to the grapheme at that index.
2177  *
2178  * The return value is represented as rectangle. Note that `pos->x` is
2179  * always the leading edge of the grapheme and `pos->x + pos->width` the
2180  * trailing edge of the grapheme. If the directionality of the grapheme
2181  * is right-to-left, then `pos->width` will be negative.
2182  */
2183 void
pango_layout_index_to_pos(PangoLayout * layout,int index,PangoRectangle * pos)2184 pango_layout_index_to_pos (PangoLayout    *layout,
2185                            int             index,
2186                            PangoRectangle *pos)
2187 {
2188   PangoRectangle logical_rect;
2189   PangoLayoutIter iter;
2190   PangoLayoutLine *layout_line = NULL;
2191   int x_pos;
2192 
2193   g_return_if_fail (layout != NULL);
2194   g_return_if_fail (index >= 0);
2195   g_return_if_fail (pos != NULL);
2196 
2197   _pango_layout_get_iter (layout, &iter);
2198 
2199   if (!ITER_IS_INVALID (&iter))
2200     {
2201       while (TRUE)
2202         {
2203           PangoLayoutLine *tmp_line = _pango_layout_iter_get_line (&iter);
2204 
2205           if (tmp_line->start_index > index)
2206             {
2207               /* index is in the paragraph delim&iters, move to
2208                * end of previous line
2209                *
2210                * This shouldn’t occur in the first loop &iteration as the first
2211                * line’s start_index should always be 0.
2212                */
2213               g_assert (layout_line != NULL);
2214               index = layout_line->start_index + layout_line->length;
2215               break;
2216             }
2217 
2218           layout_line = tmp_line;
2219 
2220           pango_layout_iter_get_line_extents (&iter, NULL, &logical_rect);
2221 
2222           if (layout_line->start_index + layout_line->length > index)
2223             break;
2224 
2225           if (!pango_layout_iter_next_line (&iter))
2226             {
2227               index = layout_line->start_index + layout_line->length;
2228               break;
2229             }
2230         }
2231 
2232       pos->y = logical_rect.y;
2233       pos->height = logical_rect.height;
2234 
2235       pango_layout_line_index_to_x (layout_line, index, 0, &x_pos);
2236       pos->x = logical_rect.x + x_pos;
2237 
2238       if (index < layout_line->start_index + layout_line->length)
2239         {
2240           pango_layout_line_index_to_x (layout_line, index, 1, &x_pos);
2241           pos->width = (logical_rect.x + x_pos) - pos->x;
2242         }
2243       else
2244         pos->width = 0;
2245     }
2246 
2247   _pango_layout_iter_destroy (&iter);
2248 }
2249 
2250 static void
pango_layout_line_get_range(PangoLayoutLine * line,char ** start,char ** end)2251 pango_layout_line_get_range (PangoLayoutLine *line,
2252                              char           **start,
2253                              char           **end)
2254 {
2255   char *p;
2256 
2257   p = line->layout->text + line->start_index;
2258 
2259   if (start)
2260     *start = p;
2261   if (end)
2262     *end = p + line->length;
2263 }
2264 
2265 static int *
pango_layout_line_get_vis2log_map(PangoLayoutLine * line,gboolean strong)2266 pango_layout_line_get_vis2log_map (PangoLayoutLine *line,
2267                                    gboolean         strong)
2268 {
2269   PangoLayout *layout = line->layout;
2270   PangoDirection prev_dir;
2271   PangoDirection cursor_dir;
2272   GSList *tmp_list;
2273   gchar *start, *end;
2274   int *result;
2275   int pos;
2276   int n_chars;
2277 
2278   pango_layout_line_get_range (line, &start, &end);
2279   n_chars = pango_utf8_strlen (start, end - start);
2280 
2281   result = g_new (int, n_chars + 1);
2282 
2283   if (strong)
2284     cursor_dir = line->resolved_dir;
2285   else
2286     cursor_dir = (line->resolved_dir == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
2287 
2288   /* Handle the first visual position
2289    */
2290   if (line->resolved_dir == cursor_dir)
2291     result[0] = line->resolved_dir == PANGO_DIRECTION_LTR ? 0 : end - start;
2292 
2293   prev_dir = line->resolved_dir;
2294   pos = 0;
2295   tmp_list = line->runs;
2296   while (tmp_list)
2297     {
2298       PangoLayoutRun *run = tmp_list->data;
2299       int run_n_chars = run->item->num_chars;
2300       PangoDirection run_dir = (run->item->analysis.level % 2) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
2301       char *p = layout->text + run->item->offset;
2302       int i;
2303 
2304       /* pos is the visual position at the start of the run */
2305       /* p is the logical byte index at the start of the run */
2306 
2307       if (run_dir == PANGO_DIRECTION_LTR)
2308         {
2309           if ((cursor_dir == PANGO_DIRECTION_LTR) ||
2310               (prev_dir == run_dir))
2311             result[pos] = p - start;
2312 
2313           p = g_utf8_next_char (p);
2314 
2315           for (i = 1; i < run_n_chars; i++)
2316             {
2317               result[pos + i] = p - start;
2318               p = g_utf8_next_char (p);
2319             }
2320 
2321           if (cursor_dir == PANGO_DIRECTION_LTR)
2322             result[pos + run_n_chars] = p - start;
2323         }
2324       else
2325         {
2326           if (cursor_dir == PANGO_DIRECTION_RTL)
2327             result[pos + run_n_chars] = p - start;
2328 
2329           p = g_utf8_next_char (p);
2330 
2331           for (i = 1; i < run_n_chars; i++)
2332             {
2333               result[pos + run_n_chars - i] = p - start;
2334               p = g_utf8_next_char (p);
2335             }
2336 
2337           if ((cursor_dir == PANGO_DIRECTION_RTL) ||
2338               (prev_dir == run_dir))
2339             result[pos] = p - start;
2340         }
2341 
2342       pos += run_n_chars;
2343       prev_dir = run_dir;
2344       tmp_list = tmp_list->next;
2345     }
2346 
2347   /* And the last visual position
2348    */
2349   if ((cursor_dir == line->resolved_dir) || (prev_dir == line->resolved_dir))
2350     result[pos] = line->resolved_dir == PANGO_DIRECTION_LTR ? end - start : 0;
2351 
2352   return result;
2353 }
2354 
2355 static int *
pango_layout_line_get_log2vis_map(PangoLayoutLine * line,gboolean strong)2356 pango_layout_line_get_log2vis_map (PangoLayoutLine *line,
2357                                    gboolean         strong)
2358 {
2359   gchar *start, *end;
2360   int *reverse_map;
2361   int *result;
2362   int i;
2363   int n_chars;
2364 
2365   pango_layout_line_get_range (line, &start, &end);
2366   n_chars = pango_utf8_strlen (start, end - start);
2367   result = g_new0 (int, end - start + 1);
2368 
2369   reverse_map = pango_layout_line_get_vis2log_map (line, strong);
2370 
2371   for (i=0; i <= n_chars; i++)
2372     result[reverse_map[i]] = i;
2373 
2374   g_free (reverse_map);
2375 
2376   return result;
2377 }
2378 
2379 static PangoDirection
pango_layout_line_get_char_direction(PangoLayoutLine * layout_line,int index)2380 pango_layout_line_get_char_direction (PangoLayoutLine *layout_line,
2381                                       int              index)
2382 {
2383   GSList *run_list;
2384 
2385   run_list = layout_line->runs;
2386   while (run_list)
2387     {
2388       PangoLayoutRun *run = run_list->data;
2389 
2390       if (run->item->offset <= index && run->item->offset + run->item->length > index)
2391         return run->item->analysis.level % 2 ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
2392 
2393       run_list = run_list->next;
2394     }
2395 
2396   return PANGO_DIRECTION_LTR;
2397 }
2398 
2399 /**
2400  * pango_layout_get_direction:
2401  * @layout: a `PangoLayout`
2402  * @index: the byte index of the char
2403  *
2404  * Gets the text direction at the given character position in @layout.
2405  *
2406  * Returns: the text direction at @index
2407  *
2408  * Since: 1.46
2409  */
2410 PangoDirection
pango_layout_get_direction(PangoLayout * layout,int index)2411 pango_layout_get_direction (PangoLayout *layout,
2412                             int          index)
2413 {
2414   PangoLayoutLine *line;
2415 
2416   line = pango_layout_index_to_line_and_extents (layout, index, NULL);
2417 
2418   if (line)
2419     return pango_layout_line_get_char_direction (line, index);
2420 
2421   return PANGO_DIRECTION_LTR;
2422 }
2423 
2424 /**
2425  * pango_layout_get_cursor_pos:
2426  * @layout: a `PangoLayout`
2427  * @index_: the byte index of the cursor
2428  * @strong_pos: (out) (optional): location to store the strong cursor position
2429  * @weak_pos: (out) (optional): location to store the weak cursor position
2430  *
2431  * Given an index within a layout, determines the positions that of the
2432  * strong and weak cursors if the insertion point is at that index.
2433  *
2434  * The position of each cursor is stored as a zero-width rectangle.
2435  * The strong cursor location is the location where characters of the
2436  * directionality equal to the base direction of the layout are inserted.
2437  * The weak cursor location is the location where characters of the
2438  * directionality opposite to the base direction of the layout are inserted.
2439  */
2440 void
pango_layout_get_cursor_pos(PangoLayout * layout,int index,PangoRectangle * strong_pos,PangoRectangle * weak_pos)2441 pango_layout_get_cursor_pos (PangoLayout    *layout,
2442                              int             index,
2443                              PangoRectangle *strong_pos,
2444                              PangoRectangle *weak_pos)
2445 {
2446   PangoDirection dir1;
2447   PangoRectangle line_rect;
2448   PangoLayoutLine *layout_line = NULL; /* Quiet GCC */
2449   int x1_trailing;
2450   int x2;
2451 
2452   g_return_if_fail (layout != NULL);
2453   g_return_if_fail (index >= 0 && index <= layout->length);
2454 
2455   layout_line = pango_layout_index_to_line_and_extents (layout, index,
2456                                                         &line_rect);
2457 
2458   g_assert (index >= layout_line->start_index);
2459 
2460   /* Examine the trailing edge of the character before the cursor */
2461   if (index == layout_line->start_index)
2462     {
2463       dir1 = layout_line->resolved_dir;
2464       if (layout_line->resolved_dir == PANGO_DIRECTION_LTR)
2465         x1_trailing = 0;
2466       else
2467         x1_trailing = line_rect.width;
2468     }
2469   else if (index >= layout_line->start_index + layout_line->length)
2470     {
2471       dir1 = layout_line->resolved_dir;
2472       if (layout_line->resolved_dir == PANGO_DIRECTION_LTR)
2473         x1_trailing = line_rect.width;
2474       else
2475         x1_trailing = 0;
2476     }
2477   else
2478     {
2479       gint prev_index = g_utf8_prev_char (layout->text + index) - layout->text;
2480       dir1 = pango_layout_line_get_char_direction (layout_line, prev_index);
2481       pango_layout_line_index_to_x (layout_line, prev_index, TRUE, &x1_trailing);
2482     }
2483 
2484   /* Examine the leading edge of the character after the cursor */
2485   if (index >= layout_line->start_index + layout_line->length)
2486     {
2487       if (layout_line->resolved_dir == PANGO_DIRECTION_LTR)
2488         x2 = line_rect.width;
2489       else
2490         x2 = 0;
2491     }
2492   else
2493     {
2494       pango_layout_line_index_to_x (layout_line, index, FALSE, &x2);
2495     }
2496 
2497   if (strong_pos)
2498     {
2499       strong_pos->x = line_rect.x;
2500 
2501       if (dir1 == layout_line->resolved_dir)
2502         strong_pos->x += x1_trailing;
2503       else
2504         strong_pos->x += x2;
2505 
2506       strong_pos->y = line_rect.y;
2507       strong_pos->width = 0;
2508       strong_pos->height = line_rect.height;
2509     }
2510 
2511   if (weak_pos)
2512     {
2513       weak_pos->x = line_rect.x;
2514 
2515       if (dir1 == layout_line->resolved_dir)
2516         weak_pos->x += x2;
2517       else
2518         weak_pos->x += x1_trailing;
2519 
2520       weak_pos->y = line_rect.y;
2521       weak_pos->width = 0;
2522       weak_pos->height = line_rect.height;
2523     }
2524 }
2525 
2526 static inline int
direction_simple(PangoDirection d)2527 direction_simple (PangoDirection d)
2528 {
2529   switch (d)
2530     {
2531     case PANGO_DIRECTION_LTR :
2532     case PANGO_DIRECTION_WEAK_LTR :
2533     case PANGO_DIRECTION_TTB_RTL :
2534       return 1;
2535     case PANGO_DIRECTION_RTL :
2536     case PANGO_DIRECTION_WEAK_RTL :
2537     case PANGO_DIRECTION_TTB_LTR :
2538       return -1;
2539     case PANGO_DIRECTION_NEUTRAL :
2540       return 0;
2541     /* no default, compiler should complain if a new values is added */
2542     }
2543   /* not reached */
2544   return 0;
2545 }
2546 
2547 static PangoAlignment
get_alignment(PangoLayout * layout,PangoLayoutLine * line)2548 get_alignment (PangoLayout     *layout,
2549                PangoLayoutLine *line)
2550 {
2551   PangoAlignment alignment = layout->alignment;
2552 
2553   if (alignment != PANGO_ALIGN_CENTER && line->layout->auto_dir &&
2554       direction_simple (line->resolved_dir) ==
2555       -direction_simple (pango_context_get_base_dir (layout->context)))
2556     {
2557       if (alignment == PANGO_ALIGN_LEFT)
2558         alignment = PANGO_ALIGN_RIGHT;
2559       else if (alignment == PANGO_ALIGN_RIGHT)
2560         alignment = PANGO_ALIGN_LEFT;
2561     }
2562 
2563   return alignment;
2564 }
2565 
2566 static void
get_x_offset(PangoLayout * layout,PangoLayoutLine * line,int layout_width,int line_width,int * x_offset)2567 get_x_offset (PangoLayout     *layout,
2568               PangoLayoutLine *line,
2569               int              layout_width,
2570               int              line_width,
2571               int             *x_offset)
2572 {
2573   PangoAlignment alignment = get_alignment (layout, line);
2574 
2575   /* Alignment */
2576   if (layout_width == 0)
2577     *x_offset = 0;
2578   else if (alignment == PANGO_ALIGN_RIGHT)
2579     *x_offset = layout_width - line_width;
2580   else if (alignment == PANGO_ALIGN_CENTER) {
2581     *x_offset = (layout_width - line_width) / 2;
2582     /* hinting */
2583     if (((layout_width | line_width) & (PANGO_SCALE - 1)) == 0)
2584       {
2585         *x_offset = PANGO_UNITS_ROUND (*x_offset);
2586       }
2587   } else
2588     *x_offset = 0;
2589 
2590   /* Indentation */
2591 
2592   /* For center, we ignore indentation; I think I've seen word
2593    * processors that still do the indentation here as if it were
2594    * indented left/right, though we can't sensibly do that without
2595    * knowing whether left/right is the "normal" thing for this text
2596    */
2597 
2598   if (alignment == PANGO_ALIGN_CENTER)
2599     return;
2600 
2601   if (line->is_paragraph_start)
2602     {
2603       if (layout->indent > 0)
2604         {
2605           if (alignment == PANGO_ALIGN_LEFT)
2606             *x_offset += layout->indent;
2607           else
2608             *x_offset -= layout->indent;
2609         }
2610     }
2611   else
2612     {
2613       if (layout->indent < 0)
2614         {
2615           if (alignment == PANGO_ALIGN_LEFT)
2616             *x_offset -= layout->indent;
2617           else
2618             *x_offset += layout->indent;
2619         }
2620     }
2621 }
2622 
2623 static void
2624 pango_layout_line_get_extents_and_height (PangoLayoutLine *line,
2625                                           PangoRectangle *ink,
2626                                           PangoRectangle *logical,
2627                                           int            *height);
2628 static void
get_line_extents_layout_coords(PangoLayout * layout,PangoLayoutLine * line,int layout_width,int y_offset,int * baseline,PangoRectangle * line_ink_layout,PangoRectangle * line_logical_layout)2629 get_line_extents_layout_coords (PangoLayout     *layout,
2630                                 PangoLayoutLine *line,
2631                                 int              layout_width,
2632                                 int              y_offset,
2633                                 int             *baseline,
2634                                 PangoRectangle  *line_ink_layout,
2635                                 PangoRectangle  *line_logical_layout)
2636 {
2637   int x_offset;
2638   /* Line extents in line coords (origin at line baseline) */
2639   PangoRectangle line_ink;
2640   PangoRectangle line_logical;
2641   gboolean first_line;
2642   int new_baseline;
2643   int height;
2644 
2645   if (layout->lines->data == line)
2646     first_line = TRUE;
2647   else
2648     first_line = FALSE;
2649 
2650   pango_layout_line_get_extents_and_height (line, line_ink_layout ? &line_ink : NULL,
2651                                             &line_logical,
2652                                             &height);
2653 
2654   get_x_offset (layout, line, layout_width, line_logical.width, &x_offset);
2655 
2656   if (first_line || !baseline || layout->line_spacing == 0.0)
2657     new_baseline = y_offset - line_logical.y;
2658   else
2659     new_baseline = *baseline + layout->line_spacing * height;
2660 
2661   /* Convert the line's extents into layout coordinates */
2662   if (line_ink_layout)
2663     {
2664       *line_ink_layout = line_ink;
2665       line_ink_layout->x = line_ink.x + x_offset;
2666       line_ink_layout->y = new_baseline + line_ink.y;
2667     }
2668 
2669   if (line_logical_layout)
2670     {
2671       *line_logical_layout = line_logical;
2672       line_logical_layout->x = line_logical.x + x_offset;
2673       line_logical_layout->y = new_baseline + line_logical.y;
2674     }
2675 
2676   if (baseline)
2677     *baseline = new_baseline;
2678 }
2679 
2680 /* if non-NULL line_extents returns a list of line extents
2681  * in layout coordinates
2682  */
2683 static void
pango_layout_get_extents_internal(PangoLayout * layout,PangoRectangle * ink_rect,PangoRectangle * logical_rect,Extents ** line_extents)2684 pango_layout_get_extents_internal (PangoLayout    *layout,
2685                                    PangoRectangle *ink_rect,
2686                                    PangoRectangle *logical_rect,
2687                                    Extents        **line_extents)
2688 {
2689   GSList *line_list;
2690   int y_offset = 0;
2691   int width;
2692   gboolean need_width = FALSE;
2693   int line_index = 0;
2694   int baseline;
2695 
2696   g_return_if_fail (layout != NULL);
2697 
2698   pango_layout_check_lines (layout);
2699 
2700   if (ink_rect && layout->ink_rect_cached)
2701     {
2702       *ink_rect = layout->ink_rect;
2703       ink_rect = NULL;
2704     }
2705   if (logical_rect && layout->logical_rect_cached)
2706     {
2707       *logical_rect = layout->logical_rect;
2708       logical_rect = NULL;
2709     }
2710   if (!ink_rect && !logical_rect && !line_extents)
2711     return;
2712 
2713   /* When we are not wrapping, we need the overall width of the layout to
2714    * figure out the x_offsets of each line. However, we only need the
2715    * x_offsets if we are computing the ink_rect or individual line extents.
2716    */
2717   width = layout->width;
2718 
2719   if (layout->auto_dir)
2720     {
2721       /* If one of the lines of the layout is not left aligned, then we need
2722        * the width of the layout to calculate line x-offsets; this requires
2723        * looping through the lines for layout->auto_dir.
2724        */
2725       line_list = layout->lines;
2726       while (line_list && !need_width)
2727         {
2728           PangoLayoutLine *line = line_list->data;
2729 
2730           if (get_alignment (layout, line) != PANGO_ALIGN_LEFT)
2731             need_width = TRUE;
2732 
2733           line_list = line_list->next;
2734         }
2735     }
2736   else if (layout->alignment != PANGO_ALIGN_LEFT)
2737     need_width = TRUE;
2738 
2739   if (width == -1 && need_width && (ink_rect || line_extents))
2740     {
2741       PangoRectangle overall_logical;
2742 
2743       pango_layout_get_extents_internal (layout, NULL, &overall_logical, NULL);
2744       width = overall_logical.width;
2745     }
2746 
2747   if (logical_rect)
2748     {
2749       logical_rect->x = 0;
2750       logical_rect->y = 0;
2751       logical_rect->width = 0;
2752       logical_rect->height = 0;
2753     }
2754 
2755 
2756   if (line_extents && layout->line_count > 0)
2757     {
2758       *line_extents = g_malloc (sizeof (Extents) * layout->line_count);
2759     }
2760 
2761   baseline = 0;
2762   line_list = layout->lines;
2763   while (line_list)
2764     {
2765       PangoLayoutLine *line = line_list->data;
2766       /* Line extents in layout coords (origin at 0,0 of the layout) */
2767       PangoRectangle line_ink_layout;
2768       PangoRectangle line_logical_layout;
2769 
2770       int new_pos;
2771 
2772       /* This block gets the line extents in layout coords */
2773       {
2774         get_line_extents_layout_coords (layout, line,
2775                                         width, y_offset,
2776                                         &baseline,
2777                                         ink_rect ? &line_ink_layout : NULL,
2778                                         &line_logical_layout);
2779 
2780         if (line_extents && layout->line_count > 0)
2781           {
2782             Extents *ext = &(*line_extents)[line_index];
2783             ext->baseline = baseline;
2784             ext->ink_rect = line_ink_layout;
2785             ext->logical_rect = line_logical_layout;
2786           }
2787       }
2788 
2789       if (ink_rect)
2790         {
2791           /* Compute the union of the current ink_rect with
2792            * line_ink_layout
2793            */
2794 
2795           if (line_list == layout->lines)
2796             {
2797               *ink_rect = line_ink_layout;
2798             }
2799           else
2800             {
2801               new_pos = MIN (ink_rect->x, line_ink_layout.x);
2802               ink_rect->width =
2803                 MAX (ink_rect->x + ink_rect->width,
2804                      line_ink_layout.x + line_ink_layout.width) - new_pos;
2805               ink_rect->x = new_pos;
2806 
2807               new_pos = MIN (ink_rect->y, line_ink_layout.y);
2808               ink_rect->height =
2809                 MAX (ink_rect->y + ink_rect->height,
2810                      line_ink_layout.y + line_ink_layout.height) - new_pos;
2811               ink_rect->y = new_pos;
2812             }
2813         }
2814 
2815       if (logical_rect)
2816         {
2817           if (layout->width == -1)
2818             {
2819               /* When no width is set on layout, we can just compute the max of the
2820                * line lengths to get the horizontal extents ... logical_rect.x = 0.
2821                */
2822               logical_rect->width = MAX (logical_rect->width, line_logical_layout.width);
2823             }
2824           else
2825             {
2826               /* When a width is set, we have to compute the union of the horizontal
2827                * extents of all the lines.
2828                */
2829               if (line_list == layout->lines)
2830                 {
2831                   logical_rect->x = line_logical_layout.x;
2832                   logical_rect->width = line_logical_layout.width;
2833                 }
2834               else
2835                 {
2836                   new_pos = MIN (logical_rect->x, line_logical_layout.x);
2837                   logical_rect->width =
2838                     MAX (logical_rect->x + logical_rect->width,
2839                          line_logical_layout.x + line_logical_layout.width) - new_pos;
2840                   logical_rect->x = new_pos;
2841 
2842                 }
2843             }
2844 
2845           logical_rect->height = line_logical_layout.y + line_logical_layout.height - logical_rect->y;
2846         }
2847 
2848       y_offset = line_logical_layout.y + line_logical_layout.height + layout->spacing;
2849       line_list = line_list->next;
2850       line_index ++;
2851     }
2852 
2853   if (ink_rect)
2854     {
2855       layout->ink_rect = *ink_rect;
2856       layout->ink_rect_cached = TRUE;
2857     }
2858   if (logical_rect)
2859     {
2860       layout->logical_rect = *logical_rect;
2861       layout->logical_rect_cached = TRUE;
2862     }
2863 }
2864 
2865 /**
2866  * pango_layout_get_extents:
2867  * @layout: a `PangoLayout`
2868  * @ink_rect: (out) (optional): rectangle used to store the extents of the
2869  *   layout as drawn
2870  * @logical_rect: (out) (optional):rectangle used to store the logical
2871  *   extents of the layout
2872  *
2873  * Computes the logical and ink extents of @layout.
2874  *
2875  * Logical extents are usually what you want for positioning things. Note
2876  * that both extents may have non-zero x and y. You may want to use those
2877  * to offset where you render the layout. Not doing that is a very typical
2878  * bug that shows up as right-to-left layouts not being correctly positioned
2879  * in a layout with a set width.
2880  *
2881  * The extents are given in layout coordinates and in Pango units; layout
2882  * coordinates begin at the top left corner of the layout.
2883  */
2884 void
pango_layout_get_extents(PangoLayout * layout,PangoRectangle * ink_rect,PangoRectangle * logical_rect)2885 pango_layout_get_extents (PangoLayout    *layout,
2886                           PangoRectangle *ink_rect,
2887                           PangoRectangle *logical_rect)
2888 {
2889   g_return_if_fail (layout != NULL);
2890 
2891   pango_layout_get_extents_internal (layout, ink_rect, logical_rect, NULL);
2892 }
2893 
2894 /**
2895  * pango_layout_get_pixel_extents:
2896  * @layout: a `PangoLayout`
2897  * @ink_rect: (out) (optional): rectangle used to store the extents of the
2898  *   layout as drawn
2899  * @logical_rect: (out) (optional): rectangle used to store the logical
2900  *   extents of the layout
2901  *
2902  * Computes the logical and ink extents of @layout in device units.
2903  *
2904  * This function just calls [method@Pango.Layout.get_extents] followed by
2905  * two [func@extents_to_pixels] calls, rounding @ink_rect and @logical_rect
2906  * such that the rounded rectangles fully contain the unrounded one (that is,
2907  * passes them as first argument to [func@Pango.extents_to_pixels]).
2908  */
2909 void
pango_layout_get_pixel_extents(PangoLayout * layout,PangoRectangle * ink_rect,PangoRectangle * logical_rect)2910 pango_layout_get_pixel_extents (PangoLayout    *layout,
2911                                 PangoRectangle *ink_rect,
2912                                 PangoRectangle *logical_rect)
2913 {
2914   g_return_if_fail (PANGO_IS_LAYOUT (layout));
2915 
2916   pango_layout_get_extents (layout, ink_rect, logical_rect);
2917   pango_extents_to_pixels (ink_rect, NULL);
2918   pango_extents_to_pixels (logical_rect, NULL);
2919 }
2920 
2921 /**
2922  * pango_layout_get_size:
2923  * @layout: a `PangoLayout`
2924  * @width: (out) (optional): location to store the logical width
2925  * @height: (out) (optional): location to store the logical height
2926  *
2927  * Determines the logical width and height of a `PangoLayout` in Pango
2928  * units.
2929  *
2930  * This is simply a convenience function around [method@Pango.Layout.get_extents].
2931  */
2932 void
pango_layout_get_size(PangoLayout * layout,int * width,int * height)2933 pango_layout_get_size (PangoLayout *layout,
2934                        int         *width,
2935                        int         *height)
2936 {
2937   PangoRectangle logical_rect;
2938 
2939   pango_layout_get_extents (layout, NULL, &logical_rect);
2940 
2941   if (width)
2942     *width = logical_rect.width;
2943   if (height)
2944     *height = logical_rect.height;
2945 }
2946 
2947 /**
2948  * pango_layout_get_pixel_size:
2949  * @layout: a `PangoLayout`
2950  * @width: (out) (optional): location to store the logical width
2951  * @height: (out) (optional): location to store the logical height
2952  *
2953  * Determines the logical width and height of a `PangoLayout` in device
2954  * units.
2955  *
2956  * [method@Pango.Layout.get_size] returns the width and height
2957  * scaled by %PANGO_SCALE. This is simply a convenience function
2958  * around [method@Pango.Layout.get_pixel_extents].
2959  */
2960 void
pango_layout_get_pixel_size(PangoLayout * layout,int * width,int * height)2961 pango_layout_get_pixel_size (PangoLayout *layout,
2962                              int         *width,
2963                              int         *height)
2964 {
2965   PangoRectangle logical_rect;
2966 
2967   pango_layout_get_extents_internal (layout, NULL, &logical_rect, NULL);
2968   pango_extents_to_pixels (&logical_rect, NULL);
2969 
2970   if (width)
2971     *width = logical_rect.width;
2972   if (height)
2973     *height = logical_rect.height;
2974 }
2975 
2976 /**
2977  * pango_layout_get_baseline:
2978  * @layout: a `PangoLayout`
2979  *
2980  * Gets the Y position of baseline of the first line in @layout.
2981  *
2982  * Return value: baseline of first line, from top of @layout
2983  *
2984  * Since: 1.22
2985  */
2986 int
pango_layout_get_baseline(PangoLayout * layout)2987 pango_layout_get_baseline (PangoLayout *layout)
2988 {
2989   int baseline;
2990   Extents *extents = NULL;
2991 
2992   /* XXX this is kinda inefficient */
2993   pango_layout_get_extents_internal (layout, NULL, NULL, &extents);
2994   baseline = extents ? extents[0].baseline : 0;
2995 
2996   g_free (extents);
2997 
2998   return baseline;
2999 }
3000 
3001 static void
pango_layout_clear_lines(PangoLayout * layout)3002 pango_layout_clear_lines (PangoLayout *layout)
3003 {
3004   if (layout->lines)
3005     {
3006       GSList *tmp_list = layout->lines;
3007       while (tmp_list)
3008         {
3009           PangoLayoutLine *line = tmp_list->data;
3010           tmp_list = tmp_list->next;
3011 
3012           line->layout = NULL;
3013           pango_layout_line_unref (line);
3014         }
3015 
3016       g_slist_free (layout->lines);
3017       layout->lines = NULL;
3018       layout->line_count = 0;
3019 
3020       /* This could be handled separately, since we don't need to
3021        * recompute log_attrs on a width change, but this is easiest
3022        */
3023       g_free (layout->log_attrs);
3024       layout->log_attrs = NULL;
3025     }
3026 
3027   layout->unknown_glyphs_count = -1;
3028   layout->logical_rect_cached = FALSE;
3029   layout->ink_rect_cached = FALSE;
3030   layout->is_ellipsized = FALSE;
3031   layout->is_wrapped = FALSE;
3032 }
3033 
3034 static void
pango_layout_line_leaked(PangoLayoutLine * line)3035 pango_layout_line_leaked (PangoLayoutLine *line)
3036 {
3037   PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line;
3038 
3039   private->cache_status = LEAKED;
3040 
3041   if (line->layout)
3042     {
3043       line->layout->logical_rect_cached = FALSE;
3044       line->layout->ink_rect_cached = FALSE;
3045     }
3046 }
3047 
3048 
3049 /*****************
3050  * Line Breaking *
3051  *****************/
3052 
3053 static void shape_tab (PangoLayoutLine  *line,
3054                        PangoItem        *item,
3055                        PangoGlyphString *glyphs);
3056 
3057 static void
free_run(PangoLayoutRun * run,gpointer data)3058 free_run (PangoLayoutRun *run, gpointer data)
3059 {
3060   gboolean free_item = data != NULL;
3061   if (free_item)
3062     pango_item_free (run->item);
3063 
3064   pango_glyph_string_free (run->glyphs);
3065   g_slice_free (PangoLayoutRun, run);
3066 }
3067 
3068 static PangoItem *
uninsert_run(PangoLayoutLine * line)3069 uninsert_run (PangoLayoutLine *line)
3070 {
3071   PangoLayoutRun *run;
3072   PangoItem *item;
3073 
3074   GSList *tmp_node = line->runs;
3075 
3076   run = tmp_node->data;
3077   item = run->item;
3078 
3079   line->runs = tmp_node->next;
3080   line->length -= item->length;
3081 
3082   g_slist_free_1 (tmp_node);
3083   free_run (run, (gpointer)FALSE);
3084 
3085   return item;
3086 }
3087 
3088 static void
ensure_tab_width(PangoLayout * layout)3089 ensure_tab_width (PangoLayout *layout)
3090 {
3091   if (layout->tab_width == -1)
3092     {
3093       /* Find out how wide 8 spaces are in the context's default
3094        * font. Utter performance killer. :-(
3095        */
3096       PangoGlyphString *glyphs = pango_glyph_string_new ();
3097       PangoItem *item;
3098       GList *items;
3099       PangoAttribute *attr;
3100       PangoAttrList *layout_attrs;
3101       PangoAttrList tmp_attrs;
3102       PangoFontDescription *font_desc = pango_font_description_copy_static (pango_context_get_font_description (layout->context));
3103       PangoLanguage *language = NULL;
3104       PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
3105 
3106       if (pango_context_get_round_glyph_positions (layout->context))
3107         shape_flags |= PANGO_SHAPE_ROUND_POSITIONS;
3108 
3109       layout_attrs = pango_layout_get_effective_attributes (layout);
3110       if (layout_attrs)
3111         {
3112           PangoAttrIterator iter;
3113 
3114           _pango_attr_list_get_iterator (layout_attrs, &iter);
3115           pango_attr_iterator_get_font (&iter, font_desc, &language, NULL);
3116           _pango_attr_iterator_destroy (&iter);
3117         }
3118 
3119       _pango_attr_list_init (&tmp_attrs);
3120 
3121       attr = pango_attr_font_desc_new (font_desc);
3122       pango_font_description_free (font_desc);
3123       pango_attr_list_insert_before (&tmp_attrs, attr);
3124 
3125       if (language)
3126         {
3127           attr = pango_attr_language_new (language);
3128           pango_attr_list_insert_before (&tmp_attrs, attr);
3129         }
3130 
3131       items = pango_itemize (layout->context, " ", 0, 1, &tmp_attrs, NULL);
3132 
3133       if (layout_attrs != layout->attrs)
3134         {
3135           pango_attr_list_unref (layout_attrs);
3136           layout_attrs = NULL;
3137         }
3138       _pango_attr_list_destroy (&tmp_attrs);
3139 
3140       item = items->data;
3141       pango_shape_with_flags ("        ", 8, "        ", 8, &item->analysis, glyphs, shape_flags);
3142 
3143       pango_item_free (item);
3144       g_list_free (items);
3145 
3146       layout->tab_width = pango_glyph_string_get_width (glyphs);
3147 
3148       pango_glyph_string_free (glyphs);
3149 
3150       /* We need to make sure the tab_width is > 0 so finding tab positions
3151        * terminates. This check should be necessary only under extreme
3152        * problems with the font.
3153        */
3154       if (layout->tab_width <= 0)
3155         layout->tab_width = 50 * PANGO_SCALE; /* pretty much arbitrary */
3156     }
3157 }
3158 
3159 /* For now we only need the tab position, we assume
3160  * all tabs are left-aligned.
3161  */
3162 static int
get_tab_pos(PangoLayout * layout,int index,gboolean * is_default)3163 get_tab_pos (PangoLayout *layout,
3164              int          index,
3165              gboolean    *is_default)
3166 {
3167   gint n_tabs;
3168   gboolean in_pixels;
3169 
3170   if (layout->tabs)
3171     {
3172       n_tabs = pango_tab_array_get_size (layout->tabs);
3173       in_pixels = pango_tab_array_get_positions_in_pixels (layout->tabs);
3174       if (is_default)
3175         *is_default = FALSE;
3176     }
3177   else
3178     {
3179       n_tabs = 0;
3180       in_pixels = FALSE;
3181       if (is_default)
3182         *is_default = TRUE;
3183     }
3184 
3185   if (index < n_tabs)
3186     {
3187       gint pos = 0;
3188 
3189       pango_tab_array_get_tab (layout->tabs, index, NULL, &pos);
3190 
3191       if (in_pixels)
3192         return pos * PANGO_SCALE;
3193       else
3194         return pos;
3195     }
3196 
3197   if (n_tabs > 0)
3198     {
3199       /* Extrapolate tab position, repeating the last tab gap to
3200        * infinity.
3201        */
3202       int last_pos = 0;
3203       int next_to_last_pos = 0;
3204       int tab_width;
3205 
3206       pango_tab_array_get_tab (layout->tabs, n_tabs - 1, NULL, &last_pos);
3207 
3208       if (n_tabs > 1)
3209         pango_tab_array_get_tab (layout->tabs, n_tabs - 2, NULL, &next_to_last_pos);
3210       else
3211         next_to_last_pos = 0;
3212 
3213       if (in_pixels)
3214         {
3215           next_to_last_pos *= PANGO_SCALE;
3216           last_pos *= PANGO_SCALE;
3217         }
3218 
3219       if (last_pos > next_to_last_pos)
3220         {
3221           tab_width = last_pos - next_to_last_pos;
3222         }
3223       else
3224         {
3225           tab_width = layout->tab_width;
3226         }
3227 
3228       return last_pos + tab_width * (index - n_tabs + 1);
3229     }
3230   else
3231     {
3232       /* No tab array set, so use default tab width
3233        */
3234       return layout->tab_width * index;
3235     }
3236 }
3237 
3238 static int
line_width(PangoLayoutLine * line)3239 line_width (PangoLayoutLine *line)
3240 {
3241   GSList *l;
3242   int i;
3243   int width = 0;
3244 
3245   /* Compute the width of the line currently - inefficient, but easier
3246    * than keeping the current width of the line up to date everywhere
3247    */
3248   for (l = line->runs; l; l = l->next)
3249     {
3250       PangoLayoutRun *run = l->data;
3251 
3252       for (i=0; i < run->glyphs->num_glyphs; i++)
3253         width += run->glyphs->glyphs[i].geometry.width;
3254     }
3255 
3256   return width;
3257 }
3258 
3259 static gboolean
showing_space(const PangoAnalysis * analysis)3260 showing_space (const PangoAnalysis *analysis)
3261 {
3262   GSList *l;
3263 
3264   for (l = analysis->extra_attrs; l; l = l->next)
3265     {
3266       PangoAttribute *attr = l->data;
3267 
3268       if (attr->klass->type == PANGO_ATTR_SHOW &&
3269           (((PangoAttrInt*)attr)->value & PANGO_SHOW_SPACES) != 0)
3270         return TRUE;
3271     }
3272 
3273   return FALSE;
3274 }
3275 
3276 static void
shape_tab(PangoLayoutLine * line,PangoItem * item,PangoGlyphString * glyphs)3277 shape_tab (PangoLayoutLine  *line,
3278            PangoItem        *item,
3279            PangoGlyphString *glyphs)
3280 {
3281   int i, space_width;
3282 
3283   int current_width = line_width (line);
3284 
3285   pango_glyph_string_set_size (glyphs, 1);
3286 
3287   if (showing_space (&item->analysis))
3288     glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH ('\t');
3289   else
3290     glyphs->glyphs[0].glyph = PANGO_GLYPH_EMPTY;
3291   glyphs->glyphs[0].geometry.x_offset = 0;
3292   glyphs->glyphs[0].geometry.y_offset = 0;
3293   glyphs->glyphs[0].attr.is_cluster_start = 1;
3294 
3295   glyphs->log_clusters[0] = 0;
3296 
3297   ensure_tab_width (line->layout);
3298   space_width = line->layout->tab_width / 8;
3299 
3300   for (i=0;;i++)
3301     {
3302       gboolean is_default;
3303       int tab_pos = get_tab_pos (line->layout, i, &is_default);
3304       /* Make sure there is at least a space-width of space between
3305        * tab-aligned text and the text before it.  However, only do
3306        * this if no tab array is set on the layout, ie. using default
3307        * tab positions.  If use has set tab positions, respect it to
3308        * the pixel.
3309        */
3310       if (tab_pos >= current_width + (is_default ? space_width : 1))
3311         {
3312           glyphs->glyphs[0].geometry.width = tab_pos - current_width;
3313           break;
3314         }
3315     }
3316 }
3317 
3318 static inline gboolean
can_break_at(PangoLayout * layout,gint offset,gboolean always_wrap_char)3319 can_break_at (PangoLayout *layout,
3320               gint         offset,
3321               gboolean     always_wrap_char)
3322 {
3323   PangoWrapMode wrap;
3324   /* We probably should have a mode where we treat all white-space as
3325    * of fungible width - appropriate for typography but not for
3326    * editing.
3327    */
3328   wrap = layout->wrap;
3329 
3330   if (wrap == PANGO_WRAP_WORD_CHAR)
3331     wrap = always_wrap_char ? PANGO_WRAP_CHAR : PANGO_WRAP_WORD;
3332 
3333   if (offset == layout->n_chars)
3334     return TRUE;
3335   else if (wrap == PANGO_WRAP_WORD)
3336     return layout->log_attrs[offset].is_line_break;
3337   else if (wrap == PANGO_WRAP_CHAR)
3338     return layout->log_attrs[offset].is_char_break;
3339   else
3340     {
3341       g_warning (G_STRLOC": broken PangoLayout");
3342       return TRUE;
3343     }
3344 }
3345 
3346 static inline gboolean
can_break_in(PangoLayout * layout,int start_offset,int num_chars,gboolean allow_break_at_start)3347 can_break_in (PangoLayout *layout,
3348               int          start_offset,
3349               int          num_chars,
3350               gboolean     allow_break_at_start)
3351 {
3352   int i;
3353 
3354   for (i = allow_break_at_start ? 0 : 1; i < num_chars; i++)
3355     if (can_break_at (layout, start_offset + i, FALSE))
3356       return TRUE;
3357 
3358   return FALSE;
3359 }
3360 
3361 static inline void
distribute_letter_spacing(int letter_spacing,int * space_left,int * space_right)3362 distribute_letter_spacing (int  letter_spacing,
3363                            int *space_left,
3364                            int *space_right)
3365 {
3366   *space_left = letter_spacing / 2;
3367   /* hinting */
3368   if ((letter_spacing & (PANGO_SCALE - 1)) == 0)
3369     {
3370       *space_left = PANGO_UNITS_ROUND (*space_left);
3371     }
3372   *space_right = letter_spacing - *space_left;
3373 }
3374 
3375 typedef enum
3376 {
3377   BREAK_NONE_FIT,
3378   BREAK_SOME_FIT,
3379   BREAK_ALL_FIT,
3380   BREAK_EMPTY_FIT,
3381   BREAK_LINE_SEPARATOR
3382 } BreakResult;
3383 
3384 struct _ParaBreakState
3385 {
3386   /* maintained per layout */
3387   int line_height;              /* Estimate of height of current line; < 0 is no estimate */
3388   int remaining_height;         /* Remaining height of the layout;  only defined if layout->height >= 0 */
3389 
3390   /* maintained per paragraph */
3391   PangoAttrList *attrs;         /* Attributes being used for itemization */
3392   GList *items;                 /* This paragraph turned into items */
3393   PangoDirection base_dir;      /* Current resolved base direction */
3394   int line_of_par;              /* Line of the paragraph, starting at 1 for first line */
3395 
3396   PangoGlyphString *glyphs;     /* Glyphs for the first item in state->items */
3397   int start_offset;             /* Character offset of first item in state->items in layout->text */
3398   ItemProperties properties;    /* Properties for the first item in state->items */
3399   int *log_widths;              /* Logical widths for first item in state->items.. */
3400   int log_widths_offset;        /* Offset into log_widths to the point corresponding
3401                                  * to the remaining portion of the first item */
3402 
3403   int *need_hyphen;             /* Insert a hyphen if breaking here ? */
3404   int line_start_index;         /* Start index (byte offset) of line in layout->text */
3405   int line_start_offset;        /* Character offset of line in layout->text */
3406 
3407   /* maintained per line */
3408   int line_width;               /* Goal width of line currently processing; < 0 is infinite */
3409   int remaining_width;          /* Amount of space remaining on line; < 0 is infinite */
3410 
3411   int hyphen_width;             /* How much space a hyphen will take */
3412 };
3413 
3414 static gboolean
3415 should_ellipsize_current_line (PangoLayout    *layout,
3416                                ParaBreakState *state);
3417 
3418 static PangoGlyphString *
shape_run(PangoLayoutLine * line,ParaBreakState * state,PangoItem * item)3419 shape_run (PangoLayoutLine *line,
3420            ParaBreakState  *state,
3421            PangoItem       *item)
3422 {
3423   PangoLayout *layout = line->layout;
3424   PangoGlyphString *glyphs = pango_glyph_string_new ();
3425 
3426   if (layout->text[item->offset] == '\t')
3427     shape_tab (line, item, glyphs);
3428   else
3429     {
3430       PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
3431 
3432       if (pango_context_get_round_glyph_positions (layout->context))
3433         shape_flags |= PANGO_SHAPE_ROUND_POSITIONS;
3434 
3435       if (state->properties.shape_set)
3436         _pango_shape_shape (layout->text + item->offset, item->num_chars,
3437                             state->properties.shape_ink_rect, state->properties.shape_logical_rect,
3438                             glyphs);
3439       else
3440         pango_shape_with_flags (layout->text + item->offset, item->length,
3441                                 layout->text, layout->length,
3442                                 &item->analysis, glyphs,
3443                                 shape_flags);
3444 
3445       if (state->properties.letter_spacing)
3446         {
3447           PangoGlyphItem glyph_item;
3448           int space_left, space_right;
3449 
3450           glyph_item.item = item;
3451           glyph_item.glyphs = glyphs;
3452 
3453           pango_glyph_item_letter_space (&glyph_item,
3454                                          layout->text,
3455                                          layout->log_attrs + state->start_offset,
3456                                          state->properties.letter_spacing);
3457 
3458           distribute_letter_spacing (state->properties.letter_spacing, &space_left, &space_right);
3459 
3460           glyphs->glyphs[0].geometry.width += space_left;
3461           glyphs->glyphs[0].geometry.x_offset += space_left;
3462           glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += space_right;
3463         }
3464     }
3465 
3466   return glyphs;
3467 }
3468 
3469 static void
insert_run(PangoLayoutLine * line,ParaBreakState * state,PangoItem * run_item,gboolean last_run)3470 insert_run (PangoLayoutLine *line,
3471             ParaBreakState  *state,
3472             PangoItem       *run_item,
3473             gboolean         last_run)
3474 {
3475   PangoLayoutRun *run = g_slice_new (PangoLayoutRun);
3476 
3477   run->item = run_item;
3478 
3479   if (last_run && state->log_widths_offset == 0)
3480     run->glyphs = state->glyphs;
3481   else
3482     run->glyphs = shape_run (line, state, run_item);
3483 
3484   if (last_run)
3485     {
3486       if (state->log_widths_offset > 0)
3487         pango_glyph_string_free (state->glyphs);
3488       state->glyphs = NULL;
3489       g_free (state->log_widths);
3490       state->log_widths = NULL;
3491       g_free (state->need_hyphen);
3492       state->need_hyphen = NULL;
3493     }
3494 
3495   line->runs = g_slist_prepend (line->runs, run);
3496   line->length += run_item->length;
3497 }
3498 
3499 static void
get_need_hyphen(PangoItem * item,const char * text,int * need_hyphen)3500 get_need_hyphen (PangoItem  *item,
3501                  const char *text,
3502                  int        *need_hyphen)
3503 {
3504   int i;
3505   const char *p;
3506   gboolean prev_space;
3507   gboolean prev_hyphen;
3508   PangoAttrList attrs;
3509   PangoAttrIterator iter;
3510   GSList *l;
3511 
3512   _pango_attr_list_init (&attrs);
3513   for (l = item->analysis.extra_attrs; l; l = l->next)
3514     {
3515       PangoAttribute *attr = l->data;
3516       if (attr->klass->type == PANGO_ATTR_INSERT_HYPHENS)
3517         pango_attr_list_change (&attrs, pango_attribute_copy (attr));
3518     }
3519   _pango_attr_list_get_iterator (&attrs, &iter);
3520 
3521   prev_space = prev_hyphen = TRUE;
3522 
3523   for (i = 0, p = text + item->offset; i < item->num_chars; i++, p = g_utf8_next_char (p))
3524     {
3525       gunichar wc = g_utf8_get_char (p);
3526       gboolean space;
3527       gboolean hyphen;
3528       int start, end, pos;
3529       gboolean insert_hyphens = TRUE;
3530 
3531       pos = p - text;
3532       do {
3533         pango_attr_iterator_range (&iter, &start, &end);
3534         if (end > pos)
3535           break;
3536       } while (pango_attr_iterator_next (&iter));
3537 
3538       if (start <= pos && pos < end)
3539         {
3540           PangoAttribute *attr;
3541           attr = pango_attr_iterator_get (&iter, PANGO_ATTR_INSERT_HYPHENS);
3542           if (attr)
3543             insert_hyphens = ((PangoAttrInt*)attr)->value;
3544 
3545           /* Some scripts don't use hyphen.*/
3546           switch (item->analysis.script)
3547             {
3548             case PANGO_SCRIPT_COMMON:
3549             case PANGO_SCRIPT_HAN:
3550             case PANGO_SCRIPT_HANGUL:
3551             case PANGO_SCRIPT_HIRAGANA:
3552             case PANGO_SCRIPT_KATAKANA:
3553               insert_hyphens = FALSE;
3554               break;
3555             default:
3556               break;
3557             }
3558         }
3559 
3560       switch (g_unichar_type (wc))
3561         {
3562         case G_UNICODE_SPACE_SEPARATOR:
3563         case G_UNICODE_LINE_SEPARATOR:
3564         case G_UNICODE_PARAGRAPH_SEPARATOR:
3565           space = TRUE;
3566           break;
3567         case G_UNICODE_CONTROL:
3568           if (wc == '\t' || wc == '\n' || wc == '\r' || wc == '\f')
3569             space = TRUE;
3570           else
3571             space = FALSE;
3572           break;
3573         default:
3574           space = FALSE;
3575           break;
3576         }
3577 
3578       if (wc == '-'    || /* Hyphen-minus */
3579           wc == 0x058a || /* Armenian hyphen */
3580           wc == 0x1400 || /* Canadian syllabics hyphen */
3581           wc == 0x1806 || /* Mongolian todo hyphen */
3582           wc == 0x2010 || /* Hyphen */
3583           wc == 0x2027 || /* Hyphenation point */
3584           wc == 0x2e17 || /* Double oblique hyphen */
3585           wc == 0x2e40 || /* Double hyphen */
3586           wc == 0x30a0 || /* Katakana-Hiragana double hyphen */
3587           wc == 0xfe63 || /* Small hyphen-minus */
3588           wc == 0xff0d)   /* Fullwidth hyphen-minus */
3589         hyphen = TRUE;
3590       else
3591         hyphen = FALSE;
3592 
3593       if (prev_space || space)
3594         need_hyphen[i] = FALSE;
3595       else if (prev_hyphen || hyphen)
3596         need_hyphen[i] = FALSE;
3597       else
3598         need_hyphen[i] = insert_hyphens;
3599 
3600       prev_space = space;
3601       prev_hyphen = hyphen;
3602     }
3603 
3604   need_hyphen[item->num_chars - 1] = FALSE;
3605 
3606   _pango_attr_iterator_destroy (&iter);
3607   _pango_attr_list_destroy (&attrs);
3608 }
3609 
3610 static gboolean
break_needs_hyphen(PangoLayout * layout,ParaBreakState * state,int pos)3611 break_needs_hyphen (PangoLayout    *layout,
3612                     ParaBreakState *state,
3613                     int             pos)
3614 {
3615   if (state->log_widths_offset + pos == 0)
3616     return FALSE;
3617 
3618   if (layout->log_attrs[state->start_offset + pos].is_word_boundary)
3619     return FALSE;
3620 
3621   if (state->need_hyphen[state->log_widths_offset + pos - 1])
3622     return TRUE;
3623 
3624   return FALSE;
3625 }
3626 
3627 static int
find_hyphen_width(PangoItem * item)3628 find_hyphen_width (PangoItem *item)
3629 {
3630   hb_font_t *hb_font;
3631   hb_codepoint_t glyph;
3632 
3633   if (!item->analysis.font)
3634     return 0;
3635 
3636   /* This is not technically correct, since
3637    * a) we may end up inserting a different hyphen
3638    * b) we should reshape the entire run
3639    * But it is close enough in practice
3640    */
3641   hb_font = pango_font_get_hb_font (item->analysis.font);
3642   if (hb_font_get_nominal_glyph (hb_font, 0x2010, &glyph) ||
3643       hb_font_get_nominal_glyph (hb_font, '-', &glyph))
3644     return hb_font_get_glyph_h_advance (hb_font, glyph);
3645 
3646   return 0;
3647 }
3648 
3649 static int
find_break_extra_width(PangoLayout * layout,ParaBreakState * state,int pos)3650 find_break_extra_width (PangoLayout    *layout,
3651                         ParaBreakState *state,
3652                         int             pos)
3653 {
3654   /* Check whether to insert a hyphen */
3655   if (break_needs_hyphen (layout, state, pos))
3656     {
3657       if (state->hyphen_width < 0)
3658         {
3659           PangoItem *item = state->items->data;
3660           state->hyphen_width = find_hyphen_width (item);
3661         }
3662 
3663       return state->hyphen_width;
3664     }
3665   else
3666     return 0;
3667 }
3668 
3669 #if 0
3670 # define DEBUG debug
3671 void
3672 debug (const char *where, PangoLayoutLine *line, ParaBreakState *state)
3673 {
3674   int line_width = pango_layout_line_get_width (line);
3675 
3676   g_debug ("rem %d + line %d = %d               %s",
3677            state->remaining_width,
3678            line_width,
3679            state->remaining_width + line_width,
3680            where);
3681 }
3682 #else
3683 # define DEBUG(where, line, state) do { } while (0)
3684 #endif
3685 
3686 /* Tries to insert as much as possible of the item at the head of
3687  * state->items onto @line. Five results are possible:
3688  *
3689  *  %BREAK_NONE_FIT: Couldn't fit anything.
3690  *  %BREAK_SOME_FIT: The item was broken in the middle.
3691  *  %BREAK_ALL_FIT: Everything fit.
3692  *  %BREAK_EMPTY_FIT: Nothing fit, but that was ok, as we can break at the first char.
3693  *  %BREAK_LINE_SEPARATOR: Item begins with a line separator.
3694  *
3695  * If @force_fit is %TRUE, then %BREAK_NONE_FIT will never
3696  * be returned, a run will be added even if inserting the minimum amount
3697  * will cause the line to overflow. This is used at the start of a line
3698  * and until we've found at least some place to break.
3699  *
3700  * If @no_break_at_end is %TRUE, then %BREAK_ALL_FIT will never be
3701  * returned even everything fits; the run will be broken earlier,
3702  * or %BREAK_NONE_FIT returned. This is used when the end of the
3703  * run is not a break position.
3704  */
3705 static BreakResult
process_item(PangoLayout * layout,PangoLayoutLine * line,ParaBreakState * state,gboolean force_fit,gboolean no_break_at_end)3706 process_item (PangoLayout     *layout,
3707               PangoLayoutLine *line,
3708               ParaBreakState  *state,
3709               gboolean         force_fit,
3710               gboolean         no_break_at_end)
3711 {
3712   PangoItem *item = state->items->data;
3713   gboolean shape_set = FALSE;
3714   int width;
3715   int extra_width;
3716   int length;
3717   int i;
3718   gboolean processing_new_item = FALSE;
3719 
3720   /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 5.0;
3721    * update this if that changes. */
3722 #define LINE_SEPARATOR 0x2028
3723 
3724   if (!state->glyphs)
3725     {
3726       pango_layout_get_item_properties (item, &state->properties);
3727       state->glyphs = shape_run (line, state, item);
3728 
3729       state->log_widths = NULL;
3730       state->need_hyphen = NULL;
3731       state->log_widths_offset = 0;
3732 
3733       processing_new_item = TRUE;
3734     }
3735 
3736   if (!layout->single_paragraph &&
3737       g_utf8_get_char (layout->text + item->offset) == LINE_SEPARATOR &&
3738       !should_ellipsize_current_line (layout, state))
3739     {
3740       insert_run (line, state, item, TRUE);
3741       state->log_widths_offset += item->num_chars;
3742 
3743       return BREAK_LINE_SEPARATOR;
3744     }
3745 
3746   if (state->remaining_width < 0 && !no_break_at_end)  /* Wrapping off */
3747     {
3748       insert_run (line, state, item, TRUE);
3749 
3750       return BREAK_ALL_FIT;
3751     }
3752 
3753   width = 0;
3754   if (processing_new_item)
3755     {
3756       width = pango_glyph_string_get_width (state->glyphs);
3757     }
3758   else
3759     {
3760       for (i = 0; i < item->num_chars; i++)
3761         width += state->log_widths[state->log_widths_offset + i];
3762     }
3763 
3764   if ((width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) &&
3765       !no_break_at_end)
3766     {
3767       state->remaining_width -= width;
3768       state->remaining_width = MAX (state->remaining_width, 0);
3769       insert_run (line, state, item, TRUE);
3770 
3771       return BREAK_ALL_FIT;
3772     }
3773   else
3774     {
3775       int num_chars = item->num_chars;
3776       int break_num_chars = num_chars;
3777       int break_width = width;
3778       int orig_width = width;
3779       int break_extra_width = 0;
3780       gboolean retrying_with_char_breaks = FALSE;
3781 
3782       if (processing_new_item)
3783         {
3784           PangoGlyphItem glyph_item = {item, state->glyphs};
3785           state->log_widths = g_new (int, item->num_chars);
3786           pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
3787           state->need_hyphen = g_new (int, item->num_chars);
3788           get_need_hyphen (item, layout->text, state->need_hyphen);
3789         }
3790 
3791     retry_break:
3792 
3793       /* See how much of the item we can stuff in the line. */
3794       width = 0;
3795       extra_width = 0;
3796       for (num_chars = 0; num_chars < item->num_chars; num_chars++)
3797         {
3798           if (width + extra_width > state->remaining_width && break_num_chars < item->num_chars)
3799             {
3800               break;
3801             }
3802 
3803           /* If there are no previous runs we have to take care to grab at least one char. */
3804           if (can_break_at (layout, state->start_offset + num_chars, retrying_with_char_breaks) &&
3805               (num_chars > 0 || line->runs))
3806             {
3807               break_num_chars = num_chars;
3808               break_width = width;
3809               break_extra_width = extra_width;
3810 
3811               extra_width = find_break_extra_width (layout, state, num_chars);
3812             }
3813           else
3814             extra_width = 0;
3815 
3816           width += state->log_widths[state->log_widths_offset + num_chars];
3817         }
3818 
3819       /* If there's a space at the end of the line, include that also.
3820        * The logic here should match zero_line_final_space().
3821        * XXX Currently it doesn't quite match the logic there.  We don't check
3822        * the cluster here.  But should be fine in practice. */
3823       if (break_num_chars > 0 && break_num_chars < item->num_chars &&
3824           layout->log_attrs[state->start_offset + break_num_chars - 1].is_white)
3825       {
3826           break_width -= state->log_widths[state->log_widths_offset + break_num_chars - 1];
3827       }
3828 
3829       if (layout->wrap == PANGO_WRAP_WORD_CHAR && force_fit && break_width + break_extra_width > state->remaining_width && !retrying_with_char_breaks)
3830         {
3831           retrying_with_char_breaks = TRUE;
3832           num_chars = item->num_chars;
3833           width = orig_width;
3834           break_num_chars = num_chars;
3835           break_width = width;
3836           goto retry_break;
3837         }
3838 
3839       if (force_fit || break_width + break_extra_width <= state->remaining_width)       /* Successfully broke the item */
3840         {
3841           if (state->remaining_width >= 0)
3842             {
3843               state->remaining_width -= break_width;
3844               state->remaining_width = MAX (state->remaining_width, 0);
3845             }
3846 
3847           if (break_num_chars == item->num_chars)
3848             {
3849               if (break_needs_hyphen (layout, state, break_num_chars))
3850                 item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
3851               insert_run (line, state, item, TRUE);
3852 
3853               return BREAK_ALL_FIT;
3854             }
3855           else if (break_num_chars == 0)
3856             {
3857               return BREAK_EMPTY_FIT;
3858             }
3859           else
3860             {
3861               PangoItem *new_item;
3862 
3863               length = g_utf8_offset_to_pointer (layout->text + item->offset, break_num_chars) - (layout->text + item->offset);
3864 
3865               new_item = pango_item_split (item, length, break_num_chars);
3866 
3867               if (break_needs_hyphen (layout, state, break_num_chars))
3868                 new_item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
3869               /* Add the width back, to the line, reshape, subtract the new width */
3870               state->remaining_width += break_width;
3871               insert_run (line, state, new_item, FALSE);
3872               break_width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
3873               state->remaining_width -= break_width;
3874 
3875               state->log_widths_offset += break_num_chars;
3876 
3877               /* Shaped items should never be broken */
3878               g_assert (!shape_set);
3879 
3880               return BREAK_SOME_FIT;
3881             }
3882         }
3883       else
3884         {
3885           pango_glyph_string_free (state->glyphs);
3886           state->glyphs = NULL;
3887           g_free (state->log_widths);
3888           state->log_widths = NULL;
3889           g_free (state->need_hyphen);
3890           state->need_hyphen = NULL;
3891 
3892           return BREAK_NONE_FIT;
3893         }
3894     }
3895 }
3896 
3897 /* The resolved direction for the line is always one
3898  * of LTR/RTL; not a week or neutral directions
3899  */
3900 static void
line_set_resolved_dir(PangoLayoutLine * line,PangoDirection direction)3901 line_set_resolved_dir (PangoLayoutLine *line,
3902                        PangoDirection   direction)
3903 {
3904   switch (direction)
3905     {
3906     default:
3907     case PANGO_DIRECTION_LTR:
3908     case PANGO_DIRECTION_TTB_RTL:
3909     case PANGO_DIRECTION_WEAK_LTR:
3910     case PANGO_DIRECTION_NEUTRAL:
3911       line->resolved_dir = PANGO_DIRECTION_LTR;
3912       break;
3913     case PANGO_DIRECTION_RTL:
3914     case PANGO_DIRECTION_WEAK_RTL:
3915     case PANGO_DIRECTION_TTB_LTR:
3916       line->resolved_dir = PANGO_DIRECTION_RTL;
3917       break;
3918     }
3919 
3920   /* The direction vs. gravity dance:
3921    *    - If gravity is SOUTH, leave direction untouched.
3922    *    - If gravity is NORTH, switch direction.
3923    *    - If gravity is EAST, set to LTR, as
3924    *      it's a clockwise-rotated layout, so the rotated
3925    *      top is unrotated left.
3926    *    - If gravity is WEST, set to RTL, as
3927    *      it's a counter-clockwise-rotated layout, so the rotated
3928    *      top is unrotated right.
3929    *
3930    * A similar dance is performed in pango-context.c:
3931    * itemize_state_add_character().  Keep in synch.
3932    */
3933   switch (pango_context_get_gravity (line->layout->context))
3934     {
3935     default:
3936     case PANGO_GRAVITY_AUTO:
3937     case PANGO_GRAVITY_SOUTH:
3938       break;
3939     case PANGO_GRAVITY_NORTH:
3940       line->resolved_dir = PANGO_DIRECTION_LTR
3941                          + PANGO_DIRECTION_RTL
3942                          - line->resolved_dir;
3943       break;
3944     case PANGO_GRAVITY_EAST:
3945       /* This is in fact why deprecated TTB_RTL is LTR */
3946       line->resolved_dir = PANGO_DIRECTION_LTR;
3947       break;
3948     case PANGO_GRAVITY_WEST:
3949       /* This is in fact why deprecated TTB_LTR is RTL */
3950       line->resolved_dir = PANGO_DIRECTION_RTL;
3951       break;
3952     }
3953 }
3954 
3955 static gboolean
should_ellipsize_current_line(PangoLayout * layout,ParaBreakState * state)3956 should_ellipsize_current_line (PangoLayout    *layout,
3957                                ParaBreakState *state)
3958 {
3959   if (G_LIKELY (layout->ellipsize == PANGO_ELLIPSIZE_NONE || layout->width < 0))
3960     return FALSE;
3961 
3962   if (layout->height >= 0)
3963     {
3964       /* state->remaining_height is height of layout left */
3965 
3966       /* if we can't stuff two more lines at the current guess of line height,
3967        * the line we are going to produce is going to be the last line
3968        */
3969       return state->line_height * 2 > state->remaining_height;
3970     }
3971   else
3972     {
3973       /* -layout->height is number of lines per paragraph to show */
3974       return state->line_of_par == - layout->height;
3975     }
3976 }
3977 
3978 static void
add_line(PangoLayoutLine * line,ParaBreakState * state)3979 add_line (PangoLayoutLine *line,
3980           ParaBreakState  *state)
3981 {
3982   PangoLayout *layout = line->layout;
3983 
3984   /* we prepend, then reverse the list later */
3985   layout->lines = g_slist_prepend (layout->lines, line);
3986   layout->line_count++;
3987 
3988   if (layout->height >= 0)
3989     {
3990       PangoRectangle logical_rect;
3991       pango_layout_line_get_extents (line, NULL, &logical_rect);
3992       state->remaining_height -= logical_rect.height;
3993       state->remaining_height -= layout->spacing;
3994       state->line_height = logical_rect.height;
3995     }
3996 }
3997 
3998 static void
process_line(PangoLayout * layout,ParaBreakState * state)3999 process_line (PangoLayout    *layout,
4000               ParaBreakState *state)
4001 {
4002   PangoLayoutLine *line;
4003 
4004   gboolean have_break = FALSE;      /* If we've seen a possible break yet */
4005   int break_remaining_width = 0;    /* Remaining width before adding run with break */
4006   int break_start_offset = 0;       /* Start offset before adding run with break */
4007   GSList *break_link = NULL;        /* Link holding run before break */
4008   gboolean wrapped = FALSE;         /* If we had to wrap the line */
4009 
4010   line = pango_layout_line_new (layout);
4011   line->start_index = state->line_start_index;
4012   line->is_paragraph_start = state->line_of_par == 1;
4013   line_set_resolved_dir (line, state->base_dir);
4014 
4015   state->line_width = layout->width;
4016   if (state->line_width >= 0 && layout->alignment != PANGO_ALIGN_CENTER)
4017     {
4018       if (line->is_paragraph_start && layout->indent >= 0)
4019         state->line_width -= layout->indent;
4020       else if (!line->is_paragraph_start && layout->indent < 0)
4021         state->line_width += layout->indent;
4022 
4023       if (state->line_width < 0)
4024         state->line_width = 0;
4025     }
4026 
4027   if (G_UNLIKELY (should_ellipsize_current_line (layout, state)))
4028     state->remaining_width = -1;
4029   else
4030     state->remaining_width = state->line_width;
4031   DEBUG ("starting to fill line", line, state);
4032 
4033   while (state->items)
4034     {
4035       PangoItem *item = state->items->data;
4036       BreakResult result;
4037       int old_num_chars;
4038       int old_remaining_width;
4039       gboolean first_item_in_line;
4040 
4041       old_num_chars = item->num_chars;
4042       old_remaining_width = state->remaining_width;
4043       first_item_in_line = line->runs != NULL;
4044 
4045       result = process_item (layout, line, state, !have_break, FALSE);
4046 
4047       switch (result)
4048         {
4049         case BREAK_ALL_FIT:
4050           if (can_break_in (layout, state->start_offset, old_num_chars, first_item_in_line))
4051             {
4052               have_break = TRUE;
4053               break_remaining_width = old_remaining_width;
4054               break_start_offset = state->start_offset;
4055               break_link = line->runs->next;
4056             }
4057 
4058           state->items = g_list_delete_link (state->items, state->items);
4059           state->start_offset += old_num_chars;
4060 
4061           break;
4062 
4063         case BREAK_EMPTY_FIT:
4064           wrapped = TRUE;
4065           goto done;
4066 
4067         case BREAK_SOME_FIT:
4068           state->start_offset += old_num_chars - item->num_chars;
4069           wrapped = TRUE;
4070           goto done;
4071 
4072         case BREAK_NONE_FIT:
4073           /* Back up over unused runs to run where there is a break */
4074           while (line->runs && line->runs != break_link)
4075             state->items = g_list_prepend (state->items, uninsert_run (line));
4076 
4077           state->start_offset = break_start_offset;
4078           state->remaining_width = break_remaining_width;
4079 
4080           /* Reshape run to break */
4081           item = state->items->data;
4082 
4083           old_num_chars = item->num_chars;
4084           result = process_item (layout, line, state, TRUE, TRUE);
4085           g_assert (result == BREAK_SOME_FIT || result == BREAK_EMPTY_FIT);
4086 
4087           state->start_offset += old_num_chars - item->num_chars;
4088 
4089           wrapped = TRUE;
4090           goto done;
4091 
4092         case BREAK_LINE_SEPARATOR:
4093           state->items = g_list_delete_link (state->items, state->items);
4094           state->start_offset += old_num_chars;
4095           /* A line-separate is just a forced break.  Set wrapped, so we do
4096            * justification */
4097           wrapped = TRUE;
4098           goto done;
4099         }
4100     }
4101 
4102  done:
4103   pango_layout_line_postprocess (line, state, wrapped);
4104   add_line (line, state);
4105   state->line_of_par++;
4106   state->line_start_index += line->length;
4107   state->line_start_offset = state->start_offset;
4108 }
4109 
4110 static void
get_items_log_attrs(const char * text,int start,int length,GList * items,PangoLogAttr * log_attrs,int log_attrs_len)4111 get_items_log_attrs (const char   *text,
4112                      int           start,
4113                      int           length,
4114                      GList        *items,
4115                      PangoLogAttr *log_attrs,
4116                      int           log_attrs_len)
4117 {
4118   int offset = 0;
4119   GList *l;
4120 
4121   pango_default_break (text + start, length, NULL, log_attrs, log_attrs_len);
4122 
4123   for (l = items; l; l = l->next)
4124     {
4125       PangoItem *item = l->data;
4126       g_assert (item->offset <= start + length);
4127       g_assert (item->length <= (start + length) - item->offset);
4128 
4129       pango_tailor_break (text + item->offset,
4130                           item->length,
4131                           &item->analysis,
4132                           item->offset,
4133                           log_attrs + offset,
4134                           item->num_chars + 1);
4135 
4136       offset += item->num_chars;
4137     }
4138 }
4139 
4140 static PangoAttrList *
pango_layout_get_effective_attributes(PangoLayout * layout)4141 pango_layout_get_effective_attributes (PangoLayout *layout)
4142 {
4143   PangoAttrList *attrs;
4144 
4145   if (layout->attrs)
4146     attrs = pango_attr_list_copy (layout->attrs);
4147   else
4148     attrs = NULL;
4149 
4150   if (layout->font_desc)
4151     {
4152       PangoAttribute *attr = pango_attr_font_desc_new (layout->font_desc);
4153 
4154       if (!attrs)
4155         attrs = pango_attr_list_new ();
4156 
4157       pango_attr_list_insert_before (attrs, attr);
4158     }
4159 
4160   if (layout->single_paragraph)
4161     {
4162       PangoAttribute *attr = pango_attr_show_new (PANGO_SHOW_LINE_BREAKS);
4163 
4164       if (!attrs)
4165         attrs = pango_attr_list_new ();
4166 
4167       pango_attr_list_insert_before (attrs, attr);
4168     }
4169 
4170   return attrs;
4171 }
4172 
4173 static gboolean
affects_itemization(PangoAttribute * attr,gpointer data)4174 affects_itemization (PangoAttribute *attr,
4175                      gpointer        data)
4176 {
4177   switch (attr->klass->type)
4178     {
4179     /* These affect font selection */
4180     case PANGO_ATTR_LANGUAGE:
4181     case PANGO_ATTR_FAMILY:
4182     case PANGO_ATTR_STYLE:
4183     case PANGO_ATTR_WEIGHT:
4184     case PANGO_ATTR_VARIANT:
4185     case PANGO_ATTR_STRETCH:
4186     case PANGO_ATTR_SIZE:
4187     case PANGO_ATTR_FONT_DESC:
4188     case PANGO_ATTR_SCALE:
4189     case PANGO_ATTR_FALLBACK:
4190     case PANGO_ATTR_ABSOLUTE_SIZE:
4191     case PANGO_ATTR_GRAVITY:
4192     case PANGO_ATTR_GRAVITY_HINT:
4193     /* These need to be constant across runs */
4194     case PANGO_ATTR_LETTER_SPACING:
4195     case PANGO_ATTR_SHAPE:
4196     case PANGO_ATTR_RISE:
4197       return TRUE;
4198     default:
4199       return FALSE;
4200     }
4201 }
4202 
4203 static gboolean
affects_break_or_shape(PangoAttribute * attr,gpointer data)4204 affects_break_or_shape (PangoAttribute *attr,
4205                         gpointer        data)
4206 {
4207   switch (attr->klass->type)
4208     {
4209     /* Affects breaks */
4210     case PANGO_ATTR_ALLOW_BREAKS:
4211     /* Affects shaping */
4212     case PANGO_ATTR_INSERT_HYPHENS:
4213     case PANGO_ATTR_FONT_FEATURES:
4214     case PANGO_ATTR_SHOW:
4215       return TRUE;
4216     default:
4217       return FALSE;
4218     }
4219 }
4220 
4221 static void
apply_attributes_to_items(GList * items,PangoAttrList * attrs)4222 apply_attributes_to_items (GList         *items,
4223                            PangoAttrList *attrs)
4224 {
4225   GList *l;
4226   PangoAttrIterator iter;
4227 
4228   if (!attrs)
4229     return;
4230 
4231   _pango_attr_list_get_iterator (attrs, &iter);
4232 
4233   for (l = items; l; l = l->next)
4234     {
4235       PangoItem *item = l->data;
4236       pango_item_apply_attrs (item, &iter);
4237     }
4238 
4239   _pango_attr_iterator_destroy (&iter);
4240 }
4241 
4242 static void
apply_attributes_to_runs(PangoLayout * layout,PangoAttrList * attrs)4243 apply_attributes_to_runs (PangoLayout   *layout,
4244                           PangoAttrList *attrs)
4245 {
4246   GSList *ll;
4247 
4248   if (!attrs)
4249     return;
4250 
4251   for (ll = layout->lines; ll; ll = ll->next)
4252     {
4253       PangoLayoutLine *line = ll->data;
4254       GSList *old_runs = g_slist_reverse (line->runs);
4255       GSList *rl;
4256 
4257       line->runs = NULL;
4258       for (rl = old_runs; rl; rl = rl->next)
4259         {
4260           PangoGlyphItem *glyph_item = rl->data;
4261           GSList *new_runs;
4262 
4263           new_runs = pango_glyph_item_apply_attrs (glyph_item,
4264                                                    layout->text,
4265                                                    attrs);
4266 
4267           line->runs = g_slist_concat (new_runs, line->runs);
4268         }
4269 
4270       g_slist_free (old_runs);
4271     }
4272 }
4273 
4274 #pragma GCC diagnostic push
4275 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4276 
4277 static void
pango_layout_check_lines(PangoLayout * layout)4278 pango_layout_check_lines (PangoLayout *layout)
4279 {
4280   const char *start;
4281   gboolean done = FALSE;
4282   int start_offset;
4283   PangoAttrList *attrs;
4284   PangoAttrList *itemize_attrs;
4285   PangoAttrList *shape_attrs;
4286   PangoAttrIterator iter;
4287   PangoDirection prev_base_dir = PANGO_DIRECTION_NEUTRAL, base_dir = PANGO_DIRECTION_NEUTRAL;
4288   ParaBreakState state;
4289 
4290   check_context_changed (layout);
4291 
4292   if (G_LIKELY (layout->lines))
4293     return;
4294 
4295   g_assert (!layout->log_attrs);
4296 
4297   /* For simplicity, we make sure at this point that layout->text
4298    * is non-NULL even if it is zero length
4299    */
4300   if (G_UNLIKELY (!layout->text))
4301     pango_layout_set_text (layout, NULL, 0);
4302 
4303   attrs = pango_layout_get_effective_attributes (layout);
4304   if (attrs)
4305     {
4306       shape_attrs = pango_attr_list_filter (attrs, affects_break_or_shape, NULL);
4307       itemize_attrs = pango_attr_list_filter (attrs, affects_itemization, NULL);
4308 
4309       if (itemize_attrs)
4310         _pango_attr_list_get_iterator (itemize_attrs, &iter);
4311     }
4312   else
4313     {
4314       shape_attrs = NULL;
4315       itemize_attrs = NULL;
4316     }
4317 
4318   layout->log_attrs = g_new0 (PangoLogAttr, layout->n_chars + 1);
4319 
4320   start_offset = 0;
4321   start = layout->text;
4322 
4323   /* Find the first strong direction of the text */
4324   if (layout->auto_dir)
4325     {
4326       prev_base_dir = pango_find_base_dir (layout->text, layout->length);
4327       if (prev_base_dir == PANGO_DIRECTION_NEUTRAL)
4328         prev_base_dir = pango_context_get_base_dir (layout->context);
4329     }
4330   else
4331     base_dir = pango_context_get_base_dir (layout->context);
4332 
4333   /* these are only used if layout->height >= 0 */
4334   state.remaining_height = layout->height;
4335   state.line_height = -1;
4336   if (layout->height >= 0)
4337     {
4338       PangoRectangle logical;
4339       int height;
4340       pango_layout_get_empty_extents_and_height_at_index (layout, 0, &logical, &height);
4341       state.line_height = layout->line_spacing == 0.0 ? logical.height : layout->line_spacing * height;
4342     }
4343 
4344   do
4345     {
4346       int delim_len;
4347       const char *end;
4348       int delimiter_index, next_para_index;
4349 
4350       if (layout->single_paragraph)
4351         {
4352           delimiter_index = layout->length;
4353           next_para_index = layout->length;
4354         }
4355       else
4356         {
4357           pango_find_paragraph_boundary (start,
4358                                          (layout->text + layout->length) - start,
4359                                          &delimiter_index,
4360                                          &next_para_index);
4361         }
4362 
4363       g_assert (next_para_index >= delimiter_index);
4364 
4365       if (layout->auto_dir)
4366         {
4367           base_dir = pango_find_base_dir (start, delimiter_index);
4368 
4369           /* Propagate the base direction for neutral paragraphs */
4370           if (base_dir == PANGO_DIRECTION_NEUTRAL)
4371             base_dir = prev_base_dir;
4372           else
4373             prev_base_dir = base_dir;
4374         }
4375 
4376       end = start + delimiter_index;
4377 
4378       delim_len = next_para_index - delimiter_index;
4379 
4380       if (end == (layout->text + layout->length))
4381         done = TRUE;
4382 
4383       g_assert (end <= (layout->text + layout->length));
4384       g_assert (start <= (layout->text + layout->length));
4385       g_assert (delim_len < 4); /* PS is 3 bytes */
4386       g_assert (delim_len >= 0);
4387 
4388       state.attrs = itemize_attrs;
4389       state.items = pango_itemize_with_base_dir (layout->context,
4390                                                  base_dir,
4391                                                  layout->text,
4392                                                  start - layout->text,
4393                                                  end - start,
4394                                                  itemize_attrs,
4395                                                  itemize_attrs ? &iter : NULL);
4396 
4397       apply_attributes_to_items (state.items, shape_attrs);
4398 
4399       get_items_log_attrs (layout->text,
4400                            start - layout->text,
4401                            delimiter_index + delim_len,
4402                            state.items,
4403                            layout->log_attrs + start_offset,
4404                            layout->n_chars + 1 - start_offset);
4405 
4406       state.base_dir = base_dir;
4407       state.line_of_par = 1;
4408       state.start_offset = start_offset;
4409       state.line_start_offset = start_offset;
4410       state.line_start_index = start - layout->text;
4411 
4412       state.glyphs = NULL;
4413       state.log_widths = NULL;
4414       state.need_hyphen = NULL;
4415 
4416       /* for deterministic bug hunting's sake set everything! */
4417       state.line_width = -1;
4418       state.remaining_width = -1;
4419       state.log_widths_offset = 0;
4420 
4421       state.hyphen_width = -1;
4422 
4423       if (state.items)
4424         {
4425           while (state.items)
4426             process_line (layout, &state);
4427         }
4428       else
4429         {
4430           PangoLayoutLine *empty_line;
4431 
4432           empty_line = pango_layout_line_new (layout);
4433           empty_line->start_index = state.line_start_index;
4434           empty_line->is_paragraph_start = TRUE;
4435           line_set_resolved_dir (empty_line, base_dir);
4436 
4437           add_line (empty_line, &state);
4438         }
4439 
4440       if (layout->height >= 0 && state.remaining_height < state.line_height)
4441         done = TRUE;
4442 
4443       if (!done)
4444         start_offset += pango_utf8_strlen (start, (end - start) + delim_len);
4445 
4446       start = end + delim_len;
4447     }
4448   while (!done);
4449 
4450   apply_attributes_to_runs (layout, attrs);
4451   layout->lines = g_slist_reverse (layout->lines);
4452 
4453   if (itemize_attrs)
4454     {
4455       pango_attr_list_unref (itemize_attrs);
4456       _pango_attr_iterator_destroy (&iter);
4457     }
4458 
4459   pango_attr_list_unref (shape_attrs);
4460   pango_attr_list_unref (attrs);
4461 }
4462 
4463 #pragma GCC diagnostic pop
4464 
4465 /**
4466  * pango_layout_line_ref:
4467  * @line: (nullable): a `PangoLayoutLine`
4468  *
4469  * Increase the reference count of a `PangoLayoutLine` by one.
4470  *
4471  * Return value: the line passed in.
4472  *
4473  * Since: 1.10
4474  */
4475 PangoLayoutLine *
pango_layout_line_ref(PangoLayoutLine * line)4476 pango_layout_line_ref (PangoLayoutLine *line)
4477 {
4478   PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line;
4479 
4480   if (line == NULL)
4481     return NULL;
4482 
4483   g_atomic_int_inc ((int *) &private->ref_count);
4484 
4485   return line;
4486 }
4487 
4488 /**
4489  * pango_layout_line_unref:
4490  * @line: a `PangoLayoutLine`
4491  *
4492  * Decrease the reference count of a `PangoLayoutLine` by one.
4493  *
4494  * If the result is zero, the line and all associated memory
4495  * will be freed.
4496  */
4497 void
pango_layout_line_unref(PangoLayoutLine * line)4498 pango_layout_line_unref (PangoLayoutLine *line)
4499 {
4500   PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line;
4501 
4502   if (line == NULL)
4503     return;
4504 
4505   g_return_if_fail (private->ref_count > 0);
4506 
4507   if (g_atomic_int_dec_and_test ((int *) &private->ref_count))
4508     {
4509       g_slist_foreach (line->runs, (GFunc)free_run, GINT_TO_POINTER (1));
4510       g_slist_free (line->runs);
4511       g_slice_free (PangoLayoutLinePrivate, private);
4512     }
4513 }
4514 
4515 G_DEFINE_BOXED_TYPE (PangoLayoutLine, pango_layout_line,
4516                      pango_layout_line_ref,
4517                      pango_layout_line_unref);
4518 
4519 /**
4520  * pango_layout_line_x_to_index:
4521  * @line: a `PangoLayoutLine`
4522  * @x_pos: the X offset (in Pango units) from the left edge of the line.
4523  * @index_: (out): location to store calculated byte index for the grapheme
4524  *   in which the user clicked
4525  * @trailing: (out): location to store an integer indicating where in the
4526  *   grapheme the user clicked. It will either be zero, or the number of
4527  *   characters in the grapheme. 0 represents the leading edge of the grapheme.
4528  *
4529  * Converts from x offset to the byte index of the corresponding character
4530  * within the text of the layout.
4531  *
4532  * If @x_pos is outside the line, @index_ and @trailing will point to the very
4533  * first or very last position in the line. This determination is based on the
4534  * resolved direction of the paragraph; for example, if the resolved direction
4535  * is right-to-left, then an X position to the right of the line (after it)
4536  * results in 0 being stored in @index_ and @trailing. An X position to the
4537  * left of the line results in @index_ pointing to the (logical) last grapheme
4538  * in the line and @trailing being set to the number of characters in that
4539  * grapheme. The reverse is true for a left-to-right line.
4540  *
4541  * Return value: %FALSE if @x_pos was outside the line, %TRUE if inside
4542  */
4543 gboolean
pango_layout_line_x_to_index(PangoLayoutLine * line,int x_pos,int * index,int * trailing)4544 pango_layout_line_x_to_index (PangoLayoutLine *line,
4545                               int              x_pos,
4546                               int             *index,
4547                               int             *trailing)
4548 {
4549   GSList *tmp_list;
4550   gint start_pos = 0;
4551   gint first_index = 0; /* line->start_index */
4552   gint first_offset;
4553   gint last_index;      /* start of last grapheme in line */
4554   gint last_offset;
4555   gint end_index;       /* end iterator for line */
4556   gint end_offset;      /* end iterator for line */
4557   PangoLayout *layout;
4558   gint last_trailing;
4559   gboolean suppress_last_trailing;
4560 
4561   g_return_val_if_fail (LINE_IS_VALID (line), FALSE);
4562 
4563   layout = line->layout;
4564 
4565   /* Find the last index in the line
4566    */
4567   first_index = line->start_index;
4568 
4569   if (line->length == 0)
4570     {
4571       if (index)
4572         *index = first_index;
4573       if (trailing)
4574         *trailing = 0;
4575 
4576       return FALSE;
4577     }
4578 
4579   g_assert (line->length > 0);
4580 
4581   first_offset = g_utf8_pointer_to_offset (layout->text, layout->text + line->start_index);
4582 
4583   end_index = first_index + line->length;
4584   end_offset = first_offset + g_utf8_pointer_to_offset (layout->text + first_index, layout->text + end_index);
4585 
4586   last_index = end_index;
4587   last_offset = end_offset;
4588   last_trailing = 0;
4589   do
4590     {
4591       last_index = g_utf8_prev_char (layout->text + last_index) - layout->text;
4592       last_offset--;
4593       last_trailing++;
4594     }
4595   while (last_offset > first_offset && !layout->log_attrs[last_offset].is_cursor_position);
4596 
4597   /* This is a HACK. If a program only keeps track of cursor (etc)
4598    * indices and not the trailing flag, then the trailing index of the
4599    * last character on a wrapped line is identical to the leading
4600    * index of the next line. So, we fake it and set the trailing flag
4601    * to zero.
4602    *
4603    * That is, if the text is "now is the time", and is broken between
4604    * 'now' and 'is'
4605    *
4606    * Then when the cursor is actually at:
4607    *
4608    * n|o|w| |i|s|
4609    *              ^
4610    * we lie and say it is at:
4611    *
4612    * n|o|w| |i|s|
4613    *            ^
4614    *
4615    * So the cursor won't appear on the next line before 'the'.
4616    *
4617    * Actually, any program keeping cursor
4618    * positions with wrapped lines should distinguish leading and
4619    * trailing cursors.
4620    */
4621   tmp_list = layout->lines;
4622   while (tmp_list->data != line)
4623     tmp_list = tmp_list->next;
4624 
4625   if (tmp_list->next &&
4626       line->start_index + line->length == ((PangoLayoutLine *)tmp_list->next->data)->start_index)
4627     suppress_last_trailing = TRUE;
4628   else
4629     suppress_last_trailing = FALSE;
4630 
4631   if (x_pos < 0)
4632     {
4633       /* pick the leftmost char */
4634       if (index)
4635         *index = (line->resolved_dir == PANGO_DIRECTION_LTR) ? first_index : last_index;
4636       /* and its leftmost edge */
4637       if (trailing)
4638         *trailing = (line->resolved_dir == PANGO_DIRECTION_LTR || suppress_last_trailing) ? 0 : last_trailing;
4639 
4640       return FALSE;
4641     }
4642 
4643   tmp_list = line->runs;
4644   while (tmp_list)
4645     {
4646       PangoLayoutRun *run = tmp_list->data;
4647       int logical_width;
4648 
4649       logical_width = pango_glyph_string_get_width (run->glyphs);
4650 
4651       if (x_pos >= start_pos && x_pos < start_pos + logical_width)
4652         {
4653           int offset;
4654           gboolean char_trailing;
4655           int grapheme_start_index;
4656           int grapheme_start_offset;
4657           int grapheme_end_offset;
4658           int pos;
4659           int char_index;
4660 
4661           pango_glyph_string_x_to_index (run->glyphs,
4662                                          layout->text + run->item->offset, run->item->length,
4663                                          &run->item->analysis,
4664                                          x_pos - start_pos,
4665                                          &pos, &char_trailing);
4666 
4667           char_index = run->item->offset + pos;
4668 
4669           /* Convert from characters to graphemes */
4670 
4671           offset = g_utf8_pointer_to_offset (layout->text, layout->text + char_index);
4672 
4673           grapheme_start_offset = offset;
4674           grapheme_start_index = char_index;
4675           while (grapheme_start_offset > first_offset &&
4676                  !layout->log_attrs[grapheme_start_offset].is_cursor_position)
4677             {
4678               grapheme_start_index = g_utf8_prev_char (layout->text + grapheme_start_index) - layout->text;
4679               grapheme_start_offset--;
4680             }
4681 
4682           grapheme_end_offset = offset;
4683           do
4684             {
4685               grapheme_end_offset++;
4686             }
4687           while (grapheme_end_offset < end_offset &&
4688                  !layout->log_attrs[grapheme_end_offset].is_cursor_position);
4689 
4690           if (index)
4691             *index = grapheme_start_index;
4692 
4693           if (trailing)
4694             {
4695               if ((grapheme_end_offset == end_offset && suppress_last_trailing) ||
4696                   offset + char_trailing <= (grapheme_start_offset + grapheme_end_offset) / 2)
4697                 *trailing = 0;
4698               else
4699                 *trailing = grapheme_end_offset - grapheme_start_offset;
4700             }
4701 
4702           return TRUE;
4703         }
4704 
4705       start_pos += logical_width;
4706       tmp_list = tmp_list->next;
4707     }
4708 
4709   /* pick the rightmost char */
4710   if (index)
4711     *index = (line->resolved_dir == PANGO_DIRECTION_LTR) ? last_index : first_index;
4712 
4713   /* and its rightmost edge */
4714   if (trailing)
4715     *trailing = (line->resolved_dir == PANGO_DIRECTION_LTR && !suppress_last_trailing) ? last_trailing : 0;
4716 
4717   return FALSE;
4718 }
4719 
4720 static int
pango_layout_line_get_width(PangoLayoutLine * line)4721 pango_layout_line_get_width (PangoLayoutLine *line)
4722 {
4723   int width = 0;
4724   GSList *tmp_list = line->runs;
4725 
4726   while (tmp_list)
4727     {
4728       PangoLayoutRun *run = tmp_list->data;
4729 
4730       width += pango_glyph_string_get_width (run->glyphs);
4731 
4732       tmp_list = tmp_list->next;
4733     }
4734 
4735   return width;
4736 }
4737 
4738 /**
4739  * pango_layout_line_get_x_ranges:
4740  * @line: a `PangoLayoutLine`
4741  * @start_index: Start byte index of the logical range. If this value
4742  *   is less than the start index for the line, then the first range
4743  *   will extend all the way to the leading edge of the layout. Otherwise,
4744  *   it will start at the leading edge of the first character.
4745  * @end_index: Ending byte index of the logical range. If this value is
4746  *   greater than the end index for the line, then the last range will
4747  *   extend all the way to the trailing edge of the layout. Otherwise,
4748  *   it will end at the trailing edge of the last character.
4749  * @ranges: (out) (array length=n_ranges) (transfer full): location to
4750  *   store a pointer to an array of ranges. The array will be of length
4751  *   `2*n_ranges`, with each range starting at `(*ranges)[2*n]` and of
4752  *   width `(*ranges)[2*n + 1] - (*ranges)[2*n]`. This array must be freed
4753  *   with g_free(). The coordinates are relative to the layout and are in
4754  *   Pango units.
4755  * @n_ranges: The number of ranges stored in @ranges
4756  *
4757  * Gets a list of visual ranges corresponding to a given logical range.
4758  *
4759  * This list is not necessarily minimal - there may be consecutive
4760  * ranges which are adjacent. The ranges will be sorted from left to
4761  * right. The ranges are with respect to the left edge of the entire
4762  * layout, not with respect to the line.
4763  */
4764 void
pango_layout_line_get_x_ranges(PangoLayoutLine * line,int start_index,int end_index,int ** ranges,int * n_ranges)4765 pango_layout_line_get_x_ranges (PangoLayoutLine  *line,
4766                                 int               start_index,
4767                                 int               end_index,
4768                                 int             **ranges,
4769                                 int              *n_ranges)
4770 {
4771   gint line_start_index = 0;
4772   GSList *tmp_list;
4773   int range_count = 0;
4774   int accumulated_width = 0;
4775   int x_offset;
4776   int width, line_width;
4777   PangoAlignment alignment;
4778 
4779   g_return_if_fail (line != NULL);
4780   g_return_if_fail (line->layout != NULL);
4781   g_return_if_fail (start_index <= end_index);
4782 
4783   alignment = get_alignment (line->layout, line);
4784 
4785   width = line->layout->width;
4786   if (width == -1 && alignment != PANGO_ALIGN_LEFT)
4787     {
4788       PangoRectangle logical_rect;
4789       pango_layout_get_extents (line->layout, NULL, &logical_rect);
4790       width = logical_rect.width;
4791     }
4792 
4793   /* FIXME: The computations here could be optimized, by moving the
4794    * computations of the x_offset after we go through and figure
4795    * out where each range is.
4796    */
4797 
4798   {
4799     PangoRectangle logical_rect;
4800     pango_layout_line_get_extents (line, NULL, &logical_rect);
4801     line_width = logical_rect.width;
4802   }
4803 
4804   get_x_offset (line->layout, line, width, line_width, &x_offset);
4805 
4806   line_start_index = line->start_index;
4807 
4808   /* Allocate the maximum possible size */
4809   if (ranges)
4810     *ranges = g_new (int, 2 * (2 + g_slist_length (line->runs)));
4811 
4812   if (x_offset > 0 &&
4813       ((line->resolved_dir == PANGO_DIRECTION_LTR && start_index < line_start_index) ||
4814        (line->resolved_dir == PANGO_DIRECTION_RTL && end_index > line_start_index + line->length)))
4815     {
4816       if (ranges)
4817         {
4818           (*ranges)[2*range_count] = 0;
4819           (*ranges)[2*range_count + 1] = x_offset;
4820         }
4821 
4822       range_count ++;
4823     }
4824 
4825   tmp_list = line->runs;
4826   while (tmp_list)
4827     {
4828       PangoLayoutRun *run = (PangoLayoutRun *)tmp_list->data;
4829 
4830       if ((start_index < run->item->offset + run->item->length &&
4831            end_index > run->item->offset))
4832         {
4833           if (ranges)
4834             {
4835               int run_start_index = MAX (start_index, run->item->offset);
4836               int run_end_index = MIN (end_index, run->item->offset + run->item->length);
4837               int run_start_x, run_end_x;
4838 
4839               g_assert (run_end_index > 0);
4840 
4841               /* Back the end_index off one since we want to find the trailing edge of the preceding character */
4842 
4843               run_end_index = g_utf8_prev_char (line->layout->text + run_end_index) - line->layout->text;
4844 
4845               pango_glyph_string_index_to_x (run->glyphs,
4846                                              line->layout->text + run->item->offset,
4847                                              run->item->length,
4848                                              &run->item->analysis,
4849                                              run_start_index - run->item->offset, FALSE,
4850                                              &run_start_x);
4851               pango_glyph_string_index_to_x (run->glyphs,
4852                                              line->layout->text + run->item->offset,
4853                                              run->item->length,
4854                                              &run->item->analysis,
4855                                              run_end_index - run->item->offset, TRUE,
4856                                              &run_end_x);
4857 
4858               (*ranges)[2*range_count] = x_offset + accumulated_width + MIN (run_start_x, run_end_x);
4859               (*ranges)[2*range_count + 1] = x_offset + accumulated_width + MAX (run_start_x, run_end_x);
4860             }
4861 
4862           range_count++;
4863         }
4864 
4865       if (tmp_list->next)
4866         accumulated_width += pango_glyph_string_get_width (run->glyphs);
4867 
4868       tmp_list = tmp_list->next;
4869     }
4870 
4871   if (x_offset + line_width < line->layout->width &&
4872       ((line->resolved_dir == PANGO_DIRECTION_LTR && end_index > line_start_index + line->length) ||
4873        (line->resolved_dir == PANGO_DIRECTION_RTL && start_index < line_start_index)))
4874     {
4875       if (ranges)
4876         {
4877           (*ranges)[2*range_count] = x_offset + line_width;
4878           (*ranges)[2*range_count + 1] = line->layout->width;
4879         }
4880 
4881       range_count ++;
4882     }
4883 
4884   if (n_ranges)
4885     *n_ranges = range_count;
4886 }
4887 
4888 static void
pango_layout_get_empty_extents_and_height_at_index(PangoLayout * layout,int index,PangoRectangle * logical_rect,int * height)4889 pango_layout_get_empty_extents_and_height_at_index (PangoLayout    *layout,
4890                                                     int             index,
4891                                                     PangoRectangle *logical_rect,
4892                                                     int             *height)
4893 {
4894   if (logical_rect)
4895     {
4896       PangoFont *font;
4897       PangoFontDescription *font_desc = NULL;
4898       gboolean free_font_desc = FALSE;
4899 
4900       font_desc = pango_context_get_font_description (layout->context);
4901 
4902       if (layout->font_desc)
4903         {
4904           font_desc = pango_font_description_copy_static (font_desc);
4905           pango_font_description_merge (font_desc, layout->font_desc, TRUE);
4906           free_font_desc = TRUE;
4907         }
4908 
4909       /* Find the font description for this line
4910        */
4911       if (layout->attrs)
4912         {
4913           PangoAttrIterator iter;
4914           int start, end;
4915 
4916           _pango_attr_list_get_iterator (layout->attrs, &iter);
4917 
4918           do
4919             {
4920               pango_attr_iterator_range (&iter, &start, &end);
4921 
4922               if (start <= index && index < end)
4923                 {
4924                   if (!free_font_desc)
4925                     {
4926                       font_desc = pango_font_description_copy_static (font_desc);
4927                       free_font_desc = TRUE;
4928                     }
4929 
4930                   pango_attr_iterator_get_font (&iter,
4931                                                 font_desc,
4932                                                 NULL,
4933                                                 NULL);
4934 
4935                   break;
4936                 }
4937 
4938             }
4939           while (pango_attr_iterator_next (&iter));
4940 
4941           _pango_attr_iterator_destroy (&iter);
4942         }
4943 
4944       font = pango_context_load_font (layout->context, font_desc);
4945       if (font)
4946         {
4947           PangoFontMetrics *metrics;
4948 
4949           metrics = pango_font_get_metrics (font,
4950                                             pango_context_get_language (layout->context));
4951 
4952           if (metrics)
4953             {
4954               logical_rect->y = - pango_font_metrics_get_ascent (metrics);
4955               logical_rect->height = - logical_rect->y + pango_font_metrics_get_descent (metrics);
4956               if (height)
4957                 *height = pango_font_metrics_get_height (metrics);
4958 
4959               pango_font_metrics_unref (metrics);
4960             }
4961           else
4962             {
4963               logical_rect->y = 0;
4964               logical_rect->height = 0;
4965             }
4966           g_object_unref (font);
4967         }
4968       else
4969         {
4970           logical_rect->y = 0;
4971           logical_rect->height = 0;
4972         }
4973 
4974       if (free_font_desc)
4975         pango_font_description_free (font_desc);
4976 
4977       logical_rect->x = 0;
4978       logical_rect->width = 0;
4979     }
4980 }
4981 
4982 static void
pango_layout_line_get_empty_extents_and_height(PangoLayoutLine * line,PangoRectangle * logical_rect,int * height)4983 pango_layout_line_get_empty_extents_and_height (PangoLayoutLine *line,
4984                                                 PangoRectangle  *logical_rect,
4985                                                 int             *height)
4986 {
4987   pango_layout_get_empty_extents_and_height_at_index (line->layout, line->start_index, logical_rect, height);
4988 }
4989 
4990 static void
pango_layout_run_get_extents_and_height(PangoLayoutRun * run,PangoRectangle * run_ink,PangoRectangle * run_logical,int * height)4991 pango_layout_run_get_extents_and_height (PangoLayoutRun *run,
4992                                          PangoRectangle *run_ink,
4993                                          PangoRectangle *run_logical,
4994                                          int            *height)
4995 {
4996   PangoRectangle logical;
4997   ItemProperties properties;
4998   PangoFontMetrics *metrics = NULL;
4999   gboolean has_underline;
5000   gboolean has_overline;
5001 
5002   if (G_UNLIKELY (!run_ink && !run_logical && !height))
5003     return;
5004 
5005   pango_layout_get_item_properties (run->item, &properties);
5006 
5007   has_underline = properties.uline_single || properties.uline_double ||
5008                   properties.uline_low || properties.uline_error;
5009   has_overline = properties.oline_single;
5010 
5011   if (!run_logical && (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE))
5012     run_logical = &logical;
5013 
5014   if (!run_logical && (has_underline || has_overline || properties.strikethrough))
5015     run_logical = &logical;
5016 
5017   if (properties.shape_set)
5018     _pango_shape_get_extents (run->item->num_chars,
5019                               properties.shape_ink_rect,
5020                               properties.shape_logical_rect,
5021                               run_ink, run_logical);
5022   else
5023     pango_glyph_string_extents (run->glyphs, run->item->analysis.font,
5024                                 run_ink, run_logical);
5025 
5026   if (run_ink && (has_underline || has_overline || properties.strikethrough))
5027     {
5028       int underline_thickness;
5029       int underline_position;
5030       int strikethrough_thickness;
5031       int strikethrough_position;
5032       int new_pos;
5033 
5034       if (!metrics)
5035         metrics = pango_font_get_metrics (run->item->analysis.font,
5036                                           run->item->analysis.language);
5037 
5038       underline_thickness = pango_font_metrics_get_underline_thickness (metrics);
5039       underline_position = pango_font_metrics_get_underline_position (metrics);
5040       strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness (metrics);
5041       strikethrough_position = pango_font_metrics_get_strikethrough_position (metrics);
5042 
5043       /* the underline/strikethrough takes x,width of logical_rect.  reflect
5044        * that into ink_rect.
5045        */
5046       new_pos = MIN (run_ink->x, run_logical->x);
5047       run_ink->width = MAX (run_ink->x + run_ink->width, run_logical->x + run_logical->width) - new_pos;
5048       run_ink->x = new_pos;
5049 
5050       /* We should better handle the case of height==0 in the following cases.
5051        * If run_ink->height == 0, we should adjust run_ink->y appropriately.
5052        */
5053 
5054       if (properties.strikethrough)
5055         {
5056           if (run_ink->height == 0)
5057             {
5058               run_ink->height = strikethrough_thickness;
5059               run_ink->y = -strikethrough_position;
5060             }
5061         }
5062 
5063       if (properties.oline_single)
5064         {
5065           run_ink->y -= underline_thickness;
5066           run_ink->height += underline_thickness;
5067         }
5068 
5069       if (properties.uline_low)
5070         run_ink->height += 2 * underline_thickness;
5071       if (properties.uline_single)
5072         run_ink->height = MAX (run_ink->height,
5073                                underline_thickness - underline_position - run_ink->y);
5074       if (properties.uline_double)
5075         run_ink->height = MAX (run_ink->height,
5076                                  3 * underline_thickness - underline_position - run_ink->y);
5077       if (properties.uline_error)
5078         run_ink->height = MAX (run_ink->height,
5079                                3 * underline_thickness - underline_position - run_ink->y);
5080     }
5081 
5082   if (height)
5083     {
5084       if (!metrics)
5085         metrics = pango_font_get_metrics (run->item->analysis.font,
5086                                           run->item->analysis.language);
5087       *height = pango_font_metrics_get_height (metrics);
5088     }
5089 
5090   if (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE)
5091     {
5092       gboolean is_hinted = (run_logical->y & run_logical->height & (PANGO_SCALE - 1)) == 0;
5093       int adjustment = run_logical->y + run_logical->height / 2;
5094 
5095       if (is_hinted)
5096         adjustment = PANGO_UNITS_ROUND (adjustment);
5097 
5098       properties.rise += adjustment;
5099     }
5100 
5101   if (properties.rise != 0)
5102     {
5103       if (run_ink)
5104         run_ink->y -= properties.rise;
5105 
5106       if (run_logical)
5107         run_logical->y -= properties.rise;
5108     }
5109 
5110   if (metrics)
5111     pango_font_metrics_unref (metrics);
5112 }
5113 
5114 static void
pango_layout_line_get_extents_and_height(PangoLayoutLine * line,PangoRectangle * ink_rect,PangoRectangle * logical_rect,int * height)5115 pango_layout_line_get_extents_and_height (PangoLayoutLine *line,
5116                                           PangoRectangle  *ink_rect,
5117                                           PangoRectangle  *logical_rect,
5118                                           int             *height)
5119 {
5120   PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line;
5121   GSList *tmp_list;
5122   int x_pos = 0;
5123   gboolean caching = FALSE;
5124 
5125   g_return_if_fail (LINE_IS_VALID (line));
5126 
5127   if (G_UNLIKELY (!ink_rect && !logical_rect && !height))
5128     return;
5129 
5130   switch (private->cache_status)
5131     {
5132     case CACHED:
5133       {
5134         if (ink_rect)
5135           *ink_rect = private->ink_rect;
5136         if (logical_rect)
5137           *logical_rect = private->logical_rect;
5138         if (height)
5139           *height = private->height;
5140         return;
5141       }
5142     case NOT_CACHED:
5143       {
5144         caching = TRUE;
5145         if (!ink_rect)
5146           ink_rect = &private->ink_rect;
5147         if (!logical_rect)
5148           logical_rect = &private->logical_rect;
5149         if (!height)
5150           height = &private->height;
5151         break;
5152       }
5153     case LEAKED:
5154       {
5155         break;
5156       }
5157     }
5158 
5159   if (ink_rect)
5160     {
5161       ink_rect->x = 0;
5162       ink_rect->y = 0;
5163       ink_rect->width = 0;
5164       ink_rect->height = 0;
5165     }
5166 
5167   if (logical_rect)
5168     {
5169       logical_rect->x = 0;
5170       logical_rect->y = 0;
5171       logical_rect->width = 0;
5172       logical_rect->height = 0;
5173     }
5174 
5175   if (height)
5176     *height = 0;
5177 
5178   tmp_list = line->runs;
5179   while (tmp_list)
5180     {
5181       PangoLayoutRun *run = tmp_list->data;
5182       int new_pos;
5183       PangoRectangle run_ink;
5184       PangoRectangle run_logical;
5185       int run_height;
5186 
5187       pango_layout_run_get_extents_and_height (run,
5188                                                ink_rect ? &run_ink : NULL,
5189                                                &run_logical,
5190                                                height ? &run_height : NULL);
5191 
5192       if (ink_rect)
5193         {
5194           if (ink_rect->width == 0 || ink_rect->height == 0)
5195             {
5196               *ink_rect = run_ink;
5197               ink_rect->x += x_pos;
5198             }
5199           else if (run_ink.width != 0 && run_ink.height != 0)
5200             {
5201               new_pos = MIN (ink_rect->x, x_pos + run_ink.x);
5202               ink_rect->width = MAX (ink_rect->x + ink_rect->width,
5203                                      x_pos + run_ink.x + run_ink.width) - new_pos;
5204               ink_rect->x = new_pos;
5205 
5206               new_pos = MIN (ink_rect->y, run_ink.y);
5207               ink_rect->height = MAX (ink_rect->y + ink_rect->height,
5208                                       run_ink.y + run_ink.height) - new_pos;
5209               ink_rect->y = new_pos;
5210             }
5211         }
5212 
5213       if (logical_rect)
5214         {
5215           new_pos = MIN (logical_rect->x, x_pos + run_logical.x);
5216           logical_rect->width = MAX (logical_rect->x + logical_rect->width,
5217                                      x_pos + run_logical.x + run_logical.width) - new_pos;
5218           logical_rect->x = new_pos;
5219 
5220           new_pos = MIN (logical_rect->y, run_logical.y);
5221           logical_rect->height = MAX (logical_rect->y + logical_rect->height,
5222                                       run_logical.y + run_logical.height) - new_pos;
5223           logical_rect->y = new_pos;
5224         }
5225 
5226       if (height)
5227         *height = MAX (*height, run_height);
5228 
5229       x_pos += run_logical.width;
5230       tmp_list = tmp_list->next;
5231     }
5232 
5233   if (!line->runs)
5234     {
5235       PangoRectangle r, *rect;
5236 
5237       rect = logical_rect ? logical_rect : &r;
5238       pango_layout_line_get_empty_extents_and_height (line, rect, height);
5239     }
5240 
5241   if (caching)
5242     {
5243       if (&private->ink_rect != ink_rect)
5244         private->ink_rect = *ink_rect;
5245       if (&private->logical_rect != logical_rect)
5246         private->logical_rect = *logical_rect;
5247       if (&private->height != height)
5248         private->height = *height;
5249       private->cache_status = CACHED;
5250     }
5251 }
5252 
5253 /**
5254  * pango_layout_line_get_extents:
5255  * @line: a `PangoLayoutLine`
5256  * @ink_rect: (out) (optional): rectangle used to store the extents of
5257  *   the glyph string as drawn
5258  * @logical_rect: (out) (optional): rectangle used to store the logical
5259  *   extents of the glyph string
5260  *
5261  * Computes the logical and ink extents of a layout line.
5262  *
5263  * See [method@Pango.Font.get_glyph_extents] for details
5264  * about the interpretation of the rectangles.
5265  */
5266 void
pango_layout_line_get_extents(PangoLayoutLine * line,PangoRectangle * ink_rect,PangoRectangle * logical_rect)5267 pango_layout_line_get_extents (PangoLayoutLine *line,
5268                                PangoRectangle  *ink_rect,
5269                                PangoRectangle  *logical_rect)
5270 {
5271   pango_layout_line_get_extents_and_height (line, ink_rect, logical_rect, NULL);
5272 }
5273 
5274 /**
5275  * pango_layout_line_get_height:
5276  * @line: a `PangoLayoutLine`
5277  * @height: (out) (optional): return location for the line height
5278  *
5279  * Computes the height of the line, i.e. the distance between
5280  * this and the previous lines baseline.
5281  *
5282  * Since: 1.44
5283  */
5284 void
pango_layout_line_get_height(PangoLayoutLine * line,int * height)5285 pango_layout_line_get_height (PangoLayoutLine *line,
5286                               int             *height)
5287 {
5288   pango_layout_line_get_extents_and_height (line, NULL, NULL, height);
5289 }
5290 
5291 static PangoLayoutLine *
pango_layout_line_new(PangoLayout * layout)5292 pango_layout_line_new (PangoLayout *layout)
5293 {
5294   PangoLayoutLinePrivate *private = g_slice_new (PangoLayoutLinePrivate);
5295 
5296   private->ref_count = 1;
5297   private->line.layout = layout;
5298   private->line.runs = NULL;
5299   private->line.length = 0;
5300   private->cache_status = NOT_CACHED;
5301 
5302   /* Note that we leave start_index, resolved_dir, and is_paragraph_start
5303    *  uninitialized */
5304 
5305   return (PangoLayoutLine *) private;
5306 }
5307 
5308 /**
5309  * pango_layout_line_get_pixel_extents:
5310  * @layout_line: a `PangoLayoutLine`
5311  * @ink_rect: (out) (optional): rectangle used to store the extents of
5312  *   the glyph string as drawn
5313  * @logical_rect: (out) (optional): rectangle used to store the logical
5314  *   extents of the glyph string
5315  *
5316  * Computes the logical and ink extents of @layout_line in device units.
5317  *
5318  * This function just calls [method@Pango.LayoutLine.get_extents] followed by
5319  * two [func@extents_to_pixels] calls, rounding @ink_rect and @logical_rect
5320  * such that the rounded rectangles fully contain the unrounded one (that is,
5321  * passes them as first argument to [func@extents_to_pixels]).
5322  */
5323 void
pango_layout_line_get_pixel_extents(PangoLayoutLine * layout_line,PangoRectangle * ink_rect,PangoRectangle * logical_rect)5324 pango_layout_line_get_pixel_extents (PangoLayoutLine *layout_line,
5325                                      PangoRectangle  *ink_rect,
5326                                      PangoRectangle  *logical_rect)
5327 {
5328   g_return_if_fail (LINE_IS_VALID (layout_line));
5329 
5330   pango_layout_line_get_extents (layout_line, ink_rect, logical_rect);
5331   pango_extents_to_pixels (ink_rect, NULL);
5332   pango_extents_to_pixels (logical_rect, NULL);
5333 }
5334 
5335 /*
5336  * NB: This implement the exact same algorithm as
5337  *     reorder-items.c:pango_reorder_items().
5338  */
5339 static GSList *
reorder_runs_recurse(GSList * items,int n_items)5340 reorder_runs_recurse (GSList *items,
5341                       int     n_items)
5342 {
5343   GSList *tmp_list, *level_start_node;
5344   int i, level_start_i;
5345   int min_level = G_MAXINT;
5346   GSList *result = NULL;
5347 
5348   if (n_items == 0)
5349     return NULL;
5350 
5351   tmp_list = items;
5352   for (i=0; i<n_items; i++)
5353     {
5354       PangoLayoutRun *run = tmp_list->data;
5355 
5356       min_level = MIN (min_level, run->item->analysis.level);
5357 
5358       tmp_list = tmp_list->next;
5359     }
5360 
5361   level_start_i = 0;
5362   level_start_node = items;
5363   tmp_list = items;
5364   for (i=0; i<n_items; i++)
5365     {
5366       PangoLayoutRun *run = tmp_list->data;
5367 
5368       if (run->item->analysis.level == min_level)
5369         {
5370           if (min_level % 2)
5371             {
5372               if (i > level_start_i)
5373                 result = g_slist_concat (reorder_runs_recurse (level_start_node, i - level_start_i), result);
5374               result = g_slist_prepend (result, run);
5375             }
5376           else
5377             {
5378               if (i > level_start_i)
5379                 result = g_slist_concat (result, reorder_runs_recurse (level_start_node, i - level_start_i));
5380               result = g_slist_append (result, run);
5381             }
5382 
5383           level_start_i = i + 1;
5384           level_start_node = tmp_list->next;
5385         }
5386 
5387       tmp_list = tmp_list->next;
5388     }
5389 
5390   if (min_level % 2)
5391     {
5392       if (i > level_start_i)
5393         result = g_slist_concat (reorder_runs_recurse (level_start_node, i - level_start_i), result);
5394     }
5395   else
5396     {
5397       if (i > level_start_i)
5398         result = g_slist_concat (result, reorder_runs_recurse (level_start_node, i - level_start_i));
5399     }
5400 
5401   return result;
5402 }
5403 
5404 static void
pango_layout_line_reorder(PangoLayoutLine * line)5405 pango_layout_line_reorder (PangoLayoutLine *line)
5406 {
5407   GSList *logical_runs = line->runs;
5408   GSList *tmp_list;
5409   gboolean all_even, all_odd;
5410   guint8 level_or = 0, level_and = 1;
5411   int length = 0;
5412 
5413   /* Check if all items are in the same direction, in that case, the
5414    * line does not need modification and we can avoid the expensive
5415    * reorder runs recurse procedure.
5416    */
5417   for (tmp_list = logical_runs; tmp_list != NULL; tmp_list = tmp_list->next)
5418     {
5419       PangoLayoutRun *run = tmp_list->data;
5420 
5421       level_or |= run->item->analysis.level;
5422       level_and &= run->item->analysis.level;
5423 
5424       length++;
5425     }
5426 
5427   /* If none of the levels had the LSB set, all numbers were even. */
5428   all_even = (level_or & 0x1) == 0;
5429 
5430   /* If all of the levels had the LSB set, all numbers were odd. */
5431   all_odd = (level_and & 0x1) == 1;
5432 
5433   if (!all_even && !all_odd)
5434     {
5435       line->runs = reorder_runs_recurse (logical_runs, length);
5436       g_slist_free (logical_runs);
5437     }
5438   else if (all_odd)
5439       line->runs = g_slist_reverse (logical_runs);
5440 }
5441 
5442 static int
get_item_letter_spacing(PangoItem * item)5443 get_item_letter_spacing (PangoItem *item)
5444 {
5445   ItemProperties properties;
5446 
5447   pango_layout_get_item_properties (item, &properties);
5448 
5449   return properties.letter_spacing;
5450 }
5451 
5452 static void
pad_glyphstring_right(PangoGlyphString * glyphs,ParaBreakState * state,int adjustment)5453 pad_glyphstring_right (PangoGlyphString *glyphs,
5454                        ParaBreakState   *state,
5455                        int               adjustment)
5456 {
5457   int glyph = glyphs->num_glyphs - 1;
5458 
5459   while (glyph >= 0 && glyphs->glyphs[glyph].geometry.width == 0)
5460     glyph--;
5461 
5462   if (glyph < 0)
5463     return;
5464 
5465   state->remaining_width -= adjustment;
5466   glyphs->glyphs[glyph].geometry.width += adjustment;
5467   if (glyphs->glyphs[glyph].geometry.width < 0)
5468     {
5469       state->remaining_width += glyphs->glyphs[glyph].geometry.width;
5470       glyphs->glyphs[glyph].geometry.width = 0;
5471     }
5472 }
5473 
5474 static void
pad_glyphstring_left(PangoGlyphString * glyphs,ParaBreakState * state,int adjustment)5475 pad_glyphstring_left (PangoGlyphString *glyphs,
5476                       ParaBreakState   *state,
5477                       int               adjustment)
5478 {
5479   int glyph = 0;
5480 
5481   while (glyph < glyphs->num_glyphs && glyphs->glyphs[glyph].geometry.width == 0)
5482     glyph++;
5483 
5484   if (glyph == glyphs->num_glyphs)
5485     return;
5486 
5487   state->remaining_width -= adjustment;
5488   glyphs->glyphs[glyph].geometry.width += adjustment;
5489   glyphs->glyphs[glyph].geometry.x_offset += adjustment;
5490 }
5491 
5492 static gboolean
is_tab_run(PangoLayout * layout,PangoLayoutRun * run)5493 is_tab_run (PangoLayout    *layout,
5494             PangoLayoutRun *run)
5495 {
5496   return (layout->text[run->item->offset] == '\t');
5497 }
5498 
5499 static void
zero_line_final_space(PangoLayoutLine * line,ParaBreakState * state,PangoLayoutRun * run)5500 zero_line_final_space (PangoLayoutLine *line,
5501                        ParaBreakState  *state,
5502                        PangoLayoutRun  *run)
5503 {
5504   PangoLayout *layout = line->layout;
5505   PangoItem *item = run->item;
5506   PangoGlyphString *glyphs = run->glyphs;
5507   int glyph = item->analysis.level % 2 ? 0 : glyphs->num_glyphs - 1;
5508 
5509   if (glyphs->glyphs[glyph].glyph == PANGO_GET_UNKNOWN_GLYPH (0x2028))
5510     return; /* this LS is visible */
5511 
5512   /* if the final char of line forms a cluster, and it's
5513    * a whitespace char, zero its glyph's width as it's been wrapped
5514    */
5515 
5516   if (glyphs->num_glyphs < 1 || state->start_offset == 0 ||
5517       !layout->log_attrs[state->start_offset - 1].is_white)
5518     return;
5519 
5520   if (glyphs->num_glyphs >= 2 &&
5521       glyphs->log_clusters[glyph] == glyphs->log_clusters[glyph + (item->analysis.level % 2 ? 1 : -1)])
5522     return;
5523 
5524   state->remaining_width += glyphs->glyphs[glyph].geometry.width;
5525   glyphs->glyphs[glyph].geometry.width = 0;
5526   glyphs->glyphs[glyph].glyph = PANGO_GLYPH_EMPTY;
5527 }
5528 
5529 /* When doing shaping, we add the letter spacing value for a
5530  * run after every grapheme in the run. This produces ugly
5531  * asymmetrical results, so what this routine is redistributes
5532  * that space to the beginning and the end of the run.
5533  *
5534  * We also trim the letter spacing from runs adjacent to
5535  * tabs and from the outside runs of the lines so that things
5536  * line up properly. The line breaking and tab positioning
5537  * were computed without this trimming so they are no longer
5538  * exactly correct, but this won't be very noticeable in most
5539  * cases.
5540  */
5541 static void
adjust_line_letter_spacing(PangoLayoutLine * line,ParaBreakState * state)5542 adjust_line_letter_spacing (PangoLayoutLine *line,
5543                             ParaBreakState  *state)
5544 {
5545   PangoLayout *layout = line->layout;
5546   gboolean reversed;
5547   PangoLayoutRun *last_run;
5548   int tab_adjustment;
5549   GSList *l;
5550 
5551   /* If we have tab stops and the resolved direction of the
5552    * line is RTL, then we need to walk through the line
5553    * in reverse direction to figure out the corrections for
5554    * tab stops.
5555    */
5556   reversed = FALSE;
5557   if (line->resolved_dir == PANGO_DIRECTION_RTL)
5558     {
5559       for (l = line->runs; l; l = l->next)
5560         if (is_tab_run (layout, l->data))
5561           {
5562             line->runs = g_slist_reverse (line->runs);
5563             reversed = TRUE;
5564             break;
5565           }
5566     }
5567 
5568   /* Walk over the runs in the line, redistributing letter
5569    * spacing from the end of the run to the start of the
5570    * run and trimming letter spacing from the ends of the
5571    * runs adjacent to the ends of the line or tab stops.
5572    *
5573    * We accumulate a correction factor from this trimming
5574    * which we add onto the next tab stop space to keep the
5575    * things properly aligned.
5576    */
5577   last_run = NULL;
5578   tab_adjustment = 0;
5579   for (l = line->runs; l; l = l->next)
5580     {
5581       PangoLayoutRun *run = l->data;
5582       PangoLayoutRun *next_run = l->next ? l->next->data : NULL;
5583 
5584       if (is_tab_run (layout, run))
5585         {
5586           pad_glyphstring_right (run->glyphs, state, tab_adjustment);
5587           tab_adjustment = 0;
5588         }
5589       else
5590         {
5591           PangoLayoutRun *visual_next_run = reversed ? last_run : next_run;
5592           PangoLayoutRun *visual_last_run = reversed ? next_run : last_run;
5593           int run_spacing = get_item_letter_spacing (run->item);
5594           int space_left, space_right;
5595 
5596           distribute_letter_spacing (run_spacing, &space_left, &space_right);
5597 
5598           if (run->glyphs->glyphs[0].geometry.width == 0)
5599             {
5600               /* we've zeroed this space glyph at the end of line, now remove
5601                * the letter spacing added to its adjacent glyph */
5602               pad_glyphstring_left (run->glyphs, state, - space_left);
5603             }
5604           else if (!visual_last_run || is_tab_run (layout, visual_last_run))
5605             {
5606               pad_glyphstring_left (run->glyphs, state, - space_left);
5607               tab_adjustment += space_left;
5608             }
5609 
5610           if (run->glyphs->glyphs[run->glyphs->num_glyphs - 1].geometry.width == 0)
5611             {
5612               /* we've zeroed this space glyph at the end of line, now remove
5613                * the letter spacing added to its adjacent glyph */
5614               pad_glyphstring_right (run->glyphs, state, - space_right);
5615             }
5616           else if (!visual_next_run || is_tab_run (layout, visual_next_run))
5617             {
5618               pad_glyphstring_right (run->glyphs, state, - space_right);
5619               tab_adjustment += space_right;
5620             }
5621         }
5622 
5623       last_run = run;
5624     }
5625 
5626   if (reversed)
5627     line->runs = g_slist_reverse (line->runs);
5628 }
5629 
5630 static void
justify_clusters(PangoLayoutLine * line,ParaBreakState * state)5631 justify_clusters (PangoLayoutLine *line,
5632                   ParaBreakState  *state)
5633 {
5634   const gchar *text = line->layout->text;
5635   const PangoLogAttr *log_attrs = line->layout->log_attrs;
5636 
5637   int total_remaining_width, total_gaps = 0;
5638   int added_so_far, gaps_so_far;
5639   gboolean is_hinted;
5640   GSList *run_iter;
5641   enum {
5642     MEASURE,
5643     ADJUST
5644   } mode;
5645 
5646   total_remaining_width = state->remaining_width;
5647   if (total_remaining_width <= 0)
5648     return;
5649 
5650   /* hint to full pixel if total remaining width was so */
5651   is_hinted = (total_remaining_width & (PANGO_SCALE - 1)) == 0;
5652 
5653   for (mode = MEASURE; mode <= ADJUST; mode++)
5654     {
5655       gboolean leftedge = TRUE;
5656       PangoGlyphString *rightmost_glyphs = NULL;
5657       int rightmost_space = 0;
5658       int residual = 0;
5659 
5660       added_so_far = 0;
5661       gaps_so_far = 0;
5662 
5663       for (run_iter = line->runs; run_iter; run_iter = run_iter->next)
5664         {
5665           PangoLayoutRun *run = run_iter->data;
5666           PangoGlyphString *glyphs = run->glyphs;
5667           PangoGlyphItemIter cluster_iter;
5668           gboolean have_cluster;
5669           int dir;
5670           int offset;
5671 
5672           dir = run->item->analysis.level % 2 == 0 ? +1 : -1;
5673 
5674           /* We need character offset of the start of the run.  We don't have this.
5675            * Compute by counting from the beginning of the line.  The naming is
5676            * confusing.  Note that:
5677            *
5678            * run->item->offset        is byte offset of start of run in layout->text.
5679            * state->line_start_index  is byte offset of start of line in layout->text.
5680            * state->line_start_offset is character offset of start of line in layout->text.
5681            */
5682           g_assert (run->item->offset >= state->line_start_index);
5683           offset = state->line_start_offset
5684                  + pango_utf8_strlen (text + state->line_start_index,
5685                                       run->item->offset - state->line_start_index);
5686 
5687           for (have_cluster = dir > 0 ?
5688                  pango_glyph_item_iter_init_start (&cluster_iter, run, text) :
5689                  pango_glyph_item_iter_init_end   (&cluster_iter, run, text);
5690                have_cluster;
5691                have_cluster = dir > 0 ?
5692                  pango_glyph_item_iter_next_cluster (&cluster_iter) :
5693                  pango_glyph_item_iter_prev_cluster (&cluster_iter))
5694             {
5695               int i;
5696               int width = 0;
5697 
5698               /* don't expand in the middle of graphemes */
5699               if (!log_attrs[offset + cluster_iter.start_char].is_cursor_position)
5700                 continue;
5701 
5702               for (i = cluster_iter.start_glyph; i != cluster_iter.end_glyph; i += dir)
5703                 width += glyphs->glyphs[i].geometry.width;
5704 
5705               /* also don't expand zero-width clusters. */
5706               if (width == 0)
5707                 continue;
5708 
5709               gaps_so_far++;
5710 
5711               if (mode == ADJUST)
5712                 {
5713                   int leftmost, rightmost;
5714                   int adjustment, space_left, space_right;
5715 
5716                   adjustment = total_remaining_width / total_gaps + residual;
5717                   if (is_hinted)
5718                   {
5719                     int old_adjustment = adjustment;
5720                     adjustment = PANGO_UNITS_ROUND (adjustment);
5721                     residual = old_adjustment - adjustment;
5722                   }
5723                   /* distribute to before/after */
5724                   distribute_letter_spacing (adjustment, &space_left, &space_right);
5725 
5726                   if (cluster_iter.start_glyph < cluster_iter.end_glyph)
5727                   {
5728                     /* LTR */
5729                     leftmost  = cluster_iter.start_glyph;
5730                     rightmost = cluster_iter.end_glyph - 1;
5731                   }
5732                   else
5733                   {
5734                     /* RTL */
5735                     leftmost  = cluster_iter.end_glyph + 1;
5736                     rightmost = cluster_iter.start_glyph;
5737                   }
5738                   /* Don't add to left-side of left-most glyph of left-most non-zero run. */
5739                   if (leftedge)
5740                     leftedge = FALSE;
5741                   else
5742                   {
5743                     glyphs->glyphs[leftmost].geometry.width    += space_left ;
5744                     glyphs->glyphs[leftmost].geometry.x_offset += space_left ;
5745                     added_so_far += space_left;
5746                   }
5747                   /* Don't add to right-side of right-most glyph of right-most non-zero run. */
5748                   {
5749                     /* Save so we can undo later. */
5750                     rightmost_glyphs = glyphs;
5751                     rightmost_space = space_right;
5752 
5753                     glyphs->glyphs[rightmost].geometry.width  += space_right;
5754                     added_so_far += space_right;
5755                   }
5756                 }
5757             }
5758         }
5759 
5760       if (mode == MEASURE)
5761         {
5762           total_gaps = gaps_so_far - 1;
5763 
5764           if (total_gaps == 0)
5765             {
5766               /* a single cluster, can't really justify it */
5767               return;
5768             }
5769         }
5770       else /* mode == ADJUST */
5771         {
5772           if (rightmost_glyphs)
5773            {
5774              rightmost_glyphs->glyphs[rightmost_glyphs->num_glyphs - 1].geometry.width -= rightmost_space;
5775              added_so_far -= rightmost_space;
5776            }
5777         }
5778     }
5779 
5780   state->remaining_width -= added_so_far;
5781 }
5782 
5783 static void
justify_words(PangoLayoutLine * line,ParaBreakState * state)5784 justify_words (PangoLayoutLine *line,
5785                ParaBreakState  *state)
5786 {
5787   const gchar *text = line->layout->text;
5788   const PangoLogAttr *log_attrs = line->layout->log_attrs;
5789 
5790   int total_remaining_width, total_space_width = 0;
5791   int added_so_far, spaces_so_far;
5792   gboolean is_hinted;
5793   GSList *run_iter;
5794   enum {
5795     MEASURE,
5796     ADJUST
5797   } mode;
5798 
5799   total_remaining_width = state->remaining_width;
5800   if (total_remaining_width <= 0)
5801     return;
5802 
5803   /* hint to full pixel if total remaining width was so */
5804   is_hinted = (total_remaining_width & (PANGO_SCALE - 1)) == 0;
5805 
5806   for (mode = MEASURE; mode <= ADJUST; mode++)
5807     {
5808       added_so_far = 0;
5809       spaces_so_far = 0;
5810 
5811       for (run_iter = line->runs; run_iter; run_iter = run_iter->next)
5812         {
5813           PangoLayoutRun *run = run_iter->data;
5814           PangoGlyphString *glyphs = run->glyphs;
5815           PangoGlyphItemIter cluster_iter;
5816           gboolean have_cluster;
5817           int offset;
5818 
5819           /* We need character offset of the start of the run.  We don't have this.
5820            * Compute by counting from the beginning of the line.  The naming is
5821            * confusing.  Note that:
5822            *
5823            * run->item->offset        is byte offset of start of run in layout->text.
5824            * state->line_start_index  is byte offset of start of line in layout->text.
5825            * state->line_start_offset is character offset of start of line in layout->text.
5826            */
5827           g_assert (run->item->offset >= state->line_start_index);
5828           offset = state->line_start_offset
5829                  + pango_utf8_strlen (text + state->line_start_index,
5830                                       run->item->offset - state->line_start_index);
5831 
5832           for (have_cluster = pango_glyph_item_iter_init_start (&cluster_iter, run, text);
5833                have_cluster;
5834                have_cluster = pango_glyph_item_iter_next_cluster (&cluster_iter))
5835             {
5836               int i;
5837               int dir;
5838 
5839               if (!log_attrs[offset + cluster_iter.start_char].is_expandable_space)
5840                 continue;
5841 
5842               dir = (cluster_iter.start_glyph < cluster_iter.end_glyph) ? 1 : -1;
5843               for (i = cluster_iter.start_glyph; i != cluster_iter.end_glyph; i += dir)
5844                 {
5845                   int glyph_width = glyphs->glyphs[i].geometry.width;
5846 
5847                   if (glyph_width == 0)
5848                     continue;
5849 
5850                   spaces_so_far += glyph_width;
5851 
5852                   if (mode == ADJUST)
5853                     {
5854                       int adjustment;
5855 
5856                       adjustment = ((guint64) spaces_so_far * total_remaining_width) / total_space_width - added_so_far;
5857                       if (is_hinted)
5858                         adjustment = PANGO_UNITS_ROUND (adjustment);
5859 
5860                       glyphs->glyphs[i].geometry.width += adjustment;
5861                       added_so_far += adjustment;
5862                     }
5863                 }
5864             }
5865         }
5866 
5867       if (mode == MEASURE)
5868         {
5869           total_space_width = spaces_so_far;
5870 
5871           if (total_space_width == 0)
5872             {
5873               justify_clusters (line, state);
5874               return;
5875             }
5876         }
5877     }
5878 
5879   state->remaining_width -= added_so_far;
5880 }
5881 
5882 static void
pango_layout_line_postprocess(PangoLayoutLine * line,ParaBreakState * state,gboolean wrapped)5883 pango_layout_line_postprocess (PangoLayoutLine *line,
5884                                ParaBreakState  *state,
5885                                gboolean         wrapped)
5886 {
5887   gboolean ellipsized = FALSE;
5888 
5889   DEBUG ("postprocessing", line, state);
5890 
5891   /* Truncate the logical-final whitespace in the line if we broke the line at it
5892    */
5893   if (wrapped)
5894     /* The runs are in reverse order at this point, since we prepended them to the list.
5895      * So, the first run is the last logical run. */
5896     zero_line_final_space (line, state, line->runs->data);
5897 
5898   /* Reverse the runs
5899    */
5900   line->runs = g_slist_reverse (line->runs);
5901 
5902   /* Ellipsize the line if necessary
5903    */
5904   if (G_UNLIKELY (state->line_width >= 0 &&
5905                   should_ellipsize_current_line (line->layout, state)))
5906     {
5907       PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
5908 
5909       if (pango_context_get_round_glyph_positions (line->layout->context))
5910         shape_flags |= PANGO_SHAPE_ROUND_POSITIONS;
5911 
5912       ellipsized = _pango_layout_line_ellipsize (line, state->attrs, shape_flags, state->line_width);
5913     }
5914 
5915   DEBUG ("after removing final space", line, state);
5916 
5917   /* Now convert logical to visual order
5918    */
5919   pango_layout_line_reorder (line);
5920 
5921   DEBUG ("after reordering", line, state);
5922 
5923   /* Fixup letter spacing between runs
5924    */
5925   adjust_line_letter_spacing (line, state);
5926 
5927   DEBUG ("after letter spacing", line, state);
5928 
5929   /* Distribute extra space between words if justifying and line was wrapped
5930    */
5931   if (line->layout->justify && (wrapped || ellipsized))
5932     {
5933       /* if we ellipsized, we don't have remaining_width set */
5934       if (state->remaining_width < 0)
5935         state->remaining_width = state->line_width - pango_layout_line_get_width (line);
5936 
5937       justify_words (line, state);
5938     }
5939 
5940   DEBUG ("after justification", line, state);
5941 
5942   line->layout->is_wrapped |= wrapped;
5943   line->layout->is_ellipsized |= ellipsized;
5944 }
5945 
5946 static void
pango_layout_get_item_properties(PangoItem * item,ItemProperties * properties)5947 pango_layout_get_item_properties (PangoItem      *item,
5948                                   ItemProperties *properties)
5949 {
5950   GSList *tmp_list = item->analysis.extra_attrs;
5951 
5952   properties->uline_single = FALSE;
5953   properties->uline_double = FALSE;
5954   properties->uline_low = FALSE;
5955   properties->uline_error = FALSE;
5956   properties->oline_single = FALSE;
5957   properties->strikethrough = FALSE;
5958   properties->letter_spacing = 0;
5959   properties->rise = 0;
5960   properties->shape_set = FALSE;
5961   properties->shape_ink_rect = NULL;
5962   properties->shape_logical_rect = NULL;
5963 
5964   while (tmp_list)
5965     {
5966       PangoAttribute *attr = tmp_list->data;
5967 
5968       switch ((int) attr->klass->type)
5969         {
5970         case PANGO_ATTR_UNDERLINE:
5971           switch (((PangoAttrInt *)attr)->value)
5972             {
5973             case PANGO_UNDERLINE_NONE:
5974               break;
5975             case PANGO_UNDERLINE_SINGLE:
5976             case PANGO_UNDERLINE_SINGLE_LINE:
5977               properties->uline_single = TRUE;
5978               break;
5979             case PANGO_UNDERLINE_DOUBLE:
5980             case PANGO_UNDERLINE_DOUBLE_LINE:
5981               properties->uline_double = TRUE;
5982               break;
5983             case PANGO_UNDERLINE_LOW:
5984               properties->uline_low = TRUE;
5985               break;
5986             case PANGO_UNDERLINE_ERROR:
5987             case PANGO_UNDERLINE_ERROR_LINE:
5988               properties->uline_error = TRUE;
5989               break;
5990             default:
5991               g_assert_not_reached ();
5992               break;
5993             }
5994           break;
5995 
5996         case PANGO_ATTR_OVERLINE:
5997           switch (((PangoAttrInt *)attr)->value)
5998             {
5999             case PANGO_OVERLINE_SINGLE:
6000               properties->oline_single = TRUE;
6001               break;
6002             default:
6003               g_assert_not_reached ();
6004               break;
6005             }
6006           break;
6007 
6008         case PANGO_ATTR_STRIKETHROUGH:
6009           properties->strikethrough = ((PangoAttrInt *)attr)->value;
6010           break;
6011 
6012         case PANGO_ATTR_RISE:
6013           properties->rise = ((PangoAttrInt *)attr)->value;
6014           break;
6015 
6016         case PANGO_ATTR_LETTER_SPACING:
6017           properties->letter_spacing = ((PangoAttrInt *)attr)->value;
6018           break;
6019 
6020         case PANGO_ATTR_SHAPE:
6021           properties->shape_set = TRUE;
6022           properties->shape_logical_rect = &((PangoAttrShape *)attr)->logical_rect;
6023           properties->shape_ink_rect = &((PangoAttrShape *)attr)->ink_rect;
6024           break;
6025 
6026         default:
6027           break;
6028         }
6029       tmp_list = tmp_list->next;
6030     }
6031 }
6032 
6033 static int
next_cluster_start(PangoGlyphString * gs,int cluster_start)6034 next_cluster_start (PangoGlyphString *gs,
6035                     int               cluster_start)
6036 {
6037   int i;
6038 
6039   i = cluster_start + 1;
6040   while (i < gs->num_glyphs)
6041     {
6042       if (gs->glyphs[i].attr.is_cluster_start)
6043         return i;
6044 
6045       i++;
6046     }
6047 
6048   return gs->num_glyphs;
6049 }
6050 
6051 static int
cluster_width(PangoGlyphString * gs,int cluster_start)6052 cluster_width (PangoGlyphString *gs,
6053                int               cluster_start)
6054 {
6055   int i;
6056   int width;
6057 
6058   width = gs->glyphs[cluster_start].geometry.width;
6059   i = cluster_start + 1;
6060   while (i < gs->num_glyphs)
6061     {
6062       if (gs->glyphs[i].attr.is_cluster_start)
6063         break;
6064 
6065       width += gs->glyphs[i].geometry.width;
6066       i++;
6067     }
6068 
6069   return width;
6070 }
6071 
6072 static inline void
offset_y(PangoLayoutIter * iter,int * y)6073 offset_y (PangoLayoutIter *iter,
6074           int             *y)
6075 {
6076   *y += iter->line_extents[iter->line_index].baseline;
6077 }
6078 
6079 /* Sets up the iter for the start of a new cluster. cluster_start_index
6080  * is the byte index of the cluster start relative to the run.
6081  */
6082 static void
update_cluster(PangoLayoutIter * iter,int cluster_start_index)6083 update_cluster (PangoLayoutIter *iter,
6084                 int              cluster_start_index)
6085 {
6086   char             *cluster_text;
6087   PangoGlyphString *gs;
6088   int               cluster_length;
6089 
6090   iter->character_position = 0;
6091 
6092   gs = iter->run->glyphs;
6093   iter->cluster_width = cluster_width (gs, iter->cluster_start);
6094   iter->next_cluster_glyph = next_cluster_start (gs, iter->cluster_start);
6095 
6096   if (iter->ltr)
6097     {
6098       /* For LTR text, finding the length of the cluster is easy
6099        * since logical and visual runs are in the same direction.
6100        */
6101       if (iter->next_cluster_glyph < gs->num_glyphs)
6102         cluster_length = gs->log_clusters[iter->next_cluster_glyph] - cluster_start_index;
6103       else
6104         cluster_length = iter->run->item->length - cluster_start_index;
6105     }
6106   else
6107     {
6108       /* For RTL text, we have to scan backwards to find the previous
6109        * visual cluster which is the next logical cluster.
6110        */
6111       int i = iter->cluster_start;
6112       while (i > 0 && gs->log_clusters[i - 1] == cluster_start_index)
6113         i--;
6114 
6115       if (i == 0)
6116         cluster_length = iter->run->item->length - cluster_start_index;
6117       else
6118         cluster_length = gs->log_clusters[i - 1] - cluster_start_index;
6119     }
6120 
6121   cluster_text = iter->layout->text + iter->run->item->offset + cluster_start_index;
6122   iter->cluster_num_chars = pango_utf8_strlen (cluster_text, cluster_length);
6123 
6124   if (iter->ltr)
6125     iter->index = cluster_text - iter->layout->text;
6126   else
6127     iter->index = g_utf8_prev_char (cluster_text + cluster_length) - iter->layout->text;
6128 }
6129 
6130 static void
update_run(PangoLayoutIter * iter,int run_start_index)6131 update_run (PangoLayoutIter *iter,
6132             int              run_start_index)
6133 {
6134   const Extents *line_ext = &iter->line_extents[iter->line_index];
6135 
6136   /* Note that in iter_new() the iter->run_width
6137    * is garbage but we don't use it since we're on the first run of
6138    * a line.
6139    */
6140   if (iter->run_list_link == iter->line->runs)
6141     iter->run_x = line_ext->logical_rect.x;
6142   else
6143     iter->run_x += iter->run_width;
6144 
6145   if (iter->run)
6146     {
6147       iter->run_width = pango_glyph_string_get_width (iter->run->glyphs);
6148     }
6149   else
6150     {
6151       /* The empty run at the end of a line */
6152       iter->run_width = 0;
6153     }
6154 
6155   if (iter->run)
6156     iter->ltr = (iter->run->item->analysis.level % 2) == 0;
6157   else
6158     iter->ltr = TRUE;
6159 
6160   iter->cluster_start = 0;
6161   iter->cluster_x = iter->run_x;
6162 
6163   if (iter->run)
6164     {
6165       update_cluster (iter, iter->run->glyphs->log_clusters[0]);
6166     }
6167   else
6168     {
6169       iter->cluster_width = 0;
6170       iter->character_position = 0;
6171       iter->cluster_num_chars = 0;
6172       iter->index = run_start_index;
6173     }
6174 }
6175 
6176 /**
6177  * pango_layout_iter_copy:
6178  * @iter: (nullable): a `PangoLayoutIter`
6179  *
6180  * Copies a `PangoLayoutIter`.
6181  *
6182  * Return value: (nullable): the newly allocated `PangoLayoutIter`
6183  *
6184  * Since: 1.20
6185  */
6186 PangoLayoutIter *
pango_layout_iter_copy(PangoLayoutIter * iter)6187 pango_layout_iter_copy (PangoLayoutIter *iter)
6188 {
6189   PangoLayoutIter *new;
6190 
6191   if (iter == NULL)
6192     return NULL;
6193 
6194   new = g_slice_new (PangoLayoutIter);
6195 
6196   new->layout = g_object_ref (iter->layout);
6197   new->line_list_link = iter->line_list_link;
6198   new->line = iter->line;
6199   pango_layout_line_ref (new->line);
6200 
6201   new->run_list_link = iter->run_list_link;
6202   new->run = iter->run;
6203   new->index = iter->index;
6204 
6205   new->line_extents = NULL;
6206   if (iter->line_extents != NULL)
6207     {
6208       new->line_extents = g_memdup2 (iter->line_extents,
6209                                      iter->layout->line_count * sizeof (Extents));
6210 
6211     }
6212   new->line_index = iter->line_index;
6213 
6214   new->run_x = iter->run_x;
6215   new->run_width = iter->run_width;
6216   new->ltr = iter->ltr;
6217 
6218   new->cluster_x = iter->cluster_x;
6219   new->cluster_width = iter->cluster_width;
6220 
6221   new->cluster_start = iter->cluster_start;
6222   new->next_cluster_glyph = iter->next_cluster_glyph;
6223 
6224   new->cluster_num_chars = iter->cluster_num_chars;
6225   new->character_position = iter->character_position;
6226 
6227   new->layout_width = iter->layout_width;
6228 
6229   return new;
6230 }
6231 
6232 G_DEFINE_BOXED_TYPE (PangoLayoutIter, pango_layout_iter,
6233                      pango_layout_iter_copy,
6234                      pango_layout_iter_free);
6235 
6236 /**
6237  * pango_layout_get_iter:
6238  * @layout: a `PangoLayout`
6239  *
6240  * Returns an iterator to iterate over the visual extents of the layout.
6241  *
6242  * Return value: the new `PangoLayoutIter`
6243  */
6244 PangoLayoutIter*
pango_layout_get_iter(PangoLayout * layout)6245 pango_layout_get_iter (PangoLayout *layout)
6246 {
6247   PangoLayoutIter *iter;
6248 
6249   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
6250 
6251   iter = g_slice_new (PangoLayoutIter);
6252 
6253   _pango_layout_get_iter (layout, iter);
6254 
6255   return iter;
6256 }
6257 
6258 void
_pango_layout_get_iter(PangoLayout * layout,PangoLayoutIter * iter)6259 _pango_layout_get_iter (PangoLayout    *layout,
6260                         PangoLayoutIter*iter)
6261 {
6262   int run_start_index;
6263 
6264   g_return_if_fail (PANGO_IS_LAYOUT (layout));
6265 
6266   iter->layout = g_object_ref (layout);
6267 
6268   pango_layout_check_lines (layout);
6269 
6270   iter->line_list_link = layout->lines;
6271   iter->line = iter->line_list_link->data;
6272   pango_layout_line_ref (iter->line);
6273 
6274   run_start_index = iter->line->start_index;
6275   iter->run_list_link = iter->line->runs;
6276 
6277   if (iter->run_list_link)
6278     {
6279       iter->run = iter->run_list_link->data;
6280       run_start_index = iter->run->item->offset;
6281     }
6282   else
6283     iter->run = NULL;
6284 
6285   iter->line_extents = NULL;
6286 
6287   if (layout->width == -1)
6288     {
6289       PangoRectangle logical_rect;
6290 
6291       pango_layout_get_extents_internal (layout,
6292                                          NULL,
6293                                          &logical_rect,
6294                                          &iter->line_extents);
6295       iter->layout_width = logical_rect.width;
6296     }
6297   else
6298     {
6299       pango_layout_get_extents_internal (layout,
6300                                          NULL,
6301                                          NULL,
6302                                          &iter->line_extents);
6303       iter->layout_width = layout->width;
6304     }
6305   iter->line_index = 0;
6306 
6307   update_run (iter, run_start_index);
6308 }
6309 
6310 void
_pango_layout_iter_destroy(PangoLayoutIter * iter)6311 _pango_layout_iter_destroy (PangoLayoutIter *iter)
6312 {
6313   if (iter == NULL)
6314     return;
6315 
6316   g_free (iter->line_extents);
6317   pango_layout_line_unref (iter->line);
6318   g_object_unref (iter->layout);
6319 }
6320 
6321 /**
6322  * pango_layout_iter_free:
6323  * @iter: (nullable): a `PangoLayoutIter`, may be %NULL
6324  *
6325  * Frees an iterator that's no longer in use.
6326  **/
6327 void
pango_layout_iter_free(PangoLayoutIter * iter)6328 pango_layout_iter_free (PangoLayoutIter *iter)
6329 {
6330   if (iter == NULL)
6331     return;
6332 
6333   _pango_layout_iter_destroy (iter);
6334   g_slice_free (PangoLayoutIter, iter);
6335 }
6336 
6337 /**
6338  * pango_layout_iter_get_index:
6339  * @iter: a `PangoLayoutIter`
6340  *
6341  * Gets the current byte index.
6342  *
6343  * Note that iterating forward by char moves in visual order,
6344  * not logical order, so indexes may not be sequential. Also,
6345  * the index may be equal to the length of the text in the
6346  * layout, if on the %NULL run (see [method@Pango.LayoutIter.get_run]).
6347  *
6348  * Return value: current byte index
6349  */
6350 int
pango_layout_iter_get_index(PangoLayoutIter * iter)6351 pango_layout_iter_get_index (PangoLayoutIter *iter)
6352 {
6353   if (ITER_IS_INVALID (iter))
6354     return 0;
6355 
6356   return iter->index;
6357 }
6358 
6359 /**
6360  * pango_layout_iter_get_run:
6361  * @iter: a `PangoLayoutIter`
6362  *
6363  * Gets the current run.
6364  *
6365  * When iterating by run, at the end of each line, there's a position
6366  * with a %NULL run, so this function can return %NULL. The %NULL run
6367  * at the end of each line ensures that all lines have at least one run,
6368  * even lines consisting of only a newline.
6369  *
6370  * Use the faster [method@Pango.LayoutIter.get_run_readonly] if you do not
6371  * plan to modify the contents of the run (glyphs, glyph widths, etc.).
6372  *
6373  * Return value: (transfer none) (nullable): the current run
6374  */
6375 PangoLayoutRun*
pango_layout_iter_get_run(PangoLayoutIter * iter)6376 pango_layout_iter_get_run (PangoLayoutIter *iter)
6377 {
6378   if (ITER_IS_INVALID (iter))
6379     return NULL;
6380 
6381   pango_layout_line_leaked (iter->line);
6382 
6383   return iter->run;
6384 }
6385 
6386 /**
6387  * pango_layout_iter_get_run_readonly:
6388  * @iter: a `PangoLayoutIter`
6389  *
6390  * Gets the current run for read-only access.
6391  *
6392  * When iterating by run, at the end of each line, there's a position
6393  * with a %NULL run, so this function can return %NULL. The %NULL run
6394  * at the end of each line ensures that all lines have at least one run,
6395  * even lines consisting of only a newline.
6396  *
6397  * This is a faster alternative to [method@Pango.LayoutIter.get_run],
6398  * but the user is not expected to modify the contents of the run (glyphs,
6399  * glyph widths, etc.).
6400  *
6401  * Return value: (transfer none) (nullable): the current run, that
6402  *   should not be modified
6403  *
6404  * Since: 1.16
6405  */
6406 PangoLayoutRun*
pango_layout_iter_get_run_readonly(PangoLayoutIter * iter)6407 pango_layout_iter_get_run_readonly (PangoLayoutIter *iter)
6408 {
6409   if (ITER_IS_INVALID (iter))
6410     return NULL;
6411 
6412   pango_layout_line_leaked (iter->line);
6413 
6414   return iter->run;
6415 }
6416 
6417 /* an inline-able version for local use */
6418 static PangoLayoutLine*
_pango_layout_iter_get_line(PangoLayoutIter * iter)6419 _pango_layout_iter_get_line (PangoLayoutIter *iter)
6420 {
6421   return iter->line;
6422 }
6423 
6424 /**
6425  * pango_layout_iter_get_line:
6426  * @iter: a `PangoLayoutIter`
6427  *
6428  * Gets the current line.
6429  *
6430  * Use the faster [method@Pango.LayoutIter.get_line_readonly] if
6431  * you do not plan to modify the contents of the line (glyphs,
6432  * glyph widths, etc.).
6433  *
6434  * Return value: (transfer none): the current line
6435  */
6436 PangoLayoutLine*
pango_layout_iter_get_line(PangoLayoutIter * iter)6437 pango_layout_iter_get_line (PangoLayoutIter *iter)
6438 {
6439   if (ITER_IS_INVALID (iter))
6440     return NULL;
6441 
6442   pango_layout_line_leaked (iter->line);
6443 
6444   return iter->line;
6445 }
6446 
6447 /**
6448  * pango_layout_iter_get_line_readonly:
6449  * @iter: a `PangoLayoutIter`
6450  *
6451  * Gets the current line for read-only access.
6452  *
6453  * This is a faster alternative to [method@Pango.LayoutIter.get_line],
6454  * but the user is not expected to modify the contents of the line
6455  * (glyphs, glyph widths, etc.).
6456  *
6457  * Return value: (transfer none): the current line, that should not be
6458  *   modified
6459  *
6460  * Since: 1.16
6461  */
6462 PangoLayoutLine*
pango_layout_iter_get_line_readonly(PangoLayoutIter * iter)6463 pango_layout_iter_get_line_readonly (PangoLayoutIter *iter)
6464 {
6465   if (ITER_IS_INVALID (iter))
6466     return NULL;
6467 
6468   return iter->line;
6469 }
6470 
6471 /**
6472  * pango_layout_iter_at_last_line:
6473  * @iter: a `PangoLayoutIter`
6474  *
6475  * Determines whether @iter is on the last line of the layout.
6476  *
6477  * Return value: %TRUE if @iter is on the last line
6478  */
6479 gboolean
pango_layout_iter_at_last_line(PangoLayoutIter * iter)6480 pango_layout_iter_at_last_line (PangoLayoutIter *iter)
6481 {
6482   if (ITER_IS_INVALID (iter))
6483     return FALSE;
6484 
6485   return iter->line_index == iter->layout->line_count - 1;
6486 }
6487 
6488 /**
6489  * pango_layout_iter_get_layout:
6490  * @iter: a `PangoLayoutIter`
6491  *
6492  * Gets the layout associated with a `PangoLayoutIter`.
6493  *
6494  * Return value: (transfer none): the layout associated with @iter
6495  *
6496  * Since: 1.20
6497  */
6498 PangoLayout*
pango_layout_iter_get_layout(PangoLayoutIter * iter)6499 pango_layout_iter_get_layout (PangoLayoutIter *iter)
6500 {
6501   /* check is redundant as it simply checks that iter->layout is not NULL */
6502   if (ITER_IS_INVALID (iter))
6503     return NULL;
6504 
6505   return iter->layout;
6506 }
6507 
6508 static gboolean
line_is_terminated(PangoLayoutIter * iter)6509 line_is_terminated (PangoLayoutIter *iter)
6510 {
6511   /* There is a real terminator at the end of each paragraph other
6512    * than the last.
6513    */
6514   if (iter->line_list_link->next)
6515     {
6516       PangoLayoutLine *next_line = iter->line_list_link->next->data;
6517       if (next_line->is_paragraph_start)
6518         return TRUE;
6519     }
6520 
6521   return FALSE;
6522 }
6523 
6524 /* Moves to the next non-empty line. If @include_terminators
6525  * is set, a line with just an explicit paragraph separator
6526  * is considered non-empty.
6527  */
6528 static gboolean
next_nonempty_line(PangoLayoutIter * iter,gboolean include_terminators)6529 next_nonempty_line (PangoLayoutIter *iter,
6530                     gboolean         include_terminators)
6531 {
6532   gboolean result;
6533 
6534   while (TRUE)
6535     {
6536       result = pango_layout_iter_next_line (iter);
6537       if (!result)
6538         break;
6539 
6540       if (iter->line->runs)
6541         break;
6542 
6543       if (include_terminators && line_is_terminated (iter))
6544         break;
6545     }
6546 
6547   return result;
6548 }
6549 
6550 /* Moves to the next non-empty run. If @include_terminators
6551  * is set, the trailing run at the end of a line with an explicit
6552  * paragraph separator is considered non-empty.
6553  */
6554 static gboolean
next_nonempty_run(PangoLayoutIter * iter,gboolean include_terminators)6555 next_nonempty_run (PangoLayoutIter *iter,
6556                     gboolean         include_terminators)
6557 {
6558   gboolean result;
6559 
6560   while (TRUE)
6561     {
6562       result = pango_layout_iter_next_run (iter);
6563       if (!result)
6564         break;
6565 
6566       if (iter->run)
6567         break;
6568 
6569       if (include_terminators && line_is_terminated (iter))
6570         break;
6571     }
6572 
6573   return result;
6574 }
6575 
6576 /* Like pango_layout_next_cluster(), but if @include_terminators
6577  * is set, includes the fake runs/clusters for empty lines.
6578  * (But not positions introduced by line wrapping).
6579  */
6580 static gboolean
next_cluster_internal(PangoLayoutIter * iter,gboolean include_terminators)6581 next_cluster_internal (PangoLayoutIter *iter,
6582                        gboolean         include_terminators)
6583 {
6584   PangoGlyphString *gs;
6585   int               next_start;
6586 
6587   if (ITER_IS_INVALID (iter))
6588     return FALSE;
6589 
6590   if (iter->run == NULL)
6591     return next_nonempty_line (iter, include_terminators);
6592 
6593   gs = iter->run->glyphs;
6594 
6595   next_start = iter->next_cluster_glyph;
6596   if (next_start == gs->num_glyphs)
6597     {
6598       return next_nonempty_run (iter, include_terminators);
6599     }
6600   else
6601     {
6602       iter->cluster_start = next_start;
6603       iter->cluster_x += iter->cluster_width;
6604       update_cluster(iter, gs->log_clusters[iter->cluster_start]);
6605 
6606       return TRUE;
6607     }
6608 }
6609 
6610 /**
6611  * pango_layout_iter_next_char:
6612  * @iter: a `PangoLayoutIter`
6613  *
6614  * Moves @iter forward to the next character in visual order.
6615  *
6616  * If @iter was already at the end of the layout, returns %FALSE.
6617  *
6618  * Return value: whether motion was possible
6619  */
6620 gboolean
pango_layout_iter_next_char(PangoLayoutIter * iter)6621 pango_layout_iter_next_char (PangoLayoutIter *iter)
6622 {
6623   const char *text;
6624 
6625   if (ITER_IS_INVALID (iter))
6626     return FALSE;
6627 
6628   if (iter->run == NULL)
6629     {
6630       /* We need to fake an iterator position in the middle of a \r\n line terminator */
6631       if (line_is_terminated (iter) &&
6632           strncmp (iter->layout->text + iter->line->start_index + iter->line->length, "\r\n", 2) == 0 &&
6633           iter->character_position == 0)
6634         {
6635           iter->character_position++;
6636           return TRUE;
6637         }
6638 
6639       return next_nonempty_line (iter, TRUE);
6640     }
6641 
6642   iter->character_position++;
6643   if (iter->character_position >= iter->cluster_num_chars)
6644     return next_cluster_internal (iter, TRUE);
6645 
6646   text = iter->layout->text;
6647   if (iter->ltr)
6648     iter->index = g_utf8_next_char (text + iter->index) - text;
6649   else
6650     iter->index = g_utf8_prev_char (text + iter->index) - text;
6651 
6652   return TRUE;
6653 }
6654 
6655 /**
6656  * pango_layout_iter_next_cluster:
6657  * @iter: a `PangoLayoutIter`
6658  *
6659  * Moves @iter forward to the next cluster in visual order.
6660  *
6661  * If @iter was already at the end of the layout, returns %FALSE.
6662  *
6663  * Return value: whether motion was possible
6664  */
6665 gboolean
pango_layout_iter_next_cluster(PangoLayoutIter * iter)6666 pango_layout_iter_next_cluster (PangoLayoutIter *iter)
6667 {
6668   return next_cluster_internal (iter, FALSE);
6669 }
6670 
6671 /**
6672  * pango_layout_iter_next_run:
6673  * @iter: a `PangoLayoutIter`
6674  *
6675  * Moves @iter forward to the next run in visual order.
6676  *
6677  * If @iter was already at the end of the layout, returns %FALSE.
6678  *
6679  * Return value: whether motion was possible
6680  */
6681 gboolean
pango_layout_iter_next_run(PangoLayoutIter * iter)6682 pango_layout_iter_next_run (PangoLayoutIter *iter)
6683 {
6684   int next_run_start; /* byte index */
6685   GSList *next_link;
6686 
6687   if (ITER_IS_INVALID (iter))
6688     return FALSE;
6689 
6690   if (iter->run == NULL)
6691     return pango_layout_iter_next_line (iter);
6692 
6693   next_link = iter->run_list_link->next;
6694 
6695   if (next_link == NULL)
6696     {
6697       /* Moving on to the zero-width "virtual run" at the end of each
6698        * line
6699        */
6700       next_run_start = iter->run->item->offset + iter->run->item->length;
6701       iter->run = NULL;
6702       iter->run_list_link = NULL;
6703     }
6704   else
6705     {
6706       iter->run_list_link = next_link;
6707       iter->run = iter->run_list_link->data;
6708       next_run_start = iter->run->item->offset;
6709     }
6710 
6711   update_run (iter, next_run_start);
6712 
6713   return TRUE;
6714 }
6715 
6716 /**
6717  * pango_layout_iter_next_line:
6718  * @iter: a `PangoLayoutIter`
6719  *
6720  * Moves @iter forward to the start of the next line.
6721  *
6722  * If @iter is already on the last line, returns %FALSE.
6723  *
6724  * Return value: whether motion was possible
6725  */
6726 gboolean
pango_layout_iter_next_line(PangoLayoutIter * iter)6727 pango_layout_iter_next_line (PangoLayoutIter *iter)
6728 {
6729   GSList *next_link;
6730 
6731   if (ITER_IS_INVALID (iter))
6732     return FALSE;
6733 
6734   next_link = iter->line_list_link->next;
6735 
6736   if (next_link == NULL)
6737     return FALSE;
6738 
6739   iter->line_list_link = next_link;
6740 
6741   pango_layout_line_unref (iter->line);
6742 
6743   iter->line = iter->line_list_link->data;
6744 
6745   pango_layout_line_ref (iter->line);
6746 
6747   iter->run_list_link = iter->line->runs;
6748 
6749   if (iter->run_list_link)
6750     iter->run = iter->run_list_link->data;
6751   else
6752     iter->run = NULL;
6753 
6754   iter->line_index ++;
6755 
6756   update_run (iter, iter->line->start_index);
6757 
6758   return TRUE;
6759 }
6760 
6761 /**
6762  * pango_layout_iter_get_char_extents:
6763  * @iter: a `PangoLayoutIter`
6764  * @logical_rect: (out caller-allocates): rectangle to fill with
6765  *   logical extents
6766  *
6767  * Gets the extents of the current character, in layout coordinates.
6768  *
6769  * Layout coordinates have the origin at the top left of the entire layout.
6770  *
6771  * Only logical extents can sensibly be obtained for characters;
6772  * ink extents make sense only down to the level of clusters.
6773  */
6774 void
pango_layout_iter_get_char_extents(PangoLayoutIter * iter,PangoRectangle * logical_rect)6775 pango_layout_iter_get_char_extents (PangoLayoutIter *iter,
6776                                     PangoRectangle  *logical_rect)
6777 {
6778   PangoRectangle cluster_rect;
6779   int            x0, x1;
6780 
6781   if (ITER_IS_INVALID (iter))
6782     return;
6783 
6784   if (logical_rect == NULL)
6785     return;
6786 
6787   pango_layout_iter_get_cluster_extents (iter, NULL, &cluster_rect);
6788 
6789   if (iter->run == NULL)
6790     {
6791       /* When on the NULL run, cluster, char, and run all have the
6792        * same extents
6793        */
6794       *logical_rect = cluster_rect;
6795       return;
6796     }
6797 
6798   if (iter->cluster_num_chars)
6799   {
6800     x0 = (iter->character_position * cluster_rect.width) / iter->cluster_num_chars;
6801     x1 = ((iter->character_position + 1) * cluster_rect.width) / iter->cluster_num_chars;
6802   }
6803   else
6804   {
6805     x0 = x1 = 0;
6806   }
6807 
6808   logical_rect->width = x1 - x0;
6809   logical_rect->height = cluster_rect.height;
6810   logical_rect->y = cluster_rect.y;
6811   logical_rect->x = cluster_rect.x + x0;
6812 }
6813 
6814 /**
6815  * pango_layout_iter_get_cluster_extents:
6816  * @iter: a `PangoLayoutIter`
6817  * @ink_rect: (out) (optional): rectangle to fill with ink extents
6818  * @logical_rect: (out) (optional): rectangle to fill with logical extents
6819  *
6820  * Gets the extents of the current cluster, in layout coordinates.
6821  *
6822  * Layout coordinates have the origin at the top left of the entire layout.
6823  */
6824 void
pango_layout_iter_get_cluster_extents(PangoLayoutIter * iter,PangoRectangle * ink_rect,PangoRectangle * logical_rect)6825 pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter,
6826                                        PangoRectangle  *ink_rect,
6827                                        PangoRectangle  *logical_rect)
6828 {
6829   if (ITER_IS_INVALID (iter))
6830     return;
6831 
6832   if (iter->run == NULL)
6833     {
6834       /* When on the NULL run, cluster, char, and run all have the
6835        * same extents
6836        */
6837       pango_layout_iter_get_run_extents (iter, ink_rect, logical_rect);
6838       return;
6839     }
6840 
6841   pango_glyph_string_extents_range (iter->run->glyphs,
6842                                     iter->cluster_start,
6843                                     iter->next_cluster_glyph,
6844                                     iter->run->item->analysis.font,
6845                                     ink_rect,
6846                                     logical_rect);
6847 
6848   if (ink_rect)
6849     {
6850       ink_rect->x += iter->cluster_x;
6851       offset_y (iter, &ink_rect->y);
6852     }
6853 
6854   if (logical_rect)
6855     {
6856       g_assert (logical_rect->width == iter->cluster_width);
6857       logical_rect->x += iter->cluster_x;
6858       offset_y (iter, &logical_rect->y);
6859     }
6860 }
6861 
6862 /**
6863  * pango_layout_iter_get_run_extents:
6864  * @iter: a `PangoLayoutIter`
6865  * @ink_rect: (out) (optional): rectangle to fill with ink extents
6866  * @logical_rect: (out) (optional): rectangle to fill with logical extents
6867  *
6868  * Gets the extents of the current run in layout coordinates.
6869  *
6870  * Layout coordinates have the origin at the top left of the entire layout.
6871  */
6872 void
pango_layout_iter_get_run_extents(PangoLayoutIter * iter,PangoRectangle * ink_rect,PangoRectangle * logical_rect)6873 pango_layout_iter_get_run_extents (PangoLayoutIter *iter,
6874                                    PangoRectangle  *ink_rect,
6875                                    PangoRectangle  *logical_rect)
6876 {
6877   if (G_UNLIKELY (!ink_rect && !logical_rect))
6878     return;
6879 
6880   if (ITER_IS_INVALID (iter))
6881     return;
6882 
6883   if (iter->run)
6884     {
6885       pango_layout_run_get_extents_and_height (iter->run, ink_rect, logical_rect, NULL);
6886 
6887       if (ink_rect)
6888         {
6889           offset_y (iter, &ink_rect->y);
6890           ink_rect->x += iter->run_x;
6891         }
6892 
6893       if (logical_rect)
6894         {
6895           offset_y (iter, &logical_rect->y);
6896           logical_rect->x += iter->run_x;
6897         }
6898     }
6899   else
6900     {
6901       /* The empty run at the end of a line */
6902 
6903       pango_layout_iter_get_line_extents (iter, ink_rect, logical_rect);
6904 
6905       if (ink_rect)
6906         {
6907           ink_rect->x = iter->run_x;
6908           ink_rect->width = 0;
6909         }
6910 
6911       if (logical_rect)
6912         {
6913           logical_rect->x = iter->run_x;
6914           logical_rect->width = 0;
6915         }
6916     }
6917 }
6918 
6919 /**
6920  * pango_layout_iter_get_line_extents:
6921  * @iter: a `PangoLayoutIter`
6922  * @ink_rect: (out) (optional): rectangle to fill with ink extents
6923  * @logical_rect: (out) (optional): rectangle to fill with logical extents
6924  *
6925  * Obtains the extents of the current line.
6926  *
6927  * Extents are in layout coordinates (origin is the top-left corner
6928  * of the entire `PangoLayout`). Thus the extents returned by this
6929  * function will be the same width/height but not at the same x/y
6930  * as the extents returned from [method@Pango.LayoutLine.get_extents].
6931  */
6932 void
pango_layout_iter_get_line_extents(PangoLayoutIter * iter,PangoRectangle * ink_rect,PangoRectangle * logical_rect)6933 pango_layout_iter_get_line_extents (PangoLayoutIter *iter,
6934                                     PangoRectangle  *ink_rect,
6935                                     PangoRectangle  *logical_rect)
6936 {
6937   const Extents *ext;
6938 
6939   if (ITER_IS_INVALID (iter))
6940     return;
6941 
6942   ext = &iter->line_extents[iter->line_index];
6943 
6944   if (ink_rect)
6945     {
6946       get_line_extents_layout_coords (iter->layout, iter->line,
6947                                       iter->layout_width,
6948                                       ext->logical_rect.y,
6949                                       NULL,
6950                                       ink_rect,
6951                                       NULL);
6952     }
6953 
6954   if (logical_rect)
6955     *logical_rect = ext->logical_rect;
6956 }
6957 
6958 /**
6959  * pango_layout_iter_get_line_yrange:
6960  * @iter: a `PangoLayoutIter`
6961  * @y0_: (out) (optional): start of line
6962  * @y1_: (out) (optional): end of line
6963  *
6964  * Divides the vertical space in the `PangoLayout` being iterated over
6965  * between the lines in the layout, and returns the space belonging to
6966  * the current line.
6967  *
6968  * A line's range includes the line's logical extents. plus half of the
6969  * spacing above and below the line, if [method@Pango.Layout.set_spacing]
6970  * has been called to set layout spacing. The Y positions are in layout
6971  * coordinates (origin at top left of the entire layout).
6972  *
6973  * Note: Since 1.44, Pango uses line heights for placing lines, and there
6974  * may be gaps between the ranges returned by this function.
6975  */
6976 void
pango_layout_iter_get_line_yrange(PangoLayoutIter * iter,int * y0,int * y1)6977 pango_layout_iter_get_line_yrange (PangoLayoutIter *iter,
6978                                    int             *y0,
6979                                    int             *y1)
6980 {
6981   const Extents *ext;
6982   int half_spacing;
6983 
6984   if (ITER_IS_INVALID (iter))
6985     return;
6986 
6987   ext = &iter->line_extents[iter->line_index];
6988 
6989   half_spacing = iter->layout->spacing / 2;
6990 
6991   /* Note that if layout->spacing is odd, the remainder spacing goes
6992    * above the line (this is pretty arbitrary of course)
6993    */
6994 
6995   if (y0)
6996     {
6997       /* No spacing above the first line */
6998 
6999       if (iter->line_index == 0)
7000         *y0 = ext->logical_rect.y;
7001       else
7002         *y0 = ext->logical_rect.y - (iter->layout->spacing - half_spacing);
7003     }
7004 
7005   if (y1)
7006     {
7007       /* No spacing below the last line */
7008       if (iter->line_index == iter->layout->line_count - 1)
7009         *y1 = ext->logical_rect.y + ext->logical_rect.height;
7010       else
7011         *y1 = ext->logical_rect.y + ext->logical_rect.height + half_spacing;
7012     }
7013 }
7014 
7015 /**
7016  * pango_layout_iter_get_baseline:
7017  * @iter: a `PangoLayoutIter`
7018  *
7019  * Gets the Y position of the current line's baseline, in layout
7020  * coordinates.
7021  *
7022  * Layout coordinates have the origin at the top left of the entire layout.
7023  *
7024  * Return value: baseline of current line
7025  */
7026 int
pango_layout_iter_get_baseline(PangoLayoutIter * iter)7027 pango_layout_iter_get_baseline (PangoLayoutIter *iter)
7028 {
7029   if (ITER_IS_INVALID (iter))
7030     return 0;
7031 
7032   return iter->line_extents[iter->line_index].baseline;
7033 }
7034 
7035 /**
7036  * pango_layout_iter_get_layout_extents:
7037  * @iter: a `PangoLayoutIter`
7038  * @ink_rect: (out) (optional): rectangle to fill with ink extents
7039  * @logical_rect: (out) (optional): rectangle to fill with logical extents
7040  *
7041  * Obtains the extents of the `PangoLayout` being iterated over.
7042  */
7043 void
pango_layout_iter_get_layout_extents(PangoLayoutIter * iter,PangoRectangle * ink_rect,PangoRectangle * logical_rect)7044 pango_layout_iter_get_layout_extents (PangoLayoutIter *iter,
7045                                       PangoRectangle  *ink_rect,
7046                                       PangoRectangle  *logical_rect)
7047 {
7048   if (ITER_IS_INVALID (iter))
7049     return;
7050 
7051   pango_layout_get_extents (iter->layout, ink_rect, logical_rect);
7052 }
7053