diff options
Diffstat (limited to 'gtk/a11y/gtkatspipango.c')
-rw-r--r-- | gtk/a11y/gtkatspipango.c | 1257 |
1 files changed, 1257 insertions, 0 deletions
diff --git a/gtk/a11y/gtkatspipango.c b/gtk/a11y/gtkatspipango.c new file mode 100644 index 0000000000..1171c8758b --- /dev/null +++ b/gtk/a11y/gtkatspipango.c @@ -0,0 +1,1257 @@ +/* gtkatspipango.c - pango-related utilities for AT-SPI + * + * Copyright (c) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free + */ + +#include "config.h" +#include "gtkatspipangoprivate.h" + +const char * +pango_style_to_string (PangoStyle style) +{ + switch (style) + { + case PANGO_STYLE_NORMAL: + return "normal"; + case PANGO_STYLE_OBLIQUE: + return "oblique"; + case PANGO_STYLE_ITALIC: + return "italic"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_variant_to_string (PangoVariant variant) +{ + switch (variant) + { + case PANGO_VARIANT_NORMAL: + return "normal"; + case PANGO_VARIANT_SMALL_CAPS: + return "small_caps"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_stretch_to_string (PangoStretch stretch) +{ + switch (stretch) + { + case PANGO_STRETCH_ULTRA_CONDENSED: + return "ultra_condensed"; + case PANGO_STRETCH_EXTRA_CONDENSED: + return "extra_condensed"; + case PANGO_STRETCH_CONDENSED: + return "condensed"; + case PANGO_STRETCH_SEMI_CONDENSED: + return "semi_condensed"; + case PANGO_STRETCH_NORMAL: + return "normal"; + case PANGO_STRETCH_SEMI_EXPANDED: + return "semi_expanded"; + case PANGO_STRETCH_EXPANDED: + return "expanded"; + case PANGO_STRETCH_EXTRA_EXPANDED: + return "extra_expanded"; + case PANGO_STRETCH_ULTRA_EXPANDED: + return "ultra_expanded"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_underline_to_string (PangoUnderline value) +{ + switch (value) + { + case PANGO_UNDERLINE_NONE: + return "none"; + case PANGO_UNDERLINE_SINGLE: + case PANGO_UNDERLINE_SINGLE_LINE: + return "single"; + case PANGO_UNDERLINE_DOUBLE: + case PANGO_UNDERLINE_DOUBLE_LINE: + return "double"; + case PANGO_UNDERLINE_LOW: + return "low"; + case PANGO_UNDERLINE_ERROR: + case PANGO_UNDERLINE_ERROR_LINE: + return "error"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_wrap_mode_to_string (PangoWrapMode mode) +{ + switch (mode) + { + case PANGO_WRAP_WORD: + return "word"; + case PANGO_WRAP_CHAR: + return "char"; + case PANGO_WRAP_WORD_CHAR: + return "word-char"; + default: + g_assert_not_reached (); + } +} + +void +gtk_pango_get_font_attributes (PangoFontDescription *font, + GVariantBuilder *builder) +{ + char buf[60]; + + g_variant_builder_add (builder, "{ss}", "style", + pango_style_to_string (pango_font_description_get_style (font))); + g_variant_builder_add (builder, "{ss}", "variant", + pango_variant_to_string (pango_font_description_get_variant (font))); + g_variant_builder_add (builder, "{ss}", "stretch", + pango_stretch_to_string (pango_font_description_get_stretch (font))); + g_variant_builder_add (builder, "{ss}", "family-name", + pango_font_description_get_family (font)); + + g_snprintf (buf, 60, "%d", pango_font_description_get_weight (font)); + g_variant_builder_add (builder, "{ss}", "weight", buf); + g_snprintf (buf, 60, "%i", pango_font_description_get_size (font) / PANGO_SCALE); + g_variant_builder_add (builder, "{ss}", "size", buf); +} + +/* + * gtk_pango_get_default_attributes: + * @attributes: a #AtkAttributeSet to add the attributes to + * @layout: the #PangoLayout from which to get attributes + * + * Adds the default text attributes from @layout to @attributes, + * after translating them from Pango attributes to atspi attributes. + * + * This is a convenience function that can be used to implement + * support for the #AtkText interface in widgets using Pango + * layouts. + * + * Returns: the modified @attributes + */ +void +gtk_pango_get_default_attributes (PangoLayout *layout, + GVariantBuilder *builder) +{ + PangoContext *context; + const char *val; + PangoAlignment align; + PangoWrapMode mode; + + context = pango_layout_get_context (layout); + if (context) + { + PangoLanguage *language; + PangoFontDescription *font; + + language = pango_context_get_language (context); + if (language) + g_variant_builder_add (builder, "{ss}", "language", + pango_language_to_string (language)); + + font = pango_context_get_font_description (context); + if (font) + gtk_pango_get_font_attributes (font, builder); + } + if (pango_layout_get_justify (layout)) + { + val = "fill"; + } + else + { + align = pango_layout_get_alignment (layout); + if (align == PANGO_ALIGN_LEFT) + val = "left"; + else if (align == PANGO_ALIGN_CENTER) + val = "center"; + else + val = "right"; + } + g_variant_builder_add (builder, "{ss}", "justification", val); + + mode = pango_layout_get_wrap (layout); + g_variant_builder_add (builder, "{ss}", "wrap-mode", + pango_wrap_mode_to_string (mode)); + + g_variant_builder_add (builder, "{ss}", "strikethrough", "false"); + g_variant_builder_add (builder, "{ss}", "underline", "false"); + g_variant_builder_add (builder, "{ss}", "rise", "0"); + g_variant_builder_add (builder, "{ss}", "scale", "1"); + g_variant_builder_add (builder, "{ss}", "bg-full-height", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-below-lines", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-above-lines", "0"); + g_variant_builder_add (builder, "{ss}", "editable", "false"); + g_variant_builder_add (builder, "{ss}", "invisible", "false"); + g_variant_builder_add (builder, "{ss}", "indent", "0"); + g_variant_builder_add (builder, "{ss}", "right-margin", "0"); + g_variant_builder_add (builder, "{ss}", "left-margin", "0"); +} + +/* + * gtk_pango_get_run_attributes: + * @layout: the #PangoLayout to get the attributes from + * @builder: GVariantBuilder to add to + * @offset: the offset at which the attributes are wanted + * @start_offset: return location for the starting offset + * of the current run + * @end_offset: return location for the ending offset of the + * current run + * + * Finds the “run” around index (i.e. the maximal range of characters + * where the set of applicable attributes remains constant) and + * returns the starting and ending offsets for it. + * + * The attributes for the run are added to @attributes, after + * translating them from Pango attributes to atspi attributes. + * + * This is a convenience function that can be used to implement + * support for the #AtkText interface in widgets using Pango + * layouts. + */ +void +gtk_pango_get_run_attributes (PangoLayout *layout, + GVariantBuilder *builder, + int offset, + int *start_offset, + int *end_offset) +{ + PangoAttrIterator *iter; + PangoAttrList *attr; + PangoAttrString *pango_string; + PangoAttrInt *pango_int; + PangoAttrColor *pango_color; + PangoAttrLanguage *pango_lang; + PangoAttrFloat *pango_float; + int index, start_index, end_index; + gboolean is_next; + glong len; + const char *text; + char *value; + const char *val; + + text = pango_layout_get_text (layout); + len = g_utf8_strlen (text, -1); + + /* Grab the attributes of the PangoLayout, if any */ + attr = pango_layout_get_attributes (layout); + + if (attr == NULL) + { + *start_offset = 0; + *end_offset = len; + return; + } + + iter = pango_attr_list_get_iterator (attr); + /* Get invariant range offsets */ + /* If offset out of range, set offset in range */ + if (offset > len) + offset = len; + else if (offset < 0) + offset = 0; + + index = g_utf8_offset_to_pointer (text, offset) - text; + pango_attr_iterator_range (iter, &start_index, &end_index); + is_next = TRUE; + while (is_next) + { + if (index >= start_index && index < end_index) + { + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + if (end_index == G_MAXINT) /* Last iterator */ + end_index = len; + + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); + break; + } + is_next = pango_attr_iterator_next (iter); + pango_attr_iterator_range (iter, &start_index, &end_index); + } + + /* Get attributes */ + pango_string = (PangoAttrString *) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY); + if (pango_string != NULL) + { + value = g_strdup_printf ("%s", pango_string->value); + g_variant_builder_add (builder, "{ss}", "family-name", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STYLE); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "style", pango_style_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value); + g_variant_builder_add (builder, "{ss}", "weight", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_VARIANT); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "variant", + pango_variant_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRETCH); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "stretch", + pango_stretch_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE); + g_variant_builder_add (builder, "{ss}", "size", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "underline", + pango_underline_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH); + if (pango_int != NULL) + { + if (pango_int->value) + val = "true"; + else + val = "false"; + g_variant_builder_add (builder, "{ss}", "strikethrough", val); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_RISE); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value); + g_variant_builder_add (builder, "{ss}", "rise", value); + g_free (value); + } + + pango_lang = (PangoAttrLanguage *) pango_attr_iterator_get (iter, PANGO_ATTR_LANGUAGE); + if (pango_lang != NULL) + { + g_variant_builder_add (builder, "{ss}", "language", + pango_language_to_string (pango_lang->value)); + } + + pango_float = (PangoAttrFloat *) pango_attr_iterator_get (iter, PANGO_ATTR_SCALE); + if (pango_float != NULL) + { + value = g_strdup_printf ("%g", pango_float->value); + g_variant_builder_add (builder, "{ss}", "scale", value); + g_free (value); + } + + pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND); + if (pango_color != NULL) + { + value = g_strdup_printf ("%u,%u,%u", + pango_color->color.red, + pango_color->color.green, + pango_color->color.blue); + g_variant_builder_add (builder, "{ss}", "fg-color", value); + g_free (value); + } + + pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_BACKGROUND); + if (pango_color != NULL) + { + value = g_strdup_printf ("%u,%u,%u", + pango_color->color.red, + pango_color->color.green, + pango_color->color.blue); + g_variant_builder_add (builder, "{ss}", "bg-color", value); + g_free (value); + } + pango_attr_iterator_destroy (iter); +} + +/* + * gtk_pango_move_chars: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of characters to move from @offset + * + * Returns the position that is @count characters from the + * given @offset. @count may be positive or negative. + * + * For the purpose of this function, characters are defined + * by what Pango considers cursor positions. + * + * Returns: the new position + */ +static int +gtk_pango_move_chars (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_cursor_position); + + count++; + } + + return offset; +} + +/* + * gtk_pango_move_words: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of words to move from @offset + * + * Returns the position that is @count words from the + * given @offset. @count may be positive or negative. + * + * If @count is positive, the returned position will + * be a word end, otherwise it will be a word start. + * See the Pango documentation for details on how + * word starts and ends are defined. + * + * Returns: the new position + */ +static int +gtk_pango_move_words (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_word_end); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_word_start); + + count++; + } + + return offset; +} + +/* + * gtk_pango_move_sentences: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of sentences to move from @offset + * + * Returns the position that is @count sentences from the + * given @offset. @count may be positive or negative. + * + * If @count is positive, the returned position will + * be a sentence end, otherwise it will be a sentence start. + * See the Pango documentation for details on how + * sentence starts and ends are defined. + * + * Returns: the new position + */ +static int +gtk_pango_move_sentences (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_sentence_start); + + count++; + } + + return offset; +} + +#if 0 +/* + * gtk_pango_move_lines: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of lines to move from @offset + * + * Returns the position that is @count lines from the + * given @offset. @count may be positive or negative. + * + * If @count is negative, the returned position will + * be the start of a line, else it will be the end of + * line. + * + * Returns: the new position + */ +static int +gtk_pango_move_lines (PangoLayout *layout, + int offset, + int count) +{ + GSList *lines, *l; + PangoLayoutLine *line; + int num; + const char *text; + int pos, line_pos; + int index; + int len; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + lines = pango_layout_get_lines (layout); + line = NULL; + + num = 0; + for (l = lines; l; l = l->next) + { + line = l->data; + if (index < line->start_index + line->length) + break; + num++; + } + + if (count < 0) + { + num += count; + if (num < 0) + num = 0; + + line = g_slist_nth_data (lines, num); + + return g_utf8_pointer_to_offset (text, text + line->start_index); + } + else + { + line_pos = index - line->start_index; + + len = g_slist_length (lines); + num += count; + if (num >= len || (count == 0 && num == len - 1)) + return g_utf8_strlen (text, -1) - 1; + + line = l->data; + pos = line->start_index + line_pos; + if (pos >= line->start_index + line->length) + pos = line->start_index + line->length - 1; + + return g_utf8_pointer_to_offset (text, text + pos); + } +} +#endif + +/* + * gtk_pango_is_inside_word: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * + * Returns whether the given position is inside + * a word. + * + * Returns: %TRUE if @offset is inside a word + */ +static gboolean +gtk_pango_is_inside_word (PangoLayout *layout, + int offset) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (offset >= 0 && + !(attrs[offset].is_word_start || attrs[offset].is_word_end)) + offset--; + + if (offset >= 0) + return attrs[offset].is_word_start; + + return FALSE; +} + +/* + * gtk_pango_is_inside_sentence: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * + * Returns whether the given position is inside + * a sentence. + * + * Returns: %TRUE if @offset is inside a sentence + */ +static gboolean +gtk_pango_is_inside_sentence (PangoLayout *layout, + int offset) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (offset >= 0 && + !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end)) + offset--; + + if (offset >= 0) + return attrs[offset].is_sentence_start; + + return FALSE; +} + +static void +pango_layout_get_line_before (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + if (prev_line) + { + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + end_index = start_index; + start_index = prev_line->start_index; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + if (prev_prev_line) + start_index = prev_prev_line->start_index + prev_prev_line->length; + else + start_index = 0; + end_index = prev_line->start_index + prev_line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + } + else + start_index = end_index = 0; + + found = TRUE; + break; + } + + prev_prev_line = prev_line; + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +static void +pango_layout_get_line_at (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + if (pango_layout_iter_next_line (iter)) + end_index = pango_layout_iter_get_line (iter)->start_index; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + if (prev_line) + start_index = prev_line->start_index + prev_line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + + found = TRUE; + break; + } + + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +static void +pango_layout_get_line_after (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + if (pango_layout_iter_next_line (iter)) + { + line = pango_layout_iter_get_line (iter); + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + start_index = line->start_index; + if (pango_layout_iter_next_line (iter)) + end_index = pango_layout_iter_get_line (iter)->start_index; + else + end_index = start_index + line->length; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + start_index = end_index; + end_index = line->start_index + line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + } + else + start_index = end_index; + + found = TRUE; + break; + } + + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +/* + * gtk_pango_get_text_before: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout before @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_before_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_before (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + end = start; + start = gtk_pango_move_words (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_pango_is_inside_word (layout, start) && + !attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = start; + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + end = start; + start = gtk_pango_move_sentences (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_pango_is_inside_sentence (layout, start) && + !attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = start; + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_before (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +/* + * gtk_pango_get_text_after: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout after @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_after_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_after (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + start = gtk_pango_move_chars (layout, start, 1); + end = start; + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + start = end; + if (end < n_attrs - 1) + { + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + } + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + end = gtk_pango_move_words (layout, end, 1); + start = end; + if (end < n_attrs - 1) + end = gtk_pango_move_words (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + start = end; + if (end < n_attrs - 1) + { + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + } + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + end = gtk_pango_move_sentences (layout, end, 1); + start = end; + if (end < n_attrs - 1) + end = gtk_pango_move_sentences (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_after (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +/* + * gtk_pango_get_text_at: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout at @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_after_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_at (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_pango_is_inside_word (layout, start) && + !attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = gtk_pango_move_words (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_pango_is_inside_sentence (layout, start) && + !attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = gtk_pango_move_sentences (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_at (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +char *gtk_pango_get_string_at (PangoLayout *layout, + int offset, + AtspiTextGranularity granularity, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (granularity) + { + case ATSPI_TEXT_GRANULARITY_CHAR: + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_WORD: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_SENTENCE: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_LINE: + pango_layout_get_line_at (layout, offset, ATSPI_TEXT_BOUNDARY_LINE_START, &start, &end); + break; + + case ATSPI_TEXT_GRANULARITY_PARAGRAPH: + /* FIXME: In theory, a layout can hold more than one paragraph */ + start = 0; + end = g_utf8_strlen (text, -1); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} |