1/* GTK - The GIMP Toolkit
2 * gtktextlayout.c - calculate the layout of the text
3 *
4 * Copyright (c) 1992-1994 The Regents of the University of California.
5 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
6 * Copyright (c) 2000 Red Hat, Inc.
7 * Tk->Gtk port by Havoc Pennington
8 * Pango support by Owen Taylor
9 *
10 * This file can be used under your choice of two licenses, the LGPL
11 * and the original Tk license.
12 *
13 * LGPL:
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
27 *
28 * Original Tk license:
29 *
30 * This software is copyrighted by the Regents of the University of
31 * California, Sun Microsystems, Inc., and other parties. The
32 * following terms apply to all files associated with the software
33 * unless explicitly disclaimed in individual files.
34 *
35 * The authors hereby grant permission to use, copy, modify,
36 * distribute, and license this software and its documentation for any
37 * purpose, provided that existing copyright notices are retained in
38 * all copies and that this notice is included verbatim in any
39 * distributions. No written agreement, license, or royalty fee is
40 * required for any of the authorized uses. Modifications to this
41 * software may be copyrighted by their authors and need not follow
42 * the licensing terms described here, provided that the new terms are
43 * clearly indicated on the first page of each file where they apply.
44 *
45 * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY
46 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
47 * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION,
48 * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED
49 * OF THE POSSIBILITY OF SUCH DAMAGE.
50 *
51 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
52 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
54 * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
55 * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
56 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
57 *
58 * GOVERNMENT USE: If you are acquiring this software on behalf of the
59 * U.S. government, the Government shall have only "Restricted Rights"
60 * in the software and related documentation as defined in the Federal
61 * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
62 * are acquiring the software on behalf of the Department of Defense,
63 * the software shall be classified as "Commercial Computer Software"
64 * and the Government shall have only "Restricted Rights" as defined
65 * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the
66 * foregoing, the authors grant the U.S. Government and others acting
67 * in its behalf permission to use and distribute the software in
68 * accordance with the terms specified in this license.
69 *
70 */
71/*
72 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
73 * file for a list of people on the GTK+ Team. See the ChangeLog
74 * files for a list of changes. These files are distributed with
75 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
76 */
77
78#include "config.h"
79
80#include "gtktextlayoutprivate.h"
81
82#include "gtkcsscolorvalueprivate.h"
83#include "gtkmarshalers.h"
84#include "gtkstylecontextprivate.h"
85#include "gtktextbtree.h"
86#include "gtktextbufferprivate.h"
87#include "gtktextiterprivate.h"
88#include "gtktextlinedisplaycacheprivate.h"
89#include "gtktextutil.h"
90#include "gskpango.h"
91#include "gtkintl.h"
92#include "gtksnapshotprivate.h"
93#include "gtkwidgetprivate.h"
94#include "gtktextviewprivate.h"
95
96#include <stdlib.h>
97#include <string.h>
98
99#define GTK_TEXT_LAYOUT_GET_PRIVATE(o) ((GtkTextLayoutPrivate *) gtk_text_layout_get_instance_private ((o)))
100
101typedef struct _GtkTextLayoutPrivate GtkTextLayoutPrivate;
102
103struct _GtkTextLayoutPrivate
104{
105 /* Cache the line that the cursor is positioned on, as the keyboard
106 direction only influences the direction of the cursor line.
107 */
108 GtkTextLine *cursor_line;
109
110 /* Cache for GtkTextLineDisplay to reduce overhead creating layouts */
111 GtkTextLineDisplayCache *cache;
112};
113
114static void gtk_text_layout_invalidated (GtkTextLayout *layout);
115
116static void gtk_text_layout_invalidate_cache (GtkTextLayout *layout,
117 GtkTextLine *line,
118 gboolean cursors_only);
119static void gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout,
120 gboolean cursors_only);
121static void gtk_text_layout_emit_changed (GtkTextLayout *layout,
122 int y,
123 int old_height,
124 int new_height);
125
126static void gtk_text_layout_invalidate_all (GtkTextLayout *layout);
127
128static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance);
129
130static void gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer,
131 const GtkTextIter *location,
132 GtkTextMark *mark,
133 gpointer data);
134static void gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer,
135 GtkTextIter *iter,
136 char *str,
137 int len,
138 gpointer data);
139static void gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer,
140 GtkTextIter *start,
141 GtkTextIter *end,
142 gpointer data);
143static void gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer,
144 const GtkTextIter *location,
145 GtkTextMark *mark,
146 gpointer data);
147static void gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer,
148 GtkTextIter *iter,
149 char *str,
150 int len,
151 gpointer data);
152static void gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer,
153 GtkTextIter *start,
154 GtkTextIter *end,
155 gpointer data);
156
157
158static void gtk_text_layout_update_cursor_line (GtkTextLayout *layout);
159
160static void line_display_index_to_iter (GtkTextLayout *layout,
161 GtkTextLineDisplay*display,
162 GtkTextIter *iter,
163 int index,
164 int trailing);
165
166static int line_display_iter_to_index (GtkTextLayout *layout,
167 GtkTextLineDisplay*display,
168 const GtkTextIter *iter);
169
170enum {
171 INVALIDATED,
172 CHANGED,
173 ALLOCATE_CHILD,
174 LAST_SIGNAL
175};
176
177enum {
178 ARG_0,
179 LAST_ARG
180};
181
182#define PIXEL_BOUND(d) (((d) + PANGO_SCALE - 1) / PANGO_SCALE)
183
184static guint signals[LAST_SIGNAL] = { 0 };
185
186PangoAttrType gtk_text_attr_appearance_type = 0;
187
188G_DEFINE_TYPE_WITH_PRIVATE (GtkTextLayout, gtk_text_layout, G_TYPE_OBJECT)
189
190static void
191gtk_text_layout_dispose (GObject *object)
192{
193 GtkTextLayout *layout = GTK_TEXT_LAYOUT (object);
194 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
195
196 g_clear_pointer (&priv->cache, gtk_text_line_display_cache_free);
197
198 gtk_text_layout_set_buffer (layout, NULL);
199
200 if (layout->default_style != NULL)
201 {
202 gtk_text_attributes_unref (values: layout->default_style);
203 layout->default_style = NULL;
204 }
205
206 g_clear_object (&layout->ltr_context);
207 g_clear_object (&layout->rtl_context);
208
209 if (layout->preedit_attrs != NULL)
210 {
211 pango_attr_list_unref (list: layout->preedit_attrs);
212 layout->preedit_attrs = NULL;
213 }
214
215 G_OBJECT_CLASS (gtk_text_layout_parent_class)->dispose (object);
216}
217
218static void
219gtk_text_layout_finalize (GObject *object)
220{
221 GtkTextLayout *layout;
222
223 layout = GTK_TEXT_LAYOUT (object);
224
225 g_free (mem: layout->preedit_string);
226
227 G_OBJECT_CLASS (gtk_text_layout_parent_class)->finalize (object);
228}
229
230static void
231gtk_text_layout_class_init (GtkTextLayoutClass *klass)
232{
233 GObjectClass *object_class = G_OBJECT_CLASS (klass);
234
235 object_class->dispose = gtk_text_layout_dispose;
236 object_class->finalize = gtk_text_layout_finalize;
237
238 signals[INVALIDATED] =
239 g_signal_new (I_("invalidated"),
240 G_OBJECT_CLASS_TYPE (object_class),
241 signal_flags: G_SIGNAL_RUN_LAST,
242 class_offset: 0,
243 NULL, NULL,
244 NULL,
245 G_TYPE_NONE,
246 n_params: 0);
247
248 signals[CHANGED] =
249 g_signal_new (I_("changed"),
250 G_OBJECT_CLASS_TYPE (object_class),
251 signal_flags: G_SIGNAL_RUN_LAST,
252 class_offset: 0,
253 NULL, NULL,
254 c_marshaller: _gtk_marshal_VOID__INT_INT_INT,
255 G_TYPE_NONE,
256 n_params: 3,
257 G_TYPE_INT,
258 G_TYPE_INT,
259 G_TYPE_INT);
260 g_signal_set_va_marshaller (signal_id: signals[CHANGED], G_TYPE_FROM_CLASS (klass),
261 va_marshaller: _gtk_marshal_VOID__INT_INT_INTv);
262
263 signals[ALLOCATE_CHILD] =
264 g_signal_new (I_("allocate-child"),
265 G_OBJECT_CLASS_TYPE (object_class),
266 signal_flags: G_SIGNAL_RUN_LAST,
267 class_offset: 0,
268 NULL, NULL,
269 c_marshaller: _gtk_marshal_VOID__OBJECT_INT_INT,
270 G_TYPE_NONE,
271 n_params: 3,
272 G_TYPE_OBJECT,
273 G_TYPE_INT,
274 G_TYPE_INT);
275}
276
277static void
278gtk_text_layout_init (GtkTextLayout *text_layout)
279{
280 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (text_layout);
281
282 text_layout->cursor_visible = TRUE;
283 priv->cache = gtk_text_line_display_cache_new ();
284}
285
286GtkTextLayout*
287gtk_text_layout_new (void)
288{
289 return g_object_new (GTK_TYPE_TEXT_LAYOUT, NULL);
290}
291
292static void
293free_style_cache (GtkTextLayout *text_layout)
294{
295 if (text_layout->one_style_cache)
296 {
297 gtk_text_attributes_unref (values: text_layout->one_style_cache);
298 text_layout->one_style_cache = NULL;
299 }
300}
301
302/*
303 * gtk_text_layout_set_buffer:
304 * @buffer: (nullable):
305 */
306void
307gtk_text_layout_set_buffer (GtkTextLayout *layout,
308 GtkTextBuffer *buffer)
309{
310 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
311 g_return_if_fail (buffer == NULL || GTK_IS_TEXT_BUFFER (buffer));
312
313 if (layout->buffer == buffer)
314 return;
315
316 free_style_cache (text_layout: layout);
317
318 if (layout->buffer)
319 {
320 _gtk_text_btree_remove_view (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
321 view_id: layout);
322
323 g_signal_handlers_disconnect_by_func (layout->buffer,
324 G_CALLBACK (gtk_text_layout_after_mark_set_handler),
325 layout);
326 g_signal_handlers_disconnect_by_func (layout->buffer,
327 G_CALLBACK (gtk_text_layout_after_buffer_insert_text),
328 layout);
329 g_signal_handlers_disconnect_by_func (layout->buffer,
330 G_CALLBACK (gtk_text_layout_after_buffer_delete_range),
331 layout);
332
333 g_signal_handlers_disconnect_by_func (layout->buffer,
334 G_CALLBACK (gtk_text_layout_before_mark_set_handler),
335 layout);
336 g_signal_handlers_disconnect_by_func (layout->buffer,
337 G_CALLBACK (gtk_text_layout_before_buffer_insert_text),
338 layout);
339 g_signal_handlers_disconnect_by_func (layout->buffer,
340 G_CALLBACK (gtk_text_layout_before_buffer_delete_range),
341 layout);
342
343 g_object_unref (object: layout->buffer);
344 layout->buffer = NULL;
345 }
346
347 if (buffer)
348 {
349 layout->buffer = buffer;
350
351 g_object_ref (buffer);
352
353 _gtk_text_btree_add_view (tree: _gtk_text_buffer_get_btree (buffer), layout);
354
355 /* Bind to all signals that move the insert mark. */
356 g_signal_connect_after (layout->buffer, "mark-set",
357 G_CALLBACK (gtk_text_layout_after_mark_set_handler), layout);
358 g_signal_connect_after (layout->buffer, "insert-text",
359 G_CALLBACK (gtk_text_layout_after_buffer_insert_text), layout);
360 g_signal_connect_after (layout->buffer, "delete-range",
361 G_CALLBACK (gtk_text_layout_after_buffer_delete_range), layout);
362
363 g_signal_connect (layout->buffer, "mark-set",
364 G_CALLBACK (gtk_text_layout_before_mark_set_handler), layout);
365 g_signal_connect (layout->buffer, "insert-text",
366 G_CALLBACK (gtk_text_layout_before_buffer_insert_text), layout);
367 g_signal_connect (layout->buffer, "delete-range",
368 G_CALLBACK (gtk_text_layout_before_buffer_delete_range), layout);
369
370 gtk_text_layout_update_cursor_line (layout);
371 }
372}
373
374void
375gtk_text_layout_default_style_changed (GtkTextLayout *layout)
376{
377 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
378
379 DV (g_print ("invalidating all due to default style change (%s)\n", G_STRLOC));
380 gtk_text_layout_invalidate_all (layout);
381}
382
383void
384gtk_text_layout_set_default_style (GtkTextLayout *layout,
385 GtkTextAttributes *values)
386{
387 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
388 g_return_if_fail (values != NULL);
389
390 if (values == layout->default_style)
391 return;
392
393 gtk_text_attributes_ref (values);
394
395 if (layout->default_style)
396 gtk_text_attributes_unref (values: layout->default_style);
397
398 layout->default_style = values;
399
400 gtk_text_layout_default_style_changed (layout);
401}
402
403void
404gtk_text_layout_set_contexts (GtkTextLayout *layout,
405 PangoContext *ltr_context,
406 PangoContext *rtl_context)
407{
408 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
409
410 if (layout->ltr_context != ltr_context)
411 {
412 if (layout->ltr_context)
413 g_object_unref (object: layout->ltr_context);
414
415 layout->ltr_context = ltr_context;
416 g_object_ref (layout->ltr_context);
417 }
418
419 if (layout->rtl_context != rtl_context)
420 {
421 if (layout->rtl_context)
422 g_object_unref (object: layout->rtl_context);
423
424 layout->rtl_context = rtl_context;
425 g_object_ref (layout->rtl_context);
426 }
427
428 DV (g_print ("invalidating all due to new pango contexts (%s)\n", G_STRLOC));
429 gtk_text_layout_invalidate_all (layout);
430}
431
432/**
433 * gtk_text_layout_set_overwrite_mode:
434 * @layout: a `GtkTextLayout`
435 * @overwrite: overwrite mode
436 *
437 * Sets overwrite mode
438 */
439void
440gtk_text_layout_set_overwrite_mode (GtkTextLayout *layout,
441 gboolean overwrite)
442{
443 overwrite = overwrite != 0;
444 if (overwrite != layout->overwrite_mode)
445 {
446 layout->overwrite_mode = overwrite;
447 gtk_text_layout_invalidate_cursor_line (layout, TRUE);
448 }
449}
450
451/**
452 * gtk_text_layout_set_cursor_direction:
453 * @direction: the new direction(s) for which to draw cursors.
454 * %GTK_TEXT_DIR_NONE means draw cursors for both
455 * left-to-right insertion and right-to-left insertion.
456 * (The two cursors will be visually distinguished.)
457 *
458 * Sets which text directions (left-to-right and/or right-to-left) for
459 * which cursors will be drawn for the insertion point. The visual
460 * point at which new text is inserted depends on whether the new
461 * text is right-to-left or left-to-right, so it may be desired to
462 * make the drawn position of the cursor depend on the keyboard state.
463 */
464void
465gtk_text_layout_set_cursor_direction (GtkTextLayout *layout,
466 GtkTextDirection direction)
467{
468 if (direction != layout->cursor_direction)
469 {
470 layout->cursor_direction = direction;
471 gtk_text_layout_invalidate_cursor_line (layout, TRUE);
472 }
473}
474
475/**
476 * gtk_text_layout_set_keyboard_direction:
477 * @keyboard_dir: the current direction of the keyboard.
478 *
479 * Sets the keyboard direction; this is used as for the bidirectional
480 * base direction for the line with the cursor if the line contains
481 * only neutral characters.
482 */
483void
484gtk_text_layout_set_keyboard_direction (GtkTextLayout *layout,
485 GtkTextDirection keyboard_dir)
486{
487 if (keyboard_dir != layout->keyboard_direction)
488 {
489 layout->keyboard_direction = keyboard_dir;
490 gtk_text_layout_invalidate_cursor_line (layout, TRUE);
491 }
492}
493
494/**
495 * gtk_text_layout_get_buffer:
496 * @layout: a `GtkTextLayout`
497 *
498 * Gets the text buffer used by the layout. See
499 * gtk_text_layout_set_buffer().
500 *
501 * Returns: the text buffer used by the layout.
502 */
503GtkTextBuffer *
504gtk_text_layout_get_buffer (GtkTextLayout *layout)
505{
506 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), NULL);
507
508 return layout->buffer;
509}
510
511void
512gtk_text_layout_set_screen_width (GtkTextLayout *layout, int width)
513{
514 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
515 g_return_if_fail (width >= 0);
516 g_return_if_fail (layout->wrap_loop_count == 0);
517
518 if (layout->screen_width == width)
519 return;
520
521 layout->screen_width = width;
522
523 DV (g_print ("invalidating all due to new screen width (%s)\n", G_STRLOC));
524 gtk_text_layout_invalidate_all (layout);
525}
526
527/**
528 * gtk_text_layout_set_cursor_visible:
529 * @layout: a `GtkTextLayout`
530 * @cursor_visible: If %FALSE, then the insertion cursor will not
531 * be shown, even if the text is editable.
532 *
533 * Sets whether the insertion cursor should be shown. Generally,
534 * widgets using `GtkTextLayout` will hide the cursor when the
535 * widget does not have the input focus.
536 */
537void
538gtk_text_layout_set_cursor_visible (GtkTextLayout *layout,
539 gboolean cursor_visible)
540{
541 cursor_visible = (cursor_visible != FALSE);
542
543 if (layout->cursor_visible != cursor_visible)
544 {
545 GtkTextIter iter;
546 int y, height;
547
548 layout->cursor_visible = cursor_visible;
549
550 /* Now queue a redraw on the paragraph containing the cursor
551 */
552 gtk_text_buffer_get_iter_at_mark (buffer: layout->buffer, iter: &iter,
553 mark: gtk_text_buffer_get_insert (buffer: layout->buffer));
554
555 gtk_text_layout_get_line_yrange (layout, iter: &iter, y: &y, height: &height);
556 gtk_text_layout_emit_changed (layout, y, old_height: height, new_height: height);
557
558 gtk_text_layout_invalidate_cache (layout, line: _gtk_text_iter_get_text_line (iter: &iter), TRUE);
559 }
560}
561
562/**
563 * gtk_text_layout_get_cursor_visible:
564 * @layout: a `GtkTextLayout`
565 *
566 * Returns whether the insertion cursor will be shown.
567 *
568 * Returns: if %FALSE, the insertion cursor will not be
569 * shown, even if the text is editable.
570 */
571gboolean
572gtk_text_layout_get_cursor_visible (GtkTextLayout *layout)
573{
574 return layout->cursor_visible;
575}
576
577/**
578 * gtk_text_layout_set_preedit_string:
579 * @layout: a `PangoLayout`
580 * @preedit_string: a string to display at the insertion point
581 * @preedit_attrs: a `PangoAttrList` of attributes that apply to @preedit_string
582 * @cursor_pos: position of cursor within preedit string in chars
583 *
584 * Set the preedit string and attributes. The preedit string is a
585 * string showing text that is currently being edited and not
586 * yet committed into the buffer.
587 */
588void
589gtk_text_layout_set_preedit_string (GtkTextLayout *layout,
590 const char *preedit_string,
591 PangoAttrList *preedit_attrs,
592 int cursor_pos)
593{
594 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
595 g_return_if_fail (preedit_attrs != NULL || preedit_string == NULL);
596
597 g_free (mem: layout->preedit_string);
598
599 if (layout->preedit_attrs)
600 pango_attr_list_unref (list: layout->preedit_attrs);
601
602 if (preedit_string)
603 {
604 layout->preedit_string = g_strdup (str: preedit_string);
605 layout->preedit_len = strlen (s: layout->preedit_string);
606 pango_attr_list_ref (list: preedit_attrs);
607 layout->preedit_attrs = preedit_attrs;
608
609 cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (layout->preedit_string, -1));
610 layout->preedit_cursor = g_utf8_offset_to_pointer (str: layout->preedit_string, offset: cursor_pos) - layout->preedit_string;
611 }
612 else
613 {
614 layout->preedit_string = NULL;
615 layout->preedit_len = 0;
616 layout->preedit_attrs = NULL;
617 layout->preedit_cursor = 0;
618 }
619
620 gtk_text_layout_invalidate_cursor_line (layout, FALSE);
621}
622
623void
624gtk_text_layout_get_size (GtkTextLayout *layout,
625 int *width,
626 int *height)
627{
628 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
629
630 if (width)
631 *width = layout->width;
632
633 if (height)
634 *height = layout->height;
635}
636
637static void
638gtk_text_layout_invalidated (GtkTextLayout *layout)
639{
640 g_signal_emit (instance: layout, signal_id: signals[INVALIDATED], detail: 0);
641}
642
643static void
644gtk_text_layout_emit_changed (GtkTextLayout *layout,
645 int y,
646 int old_height,
647 int new_height)
648{
649 g_signal_emit (instance: layout, signal_id: signals[CHANGED], detail: 0, y, old_height, new_height);
650}
651
652void
653gtk_text_layout_changed (GtkTextLayout *layout,
654 int y,
655 int old_height,
656 int new_height)
657{
658 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
659 gtk_text_line_display_cache_invalidate_y_range (cache: priv->cache, layout, y, old_height, new_height, FALSE);
660 gtk_text_layout_emit_changed (layout, y, old_height, new_height);
661}
662
663void
664gtk_text_layout_cursors_changed (GtkTextLayout *layout,
665 int y,
666 int old_height,
667 int new_height)
668{
669 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
670 gtk_text_line_display_cache_invalidate_y_range (cache: priv->cache, layout, y, old_height, new_height, TRUE);
671 gtk_text_layout_emit_changed (layout, y, old_height, new_height);
672}
673
674static void
675invalidate_cached_style (GtkTextLayout *layout)
676{
677 free_style_cache (text_layout: layout);
678}
679
680/* These should be called around a loop which wraps a CONTIGUOUS bunch
681 * of display lines. If the lines aren’t contiguous you can’t call
682 * these.
683 */
684void
685gtk_text_layout_wrap_loop_start (GtkTextLayout *layout)
686{
687 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
688 g_return_if_fail (layout->one_style_cache == NULL);
689
690 layout->wrap_loop_count += 1;
691}
692
693void
694gtk_text_layout_wrap_loop_end (GtkTextLayout *layout)
695{
696 g_return_if_fail (layout->wrap_loop_count > 0);
697
698 layout->wrap_loop_count -= 1;
699
700 if (layout->wrap_loop_count == 0)
701 {
702 /* We cache a some stuff if we're iterating over some lines wrapping
703 * them. This cleans it up.
704 */
705 /* Nuke our cached style */
706 invalidate_cached_style (layout);
707 g_assert (layout->one_style_cache == NULL);
708 }
709}
710
711static void
712gtk_text_layout_invalidate_all (GtkTextLayout *layout)
713{
714 GtkTextIter start;
715 GtkTextIter end;
716
717 if (layout->buffer == NULL)
718 return;
719
720 gtk_text_buffer_get_bounds (buffer: layout->buffer, start: &start, end: &end);
721
722 gtk_text_layout_invalidate (layout, start: &start, end: &end);
723}
724
725static void
726gtk_text_layout_invalidate_cache (GtkTextLayout *layout,
727 GtkTextLine *line,
728 gboolean cursors_only)
729{
730 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
731
732 g_assert (GTK_IS_TEXT_LAYOUT (layout));
733
734 if (priv->cache != NULL)
735 {
736 if (cursors_only)
737 gtk_text_line_display_cache_invalidate_cursors (cache: priv->cache, line);
738 else
739 gtk_text_line_display_cache_invalidate_line (cache: priv->cache, line);
740 }
741}
742
743/* Now invalidate the paragraph containing the cursor
744 */
745static void
746gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout,
747 gboolean cursors_only)
748{
749 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
750 GtkTextLineData *line_data;
751
752 if (priv->cursor_line == NULL)
753 return;
754
755 line_data = _gtk_text_line_get_data (line: priv->cursor_line, view_id: layout);
756
757 if (line_data != NULL)
758 {
759 gtk_text_layout_invalidate_cache (layout, line: priv->cursor_line, cursors_only);
760
761 if (!cursors_only)
762 _gtk_text_line_invalidate_wrap (line: priv->cursor_line, ld: line_data);
763
764 gtk_text_layout_invalidated (layout);
765 }
766}
767
768static void
769gtk_text_layout_update_cursor_line (GtkTextLayout *layout)
770{
771 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
772 GtkTextIter iter;
773
774 gtk_text_buffer_get_iter_at_mark (buffer: layout->buffer, iter: &iter,
775 mark: gtk_text_buffer_get_insert (buffer: layout->buffer));
776
777 priv->cursor_line = _gtk_text_iter_get_text_line (iter: &iter);
778
779 gtk_text_line_display_cache_set_cursor_line (cache: priv->cache, line: priv->cursor_line);
780}
781
782void
783gtk_text_layout_invalidate (GtkTextLayout *layout,
784 const GtkTextIter *start,
785 const GtkTextIter *end)
786{
787 GtkTextLine *line;
788 GtkTextLine *last_line;
789
790 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
791 g_return_if_fail (layout->wrap_loop_count == 0);
792
793 /* Because we may be invalidating a mark, it's entirely possible
794 * that gtk_text_iter_equal (start, end) in which case we
795 * should still invalidate the line they are both on. i.e.
796 * we always invalidate the line with "start" even
797 * if there's an empty range.
798 */
799
800#if 0
801 gtk_text_view_index_spew (start_index, "invalidate start");
802 gtk_text_view_index_spew (end_index, "invalidate end");
803#endif
804
805 last_line = _gtk_text_iter_get_text_line (iter: end);
806 line = _gtk_text_iter_get_text_line (iter: start);
807
808 while (TRUE)
809 {
810 GtkTextLineData *line_data = _gtk_text_line_get_data (line, view_id: layout);
811
812 gtk_text_layout_invalidate_cache (layout, line, FALSE);
813
814 if (line_data)
815 _gtk_text_line_invalidate_wrap (line, ld: line_data);
816
817 if (line == last_line)
818 break;
819
820 line = _gtk_text_line_next_excluding_last (line);
821 }
822
823 gtk_text_layout_invalidated (layout);
824}
825
826void
827gtk_text_layout_invalidate_cursors (GtkTextLayout *layout,
828 const GtkTextIter *start,
829 const GtkTextIter *end)
830{
831 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
832 gtk_text_line_display_cache_invalidate_range (cache: priv->cache, layout, begin: start, end, TRUE);
833 gtk_text_layout_invalidated (layout);
834}
835
836void
837gtk_text_layout_invalidate_selection (GtkTextLayout *layout)
838{
839 GtkTextIter selection_start, selection_end;
840
841 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
842
843 if (layout->buffer &&
844 gtk_text_buffer_get_selection_bounds (buffer: layout->buffer,
845 start: &selection_start,
846 end: &selection_end))
847 gtk_text_layout_invalidate (layout, start: &selection_start, end: &selection_end);
848}
849
850void
851gtk_text_layout_free_line_data (GtkTextLayout *layout,
852 GtkTextLine *line,
853 GtkTextLineData *line_data)
854{
855 gtk_text_layout_invalidate_cache (layout, line, FALSE);
856
857 g_slice_free (GtkTextLineData, line_data);
858}
859
860/**
861 * gtk_text_layout_is_valid:
862 * @layout: a `GtkTextLayout`
863 *
864 * Check if there are any invalid regions in a `GtkTextLayout`’s buffer
865 *
866 * Returns: %TRUE if any invalid regions were found
867 */
868gboolean
869gtk_text_layout_is_valid (GtkTextLayout *layout)
870{
871 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
872
873 return _gtk_text_btree_is_valid (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
874 view_id: layout);
875}
876
877static void
878update_layout_size (GtkTextLayout *layout)
879{
880 _gtk_text_btree_get_view_size (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
881 view_id: layout,
882 width: &layout->width, height: &layout->height);
883}
884
885/**
886 * gtk_text_layout_validate_yrange:
887 * @layout: a `GtkTextLayout`
888 * @anchor: iter pointing into a line that will be used as the
889 * coordinate origin
890 * @y0_: offset from the top of the line pointed to by @anchor at
891 * which to begin validation. (The offset here is in pixels
892 * after validation.)
893 * @y1_: offset from the top of the line pointed to by @anchor at
894 * which to end validation. (The offset here is in pixels
895 * after validation.)
896 *
897 * Ensure that a region of a `GtkTextLayout` is valid. The ::changed
898 * signal will be emitted if any lines are validated.
899 */
900void
901gtk_text_layout_validate_yrange (GtkTextLayout *layout,
902 GtkTextIter *anchor,
903 int y0,
904 int y1)
905{
906 GtkTextLine *line;
907 GtkTextLine *first_line = NULL;
908 GtkTextLine *last_line = NULL;
909 int seen;
910 int delta_height = 0;
911 int first_line_y = 0; /* Quiet GCC */
912 int last_line_y = 0; /* Quiet GCC */
913
914 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
915
916 if (y0 > 0)
917 y0 = 0;
918 if (y1 < 0)
919 y1 = 0;
920
921 /* Validate backwards from the anchor line to y0
922 */
923 line = _gtk_text_iter_get_text_line (iter: anchor);
924 line = _gtk_text_line_previous (line);
925 seen = 0;
926 while (line && seen < -y0)
927 {
928 GtkTextLineData *line_data = _gtk_text_line_get_data (line, view_id: layout);
929 if (!line_data || !line_data->valid)
930 {
931 int old_height, new_height;
932 int top_ink, bottom_ink;
933
934 old_height = line_data ? line_data->height : 0;
935 top_ink = line_data ? line_data->top_ink : 0;
936 bottom_ink = line_data ? line_data->bottom_ink : 0;
937
938 _gtk_text_btree_validate_line (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
939 line, view_id: layout);
940 line_data = _gtk_text_line_get_data (line, view_id: layout);
941
942 new_height = line_data ? line_data->height : 0;
943 if (line_data)
944 {
945 top_ink = MAX (top_ink, line_data->top_ink);
946 bottom_ink = MAX (bottom_ink, line_data->bottom_ink);
947 }
948
949 delta_height += new_height - old_height;
950
951 first_line = line;
952 first_line_y = -seen - new_height - top_ink;
953 if (!last_line)
954 {
955 last_line = line;
956 last_line_y = -seen + bottom_ink;
957 }
958 }
959
960 seen += line_data ? line_data->height : 0;
961 line = _gtk_text_line_previous (line);
962 }
963
964 /* Validate forwards to y1 */
965 line = _gtk_text_iter_get_text_line (iter: anchor);
966 seen = 0;
967 while (line && seen < y1)
968 {
969 GtkTextLineData *line_data = _gtk_text_line_get_data (line, view_id: layout);
970 if (!line_data || !line_data->valid)
971 {
972 int old_height, new_height;
973 int top_ink, bottom_ink;
974
975 old_height = line_data ? line_data->height : 0;
976 top_ink = line_data ? line_data->top_ink : 0;
977 bottom_ink = line_data ? line_data->bottom_ink : 0;
978
979 _gtk_text_btree_validate_line (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
980 line, view_id: layout);
981 line_data = _gtk_text_line_get_data (line, view_id: layout);
982 new_height = line_data ? line_data->height : 0;
983 if (line_data)
984 {
985 top_ink = MAX (top_ink, line_data->top_ink);
986 bottom_ink = MAX (bottom_ink, line_data->bottom_ink);
987 }
988
989 delta_height += new_height - old_height;
990
991 if (!first_line)
992 {
993 first_line = line;
994 first_line_y = seen - top_ink;
995 }
996 last_line = line;
997 last_line_y = seen + new_height + bottom_ink;
998 }
999
1000 seen += line_data ? line_data->height : 0;
1001 line = _gtk_text_line_next_excluding_last (line);
1002 }
1003
1004 /* If we found and validated any invalid lines, update size and
1005 * emit the changed signal
1006 */
1007 if (first_line)
1008 {
1009 int line_top;
1010
1011 update_layout_size (layout);
1012
1013 line_top = _gtk_text_btree_find_line_top (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
1014 line: first_line, view_id: layout);
1015
1016 gtk_text_layout_emit_changed (layout,
1017 y: line_top,
1018 old_height: last_line_y - first_line_y - delta_height,
1019 new_height: last_line_y - first_line_y);
1020 }
1021}
1022
1023/**
1024 * gtk_text_layout_validate:
1025 * @tree: a `GtkTextLayout`
1026 * @max_pixels: the maximum number of pixels to validate. (No more
1027 * than one paragraph beyond this limit will be validated)
1028 *
1029 * Validate regions of a `GtkTextLayout`. The ::changed signal will
1030 * be emitted for each region validated.
1031 **/
1032void
1033gtk_text_layout_validate (GtkTextLayout *layout,
1034 int max_pixels)
1035{
1036 GtkTextBTree *btree;
1037 int y, old_height, new_height;
1038
1039 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
1040
1041 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
1042 while (max_pixels > 0 &&
1043 _gtk_text_btree_validate (tree: btree,
1044 view_id: layout, max_pixels,
1045 y: &y, old_height: &old_height, new_height: &new_height))
1046 {
1047 max_pixels -= new_height;
1048
1049 update_layout_size (layout);
1050 gtk_text_layout_emit_changed (layout, y, old_height, new_height);
1051 }
1052}
1053
1054GtkTextLineData *
1055gtk_text_layout_wrap (GtkTextLayout *layout,
1056 GtkTextLine *line,
1057 /* may be NULL */
1058 GtkTextLineData *line_data)
1059{
1060 GtkTextLineDisplay *display;
1061 PangoRectangle ink_rect, logical_rect;
1062
1063 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), NULL);
1064 g_return_val_if_fail (line != NULL, NULL);
1065
1066 if (line_data == NULL)
1067 {
1068 line_data = _gtk_text_line_data_new (layout, line);
1069 _gtk_text_line_add_data (line, data: line_data);
1070 }
1071
1072 display = gtk_text_layout_get_line_display (layout, line, TRUE);
1073 line_data->width = display->width;
1074 line_data->height = display->height;
1075 line_data->valid = TRUE;
1076 pango_layout_get_pixel_extents (layout: display->layout, ink_rect: &ink_rect, logical_rect: &logical_rect);
1077 line_data->top_ink = MAX (0, logical_rect.x - ink_rect.x);
1078 line_data->bottom_ink = MAX (0, logical_rect.x + logical_rect.width - ink_rect.x - ink_rect.width);
1079 gtk_text_line_display_unref (display);
1080
1081 return line_data;
1082}
1083
1084/*
1085 * Layout utility functions
1086 */
1087
1088/* If you get the style with get_style () you need to call
1089 release_style () to free it. */
1090static GtkTextAttributes*
1091get_style (GtkTextLayout *layout,
1092 GPtrArray *tags)
1093{
1094 GtkTextAttributes *style;
1095
1096 /* If we have the one-style cache, then it means
1097 that we haven't seen a toggle since we filled in the
1098 one-style cache.
1099 */
1100 if (layout->one_style_cache != NULL)
1101 {
1102 gtk_text_attributes_ref (values: layout->one_style_cache);
1103 return layout->one_style_cache;
1104 }
1105
1106 g_assert (layout->one_style_cache == NULL);
1107
1108 /* No tags, use default style */
1109 if (tags == NULL || tags->len == 0)
1110 {
1111 /* One ref for the return value, one ref for the
1112 layout->one_style_cache reference */
1113 gtk_text_attributes_ref (values: layout->default_style);
1114 gtk_text_attributes_ref (values: layout->default_style);
1115 layout->one_style_cache = layout->default_style;
1116
1117 return layout->default_style;
1118 }
1119
1120 style = gtk_text_attributes_new ();
1121
1122 gtk_text_attributes_copy_values (src: layout->default_style,
1123 dest: style);
1124
1125 _gtk_text_attributes_fill_from_tags (values: style, tags);
1126
1127 g_assert (style->refcount == 1);
1128
1129 /* Leave this style as the last one seen */
1130 g_assert (layout->one_style_cache == NULL);
1131 gtk_text_attributes_ref (values: style); /* ref held by layout->one_style_cache */
1132 layout->one_style_cache = style;
1133
1134 /* Returning yet another refcount */
1135 return style;
1136}
1137
1138static void
1139release_style (GtkTextLayout *layout,
1140 GtkTextAttributes *style)
1141{
1142 g_return_if_fail (style != NULL);
1143 g_return_if_fail (style->refcount > 0);
1144
1145 gtk_text_attributes_unref (values: style);
1146}
1147
1148/*
1149 * Lines
1150 */
1151
1152/* This function tries to optimize the case where a line
1153 is completely invisible */
1154static gboolean
1155totally_invisible_line (GtkTextLayout *layout,
1156 GtkTextLine *line,
1157 GtkTextIter *iter)
1158{
1159 GtkTextLineSegment *seg;
1160
1161 /* Check if the first char is visible, if so we are partially visible.
1162 * Note that we have to check this since we don't know the current
1163 * invisible/noninvisible toggle state; this function can use the whole btree
1164 * to get it right.
1165 */
1166 gtk_text_layout_get_iter_at_line (layout, iter, line, byte_offset: 0);
1167 if (!_gtk_text_btree_char_is_invisible (iter))
1168 return FALSE;
1169
1170 seg = line->segments;
1171
1172 while (seg != NULL)
1173 {
1174 if (seg->byte_count <= 0 &&
1175 seg->type == &gtk_text_toggle_on_type)
1176 {
1177 invalidate_cached_style (layout);
1178
1179 /* Bail out if an elision-unsetting tag begins */
1180 if (seg->body.toggle.info->tag->priv->invisible_set &&
1181 !seg->body.toggle.info->tag->priv->values->invisible)
1182 break;
1183 }
1184 else if (seg->type == &gtk_text_toggle_off_type)
1185 {
1186 invalidate_cached_style (layout);
1187
1188 /* Bail out if an elision-setting tag ends */
1189 if (seg->body.toggle.info->tag->priv->invisible_set &&
1190 seg->body.toggle.info->tag->priv->values->invisible)
1191 break;
1192 }
1193
1194 seg = seg->next;
1195 }
1196
1197 if (seg != NULL) /* didn't reach line end */
1198 return FALSE;
1199
1200 return TRUE;
1201}
1202
1203static void
1204set_para_values (GtkTextLayout *layout,
1205 PangoDirection base_dir,
1206 GtkTextAttributes *style,
1207 GtkTextLineDisplay *display)
1208{
1209 PangoAlignment pango_align = PANGO_ALIGN_LEFT;
1210 PangoWrapMode pango_wrap = PANGO_WRAP_WORD;
1211 int h_margin;
1212 int h_padding;
1213
1214 switch (base_dir)
1215 {
1216 /* If no base direction was found, then use the style direction */
1217 case PANGO_DIRECTION_NEUTRAL :
1218 display->direction = style->direction;
1219
1220 /* Override the base direction */
1221 if (display->direction == GTK_TEXT_DIR_RTL)
1222 base_dir = PANGO_DIRECTION_RTL;
1223 else
1224 base_dir = PANGO_DIRECTION_LTR;
1225
1226 break;
1227 case PANGO_DIRECTION_RTL :
1228 display->direction = GTK_TEXT_DIR_RTL;
1229 break;
1230 case PANGO_DIRECTION_LTR:
1231 case PANGO_DIRECTION_TTB_LTR:
1232 case PANGO_DIRECTION_TTB_RTL:
1233 case PANGO_DIRECTION_WEAK_LTR:
1234 case PANGO_DIRECTION_WEAK_RTL:
1235 default:
1236 display->direction = GTK_TEXT_DIR_LTR;
1237 break;
1238 }
1239
1240 if (display->direction == GTK_TEXT_DIR_RTL)
1241 display->layout = pango_layout_new (context: layout->rtl_context);
1242 else
1243 display->layout = pango_layout_new (context: layout->ltr_context);
1244
1245 switch (style->justification)
1246 {
1247 case GTK_JUSTIFY_LEFT:
1248 pango_align = (base_dir == PANGO_DIRECTION_LTR) ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
1249 break;
1250 case GTK_JUSTIFY_RIGHT:
1251 pango_align = (base_dir == PANGO_DIRECTION_LTR) ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
1252 break;
1253 case GTK_JUSTIFY_CENTER:
1254 pango_align = PANGO_ALIGN_CENTER;
1255 break;
1256 case GTK_JUSTIFY_FILL:
1257 pango_align = (base_dir == PANGO_DIRECTION_LTR) ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
1258 pango_layout_set_justify (layout: display->layout, TRUE);
1259 break;
1260 default:
1261 g_assert_not_reached ();
1262 break;
1263 }
1264
1265 pango_layout_set_alignment (layout: display->layout, alignment: pango_align);
1266 pango_layout_set_spacing (layout: display->layout,
1267 spacing: style->pixels_inside_wrap * PANGO_SCALE);
1268
1269 if (style->tabs)
1270 pango_layout_set_tabs (layout: display->layout, tabs: style->tabs);
1271
1272 display->top_margin = style->pixels_above_lines;
1273 display->height = style->pixels_above_lines + style->pixels_below_lines;
1274 display->bottom_margin = style->pixels_below_lines;
1275 display->left_margin = style->left_margin;
1276 display->right_margin = style->right_margin;
1277
1278 display->x_offset = display->left_margin;
1279
1280 pango_layout_set_indent (layout: display->layout,
1281 indent: style->indent * PANGO_SCALE);
1282
1283 switch (style->wrap_mode)
1284 {
1285 case GTK_WRAP_CHAR:
1286 pango_wrap = PANGO_WRAP_CHAR;
1287 break;
1288 case GTK_WRAP_WORD:
1289 pango_wrap = PANGO_WRAP_WORD;
1290 break;
1291
1292 case GTK_WRAP_WORD_CHAR:
1293 pango_wrap = PANGO_WRAP_WORD_CHAR;
1294 break;
1295
1296 case GTK_WRAP_NONE:
1297 default:
1298 break;
1299 }
1300
1301 h_margin = display->left_margin + display->right_margin;
1302 h_padding = layout->left_padding + layout->right_padding;
1303
1304 if (style->wrap_mode != GTK_WRAP_NONE)
1305 {
1306 int layout_width = (layout->screen_width - h_margin - h_padding);
1307 pango_layout_set_width (layout: display->layout, width: layout_width * PANGO_SCALE);
1308 pango_layout_set_wrap (layout: display->layout, wrap: pango_wrap);
1309 }
1310 display->total_width = MAX (layout->screen_width, layout->width) - h_margin - h_padding;
1311
1312 if (style->pg_bg_rgba)
1313 {
1314 display->pg_bg_rgba = *style->pg_bg_rgba;
1315 display->pg_bg_rgba_set = TRUE;
1316 }
1317 else
1318 {
1319 display->pg_bg_rgba_set = FALSE;
1320 }
1321}
1322
1323static PangoAttribute *
1324gtk_text_attr_appearance_copy (const PangoAttribute *attr)
1325{
1326 const GtkTextAttrAppearance *appearance_attr = (const GtkTextAttrAppearance *)attr;
1327
1328 return gtk_text_attr_appearance_new (appearance: &appearance_attr->appearance);
1329}
1330
1331static void
1332gtk_text_attr_appearance_destroy (PangoAttribute *attr)
1333{
1334 GtkTextAttrAppearance *appearance_attr = (GtkTextAttrAppearance *)attr;
1335
1336 if (appearance_attr->appearance.fg_rgba)
1337 gdk_rgba_free (rgba: appearance_attr->appearance.fg_rgba);
1338
1339 if (appearance_attr->appearance.bg_rgba)
1340 gdk_rgba_free (rgba: appearance_attr->appearance.bg_rgba);
1341
1342 if (appearance_attr->appearance.underline_rgba)
1343 gdk_rgba_free (rgba: appearance_attr->appearance.underline_rgba);
1344
1345 if (appearance_attr->appearance.overline_rgba)
1346 gdk_rgba_free (rgba: appearance_attr->appearance.overline_rgba);
1347
1348 if (appearance_attr->appearance.strikethrough_rgba)
1349 gdk_rgba_free (rgba: appearance_attr->appearance.strikethrough_rgba);
1350
1351 g_slice_free (GtkTextAttrAppearance, appearance_attr);
1352}
1353
1354static gboolean
1355rgba_equal (const GdkRGBA *rgba1, const GdkRGBA *rgba2)
1356{
1357 if (rgba1 && rgba2)
1358 return gdk_rgba_equal (p1: rgba1, p2: rgba2);
1359
1360 if (rgba1 || rgba2)
1361 return FALSE;
1362
1363 return TRUE;
1364}
1365
1366static gboolean
1367underline_equal (const GtkTextAppearance *appearance1,
1368 const GtkTextAppearance *appearance2)
1369{
1370 return (appearance1->underline == appearance2->underline) &&
1371 rgba_equal (rgba1: (const GdkRGBA *)appearance1->underline_rgba,
1372 rgba2: (const GdkRGBA *)appearance2->underline_rgba);
1373}
1374
1375static gboolean
1376overline_equal (const GtkTextAppearance *appearance1,
1377 const GtkTextAppearance *appearance2)
1378{
1379 return (appearance1->overline == appearance2->overline) &&
1380 rgba_equal (rgba1: (const GdkRGBA *)appearance1->overline_rgba,
1381 rgba2: (const GdkRGBA *)appearance2->overline_rgba);
1382}
1383
1384static gboolean
1385strikethrough_equal (const GtkTextAppearance *appearance1,
1386 const GtkTextAppearance *appearance2)
1387{
1388 return (appearance1->strikethrough == appearance2->strikethrough) &&
1389 rgba_equal (rgba1: (const GdkRGBA *)appearance1->strikethrough_rgba,
1390 rgba2: (const GdkRGBA *)appearance2->strikethrough_rgba);
1391}
1392
1393static gboolean
1394gtk_text_attr_appearance_compare (const PangoAttribute *attr1,
1395 const PangoAttribute *attr2)
1396{
1397 const GtkTextAppearance *appearance1 = &((const GtkTextAttrAppearance *)attr1)->appearance;
1398 const GtkTextAppearance *appearance2 = &((const GtkTextAttrAppearance *)attr2)->appearance;
1399
1400 return rgba_equal (rgba1: appearance1->fg_rgba, rgba2: appearance2->fg_rgba) &&
1401 rgba_equal (rgba1: appearance1->bg_rgba, rgba2: appearance2->bg_rgba) &&
1402 appearance1->draw_bg == appearance2->draw_bg &&
1403 strikethrough_equal (appearance1, appearance2) &&
1404 underline_equal (appearance1, appearance2) &&
1405 overline_equal (appearance1, appearance2);
1406}
1407
1408/*
1409 * gtk_text_attr_appearance_new:
1410 * @desc:
1411 *
1412 * Create a new font description attribute. (This attribute
1413 * allows setting family, style, weight, variant, stretch,
1414 * and size simultaneously.)
1415 *
1416 * Returns:
1417 */
1418static PangoAttribute *
1419gtk_text_attr_appearance_new (const GtkTextAppearance *appearance)
1420{
1421 static PangoAttrClass klass = {
1422 0,
1423 gtk_text_attr_appearance_copy,
1424 gtk_text_attr_appearance_destroy,
1425 gtk_text_attr_appearance_compare
1426 };
1427
1428 GtkTextAttrAppearance *result;
1429
1430 if (!klass.type)
1431 klass.type = gtk_text_attr_appearance_type =
1432 pango_attr_type_register (I_("GtkTextAttrAppearance"));
1433
1434 result = g_slice_new (GtkTextAttrAppearance);
1435 result->attr.klass = &klass;
1436
1437 result->appearance = *appearance;
1438
1439 if (appearance->fg_rgba)
1440 result->appearance.fg_rgba = gdk_rgba_copy (rgba: appearance->fg_rgba);
1441
1442 if (appearance->bg_rgba)
1443 result->appearance.bg_rgba = gdk_rgba_copy (rgba: appearance->bg_rgba);
1444
1445 if (appearance->underline_rgba)
1446 result->appearance.underline_rgba = gdk_rgba_copy (rgba: appearance->underline_rgba);
1447
1448 if (appearance->overline_rgba)
1449 result->appearance.overline_rgba = gdk_rgba_copy (rgba: appearance->overline_rgba);
1450
1451 if (appearance->strikethrough_rgba)
1452 result->appearance.strikethrough_rgba = gdk_rgba_copy (rgba: appearance->strikethrough_rgba);
1453
1454 return (PangoAttribute *)result;
1455}
1456
1457static void
1458add_generic_attrs (GtkTextLayout *layout,
1459 GtkTextAppearance *appearance,
1460 int byte_count,
1461 PangoAttrList *attrs,
1462 int start,
1463 gboolean size_only,
1464 gboolean is_text)
1465{
1466 PangoAttribute *attr;
1467
1468 if (appearance->underline != PANGO_UNDERLINE_NONE)
1469 {
1470 attr = pango_attr_underline_new (underline: appearance->underline);
1471
1472 attr->start_index = start;
1473 attr->end_index = start + byte_count;
1474
1475 pango_attr_list_insert (list: attrs, attr);
1476 }
1477
1478 if (appearance->underline_rgba)
1479 {
1480 attr = pango_attr_underline_color_new (red: appearance->underline_rgba->red * 65535,
1481 green: appearance->underline_rgba->green * 65535,
1482 blue: appearance->underline_rgba->blue * 65535);
1483
1484 attr->start_index = start;
1485 attr->end_index = start + byte_count;
1486
1487 pango_attr_list_insert (list: attrs, attr);
1488 }
1489
1490 if (appearance->overline != PANGO_OVERLINE_NONE)
1491 {
1492 attr = pango_attr_overline_new (overline: appearance->overline);
1493
1494 attr->start_index = start;
1495 attr->end_index = start + byte_count;
1496
1497 pango_attr_list_insert (list: attrs, attr);
1498 }
1499
1500 if (appearance->overline_rgba)
1501 {
1502 attr = pango_attr_overline_color_new (red: appearance->overline_rgba->red * 65535,
1503 green: appearance->overline_rgba->green * 65535,
1504 blue: appearance->overline_rgba->blue * 65535);
1505
1506 attr->start_index = start;
1507 attr->end_index = start + byte_count;
1508
1509 pango_attr_list_insert (list: attrs, attr);
1510 }
1511
1512 if (appearance->strikethrough)
1513 {
1514 attr = pango_attr_strikethrough_new (strikethrough: appearance->strikethrough);
1515
1516 attr->start_index = start;
1517 attr->end_index = start + byte_count;
1518
1519 pango_attr_list_insert (list: attrs, attr);
1520 }
1521
1522 if (appearance->strikethrough_rgba)
1523 {
1524 attr = pango_attr_strikethrough_color_new (red: appearance->strikethrough_rgba->red * 65535,
1525 green: appearance->strikethrough_rgba->green * 65535,
1526 blue: appearance->strikethrough_rgba->blue * 65535);
1527
1528 attr->start_index = start;
1529 attr->end_index = start + byte_count;
1530
1531 pango_attr_list_insert (list: attrs, attr);
1532 }
1533
1534 if (appearance->rise != 0)
1535 {
1536 attr = pango_attr_rise_new (rise: appearance->rise);
1537
1538 attr->start_index = start;
1539 attr->end_index = start + byte_count;
1540
1541 pango_attr_list_insert (list: attrs, attr);
1542 }
1543
1544 if (!size_only)
1545 {
1546 attr = gtk_text_attr_appearance_new (appearance);
1547
1548 attr->start_index = start;
1549 attr->end_index = start + byte_count;
1550
1551 ((GtkTextAttrAppearance *)attr)->appearance.is_text = is_text;
1552
1553 pango_attr_list_insert (list: attrs, attr);
1554 }
1555}
1556
1557static void
1558add_text_attrs (GtkTextLayout *layout,
1559 GtkTextAttributes *style,
1560 int byte_count,
1561 PangoAttrList *attrs,
1562 int start,
1563 gboolean size_only,
1564 PangoAttribute **last_font_attr,
1565 PangoAttribute **last_scale_attr,
1566 PangoAttribute **last_fallback_attr)
1567{
1568 PangoAttribute *attr;
1569
1570 if (*last_font_attr &&
1571 pango_font_description_equal (desc1: style->font, desc2: ((PangoAttrFontDesc*)*last_font_attr)->desc) &&
1572 (*last_font_attr)->end_index >= start)
1573 {
1574 (*last_font_attr)->start_index = MIN ((*last_font_attr)->start_index, start);
1575 (*last_font_attr)->end_index = MAX ((*last_font_attr)->end_index, start + byte_count);
1576 }
1577 else
1578 {
1579 attr = pango_attr_font_desc_new (desc: style->font);
1580 attr->start_index = start;
1581 attr->end_index = start + byte_count;
1582
1583 pango_attr_list_insert (list: attrs, attr);
1584 *last_font_attr = attr;
1585 }
1586
1587 if (*last_scale_attr &&
1588 style->font_scale == ((PangoAttrFloat*)*last_scale_attr)->value &&
1589 (*last_scale_attr)->end_index >= start)
1590 {
1591 (*last_scale_attr)->start_index = MIN ((*last_scale_attr)->start_index, start);
1592 (*last_scale_attr)->end_index = MAX ((*last_scale_attr)->end_index, start + byte_count);
1593 }
1594 else if (style->font_scale != 1.0)
1595 {
1596 attr = pango_attr_scale_new (scale_factor: style->font_scale);
1597 attr->start_index = start;
1598 attr->end_index = start + byte_count;
1599
1600 pango_attr_list_insert (list: attrs, attr);
1601 *last_scale_attr = attr;
1602 }
1603
1604 if (*last_fallback_attr &&
1605 (!style->no_fallback) == ((PangoAttrInt*)*last_fallback_attr)->value &&
1606 (*last_fallback_attr)->end_index >= start)
1607 {
1608 (*last_fallback_attr)->start_index = MIN ((*last_fallback_attr)->start_index, start);
1609 (*last_fallback_attr)->end_index = MAX ((*last_fallback_attr)->end_index, start + byte_count);
1610 }
1611 else if (style->no_fallback)
1612 {
1613 attr = pango_attr_fallback_new (enable_fallback: !style->no_fallback);
1614 attr->start_index = start;
1615 attr->end_index = start + byte_count;
1616
1617 pango_attr_list_insert (list: attrs, attr);
1618 *last_fallback_attr = attr;
1619 }
1620
1621 if (style->letter_spacing != 0)
1622 {
1623 attr = pango_attr_letter_spacing_new (letter_spacing: style->letter_spacing);
1624 attr->start_index = start;
1625 attr->end_index = start + byte_count;
1626
1627 pango_attr_list_insert (list: attrs, attr);
1628 }
1629
1630 if (style->line_height != 0.0)
1631 {
1632 if (style->line_height_is_absolute)
1633 attr = pango_attr_line_height_new_absolute (height: style->line_height * PANGO_SCALE);
1634 else
1635 attr = pango_attr_line_height_new (factor: style->line_height);
1636 attr->start_index = start;
1637 attr->end_index = start + byte_count;
1638
1639 pango_attr_list_insert (list: attrs, attr);
1640 }
1641
1642 if (style->font_features)
1643 {
1644 attr = pango_attr_font_features_new (features: style->font_features);
1645 attr->start_index = start;
1646 attr->end_index = start + byte_count;
1647
1648 pango_attr_list_insert (list: attrs, attr);
1649 }
1650
1651 if (style->no_breaks)
1652 {
1653 attr = pango_attr_allow_breaks_new (FALSE);
1654 attr->start_index = start;
1655 attr->end_index = start + byte_count;
1656
1657 pango_attr_list_insert (list: attrs, attr);
1658 }
1659
1660 if (style->show_spaces != PANGO_SHOW_NONE)
1661 {
1662 attr = pango_attr_show_new (flags: style->show_spaces);
1663 attr->start_index = start;
1664 attr->end_index = start + byte_count;
1665
1666 pango_attr_list_insert (list: attrs, attr);
1667 }
1668
1669 if (style->no_hyphens)
1670 {
1671 attr = pango_attr_insert_hyphens_new (FALSE);
1672 attr->start_index = start;
1673 attr->end_index = start + byte_count;
1674
1675 pango_attr_list_insert (list: attrs, attr);
1676 }
1677
1678 if (style->text_transform != PANGO_TEXT_TRANSFORM_NONE)
1679 {
1680 attr = pango_attr_text_transform_new (transform: style->text_transform);
1681 attr->start_index = start;
1682 attr->end_index = start + byte_count;
1683
1684 pango_attr_list_insert (list: attrs, attr);
1685 }
1686
1687 if (style->word)
1688 {
1689 attr = pango_attr_word_new ();
1690 attr->start_index = start;
1691 attr->end_index = start + byte_count;
1692
1693 pango_attr_list_insert (list: attrs, attr);
1694 }
1695
1696 if (style->sentence)
1697 {
1698 attr = pango_attr_sentence_new ();
1699 attr->start_index = start;
1700 attr->end_index = start + byte_count;
1701
1702 pango_attr_list_insert (list: attrs, attr);
1703 }
1704}
1705
1706static void
1707add_paintable_attrs (GtkTextLayout *layout,
1708 GtkTextLineDisplay *display,
1709 GtkTextAttributes *style,
1710 GtkTextLineSegment *seg,
1711 PangoAttrList *attrs,
1712 int start)
1713{
1714 PangoAttribute *attr;
1715 PangoRectangle logical_rect;
1716 GtkTextPaintable *paintable = &seg->body.paintable;
1717 int width, height;
1718
1719 width = gdk_paintable_get_intrinsic_width (paintable: paintable->paintable);
1720 height = gdk_paintable_get_intrinsic_height (paintable: paintable->paintable);
1721
1722 /* Pick *some* default size */
1723 if (width == 0)
1724 width = 32;
1725 if (height == 0)
1726 {
1727 double aspect = gdk_paintable_get_intrinsic_aspect_ratio (paintable: paintable->paintable);
1728 if (aspect == 0)
1729 aspect = 1.0;
1730 height = width / aspect;
1731 }
1732
1733 logical_rect.x = 0;
1734 logical_rect.y = -height * PANGO_SCALE;
1735 logical_rect.width = width * PANGO_SCALE;
1736 logical_rect.height = height * PANGO_SCALE;
1737
1738 attr = pango_attr_shape_new_with_data (ink_rect: &logical_rect, logical_rect: &logical_rect,
1739 data: paintable->paintable, NULL, NULL);
1740 attr->start_index = start;
1741 attr->end_index = start + seg->byte_count;
1742 pango_attr_list_insert (list: attrs, attr);
1743}
1744
1745static void
1746add_child_attrs (GtkTextLayout *layout,
1747 GtkTextLineDisplay *display,
1748 GtkTextAttributes *style,
1749 GtkTextLineSegment *seg,
1750 PangoAttrList *attrs,
1751 int start)
1752{
1753 PangoAttribute *attr;
1754 PangoRectangle logical_rect;
1755 int width, height;
1756 GSList *tmp_list;
1757 GtkWidget *widget = NULL;
1758
1759 width = 1;
1760 height = 1;
1761
1762 tmp_list = seg->body.child.widgets;
1763 while (tmp_list != NULL)
1764 {
1765 GtkWidget *child = tmp_list->data;
1766
1767 if (_gtk_anchored_child_get_layout (child) == layout)
1768 {
1769 /* Found it */
1770 GtkRequisition req;
1771
1772 gtk_widget_get_preferred_size (widget: child, minimum_size: &req, NULL);
1773
1774 width = req.width;
1775 height = req.height;
1776
1777 widget = child;
1778
1779 break;
1780 }
1781
1782 tmp_list = tmp_list->next;
1783 }
1784
1785 if (tmp_list == NULL)
1786 {
1787 /* If tmp_list == NULL then there is no widget at this anchor in
1788 * this display; not an error. We make up an arbitrary size
1789 * to use, just so the programmer can see the blank spot.
1790 * We also put a NULL in the shaped objects list, to keep
1791 * the correspondence between the list and the shaped chars in
1792 * the layout. A bad hack, yes.
1793 */
1794
1795 width = 30;
1796 height = 20;
1797
1798 widget = NULL;
1799 }
1800
1801 logical_rect.x = 0;
1802 logical_rect.y = -height * PANGO_SCALE;
1803 logical_rect.width = width * PANGO_SCALE;
1804 logical_rect.height = height * PANGO_SCALE;
1805
1806 attr = pango_attr_shape_new_with_data (ink_rect: &logical_rect, logical_rect: &logical_rect,
1807 data: widget, NULL, NULL);
1808 attr->start_index = start;
1809 attr->end_index = start + seg->byte_count;
1810 pango_attr_list_insert (list: attrs, attr);
1811}
1812
1813/*
1814 * get_block_cursor:
1815 * @layout: a `GtkTextLayout`
1816 * @display: a `GtkTextLineDisplay`
1817 * @insert_iter: iter pointing to the cursor location
1818 * @insert_index: cursor offset in the @display’s layout, it may
1819 * be different from @insert_iter’s offset in case when preedit
1820 * string is present.
1821 * @pos: location to store cursor position
1822 * @cursor_at_line_end: whether cursor is at the end of line
1823 *
1824 * Checks whether layout should display block cursor at given position.
1825 * For this layout must be in overwrite mode and text at @insert_iter
1826 * must be editable.
1827 */
1828static gboolean
1829get_block_cursor (GtkTextLayout *layout,
1830 GtkTextLineDisplay *display,
1831 const GtkTextIter *insert_iter,
1832 int insert_index,
1833 GdkRectangle *pos,
1834 gboolean *cursor_at_line_end)
1835{
1836 PangoRectangle pango_pos;
1837
1838 if (layout->overwrite_mode &&
1839 gtk_text_iter_editable (iter: insert_iter, TRUE) &&
1840 _gtk_text_util_get_block_cursor_location (layout: display->layout,
1841 index_: insert_index,
1842 rectangle: &pango_pos,
1843 at_line_end: cursor_at_line_end))
1844 {
1845 if (pos)
1846 {
1847 pos->x = PANGO_PIXELS (pango_pos.x);
1848 pos->y = PANGO_PIXELS (pango_pos.y);
1849 pos->width = PANGO_PIXELS (pango_pos.width);
1850 pos->height = PANGO_PIXELS (pango_pos.height);
1851 }
1852
1853 return TRUE;
1854 }
1855 else
1856 return FALSE;
1857}
1858
1859static void
1860add_cursor (GtkTextLayout *layout,
1861 GtkTextLineDisplay *display,
1862 GtkTextLineSegment *seg,
1863 int start)
1864{
1865 CursorPosition cursor;
1866 GtkTextBTree *btree;
1867
1868 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
1869
1870 cursor.pos = start;
1871 cursor.is_insert = _gtk_text_btree_mark_is_insert (tree: btree, segment: seg->body.mark.obj);
1872 cursor.is_selection_bound = _gtk_text_btree_mark_is_selection_bound (tree: btree, segment: seg->body.mark.obj);
1873
1874 /* Hide insertion cursor when we have a selection or the layout
1875 * user has hidden the cursor.
1876 */
1877 if (cursor.is_insert &&
1878 (!layout->cursor_visible || gtk_text_buffer_get_selection_bounds (buffer: layout->buffer, NULL, NULL)))
1879 return;
1880
1881 if (layout->overwrite_mode && cursor.is_insert)
1882 {
1883 GtkTextIter iter;
1884 gboolean cursor_at_line_end;
1885
1886 _gtk_text_btree_get_iter_at_mark (tree: btree, iter: &iter, mark: seg->body.mark.obj);
1887
1888 if (get_block_cursor (layout, display, insert_iter: &iter, insert_index: start,
1889 pos: &display->block_cursor,
1890 cursor_at_line_end: &cursor_at_line_end))
1891 {
1892 display->has_block_cursor = TRUE;
1893 display->cursor_at_line_end = cursor_at_line_end;
1894 return;
1895 }
1896 }
1897
1898 if (!display->cursors)
1899 display->cursors = g_array_new (FALSE, FALSE, element_size: sizeof(CursorPosition));
1900
1901 display->cursors = g_array_append_val (display->cursors, cursor);
1902}
1903
1904static gboolean
1905is_shape (PangoLayoutRun *run)
1906{
1907 GSList *tmp_list = run->item->analysis.extra_attrs;
1908
1909 while (tmp_list)
1910 {
1911 PangoAttribute *attr = tmp_list->data;
1912
1913 if (attr->klass->type == PANGO_ATTR_SHAPE)
1914 return TRUE;
1915
1916 tmp_list = tmp_list->next;
1917 }
1918
1919 return FALSE;
1920}
1921
1922static void
1923allocate_child_widgets (GtkTextLayout *text_layout,
1924 GtkTextLineDisplay *display)
1925{
1926 PangoLayout *layout = display->layout;
1927 PangoLayoutIter *run_iter;
1928
1929 run_iter = pango_layout_get_iter (layout);
1930 do
1931 {
1932 PangoLayoutRun *run = pango_layout_iter_get_run_readonly (iter: run_iter);
1933
1934 if (run && is_shape (run))
1935 {
1936 int byte_index;
1937 GtkTextIter text_iter;
1938 GtkTextChildAnchor *anchor = NULL;
1939 GtkWidget **widgets = NULL;
1940 guint n_widgets = 0;
1941 guint i;
1942
1943 /* The pango iterator iterates in visual order.
1944 * We use the byte index to find the child widget.
1945 */
1946 byte_index = pango_layout_iter_get_index (iter: run_iter);
1947 line_display_index_to_iter (layout: text_layout, display, iter: &text_iter, index: byte_index, trailing: 0);
1948 anchor = gtk_text_iter_get_child_anchor (iter: &text_iter);
1949 if (anchor)
1950 widgets = gtk_text_child_anchor_get_widgets (anchor, out_len: &n_widgets);
1951
1952 for (i = 0; i < n_widgets; i++)
1953 {
1954 GtkWidget *child = widgets[i];
1955 PangoRectangle extents;
1956
1957 if (_gtk_anchored_child_get_layout (child) == text_layout)
1958 {
1959
1960 /* We emit "allocate_child" with the x,y of
1961 * the widget with respect to the top of the line
1962 * and the left side of the buffer
1963 */
1964 pango_layout_iter_get_run_extents (iter: run_iter,
1965 NULL,
1966 logical_rect: &extents);
1967
1968 g_signal_emit (instance: text_layout,
1969 signal_id: signals[ALLOCATE_CHILD],
1970 detail: 0,
1971 child,
1972 PANGO_PIXELS (extents.x) + display->x_offset,
1973 PANGO_PIXELS (extents.y) + display->top_margin);
1974 }
1975 }
1976
1977 g_free (mem: widgets);
1978 }
1979 }
1980 while (pango_layout_iter_next_run (iter: run_iter));
1981
1982 pango_layout_iter_free (iter: run_iter);
1983}
1984
1985void
1986gtk_text_layout_update_children (GtkTextLayout *text_layout,
1987 GtkTextLineDisplay *display)
1988{
1989 allocate_child_widgets (text_layout, display);
1990}
1991
1992static void
1993convert_color (GdkRGBA *result,
1994 PangoAttrColor *attr)
1995{
1996 result->red = attr->color.red / 65535.;
1997 result->blue = attr->color.blue / 65535.;
1998 result->green = attr->color.green / 65535.;
1999 result->alpha = 1;
2000}
2001
2002/* This function is used to convert the preedit string attributes, which are
2003 * standard PangoAttributes, into the custom attributes used by the text
2004 * widget and insert them into an attr list with a given offset.
2005 */
2006static void
2007add_preedit_attrs (GtkTextLayout *layout,
2008 GtkTextAttributes *style,
2009 PangoAttrList *attrs,
2010 int offset,
2011 gboolean size_only)
2012{
2013 PangoAttrIterator *iter = pango_attr_list_get_iterator (list: layout->preedit_attrs);
2014
2015 do
2016 {
2017 GtkTextAppearance appearance = style->appearance;
2018 PangoFontDescription *font_desc = pango_font_description_copy_static (desc: style->font);
2019 PangoAttribute *insert_attr;
2020 GSList *extra_attrs = NULL;
2021 GSList *tmp_list;
2022 PangoLanguage *language;
2023 int start, end;
2024
2025 pango_attr_iterator_range (iterator: iter, start: &start, end: &end);
2026
2027 if (end == G_MAXINT)
2028 end = layout->preedit_len;
2029
2030 if (end == start)
2031 continue;
2032
2033 pango_attr_iterator_get_font (iterator: iter, desc: font_desc, language: &language, extra_attrs: &extra_attrs);
2034
2035 if (appearance.fg_rgba)
2036 appearance.fg_rgba = gdk_rgba_copy (rgba: appearance.fg_rgba);
2037 if (appearance.bg_rgba)
2038 appearance.bg_rgba = gdk_rgba_copy (rgba: appearance.bg_rgba);
2039 if (appearance.underline_rgba)
2040 appearance.underline_rgba = gdk_rgba_copy (rgba: appearance.underline_rgba);
2041 if (appearance.overline_rgba)
2042 appearance.overline_rgba = gdk_rgba_copy (rgba: appearance.overline_rgba);
2043 if (appearance.strikethrough_rgba)
2044 appearance.strikethrough_rgba = gdk_rgba_copy (rgba: appearance.strikethrough_rgba);
2045
2046 tmp_list = extra_attrs;
2047 while (tmp_list)
2048 {
2049 PangoAttribute *attr = tmp_list->data;
2050 GdkRGBA rgba;
2051
2052 switch ((guint) attr->klass->type)
2053 {
2054 case PANGO_ATTR_FOREGROUND:
2055 convert_color (result: &rgba, attr: (PangoAttrColor *)attr);
2056 if (appearance.fg_rgba)
2057 gdk_rgba_free (rgba: appearance.fg_rgba);
2058 appearance.fg_rgba = gdk_rgba_copy (rgba: &rgba);
2059 break;
2060 case PANGO_ATTR_BACKGROUND:
2061 convert_color (result: &rgba, attr: (PangoAttrColor *)attr);
2062 if (appearance.bg_rgba)
2063 gdk_rgba_free (rgba: appearance.bg_rgba);
2064 appearance.bg_rgba = gdk_rgba_copy (rgba: &rgba);
2065 appearance.draw_bg = TRUE;
2066 break;
2067 case PANGO_ATTR_UNDERLINE:
2068 appearance.underline = ((PangoAttrInt *)attr)->value;
2069 break;
2070 case PANGO_ATTR_UNDERLINE_COLOR:
2071 convert_color (result: &rgba, attr: (PangoAttrColor*)attr);
2072 if (appearance.underline_rgba)
2073 gdk_rgba_free (rgba: appearance.underline_rgba);
2074 appearance.underline_rgba = gdk_rgba_copy (rgba: &rgba);
2075 break;
2076 case PANGO_ATTR_OVERLINE:
2077 appearance.overline = ((PangoAttrInt *)attr)->value;
2078 break;
2079 case PANGO_ATTR_OVERLINE_COLOR:
2080 convert_color (result: &rgba, attr: (PangoAttrColor*)attr);
2081 if (appearance.overline_rgba)
2082 gdk_rgba_free (rgba: appearance.overline_rgba);
2083 appearance.overline_rgba = gdk_rgba_copy (rgba: &rgba);
2084 break;
2085 case PANGO_ATTR_STRIKETHROUGH:
2086 appearance.strikethrough = ((PangoAttrInt *)attr)->value;
2087 break;
2088 case PANGO_ATTR_STRIKETHROUGH_COLOR:
2089 convert_color (result: &rgba, attr: (PangoAttrColor*)attr);
2090 if (appearance.strikethrough_rgba)
2091 gdk_rgba_free (rgba: appearance.strikethrough_rgba);
2092 appearance.strikethrough_rgba = gdk_rgba_copy (rgba: &rgba);
2093 break;
2094 case PANGO_ATTR_RISE:
2095 appearance.rise = ((PangoAttrInt *)attr)->value;
2096 break;
2097 default:
2098 break;
2099 }
2100
2101 pango_attribute_destroy (attr);
2102 tmp_list = tmp_list->next;
2103 }
2104
2105 g_slist_free (list: extra_attrs);
2106
2107 insert_attr = pango_attr_font_desc_new (desc: font_desc);
2108 insert_attr->start_index = start + offset;
2109 insert_attr->end_index = end + offset;
2110
2111 pango_attr_list_insert (list: attrs, attr: insert_attr);
2112
2113 if (language)
2114 {
2115 insert_attr = pango_attr_language_new (language);
2116 insert_attr->start_index = start + offset;
2117 insert_attr->end_index = end + offset;
2118
2119 pango_attr_list_insert (list: attrs, attr: insert_attr);
2120 }
2121
2122 add_generic_attrs (layout, appearance: &appearance, byte_count: end - start,
2123 attrs, start: start + offset,
2124 size_only, TRUE);
2125
2126 if (appearance.fg_rgba)
2127 gdk_rgba_free (rgba: appearance.fg_rgba);
2128 if (appearance.bg_rgba)
2129 gdk_rgba_free (rgba: appearance.bg_rgba);
2130 if (appearance.underline_rgba)
2131 gdk_rgba_free (rgba: appearance.underline_rgba);
2132 if (appearance.overline_rgba)
2133 gdk_rgba_free (rgba: appearance.overline_rgba);
2134 if (appearance.strikethrough_rgba)
2135 gdk_rgba_free (rgba: appearance.strikethrough_rgba);
2136
2137 pango_font_description_free (desc: font_desc);
2138 }
2139 while (pango_attr_iterator_next (iterator: iter));
2140
2141 pango_attr_iterator_destroy (iterator: iter);
2142}
2143
2144/* Iterate over the line and fill in display->cursors.
2145 * It’s a stripped copy of gtk_text_layout_get_line_display() */
2146void
2147gtk_text_layout_update_display_cursors (GtkTextLayout *layout,
2148 GtkTextLine *line,
2149 GtkTextLineDisplay *display)
2150{
2151 GtkTextLineSegment *seg;
2152 GtkTextIter iter;
2153 int layout_byte_offset, buffer_byte_offset;
2154 GSList *cursor_byte_offsets = NULL;
2155 GSList *cursor_segs = NULL;
2156 GSList *tmp_list1, *tmp_list2;
2157
2158 if (!display->cursors_invalid)
2159 return;
2160
2161 display->cursors_invalid = FALSE;
2162
2163 /* Special-case optimization for completely
2164 * invisible lines; makes it faster to deal
2165 * with sequences of invisible lines.
2166 */
2167 if (totally_invisible_line (layout, line, iter: &iter))
2168 return;
2169
2170 /* Iterate over segments */
2171 layout_byte_offset = 0; /* position in the layout text (includes preedit, does not include invisible text) */
2172 buffer_byte_offset = 0; /* position in the buffer line */
2173 seg = _gtk_text_iter_get_any_segment (iter: &iter);
2174 while (seg != NULL)
2175 {
2176 /* Displayable segments */
2177 if (seg->type == &gtk_text_char_type ||
2178 seg->type == &gtk_text_paintable_type ||
2179 seg->type == &gtk_text_child_type)
2180 {
2181 gtk_text_layout_get_iter_at_line (layout, iter: &iter, line,
2182 byte_offset: buffer_byte_offset);
2183
2184 if (!_gtk_text_btree_char_is_invisible (iter: &iter))
2185 layout_byte_offset += seg->byte_count;
2186
2187 buffer_byte_offset += seg->byte_count;
2188 }
2189
2190 /* Marks */
2191 else if (seg->type == &gtk_text_right_mark_type ||
2192 seg->type == &gtk_text_left_mark_type)
2193 {
2194 int cursor_offset = 0;
2195
2196 /* At the insertion point, add the preedit string, if any */
2197
2198 if (_gtk_text_btree_mark_is_insert (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
2199 segment: seg->body.mark.obj))
2200 {
2201 display->insert_index = layout_byte_offset;
2202
2203 if (layout->preedit_len > 0)
2204 {
2205 layout_byte_offset += layout->preedit_len;
2206 /* DO NOT increment the buffer byte offset for preedit */
2207 cursor_offset = layout->preedit_cursor - layout->preedit_len;
2208 }
2209 }
2210
2211 /* Display visible marks */
2212
2213 if (seg->body.mark.visible)
2214 {
2215 cursor_byte_offsets = g_slist_prepend (list: cursor_byte_offsets,
2216 GINT_TO_POINTER (layout_byte_offset + cursor_offset));
2217 cursor_segs = g_slist_prepend (list: cursor_segs, data: seg);
2218 }
2219 }
2220
2221 /* Toggles */
2222 else if (seg->type == &gtk_text_toggle_on_type ||
2223 seg->type == &gtk_text_toggle_off_type)
2224 {
2225 }
2226
2227 else
2228 g_error ("Unknown segment type: %s", seg->type->name);
2229
2230 seg = seg->next;
2231 }
2232
2233 tmp_list1 = cursor_byte_offsets;
2234 tmp_list2 = cursor_segs;
2235 while (tmp_list1)
2236 {
2237 add_cursor (layout, display, seg: tmp_list2->data,
2238 GPOINTER_TO_INT (tmp_list1->data));
2239 tmp_list1 = tmp_list1->next;
2240 tmp_list2 = tmp_list2->next;
2241 }
2242 g_slist_free (list: cursor_byte_offsets);
2243 g_slist_free (list: cursor_segs);
2244}
2245
2246/* Add the tag to the array if it's not there already, and remove
2247 * it otherwise. It keeps the array sorted by tags priority. */
2248static GPtrArray *
2249tags_array_toggle_tag (GPtrArray *array,
2250 GtkTextTag *tag)
2251{
2252 int pos;
2253 GtkTextTag **tags;
2254
2255 if (array == NULL)
2256 array = g_ptr_array_new ();
2257
2258 tags = (GtkTextTag**) array->pdata;
2259
2260 for (pos = 0; pos < array->len && tags[pos]->priv->priority < tag->priv->priority; pos++) ;
2261
2262 if (pos < array->len && tags[pos] == tag)
2263 g_ptr_array_remove_index (array, index_: pos);
2264 else
2265 {
2266 g_ptr_array_set_size (array, length: array->len + 1);
2267 if (pos < array->len - 1)
2268 memmove (dest: array->pdata + pos + 1, src: array->pdata + pos,
2269 n: (array->len - pos - 1) * sizeof (GtkTextTag*));
2270 array->pdata[pos] = tag;
2271 }
2272
2273 return array;
2274}
2275
2276GtkTextLineDisplay *
2277gtk_text_layout_create_display (GtkTextLayout *layout,
2278 GtkTextLine *line,
2279 gboolean size_only)
2280{
2281 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
2282 GtkTextLineDisplay *display;
2283 GtkTextLineSegment *seg;
2284 GtkTextIter iter;
2285 GtkTextAttributes *style;
2286 char *text;
2287 int text_pixel_width;
2288 PangoAttrList *attrs;
2289 int text_allocated, layout_byte_offset;
2290 PangoRectangle extents;
2291 gboolean para_values_set = FALSE;
2292 GSList *cursor_byte_offsets = NULL;
2293 GSList *cursor_segs = NULL;
2294 GSList *tmp_list1, *tmp_list2;
2295 gboolean saw_widget = FALSE;
2296 PangoDirection base_dir;
2297 GPtrArray *tags;
2298 gboolean initial_toggle_segments;
2299 int h_margin;
2300 int h_padding;
2301 PangoAttribute *last_font_attr = NULL;
2302 PangoAttribute *last_scale_attr = NULL;
2303 PangoAttribute *last_fallback_attr = NULL;
2304 GtkTextBTree *btree;
2305
2306 g_return_val_if_fail (line != NULL, NULL);
2307
2308 display = g_rc_box_new0 (GtkTextLineDisplay);
2309
2310 display->mru_link.data = display;
2311 display->size_only = !!size_only;
2312 display->line = line;
2313 display->insert_index = -1;
2314
2315 /* Special-case optimization for completely
2316 * invisible lines; makes it faster to deal
2317 * with sequences of invisible lines.
2318 */
2319 if (totally_invisible_line (layout, line, iter: &iter))
2320 {
2321 display->layout = pango_layout_new (context: layout->ltr_context);
2322 return g_steal_pointer (&display);
2323 }
2324
2325 /* Find the bidi base direction */
2326 base_dir = line->dir_propagated_forward;
2327 if (base_dir == PANGO_DIRECTION_NEUTRAL)
2328 base_dir = line->dir_propagated_back;
2329
2330 if (line == priv->cursor_line &&
2331 line->dir_strong == PANGO_DIRECTION_NEUTRAL)
2332 {
2333 base_dir = (layout->keyboard_direction == GTK_TEXT_DIR_LTR) ?
2334 PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
2335 }
2336
2337 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
2338
2339 /* Allocate space for flat text for buffer
2340 */
2341 text_allocated = _gtk_text_line_byte_count (line);
2342 text = g_malloc (n_bytes: text_allocated);
2343
2344 attrs = pango_attr_list_new ();
2345
2346 /* Iterate over segments, creating display chunks for them, and updating the tags array. */
2347 layout_byte_offset = 0; /* current length of layout text (includes preedit, does not include invisible text) */
2348 seg = _gtk_text_iter_get_any_segment (iter: &iter);
2349 tags = _gtk_text_btree_get_tags (iter: &iter);
2350 initial_toggle_segments = TRUE;
2351 while (seg != NULL)
2352 {
2353 /* Displayable segments */
2354 if (seg->type == &gtk_text_char_type ||
2355 seg->type == &gtk_text_paintable_type ||
2356 seg->type == &gtk_text_child_type)
2357 {
2358 style = get_style (layout, tags);
2359 initial_toggle_segments = FALSE;
2360
2361 /* We have to delay setting the paragraph values until we
2362 * hit the first paintable or text segment because toggles at
2363 * the beginning of the paragraph should affect the
2364 * paragraph-global values
2365 */
2366 if (!para_values_set)
2367 {
2368 set_para_values (layout, base_dir, style, display);
2369 para_values_set = TRUE;
2370 }
2371
2372 /* First see if the chunk is invisible, and ignore it if so. Tk
2373 * looked at tabs, wrap mode, etc. before doing this, but
2374 * that made no sense to me, so I am just skipping the
2375 * invisible chunks
2376 */
2377 if (!style->invisible)
2378 {
2379 if (seg->type == &gtk_text_char_type)
2380 {
2381 /* We don't want to split segments because of marks,
2382 * so we scan forward for more segments only
2383 * separated from us by marks. In theory, we should
2384 * also merge segments with identical styles, even
2385 * if there are toggles in-between
2386 */
2387
2388 int bytes = 0;
2389 GtkTextLineSegment *prev_seg = NULL;
2390
2391 while (seg)
2392 {
2393 if (seg->type == &gtk_text_char_type)
2394 {
2395 memcpy (dest: text + layout_byte_offset, src: seg->body.chars, n: seg->byte_count);
2396 layout_byte_offset += seg->byte_count;
2397 bytes += seg->byte_count;
2398 }
2399 else if (seg->type == &gtk_text_right_mark_type ||
2400 seg->type == &gtk_text_left_mark_type)
2401 {
2402 /* If we have preedit string, break out of this loop - we'll almost
2403 * certainly have different attributes on the preedit string
2404 */
2405
2406 if (layout->preedit_len > 0 &&
2407 _gtk_text_btree_mark_is_insert (tree: btree, segment: seg->body.mark.obj))
2408 break;
2409
2410 if (seg->body.mark.visible)
2411 {
2412 cursor_byte_offsets = g_slist_prepend (list: cursor_byte_offsets, GINT_TO_POINTER (layout_byte_offset));
2413 cursor_segs = g_slist_prepend (list: cursor_segs, data: seg);
2414 if (_gtk_text_btree_mark_is_insert (tree: btree, segment: seg->body.mark.obj))
2415 display->insert_index = layout_byte_offset;
2416 }
2417 }
2418 else
2419 break;
2420
2421 prev_seg = seg;
2422 seg = seg->next;
2423 }
2424
2425 seg = prev_seg; /* Back up one */
2426 add_generic_attrs (layout, appearance: &style->appearance,
2427 byte_count: bytes,
2428 attrs, start: layout_byte_offset - bytes,
2429 size_only, TRUE);
2430 add_text_attrs (layout, style, byte_count: bytes, attrs,
2431 start: layout_byte_offset - bytes, size_only,
2432 last_font_attr: &last_font_attr,
2433 last_scale_attr: &last_scale_attr,
2434 last_fallback_attr: &last_fallback_attr);
2435 }
2436 else if (seg->type == &gtk_text_paintable_type)
2437 {
2438 add_generic_attrs (layout,
2439 appearance: &style->appearance,
2440 byte_count: seg->byte_count,
2441 attrs, start: layout_byte_offset,
2442 size_only, FALSE);
2443 add_paintable_attrs (layout, display, style,
2444 seg, attrs, start: layout_byte_offset);
2445 memcpy (dest: text + layout_byte_offset, src: _gtk_text_unknown_char_utf8,
2446 n: seg->byte_count);
2447 layout_byte_offset += seg->byte_count;
2448 }
2449 else if (seg->type == &gtk_text_child_type)
2450 {
2451 saw_widget = TRUE;
2452
2453 add_generic_attrs (layout, appearance: &style->appearance,
2454 byte_count: seg->byte_count,
2455 attrs, start: layout_byte_offset,
2456 size_only, FALSE);
2457 add_child_attrs (layout, display, style,
2458 seg, attrs, start: layout_byte_offset);
2459 memcpy (dest: text + layout_byte_offset, src: gtk_text_child_anchor_get_replacement (anchor: seg->body.child.obj),
2460 n: seg->byte_count);
2461 layout_byte_offset += seg->byte_count;
2462 }
2463 else
2464 {
2465 /* We don't know this segment type */
2466 g_assert_not_reached ();
2467 }
2468
2469 } /* if (segment was visible) */
2470
2471 release_style (layout, style);
2472 }
2473
2474 /* Toggles */
2475 else if (seg->type == &gtk_text_toggle_on_type ||
2476 seg->type == &gtk_text_toggle_off_type)
2477 {
2478 /* Style may have changed, drop our
2479 current cached style */
2480 invalidate_cached_style (layout);
2481 /* Add the tag only after we have seen some non-toggle non-mark segment,
2482 * otherwise the tag is already accounted for by _gtk_text_btree_get_tags(). */
2483 if (!initial_toggle_segments)
2484 tags = tags_array_toggle_tag (array: tags, tag: seg->body.toggle.info->tag);
2485 }
2486
2487 /* Marks */
2488 else if (seg->type == &gtk_text_right_mark_type ||
2489 seg->type == &gtk_text_left_mark_type)
2490 {
2491 int cursor_offset = 0;
2492
2493 /* At the insertion point, add the preedit string, if any */
2494
2495 if (_gtk_text_btree_mark_is_insert (tree: btree, segment: seg->body.mark.obj))
2496 {
2497 display->insert_index = layout_byte_offset;
2498
2499 if (layout->preedit_len > 0)
2500 {
2501 text_allocated += layout->preedit_len;
2502 text = g_realloc (mem: text, n_bytes: text_allocated);
2503
2504 style = get_style (layout, tags);
2505 add_preedit_attrs (layout, style, attrs, offset: layout_byte_offset, size_only);
2506 release_style (layout, style);
2507
2508 memcpy (dest: text + layout_byte_offset, src: layout->preedit_string, n: layout->preedit_len);
2509 layout_byte_offset += layout->preedit_len;
2510 /* DO NOT increment the buffer byte offset for preedit */
2511
2512 cursor_offset = layout->preedit_cursor - layout->preedit_len;
2513 }
2514 }
2515
2516
2517 /* Display visible marks */
2518
2519 if (seg->body.mark.visible)
2520 {
2521 cursor_byte_offsets = g_slist_prepend (list: cursor_byte_offsets,
2522 GINT_TO_POINTER (layout_byte_offset + cursor_offset));
2523 cursor_segs = g_slist_prepend (list: cursor_segs, data: seg);
2524 }
2525 }
2526
2527 else
2528 g_error ("Unknown segment type: %s", seg->type->name);
2529
2530 seg = seg->next;
2531 }
2532
2533 if (!para_values_set)
2534 {
2535 style = get_style (layout, tags);
2536 set_para_values (layout, base_dir, style, display);
2537 release_style (layout, style);
2538 }
2539
2540 /* Pango doesn't want the trailing paragraph delimiters */
2541
2542 {
2543 /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in
2544 * Unicode 3.0; update this if that changes.
2545 */
2546#define PARAGRAPH_SEPARATOR 0x2029
2547 gunichar ch = 0;
2548
2549 if (layout_byte_offset > 0)
2550 {
2551 const char *prev = g_utf8_prev_char (p: text + layout_byte_offset);
2552 ch = g_utf8_get_char (p: prev);
2553 if (ch == PARAGRAPH_SEPARATOR || ch == '\r' || ch == '\n')
2554 layout_byte_offset = prev - text; /* chop off */
2555
2556 if (ch == '\n' && layout_byte_offset > 0)
2557 {
2558 /* Possibly chop a CR as well */
2559 prev = g_utf8_prev_char (p: text + layout_byte_offset);
2560 if (*prev == '\r')
2561 --layout_byte_offset;
2562 }
2563 }
2564 }
2565
2566 pango_layout_set_text (layout: display->layout, text, length: layout_byte_offset);
2567 pango_layout_set_attributes (layout: display->layout, attrs);
2568
2569 tmp_list1 = cursor_byte_offsets;
2570 tmp_list2 = cursor_segs;
2571 while (tmp_list1)
2572 {
2573 add_cursor (layout, display, seg: tmp_list2->data,
2574 GPOINTER_TO_INT (tmp_list1->data));
2575 tmp_list1 = tmp_list1->next;
2576 tmp_list2 = tmp_list2->next;
2577 }
2578 g_slist_free (list: cursor_byte_offsets);
2579 g_slist_free (list: cursor_segs);
2580
2581 pango_layout_get_extents (layout: display->layout, NULL, logical_rect: &extents);
2582
2583 text_pixel_width = PIXEL_BOUND (extents.width);
2584
2585 h_margin = display->left_margin + display->right_margin;
2586 h_padding = layout->left_padding + layout->right_padding;
2587
2588 display->width = text_pixel_width + h_margin + h_padding;
2589 display->height += PANGO_PIXELS (extents.height);
2590
2591 /* If we aren't wrapping, we need to do the alignment of each
2592 * paragraph ourselves.
2593 */
2594 if (pango_layout_get_width (layout: display->layout) < 0)
2595 {
2596 int excess = display->total_width - text_pixel_width;
2597
2598 switch (pango_layout_get_alignment (layout: display->layout))
2599 {
2600 case PANGO_ALIGN_LEFT:
2601 default:
2602 break;
2603 case PANGO_ALIGN_CENTER:
2604 display->x_offset += excess / 2;
2605 break;
2606 case PANGO_ALIGN_RIGHT:
2607 display->x_offset += excess;
2608 break;
2609 }
2610 }
2611
2612 /* Free this if we aren't in a loop */
2613 if (layout->wrap_loop_count == 0)
2614 invalidate_cached_style (layout);
2615
2616 g_free (mem: text);
2617 pango_attr_list_unref (list: attrs);
2618 if (tags != NULL)
2619 g_ptr_array_free (array: tags, TRUE);
2620
2621 display->has_children = saw_widget;
2622
2623 if (saw_widget)
2624 allocate_child_widgets (text_layout: layout, display);
2625
2626 return g_steal_pointer (&display);
2627}
2628
2629GtkTextLineDisplay *
2630gtk_text_layout_get_line_display (GtkTextLayout *layout,
2631 GtkTextLine *line,
2632 gboolean size_only)
2633{
2634 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
2635
2636 return gtk_text_line_display_cache_get (cache: priv->cache, layout, line, size_only);
2637}
2638
2639static void
2640gtk_text_line_display_finalize (GtkTextLineDisplay *display)
2641{
2642 g_assert (display != NULL);
2643 g_assert (display->cache_iter == NULL);
2644 g_assert (display->mru_link.prev == NULL);
2645 g_assert (display->mru_link.next == NULL);
2646 g_assert (display->mru_link.data == display);
2647
2648 g_clear_object (&display->layout);
2649 g_clear_pointer (&display->cursors, g_array_unref);
2650 g_clear_pointer (&display->node, gsk_render_node_unref);
2651}
2652
2653GtkTextLineDisplay *
2654gtk_text_line_display_ref (GtkTextLineDisplay *display)
2655{
2656 return g_rc_box_acquire (display);
2657}
2658
2659void
2660gtk_text_line_display_unref (GtkTextLineDisplay *display)
2661{
2662 g_rc_box_release_full (mem_block: display, clear_func: (GDestroyNotify)gtk_text_line_display_finalize);
2663}
2664
2665/* Functions to convert iter <=> index for the line of a GtkTextLineDisplay
2666 * taking into account the preedit string and invisible text if necessary.
2667 */
2668static int
2669line_display_iter_to_index (GtkTextLayout *layout,
2670 GtkTextLineDisplay *display,
2671 const GtkTextIter *iter)
2672{
2673 int index;
2674
2675 g_return_val_if_fail (_gtk_text_iter_get_text_line (iter) == display->line, 0);
2676
2677 index = gtk_text_iter_get_visible_line_index (iter);
2678
2679 if (layout->preedit_len > 0 && display->insert_index >= 0)
2680 {
2681 if (index >= display->insert_index)
2682 index += layout->preedit_len;
2683 }
2684
2685 return index;
2686}
2687
2688static void
2689line_display_index_to_iter (GtkTextLayout *layout,
2690 GtkTextLineDisplay *display,
2691 GtkTextIter *iter,
2692 int index,
2693 int trailing)
2694{
2695 g_return_if_fail (!_gtk_text_line_is_last (display->line,
2696 _gtk_text_buffer_get_btree (layout->buffer)));
2697
2698 if (layout->preedit_len > 0 && display->insert_index >= 0)
2699 {
2700 if (index >= display->insert_index + layout->preedit_len)
2701 index -= layout->preedit_len;
2702 else if (index > display->insert_index)
2703 {
2704 index = display->insert_index;
2705 trailing = 0;
2706 }
2707 }
2708
2709 gtk_text_layout_get_iter_at_line (layout, iter, line: display->line, byte_offset: 0);
2710
2711 gtk_text_iter_set_visible_line_index (iter, byte_on_line: index);
2712
2713 if (_gtk_text_iter_get_text_line (iter) != display->line)
2714 {
2715 /* Clamp to end of line - really this clamping should have been done
2716 * before here, maybe in Pango, this is a broken band-aid I think
2717 */
2718 gtk_text_layout_get_iter_at_line (layout, iter, line: display->line, byte_offset: 0);
2719 if (!gtk_text_iter_ends_line (iter))
2720 gtk_text_iter_forward_to_line_end (iter);
2721 }
2722
2723 gtk_text_iter_forward_chars (iter, count: trailing);
2724}
2725
2726static void
2727get_line_at_y (GtkTextLayout *layout,
2728 int y,
2729 GtkTextLine **line,
2730 int *line_top)
2731{
2732 GtkTextBTree *btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
2733
2734 if (y < 0)
2735 y = 0;
2736 if (y > layout->height)
2737 y = layout->height;
2738
2739 *line = _gtk_text_btree_find_line_by_y (tree: btree, view_id: layout, ypixel: y, line_top_y: line_top);
2740 if (*line == NULL)
2741 {
2742 *line = _gtk_text_btree_get_end_iter_line (tree: btree);
2743
2744 if (line_top)
2745 *line_top = _gtk_text_btree_find_line_top (tree: btree, line: *line, view_id: layout);
2746 }
2747}
2748
2749/**
2750 * gtk_text_layout_get_line_at_y:
2751 * @layout: a `GtkLayout`
2752 * @target_iter: the iterator in which the result is stored
2753 * @y: the y position
2754 * @line_top: location to store the y coordinate of the
2755 * top of the line. (Can by %NULL)
2756 *
2757 * Get the iter at the beginning of the line which is displayed
2758 * at the given y.
2759 */
2760void
2761gtk_text_layout_get_line_at_y (GtkTextLayout *layout,
2762 GtkTextIter *target_iter,
2763 int y,
2764 int *line_top)
2765{
2766 GtkTextLine *line;
2767
2768 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
2769 g_return_if_fail (target_iter != NULL);
2770
2771 get_line_at_y (layout, y, line: &line, line_top);
2772 gtk_text_layout_get_iter_at_line (layout, iter: target_iter, line, byte_offset: 0);
2773}
2774
2775gboolean
2776gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout,
2777 GtkTextIter *target_iter,
2778 int x,
2779 int y)
2780{
2781 int trailing;
2782 gboolean inside;
2783
2784 inside = gtk_text_layout_get_iter_at_position (layout, iter: target_iter, trailing: &trailing, x, y);
2785
2786 gtk_text_iter_forward_chars (iter: target_iter, count: trailing);
2787
2788 return inside;
2789}
2790
2791gboolean
2792gtk_text_layout_get_iter_at_position (GtkTextLayout *layout,
2793 GtkTextIter *target_iter,
2794 int *trailing,
2795 int x,
2796 int y)
2797{
2798 GtkTextLine *line;
2799 int byte_index;
2800 int line_top;
2801 GtkTextLineDisplay *display;
2802 gboolean inside;
2803
2804 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
2805 g_return_val_if_fail (target_iter != NULL, FALSE);
2806
2807 get_line_at_y (layout, y, line: &line, line_top: &line_top);
2808
2809 display = gtk_text_layout_get_line_display (layout, line, FALSE);
2810
2811 x -= display->x_offset;
2812 y -= line_top + display->top_margin;
2813
2814 /* If we are below the layout, position the cursor at the last character
2815 * of the line.
2816 */
2817 if (y > display->height - display->top_margin - display->bottom_margin)
2818 {
2819 byte_index = _gtk_text_line_byte_count (line);
2820 if (trailing)
2821 *trailing = 0;
2822
2823 inside = FALSE;
2824 }
2825 else
2826 {
2827 /* Ignore the "outside" return value from pango. Pango is doing
2828 * the right thing even if we are outside the layout in the
2829 * x-direction.
2830 */
2831 inside = pango_layout_xy_to_index (layout: display->layout, x: x * PANGO_SCALE, y: y * PANGO_SCALE,
2832 index_: &byte_index, trailing);
2833 }
2834
2835 line_display_index_to_iter (layout, display, iter: target_iter, index: byte_index, trailing: 0);
2836
2837 gtk_text_line_display_unref (display);
2838
2839 return inside;
2840}
2841
2842
2843/**
2844 * gtk_text_layout_get_cursor_locations:
2845 * @layout: a `GtkTextLayout`
2846 * @iter: a `GtkTextIter`
2847 * @strong_pos: (out) (optional): location to store the strong cursor position
2848 * @weak_pos: (out) (optional): location to store the weak cursor position
2849 *
2850 * Given an iterator within a text layout, determine the positions of the
2851 * strong and weak cursors if the insertion point is at that
2852 * iterator. The position of each cursor is stored as a zero-width
2853 * rectangle. The strong cursor location is the location where
2854 * characters of the directionality equal to the base direction of the
2855 * paragraph are inserted. The weak cursor location is the location
2856 * where characters of the directionality opposite to the base
2857 * direction of the paragraph are inserted.
2858 **/
2859void
2860gtk_text_layout_get_cursor_locations (GtkTextLayout *layout,
2861 GtkTextIter *iter,
2862 GdkRectangle *strong_pos,
2863 GdkRectangle *weak_pos)
2864{
2865 GtkTextLine *line;
2866 GtkTextLineDisplay *display;
2867 int line_top;
2868 int index;
2869 GtkTextIter insert_iter;
2870
2871 PangoRectangle pango_strong_pos;
2872 PangoRectangle pango_weak_pos;
2873
2874 g_return_if_fail (layout != NULL);
2875 g_return_if_fail (iter != NULL);
2876
2877 line = _gtk_text_iter_get_text_line (iter);
2878 display = gtk_text_layout_get_line_display (layout, line, FALSE);
2879 index = line_display_iter_to_index (layout, display, iter);
2880
2881 line_top = _gtk_text_btree_find_line_top (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
2882 line, view_id: layout);
2883
2884 gtk_text_buffer_get_iter_at_mark (buffer: layout->buffer, iter: &insert_iter,
2885 mark: gtk_text_buffer_get_insert (buffer: layout->buffer));
2886
2887 if (gtk_text_iter_equal (lhs: iter, rhs: &insert_iter))
2888 index += layout->preedit_cursor - layout->preedit_len;
2889
2890 pango_layout_get_cursor_pos (layout: display->layout, index_: index,
2891 strong_pos: strong_pos ? &pango_strong_pos : NULL,
2892 weak_pos: weak_pos ? &pango_weak_pos : NULL);
2893
2894 if (strong_pos)
2895 {
2896 strong_pos->x = display->x_offset + pango_strong_pos.x / PANGO_SCALE;
2897 strong_pos->y = line_top + display->top_margin + pango_strong_pos.y / PANGO_SCALE;
2898 strong_pos->width = 0;
2899 strong_pos->height = pango_strong_pos.height / PANGO_SCALE;
2900 }
2901
2902 if (weak_pos)
2903 {
2904 weak_pos->x = display->x_offset + pango_weak_pos.x / PANGO_SCALE;
2905 weak_pos->y = line_top + display->top_margin + pango_weak_pos.y / PANGO_SCALE;
2906 weak_pos->width = 0;
2907 weak_pos->height = pango_weak_pos.height / PANGO_SCALE;
2908 }
2909
2910 gtk_text_line_display_unref (display);
2911}
2912
2913/**
2914 * _gtk_text_layout_get_block_cursor:
2915 * @layout: a `GtkTextLayout`
2916 * @pos: a `GdkRectangle` to store block cursor position
2917 *
2918 * If layout is to display a block cursor, calculates its position
2919 * and returns %TRUE. Otherwise it returns %FALSE. In case when
2920 * cursor is visible, it simply returns the position stored in
2921 * the line display, otherwise it has to compute the position
2922 * (see get_block_cursor()).
2923 **/
2924gboolean
2925_gtk_text_layout_get_block_cursor (GtkTextLayout *layout,
2926 GdkRectangle *pos)
2927{
2928 GtkTextLine *line;
2929 GtkTextLineDisplay *display;
2930 GtkTextIter iter;
2931 GdkRectangle rect;
2932 gboolean block = FALSE;
2933
2934 g_return_val_if_fail (layout != NULL, FALSE);
2935
2936 gtk_text_buffer_get_iter_at_mark (buffer: layout->buffer, iter: &iter,
2937 mark: gtk_text_buffer_get_insert (buffer: layout->buffer));
2938 line = _gtk_text_iter_get_text_line (iter: &iter);
2939 display = gtk_text_layout_get_line_display (layout, line, FALSE);
2940
2941 if (display->has_block_cursor)
2942 {
2943 block = TRUE;
2944 rect = display->block_cursor;
2945 }
2946 else
2947 {
2948 int index = display->insert_index;
2949
2950 if (index < 0)
2951 index = gtk_text_iter_get_line_index (iter: &iter);
2952
2953 if (get_block_cursor (layout, display, insert_iter: &iter, insert_index: index, pos: &rect, NULL))
2954 block = TRUE;
2955 }
2956
2957 if (block && pos)
2958 {
2959 int line_top;
2960
2961 line_top = _gtk_text_btree_find_line_top (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
2962 line, view_id: layout);
2963
2964 *pos = rect;
2965 pos->x += display->x_offset;
2966 pos->y += line_top + display->top_margin;
2967 }
2968
2969 gtk_text_line_display_unref (display);
2970
2971 return block;
2972}
2973
2974/**
2975 * gtk_text_layout_get_line_yrange:
2976 * @layout: a `GtkTextLayout`
2977 * @iter: a `GtkTextIter`
2978 * @y: (nullable): location to store the top of the paragraph in pixels
2979 * @height: (nullable): location to store the height of the paragraph in pixels
2980 *
2981 * Find the range of y coordinates for the paragraph containing
2982 * the given iter.
2983 **/
2984void
2985gtk_text_layout_get_line_yrange (GtkTextLayout *layout,
2986 const GtkTextIter *iter,
2987 int *y,
2988 int *height)
2989{
2990 GtkTextLine *line;
2991
2992 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
2993 g_return_if_fail (_gtk_text_iter_get_btree (iter) == _gtk_text_buffer_get_btree (layout->buffer));
2994
2995 line = _gtk_text_iter_get_text_line (iter);
2996
2997 if (y)
2998 *y = _gtk_text_btree_find_line_top (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
2999 line, view_id: layout);
3000 if (height)
3001 {
3002 GtkTextLineData *line_data = _gtk_text_line_get_data (line, view_id: layout);
3003 if (line_data)
3004 *height = line_data->height;
3005 else
3006 *height = 0;
3007 }
3008}
3009
3010void
3011gtk_text_layout_get_iter_location (GtkTextLayout *layout,
3012 const GtkTextIter *iter,
3013 GdkRectangle *rect)
3014{
3015 PangoRectangle pango_rect;
3016 GtkTextLine *line;
3017 GtkTextBTree *tree;
3018 GtkTextLineDisplay *display;
3019 int byte_index;
3020 int x_offset;
3021
3022 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
3023 g_return_if_fail (_gtk_text_iter_get_btree (iter) == _gtk_text_buffer_get_btree (layout->buffer));
3024 g_return_if_fail (rect != NULL);
3025
3026 tree = _gtk_text_iter_get_btree (iter);
3027 line = _gtk_text_iter_get_text_line (iter);
3028
3029 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3030
3031 rect->y = _gtk_text_btree_find_line_top (tree, line, view_id: layout);
3032
3033 x_offset = display->x_offset * PANGO_SCALE;
3034
3035 byte_index = gtk_text_iter_get_line_index (iter);
3036
3037 pango_layout_index_to_pos (layout: display->layout, index_: byte_index, pos: &pango_rect);
3038
3039 rect->x = PANGO_PIXELS (x_offset + pango_rect.x);
3040 rect->y += PANGO_PIXELS (pango_rect.y) + display->top_margin;
3041 rect->width = PANGO_PIXELS (pango_rect.width);
3042 rect->height = PANGO_PIXELS (pango_rect.height);
3043
3044 gtk_text_line_display_unref (display);
3045}
3046
3047/* FFIXX */
3048
3049/* Find the iter for the logical beginning of the first display line whose
3050 * top y is >= y. If none exists, move the iter to the logical beginning
3051 * of the last line in the buffer.
3052 */
3053static void
3054find_display_line_below (GtkTextLayout *layout,
3055 GtkTextIter *iter,
3056 int y)
3057{
3058 GtkTextBTree *btree;
3059 GtkTextLine *line, *next;
3060 GtkTextLine *found_line = NULL;
3061 int line_top;
3062 int found_byte = 0;
3063
3064 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
3065
3066 line = _gtk_text_btree_find_line_by_y (tree: btree, view_id: layout, ypixel: y, line_top_y: &line_top);
3067 if (!line)
3068 {
3069 line = _gtk_text_btree_get_end_iter_line (tree: btree);
3070
3071 line_top = _gtk_text_btree_find_line_top (tree: btree, line, view_id: layout);
3072 }
3073
3074 while (line && !found_line)
3075 {
3076 GtkTextLineDisplay *display = gtk_text_layout_get_line_display (layout, line, FALSE);
3077 PangoLayoutIter *layout_iter;
3078
3079 layout_iter = pango_layout_get_iter (layout: display->layout);
3080
3081 line_top += display->top_margin;
3082
3083 do
3084 {
3085 int first_y, last_y;
3086 PangoLayoutLine *layout_line = pango_layout_iter_get_line_readonly (iter: layout_iter);
3087
3088 found_byte = pango_layout_line_get_start_index (line: layout_line);
3089
3090 if (line_top >= y)
3091 {
3092 found_line = line;
3093 break;
3094 }
3095
3096 pango_layout_iter_get_line_yrange (iter: layout_iter, y0_: &first_y, y1_: &last_y);
3097 line_top += (last_y - first_y) / PANGO_SCALE;
3098 }
3099 while (pango_layout_iter_next_line (iter: layout_iter));
3100
3101 pango_layout_iter_free (iter: layout_iter);
3102
3103 line_top += display->bottom_margin;
3104 gtk_text_line_display_unref (display);
3105
3106 next = _gtk_text_line_next_excluding_last (line);
3107 if (!next)
3108 found_line = line;
3109
3110 line = next;
3111 }
3112
3113 gtk_text_layout_get_iter_at_line (layout, iter, line: found_line, byte_offset: found_byte);
3114}
3115
3116/* Find the iter for the logical beginning of the last display line whose
3117 * top y is >= y. If none exists, move the iter to the logical beginning
3118 * of the first line in the buffer.
3119 */
3120static void
3121find_display_line_above (GtkTextLayout *layout,
3122 GtkTextIter *iter,
3123 int y)
3124{
3125 GtkTextBTree *btree;
3126 GtkTextLine *line;
3127 GtkTextLine *found_line = NULL;
3128 int line_top;
3129 int found_byte = 0;
3130
3131 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
3132 line = _gtk_text_btree_find_line_by_y (tree: btree, view_id: layout, ypixel: y, line_top_y: &line_top);
3133 if (!line)
3134 {
3135 line = _gtk_text_btree_get_end_iter_line (tree: btree);
3136
3137 line_top = _gtk_text_btree_find_line_top (tree: btree, line, view_id: layout);
3138 }
3139
3140 while (line && !found_line)
3141 {
3142 GtkTextLineDisplay *display = gtk_text_layout_get_line_display (layout, line, FALSE);
3143 PangoRectangle logical_rect;
3144 PangoLayoutIter *layout_iter;
3145 int tmp_top;
3146
3147 layout_iter = pango_layout_get_iter (layout: display->layout);
3148
3149 line_top -= display->top_margin + display->bottom_margin;
3150 pango_layout_iter_get_layout_extents (iter: layout_iter, NULL, logical_rect: &logical_rect);
3151 line_top -= logical_rect.height / PANGO_SCALE;
3152
3153 tmp_top = line_top + display->top_margin;
3154
3155 do
3156 {
3157 int first_y, last_y;
3158 PangoLayoutLine *layout_line = pango_layout_iter_get_line_readonly (iter: layout_iter);
3159
3160 found_byte = pango_layout_line_get_start_index (line: layout_line);
3161
3162 pango_layout_iter_get_line_yrange (iter: layout_iter, y0_: &first_y, y1_: &last_y);
3163
3164 tmp_top -= (last_y - first_y) / PANGO_SCALE;
3165
3166 if (tmp_top < y)
3167 {
3168 found_line = line;
3169 pango_layout_iter_free (iter: layout_iter);
3170 goto done;
3171 }
3172 }
3173 while (pango_layout_iter_next_line (iter: layout_iter));
3174
3175 pango_layout_iter_free (iter: layout_iter);
3176
3177 gtk_text_line_display_unref (display);
3178
3179 line = _gtk_text_line_previous (line);
3180 }
3181
3182 done:
3183
3184 if (found_line)
3185 gtk_text_layout_get_iter_at_line (layout, iter, line: found_line, byte_offset: found_byte);
3186 else
3187 gtk_text_buffer_get_iter_at_offset (buffer: layout->buffer, iter, char_offset: 0);
3188}
3189
3190/**
3191 * gtk_text_layout_clamp_iter_to_vrange:
3192 * @layout: a `GtkTextLayout`
3193 * @iter: a `GtkTextIter`
3194 * @top: the top of the range
3195 * @bottom: the bottom the range
3196 *
3197 * If the iterator is not fully in the range @top <= y < @bottom,
3198 * then, if possible, move it the minimum distance so that the
3199 * iterator in this range.
3200 *
3201 * Returns: %TRUE if the iterator was moved, otherwise %FALSE.
3202 **/
3203gboolean
3204gtk_text_layout_clamp_iter_to_vrange (GtkTextLayout *layout,
3205 GtkTextIter *iter,
3206 int top,
3207 int bottom)
3208{
3209 GdkRectangle iter_rect;
3210
3211 gtk_text_layout_get_iter_location (layout, iter, rect: &iter_rect);
3212
3213 /* If the iter is at least partially above the range, put the iter
3214 * at the first fully visible line after the range.
3215 */
3216 if (iter_rect.y < top)
3217 {
3218 find_display_line_below (layout, iter, y: top);
3219
3220 return TRUE;
3221 }
3222 /* Otherwise, if the iter is at least partially below the screen, put the
3223 * iter on the last logical position of the last completely visible
3224 * line on screen
3225 */
3226 else if (iter_rect.y + iter_rect.height > bottom)
3227 {
3228 find_display_line_above (layout, iter, y: bottom);
3229
3230 return TRUE;
3231 }
3232 else
3233 return FALSE;
3234}
3235
3236/**
3237 * gtk_text_layout_move_iter_to_previous_line:
3238 * @layout: a `GtkLayout`
3239 * @iter: a `GtkTextIter`
3240 *
3241 * Move the iterator to the beginning of the previous line. The lines
3242 * of a wrapped paragraph are treated as distinct for this operation.
3243 **/
3244gboolean
3245gtk_text_layout_move_iter_to_previous_line (GtkTextLayout *layout,
3246 GtkTextIter *iter)
3247{
3248 GtkTextLine *line;
3249 GtkTextLineDisplay *display;
3250 int line_byte;
3251 GSList *tmp_list;
3252 PangoLayoutLine *layout_line;
3253 GtkTextIter orig;
3254 gboolean update_byte = FALSE;
3255
3256 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
3257 g_return_val_if_fail (iter != NULL, FALSE);
3258
3259 orig = *iter;
3260
3261
3262 line = _gtk_text_iter_get_text_line (iter);
3263 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3264 line_byte = line_display_iter_to_index (layout, display, iter);
3265
3266 /* If display->height == 0 then the line is invisible, so don't
3267 * move onto it.
3268 */
3269 while (display->height == 0)
3270 {
3271 GtkTextLine *prev_line;
3272
3273 prev_line = _gtk_text_line_previous (line);
3274
3275 if (prev_line == NULL)
3276 {
3277 line_display_index_to_iter (layout, display, iter, index: 0, trailing: 0);
3278 goto out;
3279 }
3280
3281 gtk_text_line_display_unref (display);
3282
3283 line = prev_line;
3284 display = gtk_text_layout_get_line_display (layout, line: prev_line, FALSE);
3285 update_byte = TRUE;
3286 }
3287
3288 tmp_list = pango_layout_get_lines_readonly (layout: display->layout);
3289 layout_line = tmp_list->data;
3290
3291 if (update_byte)
3292 {
3293 line_byte = pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line);
3294 }
3295
3296 if (line_byte < pango_layout_line_get_length (line: layout_line) || !tmp_list->next) /* first line of paragraph */
3297 {
3298 GtkTextLine *prev_line;
3299
3300 prev_line = _gtk_text_line_previous (line);
3301
3302 /* first line of the whole buffer, do not move the iter and return FALSE */
3303 if (prev_line == NULL)
3304 goto out;
3305
3306 while (prev_line)
3307 {
3308 gtk_text_line_display_unref (display);
3309
3310 display = gtk_text_layout_get_line_display (layout, line: prev_line, FALSE);
3311
3312 if (display->height > 0)
3313 {
3314 tmp_list = g_slist_last (list: pango_layout_get_lines_readonly (layout: display->layout));
3315 layout_line = tmp_list->data;
3316
3317 line_display_index_to_iter (layout, display, iter,
3318 index: pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line), trailing: 0);
3319 break;
3320 }
3321
3322 prev_line = _gtk_text_line_previous (line: prev_line);
3323 }
3324 }
3325 else
3326 {
3327 int prev_offset = pango_layout_line_get_start_index (line: layout_line);
3328
3329 tmp_list = tmp_list->next;
3330 while (tmp_list)
3331 {
3332 layout_line = tmp_list->data;
3333
3334 if (line_byte < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line) ||
3335 !tmp_list->next)
3336 {
3337 line_display_index_to_iter (layout, display, iter, index: prev_offset, trailing: 0);
3338 break;
3339 }
3340
3341 prev_offset = pango_layout_line_get_start_index (line: layout_line);
3342 tmp_list = tmp_list->next;
3343 }
3344 }
3345
3346 out:
3347
3348 gtk_text_line_display_unref (display);
3349
3350 return
3351 !gtk_text_iter_equal (lhs: iter, rhs: &orig) &&
3352 !gtk_text_iter_is_end (iter);
3353}
3354
3355/**
3356 * gtk_text_layout_move_iter_to_next_line:
3357 * @layout: a `GtkLayout`
3358 * @iter: a `GtkTextIter`
3359 *
3360 * Move the iterator to the beginning of the next line. The
3361 * lines of a wrapped paragraph are treated as distinct for
3362 * this operation.
3363 **/
3364gboolean
3365gtk_text_layout_move_iter_to_next_line (GtkTextLayout *layout,
3366 GtkTextIter *iter)
3367{
3368 GtkTextLine *line;
3369 GtkTextLineDisplay *display;
3370 int line_byte;
3371 GtkTextIter orig;
3372 gboolean found = FALSE;
3373 gboolean found_after = FALSE;
3374 gboolean first = TRUE;
3375
3376 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
3377 g_return_val_if_fail (iter != NULL, FALSE);
3378
3379 orig = *iter;
3380
3381 line = _gtk_text_iter_get_text_line (iter);
3382
3383 while (line && !found_after)
3384 {
3385 GSList *tmp_list;
3386
3387 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3388
3389 if (display->height == 0)
3390 goto next;
3391
3392 if (first)
3393 {
3394 line_byte = line_display_iter_to_index (layout, display, iter);
3395 first = FALSE;
3396 }
3397 else
3398 line_byte = 0;
3399
3400 tmp_list = pango_layout_get_lines_readonly (layout: display->layout);
3401 while (tmp_list && !found_after)
3402 {
3403 PangoLayoutLine *layout_line = tmp_list->data;
3404
3405 if (found)
3406 {
3407 line_display_index_to_iter (layout, display, iter,
3408 index: pango_layout_line_get_start_index (line: layout_line), trailing: 0);
3409 found_after = TRUE;
3410 }
3411 else if (line_byte < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line) || !tmp_list->next)
3412 found = TRUE;
3413
3414 tmp_list = tmp_list->next;
3415 }
3416
3417 next:
3418
3419 gtk_text_line_display_unref (display);
3420
3421 line = _gtk_text_line_next_excluding_last (line);
3422 }
3423
3424 if (!found_after)
3425 gtk_text_buffer_get_end_iter (buffer: layout->buffer, iter);
3426
3427 return
3428 !gtk_text_iter_equal (lhs: iter, rhs: &orig) &&
3429 !gtk_text_iter_is_end (iter);
3430}
3431
3432/**
3433 * gtk_text_layout_move_iter_to_line_end:
3434 * @layout: a `GtkTextLayout`
3435 * @direction: if negative, move to beginning of line, otherwise
3436 move to end of line.
3437 *
3438 * Move to the beginning or end of a display line.
3439 **/
3440gboolean
3441gtk_text_layout_move_iter_to_line_end (GtkTextLayout *layout,
3442 GtkTextIter *iter,
3443 int direction)
3444{
3445 GtkTextLine *line;
3446 GtkTextLineDisplay *display;
3447 int line_byte;
3448 GSList *tmp_list;
3449 GtkTextIter orig;
3450
3451 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
3452 g_return_val_if_fail (iter != NULL, FALSE);
3453
3454 orig = *iter;
3455
3456 line = _gtk_text_iter_get_text_line (iter);
3457 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3458 line_byte = line_display_iter_to_index (layout, display, iter);
3459
3460 tmp_list = pango_layout_get_lines_readonly (layout: display->layout);
3461 while (tmp_list)
3462 {
3463 PangoLayoutLine *layout_line = tmp_list->data;
3464
3465 if (line_byte < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line) || !tmp_list->next)
3466 {
3467 line_display_index_to_iter (layout, display, iter,
3468 index: direction < 0 ? pango_layout_line_get_start_index (line: layout_line) : pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line),
3469 trailing: 0);
3470
3471 /* FIXME: As a bad hack, we move back one position when we
3472 * are inside a paragraph to avoid going to next line on a
3473 * forced break not at whitespace. Real fix is to keep track
3474 * of whether marks are at leading or trailing edge? */
3475 if (direction > 0 && pango_layout_line_get_length (line: layout_line) > 0 &&
3476 !gtk_text_iter_ends_line (iter) &&
3477 !_gtk_text_btree_char_is_invisible (iter))
3478 gtk_text_iter_backward_char (iter);
3479 break;
3480 }
3481
3482 tmp_list = tmp_list->next;
3483 }
3484
3485 gtk_text_line_display_unref (display);
3486
3487 return
3488 !gtk_text_iter_equal (lhs: iter, rhs: &orig) &&
3489 !gtk_text_iter_is_end (iter);
3490}
3491
3492
3493/**
3494 * gtk_text_layout_iter_starts_line:
3495 * @layout: a `GtkTextLayout`
3496 * @iter: iterator to test
3497 *
3498 * Tests whether an iterator is at the start of a display line.
3499 **/
3500gboolean
3501gtk_text_layout_iter_starts_line (GtkTextLayout *layout,
3502 const GtkTextIter *iter)
3503{
3504 GtkTextLine *line;
3505 GtkTextLineDisplay *display;
3506 int line_byte;
3507 GSList *tmp_list;
3508
3509 g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
3510 g_return_val_if_fail (iter != NULL, FALSE);
3511
3512 line = _gtk_text_iter_get_text_line (iter);
3513 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3514 line_byte = line_display_iter_to_index (layout, display, iter);
3515
3516 tmp_list = pango_layout_get_lines_readonly (layout: display->layout);
3517 while (tmp_list)
3518 {
3519 PangoLayoutLine *layout_line = tmp_list->data;
3520
3521 if (line_byte < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line) ||
3522 !tmp_list->next)
3523 {
3524 /* We're located on this line or the para delimiters before
3525 * it
3526 */
3527 gtk_text_line_display_unref (display);
3528
3529 if (line_byte == pango_layout_line_get_start_index (line: layout_line))
3530 return TRUE;
3531 else
3532 return FALSE;
3533 }
3534
3535 tmp_list = tmp_list->next;
3536 }
3537
3538 g_assert_not_reached ();
3539 return FALSE;
3540}
3541
3542void
3543gtk_text_layout_get_iter_at_line (GtkTextLayout *layout,
3544 GtkTextIter *iter,
3545 GtkTextLine *line,
3546 int byte_offset)
3547{
3548 _gtk_text_btree_get_iter_at_line (tree: _gtk_text_buffer_get_btree (buffer: layout->buffer),
3549 iter, line, byte_offset);
3550}
3551
3552/**
3553 * gtk_text_layout_move_iter_to_x:
3554 * @layout: a `GtkTextLayout`
3555 * @iter: a `GtkTextIter`
3556 * @x: X coordinate
3557 *
3558 * Keeping the iterator on the same line of the layout, move it to the
3559 * specified X coordinate. The lines of a wrapped paragraph are
3560 * treated as distinct for this operation.
3561 **/
3562void
3563gtk_text_layout_move_iter_to_x (GtkTextLayout *layout,
3564 GtkTextIter *iter,
3565 int x)
3566{
3567 GtkTextLine *line;
3568 GtkTextLineDisplay *display;
3569 int line_byte;
3570 PangoLayoutIter *layout_iter;
3571
3572 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
3573 g_return_if_fail (iter != NULL);
3574
3575 line = _gtk_text_iter_get_text_line (iter);
3576
3577 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3578 line_byte = line_display_iter_to_index (layout, display, iter);
3579
3580 layout_iter = pango_layout_get_iter (layout: display->layout);
3581
3582 do
3583 {
3584 PangoLayoutLine *layout_line = pango_layout_iter_get_line_readonly (iter: layout_iter);
3585
3586 if (line_byte < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line) ||
3587 pango_layout_iter_at_last_line (iter: layout_iter))
3588 {
3589 PangoRectangle logical_rect;
3590 int byte_index, trailing;
3591 int x_offset = display->x_offset * PANGO_SCALE;
3592
3593 pango_layout_iter_get_line_extents (iter: layout_iter, NULL, logical_rect: &logical_rect);
3594
3595 pango_layout_line_x_to_index (line: layout_line,
3596 x_pos: x * PANGO_SCALE - x_offset - logical_rect.x,
3597 index_: &byte_index, trailing: &trailing);
3598
3599 line_display_index_to_iter (layout, display, iter, index: byte_index, trailing);
3600
3601 break;
3602 }
3603 }
3604 while (pango_layout_iter_next_line (iter: layout_iter));
3605
3606 pango_layout_iter_free (iter: layout_iter);
3607
3608 gtk_text_line_display_unref (display);
3609}
3610
3611/**
3612 * gtk_text_layout_move_iter_visually:
3613 * @layout: a `GtkTextLayout`
3614 * @iter: a `GtkTextIter`
3615 * @count: number of characters to move (negative moves left, positive moves right)
3616 *
3617 * Move the iterator a given number of characters visually, treating
3618 * it as the strong cursor position. If @count is positive, then the
3619 * new strong cursor position will be @count positions to the right of
3620 * the old cursor position. If @count is negative then the new strong
3621 * cursor position will be @count positions to the left of the old
3622 * cursor position.
3623 *
3624 * In the presence of bidirection text, the correspondence
3625 * between logical and visual order will depend on the direction
3626 * of the current run, and there may be jumps when the cursor
3627 * is moved off of the end of a run.
3628 **/
3629
3630gboolean
3631gtk_text_layout_move_iter_visually (GtkTextLayout *layout,
3632 GtkTextIter *iter,
3633 int count)
3634{
3635 GtkTextLineDisplay *display = NULL;
3636 GtkTextIter orig;
3637 GtkTextIter lineiter;
3638
3639 g_return_val_if_fail (layout != NULL, FALSE);
3640 g_return_val_if_fail (iter != NULL, FALSE);
3641
3642 orig = *iter;
3643
3644 while (count != 0)
3645 {
3646 GtkTextLine *line = _gtk_text_iter_get_text_line (iter);
3647 int line_byte;
3648 int extra_back = 0;
3649 gboolean strong;
3650
3651 int byte_count = _gtk_text_line_byte_count (line);
3652
3653 int new_index;
3654 int new_trailing;
3655
3656 if (!display)
3657 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3658
3659 if (layout->cursor_direction == GTK_TEXT_DIR_NONE)
3660 strong = TRUE;
3661 else
3662 strong = display->direction == layout->cursor_direction;
3663
3664 line_byte = line_display_iter_to_index (layout, display, iter);
3665
3666 if (count > 0)
3667 {
3668 pango_layout_move_cursor_visually (layout: display->layout, strong, old_index: line_byte, old_trailing: 0, direction: 1, new_index: &new_index, new_trailing: &new_trailing);
3669 count--;
3670 }
3671 else
3672 {
3673 pango_layout_move_cursor_visually (layout: display->layout, strong, old_index: line_byte, old_trailing: 0, direction: -1, new_index: &new_index, new_trailing: &new_trailing);
3674 count++;
3675 }
3676
3677 /* We need to handle the preedit string specially. Well, we don't really need to
3678 * handle it specially, since hopefully calling gtk_im_context_reset() will
3679 * remove the preedit string; but if we start off in front of the preedit
3680 * string (logically) and end up in or on the back edge of the preedit string,
3681 * we should move the iter one place farther.
3682 */
3683 if (layout->preedit_len > 0 && display->insert_index >= 0)
3684 {
3685 if (line_byte == display->insert_index + layout->preedit_len &&
3686 new_index < display->insert_index + layout->preedit_len)
3687 {
3688 line_byte = display->insert_index;
3689 extra_back = 1;
3690 }
3691 }
3692
3693 if (new_index < 0 || (new_index == 0 && extra_back))
3694 {
3695 do
3696 {
3697 line = _gtk_text_line_previous (line);
3698 if (!line)
3699 goto done;
3700 }
3701 while (totally_invisible_line (layout, line, iter: &lineiter));
3702
3703 gtk_text_line_display_unref (display);
3704 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3705 gtk_text_iter_forward_to_line_end (iter: &lineiter);
3706 new_index = gtk_text_iter_get_visible_line_index (iter: &lineiter);
3707 }
3708 else if (new_index > byte_count)
3709 {
3710 do
3711 {
3712 line = _gtk_text_line_next_excluding_last (line);
3713 if (!line)
3714 goto done;
3715 }
3716 while (totally_invisible_line (layout, line, iter: &lineiter));
3717
3718 gtk_text_line_display_unref (display);
3719 display = gtk_text_layout_get_line_display (layout, line, FALSE);
3720 new_index = 0;
3721 }
3722
3723 line_display_index_to_iter (layout, display, iter, index: new_index, trailing: new_trailing);
3724 if (extra_back)
3725 gtk_text_iter_backward_char (iter);
3726 }
3727
3728 g_clear_pointer (&display, gtk_text_line_display_unref);
3729
3730 done:
3731
3732 return
3733 !gtk_text_iter_equal (lhs: iter, rhs: &orig) &&
3734 !gtk_text_iter_is_end (iter);
3735}
3736
3737void
3738gtk_text_layout_spew (GtkTextLayout *layout)
3739{
3740#if 0
3741 GtkTextDisplayLine *iter;
3742 guint wrapped = 0;
3743 guint paragraphs = 0;
3744 GtkTextLine *last_line = NULL;
3745
3746 iter = layout->line_list;
3747 while (iter != NULL)
3748 {
3749 if (iter->line != last_line)
3750 {
3751 printf ("%5u paragraph (%p)\n", paragraphs, iter->line);
3752 ++paragraphs;
3753 last_line = iter->line;
3754 }
3755
3756 printf (" %5u y: %d len: %d start: %d bytes: %d\n",
3757 wrapped, iter->y, iter->length, iter->byte_offset,
3758 iter->byte_count);
3759
3760 ++wrapped;
3761 iter = iter->next;
3762 }
3763
3764 printf ("Layout %s recompute\n",
3765 layout->need_recompute ? "needs" : "doesn't need");
3766
3767 printf ("Layout pars: %u lines: %u size: %d x %d Screen width: %d\n",
3768 paragraphs, wrapped, layout->width,
3769 layout->height, layout->screen_width);
3770#endif
3771}
3772
3773static void
3774gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer,
3775 const GtkTextIter *location,
3776 GtkTextMark *mark,
3777 gpointer data)
3778{
3779 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3780 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
3781
3782 if (mark == gtk_text_buffer_get_insert (buffer))
3783 gtk_text_line_display_cache_set_cursor_line (cache: priv->cache, NULL);
3784}
3785
3786/* Catch all situations that move the insertion point.
3787 */
3788static void
3789gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer,
3790 const GtkTextIter *location,
3791 GtkTextMark *mark,
3792 gpointer data)
3793{
3794 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3795 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
3796
3797 if (mark == gtk_text_buffer_get_insert (buffer))
3798 {
3799 gtk_text_layout_update_cursor_line (layout);
3800 gtk_text_line_display_cache_set_cursor_line (cache: priv->cache, line: priv->cursor_line);
3801 }
3802}
3803
3804static void
3805gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer,
3806 GtkTextIter *iter,
3807 char *str,
3808 int len,
3809 gpointer data)
3810{
3811 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3812 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
3813 GtkTextLine *line = _gtk_text_iter_get_text_line (iter);
3814
3815 gtk_text_line_display_cache_invalidate_line (cache: priv->cache, line);
3816}
3817
3818static void
3819gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer,
3820 GtkTextIter *iter,
3821 char *str,
3822 int len,
3823 gpointer data)
3824{
3825 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3826
3827 gtk_text_layout_update_cursor_line (layout);
3828}
3829
3830static void
3831gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer,
3832 GtkTextIter *start,
3833 GtkTextIter *end,
3834 gpointer data)
3835{
3836 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3837 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
3838
3839 gtk_text_line_display_cache_invalidate_range (cache: priv->cache, layout, begin: start, end, FALSE);
3840}
3841
3842static void
3843gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer,
3844 GtkTextIter *start,
3845 GtkTextIter *end,
3846 gpointer data)
3847{
3848 GtkTextLayout *layout = GTK_TEXT_LAYOUT (data);
3849
3850 gtk_text_layout_update_cursor_line (layout);
3851}
3852
3853static void
3854render_para (GskPangoRenderer *crenderer,
3855 GtkTextLineDisplay *line_display,
3856 int selection_start_index,
3857 int selection_end_index,
3858 const GdkRGBA *selection,
3859 gboolean draw_selection_text,
3860 float cursor_alpha)
3861{
3862 PangoLayout *layout = line_display->layout;
3863 int byte_offset = 0;
3864 PangoLayoutIter *iter;
3865 int screen_width;
3866 gboolean first = TRUE;
3867
3868 iter = pango_layout_get_iter (layout);
3869 screen_width = line_display->total_width;
3870
3871 do
3872 {
3873 PangoLayoutLine *line = pango_layout_iter_get_line_readonly (iter);
3874 int selection_y, selection_height;
3875 int first_y, last_y;
3876 PangoRectangle line_rect;
3877 int baseline;
3878 gboolean at_last_line;
3879
3880 pango_layout_iter_get_line_extents (iter, NULL, logical_rect: &line_rect);
3881 baseline = pango_layout_iter_get_baseline (iter);
3882 pango_layout_iter_get_line_yrange (iter, y0_: &first_y, y1_: &last_y);
3883
3884 /* Adjust for margins */
3885
3886 line_rect.x += line_display->x_offset * PANGO_SCALE;
3887 line_rect.y += line_display->top_margin * PANGO_SCALE;
3888 baseline += line_display->top_margin * PANGO_SCALE;
3889
3890 /* Selection is the height of the line, plus top/bottom
3891 * margin if we're the first/last line
3892 */
3893 selection_y = PANGO_PIXELS (first_y) + line_display->top_margin;
3894 selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y);
3895
3896 if (first)
3897 {
3898 selection_y -= line_display->top_margin;
3899 selection_height += line_display->top_margin;
3900 first = FALSE;
3901 }
3902
3903 at_last_line = pango_layout_iter_at_last_line (iter);
3904 if (at_last_line)
3905 selection_height += line_display->bottom_margin;
3906
3907 /* Don't draw the text underneath if the whole line is selected. We can
3908 * only do it if the selection is opaque.
3909 */
3910 if (selection_start_index < byte_offset &&
3911 selection_end_index > pango_layout_line_get_length (line) + byte_offset &&
3912 selection->alpha >= 1)
3913 {
3914 gtk_snapshot_append_color (snapshot: crenderer->snapshot,
3915 color: selection,
3916 bounds: &GRAPHENE_RECT_INIT (line_display->left_margin,
3917 selection_y,
3918 screen_width,
3919 selection_height));
3920
3921 if (draw_selection_text)
3922 {
3923 gsk_pango_renderer_set_state (crenderer, state: GSK_PANGO_RENDERER_SELECTED);
3924 pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
3925 line,
3926 x: line_rect.x,
3927 y: baseline);
3928 }
3929 }
3930 else
3931 {
3932 if (line_display->pg_bg_rgba_set)
3933 gtk_snapshot_append_color (snapshot: crenderer->snapshot,
3934 color: &line_display->pg_bg_rgba,
3935 bounds: &GRAPHENE_RECT_INIT (line_display->left_margin,
3936 selection_y,
3937 screen_width,
3938 selection_height));
3939
3940 gsk_pango_renderer_set_state (crenderer, state: GSK_PANGO_RENDERER_NORMAL);
3941 pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
3942 line,
3943 x: line_rect.x,
3944 y: baseline);
3945
3946 /* Check if some part of the line is selected; the newline
3947 * that is after pango_layout_line_get_length (line) for the last line of the
3948 * paragraph counts as part of the line for this
3949 */
3950 if ((selection_start_index < byte_offset + pango_layout_line_get_length (line) ||
3951 (selection_start_index == byte_offset + pango_layout_line_get_length (line) && pango_layout_iter_at_last_line (iter))) &&
3952 selection_end_index > byte_offset)
3953 {
3954 int *ranges = NULL;
3955 int n_ranges, i;
3956
3957 pango_layout_line_get_x_ranges (line, start_index: selection_start_index, end_index: selection_end_index, ranges: &ranges, n_ranges: &n_ranges);
3958
3959 gsk_pango_renderer_set_state (crenderer, state: GSK_PANGO_RENDERER_SELECTED);
3960
3961 for (i = 0; i < n_ranges; i++)
3962 {
3963 graphene_rect_t bounds;
3964
3965 bounds.origin.x = line_display->x_offset + PANGO_PIXELS (ranges[2*i]);
3966 bounds.origin.y = selection_y;
3967 bounds.size.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]);
3968 bounds.size.height = selection_height;
3969
3970 if (bounds.origin.x < PANGO_PIXELS (line_rect.x))
3971 {
3972 bounds.size.width -= PANGO_PIXELS (line_rect.x) - bounds.origin.x;
3973 bounds.origin.x = PANGO_PIXELS (line_rect.x);
3974 }
3975
3976 bounds.size.width = MIN (bounds.size.width,
3977 PANGO_PIXELS (line_rect.x) +
3978 PANGO_PIXELS (line_rect.width) -
3979 bounds.origin.x);
3980
3981 gtk_snapshot_append_color (snapshot: crenderer->snapshot, color: selection, bounds: &bounds);
3982
3983 if (draw_selection_text)
3984 {
3985 gtk_snapshot_push_clip (snapshot: crenderer->snapshot, bounds: &bounds);
3986 pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
3987 line,
3988 x: line_rect.x,
3989 y: baseline);
3990 gtk_snapshot_pop (snapshot: crenderer->snapshot);
3991 }
3992 }
3993
3994 g_free (mem: ranges);
3995
3996 /* Paint in the ends of the line */
3997 if (line_rect.x > line_display->left_margin * PANGO_SCALE &&
3998 ((line_display->direction == GTK_TEXT_DIR_LTR && selection_start_index < byte_offset) ||
3999 (line_display->direction == GTK_TEXT_DIR_RTL && selection_end_index > byte_offset + pango_layout_line_get_length (line))))
4000 gtk_snapshot_append_color (snapshot: crenderer->snapshot,
4001 color: selection,
4002 bounds: &GRAPHENE_RECT_INIT (line_display->left_margin,
4003 selection_y,
4004 PANGO_PIXELS (line_rect.x) - line_display->left_margin,
4005 selection_height));
4006
4007 if (line_rect.x + line_rect.width <
4008 (screen_width + line_display->left_margin) * PANGO_SCALE &&
4009 ((line_display->direction == GTK_TEXT_DIR_LTR && selection_end_index > byte_offset + pango_layout_line_get_length (line)) ||
4010 (line_display->direction == GTK_TEXT_DIR_RTL && selection_start_index < byte_offset)))
4011 {
4012 int nonlayout_width = line_display->left_margin
4013 + screen_width
4014 - PANGO_PIXELS (line_rect.x)
4015 - PANGO_PIXELS (line_rect.width);
4016 gtk_snapshot_append_color (snapshot: crenderer->snapshot,
4017 color: selection,
4018 bounds: &GRAPHENE_RECT_INIT (PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width),
4019 selection_y,
4020 nonlayout_width,
4021 selection_height));
4022 }
4023 }
4024 else if (line_display->has_block_cursor &&
4025 gtk_widget_has_focus (widget: crenderer->widget) &&
4026 cursor_alpha > 0 &&
4027 byte_offset <= line_display->insert_index &&
4028 (line_display->insert_index < byte_offset + pango_layout_line_get_length (line) ||
4029 (at_last_line && line_display->insert_index == byte_offset + pango_layout_line_get_length (line))))
4030 {
4031 GtkStyleContext *context;
4032 GdkRGBA cursor_color;
4033 graphene_rect_t bounds = {
4034 .origin.x = line_display->x_offset + line_display->block_cursor.x,
4035 .origin.y = line_display->block_cursor.y + line_display->top_margin,
4036 .size.width = line_display->block_cursor.width,
4037 .size.height = line_display->block_cursor.height,
4038 };
4039
4040 /* we draw text using base color on filled cursor rectangle
4041 * of cursor color (normally white on black)
4042 */
4043 context = _gtk_widget_get_style_context (widget: crenderer->widget);
4044 _gtk_style_context_get_cursor_color (context, primary_color: &cursor_color, NULL);
4045
4046 gtk_snapshot_push_opacity (snapshot: crenderer->snapshot, opacity: cursor_alpha);
4047 gtk_snapshot_append_color (snapshot: crenderer->snapshot, color: &cursor_color, bounds: &bounds);
4048
4049 /* draw text under the cursor if any */
4050 if (!line_display->cursor_at_line_end)
4051 {
4052 gsk_pango_renderer_set_state (crenderer, state: GSK_PANGO_RENDERER_CURSOR);
4053 gtk_snapshot_push_clip (snapshot: crenderer->snapshot, bounds: &bounds);
4054 pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
4055 line,
4056 x: line_rect.x,
4057 y: baseline);
4058 gtk_snapshot_pop (snapshot: crenderer->snapshot);
4059 }
4060 gtk_snapshot_pop (snapshot: crenderer->snapshot);
4061 }
4062 }
4063
4064 byte_offset += pango_layout_line_get_length (line);
4065 }
4066 while (pango_layout_iter_next_line (iter));
4067
4068 pango_layout_iter_free (iter);
4069}
4070
4071static gboolean
4072snapshot_shape (PangoAttrShape *attr,
4073 GdkSnapshot *snapshot,
4074 double width,
4075 double height)
4076{
4077 if (GDK_IS_PAINTABLE (ptr: attr->data))
4078 {
4079 gdk_paintable_snapshot (paintable: GDK_PAINTABLE (ptr: attr->data), snapshot, width, height);
4080 return TRUE;
4081 }
4082
4083 return FALSE;
4084}
4085
4086void
4087gtk_text_layout_snapshot (GtkTextLayout *layout,
4088 GtkWidget *widget,
4089 GtkSnapshot *snapshot,
4090 const GdkRectangle *clip,
4091 float cursor_alpha)
4092{
4093 GtkTextLayoutPrivate *priv;
4094 GskPangoRenderer *crenderer;
4095 GtkStyleContext *context;
4096 int offset_y;
4097 GtkTextIter selection_start, selection_end;
4098 int selection_start_line;
4099 int selection_end_line;
4100 gboolean have_selection;
4101 gboolean draw_selection_text;
4102 const GdkRGBA *selection;
4103 GdkRGBA color;
4104 GtkSnapshot *cursor_snapshot;
4105 GtkTextBTree *btree;
4106 GtkTextLine *first_line;
4107 GtkTextLine *last_line;
4108
4109 g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
4110 g_return_if_fail (layout->default_style != NULL);
4111 g_return_if_fail (layout->buffer != NULL);
4112 g_return_if_fail (snapshot != NULL);
4113
4114 priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
4115
4116 if (clip->height <= 0)
4117 return;
4118
4119 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
4120
4121 first_line = _gtk_text_btree_find_line_by_y (tree: btree, view_id: layout, ypixel: clip->y, line_top_y: &offset_y);
4122 if (first_line == NULL)
4123 return;
4124
4125 last_line = _gtk_text_btree_find_line_by_y (tree: btree, view_id: layout, ypixel: clip->y + clip->height - 1, NULL);
4126 if (last_line == NULL)
4127 last_line = _gtk_text_btree_get_end_iter_line (tree: btree);
4128
4129 context = gtk_widget_get_style_context (widget);
4130 gtk_style_context_get_color (context, color: &color);
4131
4132 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (0, offset_y));
4133 offset_y = 0;
4134
4135 cursor_snapshot = NULL;
4136
4137 crenderer = gsk_pango_renderer_acquire ();
4138
4139 gsk_pango_renderer_set_shape_handler (crenderer, handler: snapshot_shape);
4140
4141 crenderer->widget = widget;
4142 crenderer->snapshot = snapshot;
4143 crenderer->fg_color = &color;
4144
4145 have_selection = gtk_text_buffer_get_selection_bounds (buffer: layout->buffer,
4146 start: &selection_start,
4147 end: &selection_end);
4148 if (have_selection)
4149 {
4150 GtkCssNode *selection_node;
4151 GdkRGBA text_color;
4152
4153 selection_start_line = gtk_text_iter_get_line (iter: &selection_start);
4154 selection_end_line = gtk_text_iter_get_line (iter: &selection_end);
4155
4156 selection_node = gtk_text_view_get_selection_node (text_view: (GtkTextView*)widget);
4157 gtk_style_context_save_to_node (context, node: selection_node);
4158
4159 selection = gtk_css_color_value_get_rgba (color: _gtk_style_context_peek_property (context, property_id: GTK_CSS_PROPERTY_BACKGROUND_COLOR));
4160
4161 gtk_style_context_get_color (context, color: &text_color);
4162 draw_selection_text = text_color.alpha > 0;
4163
4164 gtk_style_context_restore (context);
4165 }
4166 else
4167 {
4168 selection_start_line = -1;
4169 selection_end_line = -1;
4170 selection = NULL;
4171 draw_selection_text = FALSE;
4172 }
4173
4174 gtk_text_layout_wrap_loop_start (layout);
4175
4176 for (GtkTextLine *line = first_line;
4177 line != NULL;
4178 line = _gtk_text_line_next_excluding_last (line))
4179 {
4180 GtkTextLineDisplay *line_display;
4181 int selection_start_index = -1;
4182 int selection_end_index = -1;
4183
4184 line_display = gtk_text_layout_get_line_display (layout, line, FALSE);
4185
4186 if (line_display->height > 0)
4187 {
4188 g_assert (line_display->layout != NULL);
4189
4190 if (have_selection)
4191 {
4192 GtkTextIter line_start;
4193 int current_line;
4194
4195 gtk_text_layout_get_iter_at_line (layout, iter: &line_start, line, byte_offset: 0);
4196 current_line = gtk_text_iter_get_line (iter: &line_start);
4197
4198 if (selection_start_line < current_line)
4199 selection_start_index = -1;
4200 else if (selection_start_line == current_line)
4201 selection_start_index = gtk_text_iter_get_visible_line_index (iter: &selection_start);
4202 else
4203 selection_start_index = -1;
4204
4205 if (selection_end_line < current_line)
4206 {
4207 selection_end_index = -1;
4208 have_selection = FALSE;
4209 }
4210 else if (selection_end_line == current_line)
4211 selection_end_index = gtk_text_iter_get_visible_line_index (iter: &selection_end);
4212 else if (selection_start_line <= current_line)
4213 {
4214 GtkTextIter line_end = line_start;
4215 int byte_count;
4216
4217 if (!gtk_text_iter_ends_line (iter: &line_end))
4218 gtk_text_iter_forward_to_line_end (iter: &line_end);
4219 byte_count = gtk_text_iter_get_visible_line_index (iter: &line_end);
4220 selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */
4221 }
4222 else
4223 selection_end_index = -1;
4224 }
4225
4226 if (line_display->node != NULL)
4227 {
4228 if (line_display->has_block_cursor && gtk_widget_has_focus (widget))
4229 g_clear_pointer (&line_display->node, gsk_render_node_unref);
4230 }
4231
4232 if (line_display->node == NULL &&
4233 (pango_layout_get_character_count (layout: line_display->layout) > 0 ||
4234 selection_start_index != -1 || selection_end_index != -1 ||
4235 line_display->has_block_cursor))
4236 {
4237 gtk_snapshot_push_collect (snapshot);
4238 render_para (crenderer, line_display,
4239 selection_start_index, selection_end_index,
4240 selection,
4241 draw_selection_text,
4242 cursor_alpha);
4243 line_display->node = gtk_snapshot_pop_collect (snapshot);
4244 }
4245
4246 if (line_display->node != NULL)
4247 {
4248 gtk_snapshot_save (snapshot: crenderer->snapshot);
4249 gtk_snapshot_translate (snapshot: crenderer->snapshot,
4250 point: &GRAPHENE_POINT_INIT (0, offset_y));
4251 gtk_snapshot_append_node (snapshot: crenderer->snapshot, node: line_display->node);
4252 gtk_snapshot_restore (snapshot: crenderer->snapshot);
4253 }
4254
4255 /* We paint the cursors last, because they overlap another chunk
4256 * and need to appear on top.
4257 */
4258 if (line_display->cursors != NULL)
4259 {
4260 if (cursor_snapshot == NULL)
4261 cursor_snapshot = gtk_snapshot_new ();
4262
4263 for (int i = 0; i < line_display->cursors->len; i++)
4264 {
4265 PangoDirection dir;
4266 CursorPosition cursor;
4267
4268 cursor = g_array_index (line_display->cursors, CursorPosition, i);
4269 dir = (line_display->direction == GTK_TEXT_DIR_RTL) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
4270
4271 if (cursor.is_insert || cursor.is_selection_bound)
4272 gtk_snapshot_push_opacity (snapshot: cursor_snapshot, opacity: cursor_alpha);
4273
4274 gtk_snapshot_render_insertion_cursor (snapshot: cursor_snapshot, context,
4275 x: line_display->x_offset, y: offset_y + line_display->top_margin,
4276 layout: line_display->layout, index: cursor.pos, direction: dir);
4277
4278 if (cursor.is_insert || cursor.is_selection_bound)
4279 gtk_snapshot_pop (snapshot: cursor_snapshot);
4280 }
4281 }
4282 } /* line_display->height > 0 */
4283
4284 offset_y += line_display->height;
4285
4286 gtk_text_line_display_unref (display: line_display);
4287
4288 if (line == last_line)
4289 break;
4290 }
4291
4292 gtk_text_layout_wrap_loop_end (layout);
4293
4294 if (cursor_snapshot)
4295 {
4296 GskRenderNode *cursors;
4297
4298 cursors = gtk_snapshot_free_to_node (snapshot: cursor_snapshot);
4299 if (cursors)
4300 {
4301 gtk_snapshot_append_node (snapshot: crenderer->snapshot, node: cursors);
4302 gsk_render_node_unref (node: cursors);
4303 }
4304 }
4305
4306 /* Only update eviction source once per snapshot */
4307 gtk_text_line_display_cache_delay_eviction (cache: priv->cache);
4308
4309 gsk_pango_renderer_release (crenderer);
4310}
4311
4312int
4313gtk_text_line_display_compare (const GtkTextLineDisplay *display1,
4314 const GtkTextLineDisplay *display2,
4315 GtkTextLayout *layout)
4316{
4317 int line1;
4318 int line2;
4319
4320 line1 = _gtk_text_line_get_number (line: display1->line);
4321 line2 = _gtk_text_line_get_number (line: display2->line);
4322
4323 if (line1 < line2)
4324 return -1;
4325 else if (line1 > line2)
4326 return 1;
4327 else
4328 return 0;
4329}
4330
4331void
4332gtk_text_layout_set_mru_size (GtkTextLayout *layout,
4333 guint mru_size)
4334{
4335 GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
4336
4337 gtk_text_line_display_cache_set_mru_size (cache: priv->cache, mru_size);
4338}
4339

source code of gtk/gtk/gtktextlayout.c