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