Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ev-page-accessible.c
Go to the documentation of this file.
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /* this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2014 Igalia S.L.
5  *
6  * Evince is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Evince is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * Author: Alejandro PiƱeiro Iglesias <apinheiro@igalia.com>
21  */
22 
23 #include <config.h>
24 
25 #include <glib/gi18n-lib.h>
26 #include "ev-page-accessible.h"
28 #include "ev-image-accessible.h"
29 #include "ev-link-accessible.h"
30 #include "ev-view-private.h"
31 
34  gint page;
35  GHashTable *links;
36  GPtrArray *children;
38 };
39 
40 
41 enum {
45 };
46 
47 static void ev_page_accessible_component_iface_init (AtkComponentIface *iface);
48 static void ev_page_accessible_hypertext_iface_init (AtkHypertextIface *iface);
49 static void ev_page_accessible_text_iface_init (AtkTextIface *iface);
50 
51 G_DEFINE_TYPE_WITH_CODE (EvPageAccessible, ev_page_accessible, ATK_TYPE_OBJECT,
52  G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, ev_page_accessible_component_iface_init)
53  G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERTEXT, ev_page_accessible_hypertext_iface_init)
54  G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, ev_page_accessible_text_iface_init))
55 
56 gint
58 {
59  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (page_accessible), -1);
60 
61  return page_accessible->priv->page;
62 }
63 
66 {
67  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (page_accessible), NULL);
68 
69  return page_accessible->priv->view_accessible;
70 }
71 
72 static AtkObject *
74 {
75  EvPageAccessible *self;
76 
77  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (obj), NULL);
78 
79  self = EV_PAGE_ACCESSIBLE (obj);
80 
81  return ATK_OBJECT (self->priv->view_accessible);
82 }
83 
84 static gint
86 {
87  gdouble dx, dy;
88 
89  /* Very rough heuristic for simple, non-tagged PDFs. */
90 
91  dy = a->area.y1 - b->area.y1;
92  dx = a->area.x1 - b->area.x1;
93 
94  return ABS (dy) > 10 ? dy : dx;
95 }
96 
97 static void
99 {
100  EvView *view;
101  EvMappingList *images;
102  EvMappingList *links;
103  EvMappingList *fields;
104  GList *children = NULL;
105  GList *list;
106 
107  if (self->priv->children_initialized)
108  return;
109 
110  view = ev_page_accessible_get_view (self);
111  if (!ev_page_cache_is_page_cached (view->page_cache, self->priv->page))
112  return;
113 
114  self->priv->children_initialized = TRUE;
115 
116  links = ev_page_cache_get_link_mapping (view->page_cache, self->priv->page);
117  images = ev_page_cache_get_image_mapping (view->page_cache, self->priv->page);
118  fields = ev_page_cache_get_form_field_mapping (view->page_cache, self->priv->page);
119  if (!links && !images && !fields)
120  return;
121 
122  children = g_list_copy (ev_mapping_list_get_list (links));
123  children = g_list_concat (children, g_list_copy (ev_mapping_list_get_list (images)));
124  children = g_list_concat (children, g_list_copy (ev_mapping_list_get_list (fields)));
125 
126  children = g_list_sort (children, (GCompareFunc) compare_mappings);
127  self->priv->children = g_ptr_array_new_full (g_list_length (children), (GDestroyNotify) g_object_unref);
128 
129  for (list = children; list && list->data; list = list->next) {
130  EvMapping *mapping = list->data;
131  AtkObject *child = NULL;
132 
133  if (links && ev_mapping_list_find (links, mapping->data)) {
134  EvLinkAccessible *link = ev_link_accessible_new (self, EV_LINK (mapping->data), &mapping->area);
135  AtkHyperlink *atk_link = atk_hyperlink_impl_get_hyperlink (ATK_HYPERLINK_IMPL (link));
136 
137  child = atk_hyperlink_get_object (atk_link, 0);
138  } else if (images && ev_mapping_list_find (images, mapping->data))
139  child = ATK_OBJECT (ev_image_accessible_new (self, EV_IMAGE (mapping->data), &mapping->area));
140  else if (fields && ev_mapping_list_find (fields, mapping->data))
141  child = ATK_OBJECT (ev_form_field_accessible_new (self, EV_FORM_FIELD (mapping->data), &mapping->area));
142 
143  if (child)
144  g_ptr_array_add (self->priv->children, child);
145  }
146 
147  g_list_free (children);
148 }
149 
150 static void
152 {
153  gint i;
154  AtkObject *child;
155 
156  if (!self->priv->children)
157  return;
158 
159  for (i = 0; i < self->priv->children->len; i++) {
160  child = g_ptr_array_index (self->priv->children, i);
161  atk_object_notify_state_change (child, ATK_STATE_DEFUNCT, TRUE);
162  }
163 
164  g_clear_pointer (&self->priv->children, g_ptr_array_unref);
165 }
166 
167 static void
169 {
170  EvPageAccessiblePrivate *priv = EV_PAGE_ACCESSIBLE (object)->priv;
171 
172  g_clear_pointer (&priv->links, (GDestroyNotify)g_hash_table_destroy);
174 
175  G_OBJECT_CLASS (ev_page_accessible_parent_class)->finalize (object);
176 }
177 
178 static void
180  guint prop_id,
181  const GValue *value,
182  GParamSpec *pspec)
183 {
184  EvPageAccessible *accessible = EV_PAGE_ACCESSIBLE (object);
185 
186  switch (prop_id) {
188  accessible->priv->view_accessible = EV_VIEW_ACCESSIBLE (g_value_get_object (value));
189  break;
190  case PROP_PAGE:
191  accessible->priv->page = g_value_get_int (value);
192  break;
193  default:
194  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
195  }
196 }
197 
198 static void
200  guint prop_id,
201  GValue *value,
202  GParamSpec *pspec)
203 {
204  EvPageAccessible *accessible = EV_PAGE_ACCESSIBLE (object);
205 
206  switch (prop_id) {
208  g_value_set_object (value, ev_page_accessible_get_view_accessible (accessible));
209  break;
210  case PROP_PAGE:
211  g_value_set_int (value, ev_page_accessible_get_page (accessible));
212  break;
213  default:
214  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
215  }
216 }
217 
218 /*
219  * We redefine atk_class->ref_relation_set instead of just calling
220  * atk_object_add_relationship on ev_page_accessible_new because at
221  * that moment not all the pages could be created, being easier add
222  * the relation on demand.
223  */
224 static AtkRelationSet *
225 ev_page_accessible_ref_relation_set (AtkObject *accessible)
226 {
227  gint n_pages;
228  EvPageAccessible *self;
229  AtkRelationSet *relation_set;
230  AtkObject *accessible_array[1];
231  AtkRelation *relation;
232 
233  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (accessible), NULL);
234  self = EV_PAGE_ACCESSIBLE (accessible);
235 
236  relation_set = ATK_OBJECT_CLASS (ev_page_accessible_parent_class)->ref_relation_set (accessible);
237  if (relation_set == NULL)
238  return NULL;
239 
240  n_pages = ev_view_accessible_get_n_pages (self->priv->view_accessible);
241  if (n_pages == 0)
242  return relation_set;
243 
244  if ((self->priv->page + 1) < n_pages && !atk_relation_set_contains (relation_set, ATK_RELATION_FLOWS_TO)) {
245  AtkObject *next_page;
246 
247  next_page = atk_object_ref_accessible_child (ATK_OBJECT (self->priv->view_accessible),
248  self->priv->page + 1);
249  accessible_array [0] = next_page;
250  relation = atk_relation_new (accessible_array, 1, ATK_RELATION_FLOWS_TO);
251  atk_relation_set_add (relation_set, relation);
252 
253  g_object_unref (relation);
254  g_object_unref (next_page);
255  }
256 
257  if (self->priv->page > 0 && !atk_relation_set_contains (relation_set, ATK_RELATION_FLOWS_FROM)) {
258  AtkObject *prev_page;
259 
260  prev_page = atk_object_ref_accessible_child (ATK_OBJECT (self->priv->view_accessible),
261  self->priv->page - 1);
262  accessible_array [0] = prev_page;
263  relation = atk_relation_new (accessible_array, 1, ATK_RELATION_FLOWS_FROM);
264  atk_relation_set_add (relation_set, relation);
265 
266  g_object_unref (relation);
267  g_object_unref (prev_page);
268  }
269 
270  return relation_set;
271 }
272 
273 /* page accessible's state set is a copy ev-view accessible's state
274  * set but removing ATK_STATE_SHOWING if the page is not on screen and
275  * ATK_STATE_FOCUSED if it is not the relevant page. */
276 static AtkStateSet *
277 ev_page_accessible_ref_state_set (AtkObject *accessible)
278 {
279  AtkStateSet *state_set;
280  AtkStateSet *copy_set;
281  AtkStateSet *view_accessible_state_set;
282  EvPageAccessible *self;
283  EvView *view;
284  gint relevant_page;
285 
286  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (accessible), NULL);
287  self = EV_PAGE_ACCESSIBLE (accessible);
288  view = ev_page_accessible_get_view (self);
289 
290  state_set = ATK_OBJECT_CLASS (ev_page_accessible_parent_class)->ref_state_set (accessible);
291  atk_state_set_clear_states (state_set);
292 
293  view_accessible_state_set = atk_object_ref_state_set (ATK_OBJECT (self->priv->view_accessible));
294  copy_set = atk_state_set_or_sets (state_set, view_accessible_state_set);
295 
296  if (self->priv->page >= view->start_page && self->priv->page <= view->end_page)
297  atk_state_set_add_state (copy_set, ATK_STATE_SHOWING);
298  else
299  atk_state_set_remove_state (copy_set, ATK_STATE_SHOWING);
300 
301  relevant_page = ev_view_accessible_get_relevant_page (self->priv->view_accessible);
302  if (atk_state_set_contains_state (view_accessible_state_set, ATK_STATE_FOCUSED) &&
303  self->priv->page == relevant_page)
304  atk_state_set_add_state (copy_set, ATK_STATE_FOCUSED);
305  else
306  atk_state_set_remove_state (copy_set, ATK_STATE_FOCUSED);
307 
308  relevant_page = ev_view_accessible_get_relevant_page (self->priv->view_accessible);
309  if (atk_state_set_contains_state (view_accessible_state_set, ATK_STATE_FOCUSED) &&
310  self->priv->page == relevant_page)
311  atk_state_set_add_state (copy_set, ATK_STATE_FOCUSED);
312  else
313  atk_state_set_remove_state (copy_set, ATK_STATE_FOCUSED);
314 
315  g_object_unref (state_set);
316  g_object_unref (view_accessible_state_set);
317 
318  return copy_set;
319 }
320 
321 static gint
322 ev_page_accessible_get_n_children (AtkObject *accessible)
323 {
324  EvPageAccessible *self;
325 
326  self = EV_PAGE_ACCESSIBLE (accessible);
327 
328  return self->priv->children == NULL ? 0 : self->priv->children->len;
329 }
330 
331 static AtkObject *
332 ev_page_accessible_ref_child (AtkObject *accessible,
333  gint i)
334 {
335  EvPageAccessible *self;
336 
337  self = EV_PAGE_ACCESSIBLE (accessible);
338 
339  g_return_val_if_fail (i >= 0 || i < self->priv->children->len, NULL);
340 
341  return g_object_ref (g_ptr_array_index (self->priv->children, i));
342 }
343 
344 static void
346 {
347  GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
348  AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
349 
350  g_type_class_add_private (klass, sizeof (EvPageAccessiblePrivate));
351 
352  atk_class->get_parent = ev_page_accessible_get_parent;
353  atk_class->ref_relation_set = ev_page_accessible_ref_relation_set;
354  atk_class->ref_state_set = ev_page_accessible_ref_state_set;
355  atk_class->get_n_children = ev_page_accessible_get_n_children;
356  atk_class->ref_child = ev_page_accessible_ref_child;
357 
358  g_object_class->get_property = ev_page_accessible_get_property;
359  g_object_class->set_property = ev_page_accessible_set_property;
360  g_object_class->finalize = ev_page_accessible_finalize;
361 
362  g_object_class_install_property (g_object_class,
364  g_param_spec_object ("view-accessible",
365  "View Accessible",
366  "The view accessible associated to this page",
368  G_PARAM_READWRITE |
369  G_PARAM_CONSTRUCT_ONLY |
370  G_PARAM_STATIC_STRINGS));
371  g_object_class_install_property (g_object_class,
372  PROP_PAGE,
373  g_param_spec_int ("page",
374  "Page",
375  "Page index this page represents",
376  -1, G_MAXINT, -1,
377  G_PARAM_READWRITE |
378  G_PARAM_CONSTRUCT_ONLY |
379  G_PARAM_STATIC_STRINGS));
380 
381 }
382 
383 EvView *
385 {
386  g_return_val_if_fail (EV_IS_PAGE_ACCESSIBLE (page_accessible), NULL);
387 
388  return EV_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (page_accessible->priv->view_accessible)));
389 }
390 
391 /* ATs expect to be able to identify sentence boundaries based on content. Valid,
392  * content-based boundaries may be present at the end of a newline, for instance
393  * at the end of a heading within a document. Thus being able to distinguish hard
394  * returns from soft returns is necessary. However, the text we get from Poppler
395  * for non-tagged PDFs has "\n" inserted at the end of each line resulting in a
396  * broken accessibility implementation w.r.t. sentences.
397  */
398 static gboolean
400  gint page,
401  PangoLogAttr *log_attrs,
402  gint offset)
403 {
404  EvRectangle *areas = NULL;
405  guint n_areas = 0;
406  gdouble line_spacing, this_line_height, next_word_width;
407  EvRectangle *this_line_start;
408  EvRectangle *this_line_end;
409  EvRectangle *next_line_start;
410  EvRectangle *next_line_end;
411  EvRectangle *next_word_end;
412  gint prev_offset, next_offset;
413 
414 
415  if (!log_attrs[offset].is_white)
416  return FALSE;
417 
418  ev_page_cache_get_text_layout (view->page_cache, page, &areas, &n_areas);
419  if (n_areas <= offset + 1)
420  return FALSE;
421 
422  prev_offset = offset - 1;
423  next_offset = offset + 1;
424 
425  /* In wrapped text, the character at the start of the next line starts a word.
426  * Examples where this condition might fail include bullets and images. But it
427  * also includes things like "(", so also check the next character.
428  */
429  if (!log_attrs[next_offset].is_word_start &&
430  (next_offset + 1 >= n_areas || !log_attrs[next_offset + 1].is_word_start))
431  return FALSE;
432 
433  /* In wrapped text, the chars on either side of the newline have very similar heights.
434  * Examples where this condition might fail include a newline at the end of a heading,
435  * and a newline at the end of a paragraph that is followed by a heading.
436  */
437  this_line_end = areas + prev_offset;
438  next_line_start = areas + next_offset;;
439 
440  this_line_height = this_line_end->y2 - this_line_end->y1;
441  if (ABS (this_line_height - (next_line_start->y2 - next_line_start->y1)) > 0.25)
442  return FALSE;
443 
444  /* If there is significant white space between this line and the next, odds are this
445  * is not a soft return in wrapped text. Lines within a typical paragraph are at most
446  * double-spaced. If the spacing is more than that, assume a hard return is present.
447  */
448  line_spacing = next_line_start->y1 - this_line_end->y2;
449  if (line_spacing - this_line_height > 1)
450  return FALSE;
451 
452  /* Lines within a typical paragraph have *reasonably* similar x1 coordinates. But
453  * we cannot count on them being nearly identical. Examples where indentation can
454  * be present in wrapped text include indenting the first line of the paragraph,
455  * and hanging indents (e.g. in the works cited within an academic paper). So we'll
456  * be somewhat tolerant here.
457  */
458  for ( ; prev_offset > 0 && !log_attrs[prev_offset].is_mandatory_break; prev_offset--);
459  this_line_start = areas + prev_offset;
460  if (ABS (this_line_start->x1 - next_line_start->x1) > 20)
461  return FALSE;
462 
463  /* Ditto for x2, but this line might be short due to a wide word on the next line. */
464  for ( ; next_offset < n_areas && !log_attrs[next_offset].is_word_end; next_offset++);
465  next_word_end = areas + next_offset;
466  next_word_width = next_word_end->x2 - next_line_start->x1;
467 
468  for ( ; next_offset < n_areas && !log_attrs[next_offset + 1].is_mandatory_break; next_offset++);
469  next_line_end = areas + next_offset;
470  if (next_line_end->x2 - (this_line_end->x2 + next_word_width) > 20)
471  return FALSE;
472 
473  return TRUE;
474 }
475 
476 static gchar *
478  gint start_offset,
479  gint end_offset)
480 {
481  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
482  EvView *view = ev_page_accessible_get_view (self);
483  gchar *substring, *normalized;
484  const gchar* page_text;
485 
486  if (!view->page_cache)
487  return NULL;
488 
489  page_text = ev_page_cache_get_text (view->page_cache, self->priv->page);
490  if (end_offset < 0 || end_offset > g_utf8_strlen (page_text, -1))
491  end_offset = strlen (page_text);
492  start_offset = CLAMP (start_offset, 0, end_offset);
493 
494  substring = g_utf8_substring (page_text, start_offset, end_offset);
495  normalized = g_utf8_normalize (substring, -1, G_NORMALIZE_NFKC);
496  g_free (substring);
497 
498  return normalized;
499 }
500 
501 static gchar *
503  gint start_pos,
504  gint end_pos)
505 {
506  return ev_page_accessible_get_substring (text, start_pos, end_pos);
507 }
508 
509 static gunichar
511  gint offset)
512 {
513  gchar *string;
514  gunichar unichar;
515 
516  string = ev_page_accessible_get_substring (text, offset, offset + 1);
517  unichar = g_utf8_get_char (string);
518  g_free(string);
519 
520  return unichar;
521 }
522 
523 static void
525  AtkTextBoundary boundary_type,
526  gint offset,
527  gint *start_offset,
528  gint *end_offset)
529 {
530  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
531  EvView *view = ev_page_accessible_get_view (self);
532  gint start = 0;
533  gint end = 0;
534  PangoLogAttr *log_attrs = NULL;
535  gulong n_attrs;
536 
537  if (!view->page_cache)
538  return;
539 
540  ev_page_cache_get_text_log_attrs (view->page_cache, self->priv->page, &log_attrs, &n_attrs);
541  if (!log_attrs)
542  return;
543 
544  if (offset < 0 || offset >= n_attrs)
545  return;
546 
547  switch (boundary_type) {
548  case ATK_TEXT_BOUNDARY_CHAR:
549  start = offset;
550  end = offset + 1;
551  break;
552  case ATK_TEXT_BOUNDARY_WORD_START:
553  for (start = offset; start > 0 && !log_attrs[start].is_word_start; start--);
554  for (end = offset + 1; end < n_attrs && !log_attrs[end].is_word_start; end++);
555  break;
556  case ATK_TEXT_BOUNDARY_SENTENCE_START:
557  for (start = offset; start > 0; start--) {
558  if (log_attrs[start].is_mandatory_break && treat_as_soft_return (view, self->priv->page, log_attrs, start - 1))
559  continue;
560  if (log_attrs[start].is_sentence_start)
561  break;
562  }
563  for (end = offset + 1; end < n_attrs; end++) {
564  if (log_attrs[end].is_mandatory_break && treat_as_soft_return (view, self->priv->page, log_attrs, end - 1))
565  continue;
566  if (log_attrs[end].is_sentence_start)
567  break;
568  }
569  break;
570  case ATK_TEXT_BOUNDARY_LINE_START:
571  for (start = offset; start > 0 && !log_attrs[start].is_mandatory_break; start--);
572  for (end = offset + 1; end < n_attrs && !log_attrs[end].is_mandatory_break; end++);
573  break;
574  default:
575  /* The "END" boundary types are deprecated */
576  break;
577  }
578 
579  *start_offset = start;
580  *end_offset = end;
581 }
582 
583 static gchar *
585  gint offset,
586  AtkTextBoundary boundary_type,
587  gint *start_offset,
588  gint *end_offset)
589 {
590  gchar *retval;
591 
592  ev_page_accessible_get_range_for_boundary (text, boundary_type, offset, start_offset, end_offset);
593  retval = ev_page_accessible_get_substring (text, *start_offset, *end_offset);
594 
595  /* If newlines appear inside the text of a sentence (i.e. between the start and
596  * end offsets returned by ev_page_accessible_get_substring), it interferes with
597  * the prosody of text-to-speech based-solutions such as a screen reader because
598  * speech synthesizers tend to pause after the newline char as if it were the end
599  * of the sentence.
600  */
601  if (boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_START)
602  g_strdelimit (retval, "\n", ' ');
603 
604  return retval;
605 }
606 
607 static gint
609 {
610  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
611  EvView *view = ev_page_accessible_get_view (self);
612 
613  if (self->priv->page == view->cursor_page && view->caret_enabled)
614  return view->cursor_offset;
615 
616  return -1;
617 }
618 
619 static gboolean
620 ev_page_accessible_set_caret_offset (AtkText *text, gint offset)
621 {
622  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
623  EvView *view = ev_page_accessible_get_view (self);
624 
626  self->priv->page,
627  offset);
628 
629  return TRUE;
630 }
631 
632 static gint
634 {
635  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
636  EvView *view = ev_page_accessible_get_view (self);
637  gint retval;
638 
639  retval = g_utf8_strlen (ev_page_cache_get_text (view->page_cache, self->priv->page), -1);
640 
641  return retval;
642 }
643 
644 static gboolean
646  EvViewSelection *selection,
647  gint *start_offset,
648  gint *end_offset)
649 {
650  cairo_rectangle_int_t rect;
651  gint start, end;
652 
653  if (!selection->covered_region || cairo_region_is_empty (selection->covered_region))
654  return FALSE;
655 
656  cairo_region_get_rectangle (selection->covered_region, 0, &rect);
658  selection->page,
659  rect.x / view->scale,
660  (rect.y + (rect.height / 2)) / view->scale);
661  if (start == -1)
662  return FALSE;
663 
664  cairo_region_get_rectangle (selection->covered_region,
665  cairo_region_num_rectangles (selection->covered_region) - 1,
666  &rect);
668  selection->page,
669  (rect.x + rect.width) / view->scale,
670  (rect.y + (rect.height / 2)) / view->scale);
671  if (end == -1)
672  return FALSE;
673 
674  *start_offset = start;
675  *end_offset = end;
676 
677  return TRUE;
678 }
679 
680 static gint
682 {
683  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
684  EvView *view = ev_page_accessible_get_view (self);
685  gint n_selections = 0;
686  GList *l;
687 
688  if (!EV_IS_SELECTION (view->document) || !view->selection_info.selections)
689  return 0;
690 
691  for (l = view->selection_info.selections; l != NULL; l = l->next) {
692  EvViewSelection *selection = (EvViewSelection *)l->data;
693 
694  if (selection->page != self->priv->page)
695  continue;
696 
697  n_selections = 1;
698  break;
699  }
700 
701  return n_selections;
702 }
703 
704 static gchar *
706  gint selection_num,
707  gint *start_pos,
708  gint *end_pos)
709 {
710  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
711  EvView *view = ev_page_accessible_get_view (self);
712  gchar *selected_text = NULL;
713  gchar *normalized_text = NULL;
714  GList *l;
715 
716  *start_pos = -1;
717  *end_pos = -1;
718 
719  if (selection_num != 0)
720  return NULL;
721 
722  if (!EV_IS_SELECTION (view->document) || !view->selection_info.selections)
723  return NULL;
724 
725  for (l = view->selection_info.selections; l != NULL; l = l->next) {
726  EvViewSelection *selection = (EvViewSelection *)l->data;
727  gint start, end;
728 
729  if (selection->page != self->priv->page)
730  continue;
731 
732  if (get_selection_bounds (view, selection, &start, &end) && start != end) {
733  EvPage *page;
734 
735  page = ev_document_get_page (view->document, selection->page);
736 
738  selected_text = ev_selection_get_selected_text (EV_SELECTION (view->document),
739  page,
740  selection->style,
741  &(selection->rect));
742 
744 
745  g_object_unref (page);
746 
747  *start_pos = start;
748  *end_pos = end;
749  }
750 
751  break;
752  }
753 
754  if (selected_text) {
755  normalized_text = g_utf8_normalize (selected_text, -1, G_NORMALIZE_NFKC);
756  g_free (selected_text);
757  }
758 
759  return normalized_text;
760 }
761 
762 static AtkAttributeSet *
763 add_attribute (AtkAttributeSet *attr_set,
764  AtkTextAttribute attr_type,
765  gchar *attr_value)
766 {
767  AtkAttribute *attr = g_new (AtkAttribute, 1);
768 
769  attr->name = g_strdup (atk_text_attribute_get_name (attr_type));
770  attr->value = attr_value;
771 
772  return g_slist_prepend (attr_set, attr);
773 }
774 
775 static AtkAttributeSet *
776 get_run_attributes (PangoAttrList *attrs,
777  const gchar *text,
778  gint offset,
779  gint *start_offset,
780  gint *end_offset)
781 {
782  AtkAttributeSet *atk_attr_set = NULL;
783  PangoAttrString *pango_string;
784  PangoAttrInt *pango_int;
785  PangoAttrColor *pango_color;
786  PangoAttrIterator *iter;
787  gint i, start, end;
788  gboolean has_attrs = FALSE;
789  glong text_length;
790  gchar *attr_value;
791 
792  text_length = g_utf8_strlen (text, -1);
793  if (offset < 0 || offset >= text_length)
794  return NULL;
795 
796  /* Check if there are attributes for the offset,
797  * and set the attributes range if positive */
798  iter = pango_attr_list_get_iterator (attrs);
799  i = g_utf8_offset_to_pointer (text, offset) - text;
800 
801  do {
802  pango_attr_iterator_range (iter, &start, &end);
803  if (i >= start && i < end) {
804  *start_offset = g_utf8_pointer_to_offset (text, text + start);
805  if (end == G_MAXINT) /* Last iterator */
806  end = text_length;
807  *end_offset = g_utf8_pointer_to_offset (text, text + end);
808  has_attrs = TRUE;
809  }
810  } while (!has_attrs && pango_attr_iterator_next (iter));
811 
812  if (!has_attrs) {
813  pango_attr_iterator_destroy (iter);
814  return NULL;
815  }
816 
817  /* Create the AtkAttributeSet from the Pango attributes */
818  pango_string = (PangoAttrString *) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY);
819  if (pango_string) {
820  attr_value = g_strdup (pango_string->value);
821  atk_attr_set = add_attribute (atk_attr_set, ATK_TEXT_ATTR_FAMILY_NAME, attr_value);
822  }
823 
824  pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE);
825  if (pango_int) {
826  attr_value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE);
827  atk_attr_set = add_attribute (atk_attr_set, ATK_TEXT_ATTR_SIZE, attr_value);
828  }
829 
830  pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
831  if (pango_int) {
832  atk_attr_set = add_attribute (atk_attr_set,
833  ATK_TEXT_ATTR_UNDERLINE,
834  g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE,
835  pango_int->value)));
836  }
837 
838  pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
839  if (pango_color) {
840  attr_value = g_strdup_printf ("%u,%u,%u",
841  pango_color->color.red,
842  pango_color->color.green,
843  pango_color->color.blue);
844  atk_attr_set = add_attribute (atk_attr_set, ATK_TEXT_ATTR_FG_COLOR, attr_value);
845  }
846 
847  pango_attr_iterator_destroy (iter);
848 
849  return atk_attr_set;
850 }
851 
852 static AtkAttributeSet*
854  gint offset,
855  gint *start_offset,
856  gint *end_offset)
857 {
858  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
859  EvView *view = ev_page_accessible_get_view (self);
860  PangoAttrList *attrs;
861  const gchar *page_text;
862 
863  if (offset < 0)
864  return NULL;
865 
866  if (!view->page_cache)
867  return NULL;
868 
869  page_text = ev_page_cache_get_text (view->page_cache, self->priv->page);
870  if (!page_text)
871  return NULL;
872 
873  attrs = ev_page_cache_get_text_attrs (view->page_cache, self->priv->page);
874  if (!attrs)
875  return NULL;
876 
877  return get_run_attributes (attrs, page_text, offset, start_offset, end_offset);
878 }
879 
880 static AtkAttributeSet*
882 {
883  /* No default attributes */
884  return NULL;
885 }
886 
887 static void
889  gint offset,
890  gint *x,
891  gint *y,
892  gint *width,
893  gint *height,
894  AtkCoordType coords)
895 {
896  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
897  EvView *view = ev_page_accessible_get_view (self);
898  GtkWidget *toplevel;
899  EvRectangle *areas = NULL;
900  EvRectangle *doc_rect;
901  guint n_areas = 0;
902  gint x_widget, y_widget;
903  GdkRectangle view_rect;
904 
905  if (!view->page_cache)
906  return;
907 
908  ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas);
909  if (!areas || offset >= n_areas)
910  return;
911 
912  doc_rect = areas + offset;
913  _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, doc_rect, &view_rect);
914  view_rect.x -= view->scroll_x;
915  view_rect.y -= view->scroll_y;
916 
917  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
918  gtk_widget_translate_coordinates (GTK_WIDGET (view), toplevel, 0, 0, &x_widget, &y_widget);
919  view_rect.x += x_widget;
920  view_rect.y += y_widget;
921 
922  if (coords == ATK_XY_SCREEN) {
923  gint x_window, y_window;
924 
925  gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window);
926  view_rect.x += x_window;
927  view_rect.y += y_window;
928  }
929 
930  *x = view_rect.x;
931  *y = view_rect.y;
932  *width = view_rect.width;
933  *height = view_rect.height;
934 }
935 
936 static gint
938  gint x,
939  gint y,
940  AtkCoordType coords)
941 {
942  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
943  EvView *view = ev_page_accessible_get_view (self);
944  GtkWidget *toplevel;
945  EvRectangle *areas = NULL;
946  EvRectangle *rect = NULL;
947  guint n_areas = 0;
948  guint i;
949  gint x_widget, y_widget;
950  gint offset=-1;
951  GdkPoint view_point;
952  gdouble doc_x, doc_y;
953  GtkBorder border;
954  GdkRectangle page_area;
955 
956  if (!view->page_cache)
957  return -1;
958 
959  ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas);
960  if (!areas)
961  return -1;
962 
963  view_point.x = x;
964  view_point.y = y;
965  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
966  gtk_widget_translate_coordinates (GTK_WIDGET (self), toplevel, 0, 0, &x_widget, &y_widget);
967  view_point.x -= x_widget;
968  view_point.y -= y_widget;
969 
970  if (coords == ATK_XY_SCREEN) {
971  gint x_window, y_window;
972 
973  gdk_window_get_origin (gtk_widget_get_window (toplevel), &x_window, &y_window);
974  view_point.x -= x_window;
975  view_point.y -= y_window;
976  }
977 
978  ev_view_get_page_extents (view, self->priv->page, &page_area, &border);
979  _ev_view_transform_view_point_to_doc_point (view, &view_point, &page_area, &border, &doc_x, &doc_y);
980 
981  for (i = 0; i < n_areas; i++) {
982  rect = areas + i;
983  if (doc_x >= rect->x1 && doc_x <= rect->x2 &&
984  doc_y >= rect->y1 && doc_y <= rect->y2)
985  offset = i;
986  }
987 
988  return offset;
989 }
990 
991 /* ATK allows for multiple, non-contiguous selections within a single AtkText
992  * object. Unless and until Evince supports this, selection numbers are ignored.
993  */
994 static gboolean
996  gint selection_num)
997 {
998  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
999  EvView *view = ev_page_accessible_get_view (self);
1000 
1001  if (ev_view_get_has_selection (view)) {
1002  _ev_view_clear_selection (view);
1003  return TRUE;
1004  }
1005 
1006  return FALSE;
1007 }
1008 
1009 static gboolean
1011  gint selection_num,
1012  gint start_pos,
1013  gint end_pos)
1014 {
1015  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (text);
1016  EvView *view = ev_page_accessible_get_view (self);
1017  EvRectangle *areas = NULL;
1018  guint n_areas = 0;
1019  GdkRectangle start_rect, end_rect;
1020  GdkPoint start_point, end_point;
1021 
1022  ev_page_cache_get_text_layout (view->page_cache, self->priv->page, &areas, &n_areas);
1023  if (start_pos < 0 || end_pos >= n_areas)
1024  return FALSE;
1025 
1026  _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, areas + start_pos, &start_rect);
1027  _ev_view_transform_doc_rect_to_view_rect (view, self->priv->page, areas + end_pos - 1, &end_rect);
1028  start_point.x = start_rect.x;
1029  start_point.y = start_rect.y;
1030  end_point.x = end_rect.x + end_rect.width;
1031  end_point.y = end_rect.y + end_rect.height;
1032  _ev_view_set_selection (view, &start_point, &end_point);
1033 
1034  return TRUE;
1035 }
1036 
1037 static gboolean
1039  gint start_pos,
1040  gint end_pos)
1041 {
1042  return ev_page_accessible_set_selection (text, 0, start_pos, end_pos);
1043 
1044 }
1045 
1046 static void
1048 {
1049  atk_object_set_role (ATK_OBJECT (page), ATK_ROLE_PAGE);
1050 
1051  page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, EV_TYPE_PAGE_ACCESSIBLE, EvPageAccessiblePrivate);
1052 }
1053 
1054 static void
1056 {
1057  iface->get_text = ev_page_accessible_get_text;
1058  iface->get_text_at_offset = ev_page_accessible_get_text_at_offset;
1059  iface->get_character_at_offset = ev_page_accessible_get_character_at_offset;
1060  iface->get_caret_offset = ev_page_accessible_get_caret_offset;
1061  iface->set_caret_offset = ev_page_accessible_set_caret_offset;
1062  iface->get_character_count = ev_page_accessible_get_character_count;
1063  iface->get_n_selections = ev_page_accessible_get_n_selections;
1064  iface->get_selection = ev_page_accessible_get_selection;
1065  iface->remove_selection = ev_page_accessible_remove_selection;
1066  iface->add_selection = ev_page_accessible_add_selection;
1067  iface->get_run_attributes = ev_page_accessible_get_run_attributes;
1068  iface->get_default_attributes = ev_page_accessible_get_default_attributes;
1069  iface->get_character_extents = ev_page_accessible_get_character_extents;
1070  iface->get_offset_at_point = ev_page_accessible_get_offset_at_point;
1071 }
1072 
1073 static GHashTable *
1075 {
1076  EvPageAccessiblePrivate* priv = accessible->priv;
1077 
1078  if (priv->links)
1079  return priv->links;
1080 
1081  priv->links = g_hash_table_new_full (g_direct_hash,
1082  g_direct_equal,
1083  NULL,
1084  (GDestroyNotify)g_object_unref);
1085  return priv->links;
1086 }
1087 
1088 static AtkHyperlink *
1089 ev_page_accessible_get_link (AtkHypertext *hypertext,
1090  gint link_index)
1091 {
1092  GHashTable *links;
1093  EvMappingList *link_mapping;
1094  gint n_links;
1095  EvMapping *mapping;
1096  EvLinkAccessible *atk_link;
1097  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (hypertext);
1098  EvView *view = ev_page_accessible_get_view (self);
1099 
1100  if (link_index < 0)
1101  return NULL;
1102 
1103  if (!EV_IS_DOCUMENT_LINKS (view->document))
1104  return NULL;
1105 
1106  links = ev_page_accessible_get_links (EV_PAGE_ACCESSIBLE (hypertext));
1107 
1108  atk_link = g_hash_table_lookup (links, GINT_TO_POINTER (link_index));
1109  if (atk_link)
1110  return atk_hyperlink_impl_get_hyperlink (ATK_HYPERLINK_IMPL (atk_link));
1111 
1112  link_mapping = ev_page_cache_get_link_mapping (view->page_cache, self->priv->page);
1113  if (!link_mapping)
1114  return NULL;
1115 
1116  n_links = ev_mapping_list_length (link_mapping);
1117  if (link_index > n_links - 1)
1118  return NULL;
1119 
1120  mapping = ev_mapping_list_nth (link_mapping, n_links - link_index - 1);
1121  atk_link = ev_link_accessible_new (EV_PAGE_ACCESSIBLE (hypertext),
1122  EV_LINK (mapping->data),
1123  &mapping->area);
1124  g_hash_table_insert (links, GINT_TO_POINTER (link_index), atk_link);
1125 
1126  return atk_hyperlink_impl_get_hyperlink (ATK_HYPERLINK_IMPL (atk_link));
1127 }
1128 
1129 static gint
1130 ev_page_accessible_get_n_links (AtkHypertext *hypertext)
1131 {
1132  EvMappingList *link_mapping;
1133  EvPageAccessible *self = EV_PAGE_ACCESSIBLE (hypertext);
1134  EvView *view = ev_page_accessible_get_view (self);
1135 
1136  if (!EV_IS_DOCUMENT_LINKS (view->document))
1137  return 0;
1138 
1139  link_mapping = ev_page_cache_get_link_mapping (view->page_cache,
1140  self->priv->page);
1141 
1142  return link_mapping ? ev_mapping_list_length (link_mapping) : 0;
1143 }
1144 
1145 static gint
1146 ev_page_accessible_get_link_index (AtkHypertext *hypertext,
1147  gint offset)
1148 {
1149  guint i;
1150  gint n_links = ev_page_accessible_get_n_links (hypertext);
1151 
1152  for (i = 0; i < n_links; i++) {
1153  AtkHyperlink *hyperlink;
1154  gint start_index, end_index;
1155 
1156  hyperlink = ev_page_accessible_get_link (hypertext, i);
1157  start_index = atk_hyperlink_get_start_index (hyperlink);
1158  end_index = atk_hyperlink_get_end_index (hyperlink);
1159 
1160  if (start_index <= offset && end_index >= offset)
1161  return i;
1162  }
1163 
1164  return -1;
1165 }
1166 
1167 static void
1169 {
1170  iface->get_link = ev_page_accessible_get_link;
1171  iface->get_n_links = ev_page_accessible_get_n_links;
1172  iface->get_link_index = ev_page_accessible_get_link_index;
1173 }
1174 
1175 static void
1176 ev_page_accessible_get_extents (AtkComponent *atk_component,
1177  gint *x,
1178  gint *y,
1179  gint *width,
1180  gint *height,
1181  AtkCoordType coord_type)
1182 {
1183  EvPageAccessible *self;
1184  EvView *view;
1185  GdkRectangle page_area;
1186  GtkBorder border;
1187  EvRectangle doc_rect, atk_rect;
1188 
1189  self = EV_PAGE_ACCESSIBLE (atk_component);
1190  view = ev_page_accessible_get_view (self);
1191  ev_view_get_page_extents (view, self->priv->page, &page_area, &border);
1192 
1193  doc_rect.x1 = page_area.x;
1194  doc_rect.y1 = page_area.y;
1195  doc_rect.x2 = page_area.x + page_area.width;
1196  doc_rect.y2 = page_area.y + page_area.height;
1197  _transform_doc_rect_to_atk_rect (self->priv->view_accessible, self->priv->page, &doc_rect, &atk_rect, coord_type);
1198 
1199  *x = atk_rect.x1;
1200  *y = atk_rect.y1;
1201  *width = atk_rect.x2 - atk_rect.x1;
1202  *height = atk_rect.y2 - atk_rect.y1;
1203 }
1204 
1205 static void
1207 {
1208  iface->get_extents = ev_page_accessible_get_extents;
1209 }
1210 
1211 static void
1213  gint page,
1214  EvPageAccessible *self)
1215 {
1216  if (page == self->priv->page)
1218 }
1219 
1222  gint page)
1223 {
1224  EvPageAccessible *atk_page;
1225  EvView *view;
1226 
1227  g_return_val_if_fail (EV_IS_VIEW_ACCESSIBLE (view_accessible), NULL);
1228  g_return_val_if_fail (page >= 0, NULL);
1229 
1230  atk_page = g_object_new (EV_TYPE_PAGE_ACCESSIBLE,
1231  "view-accessible", view_accessible,
1232  "page", page,
1233  NULL);
1234 
1235  view = ev_page_accessible_get_view (EV_PAGE_ACCESSIBLE (atk_page));
1236  if (ev_page_cache_is_page_cached (view->page_cache, page))
1238  else
1239  g_signal_connect (view->page_cache, "page-cached",
1240  G_CALLBACK (page_cached_cb),
1241  atk_page);
1242 
1243  return EV_PAGE_ACCESSIBLE (atk_page);
1244 }
1245 
1246 AtkObject *
1248  EvMapping *mapping)
1249 {
1250  gint i;
1251 
1252  ev_page_accessible_initialize_children (page_accessible);
1253  if (!mapping || !page_accessible->priv->children)
1254  return NULL;
1255 
1256  for (i = 0; i < page_accessible->priv->children->len; i++) {
1257  AtkObject *child;
1258 
1259  child = g_ptr_array_index (page_accessible->priv->children, i);
1260  if (EV_IS_FORM_FIELD_ACCESSIBLE (child) &&
1262  return child;
1263  }
1264 
1265  return NULL;
1266 }
1267 
1268 void
1270  EvMapping *mapping)
1271 {
1272  AtkObject *child;
1273 
1274  child = ev_page_accessible_get_accessible_for_mapping (page_accessible, mapping);
1275  if (!child)
1276  return;
1277 
1278  if (EV_IS_FORM_FIELD_ACCESSIBLE (child))
1280 }