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