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
22const char *
23pango_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
38const char *
39pango_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
62const char *
63pango_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
90const char *
91pango_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
113const char *
114pango_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
129static const char *
130pango_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
145void
146gtk_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 */
181void
182gtk_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 */
244void
245gtk_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 */
419static int
420gtk_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 */
465static int
466gtk_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 */
511static int
512gtk_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 */
557static int
558gtk_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 */
623static gboolean
624gtk_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 */
652static gboolean
653gtk_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
671static void
672pango_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
750static void
751pango_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
814static void
815pango_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 */
904char *
905gtk_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 */
1009char *
1010gtk_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 */
1120char *
1121gtk_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
1206char *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

source code of gtk/gtk/a11y/gtkatspipango.c