M12: measure each XFA table row instance under its own SOM path

A table row's height was measured under the table's SOM path, without the
row instance, while draw_row draws its cells under row[n]. A cell that
grows with its text (a multi-line field without a height) then found no
value: every instance took the empty height, and a long value was drawn
in a short slot over the rows after it (XFA 3.3, "Tables", p. 327;
"Growable Containers", p. 275).

row_height now takes the instance index and measures under that
instance's path, as draw_row does. Table measurement sums each instance's
own height, and flowed and positioned tables size each instance (and the
overflow leader) on its own.

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:56:26 +08:00
1 parent a4264efbb2
commit 5172a1b7ea
4 files changed
+46 -10

No files matched your search

+1 -1
View File
@@ -136,8 +136,8 @@ impl Layouter<'_> {
// A repeating row (a table in a positioned subform, say) gets its
// instances one under another, as in a flowed table (XFA 3.3,
// "Tables", p. 327), and the rows past them are reported.
let h = self.row_height(r, &cols, inner.w, &ctx, depth + 1);
for instance in 0..self.laid_out_instances(k, &ctx.som) {
let h = self.row_height(r, &cols, inner.w, &ctx, depth + 1, csib.saturating_add(instance));
self.draw_row(r, &cols, Rect::new(inner.x, y, inner.w, h), &ctx, depth + 1, csib + instance)?;
y += h;
}
+2 -2
View File
@@ -233,8 +233,8 @@ impl Layouter<'_> {
Some(Node::Subform(l)) => Some((li, l.as_ref())),
_ => None,
});
let lead_h = lead.map_or(0.0, |(_, l)| self.row_height(l, &cols, inner_w, ctx, depth + 1));
let sibs = SomIndices::of(&sf.children);
let lead_h = lead.map_or(0.0, |(li, l)| self.row_height(l, &cols, inner_w, ctx, depth + 1, sibs.at(li)));
for (ci, child) in sf.children.iter().enumerate() {
if !child.common().presence.occupies() {
continue;
@@ -246,7 +246,7 @@ impl Layouter<'_> {
// Rows added as data (in another viewer) repeat the row.
let instances = self.laid_out_instances(child, &ctx.som);
for inst in 0..instances {
let h = self.row_height(row, &cols, inner_w, ctx, depth + 1);
let h = self.row_height(row, &cols, inner_w, ctx, depth + 1, csib.saturating_add(inst));
let led = lead.filter(|(li, _)| *li != ci);
if self.make_room(h, led.map_or(0.0, |_| lead_h))?
&& let Some((li, l)) = led
+11 -7
View File
@@ -108,15 +108,16 @@ impl Layouter<'_> {
.enumerate()
.filter(|(_, k)| k.common().presence.occupies())
.map(|(i, k)| match k {
// A repeating row takes its height once per instance, as drawn.
Node::Subform(r) if r.layout == Layout::Row => {
self.row_height(r, &cols, inner_w, ctx, depth + 1) * self.instances(k, &som) as f64
}
// Each instance of a repeating row takes its own height, as drawn.
Node::Subform(r) if r.layout == Layout::Row => (0..self.instances(k, &som))
.map(|n| self.row_height(r, &cols, inner_w, ctx, depth + 1, sibs.at(i).saturating_add(n)))
.sum(),
_ => self.size_at(k, inner_w, ctx, depth + 1, sibs.at(i)).1,
})
.sum()
}
Layout::Row => self.row_height(s, &self.columns(s, inner_w), inner_w, ctx, depth + 1),
// `ctx` is already this row instance's own.
Layout::Row => self.cells(s, &self.columns(s, inner_w), inner_w, ctx, depth + 1).1,
},
};
content + c.margin.vertical()
@@ -389,7 +390,10 @@ impl Layouter<'_> {
(out, clamp_opt(h, row.common.min_h, row.common.max_h))
}
pub(super) fn row_height(&self, row: &Subform, cols: &[f64], inner_w: f64, ctx: &Ctx, depth: usize) -> f64 {
self.cells(row, cols, inner_w, ctx, depth).1
/// The height of instance `sib` of `row` in the table at `ctx`, measured as [`Self::draw_row`]
/// draws it: under the instance's own SOM path, so a cell that grows with its text takes
/// that instance's value (XFA 3.3, "Tables", p. 327; "Growable Containers", p. 275).
pub(super) fn row_height(&self, row: &Subform, cols: &[f64], inner_w: f64, ctx: &Ctx, depth: usize, sib: usize) -> f64 {
self.cells(row, cols, inner_w, &ctx.with_som(&row.common, sib), depth).1
}
}
+32
View File
@@ -247,3 +247,35 @@ fn each_instance_of_a_repeating_container_is_measured_with_its_own_rows() {
}
}
}
#[test]
fn each_row_instance_takes_the_height_of_its_own_growing_text() {
// A repeating row whose cell grows with its text (a multi-line field without a height) is
// as tall as its own instance's value (XFA 3.3, "Tables", p. 327; "Growable Containers",
// p. 271): a long value makes its row taller and pushes the rows after it down, wherever
// the table sits.
let long = "a long value that wraps over several lines of the cell ".repeat(6);
for place in [Place::Positioned, Place::PositionedFlow, Place::Flowed] {
let xml = table_form(place, r#"min="1" max="-1""#, 3)
.replace(
r#"<field name="qty" w="200pt" h="20pt"><ui><textEdit/></ui></field>"#,
r#"<field name="qty" w="200pt" minH="20pt"><ui><textEdit multiLine="1"/></ui></field>"#,
)
.replace("<qty>row 2</qty>", &format!("<qty>{long}</qty>"));
let form = layout_xml(&xml).unwrap();
let all = laid_out(&form);
let qty = named(&all, "qty");
assert_eq!(qty.len(), 3, "{place:?}");
let (short, tall) = (qty[0].3, qty[1].3);
assert!(tall.h > short.h + 20.0, "{place:?}: the long row {tall:?} is no taller than a short one {short:?}");
for pair in qty.windows(2) {
let (a, b) = (pair[0].3, pair[1].3);
assert!((b.y - a.bottom()).abs() < 0.01, "{place:?}: row at {b:?} after {a:?}");
}
let foot = named(&all, "total")[0].3;
assert!((foot.y - qty[2].3.bottom()).abs() < 0.01, "{place:?}: footer {foot:?} after {:?}", qty[2].3);
if let Some(after) = named(&all, "after").first() {
assert!((after.3.y - foot.bottom()).abs() < 0.01, "{place:?}: after {:?}, footer {foot:?}", after.3);
}
}
}