From 4d467e1ba6c271aeefc65ad2a0ec179248313e32 Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:29:38 +0800 Subject: [PATCH] M12: report rows cut from repeats in positioned containers A repeating subform drawn inside a positioned container (a tb, lr-tb or rl-tb subform placed on a positioned page) was cut at its occur max or MAX_INSTANCES like a flowed one, but the rows past the cut were dropped without the layout warning that flowed subforms and tables give. Both drawing paths now call warn_truncated, so the README, the MAX_INSTANCES doc and the form.xfa-data note are true for these layouts too (XFA 3.3, "The occur element", p. 786). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC --- crates/xfa/src/layout/draw.rs | 13 ++++++++++--- crates/xfa/src/layout/flow.rs | 2 +- crates/xfa/src/repeat_tests.rs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/xfa/src/layout/draw.rs b/crates/xfa/src/layout/draw.rs index f6520c1628..cc8ff5b670 100644 --- a/crates/xfa/src/layout/draw.rs +++ b/crates/xfa/src/layout/draw.rs @@ -97,8 +97,11 @@ impl Layouter<'_> { } let (w, h) = self.size_of(k, inner.w, &ctx, depth + 1); // A repeating subform here (a table in an area of a positioned page, - // say) gets its instances, as in a flowed one, one under another. - for instance in 0..self.instances(k, &ctx.som) { + // say) gets its instances, as in a flowed one, one under another, + // and the rows past them are reported as there. + let instances = self.instances(k, &ctx.som); + self.warn_truncated(k, &ctx.som, instances); + for instance in 0..instances { self.draw_node(k, Rect::new(inner.x, y, w, h), &ctx, depth + 1, sibs.at(i) + instance)?; y += h; } @@ -106,7 +109,11 @@ impl Layouter<'_> { } Layout::LrTb | Layout::RlTb => { // A repeating subform here (rows of fields under a heading) gets its - // instances, as in the other layouts. + // instances, as in the other layouts, and the rows past them are reported. + for k in s.children.iter().filter(|k| k.common().presence.occupies()) { + let instances = self.instances(k, &ctx.som); + self.warn_truncated(k, &ctx.som, instances); + } let (placed, _) = self.pack_instances(&s.children, inner.w, &ctx, depth, &ctx.som); for (i, instance, r) in placed { if let Some(k) = s.children.get(i) { diff --git a/crates/xfa/src/layout/flow.rs b/crates/xfa/src/layout/flow.rs index 36a2af9d3b..797ec6aa6c 100644 --- a/crates/xfa/src/layout/flow.rs +++ b/crates/xfa/src/layout/flow.rs @@ -295,7 +295,7 @@ impl Layouter<'_> { /// The data holds more instances of `node` than `laid_out`: say so, since calculations /// then see only those, and say why. (The template's own counts can't ask for more: the /// parser reads `initial` and `max` up to [`MAX_INSTANCES`].) - fn warn_truncated(&mut self, node: &Node, parent_som: &str, laid_out: usize) { + pub(super) fn warn_truncated(&mut self, node: &Node, parent_som: &str, laid_out: usize) { let Node::Subform(sf) = node else { return }; let from_data = match (&sf.common.name, self.data) { (Some(name), Some(d)) if sf.occur.max != Some(1) => d.count(&data_path(self.tpl, parent_som), name), diff --git a/crates/xfa/src/repeat_tests.rs b/crates/xfa/src/repeat_tests.rs index 810847c30b..bce2051838 100644 --- a/crates/xfa/src/repeat_tests.rs +++ b/crates/xfa/src/repeat_tests.rs @@ -100,3 +100,21 @@ fn an_initial_count_lays_out_whole_up_to_the_limit() { assert_eq!(crate::layout::instance_count(&unbounded, 0), MAX_INSTANCES); assert_eq!(crate::layout::instance_count(&unbounded, usize::MAX), MAX_INSTANCES); } + +#[test] +fn rows_cut_in_a_positioned_container_are_reported_too() { + // The list sits in a positioned subform, so it is drawn rather than flowed: its `tb`, + // `lr-tb` and `rl-tb` repeats are cut at the same bounds and must say so as a flowed one does. + for layout in ["tb", "lr-tb", "rl-tb"] { + let xml = rows_form("5", 60, false) + .replace(r#""#, r#""#) + .replace("", "") + .replace(r#""#, &format!(r#""#)) + .replace("
", "") + .replace("", "
"); + let form = layout_xml(&xml).unwrap(); + assert_eq!(values(&form), expected(5), "{layout}"); + let w = cut_warning(&form).unwrap_or_else(|| panic!("{layout}: the rows past occur max are reported: {:?}", form.warnings)); + assert!(w.contains("holds 60 rows") && w.contains("first 5"), "{layout}: {w}"); + } +}