diff --git a/crates/xfa/src/layout/measure.rs b/crates/xfa/src/layout/measure.rs index 2fffb3dfa0..5c1ab99b3c 100644 --- a/crates/xfa/src/layout/measure.rs +++ b/crates/xfa/src/layout/measure.rs @@ -71,39 +71,7 @@ impl Layouter<'_> { let w = clamp_opt(w, c.min_w, c.max_w); let h = match c.h { Some(h) => h, - None => { - let inner_w = (w - c.margin.horizontal()).max(1.0); - let content = match node { - Node::Draw(d) => self.value_height(&d.value, inner_w, &ctx), - Node::Field(f) => self.field_height(f, inner_w, &ctx), - Node::ExclGroup(g) => self.group_height(g, inner_w, &ctx, depth), - Node::Area(a) => self.positioned_height(&a.children, inner_w, &ctx, depth), - Node::Subform(s) => match s.layout { - Layout::Positioned => self.positioned_height(&s.children, inner_w, &ctx, depth), - // Each instance of a repeating child takes its height again. - Layout::Tb => s - .children - .iter() - .filter(|k| k.common().presence.occupies()) - .map(|k| self.size_of(k, inner_w, &ctx, depth + 1).1 * self.instances(k, &som) as f64) - .sum(), - Layout::LrTb | Layout::RlTb => self.pack_instances(&s.children, inner_w, &ctx, depth, &som).1, - Layout::Table => { - let cols = self.columns(s, inner_w); - s.children - .iter() - .filter(|k| k.common().presence.occupies()) - .map(|k| match k { - Node::Subform(r) if r.layout == Layout::Row => self.row_height(r, &cols, inner_w, &ctx, depth + 1), - _ => self.size_of(k, inner_w, &ctx, depth + 1).1, - }) - .sum() - } - Layout::Row => self.row_height(s, &self.columns(s, inner_w), inner_w, &ctx, depth + 1), - }, - }; - content + c.margin.vertical() - } + None => self.content_height(node, (w - c.margin.horizontal()).max(1.0), &ctx, &som, depth) + c.margin.vertical(), }; // A flowed subform is as tall as what it holds: other viewers do not stretch one to // the `minH` a designer left on it. @@ -114,6 +82,40 @@ impl Layouter<'_> { (w, clamp_opt(h, min_h, c.max_h)) } + /// The height of `node`'s content within `inner_w` (inside its margins), whatever height it + /// gives itself. `ctx` is the node's own context ([`Ctx::with_som`]) and `som` its SOM path. + fn content_height(&self, node: &Node, inner_w: f64, ctx: &Ctx, som: &str, depth: usize) -> f64 { + match node { + Node::Draw(d) => self.value_height(&d.value, inner_w, ctx), + Node::Field(f) => self.field_height(f, inner_w, ctx), + Node::ExclGroup(g) => self.group_height(g, inner_w, ctx, depth), + Node::Area(a) => self.positioned_height(&a.children, inner_w, ctx, depth), + Node::Subform(s) => match s.layout { + Layout::Positioned => self.positioned_height(&s.children, inner_w, ctx, depth), + // Each instance of a repeating child takes its height again. + Layout::Tb => s + .children + .iter() + .filter(|k| k.common().presence.occupies()) + .map(|k| self.size_of(k, inner_w, ctx, depth + 1).1 * self.instances(k, som) as f64) + .sum(), + Layout::LrTb | Layout::RlTb => self.pack_instances(&s.children, inner_w, ctx, depth, som).1, + Layout::Table => { + let cols = self.columns(s, inner_w); + s.children + .iter() + .filter(|k| k.common().presence.occupies()) + .map(|k| match k { + Node::Subform(r) if r.layout == Layout::Row => self.row_height(r, &cols, inner_w, ctx, depth + 1), + _ => self.size_of(k, inner_w, ctx, depth + 1).1, + }) + .sum() + } + Layout::Row => self.row_height(s, &self.columns(s, inner_w), inner_w, ctx, depth + 1), + }, + } + } + /// The natural width of a container's content within `avail_w`. fn content_width(&self, node: &Node, avail_w: f64, ctx: &Ctx, depth: usize) -> f64 { let group: Vec; @@ -350,7 +352,18 @@ impl Layouter<'_> { } let span = k.common().col_span.max(1); let w = if col < cols.len() { cols.iter().skip(col).take(span).sum::() } else { k.common().w.unwrap_or((inner_w - x).max(1.0)) }; - let (_, ch) = self.size_of(k, w, &ctx, depth + 1); + let (cw, ch) = self.size_of(k, w, &ctx, depth + 1); + // A flowed subform in a cell is as tall as its content even when it gives a height: + // other viewers (Adobe Acrobat, PDF-XChange Editor) lay a section of a table row out + // that way, though a height makes a container fixed (XFA 3.3 p. 276). + let ch = match k { + Node::Subform(s) if s.common.h.is_some() && matches!(s.layout, Layout::Tb | Layout::LrTb | Layout::RlTb | Layout::Table) => { + let own = ctx.with_som(&s.common, 0); + let inner = (cw - s.common.margin.horizontal()).max(1.0); + clamp_opt(self.content_height(k, inner, &own, &own.som, depth + 1) + s.common.margin.vertical(), None, s.common.max_h) + } + _ => ch, + }; out.push((i, Rect::new(x, 0.0, w, ch))); h = h.max(ch); x += w; diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index e424be5441..99c1b02a24 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -4145,3 +4145,32 @@ fn clear_password_values_clears_static_xfa_password_fields() { let packets = read_packets(&doc).unwrap().unwrap(); assert!(!packets.xdp.contains("hunter2"), "{}", packets.xdp); } + +/// A flowed subform in a table cell is as tall as its content even when it gives a height, as +/// other viewers lay out a public form's per-child section (a `tb` subform with `h="186mm"` in a +/// row); a positioned cell, and a flowed subform outside a table, keep their heights. +#[test] +fn a_flowed_subform_in_a_table_cell_is_as_tall_as_its_content() { + let after_y = |cell: &str| { + let body = format!( + r#"{cell} +"# + ); + let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap(); + widgets(&form.pages[0]).iter().find(|w| w.name == "after").map(|w| w.rect.y).unwrap() + }; + let fields = r#""#; + // The table starts at 0.5in (36pt); its content is two 0.3in fields (43.2pt). + let flowed = after_y(&format!(r#"{fields}"#)); + assert!((flowed - (36.0 + 43.2)).abs() < 0.5, "flowed cell: the next object at {flowed}"); + let positioned = after_y(r#""#); + assert!((positioned - (36.0 + 360.0)).abs() < 0.5, "positioned cell keeps h: {positioned}"); + let outside = { + let body = format!( + r#"{fields}"# + ); + let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap(); + widgets(&form.pages[0]).iter().find(|w| w.name == "after").map(|w| w.rect.y).unwrap() + }; + assert!((outside - (36.0 + 360.0)).abs() < 0.5, "a flowed subform outside a table keeps h: {outside}"); +}