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