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