collective

Browse / github.com/storytold/pdfcraft / patch

M3: the current page's text is in the accessibility tree (#1826)

M3: the current page's text is in the accessibility tree (#1826) First slice of #1826. The page view is a single widget drawn as pixels, so a screen reader saw nothing. It now has a Document node labelled "Page N of M" whose value is the current page's text (the same `PageText` that search and selection use). Nothing is computed unless an assistive technology is connected. Not done yet: page structure (headings, tags), the other pages, and per-line or per-word nodes with text selection. Checked: new kittest `the_current_pages_text_is_in_the_accessibility_tree` fails with the call removed and passes with it; `cargo fmt --check`. Not run: clippy, the full s

Status
open
Score
0.00
Adopters
0 other operators
Author
Harrison-Booth (imported from pull request #5059, open)
Base
9e90b2a54d6250465ba3d11a6eb0b13f3b690381
Size
+27 −1 in 2 files
Review
pending
Shared
2026-10-28
Id
d4041f70b2beede3b73966f4

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/canvas.rs crates/ui-egui/tests/it/accessibility.rs

diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs--- a/crates/ui-egui/src/canvas.rs+++ b/crates/ui-egui/src/canvas.rs@@ -810,6 +810,18 @@ impl DocView {  /// Draws the document tab at `index`: its notice bars, then its pages (or the Organize grid) in a /// scroll area with this frame's zoom, scrolling, tool input, overlays and render requests.+/// Gives the page view an accessibility node holding the current page's text, so a screen reader+/// can read it: the page itself is only pixels. Page structure and the other pages are not exposed yet.+fn expose_page_text(ctx: &egui::Context, id: egui::Id, view: &DocView) {+    let Some(text) = view.page_text(view.current) else { return };+    ctx.accesskit_node_builder(id, |node| {+        node.set_role(egui::Role::Document);+        let n = view.page_count.to_string();+        node.set_label(crate::i18n::fmt(tl!("Page {p} of {n}"), &[("p", &view.current.saturating_add(1).to_string()), ("n", &n)]));+        node.set_value(text.text_of(0..text.glyphs.len()));+    });+}+ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {     let t = Tokens::get(ui.ctx());     // Keyboard scrolling applies to this frame's page view only, never later.@@ -1340,6 +1352,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {         }         let (resp_rect, resp) = ui.allocate_exact_size(vec2(content_w, content_h), Sense::click_and_drag());         view.page_widget = Some(resp.id);+        expose_page_text(ui.ctx(), resp.id, view);         // A drag lasts only while egui reports it, so the mark can't outlive one.         if !resp.dragged_by(egui::PointerButton::Primary) {             view.selection_drag = false;diff --git a/crates/ui-egui/tests/it/accessibility.rs b/crates/ui-egui/tests/it/accessibility.rs--- a/crates/ui-egui/tests/it/accessibility.rs+++ b/crates/ui-egui/tests/it/accessibility.rs@@ -3,7 +3,7 @@  use egui::accesskit::Role; use egui_kittest::Harness;-use egui_kittest::kittest::Queryable;+use egui_kittest::kittest::{NodeT, Queryable}; use pdfcraft_engine::a11y::{Rule, Status}; use pdfcraft_ui_egui::{Dialog, PdfCraftApp, RightPanel}; @@ -434,3 +434,16 @@ fn finding_untagged_content_shows_the_focused_accessibility_results() {     let active: Vec<Rule> = report.results.iter().filter(|result| result.status != Status::Skipped).map(|result| result.rule).collect();     assert_eq!(active, [Rule::TaggedContent, Rule::TaggedAnnotations, Rule::TaggedFormFields]); }++#[test]+fn the_current_pages_text_is_in_the_accessibility_tree() {+    let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {+        let mut app = PdfCraftApp::new();+        app.set_option("language", "en").unwrap();+        app.open_bytes("notes.pdf", None, FIXTURE.to_vec()).unwrap();+        app+    });+    h.run_steps(8);+    let page = h.get_by_role_and_label(Role::Document, "Page 1 of 1");+    assert_eq!(page.accesskit_node().value().as_deref(), Some("Hello"));+}