diff --git a/papers-48.7-implement-accessible-text-for-pps-view.patch b/papers-48.7-implement-accessible-text-for-pps-view.patch new file mode 100644 index 0000000..908572c --- /dev/null +++ b/papers-48.7-implement-accessible-text-for-pps-view.patch @@ -0,0 +1,609 @@ +From 633a8b188558a3bc9d309ba2554ff665791eb3b7 Mon Sep 17 00:00:00 2001 +From: Lukáš Tyrychtr +Date: Fri, 28 Mar 2025 09:54:20 +0100 +Subject: libview: implement AccessibleText for PpsViewPage + +Emit proper notifications from the text implementation. + +Backport assisted-by Claude + +diff --git a/libview/pps-view.c b/libview/pps-view.c +index ec6c1f8d7..e3152b9dc 100644 +--- a/libview/pps-view.c ++++ b/libview/pps-view.c +@@ -258,7 +258,9 @@ static void pps_view_stop_signature_rect (PpsView *view); + + static void pps_view_update_primary_selection (PpsView *view); + +-G_DEFINE_TYPE_WITH_CODE (PpsView, pps_view, GTK_TYPE_WIDGET, G_ADD_PRIVATE (PpsView) G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) ++static void pps_view_accessible_text_init (GtkAccessibleTextInterface *iface); ++ ++G_DEFINE_TYPE_WITH_CODE (PpsView, pps_view, GTK_TYPE_WIDGET, G_ADD_PRIVATE (PpsView) G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL) G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE_TEXT, pps_view_accessible_text_init)) + + #define GET_PRIVATE(o) pps_view_get_instance_private (o) + +@@ -3855,6 +3857,7 @@ pps_view_set_caret_cursor_position (PpsView *view, + + g_signal_emit (view, signals[SIGNAL_CURSOR_MOVED], 0, + priv->cursor_page, priv->cursor_offset); ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (view)); + + if (priv->caret_enabled && cursor_is_in_visible_page (view)) + gtk_widget_queue_draw (GTK_WIDGET (view)); +@@ -4910,6 +4913,7 @@ position_caret_cursor_for_event (PpsView *view, + priv->cursor_line_offset = area.x; + + g_signal_emit (view, signals[SIGNAL_CURSOR_MOVED], 0, priv->cursor_page, priv->cursor_offset); ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (view)); + + if (redraw) { + gtk_widget_queue_draw (GTK_WIDGET (view)); +@@ -6197,6 +6201,7 @@ pps_view_move_cursor (PpsView *view, + rect.y += scroll_y; + _pps_view_ensure_rectangle_is_visible (view, priv->cursor_page, &rect); + g_signal_emit (view, signals[SIGNAL_CURSOR_MOVED], 0, priv->cursor_page, priv->cursor_offset); ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (view)); + clear_selection (view); + return TRUE; + } +@@ -6236,6 +6241,7 @@ pps_view_move_cursor (PpsView *view, + _pps_view_ensure_rectangle_is_visible (view, priv->cursor_page, &rect); + + g_signal_emit (view, signals[SIGNAL_CURSOR_MOVED], 0, priv->cursor_page, priv->cursor_offset); ++ gtk_accessible_text_update_caret_position (GTK_ACCESSIBLE_TEXT (view)); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + +@@ -7026,6 +7032,541 @@ zoom_gesture_scale_changed_cb (GtkGestureZoom *gesture, + pps_view_zoom (view, factor); + } + ++/* 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 (PpsView *self, ++ gint page, ++ PangoLogAttr *log_attrs, ++ gint offset) ++{ ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ PpsRectangle *areas = NULL; ++ guint n_areas = 0; ++ gdouble line_spacing, this_line_height, next_word_width; ++ PpsRectangle *this_line_start; ++ PpsRectangle *this_line_end; ++ PpsRectangle *next_line_start; ++ PpsRectangle *next_line_end; ++ PpsRectangle *next_word_end; ++ gint prev_offset, next_offset; ++ ++ if (!log_attrs[offset].is_white) ++ return FALSE; ++ ++ pps_page_cache_get_text_layout (priv->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. ++ */ ++ while (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. */ ++ while (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; ++ ++ while (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 gchar * ++get_substring (GtkAccessibleText *text, ++ unsigned int start, ++ unsigned int end) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ gchar *substring, *normalized; ++ const gchar *page_text; ++ ++ page_text = pps_page_cache_get_text (priv->page_cache, priv->cursor_page); ++ if (!page_text) ++ return NULL; ++ if (end > g_utf8_strlen (page_text, -1)) ++ end = strlen (page_text); ++ start = CLAMP (start, 0, end); ++ ++ substring = g_utf8_substring (page_text, start, end); ++ normalized = g_utf8_normalize (substring, -1, G_NORMALIZE_NFKC); ++ g_free (substring); ++ ++ return normalized; ++} ++ ++static GBytes * ++pps_view_accessible_text_get_contents (GtkAccessibleText *text, ++ unsigned int start, ++ unsigned int end) ++{ ++ gchar *substring = get_substring (text, start, end); ++ if (!substring) ++ return g_bytes_new (NULL, 0); ++ ++ return g_bytes_new_take (substring, strlen (substring)); ++} ++ ++static void ++get_range_for_granularity (GtkAccessibleText *text, ++ GtkAccessibleTextGranularity granularity, ++ unsigned int offset, ++ unsigned int *start_offset, ++ unsigned int *end_offset) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ gint start = 0; ++ gint end = 0; ++ PangoLogAttr *log_attrs = NULL; ++ gulong n_attrs; ++ ++ if (!priv->page_cache) ++ return; ++ ++ pps_page_cache_get_text_log_attrs (priv->page_cache, priv->cursor_page, &log_attrs, &n_attrs); ++ if (!log_attrs) ++ return; ++ ++ if (offset >= n_attrs) ++ return; ++ ++ switch (granularity) { ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_CHARACTER: ++ start = offset; ++ end = offset + 1; ++ break; ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_WORD: ++ start = offset; ++ while (start > 0 && !log_attrs[start].is_word_start) ++ start--; ++ end = offset + 1; ++ while (end < n_attrs && !log_attrs[end].is_word_start) ++ end++; ++ break; ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_SENTENCE: ++ for (start = offset; start > 0; start--) { ++ if (log_attrs[start].is_mandatory_break && treat_as_soft_return (self, priv->cursor_page, log_attrs, start - 1)) ++ continue; ++ if (log_attrs[start].is_sentence_start) ++ break; ++ } ++ for (end = offset + 1; end < n_attrs; end++) { ++ if (log_attrs[end].is_mandatory_break && treat_as_soft_return (self, priv->cursor_page, log_attrs, end - 1)) ++ continue; ++ if (log_attrs[end].is_sentence_start) ++ break; ++ } ++ break; ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_LINE: ++ start = offset; ++ while (start > 0 && !log_attrs[start].is_mandatory_break) ++ start--; ++ end = offset + 1; ++ while (end < n_attrs && !log_attrs[end].is_mandatory_break) ++ end++; ++ break; ++ case GTK_ACCESSIBLE_TEXT_GRANULARITY_PARAGRAPH: ++ /* FIXME: There is likely more than one paragraph on the page, so try to deduce it properly */ ++ start = 0; ++ end = n_attrs; ++ } ++ ++ *start_offset = start; ++ *end_offset = end; ++} ++ ++static GBytes * ++pps_view_accessible_text_get_contents_at (GtkAccessibleText *text, ++ unsigned int offset, ++ GtkAccessibleTextGranularity granularity, ++ unsigned int *start_offset, ++ unsigned int *end_offset) ++{ ++ gchar *substring; ++ ++ get_range_for_granularity (text, granularity, offset, start_offset, end_offset); ++ substring = get_substring (text, *start_offset, *end_offset); ++ ++ /* If newlines appear inside the text of a sentence (i.e. between the start and ++ * end offsets returned by get_substring), it interferes with ++ * the prosody of text-to-speech based-solutions such as a screen reader because ++ * speech synthesizers tend to pause after the newline char as if it were the end ++ * of the sentence. ++ */ ++ if (granularity == GTK_ACCESSIBLE_TEXT_GRANULARITY_SENTENCE) ++ g_strdelimit (substring, "\n", ' '); ++ ++ return g_bytes_new_take (substring, strlen (substring)); ++} ++ ++static unsigned int ++pps_view_accessible_text_get_caret_position (GtkAccessibleText *text) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ ++ if (priv->caret_enabled) ++ return priv->cursor_offset; ++ ++ return 0; ++} ++ ++static gboolean ++get_selection_bounds (PpsView *self, ++ gint *start_offset, ++ gint *end_offset) ++{ ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ cairo_rectangle_int_t rect; ++ cairo_region_t *region; ++ gint start, end; ++ gdouble scale = pps_document_model_get_scale (priv->model); ++ ++ region = pps_pixbuf_cache_get_selection_region (priv->pixbuf_cache, ++ priv->cursor_page, ++ scale); ++ ++ if (!region || cairo_region_is_empty (region)) ++ return FALSE; ++ ++ cairo_region_get_rectangle (region, 0, &rect); ++ start = _pps_view_get_caret_cursor_offset_at_doc_point (self, ++ priv->cursor_page, ++ rect.x / scale, ++ (rect.y + (rect.height / 2)) / scale); ++ if (start == -1) ++ return FALSE; ++ ++ cairo_region_get_rectangle (region, ++ cairo_region_num_rectangles (region) - 1, ++ &rect); ++ end = _pps_view_get_caret_cursor_offset_at_doc_point (self, ++ priv->cursor_page, ++ (rect.x + rect.width) / scale, ++ (rect.y + (rect.height / 2)) / scale); ++ if (end == -1) ++ return FALSE; ++ ++ *start_offset = start; ++ *end_offset = end; ++ ++ return TRUE; ++} ++ ++static gboolean ++pps_view_accessible_text_get_selection (GtkAccessibleText *text, ++ gsize *n_ranges, ++ GtkAccessibleTextRange **ranges) ++{ ++ PpsView *self = PPS_VIEW (text); ++ ++ *n_ranges = 0; ++ ++ gint start = 0, end = 0; ++ if (get_selection_bounds (self, &start, &end) && start != end) { ++ *n_ranges = 1; ++ *ranges = g_new (GtkAccessibleTextRange, 1); ++ (*ranges)[0].start = start; ++ (*ranges)[0].length = end - start; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static void ++fill_run_attributes (PangoAttrList *attrs, ++ const gchar *text, ++ unsigned int offset, ++ unsigned int *start_offset, ++ unsigned int *end_offset, ++ GStrvBuilder *names, ++ GStrvBuilder *values) ++{ ++ PangoAttrString *pango_string; ++ PangoAttrInt *pango_int; ++ PangoAttrColor *pango_color; ++ PangoAttrIterator *iter; ++ gint i, start, end; ++ gboolean has_attrs = FALSE; ++ glong text_length; ++ gchar *attr_value; ++ ++ text_length = g_utf8_strlen (text, -1); ++ if (offset < 0 || offset >= text_length) ++ return; ++ ++ /* Check if there are attributes for the offset, ++ * and set the attributes range if positive */ ++ iter = pango_attr_list_get_iterator (attrs); ++ i = g_utf8_offset_to_pointer (text, offset) - text; ++ ++ do { ++ pango_attr_iterator_range (iter, &start, &end); ++ if (i >= start && i < end) { ++ *start_offset = g_utf8_pointer_to_offset (text, text + start); ++ if (end == G_MAXINT) /* Last iterator */ ++ end = text_length; ++ *end_offset = g_utf8_pointer_to_offset (text, text + end); ++ has_attrs = TRUE; ++ } ++ } while (!has_attrs && pango_attr_iterator_next (iter)); ++ ++ if (!has_attrs) { ++ pango_attr_iterator_destroy (iter); ++ return; ++ } ++ ++ /* Fill the GtkAccessibleText attributes from the Pango attributes */ ++ pango_string = (PangoAttrString *) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY); ++ if (pango_string) { ++ g_strv_builder_add (names, GTK_ACCESSIBLE_ATTRIBUTE_FAMILY); ++ g_strv_builder_add (values, pango_string->value); ++ } ++ ++ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE); ++ if (pango_int) { ++ attr_value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE); ++ g_strv_builder_add (names, GTK_ACCESSIBLE_ATTRIBUTE_SIZE); ++ g_strv_builder_add (values, attr_value); ++ } ++ ++ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE); ++ if (pango_int) { ++ g_strv_builder_add (names, GTK_ACCESSIBLE_ATTRIBUTE_UNDERLINE); ++ g_strv_builder_add (values, pango_int->value ? GTK_ACCESSIBLE_ATTRIBUTE_UNDERLINE_NONE : GTK_ACCESSIBLE_ATTRIBUTE_UNDERLINE_SINGLE); ++ } ++ pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND); ++ if (pango_color) { ++ attr_value = g_strdup_printf ("%u,%u,%u", ++ pango_color->color.red, ++ pango_color->color.green, ++ pango_color->color.blue); ++ g_strv_builder_add (names, GTK_ACCESSIBLE_ATTRIBUTE_FOREGROUND); ++ g_strv_builder_add (values, attr_value); ++ } ++ ++ pango_attr_iterator_destroy (iter); ++} ++ ++static gboolean ++pps_view_accessible_text_get_attributes (GtkAccessibleText *text, ++ unsigned int offset, ++ gsize *n_ranges, ++ GtkAccessibleTextRange **ranges, ++ char ***attribute_names, ++ char ***attribute_values) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ PangoAttrList *attrs; ++ const gchar *page_text; ++ unsigned int start_offset = 0; ++ unsigned int end_offset = 0; ++ PpsPageCache *cache = priv->page_cache; ++ ++ if (offset < 0) ++ goto no_attrs; ++ ++ if (!cache) ++ goto no_attrs; ++ ++ page_text = pps_page_cache_get_text (cache, priv->cursor_page); ++ if (!page_text) ++ goto no_attrs; ++ ++ attrs = pps_page_cache_get_text_attrs (cache, priv->cursor_page); ++ if (!attrs) ++ goto no_attrs; ++ ++ GStrvBuilder *names = g_strv_builder_new (); ++ GStrvBuilder *values = g_strv_builder_new (); ++ fill_run_attributes (attrs, page_text, offset, &start_offset, &end_offset, names, values); ++ *n_ranges = 1; ++ *ranges = g_new (GtkAccessibleTextRange, 1); ++ (*ranges)[0].start = start_offset; ++ (*ranges)[0].length = end_offset - start_offset; ++ *attribute_names = g_strv_builder_end (names); ++ *attribute_values = g_strv_builder_end (values); ++ g_strv_builder_unref (names); ++ g_strv_builder_unref (values); ++ if (attribute_names[0] && attribute_names[0][0]) ++ return TRUE; ++ else { ++ *n_ranges = 0; ++ return FALSE; ++ } ++no_attrs: ++ *n_ranges = 0; ++ *attribute_names = g_new0 (char *, 1); ++ *attribute_values = g_new0 (char *, 1); ++ return FALSE; ++} ++ ++static void ++pps_view_accessible_text_get_default_attributes (GtkAccessibleText *text, ++ char ***attribute_names, ++ char ***attribute_values) ++{ ++ /* No default attributes */ ++ *attribute_names = g_new0 (char *, 1); ++ *attribute_values = g_new0 (char *, 1); ++ return; ++} ++ ++static gboolean ++pps_view_accessible_text_get_extents (GtkAccessibleText *text, ++ unsigned int start, ++ unsigned int end, ++ graphene_rect_t *extents) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ GtkWidget *toplevel; ++ PpsRectangle *areas = NULL; ++ PpsRectangle *doc_rect; ++ guint n_areas = 0; ++ graphene_point_t widget_point; ++ GdkRectangle view_rect; ++ PpsPageCache *cache = priv->page_cache; ++ ++ if (!cache) ++ return FALSE; ++ ++ pps_page_cache_get_text_layout (cache, priv->cursor_page, &areas, &n_areas); ++ if (!areas || start < 0 || start >= n_areas || end >= n_areas || start > end) ++ return FALSE; ++ ++ extents->origin.x = 0; ++ extents->origin.y = 0; ++ extents->size.width = 0; ++ extents->size.height = 0; ++ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self))); ++ for (int offset = start; offset <= end; offset++) { ++ doc_rect = areas + offset; ++ _pps_view_transform_doc_rect_to_view_rect (self, priv->cursor_page, doc_rect, &view_rect); ++ ++ if (!gtk_widget_compute_point (GTK_WIDGET (self), toplevel, graphene_point_zero (), &widget_point)) ++ return FALSE; ++ view_rect.x += widget_point.x; ++ view_rect.y += widget_point.y; ++ ++ extents->origin.x = MAX (extents->origin.x, view_rect.x); ++ extents->origin.y = MAX (extents->origin.y, view_rect.y); ++ extents->size.width = MAX (extents->size.width, view_rect.width); ++ extents->size.height = MAX (extents->size.height, view_rect.height); ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++pps_view_accessible_text_get_offset (GtkAccessibleText *text, ++ const graphene_point_t *point, ++ unsigned int *offset) ++{ ++ PpsView *self = PPS_VIEW (text); ++ PpsViewPrivate *priv = GET_PRIVATE (self); ++ GtkWidget *toplevel; ++ PpsRectangle *areas = NULL; ++ PpsRectangle *rect = NULL; ++ guint n_areas = 0; ++ guint i; ++ graphene_point_t widget_point; ++ gdouble view_x, view_y; ++ PpsPoint doc_point; ++ PpsPageCache *cache = priv->page_cache; ++ ++ if (!cache) ++ return FALSE; ++ ++ pps_page_cache_get_text_layout (cache, priv->cursor_page, &areas, &n_areas); ++ if (!areas) ++ return FALSE; ++ ++ view_x = point->x; ++ view_y = point->y; ++ toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self))); ++ if (!gtk_widget_compute_point (GTK_WIDGET (self), toplevel, graphene_point_zero (), &widget_point)) ++ return FALSE; ++ view_x -= widget_point.x; ++ view_y -= widget_point.y; ++ ++ doc_point = pps_view_get_doc_point_for_page (self, priv->cursor_page, view_x, view_y); ++ ++ for (i = 0; i < n_areas; i++) { ++ rect = areas + i; ++ if (doc_point.x >= rect->x1 && doc_point.x <= rect->x2 && ++ doc_point.y >= rect->y1 && doc_point.y <= rect->y2) ++ *offset = i; ++ } ++ ++ return TRUE; ++} ++ ++static void ++pps_view_accessible_text_init (GtkAccessibleTextInterface *iface) ++{ ++ iface->get_contents = pps_view_accessible_text_get_contents; ++ iface->get_contents_at = pps_view_accessible_text_get_contents_at; ++ iface->get_caret_position = pps_view_accessible_text_get_caret_position; ++ iface->get_selection = pps_view_accessible_text_get_selection; ++ iface->get_attributes = pps_view_accessible_text_get_attributes; ++ iface->get_default_attributes = pps_view_accessible_text_get_default_attributes; ++ iface->get_extents = pps_view_accessible_text_get_extents; ++ iface->get_offset = pps_view_accessible_text_get_offset; ++} ++ + static void + pps_view_class_init (PpsViewClass *class) + { +@@ -8427,6 +8975,7 @@ merge_selection_region (PpsView *view, + priv->selection_info.selections = new_list; + pps_pixbuf_cache_set_selection_list (priv->pixbuf_cache, new_list); + g_signal_emit (view, signals[SIGNAL_SELECTION_CHANGED], 0, NULL); ++ gtk_accessible_text_update_selection_bound (GTK_ACCESSIBLE_TEXT (view)); + + new_list_ptr = new_list; + old_list_ptr = old_list; +-- +2.51.1 + diff --git a/papers-48.7-use-correct-role-for-pps-view.patch b/papers-48.7-use-correct-role-for-pps-view.patch new file mode 100644 index 0000000..9335def --- /dev/null +++ b/papers-48.7-use-correct-role-for-pps-view.patch @@ -0,0 +1,20 @@ +From bc8bee5a81c20d3e6b414fa8783fdbfe79e9356b Mon Sep 17 00:00:00 2001 +From: Lukáš Tyrychtr +Date: Thu, 20 Mar 2025 15:05:03 +0100 +Subject: libview: Use correct roles for pps-view and pps-view-page + +Modified by Marek Kasik for backport. + +diff --git a/libview/pps-view.c b/libview/pps-view.c +index cededf462..2b3e094a9 100644 +--- a/libview/pps-view.c ++++ b/libview/pps-view.c +@@ -7644,6 +7644,8 @@ pps_view_class_init (PpsViewClass *class) + GDK_KEY_a, GDK_CONTROL_MASK, + (GtkShortcutFunc) pps_view_select_all, + NULL); ++ ++ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_DOCUMENT); + } + + static void diff --git a/papers.spec b/papers.spec index 32a2a6a..59d58de 100644 --- a/papers.spec +++ b/papers.spec @@ -42,6 +42,9 @@ Source1: papers-%{tarball_version}-vendor.tar.xz # Lower the requirement as we don't have the required versions Patch: papers-48.7-lower-requirements.patch +Patch: papers-48.7-implement-accessible-text-for-pps-view.patch +Patch: papers-48.7-use-correct-role-for-pps-view.patch + # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval ExcludeArch: %{ix86}