static gboolean ev_page_accessible_set_selection (AtkText *text, gint selection_num, gint start_pos, gint end_pos) { EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text); EvView *view = ev_page_accessible_get_view (self); EvRectangle *areas = NULL; guint n_areas = 0; GdkRectangle start_rect, end_rect; GdkPoint start_point, end_point; ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas); if (start_pos < 0 || end_pos >= n_areas) return FALSE; _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, areas + start_pos, &start_rect); _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, areas + end_pos - 1, &end_rect); start_point.x = start_rect.x; start_point.y = start_rect.y; end_point.x = end_rect.x + end_rect.width; end_point.y = end_rect.y + end_rect.height; _ev_view_set_selection (view, &start_point, &end_point); return TRUE; }
static gint ev_page_accessible_get_offset_at_point (AtkText *text, gint x, gint y, AtkCoordType coords) { EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text); EvView *view = ev_page_accessible_get_view (self); GtkWidget *toplevel; EvRectangle *areas = NULL; EvRectangle *rect = NULL; guint n_areas = 0; guint i; gint x_widget, y_widget; gint offset=-1; GdkPoint view_point; gdouble doc_x, doc_y; GtkBorder border; GdkRectangle page_area; if (!view->page_cache) return -1; ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas); if (!areas) return -1; view_point.x = x; view_point.y = y; toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); gtk_widget_translate_coordinates (GTK_WIDGET (self), toplevel, 0, 0, &x_widget, &y_widget); view_point.x -= x_widget; view_point.y -= y_widget; if (coords == ATK_XY_SCREEN) { gint x_window, y_window; gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window); view_point.x -= x_window; view_point.y -= y_window; } ev_view_get_page_extents (view, self->priv->page, &page_area, &border); _ev_view_transform_view_point_to_doc_point (view, &view_point, &page_area, &border, &doc_x, &doc_y); for (i = 0; i < n_areas; i++) { rect = areas + i; if (doc_x >= rect->x1 && doc_x <= rect->x2 && doc_y >= rect->y1 && doc_y <= rect->y2) offset = i; } return offset; }
static void ev_page_accessible_get_character_extents (AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text); EvView *view = ev_page_accessible_get_view (self); GtkWidget *toplevel; EvRectangle *areas = NULL; EvRectangle *doc_rect; guint n_areas = 0; gint x_widget, y_widget; GdkRectangle view_rect; if (!view->page_cache) return; ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas); if (!areas || offset >= n_areas) return; doc_rect = areas + offset; _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, doc_rect, &view_rect); view_rect.x -= view->scroll_x; view_rect.y -= view->scroll_y; toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view)); gtk_widget_translate_coordinates (GTK_WIDGET (view), toplevel, 0, 0, &x_widget, &y_widget); view_rect.x += x_widget; view_rect.y += y_widget; if (coords == ATK_XY_SCREEN) { gint x_window, y_window; gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window); view_rect.x += x_window; view_rect.y += y_window; } *x = view_rect.x; *y = view_rect.y; *width = view_rect.width; *height = view_rect.height; }
/* ATs expect to be able to identify sentence boundaries based on content. Valid, * content-based boundaries may be present at the end of a newline, for instance * at the end of a heading within a document. Thus being able to distinguish hard * returns from soft returns is necessary. However, the text we get from Poppler * for non-tagged PDFs has "\n" inserted at the end of each line resulting in a * broken accessibility implementation w.r.t. sentences. */ static gboolean treat_as_soft_return (EvView *view, gint page, PangoLogAttr *log_attrs, gint offset) { EvRectangle *areas = NULL; guint n_areas = 0; gdouble line_spacing, this_line_height, next_word_width; EvRectangle *this_line_start; EvRectangle *this_line_end; EvRectangle *next_line_start; EvRectangle *next_line_end; EvRectangle *next_word_end; gint prev_offset, next_offset; if (!log_attrs[offset].is_white) return FALSE; ev_page_cache_get_text_layout (view->page_cache, page, &areas, &n_areas); if (n_areas <= offset + 1) return FALSE; prev_offset = offset - 1; next_offset = offset + 1; /* In wrapped text, the character at the start of the next line starts a word. * Examples where this condition might fail include bullets and images. But it * also includes things like "(", so also check the next character. */ if (!log_attrs[next_offset].is_word_start && (next_offset + 1 >= n_areas || !log_attrs[next_offset + 1].is_word_start)) return FALSE; /* In wrapped text, the chars on either side of the newline have very similar heights. * Examples where this condition might fail include a newline at the end of a heading, * and a newline at the end of a paragraph that is followed by a heading. */ this_line_end = areas + prev_offset; next_line_start = areas + next_offset;; this_line_height = this_line_end->y2 - this_line_end->y1; if (ABS (this_line_height - (next_line_start->y2 - next_line_start->y1)) > 0.25) return FALSE; /* If there is significant white space between this line and the next, odds are this * is not a soft return in wrapped text. Lines within a typical paragraph are at most * double-spaced. If the spacing is more than that, assume a hard return is present. */ line_spacing = next_line_start->y1 - this_line_end->y2; if (line_spacing - this_line_height > 1) return FALSE; /* Lines within a typical paragraph have *reasonably* similar x1 coordinates. But * we cannot count on them being nearly identical. Examples where indentation can * be present in wrapped text include indenting the first line of the paragraph, * and hanging indents (e.g. in the works cited within an academic paper). So we'll * be somewhat tolerant here. */ for ( ; prev_offset > 0 && !log_attrs[prev_offset].is_mandatory_break; prev_offset--); this_line_start = areas + prev_offset; if (ABS (this_line_start->x1 - next_line_start->x1) > 20) return FALSE; /* Ditto for x2, but this line might be short due to a wide word on the next line. */ for ( ; next_offset < n_areas && !log_attrs[next_offset].is_word_end; next_offset++); next_word_end = areas + next_offset; next_word_width = next_word_end->x2 - next_line_start->x1; for ( ; next_offset < n_areas && !log_attrs[next_offset + 1].is_mandatory_break; next_offset++); next_line_end = areas + next_offset; if (next_line_end->x2 - (this_line_end->x2 + next_word_width) > 20) return FALSE; return TRUE; }
static gint ev_view_accessible_get_offset_at_point (AtkText *text, gint x, gint y, AtkCoordType coords) { GtkWidget *widget, *toplevel; EvRectangle *areas = NULL; EvRectangle *rect = NULL; guint n_areas = 0; guint i = 0; EvPageCache *page_cache; gint x_window, y_window, x_widget, y_widget; gint offset=-1, rx, ry; gdouble scale; GtkBorder border; GdkRectangle page_area; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) /* State is defunct */ return -1; page_cache = EV_VIEW (widget)->page_cache; if (!page_cache) return -1; ev_view_get_page_extents (EV_VIEW (widget), EV_VIEW (widget)->current_page, &page_area, &border); scale = EV_VIEW (widget)->scale; ev_page_cache_get_text_layout (page_cache, EV_VIEW (widget)->current_page, &areas, &n_areas); if (!areas) return -1; rx = x; ry = y; rx -= page_area.x; ry -= page_area.y; rx += EV_VIEW (widget)->scroll_x; ry += EV_VIEW (widget)->scroll_y; toplevel = gtk_widget_get_toplevel (widget); gtk_widget_translate_coordinates (widget, toplevel, 0, 0, &x_widget, &y_widget); rx -= x_widget; ry -= y_widget; if (coords == ATK_XY_SCREEN) { gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window); rx -= x_window; ry -= y_window; } rx /= scale; ry /= scale; for (i = 0; i < n_areas; i++) { rect = areas + i; if (rx >= rect->x1 && rx <= rect->x2 && ry >= rect->y1 && ry <= rect->y2) offset = i; } return offset; }
static void ev_view_accessible_get_character_extents (AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { GtkWidget *widget, *toplevel; EvRectangle *areas = NULL; EvRectangle *rect = NULL; guint n_areas = 0; EvPageCache *page_cache; gint x_widget, y_widget, x_window, y_window; gdouble scale; GtkBorder border; GdkRectangle page_area; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) /* State is defunct */ return; page_cache = EV_VIEW (widget)->page_cache; if (!page_cache) return; ev_view_get_page_extents (EV_VIEW (widget), EV_VIEW (widget)->current_page, &page_area, &border); scale = EV_VIEW (widget)->scale; ev_page_cache_get_text_layout (page_cache, EV_VIEW (widget)->current_page, &areas, &n_areas); if (!areas) return; if (offset >= n_areas) return; rect = areas + offset; *x = (int)(rect->x1 * scale); *y = (int)(rect->y1 * scale); *width = (int)(fabs (rect->x2 - rect->x1) * scale); *height = (int)(fabs (rect->y2 - rect->y1) * scale); toplevel = gtk_widget_get_toplevel (widget); gtk_widget_translate_coordinates (widget, toplevel, 0, 0, &x_widget, &y_widget); *x += x_widget; *y += y_widget; if (coords == ATK_XY_SCREEN) { gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window); *x += x_window; *y += y_window; } *x -= EV_VIEW (widget)->scroll_x; *y -= EV_VIEW (widget)->scroll_y; *x += page_area.x; *y += page_area.y; }