1 /* gtkpango.c - pango-related utilities
2  *
3  * Copyright (c) 2010 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
17  */
18 /*
19  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25 #include "config.h"
26 #include "gtkpango.h"
27 #include <pango/pangocairo.h>
28 #include <fribidi.h>
29 #include "gtkintl.h"
30 
31 #define GTK_TYPE_FILL_LAYOUT_RENDERER            (_gtk_fill_layout_renderer_get_type())
32 #define GTK_FILL_LAYOUT_RENDERER(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRenderer))
33 #define GTK_IS_FILL_LAYOUT_RENDERER(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_FILL_LAYOUT_RENDERER))
34 #define GTK_FILL_LAYOUT_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRendererClass))
35 #define GTK_IS_FILL_LAYOUT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILL_LAYOUT_RENDERER))
36 #define GTK_FILL_LAYOUT_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRendererClass))
37 
38 typedef struct _GtkFillLayoutRenderer      GtkFillLayoutRenderer;
39 typedef struct _GtkFillLayoutRendererClass GtkFillLayoutRendererClass;
40 
41 struct _GtkFillLayoutRenderer
42 {
43   PangoRenderer parent_instance;
44 
45   cairo_t *cr;
46 };
47 
48 struct _GtkFillLayoutRendererClass
49 {
50   PangoRendererClass parent_class;
51 };
52 
53 GType _gtk_fill_layout_renderer_get_type (void);
54 
G_DEFINE_TYPE(GtkFillLayoutRenderer,_gtk_fill_layout_renderer,PANGO_TYPE_RENDERER)55 G_DEFINE_TYPE (GtkFillLayoutRenderer, _gtk_fill_layout_renderer, PANGO_TYPE_RENDERER)
56 
57 static void
58 gtk_fill_layout_renderer_draw_glyphs (PangoRenderer     *renderer,
59                                       PangoFont         *font,
60                                       PangoGlyphString  *glyphs,
61                                       int                x,
62                                       int                y)
63 {
64   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
65 
66   cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
67   pango_cairo_show_glyph_string (text_renderer->cr, font, glyphs);
68 }
69 
70 static void
gtk_fill_layout_renderer_draw_glyph_item(PangoRenderer * renderer,const char * text,PangoGlyphItem * glyph_item,int x,int y)71 gtk_fill_layout_renderer_draw_glyph_item (PangoRenderer     *renderer,
72                                           const char        *text,
73                                           PangoGlyphItem    *glyph_item,
74                                           int                x,
75                                           int                y)
76 {
77   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
78 
79   cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
80   pango_cairo_show_glyph_item (text_renderer->cr, text, glyph_item);
81 }
82 
83 static void
gtk_fill_layout_renderer_draw_rectangle(PangoRenderer * renderer,PangoRenderPart part,int x,int y,int width,int height)84 gtk_fill_layout_renderer_draw_rectangle (PangoRenderer     *renderer,
85                                          PangoRenderPart    part,
86                                          int                x,
87                                          int                y,
88                                          int                width,
89                                          int                height)
90 {
91   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
92 
93   if (part == PANGO_RENDER_PART_BACKGROUND)
94     return;
95 
96   cairo_rectangle (text_renderer->cr,
97                    (double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
98 		   (double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
99   cairo_fill (text_renderer->cr);
100 }
101 
102 static void
gtk_fill_layout_renderer_draw_trapezoid(PangoRenderer * renderer,PangoRenderPart part,double y1_,double x11,double x21,double y2,double x12,double x22)103 gtk_fill_layout_renderer_draw_trapezoid (PangoRenderer     *renderer,
104                                          PangoRenderPart    part,
105                                          double             y1_,
106                                          double             x11,
107                                          double             x21,
108                                          double             y2,
109                                          double             x12,
110                                          double             x22)
111 {
112   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
113   cairo_matrix_t matrix;
114   cairo_t *cr;
115 
116   cr = text_renderer->cr;
117 
118   cairo_save (cr);
119 
120   /* use identity scale, but keep translation */
121   cairo_get_matrix (cr, &matrix);
122   matrix.xx = matrix.yy = 1;
123   matrix.xy = matrix.yx = 0;
124   cairo_set_matrix (cr, &matrix);
125 
126   cairo_move_to (cr, x11, y1_);
127   cairo_line_to (cr, x21, y1_);
128   cairo_line_to (cr, x22, y2);
129   cairo_line_to (cr, x12, y2);
130   cairo_close_path (cr);
131 
132   cairo_fill (cr);
133 
134   cairo_restore (cr);
135 }
136 
137 static void
gtk_fill_layout_renderer_draw_error_underline(PangoRenderer * renderer,int x,int y,int width,int height)138 gtk_fill_layout_renderer_draw_error_underline (PangoRenderer *renderer,
139                                                int            x,
140                                                int            y,
141                                                int            width,
142                                                int            height)
143 {
144   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
145 
146   pango_cairo_show_error_underline (text_renderer->cr,
147                                     (double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
148                                     (double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
149 }
150 
151 static void
gtk_fill_layout_renderer_draw_shape(PangoRenderer * renderer,PangoAttrShape * attr,int x,int y)152 gtk_fill_layout_renderer_draw_shape (PangoRenderer   *renderer,
153                                      PangoAttrShape  *attr,
154                                      int              x,
155                                      int              y)
156 {
157   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
158   cairo_t *cr = text_renderer->cr;
159   PangoLayout *layout;
160   PangoCairoShapeRendererFunc shape_renderer;
161   gpointer                    shape_renderer_data;
162 
163   layout = pango_renderer_get_layout (renderer);
164 
165   if (!layout)
166   	return;
167 
168   shape_renderer = pango_cairo_context_get_shape_renderer (pango_layout_get_context (layout),
169 							   &shape_renderer_data);
170 
171   if (!shape_renderer)
172     return;
173 
174   cairo_save (cr);
175 
176   cairo_move_to (cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
177 
178   shape_renderer (cr, attr, FALSE, shape_renderer_data);
179 
180   cairo_restore (cr);
181 }
182 
183 static void
gtk_fill_layout_renderer_finalize(GObject * object)184 gtk_fill_layout_renderer_finalize (GObject *object)
185 {
186   G_OBJECT_CLASS (_gtk_fill_layout_renderer_parent_class)->finalize (object);
187 }
188 
189 static void
_gtk_fill_layout_renderer_init(GtkFillLayoutRenderer * renderer)190 _gtk_fill_layout_renderer_init (GtkFillLayoutRenderer *renderer)
191 {
192 }
193 
194 static void
_gtk_fill_layout_renderer_class_init(GtkFillLayoutRendererClass * klass)195 _gtk_fill_layout_renderer_class_init (GtkFillLayoutRendererClass *klass)
196 {
197   GObjectClass *object_class = G_OBJECT_CLASS (klass);
198 
199   PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
200 
201   renderer_class->draw_glyphs = gtk_fill_layout_renderer_draw_glyphs;
202   renderer_class->draw_glyph_item = gtk_fill_layout_renderer_draw_glyph_item;
203   renderer_class->draw_rectangle = gtk_fill_layout_renderer_draw_rectangle;
204   renderer_class->draw_trapezoid = gtk_fill_layout_renderer_draw_trapezoid;
205   renderer_class->draw_error_underline = gtk_fill_layout_renderer_draw_error_underline;
206   renderer_class->draw_shape = gtk_fill_layout_renderer_draw_shape;
207 
208   object_class->finalize = gtk_fill_layout_renderer_finalize;
209 }
210 
211 void
_gtk_pango_fill_layout(cairo_t * cr,PangoLayout * layout)212 _gtk_pango_fill_layout (cairo_t     *cr,
213                         PangoLayout *layout)
214 {
215   static GtkFillLayoutRenderer *renderer = NULL;
216   gboolean has_current_point;
217   double current_x, current_y;
218 
219   has_current_point = cairo_has_current_point (cr);
220   cairo_get_current_point (cr, &current_x, &current_y);
221 
222   if (renderer == NULL)
223     renderer = g_object_new (GTK_TYPE_FILL_LAYOUT_RENDERER, NULL);
224 
225   cairo_save (cr);
226   cairo_translate (cr, current_x, current_y);
227 
228   renderer->cr = cr;
229   pango_renderer_draw_layout (PANGO_RENDERER (renderer), layout, 0, 0);
230 
231   cairo_restore (cr);
232 
233   if (has_current_point)
234     cairo_move_to (cr, current_x, current_y);
235 }
236 
237 static AtkAttributeSet *
add_attribute(AtkAttributeSet * attributes,AtkTextAttribute attr,const gchar * value)238 add_attribute (AtkAttributeSet  *attributes,
239                AtkTextAttribute  attr,
240                const gchar      *value)
241 {
242   AtkAttribute *at;
243 
244   at = g_new (AtkAttribute, 1);
245   at->name = g_strdup (atk_text_attribute_get_name (attr));
246   at->value = g_strdup (value);
247 
248   return g_slist_prepend (attributes, at);
249 }
250 
251 /*
252  * _gtk_pango_get_default_attributes:
253  * @attributes: a #AtkAttributeSet to add the attributes to
254  * @layout: the #PangoLayout from which to get attributes
255  *
256  * Adds the default text attributes from @layout to @attributes,
257  * after translating them from Pango attributes to ATK attributes.
258  *
259  * This is a convenience function that can be used to implement
260  * support for the #AtkText interface in widgets using Pango
261  * layouts.
262  *
263  * Returns: the modified @attributes
264  */
265 AtkAttributeSet*
_gtk_pango_get_default_attributes(AtkAttributeSet * attributes,PangoLayout * layout)266 _gtk_pango_get_default_attributes (AtkAttributeSet *attributes,
267                                    PangoLayout     *layout)
268 {
269   PangoContext *context;
270   gint i;
271   PangoWrapMode mode;
272 
273   context = pango_layout_get_context (layout);
274   if (context)
275     {
276       PangoLanguage *language;
277       PangoFontDescription *font;
278 
279       language = pango_context_get_language (context);
280       if (language)
281         attributes = add_attribute (attributes, ATK_TEXT_ATTR_LANGUAGE,
282                          pango_language_to_string (language));
283 
284       font = pango_context_get_font_description (context);
285       if (font)
286         {
287           gchar buf[60];
288           attributes = add_attribute (attributes, ATK_TEXT_ATTR_STYLE,
289                            atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE,
290                                  pango_font_description_get_style (font)));
291           attributes = add_attribute (attributes, ATK_TEXT_ATTR_VARIANT,
292                            atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT,
293                                  pango_font_description_get_variant (font)));
294           attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRETCH,
295                            atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH,
296                                  pango_font_description_get_stretch (font)));
297           attributes = add_attribute (attributes, ATK_TEXT_ATTR_FAMILY_NAME,
298                            pango_font_description_get_family (font));
299           g_snprintf (buf, 60, "%d", pango_font_description_get_weight (font));
300           attributes = add_attribute (attributes, ATK_TEXT_ATTR_WEIGHT, buf);
301           g_snprintf (buf, 60, "%i", pango_font_description_get_size (font) / PANGO_SCALE);
302           attributes = add_attribute (attributes, ATK_TEXT_ATTR_SIZE, buf);
303         }
304     }
305   if (pango_layout_get_justify (layout))
306     {
307       i = 3;
308     }
309   else
310     {
311       PangoAlignment align;
312 
313       align = pango_layout_get_alignment (layout);
314       if (align == PANGO_ALIGN_LEFT)
315         i = 0;
316       else if (align == PANGO_ALIGN_CENTER)
317         i = 2;
318       else   /* PANGO_ALIGN_RIGHT */
319         i = 1;
320     }
321   attributes = add_attribute (attributes, ATK_TEXT_ATTR_JUSTIFICATION,
322                    atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, i));
323   mode = pango_layout_get_wrap (layout);
324   if (mode == PANGO_WRAP_WORD)
325     i = 2;
326   else   /* PANGO_WRAP_CHAR */
327     i = 1;
328   attributes = add_attribute (attributes, ATK_TEXT_ATTR_WRAP_MODE,
329                    atk_text_attribute_get_value (ATK_TEXT_ATTR_WRAP_MODE, i));
330 
331   attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRIKETHROUGH,
332                    atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, 0));
333   attributes = add_attribute (attributes, ATK_TEXT_ATTR_UNDERLINE,
334                    atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, 0));
335   attributes = add_attribute (attributes, ATK_TEXT_ATTR_RISE, "0");
336   attributes = add_attribute (attributes, ATK_TEXT_ATTR_SCALE, "1");
337   attributes = add_attribute (attributes, ATK_TEXT_ATTR_BG_FULL_HEIGHT, "0");
338   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP, "0");
339   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_BELOW_LINES, "0");
340   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES, "0");
341   attributes = add_attribute (attributes, ATK_TEXT_ATTR_EDITABLE,
342                    atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE, 0));
343   attributes = add_attribute (attributes, ATK_TEXT_ATTR_INVISIBLE,
344                    atk_text_attribute_get_value (ATK_TEXT_ATTR_INVISIBLE, 0));
345   attributes = add_attribute (attributes, ATK_TEXT_ATTR_INDENT, "0");
346   attributes = add_attribute (attributes, ATK_TEXT_ATTR_RIGHT_MARGIN, "0");
347   attributes = add_attribute (attributes, ATK_TEXT_ATTR_LEFT_MARGIN, "0");
348 
349   return attributes;
350 }
351 
352 /*
353  * _gtk_pango_get_run_attributes:
354  * @attributes: a #AtkAttributeSet to add attributes to
355  * @layout: the #PangoLayout to get the attributes from
356  * @offset: the offset at which the attributes are wanted
357  * @start_offset: return location for the starting offset
358  *    of the current run
359  * @end_offset: return location for the ending offset of the
360  *    current run
361  *
362  * Finds the “run” around index (i.e. the maximal range of characters
363  * where the set of applicable attributes remains constant) and
364  * returns the starting and ending offsets for it.
365  *
366  * The attributes for the run are added to @attributes, after
367  * translating them from Pango attributes to ATK attributes.
368  *
369  * This is a convenience function that can be used to implement
370  * support for the #AtkText interface in widgets using Pango
371  * layouts.
372  *
373  * Returns: the modified #AtkAttributeSet
374  */
375 AtkAttributeSet *
_gtk_pango_get_run_attributes(AtkAttributeSet * attributes,PangoLayout * layout,gint offset,gint * start_offset,gint * end_offset)376 _gtk_pango_get_run_attributes (AtkAttributeSet *attributes,
377                                PangoLayout     *layout,
378                                gint             offset,
379                                gint            *start_offset,
380                                gint            *end_offset)
381 {
382   PangoAttrIterator *iter;
383   PangoAttrList *attr;
384   PangoAttrString *pango_string;
385   PangoAttrInt *pango_int;
386   PangoAttrColor *pango_color;
387   PangoAttrLanguage *pango_lang;
388   PangoAttrFloat *pango_float;
389   gint index, start_index, end_index;
390   gboolean is_next;
391   glong len;
392   const gchar *text;
393   gchar *value;
394 
395   text = pango_layout_get_text (layout);
396   len = g_utf8_strlen (text, -1);
397 
398   /* Grab the attributes of the PangoLayout, if any */
399   attr = pango_layout_get_attributes (layout);
400 
401   if (attr == NULL)
402     {
403       *start_offset = 0;
404       *end_offset = len;
405       return attributes;
406     }
407 
408   iter = pango_attr_list_get_iterator (attr);
409   /* Get invariant range offsets */
410   /* If offset out of range, set offset in range */
411   if (offset > len)
412     offset = len;
413   else if (offset < 0)
414     offset = 0;
415 
416   index = g_utf8_offset_to_pointer (text, offset) - text;
417   pango_attr_iterator_range (iter, &start_index, &end_index);
418   is_next = TRUE;
419   while (is_next)
420     {
421       if (index >= start_index && index < end_index)
422         {
423           *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
424           if (end_index == G_MAXINT) /* Last iterator */
425             end_index = len;
426 
427           *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
428           break;
429         }
430       is_next = pango_attr_iterator_next (iter);
431       pango_attr_iterator_range (iter, &start_index, &end_index);
432     }
433 
434   /* Get attributes */
435   pango_string = (PangoAttrString*) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY);
436   if (pango_string != NULL)
437     {
438       value = g_strdup_printf ("%s", pango_string->value);
439       attributes = add_attribute (attributes, ATK_TEXT_ATTR_FAMILY_NAME, value);
440       g_free (value);
441     }
442   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STYLE);
443   if (pango_int != NULL)
444     {
445       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STYLE,
446                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, pango_int->value));
447     }
448   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT);
449   if (pango_int != NULL)
450     {
451       value = g_strdup_printf ("%i", pango_int->value);
452       attributes = add_attribute (attributes, ATK_TEXT_ATTR_WEIGHT, value);
453       g_free (value);
454     }
455   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_VARIANT);
456   if (pango_int != NULL)
457     {
458       attributes = add_attribute (attributes, ATK_TEXT_ATTR_VARIANT,
459                        atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, pango_int->value));
460     }
461   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STRETCH);
462   if (pango_int != NULL)
463     {
464       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRETCH,
465                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, pango_int->value));
466     }
467   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE);
468   if (pango_int != NULL)
469     {
470       value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE);
471       attributes = add_attribute (attributes, ATK_TEXT_ATTR_SIZE, value);
472       g_free (value);
473     }
474   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
475   if (pango_int != NULL)
476     {
477       attributes = add_attribute (attributes, ATK_TEXT_ATTR_UNDERLINE,
478                        atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, pango_int->value));
479     }
480   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH);
481   if (pango_int != NULL)
482     {
483       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRIKETHROUGH,
484                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, pango_int->value));
485     }
486   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_RISE);
487   if (pango_int != NULL)
488     {
489       value = g_strdup_printf ("%i", pango_int->value);
490       attributes = add_attribute (attributes, ATK_TEXT_ATTR_RISE, value);
491       g_free (value);
492     }
493   pango_lang = (PangoAttrLanguage*) pango_attr_iterator_get (iter, PANGO_ATTR_LANGUAGE);
494   if (pango_lang != NULL)
495     {
496       attributes = add_attribute (attributes, ATK_TEXT_ATTR_LANGUAGE,
497                                   pango_language_to_string (pango_lang->value));
498     }
499   pango_float = (PangoAttrFloat*) pango_attr_iterator_get (iter, PANGO_ATTR_SCALE);
500   if (pango_float != NULL)
501     {
502       value = g_strdup_printf ("%g", pango_float->value);
503       attributes = add_attribute (attributes, ATK_TEXT_ATTR_SCALE, value);
504       g_free (value);
505     }
506   pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
507   if (pango_color != NULL)
508     {
509       value = g_strdup_printf ("%u,%u,%u",
510                                pango_color->color.red,
511                                pango_color->color.green,
512                                pango_color->color.blue);
513       attributes = add_attribute (attributes, ATK_TEXT_ATTR_FG_COLOR, value);
514       g_free (value);
515     }
516   pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter, PANGO_ATTR_BACKGROUND);
517   if (pango_color != NULL)
518     {
519       value = g_strdup_printf ("%u,%u,%u",
520                                pango_color->color.red,
521                                pango_color->color.green,
522                                pango_color->color.blue);
523       attributes = add_attribute (attributes, ATK_TEXT_ATTR_BG_COLOR, value);
524       g_free (value);
525     }
526   pango_attr_iterator_destroy (iter);
527 
528   return attributes;
529 }
530 
531 /*
532  * _gtk_pango_move_chars:
533  * @layout: a #PangoLayout
534  * @offset: a character offset in @layout
535  * @count: the number of characters to move from @offset
536  *
537  * Returns the position that is @count characters from the
538  * given @offset. @count may be positive or negative.
539  *
540  * For the purpose of this function, characters are defined
541  * by what Pango considers cursor positions.
542  *
543  * Returns: the new position
544  */
545 gint
_gtk_pango_move_chars(PangoLayout * layout,gint offset,gint count)546 _gtk_pango_move_chars (PangoLayout *layout,
547                        gint         offset,
548                        gint         count)
549 {
550   const PangoLogAttr *attrs;
551   gint n_attrs;
552 
553   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
554 
555   while (count > 0 && offset < n_attrs - 1)
556     {
557       do
558         offset++;
559       while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position);
560 
561       count--;
562     }
563   while (count < 0 && offset > 0)
564     {
565       do
566         offset--;
567       while (offset > 0 && !attrs[offset].is_cursor_position);
568 
569       count++;
570     }
571 
572   return offset;
573 }
574 
575 /*
576  * _gtk_pango_move_words:
577  * @layout: a #PangoLayout
578  * @offset: a character offset in @layout
579  * @count: the number of words to move from @offset
580  *
581  * Returns the position that is @count words from the
582  * given @offset. @count may be positive or negative.
583  *
584  * If @count is positive, the returned position will
585  * be a word end, otherwise it will be a word start.
586  * See the Pango documentation for details on how
587  * word starts and ends are defined.
588  *
589  * Returns: the new position
590  */
591 gint
_gtk_pango_move_words(PangoLayout * layout,gint offset,gint count)592 _gtk_pango_move_words (PangoLayout  *layout,
593                        gint          offset,
594                        gint          count)
595 {
596   const PangoLogAttr *attrs;
597   gint n_attrs;
598 
599   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
600 
601   while (count > 0 && offset < n_attrs - 1)
602     {
603       do
604         offset++;
605       while (offset < n_attrs - 1 && !attrs[offset].is_word_end);
606 
607       count--;
608     }
609   while (count < 0 && offset > 0)
610     {
611       do
612         offset--;
613       while (offset > 0 && !attrs[offset].is_word_start);
614 
615       count++;
616     }
617 
618   return offset;
619 }
620 
621 /*
622  * _gtk_pango_move_sentences:
623  * @layout: a #PangoLayout
624  * @offset: a character offset in @layout
625  * @count: the number of sentences to move from @offset
626  *
627  * Returns the position that is @count sentences from the
628  * given @offset. @count may be positive or negative.
629  *
630  * If @count is positive, the returned position will
631  * be a sentence end, otherwise it will be a sentence start.
632  * See the Pango documentation for details on how
633  * sentence starts and ends are defined.
634  *
635  * Returns: the new position
636  */
637 gint
_gtk_pango_move_sentences(PangoLayout * layout,gint offset,gint count)638 _gtk_pango_move_sentences (PangoLayout  *layout,
639                            gint          offset,
640                            gint          count)
641 {
642   const PangoLogAttr *attrs;
643   gint n_attrs;
644 
645   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
646 
647   while (count > 0 && offset < n_attrs - 1)
648     {
649       do
650         offset++;
651       while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end);
652 
653       count--;
654     }
655   while (count < 0 && offset > 0)
656     {
657       do
658         offset--;
659       while (offset > 0 && !attrs[offset].is_sentence_start);
660 
661       count++;
662     }
663 
664   return offset;
665 }
666 
667 /*
668  * _gtk_pango_move_lines:
669  * @layout: a #PangoLayout
670  * @offset: a character offset in @layout
671  * @count: the number of lines to move from @offset
672  *
673  * Returns the position that is @count lines from the
674  * given @offset. @count may be positive or negative.
675  *
676  * If @count is negative, the returned position will
677  * be the start of a line, else it will be the end of
678  * line.
679  *
680  * Returns: the new position
681  */
682 gint
_gtk_pango_move_lines(PangoLayout * layout,gint offset,gint count)683 _gtk_pango_move_lines (PangoLayout *layout,
684                        gint         offset,
685                        gint         count)
686 {
687   GSList *lines, *l;
688   PangoLayoutLine *line;
689   gint num;
690   const gchar *text;
691   gint pos, line_pos;
692   gint index;
693   gint len;
694 
695   text = pango_layout_get_text (layout);
696   index = g_utf8_offset_to_pointer (text, offset) - text;
697   lines = pango_layout_get_lines (layout);
698   line = NULL;
699 
700   num = 0;
701   for (l = lines; l; l = l->next)
702     {
703       line = l->data;
704       if (index < line->start_index + line->length)
705         break;
706       num++;
707     }
708 
709   if (count < 0)
710     {
711       num += count;
712       if (num < 0)
713         num = 0;
714 
715       line = g_slist_nth_data (lines, num);
716 
717       return g_utf8_pointer_to_offset (text, text + line->start_index);
718     }
719   else
720     {
721       line_pos = index - line->start_index;
722 
723       len = g_slist_length (lines);
724       num += count;
725       if (num >= len || (count == 0 && num == len - 1))
726         return g_utf8_strlen (text, -1) - 1;
727 
728       line = l->data;
729       pos = line->start_index + line_pos;
730       if (pos >= line->start_index + line->length)
731         pos = line->start_index + line->length - 1;
732 
733       return g_utf8_pointer_to_offset (text, text + pos);
734     }
735 }
736 
737 /*
738  * _gtk_pango_is_inside_word:
739  * @layout: a #PangoLayout
740  * @offset: a character offset in @layout
741  *
742  * Returns whether the given position is inside
743  * a word.
744  *
745  * Returns: %TRUE if @offset is inside a word
746  */
747 gboolean
_gtk_pango_is_inside_word(PangoLayout * layout,gint offset)748 _gtk_pango_is_inside_word (PangoLayout  *layout,
749                            gint          offset)
750 {
751   const PangoLogAttr *attrs;
752   gint n_attrs;
753 
754   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
755 
756   while (offset >= 0 &&
757          !(attrs[offset].is_word_start || attrs[offset].is_word_end))
758     offset--;
759 
760   if (offset >= 0)
761     return attrs[offset].is_word_start;
762 
763   return FALSE;
764 }
765 
766 /*
767  * _gtk_pango_is_inside_sentence:
768  * @layout: a #PangoLayout
769  * @offset: a character offset in @layout
770  *
771  * Returns whether the given position is inside
772  * a sentence.
773  *
774  * Returns: %TRUE if @offset is inside a sentence
775  */
776 gboolean
_gtk_pango_is_inside_sentence(PangoLayout * layout,gint offset)777 _gtk_pango_is_inside_sentence (PangoLayout  *layout,
778                                gint          offset)
779 {
780   const PangoLogAttr *attrs;
781   gint n_attrs;
782 
783   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
784 
785   while (offset >= 0 &&
786          !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
787     offset--;
788 
789   if (offset >= 0)
790     return attrs[offset].is_sentence_start;
791 
792   return FALSE;
793 }
794 
795 static void
pango_layout_get_line_before(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)796 pango_layout_get_line_before (PangoLayout     *layout,
797                               AtkTextBoundary  boundary_type,
798                               gint             offset,
799                               gint            *start_offset,
800                               gint            *end_offset)
801 {
802   PangoLayoutIter *iter;
803   PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL;
804   gint index, start_index, end_index;
805   const gchar *text;
806   gboolean found = FALSE;
807 
808   text = pango_layout_get_text (layout);
809   index = g_utf8_offset_to_pointer (text, offset) - text;
810   iter = pango_layout_get_iter (layout);
811   do
812     {
813       line = pango_layout_iter_get_line (iter);
814       start_index = line->start_index;
815       end_index = start_index + line->length;
816 
817       if (index >= start_index && index <= end_index)
818         {
819           /* Found line for offset */
820           if (prev_line)
821             {
822               switch (boundary_type)
823                 {
824                 case ATK_TEXT_BOUNDARY_LINE_START:
825                   end_index = start_index;
826                   start_index = prev_line->start_index;
827                   break;
828                 case ATK_TEXT_BOUNDARY_LINE_END:
829                   if (prev_prev_line)
830                     start_index = prev_prev_line->start_index + prev_prev_line->length;
831                   else
832                     start_index = 0;
833                   end_index = prev_line->start_index + prev_line->length;
834                   break;
835                 default:
836                   g_assert_not_reached();
837                 }
838             }
839           else
840             start_index = end_index = 0;
841 
842           found = TRUE;
843           break;
844         }
845 
846       prev_prev_line = prev_line;
847       prev_line = line;
848     }
849   while (pango_layout_iter_next_line (iter));
850 
851   if (!found)
852     {
853       start_index = prev_line->start_index + prev_line->length;
854       end_index = start_index;
855     }
856   pango_layout_iter_free (iter);
857 
858   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
859   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
860 }
861 
862 static void
pango_layout_get_line_at(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)863 pango_layout_get_line_at (PangoLayout     *layout,
864                           AtkTextBoundary  boundary_type,
865                           gint             offset,
866                           gint            *start_offset,
867                           gint            *end_offset)
868 {
869   PangoLayoutIter *iter;
870   PangoLayoutLine *line, *prev_line = NULL;
871   gint index, start_index, end_index;
872   const gchar *text;
873   gboolean found = FALSE;
874 
875   text = pango_layout_get_text (layout);
876   index = g_utf8_offset_to_pointer (text, offset) - text;
877   iter = pango_layout_get_iter (layout);
878   do
879     {
880       line = pango_layout_iter_get_line (iter);
881       start_index = line->start_index;
882       end_index = start_index + line->length;
883 
884       if (index >= start_index && index <= end_index)
885         {
886           /* Found line for offset */
887           switch (boundary_type)
888             {
889             case ATK_TEXT_BOUNDARY_LINE_START:
890               if (pango_layout_iter_next_line (iter))
891                 end_index = pango_layout_iter_get_line (iter)->start_index;
892               break;
893             case ATK_TEXT_BOUNDARY_LINE_END:
894               if (prev_line)
895                 start_index = prev_line->start_index + prev_line->length;
896               break;
897             default:
898               g_assert_not_reached();
899             }
900 
901           found = TRUE;
902           break;
903         }
904 
905       prev_line = line;
906     }
907   while (pango_layout_iter_next_line (iter));
908 
909   if (!found)
910     {
911       start_index = prev_line->start_index + prev_line->length;
912       end_index = start_index;
913     }
914   pango_layout_iter_free (iter);
915 
916   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
917   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
918 }
919 
920 static void
pango_layout_get_line_after(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)921 pango_layout_get_line_after (PangoLayout     *layout,
922                              AtkTextBoundary  boundary_type,
923                              gint             offset,
924                              gint            *start_offset,
925                              gint            *end_offset)
926 {
927   PangoLayoutIter *iter;
928   PangoLayoutLine *line, *prev_line = NULL;
929   gint index, start_index, end_index;
930   const gchar *text;
931   gboolean found = FALSE;
932 
933   text = pango_layout_get_text (layout);
934   index = g_utf8_offset_to_pointer (text, offset) - text;
935   iter = pango_layout_get_iter (layout);
936   do
937     {
938       line = pango_layout_iter_get_line (iter);
939       start_index = line->start_index;
940       end_index = start_index + line->length;
941 
942       if (index >= start_index && index <= end_index)
943         {
944           /* Found line for offset */
945           if (pango_layout_iter_next_line (iter))
946             {
947               line = pango_layout_iter_get_line (iter);
948               switch (boundary_type)
949                 {
950                 case ATK_TEXT_BOUNDARY_LINE_START:
951                   start_index = line->start_index;
952                   if (pango_layout_iter_next_line (iter))
953                     end_index = pango_layout_iter_get_line (iter)->start_index;
954                   else
955                     end_index = start_index + line->length;
956                   break;
957                 case ATK_TEXT_BOUNDARY_LINE_END:
958                   start_index = end_index;
959                   end_index = line->start_index + line->length;
960                   break;
961                 default:
962                   g_assert_not_reached();
963                 }
964             }
965           else
966             start_index = end_index;
967 
968           found = TRUE;
969           break;
970         }
971 
972       prev_line = line;
973     }
974   while (pango_layout_iter_next_line (iter));
975 
976   if (!found)
977     {
978       start_index = prev_line->start_index + prev_line->length;
979       end_index = start_index;
980     }
981   pango_layout_iter_free (iter);
982 
983   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
984   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
985 }
986 
987 /*
988  * _gtk_pango_get_text_before:
989  * @layout: a #PangoLayout
990  * @boundary_type: a #AtkTextBoundary
991  * @offset: a character offset in @layout
992  * @start_offset: return location for the start of the returned text
993  * @end_offset: return location for the end of the return text
994  *
995  * Gets a slice of the text from @layout before @offset.
996  *
997  * The @boundary_type determines the size of the returned slice of
998  * text. For the exact semantics of this function, see
999  * atk_text_get_text_before_offset().
1000  *
1001  * Returns: a newly allocated string containing a slice of text
1002  *     from layout. Free with g_free().
1003  */
1004 gchar *
_gtk_pango_get_text_before(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)1005 _gtk_pango_get_text_before (PangoLayout     *layout,
1006                             AtkTextBoundary  boundary_type,
1007                             gint             offset,
1008                             gint            *start_offset,
1009                             gint            *end_offset)
1010 {
1011   const gchar *text;
1012   gint start, end;
1013   const PangoLogAttr *attrs;
1014   gint n_attrs;
1015 
1016   text = pango_layout_get_text (layout);
1017 
1018   if (text[0] == 0)
1019     {
1020       *start_offset = 0;
1021       *end_offset = 0;
1022       return g_strdup ("");
1023     }
1024 
1025   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1026 
1027   start = offset;
1028   end = start;
1029 
1030   switch (boundary_type)
1031     {
1032     case ATK_TEXT_BOUNDARY_CHAR:
1033       start = _gtk_pango_move_chars (layout, start, -1);
1034       break;
1035 
1036     case ATK_TEXT_BOUNDARY_WORD_START:
1037       if (!attrs[start].is_word_start)
1038         start = _gtk_pango_move_words (layout, start, -1);
1039       end = start;
1040       start = _gtk_pango_move_words (layout, start, -1);
1041       break;
1042 
1043     case ATK_TEXT_BOUNDARY_WORD_END:
1044       if (_gtk_pango_is_inside_word (layout, start) &&
1045           !attrs[start].is_word_start)
1046         start = _gtk_pango_move_words (layout, start, -1);
1047       while (!attrs[start].is_word_end && start > 0)
1048         start = _gtk_pango_move_chars (layout, start, -1);
1049       end = start;
1050       start = _gtk_pango_move_words (layout, start, -1);
1051       while (!attrs[start].is_word_end && start > 0)
1052         start = _gtk_pango_move_chars (layout, start, -1);
1053       break;
1054 
1055     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1056       if (!attrs[start].is_sentence_start)
1057         start = _gtk_pango_move_sentences (layout, start, -1);
1058       end = start;
1059       start = _gtk_pango_move_sentences (layout, start, -1);
1060       break;
1061 
1062     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1063       if (_gtk_pango_is_inside_sentence (layout, start) &&
1064           !attrs[start].is_sentence_start)
1065         start = _gtk_pango_move_sentences (layout, start, -1);
1066       while (!attrs[start].is_sentence_end && start > 0)
1067         start = _gtk_pango_move_chars (layout, start, -1);
1068       end = start;
1069       start = _gtk_pango_move_sentences (layout, start, -1);
1070       while (!attrs[start].is_sentence_end && start > 0)
1071         start = _gtk_pango_move_chars (layout, start, -1);
1072       break;
1073 
1074     case ATK_TEXT_BOUNDARY_LINE_START:
1075     case ATK_TEXT_BOUNDARY_LINE_END:
1076       pango_layout_get_line_before (layout, boundary_type, offset, &start, &end);
1077       break;
1078     }
1079 
1080   *start_offset = start;
1081   *end_offset = end;
1082 
1083   g_assert (start <= end);
1084 
1085   return g_utf8_substring (text, start, end);
1086 }
1087 
1088 /*
1089  * _gtk_pango_get_text_after:
1090  * @layout: a #PangoLayout
1091  * @boundary_type: a #AtkTextBoundary
1092  * @offset: a character offset in @layout
1093  * @start_offset: return location for the start of the returned text
1094  * @end_offset: return location for the end of the return text
1095  *
1096  * Gets a slice of the text from @layout after @offset.
1097  *
1098  * The @boundary_type determines the size of the returned slice of
1099  * text. For the exact semantics of this function, see
1100  * atk_text_get_text_after_offset().
1101  *
1102  * Returns: a newly allocated string containing a slice of text
1103  *     from layout. Free with g_free().
1104  */
1105 gchar *
_gtk_pango_get_text_after(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)1106 _gtk_pango_get_text_after (PangoLayout     *layout,
1107                            AtkTextBoundary  boundary_type,
1108                            gint             offset,
1109                            gint            *start_offset,
1110                            gint            *end_offset)
1111 {
1112   const gchar *text;
1113   gint start, end;
1114   const PangoLogAttr *attrs;
1115   gint n_attrs;
1116 
1117   text = pango_layout_get_text (layout);
1118 
1119   if (text[0] == 0)
1120     {
1121       *start_offset = 0;
1122       *end_offset = 0;
1123       return g_strdup ("");
1124     }
1125 
1126   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1127 
1128   start = offset;
1129   end = start;
1130 
1131   switch (boundary_type)
1132     {
1133     case ATK_TEXT_BOUNDARY_CHAR:
1134       start = _gtk_pango_move_chars (layout, start, 1);
1135       end = start;
1136       end = _gtk_pango_move_chars (layout, end, 1);
1137       break;
1138 
1139     case ATK_TEXT_BOUNDARY_WORD_START:
1140       if (_gtk_pango_is_inside_word (layout, end))
1141         end = _gtk_pango_move_words (layout, end, 1);
1142       while (!attrs[end].is_word_start && end < n_attrs - 1)
1143         end = _gtk_pango_move_chars (layout, end, 1);
1144       start = end;
1145       if (end < n_attrs - 1)
1146         {
1147           end = _gtk_pango_move_words (layout, end, 1);
1148           while (!attrs[end].is_word_start && end < n_attrs - 1)
1149             end = _gtk_pango_move_chars (layout, end, 1);
1150         }
1151       break;
1152 
1153     case ATK_TEXT_BOUNDARY_WORD_END:
1154       end = _gtk_pango_move_words (layout, end, 1);
1155       start = end;
1156       if (end < n_attrs - 1)
1157         end = _gtk_pango_move_words (layout, end, 1);
1158       break;
1159 
1160     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1161       if (_gtk_pango_is_inside_sentence (layout, end))
1162         end = _gtk_pango_move_sentences (layout, end, 1);
1163       while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1164         end = _gtk_pango_move_chars (layout, end, 1);
1165       start = end;
1166       if (end < n_attrs - 1)
1167         {
1168           end = _gtk_pango_move_sentences (layout, end, 1);
1169           while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1170             end = _gtk_pango_move_chars (layout, end, 1);
1171         }
1172       break;
1173 
1174     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1175       end = _gtk_pango_move_sentences (layout, end, 1);
1176       start = end;
1177       if (end < n_attrs - 1)
1178         end = _gtk_pango_move_sentences (layout, end, 1);
1179       break;
1180 
1181     case ATK_TEXT_BOUNDARY_LINE_START:
1182     case ATK_TEXT_BOUNDARY_LINE_END:
1183       pango_layout_get_line_after (layout, boundary_type, offset, &start, &end);
1184       break;
1185     }
1186 
1187   *start_offset = start;
1188   *end_offset = end;
1189 
1190   g_assert (start <= end);
1191 
1192   return g_utf8_substring (text, start, end);
1193 }
1194 
1195 /*
1196  * _gtk_pango_get_text_at:
1197  * @layout: a #PangoLayout
1198  * @boundary_type: a #AtkTextBoundary
1199  * @offset: a character offset in @layout
1200  * @start_offset: return location for the start of the returned text
1201  * @end_offset: return location for the end of the return text
1202  *
1203  * Gets a slice of the text from @layout at @offset.
1204  *
1205  * The @boundary_type determines the size of the returned slice of
1206  * text. For the exact semantics of this function, see
1207  * atk_text_get_text_after_offset().
1208  *
1209  * Returns: a newly allocated string containing a slice of text
1210  *     from layout. Free with g_free().
1211  */
1212 gchar *
_gtk_pango_get_text_at(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)1213 _gtk_pango_get_text_at (PangoLayout     *layout,
1214                         AtkTextBoundary  boundary_type,
1215                         gint             offset,
1216                         gint            *start_offset,
1217                         gint            *end_offset)
1218 {
1219   const gchar *text;
1220   gint start, end;
1221   const PangoLogAttr *attrs;
1222   gint n_attrs;
1223 
1224   text = pango_layout_get_text (layout);
1225 
1226   if (text[0] == 0)
1227     {
1228       *start_offset = 0;
1229       *end_offset = 0;
1230       return g_strdup ("");
1231     }
1232 
1233   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1234 
1235   start = offset;
1236   end = start;
1237 
1238   switch (boundary_type)
1239     {
1240     case ATK_TEXT_BOUNDARY_CHAR:
1241       end = _gtk_pango_move_chars (layout, end, 1);
1242       break;
1243 
1244     case ATK_TEXT_BOUNDARY_WORD_START:
1245       if (!attrs[start].is_word_start)
1246         start = _gtk_pango_move_words (layout, start, -1);
1247       if (_gtk_pango_is_inside_word (layout, end))
1248         end = _gtk_pango_move_words (layout, end, 1);
1249       while (!attrs[end].is_word_start && end < n_attrs - 1)
1250         end = _gtk_pango_move_chars (layout, end, 1);
1251       break;
1252 
1253     case ATK_TEXT_BOUNDARY_WORD_END:
1254       if (_gtk_pango_is_inside_word (layout, start) &&
1255           !attrs[start].is_word_start)
1256         start = _gtk_pango_move_words (layout, start, -1);
1257       while (!attrs[start].is_word_end && start > 0)
1258         start = _gtk_pango_move_chars (layout, start, -1);
1259       end = _gtk_pango_move_words (layout, end, 1);
1260       break;
1261 
1262     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1263       if (!attrs[start].is_sentence_start)
1264         start = _gtk_pango_move_sentences (layout, start, -1);
1265       if (_gtk_pango_is_inside_sentence (layout, end))
1266         end = _gtk_pango_move_sentences (layout, end, 1);
1267       while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1268         end = _gtk_pango_move_chars (layout, end, 1);
1269       break;
1270 
1271     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1272       if (_gtk_pango_is_inside_sentence (layout, start) &&
1273           !attrs[start].is_sentence_start)
1274         start = _gtk_pango_move_sentences (layout, start, -1);
1275       while (!attrs[start].is_sentence_end && start > 0)
1276         start = _gtk_pango_move_chars (layout, start, -1);
1277       end = _gtk_pango_move_sentences (layout, end, 1);
1278       break;
1279 
1280     case ATK_TEXT_BOUNDARY_LINE_START:
1281     case ATK_TEXT_BOUNDARY_LINE_END:
1282       pango_layout_get_line_at (layout, boundary_type, offset, &start, &end);
1283       break;
1284     }
1285 
1286   *start_offset = start;
1287   *end_offset = end;
1288 
1289   g_assert (start <= end);
1290 
1291   return g_utf8_substring (text, start, end);
1292 }
1293 
1294 static gboolean
attr_list_merge_filter(PangoAttribute * attribute,gpointer list)1295 attr_list_merge_filter (PangoAttribute *attribute,
1296                         gpointer        list)
1297 {
1298   pango_attr_list_change (list, pango_attribute_copy (attribute));
1299   return FALSE;
1300 }
1301 
1302 /*
1303  * _gtk_pango_attr_list_merge:
1304  * @into: a #PangoAttrList where attributes are merged or %NULL
1305  * @from: a #PangoAttrList with the attributes to merge or %NULL
1306  *
1307  * Merges attributes from @from into @into.
1308  *
1309  * Returns: the merged list.
1310  */
1311 PangoAttrList *
_gtk_pango_attr_list_merge(PangoAttrList * into,PangoAttrList * from)1312 _gtk_pango_attr_list_merge (PangoAttrList *into,
1313                             PangoAttrList *from)
1314 {
1315   if (from)
1316     {
1317       if (into)
1318         pango_attr_list_filter (from, attr_list_merge_filter, into);
1319       else
1320        return pango_attr_list_ref (from);
1321     }
1322 
1323   return into;
1324 }
1325 
1326 PangoDirection
_gtk_pango_unichar_direction(gunichar ch)1327 _gtk_pango_unichar_direction (gunichar ch)
1328 {
1329   FriBidiCharType fribidi_ch_type;
1330 
1331   G_STATIC_ASSERT (sizeof (FriBidiChar) == sizeof (gunichar));
1332 
1333   fribidi_ch_type = fribidi_get_bidi_type (ch);
1334 
1335   if (!FRIBIDI_IS_STRONG (fribidi_ch_type))
1336     return PANGO_DIRECTION_NEUTRAL;
1337   else if (FRIBIDI_IS_RTL (fribidi_ch_type))
1338     return PANGO_DIRECTION_RTL;
1339   else
1340     return PANGO_DIRECTION_LTR;
1341 }
1342 
1343 PangoDirection
_gtk_pango_find_base_dir(const gchar * text,gint length)1344 _gtk_pango_find_base_dir (const gchar *text,
1345                           gint         length)
1346 {
1347   PangoDirection dir = PANGO_DIRECTION_NEUTRAL;
1348   const gchar *p;
1349 
1350   g_return_val_if_fail (text != NULL || length == 0, PANGO_DIRECTION_NEUTRAL);
1351 
1352   p = text;
1353   while ((length < 0 || p < text + length) && *p)
1354     {
1355       gunichar wc = g_utf8_get_char (p);
1356 
1357       dir = _gtk_pango_unichar_direction (wc);
1358 
1359       if (dir != PANGO_DIRECTION_NEUTRAL)
1360         break;
1361 
1362       p = g_utf8_next_char (p);
1363     }
1364 
1365   return dir;
1366 }
1367 
1368