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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-10-29 21:24:49 +08:00
1 parent afdb368051
commit 4d467e1ba6
3 files changed
+29 -4

No files matched your search

+10 -3
View File
@@ -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) {
+1 -1
View File
@@ -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),
+18
View File
@@ -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#"<subform name="form" layout="tb">"#, r#"<subform name="form" layout="tb"><subform name="page" w="540pt" h="720pt">"#)
.replace("</subform></template>", "</subform></subform></template>")
.replace(r#"<subform name="list" layout="tb">"#, &format!(r#"<subform name="list" x="0pt" y="0pt" w="540pt" layout="{layout}">"#))
.replace("<xfa:data><form><list>", "<xfa:data><form><page><list>")
.replace("</list></form></xfa:data>", "</list></page></form></xfa:data>");
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}");
}
}