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