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