diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 5c24862d7..bdd625804 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -1237,8 +1237,11 @@ fn fields( pages.sort(); pages.dedup(); for p in pages { - let label = - p.map(|p| crate::i18n::fmt(tl!("Page {label}"), &[("label", &info.pages[p].label)])).unwrap_or_else(|| tl!("Unplaced").to_string()); + // A field's /P can name a page the document doesn't have (#1307); it reads as unplaced. + let label = p + .and_then(|p| info.pages.get(p)) + .map(|page| crate::i18n::fmt(tl!("Page {label}"), &[("label", &page.label)])) + .unwrap_or_else(|| tl!("Unplaced").to_string()); ui.add_space(4.0); ui.label(egui::RichText::new(label).font(theme::semibold(12.5)).color(t.text_muted)); for f in ordered.iter().copied().filter(|f| f.page == p) { diff --git a/crates/ui-egui/tests/forms.rs b/crates/ui-egui/tests/forms.rs index 69c06af76..0882cc54e 100644 --- a/crates/ui-egui/tests/forms.rs +++ b/crates/ui-egui/tests/forms.rs @@ -650,3 +650,26 @@ fn overlay_clears_after_fresh_tiles_zoom_then_edit_and_rejected_edit() { } assert!(!h.state().views[0].forms.unbaked.contains(&("name".to_string(), 0))); } + +/// A field whose /P is a page number past the document's end (#1307) is listed under +/// "Unplaced" instead of panicking while the Fields panel labels its page. +#[test] +fn the_fields_panel_lists_a_field_on_a_missing_page_as_unplaced() { + use egui_kittest::kittest::Queryable; + let bytes = pdf_from_objects(&[ + "<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [4 0 R] >> >>", + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 300] /Annots [4 0 R] >>", + "<< /Type /Annot /Subtype /Widget /FT /Tx /T (orphan) /Rect [10 10 100 30] /P 99 /F 4 >>", + ]); + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| { + let mut app = PdfCraftApp::new(); + app.set_option("language", "en").unwrap(); + app.open_bytes("orphan.pdf", None, bytes).unwrap(); + app.set_option("panel", "fields").unwrap(); + app + }); + h.run_steps(6); + h.get_by_label("Unplaced"); + assert_eq!(h.query_all_by_label("orphan").count(), 1); +}