mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 13:24:35 +00:00
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:
1 parent
a4264efbb2
commit
5172a1b7ea
4 files changed
+46
-10
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user