]> Pileus Git - ~andy/gtk/blob - gtk/gtkpango.c
Merge branch 'win32-theme2'
[~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   line = NULL;
697
698   num = 0;
699   for (l = lines; l; l = l->next)
700     {
701       line = l->data;
702       if (index < line->start_index + line->length)
703         break;
704       num++;
705     }
706
707   if (count < 0)
708     {
709       num += count;
710       if (num < 0)
711         num = 0;
712
713       line = g_slist_nth_data (lines, num);
714
715       return g_utf8_pointer_to_offset (text, text + line->start_index);
716     }
717   else
718     {
719       line_pos = index - line->start_index;
720
721       len = g_slist_length (lines);
722       num += count;
723       if (num >= len || (count == 0 && num == len - 1))
724         return g_utf8_strlen (text, -1) - 1;
725
726       line = l->data;
727       pos = line->start_index + line_pos;
728       if (pos >= line->start_index + line->length)
729         pos = line->start_index + line->length - 1;
730
731       return g_utf8_pointer_to_offset (text, text + pos);
732     }
733 }
734
735 /*
736  * _gtk_pango_is_inside_word:
737  * @layout: a #PangoLayout
738  * @offset: a character offset in @layout
739  *
740  * Returns whether the given position is inside
741  * a word.
742  *
743  * Returns: %TRUE if @offset is inside a word
744  */
745 gboolean
746 _gtk_pango_is_inside_word (PangoLayout  *layout,
747                            gint          offset)
748 {
749   const PangoLogAttr *attrs;
750   gint n_attrs;
751
752   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
753
754   while (offset >= 0 &&
755          !(attrs[offset].is_word_start || attrs[offset].is_word_end))
756     offset--;
757
758   if (offset >= 0)
759     return attrs[offset].is_word_start;
760
761   return FALSE;
762 }
763
764 /*
765  * _gtk_pango_is_inside_sentence:
766  * @layout: a #PangoLayout
767  * @offset: a character offset in @layout
768  *
769  * Returns whether the given position is inside
770  * a sentence.
771  *
772  * Returns: %TRUE if @offset is inside a sentence
773  */
774 gboolean
775 _gtk_pango_is_inside_sentence (PangoLayout  *layout,
776                                gint          offset)
777 {
778   const PangoLogAttr *attrs;
779   gint n_attrs;
780
781   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
782
783   while (offset >= 0 &&
784          !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
785     offset--;
786
787   if (offset >= 0)
788     return attrs[offset].is_sentence_start;
789
790   return FALSE;
791 }
792
793 static void
794 pango_layout_get_line_before (PangoLayout     *layout,
795                               AtkTextBoundary  boundary_type,
796                               gint             offset,
797                               gint            *start_offset,
798                               gint            *end_offset)
799 {
800   PangoLayoutIter *iter;
801   PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL;
802   gint index, start_index, end_index;
803   const gchar *text;
804   gboolean found = FALSE;
805
806   text = pango_layout_get_text (layout);
807   index = g_utf8_offset_to_pointer (text, offset) - text;
808   iter = pango_layout_get_iter (layout);
809   do
810     {
811       line = pango_layout_iter_get_line (iter);
812       start_index = line->start_index;
813       end_index = start_index + line->length;
814
815       if (index >= start_index && index <= end_index)
816         {
817           /* Found line for offset */
818           if (prev_line)
819             {
820               switch (boundary_type)
821                 {
822                 case ATK_TEXT_BOUNDARY_LINE_START:
823                   end_index = start_index;
824                   start_index = prev_line->start_index;
825                   break;
826                 case ATK_TEXT_BOUNDARY_LINE_END:
827                   if (prev_prev_line)
828                     start_index = prev_prev_line->start_index + prev_prev_line->length;
829                   else
830                     start_index = 0;
831                   end_index = prev_line->start_index + prev_line->length;
832                   break;
833                 default:
834                   g_assert_not_reached();
835                 }
836             }
837           else
838             start_index = end_index = 0;
839
840           found = TRUE;
841           break;
842         }
843
844       prev_prev_line = prev_line;
845       prev_line = line;
846     }
847   while (pango_layout_iter_next_line (iter));
848
849   if (!found)
850     {
851       start_index = prev_line->start_index + prev_line->length;
852       end_index = start_index;
853     }
854   pango_layout_iter_free (iter);
855
856   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
857   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
858 }
859
860 static void
861 pango_layout_get_line_at (PangoLayout     *layout,
862                           AtkTextBoundary  boundary_type,
863                           gint             offset,
864                           gint            *start_offset,
865                           gint            *end_offset)
866 {
867   PangoLayoutIter *iter;
868   PangoLayoutLine *line, *prev_line = NULL;
869   gint index, start_index, end_index;
870   const gchar *text;
871   gboolean found = FALSE;
872
873   text = pango_layout_get_text (layout);
874   index = g_utf8_offset_to_pointer (text, offset) - text;
875   iter = pango_layout_get_iter (layout);
876   do
877     {
878       line = pango_layout_iter_get_line (iter);
879       start_index = line->start_index;
880       end_index = start_index + line->length;
881
882       if (index >= start_index && index <= end_index)
883         {
884           /* Found line for offset */
885           switch (boundary_type)
886             {
887             case ATK_TEXT_BOUNDARY_LINE_START:
888               if (pango_layout_iter_next_line (iter))
889                 end_index = pango_layout_iter_get_line (iter)->start_index;
890               break;
891             case ATK_TEXT_BOUNDARY_LINE_END:
892               if (prev_line)
893                 start_index = prev_line->start_index + prev_line->length;
894               break;
895             default:
896               g_assert_not_reached();
897             }
898
899           found = TRUE;
900           break;
901         }
902
903       prev_line = line;
904     }
905   while (pango_layout_iter_next_line (iter));
906
907   if (!found)
908     {
909       start_index = prev_line->start_index + prev_line->length;
910       end_index = start_index;
911     }
912   pango_layout_iter_free (iter);
913
914   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
915   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
916 }
917
918 static void
919 pango_layout_get_line_after (PangoLayout     *layout,
920                              AtkTextBoundary  boundary_type,
921                              gint             offset,
922                              gint            *start_offset,
923                              gint            *end_offset)
924 {
925   PangoLayoutIter *iter;
926   PangoLayoutLine *line, *prev_line = NULL;
927   gint index, start_index, end_index;
928   const gchar *text;
929   gboolean found = FALSE;
930
931   text = pango_layout_get_text (layout);
932   index = g_utf8_offset_to_pointer (text, offset) - text;
933   iter = pango_layout_get_iter (layout);
934   do
935     {
936       line = pango_layout_iter_get_line (iter);
937       start_index = line->start_index;
938       end_index = start_index + line->length;
939
940       if (index >= start_index && index <= end_index)
941         {
942           /* Found line for offset */
943           if (pango_layout_iter_next_line (iter))
944             {
945               line = pango_layout_iter_get_line (iter);
946               switch (boundary_type)
947                 {
948                 case ATK_TEXT_BOUNDARY_LINE_START:
949                   start_index = line->start_index;
950                   if (pango_layout_iter_next_line (iter))
951                     end_index = pango_layout_iter_get_line (iter)->start_index;
952                   else
953                     end_index = start_index + line->length;
954                   break;
955                 case ATK_TEXT_BOUNDARY_LINE_END:
956                   start_index = end_index;
957                   end_index = line->start_index + line->length;
958                   break;
959                 default:
960                   g_assert_not_reached();
961                 }
962             }
963           else
964             start_index = end_index;
965
966           found = TRUE;
967           break;
968         }
969
970       prev_line = line;
971     }
972   while (pango_layout_iter_next_line (iter));
973
974   if (!found)
975     {
976       start_index = prev_line->start_index + prev_line->length;
977       end_index = start_index;
978     }
979   pango_layout_iter_free (iter);
980
981   *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
982   *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
983 }
984
985 /*
986  * _gtk_pango_get_text_before:
987  * @layout: a #PangoLayout
988  * @boundary_type: a #AtkTextBoundary
989  * @offset: a character offset in @layout
990  * @start_offset: return location for the start of the returned text
991  * @end_offset: return location for the end of the return text
992  *
993  * Gets a slice of the text from @layout before @offset.
994  *
995  * The @boundary_type determines the size of the returned slice of
996  * text. For the exact semantics of this function, see
997  * atk_text_get_text_before_offset().
998  *
999  * Returns: a newly allocated string containing a slice of text
1000  *     from layout. Free with g_free().
1001  */
1002 gchar *
1003 _gtk_pango_get_text_before (PangoLayout     *layout,
1004                             AtkTextBoundary  boundary_type,
1005                             gint             offset,
1006                             gint            *start_offset,
1007                             gint            *end_offset)
1008 {
1009   const gchar *text;
1010   gint start, end;
1011   const PangoLogAttr *attrs;
1012   gint n_attrs;
1013
1014   text = pango_layout_get_text (layout);
1015
1016   if (text[0] == 0)
1017     {
1018       *start_offset = 0;
1019       *end_offset = 0;
1020       return g_strdup ("");
1021     }
1022
1023   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1024
1025   start = offset;
1026   end = start;
1027
1028   switch (boundary_type)
1029     {
1030     case ATK_TEXT_BOUNDARY_CHAR:
1031       start = _gtk_pango_move_chars (layout, start, -1);
1032       break;
1033
1034     case ATK_TEXT_BOUNDARY_WORD_START:
1035       if (!attrs[start].is_word_start)
1036         start = _gtk_pango_move_words (layout, start, -1);
1037       end = start;
1038       start = _gtk_pango_move_words (layout, start, -1);
1039       break;
1040
1041     case ATK_TEXT_BOUNDARY_WORD_END:
1042       if (_gtk_pango_is_inside_word (layout, start) &&
1043           !attrs[start].is_word_start)
1044         start = _gtk_pango_move_words (layout, start, -1);
1045       while (!attrs[start].is_word_end && start > 0)
1046         start = _gtk_pango_move_chars (layout, start, -1);
1047       end = start;
1048       start = _gtk_pango_move_words (layout, start, -1);
1049       while (!attrs[start].is_word_end && start > 0)
1050         start = _gtk_pango_move_chars (layout, start, -1);
1051       break;
1052
1053     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1054       if (!attrs[start].is_sentence_start)
1055         start = _gtk_pango_move_sentences (layout, start, -1);
1056       end = start;
1057       start = _gtk_pango_move_sentences (layout, start, -1);
1058       break;
1059
1060     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1061       if (_gtk_pango_is_inside_sentence (layout, start) &&
1062           !attrs[start].is_sentence_start)
1063         start = _gtk_pango_move_sentences (layout, start, -1);
1064       while (!attrs[start].is_sentence_end && start > 0)
1065         start = _gtk_pango_move_chars (layout, start, -1);
1066       end = start;
1067       start = _gtk_pango_move_sentences (layout, start, -1);
1068       while (!attrs[start].is_sentence_end && start > 0)
1069         start = _gtk_pango_move_chars (layout, start, -1);
1070       break;
1071
1072     case ATK_TEXT_BOUNDARY_LINE_START:
1073     case ATK_TEXT_BOUNDARY_LINE_END:
1074       pango_layout_get_line_before (layout, boundary_type, offset, &start, &end);
1075       break;
1076     }
1077
1078   *start_offset = start;
1079   *end_offset = end;
1080
1081   g_assert (start <= end);
1082
1083   return g_utf8_substring (text, start, end);
1084 }
1085
1086 /*
1087  * _gtk_pango_get_text_after:
1088  * @layout: a #PangoLayout
1089  * @boundary_type: a #AtkTextBoundary
1090  * @offset: a character offset in @layout
1091  * @start_offset: return location for the start of the returned text
1092  * @end_offset: return location for the end of the return text
1093  *
1094  * Gets a slice of the text from @layout after @offset.
1095  *
1096  * The @boundary_type determines the size of the returned slice of
1097  * text. For the exact semantics of this function, see
1098  * atk_text_get_text_after_offset().
1099  *
1100  * Returns: a newly allocated string containing a slice of text
1101  *     from layout. Free with g_free().
1102  */
1103 gchar *
1104 _gtk_pango_get_text_after (PangoLayout     *layout,
1105                            AtkTextBoundary  boundary_type,
1106                            gint             offset,
1107                            gint            *start_offset,
1108                            gint            *end_offset)
1109 {
1110   const gchar *text;
1111   gint start, end;
1112   const PangoLogAttr *attrs;
1113   gint n_attrs;
1114
1115   text = pango_layout_get_text (layout);
1116
1117   if (text[0] == 0)
1118     {
1119       *start_offset = 0;
1120       *end_offset = 0;
1121       return g_strdup ("");
1122     }
1123
1124   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1125
1126   start = offset;
1127   end = start;
1128
1129   switch (boundary_type)
1130     {
1131     case ATK_TEXT_BOUNDARY_CHAR:
1132       start = _gtk_pango_move_chars (layout, start, 1);
1133       end = start;
1134       end = _gtk_pango_move_chars (layout, end, 1);
1135       break;
1136
1137     case ATK_TEXT_BOUNDARY_WORD_START:
1138       if (_gtk_pango_is_inside_word (layout, end))
1139         end = _gtk_pango_move_words (layout, end, 1);
1140       while (!attrs[end].is_word_start && end < n_attrs - 1)
1141         end = _gtk_pango_move_chars (layout, end, 1);
1142       start = end;
1143       if (end < n_attrs - 1)
1144         {
1145           end = _gtk_pango_move_words (layout, end, 1);
1146           while (!attrs[end].is_word_start && end < n_attrs - 1)
1147             end = _gtk_pango_move_chars (layout, end, 1);
1148         }
1149       break;
1150
1151     case ATK_TEXT_BOUNDARY_WORD_END:
1152       end = _gtk_pango_move_words (layout, end, 1);
1153       start = end;
1154       if (end < n_attrs - 1)
1155         end = _gtk_pango_move_words (layout, end, 1);
1156       break;
1157
1158     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1159       if (_gtk_pango_is_inside_sentence (layout, end))
1160         end = _gtk_pango_move_sentences (layout, end, 1);
1161       while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1162         end = _gtk_pango_move_chars (layout, end, 1);
1163       start = end;
1164       if (end < n_attrs - 1)
1165         {
1166           end = _gtk_pango_move_sentences (layout, end, 1);
1167           while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1168             end = _gtk_pango_move_chars (layout, end, 1);
1169         }
1170       break;
1171
1172     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1173       end = _gtk_pango_move_sentences (layout, end, 1);
1174       start = end;
1175       if (end < n_attrs - 1)
1176         end = _gtk_pango_move_sentences (layout, end, 1);
1177       break;
1178
1179     case ATK_TEXT_BOUNDARY_LINE_START:
1180     case ATK_TEXT_BOUNDARY_LINE_END:
1181       pango_layout_get_line_after (layout, boundary_type, offset, &start, &end);
1182       break;
1183     }
1184
1185   *start_offset = start;
1186   *end_offset = end;
1187
1188   g_assert (start <= end);
1189
1190   return g_utf8_substring (text, start, end);
1191 }
1192
1193 /*
1194  * _gtk_pango_get_text_at:
1195  * @layout: a #PangoLayout
1196  * @boundary_type: a #AtkTextBoundary
1197  * @offset: a character offset in @layout
1198  * @start_offset: return location for the start of the returned text
1199  * @end_offset: return location for the end of the return text
1200  *
1201  * Gets a slice of the text from @layout at @offset.
1202  *
1203  * The @boundary_type determines the size of the returned slice of
1204  * text. For the exact semantics of this function, see
1205  * atk_text_get_text_after_offset().
1206  *
1207  * Returns: a newly allocated string containing a slice of text
1208  *     from layout. Free with g_free().
1209  */
1210 gchar *
1211 _gtk_pango_get_text_at (PangoLayout     *layout,
1212                         AtkTextBoundary  boundary_type,
1213                         gint             offset,
1214                         gint            *start_offset,
1215                         gint            *end_offset)
1216 {
1217   const gchar *text;
1218   gint start, end;
1219   const PangoLogAttr *attrs;
1220   gint n_attrs;
1221
1222   text = pango_layout_get_text (layout);
1223
1224   if (text[0] == 0)
1225     {
1226       *start_offset = 0;
1227       *end_offset = 0;
1228       return g_strdup ("");
1229     }
1230
1231   attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
1232
1233   start = offset;
1234   end = start;
1235
1236   switch (boundary_type)
1237     {
1238     case ATK_TEXT_BOUNDARY_CHAR:
1239       end = _gtk_pango_move_chars (layout, end, 1);
1240       break;
1241
1242     case ATK_TEXT_BOUNDARY_WORD_START:
1243       if (!attrs[start].is_word_start)
1244         start = _gtk_pango_move_words (layout, start, -1);
1245       if (_gtk_pango_is_inside_word (layout, end))
1246         end = _gtk_pango_move_words (layout, end, 1);
1247       while (!attrs[end].is_word_start && end < n_attrs - 1)
1248         end = _gtk_pango_move_chars (layout, end, 1);
1249       break;
1250
1251     case ATK_TEXT_BOUNDARY_WORD_END:
1252       if (_gtk_pango_is_inside_word (layout, start) &&
1253           !attrs[start].is_word_start)
1254         start = _gtk_pango_move_words (layout, start, -1);
1255       while (!attrs[start].is_word_end && start > 0)
1256         start = _gtk_pango_move_chars (layout, start, -1);
1257       end = _gtk_pango_move_words (layout, end, 1);
1258       break;
1259
1260     case ATK_TEXT_BOUNDARY_SENTENCE_START:
1261       if (!attrs[start].is_sentence_start)
1262         start = _gtk_pango_move_sentences (layout, start, -1);
1263       if (_gtk_pango_is_inside_sentence (layout, end))
1264         end = _gtk_pango_move_sentences (layout, end, 1);
1265       while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1266         end = _gtk_pango_move_chars (layout, end, 1);
1267       break;
1268
1269     case ATK_TEXT_BOUNDARY_SENTENCE_END:
1270       if (_gtk_pango_is_inside_sentence (layout, start) &&
1271           !attrs[start].is_sentence_start)
1272         start = _gtk_pango_move_sentences (layout, start, -1);
1273       while (!attrs[start].is_sentence_end && start > 0)
1274         start = _gtk_pango_move_chars (layout, start, -1);
1275       end = _gtk_pango_move_sentences (layout, end, 1);
1276       break;
1277
1278     case ATK_TEXT_BOUNDARY_LINE_START:
1279     case ATK_TEXT_BOUNDARY_LINE_END:
1280       pango_layout_get_line_at (layout, boundary_type, offset, &start, &end);
1281       break;
1282     }
1283
1284   *start_offset = start;
1285   *end_offset = end;
1286
1287   g_assert (start <= end);
1288
1289   return g_utf8_substring (text, start, end);
1290 }