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

source code of gtk/subprojects/pango/pango/pango-layout.c