diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs index a5dd72a79..ca0d809f9 100644 --- a/crates/xfa/src/layout.rs +++ b/crates/xfa/src/layout.rs @@ -649,15 +649,17 @@ impl Layouter<'_> { if self.frames.is_empty() || !placed || y + h <= rect.bottom() + 0.01 || h > rect.h + 0.01 { return Ok(()); } - self.break_frames() + self.break_frames(h) } /// Break the flowed subforms being laid out ([`Self::frames`]) across content areas, as /// another viewer breaks such a section on a synthetic form: each one's part here ends after /// its content and its bottom margin (the innermost first, each inside the next), the flow /// moves to the next content area, and each starts a new part there after its top margin - /// (the outermost first). - fn break_frames(&mut self) -> Result<(), XfaError> { + /// (the outermost first). The top margins take only what the `need` points of the child that + /// comes next leave of the new area: a child that fits a fresh area but not after the margins + /// would otherwise run past its bottom, out of its border. + fn break_frames(&mut self, need: f64) -> Result<(), XfaError> { let (rect, mut y, _) = self.cursor(); let page = self.pages.len().saturating_sub(1); let here: Vec = self.frames.iter().map(|f| self.on_area(&f.part)).collect(); @@ -671,11 +673,15 @@ impl Layouter<'_> { } } self.next_area()?; + let (rect, y, _) = self.cursor(); + let mut room = (rect.bottom() - y - need).max(0.0); for i in 0..self.frames.len() { let Some(mark) = self.flow_mark() else { break }; let Some(f) = self.frames.get_mut(i) else { break }; f.part = mark; - let top = f.margin.top; + // `max` before `min`: a NaN margin counts as none and leaves `room` a number. + let top = f.margin.top.max(0.0).min(room); + room = (room - top).max(0.0); self.advance(top); } Ok(()) diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index f20bb94ee..2bd19ddd2 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -2117,6 +2117,16 @@ fn a_flowed_subform_is_drawn_with_its_border_under_its_content() { assert!(form.pages.len() >= 2, "the long text runs onto another page"); assert!(form.pages.iter().all(|p| !p.items.iter().any(|i| matches!(i, Item::Fill { .. }))), "no fill drawn"); assert_eq!(form.warnings.iter().filter(|w| w.contains("bordered section")).count(), 1, "{:?}", form.warnings); + // A field that fits a fresh page (720pt) but not after the 7.2pt top margin: the margin + // gives way, so the field ends at the content area's bottom, inside the second part. + let tall = r#""#; + let form = lay(&boxed(&(field("a") + tall))); + assert_eq!(form.pages.len(), 2); + let tall = form.pages[1].items.iter().find_map(|i| if let Item::Widget(w) = i { (w.name == "tall").then_some(w.rect) } else { None }).unwrap(); + assert!(tall.bottom() <= 756.01, "the field stays in the content area: {tall:?}"); + let part = form.pages[1].items.iter().find_map(|i| if let Item::Fill { rect, .. } = i { Some(*rect) } else { None }).expect("the second part"); + assert!(part.y <= tall.y + 0.01 && part.bottom() >= tall.bottom() - 0.01, "the border holds the field: {part:?} {tall:?}"); + assert!(form.warnings.is_empty(), "{:?}", form.warnings); } #[test]