]> Pileus Git - ~andy/gtk/blob - gtk/gtkpango.c
Merge branch 'bgo593793-filechooser-recent-folders-master'
[~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, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 /*
20  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21  * file for a list of people on the GTK+ Team.  See the ChangeLog
22  * files for a list of changes.  These files are distributed with
23  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24  */
25
26 #include "config.h"
27 #include "gtkpango.h"
28 #include <pango/pangocairo.h>
29 #include "gtkintl.h"
30
31 #define GTK_TYPE_FILL_LAYOUT_RENDERER            (_gtk_fill_layout_renderer_get_type())
32 #define GTK_FILL_LAYOUT_RENDERER(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRenderer))
33 #define GTK_IS_FILL_LAYOUT_RENDERER(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_FILL_LAYOUT_RENDERER))
34 #define GTK_FILL_LAYOUT_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRendererClass))
35 #define GTK_IS_FILL_LAYOUT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILL_LAYOUT_RENDERER))
36 #define GTK_FILL_LAYOUT_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILL_LAYOUT_RENDERER, GtkFillLayoutRendererClass))
37
38 typedef struct _GtkFillLayoutRenderer      GtkFillLayoutRenderer;
39 typedef struct _GtkFillLayoutRendererClass GtkFillLayoutRendererClass;
40
41 struct _GtkFillLayoutRenderer
42 {
43   PangoRenderer parent_instance;
44
45   cairo_t *cr;
46 };
47
48 struct _GtkFillLayoutRendererClass
49 {
50   PangoRendererClass parent_class;
51 };
52
53 G_DEFINE_TYPE (GtkFillLayoutRenderer, _gtk_fill_layout_renderer, PANGO_TYPE_RENDERER)
54
55 static void
56 gtk_fill_layout_renderer_draw_glyphs (PangoRenderer     *renderer,
57                                       PangoFont         *font,
58                                       PangoGlyphString  *glyphs,
59                                       int                x,
60                                       int                y)
61 {
62   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
63
64   cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
65   pango_cairo_show_glyph_string (text_renderer->cr, font, glyphs);
66 }
67
68 static void
69 gtk_fill_layout_renderer_draw_glyph_item (PangoRenderer     *renderer,
70                                           const char        *text,
71                                           PangoGlyphItem    *glyph_item,
72                                           int                x,
73                                           int                y)
74 {
75   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
76
77   cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
78   pango_cairo_show_glyph_item (text_renderer->cr, text, glyph_item);
79 }
80
81 static void
82 gtk_fill_layout_renderer_draw_rectangle (PangoRenderer     *renderer,
83                                          PangoRenderPart    part,
84                                          int                x,
85                                          int                y,
86                                          int                width,
87                                          int                height)
88 {
89   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
90
91   if (part == PANGO_RENDER_PART_BACKGROUND)
92     return;
93
94   cairo_rectangle (text_renderer->cr,
95                    (double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
96                    (double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
97   cairo_fill (text_renderer->cr);
98 }
99
100 static void
101 gtk_fill_layout_renderer_draw_trapezoid (PangoRenderer     *renderer,
102                                          PangoRenderPart    part,
103                                          double             y1_,
104                                          double             x11,
105                                          double             x21,
106                                          double             y2,
107                                          double             x12,
108                                          double             x22)
109 {
110   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
111   cairo_matrix_t matrix;
112   cairo_t *cr;
113
114   cr = text_renderer->cr;
115
116   cairo_save (cr);
117
118   /* use identity scale, but keep translation */
119   cairo_get_matrix (cr, &matrix);
120   matrix.xx = matrix.yy = 1;
121   matrix.xy = matrix.yx = 0;
122   cairo_set_matrix (cr, &matrix);
123
124   cairo_move_to (cr, x11, y1_);
125   cairo_line_to (cr, x21, y1_);
126   cairo_line_to (cr, x22, y2);
127   cairo_line_to (cr, x12, y2);
128   cairo_close_path (cr);
129
130   cairo_fill (cr);
131
132   cairo_restore (cr);
133 }
134
135 static void
136 gtk_fill_layout_renderer_draw_error_underline (PangoRenderer *renderer,
137                                                int            x,
138                                                int            y,
139                                                int            width,
140                                                int            height)
141 {
142   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
143
144   pango_cairo_show_error_underline (text_renderer->cr,
145                                     (double)x / PANGO_SCALE, (double)y / PANGO_SCALE,
146                                     (double)width / PANGO_SCALE, (double)height / PANGO_SCALE);
147 }
148
149 static void
150 gtk_fill_layout_renderer_draw_shape (PangoRenderer   *renderer,
151                                      PangoAttrShape  *attr,
152                                      int              x,
153                                      int              y)
154 {
155   GtkFillLayoutRenderer *text_renderer = GTK_FILL_LAYOUT_RENDERER (renderer);
156   cairo_t *cr = text_renderer->cr;
157   PangoLayout *layout;
158   PangoCairoShapeRendererFunc shape_renderer;
159   gpointer                    shape_renderer_data;
160
161   layout = pango_renderer_get_layout (renderer);
162
163   if (!layout)
164         return;
165
166   shape_renderer = pango_cairo_context_get_shape_renderer (pango_layout_get_context (layout),
167                                                            &shape_renderer_data);
168
169   if (!shape_renderer)
170     return;
171
172   cairo_save (cr);
173
174   cairo_move_to (cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE);
175
176   shape_renderer (cr, attr, FALSE, shape_renderer_data);
177
178   cairo_restore (cr);
179 }
180
181 static void
182 gtk_fill_layout_renderer_finalize (GObject *object)
183 {
184   G_OBJECT_CLASS (_gtk_fill_layout_renderer_parent_class)->finalize (object);
185 }
186
187 static void
188 _gtk_fill_layout_renderer_init (GtkFillLayoutRenderer *renderer)
189 {
190 }
191
192 static void
193 _gtk_fill_layout_renderer_class_init (GtkFillLayoutRendererClass *klass)
194 {
195   GObjectClass *object_class = G_OBJECT_CLASS (klass);
196   
197   PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
198   
199   renderer_class->draw_glyphs = gtk_fill_layout_renderer_draw_glyphs;
200   renderer_class->draw_glyph_item = gtk_fill_layout_renderer_draw_glyph_item;
201   renderer_class->draw_rectangle = gtk_fill_layout_renderer_draw_rectangle;
202   renderer_class->draw_trapezoid = gtk_fill_layout_renderer_draw_trapezoid;
203   renderer_class->draw_error_underline = gtk_fill_layout_renderer_draw_error_underline;
204   renderer_class->draw_shape = gtk_fill_layout_renderer_draw_shape;
205
206   object_class->finalize = gtk_fill_layout_renderer_finalize;
207 }
208
209 void
210 _gtk_pango_fill_layout (cairo_t     *cr,
211                         PangoLayout *layout)
212 {
213   static GtkFillLayoutRenderer *renderer = NULL;
214   gboolean has_current_point;
215   double current_x, current_y;
216
217   has_current_point = cairo_has_current_point (cr);
218   cairo_get_current_point (cr, &current_x, &current_y);
219
220   if (renderer == NULL)
221     renderer = g_object_new (GTK_TYPE_FILL_LAYOUT_RENDERER, NULL);
222
223   cairo_save (cr);
224   cairo_translate (cr, current_x, current_y);
225
226   renderer->cr = cr;
227   pango_renderer_draw_layout (PANGO_RENDERER (renderer), layout, 0, 0);
228
229   cairo_restore (cr);
230
231   if (has_current_point)
232     cairo_move_to (cr, current_x, current_y);
233 }
234
235 static AtkAttributeSet *
236 add_attribute (AtkAttributeSet  *attributes,
237                AtkTextAttribute  attr,
238                const gchar      *value)
239 {
240   AtkAttribute *at;
241
242   at = g_new (AtkAttribute, 1);
243   at->name = g_strdup (atk_text_attribute_get_name (attr));
244   at->value = g_strdup (value);
245
246   return g_slist_prepend (attributes, at);
247 }
248
249 /*
250  * _gtk_pango_get_default_attributes:
251  * @attributes: a #AtkAttributeSet to add the attributes to
252  * @layout: the #PangoLayout from which to get attributes
253  *
254  * Adds the default text attributes from @layout to @attributes,
255  * after translating them from Pango attributes to ATK attributes.
256  *
257  * This is a convenience function that can be used to implement
258  * support for the #AtkText interface in widgets using Pango
259  * layouts.
260  *
261  * Returns: the modified @attributes
262  */
263 AtkAttributeSet*
264 _gtk_pango_get_default_attributes (AtkAttributeSet *attributes,
265                                    PangoLayout     *layout)
266 {
267   PangoContext *context;
268   gint i;
269   PangoWrapMode mode;
270
271   context = pango_layout_get_context (layout);
272   if (context)
273     {
274       PangoLanguage *language;
275       PangoFontDescription *font;
276
277       language = pango_context_get_language (context);
278       if (language)
279         attributes = add_attribute (attributes, ATK_TEXT_ATTR_LANGUAGE,
280                          pango_language_to_string (language));
281
282       font = pango_context_get_font_description (context);
283       if (font)
284         {
285           gchar buf[60];
286           attributes = add_attribute (attributes, ATK_TEXT_ATTR_STYLE,
287                            atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE,
288                                  pango_font_description_get_style (font)));
289           attributes = add_attribute (attributes, ATK_TEXT_ATTR_VARIANT,
290                            atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT,
291                                  pango_font_description_get_variant (font)));
292           attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRETCH,
293                            atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH,
294                                  pango_font_description_get_stretch (font)));
295           attributes = add_attribute (attributes, ATK_TEXT_ATTR_FAMILY_NAME,
296                            pango_font_description_get_family (font));
297           g_snprintf (buf, 60, "%d", pango_font_description_get_weight (font));
298           attributes = add_attribute (attributes, ATK_TEXT_ATTR_WEIGHT, buf);
299           g_snprintf (buf, 60, "%i", pango_font_description_get_size (font) / PANGO_SCALE);
300           attributes = add_attribute (attributes, ATK_TEXT_ATTR_SIZE, buf);
301         }
302     }
303   if (pango_layout_get_justify (layout))
304     {
305       i = 3;
306     }
307   else
308     {
309       PangoAlignment align;
310
311       align = pango_layout_get_alignment (layout);
312       if (align == PANGO_ALIGN_LEFT)
313         i = 0;
314       else if (align == PANGO_ALIGN_CENTER)
315         i = 2;
316       else   /* PANGO_ALIGN_RIGHT */
317         i = 1;
318     }
319   attributes = add_attribute (attributes, ATK_TEXT_ATTR_JUSTIFICATION,
320                    atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, i));
321   mode = pango_layout_get_wrap (layout);
322   if (mode == PANGO_WRAP_WORD)
323     i = 2;
324   else   /* PANGO_WRAP_CHAR */
325     i = 1;
326   attributes = add_attribute (attributes, ATK_TEXT_ATTR_WRAP_MODE,
327                    atk_text_attribute_get_value (ATK_TEXT_ATTR_WRAP_MODE, i));
328
329   attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRIKETHROUGH,
330                    atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, 0));
331   attributes = add_attribute (attributes, ATK_TEXT_ATTR_UNDERLINE,
332                    atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, 0));
333   attributes = add_attribute (attributes, ATK_TEXT_ATTR_RISE, "0");
334   attributes = add_attribute (attributes, ATK_TEXT_ATTR_SCALE, "1");
335   attributes = add_attribute (attributes, ATK_TEXT_ATTR_BG_FULL_HEIGHT, "0");
336   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP, "0");
337   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_BELOW_LINES, "0");
338   attributes = add_attribute (attributes, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES, "0");
339   attributes = add_attribute (attributes, ATK_TEXT_ATTR_EDITABLE,
340                    atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE, 0));
341   attributes = add_attribute (attributes, ATK_TEXT_ATTR_INVISIBLE,
342                    atk_text_attribute_get_value (ATK_TEXT_ATTR_INVISIBLE, 0));
343   attributes = add_attribute (attributes, ATK_TEXT_ATTR_INDENT, "0");
344   attributes = add_attribute (attributes, ATK_TEXT_ATTR_RIGHT_MARGIN, "0");
345   attributes = add_attribute (attributes, ATK_TEXT_ATTR_LEFT_MARGIN, "0");
346
347   return attributes;
348 }
349
350 /*
351  * _gtk_pango_get_run_attributes:
352  * @attributes: a #AtkAttributeSet to add attributes to
353  * @layout: the #PangoLayout to get the attributes from
354  * @offset: the offset at which the attributes are wanted
355  * @start_offset: return location for the starting offset
356  *    of the current run
357  * @end_offset: return location for the ending offset of the
358  *    current run
359  *
360  * Finds the 'run' around index (i.e. the maximal range of characters
361  * where the set of applicable attributes remains constant) and
362  * returns the starting and ending offsets for it.
363  *
364  * The attributes for the run are added to @attributes, after
365  * translating them from Pango attributes to ATK attributes.
366  *
367  * This is a convenience function that can be used to implement
368  * support for the #AtkText interface in widgets using Pango
369  * layouts.
370  *
371  * Returns: the modified #AtkAttributeSet
372  */
373 AtkAttributeSet *
374 _gtk_pango_get_run_attributes (AtkAttributeSet *attributes,
375                                PangoLayout     *layout,
376                                gint             offset,
377                                gint            *start_offset,
378                                gint            *end_offset)
379 {
380   PangoAttrIterator *iter;
381   PangoAttrList *attr;
382   PangoAttrString *pango_string;
383   PangoAttrInt *pango_int;
384   PangoAttrColor *pango_color;
385   PangoAttrLanguage *pango_lang;
386   PangoAttrFloat *pango_float;
387   gint index, start_index, end_index;
388   gboolean is_next;
389   glong len;
390   const gchar *text;
391   gchar *value;
392
393   text = pango_layout_get_text (layout);
394   len = g_utf8_strlen (text, -1);
395
396   /* Grab the attributes of the PangoLayout, if any */
397   attr = pango_layout_get_attributes (layout);
398
399   if (attr == NULL)
400     {
401       *start_offset = 0;
402       *end_offset = len;
403       return attributes;
404     }
405
406   iter = pango_attr_list_get_iterator (attr);
407   /* Get invariant range offsets */
408   /* If offset out of range, set offset in range */
409   if (offset > len)
410     offset = len;
411   else if (offset < 0)
412     offset = 0;
413
414   index = g_utf8_offset_to_pointer (text, offset) - text;
415   pango_attr_iterator_range (iter, &start_index, &end_index);
416   is_next = TRUE;
417   while (is_next)
418     {
419       if (index >= start_index && index < end_index)
420         {
421           *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
422           if (end_index == G_MAXINT) /* Last iterator */
423             end_index = len;
424
425           *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
426           break;
427         }
428       is_next = pango_attr_iterator_next (iter);
429       pango_attr_iterator_range (iter, &start_index, &end_index);
430     }
431
432   /* Get attributes */
433   pango_string = (PangoAttrString*) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY);
434   if (pango_string != NULL)
435     {
436       value = g_strdup_printf ("%s", pango_string->value);
437       attributes = add_attribute (attributes, ATK_TEXT_ATTR_FAMILY_NAME, value);
438       g_free (value);
439     }
440   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STYLE);
441   if (pango_int != NULL)
442     {
443       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STYLE,
444                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, pango_int->value));
445     }
446   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT);
447   if (pango_int != NULL)
448     {
449       value = g_strdup_printf ("%i", pango_int->value);
450       attributes = add_attribute (attributes, ATK_TEXT_ATTR_WEIGHT, value);
451       g_free (value);
452     }
453   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_VARIANT);
454   if (pango_int != NULL)
455     {
456       attributes = add_attribute (attributes, ATK_TEXT_ATTR_VARIANT,
457                        atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, pango_int->value));
458     }
459   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STRETCH);
460   if (pango_int != NULL)
461     {
462       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRETCH,
463                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, pango_int->value));
464     }
465   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE);
466   if (pango_int != NULL)
467     {
468       value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE);
469       attributes = add_attribute (attributes, ATK_TEXT_ATTR_SIZE, value);
470       g_free (value);
471     }
472   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
473   if (pango_int != NULL)
474     {
475       attributes = add_attribute (attributes, ATK_TEXT_ATTR_UNDERLINE,
476                        atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, pango_int->value));
477     }
478   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH);
479   if (pango_int != NULL)
480     {
481       attributes = add_attribute (attributes, ATK_TEXT_ATTR_STRIKETHROUGH,
482                        atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, pango_int->value));
483     }
484   pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter, PANGO_ATTR_RISE);
485   if (pango_int != NULL)
486     {
487       value = g_strdup_printf ("%i", pango_int->value);
488       attributes = add_attribute (attributes, ATK_TEXT_ATTR_RISE, value);
489       g_free (value);
490     }
491   pango_lang = (PangoAttrLanguage*) pango_attr_iterator_get (iter, PANGO_ATTR_LANGUAGE);
492   if (pango_lang != NULL)
493     {
494       attributes = add_attribute (attributes, ATK_TEXT_ATTR_LANGUAGE,
495                                   pango_language_to_string (pango_lang->value));
496     }
497   pango_float = (PangoAttrFloat*) pango_attr_iterator_get (iter, PANGO_ATTR_SCALE);
498   if (pango_float != NULL)
499     {
500       value = g_strdup_printf ("%g", pango_float->value);
501       attributes = add_attribute (attributes, ATK_TEXT_ATTR_SCALE, value);
502       g_free (value);
503     }
504   pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
505   if (pango_color != NULL)
506     {
507       value = g_strdup_printf ("%u,%u,%u",
508                                pango_color->color.red,
509                                pango_color->color.green,
510                                pango_color->color.blue);
511       attributes = add_attribute (attributes, ATK_TEXT_ATTR_FG_COLOR, value);
512       g_free (value);
513     }
514   pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter, PANGO_ATTR_BACKGROUND);
515   if (pango_color != NULL)
516     {
517       value = g_strdup_printf ("%u,%u,%u",
518                                pango_color->color.red,
519                                pango_color->color.green,
520                                pango_color->color.blue);
521       attributes = add_attribute (attributes, ATK_TEXT_ATTR_BG_COLOR, value);
522       g_free (value);
523     }
524   pango_attr_iterator_destroy (iter);
525
526   return attributes;
527 }
528
529 /*
530  * _gtk_pango_move_chars:
531  * @layout: a #PangoLayout
532  * @offset: a character offset in @layout
533  * @count: the number of characters to move from @offset
534  *
535  * Returns the position that is @count characters from the
536  * given @offset. @count may be positive or negative.
537  *
538  * For the purpose of this function, characters are defined
539  * by what Pango considers cursor positions.
540  *
541  * Returns: the new position
542  */
543 gint
544 _gtk_pango_move_chars (PangoLayout *layout,
545                        gint         offset,
546                        gint         count)
547 {
548   const PangoLogAttr *attrs;
549   gint n_attrs;
550
551   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
552
553   while (count > 0 && offset < n_attrs - 1)
554     {
555       do
556         offset++;
557       while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position);
558
559       count--;
560     }
561   while (count < 0 && offset > 0)
562     {
563       do
564         offset--;
565       while (offset > 0 && !attrs[offset].is_cursor_position);
566
567       count++;
568     }
569
570   return offset;
571 }
572
573 /*
574  * _gtk_pango_move_words:
575  * @layout: a #PangoLayout
576  * @offset: a character offset in @layout
577  * @count: the number of words to move from @offset
578  *
579  * Returns the position that is @count words from the
580  * given @offset. @count may be positive or negative.
581  *
582  * If @count is positive, the returned position will
583  * be a word end, otherwise it will be a word start.
584  * See the Pango documentation for details on how
585  * word starts and ends are defined.
586  *
587  * Returns: the new position
588  */
589 gint
590 _gtk_pango_move_words (PangoLayout  *layout,
591                        gint          offset,
592                        gint          count)
593 {
594   const PangoLogAttr *attrs;
595   gint n_attrs;
596
597   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
598
599   while (count > 0 && offset < n_attrs - 1)
600     {
601       do
602         offset++;
603       while (offset < n_attrs - 1 && !attrs[offset].is_word_end);
604
605       count--;
606     }
607   while (count < 0 && offset > 0)
608     {
609       do
610         offset--;
611       while (offset > 0 && !attrs[offset].is_word_start);
612
613       count++;
614     }
615
616   return offset;
617 }
618
619 /*
620  * _gtk_pango_move_sentences:
621  * @layout: a #PangoLayout
622  * @offset: a character offset in @layout
623  * @count: the number of sentences to move from @offset
624  *
625  * Returns the position that is @count sentences from the
626  * given @offset. @count may be positive or negative.
627  *
628  * If @count is positive, the returned position will
629  * be a sentence end, otherwise it will be a sentence start.
630  * See the Pango documentation for details on how
631  * sentence starts and ends are defined.
632  *
633  * Returns: the new position
634  */
635 gint
636 _gtk_pango_move_sentences (PangoLayout  *layout,
637                            gint          offset,
638                            gint          count)
639 {
640   const PangoLogAttr *attrs;
641   gint n_attrs;
642
643   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
644
645   while (count > 0 && offset < n_attrs - 1)
646     {
647       do
648         offset++;
649       while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end);
650
651       count--;
652     }
653   while (count < 0 && offset > 0)
654     {
655       do
656         offset--;
657       while (offset > 0 && !attrs[offset].is_sentence_start);
658
659       count++;
660     }
661
662   return offset;
663 }
664
665 /*
666  * _gtk_pango_move_lines:
667  * @layout: a #PangoLayout
668  * @offset: a character offset in @layout
669  * @count: the number of lines to move from @offset
670  *
671  * Returns the position that is @count lines from the
672  * given @offset. @count may be positive or negative.
673  *
674  * If @count is negative, the returned position will
675  * be the start of a line, else it will be the end of
676  * line.
677  *
678  * Returns: the new position
679  */
680 gint
681 _gtk_pango_move_lines (PangoLayout *layout,
682                        gint         offset,
683                        gint         count)
684 {
685   GSList *lines, *l;
686   PangoLayoutLine *line;
687   gint num;
688   const gchar *text;
689   gint pos, line_pos;
690   gint index;
691   gint len;
692
693   text = pango_layout_get_text (layout);
694   index = g_utf8_offset_to_pointer (text, offset) - text;
695   lines = pango_layout_get_lines (layout);
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_end && 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_end && 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_word_end && 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 }