fix(ui): the Fields panel lists a field on a missing page as unplaced (#1307) (#1390)

A widget whose /P is a page number past the end of the document (here
/P 99 in a one-page file) kept that page, and the Fields panel indexed
the page list with it to label the heading, which panicked. A page the
document doesn't have now gets the existing "Unplaced" heading.

Adds forms::the_fields_panel_lists_a_field_on_a_missing_page_as_unplaced,
which fails before this change.
This commit is contained in:
Marco Arnulfo
2026-10-12 07:18:32 -04:00
committed by GitHub
parent af1c997aff
commit b6a1402bf3
2 changed files with 28 additions and 2 deletions
+5 -2
View File
@@ -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) {
+23
View File
@@ -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);
}