M6: detected form fields get no border or fill

Prepare a form ▸ Detect form fields places each field on a line or box the
page already draws, but every new widget got a grey /MK /BC and an opaque
white /MK /BG, so the fill hid the printed form underneath. Detected fields
now clear both in the same undoable step; fields drawn by hand keep the
defaults.

Refs #294, #2831.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
boneyland
2026-10-16 18:59:09 +08:00
co-authored by Claude Opus 5.5
parent f0c9901c39
commit bf2eb470ed
3 changed files with 36 additions and 7 deletions
+12 -5
View File
@@ -343,8 +343,17 @@ impl Session {
let names = found.iter().map(|(_, c, _)| c.name.clone()).collect();
let mut edits = Vec::with_capacity(found.len());
for (page, c, turn) in found {
// A turned field keeps its place: the rectangle is already the one on the page.
let props = (turn != 0).then(|| pdfcraft_forms::FieldProps { rotation: Some((0, turn)), rect: Some((0, c.rect)), ..Default::default() });
// The page already draws the line or box the field sits on, so the field gets no
// border or fill of its own: an opaque fill would hide the printed form (#294, #2831).
let mut props = pdfcraft_forms::FieldProps {
appearance: Some(pdfcraft_forms::LookPatch { border: Some(None), fill: Some(None), ..Default::default() }),
..Default::default()
};
if turn != 0 {
// A turned field keeps its place: the rectangle is already the one on the page.
props.rotation = Some((0, turn));
props.rect = Some((0, c.rect));
}
let tall = if turn % 180 == 0 { c.rect[3] - c.rect[1] } else { c.rect[2] - c.rect[0] };
edits.push(Edit::AddField {
page,
@@ -355,9 +364,7 @@ impl Session {
},
name: Some(c.name.clone()),
});
if let Some(props) = props {
edits.push(Edit::SetFieldProps { name: c.name, props: Box::new(props) });
}
edits.push(Edit::SetFieldProps { name: c.name, props: Box::new(props) });
}
self.apply(id, Edit::Batch { label: "Detect form fields".into(), edits })?;
Ok(names)
+22
View File
@@ -3489,6 +3489,28 @@ fn detecting_fields_on_a_printed_form() {
assert!(s.detect_fields(id, &[]).is_empty(), "nothing left to detect");
}
/// #294, #2831: a detected field sits on a line or box the page already draws, so it gets no border
/// and no fill that would cover the printed form. A field drawn by hand keeps the defaults.
#[test]
fn detected_fields_have_no_border_or_fill() {
let mut s = Session::new().with_clock(|| 1_700_000_000);
let id = s.open("paper.pdf", None, Arc::new(paper_form()), None).unwrap();
let added = s.auto_detect_fields(id, &[]).unwrap();
let d = s.get(id).unwrap();
let kinds: Vec<pdfcraft_forms::FieldKind> = d.form.iter().map(|f| f.kind).collect();
assert!(kinds.contains(&pdfcraft_forms::FieldKind::Text) && kinds.contains(&pdfcraft_forms::FieldKind::CheckBox), "{kinds:?}");
for name in &added {
let look = d.field_look(name).unwrap();
assert_eq!((look.border, look.fill), (None, None), "{name}");
}
assert_eq!(d.can_undo(), Some("Detect form fields"), "still one step");
s.apply(id, Edit::AddField { page: 0, rect: [200.0, 10.0, 300.0, 30.0], kind: NewField::Text { multiline: false }, name: Some("drawn".into()) })
.unwrap();
let look = s.get(id).unwrap().field_look("drawn").unwrap();
assert!(look.border.is_some() && look.fill.is_some(), "{look:?}");
}
/// A printed form, 420 × 300 pt: a title, a label with a rule to write on, a box with a small
/// label in its corner, an empty box with its label above, and two check boxes.
fn paper_form() -> Vec<u8> {
+2 -2
View File
@@ -5088,8 +5088,8 @@ milestone = "M6"
status = "partial"
commands = ["form.detect", "form.prepare"]
tools = ["form_detect_fields"]
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/engine/src/tests.rs::detecting_fields_on_a_scanned_form", "crates/forms/src/detect/raster.rs::a_scanned_form_gives_its_rules_boxes_and_check_boxes", "crates/forms/src/detect/raster.rs::noise_and_uneven_paper_do_not_hide_the_lines", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel"]
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields, and from Prepare a form ▸ Detect form fields at any time (existing fields are skipped). Scanned pages (an image covers at least half the page and the content draws no lines) are rendered and their rules, boxes and check boxes read from the pixels; a box with a small printed label gets a field in its empty part. Tested on synthetic scans only: straight, stored sideways with /Rotate 90 (fields get /MK /R so typing reads upright), noisy, and 1 degree askew (fields stay inside their boxes but lose up to the skew in height). Labels come from the text layer or, when the OCR models are installed, from text recognition (Latin without accents, so German labels with umlauts make poorer names); without either, fields get default names (Text1, Check Box1). Recognition runs synchronously, about a second per scanned page. Not yet: comb boxes, dotted leader lines, tables of cells as such, deskewing, radio groups."
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/engine/src/tests.rs::detected_fields_have_no_border_or_fill", "crates/engine/src/tests.rs::detecting_fields_on_a_scanned_form", "crates/forms/src/detect/raster.rs::a_scanned_form_gives_its_rules_boxes_and_check_boxes", "crates/forms/src/detect/raster.rs::noise_and_uneven_paper_do_not_hide_the_lines", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel"]
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields, and from Prepare a form ▸ Detect form fields at any time (existing fields are skipped). Scanned pages (an image covers at least half the page and the content draws no lines) are rendered and their rules, boxes and check boxes read from the pixels; a box with a small printed label gets a field in its empty part. Detected fields get no border and no fill, since the page already draws the line or box they sit on; fields drawn by hand keep the grey border and white fill. Tested on synthetic scans only: straight, stored sideways with /Rotate 90 (fields get /MK /R so typing reads upright), noisy, and 1 degree askew (fields stay inside their boxes but lose up to the skew in height). Labels come from the text layer or, when the OCR models are installed, from text recognition (Latin without accents, so German labels with umlauts make poorer names); without either, fields get default names (Text1, Check Box1). Recognition runs synchronously, about a second per scanned page. Not yet: comb boxes, dotted leader lines, tables of cells as such, deskewing, radio groups."
[[feature]]
id = "form.auto-name-fields"