mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 13:24:35 +00:00
Merge #5369: M12: an XFA subformSet is transparent to layout, so its members sit where their container puts them (#5366)
# Conflicts: # crates/xfa/src/layout/measure.rs # crates/xfa/src/parse.rs # crates/xfa/src/tests.rs
This commit is contained in:
commit
9e55f61de0
5 files changed
+299
-31
No files matched your search
@@ -163,10 +163,33 @@ impl Layouter<'_> {
|
||||
self.laid_out_instances(k, &ctx.som);
|
||||
}
|
||||
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) {
|
||||
// The sibling indices of each `subformSet` the steps go into, found once per
|
||||
// set (known by where its children are); the subform's own are `sibs`.
|
||||
let mut set_sibs: std::collections::HashMap<usize, SomIndices> = std::collections::HashMap::new();
|
||||
for (at, r) in placed {
|
||||
// Down the steps: a member of a `subformSet` takes the set's SOM path.
|
||||
let (mut nodes, mut node_ctx, mut node_depth) = (s.children.as_slice(), ctx.clone(), depth + 1);
|
||||
let mut target = None;
|
||||
for (step, &(i, instance)) in at.iter().enumerate() {
|
||||
let Some(k) = nodes.get(i) else { break };
|
||||
let index = if step == 0 {
|
||||
sibs.at(i)
|
||||
} else {
|
||||
set_sibs.entry(nodes.as_ptr() as usize).or_insert_with(|| SomIndices::of(nodes)).at(i)
|
||||
};
|
||||
let sib = index + instance;
|
||||
match k {
|
||||
Node::Subform(set) if set.set && step + 1 < at.len() => {
|
||||
node_ctx = node_ctx.with_som(&set.common, sib);
|
||||
nodes = &set.children;
|
||||
node_depth += 1;
|
||||
}
|
||||
_ => target = Some((k, sib)),
|
||||
}
|
||||
}
|
||||
if let Some((k, sib)) = target {
|
||||
let x = if s.layout == Layout::RlTb { inner.right() - r.x - r.w } else { inner.x + r.x };
|
||||
self.draw_node(k, Rect::new(x, inner.y + r.y, r.w, r.h), &ctx, depth + 1, sibs.at(i) + instance)?;
|
||||
self.draw_node(k, Rect::new(x, inner.y + r.y, r.w, r.h), &node_ctx, node_depth, sib)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ use super::{
|
||||
Subform, Ui, Value, data_path, text,
|
||||
};
|
||||
|
||||
/// Where [`Layouter::pack_instances`] puts one object: the (child index, instance) steps to it, and
|
||||
/// its place in the packed container's box.
|
||||
pub(super) type Packed = (Vec<(usize, usize)>, Rect);
|
||||
|
||||
impl Layouter<'_> {
|
||||
/// The width and height instance `index` of `node` under `ctx` takes in `avail_w`: its own
|
||||
/// size, else what its content needs (containers are measured once per width, depth and
|
||||
@@ -370,36 +374,69 @@ impl Layouter<'_> {
|
||||
.fold(0.0, f64::max)
|
||||
}
|
||||
|
||||
/// Left-to-right packing: rows of children, wrapped at the available width, as (child,
|
||||
/// instance, place). A repeating subform is packed once per instance it has under the
|
||||
/// container at `som`.
|
||||
pub(super) fn pack_instances(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize, som: &str) -> (Vec<(usize, usize, Rect)>, f64) {
|
||||
/// Left-to-right packing: rows of children, wrapped at the available width, as (steps, place).
|
||||
/// The steps are (child index, instance) from the packed container down to the object: one
|
||||
/// step for a child, more for a member of a `subformSet`, which is transparent to layout (XFA
|
||||
/// 3.3 p. 357), so its members go on among the container's other children. A repeating
|
||||
/// subform is packed once per instance it has under the container at `som`.
|
||||
pub(super) fn pack_instances(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize, som: &str) -> (Vec<Packed>, f64) {
|
||||
let mut out = Vec::new();
|
||||
let mut line = (0.0, 0.0, 0.0);
|
||||
self.pack_into(children, inner_w, ctx, depth, som, &[], &mut line, &mut out);
|
||||
(out, line.1 + line.2)
|
||||
}
|
||||
|
||||
/// [`Self::pack_instances`] from `line` (x, y, the line's height so far), the steps to
|
||||
/// `children` being `path`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn pack_into(
|
||||
&self,
|
||||
children: &[Node],
|
||||
inner_w: f64,
|
||||
ctx: &Ctx,
|
||||
depth: usize,
|
||||
som: &str,
|
||||
path: &[(usize, usize)],
|
||||
line: &mut (f64, f64, f64),
|
||||
out: &mut Vec<Packed>,
|
||||
) {
|
||||
if depth > MAX_DEPTH {
|
||||
return;
|
||||
}
|
||||
let sibs = SomIndices::of(children);
|
||||
let (mut x, mut y, mut line_h) = (0.0, 0.0, 0.0);
|
||||
for (i, k) in children.iter().enumerate() {
|
||||
if !k.common().presence.occupies() {
|
||||
continue;
|
||||
}
|
||||
if let Node::Subform(s) = k
|
||||
&& s.set
|
||||
{
|
||||
for instance in 0..self.instances(k, som) {
|
||||
let set_ctx = ctx.with_som(&s.common, sibs.at(i).saturating_add(instance));
|
||||
let at: Vec<(usize, usize)> = path.iter().copied().chain([(i, instance)]).collect();
|
||||
self.pack_into(&s.children, inner_w, &set_ctx, depth + 1, &set_ctx.som, &at, line, out);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for instance in 0..self.instances(k, som) {
|
||||
let (w, h) = self.size_at(k, inner_w, ctx, depth + 1, sibs.at(i).saturating_add(instance));
|
||||
if x > 0.0 && x + w > inner_w + 0.05 {
|
||||
y += line_h;
|
||||
x = 0.0;
|
||||
line_h = 0.0;
|
||||
let (x, y, line_h) = line;
|
||||
if *x > 0.0 && *x + w > inner_w + 0.05 {
|
||||
*y += *line_h;
|
||||
*x = 0.0;
|
||||
*line_h = 0.0;
|
||||
}
|
||||
out.push((i, instance, Rect::new(x, y, w, h)));
|
||||
x += w;
|
||||
line_h = line_h.max(h);
|
||||
out.push((path.iter().copied().chain([(i, instance)]).collect(), Rect::new(*x, *y, w, h)));
|
||||
*x += w;
|
||||
*line_h = line_h.max(h);
|
||||
}
|
||||
}
|
||||
(out, y + line_h)
|
||||
}
|
||||
|
||||
/// [`Self::pack_instances`] for children that don't repeat (the fields of a group).
|
||||
pub(super) fn pack(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize) -> (Vec<(usize, Rect)>, f64) {
|
||||
let (placed, h) = self.pack_instances(children, inner_w, ctx, depth, "");
|
||||
(placed.into_iter().map(|(i, _, r)| (i, r)).collect(), h)
|
||||
(placed.into_iter().filter_map(|(at, r)| at.first().map(|&(i, _)| (i, r))).collect(), h)
|
||||
}
|
||||
|
||||
fn packed_height(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize) -> f64 {
|
||||
|
||||
@@ -521,6 +521,9 @@ pub struct Subform {
|
||||
/// `<breakBefore targetType="pageArea">`: start on a new page.
|
||||
pub break_before_page: bool,
|
||||
pub break_after_page: bool,
|
||||
/// Parsed from a `subformSet`: transparent to layout (XFA 3.3 p. 357), so in a flowing
|
||||
/// container its members are laid out among their parent's other children.
|
||||
pub set: bool,
|
||||
/// Tables: the width of each column.
|
||||
pub column_widths: Vec<f64>,
|
||||
/// `<overflow leader="…">`: the named child (a header row) is repeated after a page break.
|
||||
|
||||
+21
-14
@@ -916,7 +916,7 @@ fn parse_page_set(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> PageS
|
||||
if content.is_empty() {
|
||||
content.push(Rect::new(0.0, 0.0, width, height));
|
||||
}
|
||||
let items = pa.children().filter(|c| c.is_element()).filter_map(|c| parse_node(c, warnings, depth + 1)).collect();
|
||||
let items = pa.children().filter(|c| c.is_element()).filter_map(|c| parse_node(c, warnings, depth + 1, Layout::Positioned)).collect();
|
||||
// A page area with no `<occur>` has no limit and no minimum (a subform's default of one
|
||||
// would exhaust it, and demand a page for every master).
|
||||
let occur = child(pa, "occur").map(|o| parse_occur(Some(o)));
|
||||
@@ -933,16 +933,18 @@ fn parse_page_set(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> PageS
|
||||
}
|
||||
|
||||
/// A `subform` element: its own attributes, its page set if it has one, and the nodes it holds,
|
||||
/// `depth` levels down (for the nesting limit).
|
||||
fn parse_subform(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> Subform {
|
||||
/// `depth` levels down (for the nesting limit). `set_layout` is the layout a `subformSet` takes
|
||||
/// on from its container, in place of its own.
|
||||
fn parse_subform(n: XmlNode, warnings: &mut Vec<String>, depth: usize, set_layout: Option<Layout>) -> Subform {
|
||||
let common = parse_common(n, warnings);
|
||||
let layout = set_layout.unwrap_or_else(|| Layout::parse(n.attribute("layout")));
|
||||
let mut children_out = Vec::new();
|
||||
let mut page_set = None;
|
||||
for c in n.children().filter(|c| c.is_element()) {
|
||||
match c.tag_name().name() {
|
||||
"pageSet" => page_set = Some(parse_page_set(c, warnings, depth + 1)),
|
||||
_ => {
|
||||
if let Some(node) = parse_node(c, warnings, depth + 1) {
|
||||
if let Some(node) = parse_node(c, warnings, depth + 1, layout) {
|
||||
children_out.push(node);
|
||||
}
|
||||
}
|
||||
@@ -955,7 +957,7 @@ fn parse_subform(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> Subfor
|
||||
let break_to_page = |name: &str| children(n, name).any(|b| b.attribute("targetType").is_some_and(|t| t == "pageArea" || t == "contentArea"));
|
||||
Subform {
|
||||
common,
|
||||
layout: Layout::parse(n.attribute("layout")),
|
||||
layout,
|
||||
children: children_out,
|
||||
scripts: parse_events(n),
|
||||
page_set,
|
||||
@@ -963,6 +965,7 @@ fn parse_subform(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> Subfor
|
||||
break_before_page: break_to_page("breakBefore")
|
||||
|| child(n, "break").is_some_and(|b| b.attribute("before").is_some_and(|v| v == "pageArea" || v == "contentArea")),
|
||||
break_after_page: break_to_page("breakAfter"),
|
||||
set: false,
|
||||
column_widths,
|
||||
overflow_leader: child(n, "overflow").and_then(|o| o.attribute("leader")).map(str::to_string).filter(|s| !s.is_empty()),
|
||||
}
|
||||
@@ -970,22 +973,26 @@ fn parse_subform(n: XmlNode, warnings: &mut Vec<String>, depth: usize) -> Subfor
|
||||
|
||||
/// The template object element `c` stands for (a subform, area, field, draw or exclusion group,
|
||||
/// with what it holds), or `None` for anything else and past [`MAX_DEPTH`] levels. What can't be
|
||||
/// read faithfully is noted in `warnings`.
|
||||
fn parse_node(c: XmlNode, warnings: &mut Vec<String>, depth: usize) -> Option<Node> {
|
||||
/// read faithfully is noted in `warnings`. `parent`: the layout of the container `c` is in, which
|
||||
/// a `subformSet` takes on.
|
||||
fn parse_node(c: XmlNode, warnings: &mut Vec<String>, depth: usize, parent: Layout) -> Option<Node> {
|
||||
if depth > MAX_DEPTH {
|
||||
return None;
|
||||
}
|
||||
Some(match c.tag_name().name() {
|
||||
"subform" => Node::Subform(Box::new(parse_subform(c, warnings, depth))),
|
||||
"subform" => Node::Subform(Box::new(parse_subform(c, warnings, depth, None))),
|
||||
// A set of related subforms is transparent to layout (XFA 3.3 p. 357): its members are laid out
|
||||
// as its container lays out its own children, at their own positions in a positioned one. In a
|
||||
// table or a row they go one under another.
|
||||
"subformSet" => {
|
||||
// A set of alternative subforms: lay out its members in order.
|
||||
let mut sf = parse_subform(c, warnings, depth);
|
||||
sf.layout = Layout::Tb;
|
||||
Node::Subform(Box::new(sf))
|
||||
let layout = if matches!(parent, Layout::Table | Layout::Row) { Layout::Tb } else { parent };
|
||||
let mut set = parse_subform(c, warnings, depth, Some(layout));
|
||||
set.set = true;
|
||||
Node::Subform(Box::new(set))
|
||||
}
|
||||
"area" => Node::Area(Box::new(Area {
|
||||
common: parse_common(c, warnings),
|
||||
children: c.children().filter(|k| k.is_element()).filter_map(|k| parse_node(k, warnings, depth + 1)).collect(),
|
||||
children: c.children().filter(|k| k.is_element()).filter_map(|k| parse_node(k, warnings, depth + 1, Layout::Positioned)).collect(),
|
||||
})),
|
||||
"field" => Node::Field(Box::new(parse_field(c, warnings))),
|
||||
"draw" => Node::Draw(Box::new(Draw { common: parse_common(c, warnings), value: parse_value(child(c, "value"), warnings) })),
|
||||
@@ -1271,7 +1278,7 @@ pub fn parse(xml: &str) -> Result<(Template, Vec<String>), XfaError> {
|
||||
None => template,
|
||||
};
|
||||
let sf = children(template, "subform").next().ok_or_else(|| XfaError::Malformed("the template has no root subform".into()))?;
|
||||
let mut root_sf = parse_subform(sf, &mut warnings, 0);
|
||||
let mut root_sf = parse_subform(sf, &mut warnings, 0, None);
|
||||
if root_sf.page_set.is_none() {
|
||||
// A page set may sit beside the root subform.
|
||||
root_sf.page_set = child(template, "pageSet").map(|ps| parse_page_set(ps, &mut warnings, 0));
|
||||
|
||||
@@ -5405,6 +5405,204 @@ fn page_area_uses_follow_hidden_masters_and_shrink_with_multiple_areas() {
|
||||
assert!(!uses.contains_key("master"), "removed pages leave no stale occurrence metadata: {uses:?}");
|
||||
}
|
||||
|
||||
/// A `subformSet` is transparent to layout (XFA 3.3 p. 357): in a positioned subform its members
|
||||
/// sit at their own positions, side by side as a public stamp-duty form's two columns do, not one
|
||||
/// under the other; in a flowed one they still go one under another.
|
||||
#[test]
|
||||
fn a_subform_set_in_a_positioned_subform_places_its_members_where_they_are() {
|
||||
let xml = |layout: &str| {
|
||||
format!(
|
||||
r#"<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform name="form" layout="tb">
|
||||
<pageSet><pageArea name="p"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
|
||||
<subform name="box" w="7in" {layout}>
|
||||
<subformSet name="cols">
|
||||
<subform name="left" w="3in" h="2in"><draw name="a" x="0in" y="0.5in" w="2in" h="0.3in"><value><text>left</text></value></draw></subform>
|
||||
<subform name="right" w="3in" h="2in"><draw name="b" x="4in" y="0.5in" w="2in" h="0.3in"><value><text>right</text></value></draw></subform>
|
||||
</subformSet>
|
||||
</subform>
|
||||
</subform></template>"#
|
||||
)
|
||||
};
|
||||
let baseline = |layout: &str, text: &str| {
|
||||
let form = crate::layout::layout(&crate::parse(&xml(layout)).unwrap().0, None).unwrap();
|
||||
form.pages[0].items.iter().find_map(|i| if let Item::Text(s) = i { (s.text == text).then_some(s.baseline) } else { None }).unwrap()
|
||||
};
|
||||
assert!((baseline(r#"h="3in""#, "left") - baseline(r#"h="3in""#, "right")).abs() < 0.01, "positioned: side by side");
|
||||
assert!(baseline(r#"layout="tb""#, "right") - baseline(r#"layout="tb""#, "left") > 140.0, "flowed: one under the other");
|
||||
}
|
||||
|
||||
/// In a flowing (`lr-tb`) subform a `subformSet`'s members go on among their parent's other
|
||||
/// children (XFA 3.3 p. 357): beside a 1in field in a 2in row, the first 0.6in member takes the
|
||||
/// rest of that line and the second wraps, rather than the set moving to the next line whole.
|
||||
/// The members keep the set in their SOM paths.
|
||||
#[test]
|
||||
fn a_subform_set_in_an_lr_tb_subform_flows_its_members_with_their_siblings() {
|
||||
let body = r#"<subform name="row" layout="lr-tb" w="2in">
|
||||
<field name="a" w="1in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
<subformSet name="grp" relation="ordered">
|
||||
<subform name="m1" w="0.6in" h="0.3in"><field name="x" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
<subform name="m2" w="0.6in" h="0.3in"><field name="y" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
</subform>"#;
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap();
|
||||
let w = widgets(&form.pages[0]);
|
||||
let at = |name: &str| w.iter().find(|w| w.name == name).map(|w| (w.som.clone(), w.rect.x, w.rect.y)).unwrap();
|
||||
let (_, ax, ay) = at("a");
|
||||
let (xsom, xx, xy) = at("x");
|
||||
let (ysom, yx, yy) = at("y");
|
||||
assert!((xy - ay).abs() < 0.5 && (xx - (ax + 72.0)).abs() < 0.5, "the first member goes on beside a: a at ({ax}, {ay}), x at ({xx}, {xy})");
|
||||
assert!(yy > ay + 1.0 && (yx - ax).abs() < 0.5, "the second wraps to the next line: y at ({yx}, {yy})");
|
||||
assert!(xsom.ends_with("row[0].grp[0].m1[0].x[0]") && ysom.ends_with("row[0].grp[0].m2[0].y[0]"), "{xsom} {ysom}");
|
||||
}
|
||||
|
||||
/// In a table, and in one of its rows, a `subformSet`'s members still go one under another: the
|
||||
/// set lays them out top to bottom there rather than taking the table's or the row's layout.
|
||||
#[test]
|
||||
fn a_subform_set_in_a_table_or_a_row_puts_its_members_one_under_another() {
|
||||
let set = |name: &str| {
|
||||
format!(
|
||||
r#"<subformSet name="grp">
|
||||
<subform name="m1" w="2in" h="0.3in"><field name="{name}1" w="2in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
<subform name="m2" w="2in" h="0.3in"><field name="{name}2" w="2in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>"#
|
||||
)
|
||||
};
|
||||
let body = format!(
|
||||
r#"<subform name="t" layout="table" columnWidths="2in 2in">
|
||||
{}
|
||||
<subform name="r" layout="row"><field name="c" w="2in" h="0.3in"><ui><textEdit/></ui></field>{}</subform>
|
||||
</subform>"#,
|
||||
set("s"),
|
||||
set("q")
|
||||
);
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap();
|
||||
let w = widgets(&form.pages[0]);
|
||||
let at = |name: &str| w.iter().find(|w| w.name == name).map(|w| (w.rect.x, w.rect.y)).unwrap();
|
||||
for (first, second) in [("s1", "s2"), ("q1", "q2")] {
|
||||
let ((x1, y1), (x2, y2)) = (at(first), at(second));
|
||||
assert!((x1 - x2).abs() < 0.5 && y2 > y1 + 20.0, "{second} under {first}: ({x1}, {y1}) then ({x2}, {y2})");
|
||||
}
|
||||
}
|
||||
|
||||
/// The SOM path and place of each widget of the template's field `name`, in layout order, found
|
||||
/// by its SOM path (a repeated field's PDF name is made up: `x_2`).
|
||||
fn widgets_named(form: &Form, name: &str) -> Vec<(String, f64, f64)> {
|
||||
let tail = format!(".{name}[0]");
|
||||
widgets(&form.pages[0]).iter().filter(|w| w.som.ends_with(&tail)).map(|w| (w.som.clone(), w.rect.x, w.rect.y)).collect()
|
||||
}
|
||||
|
||||
/// #5369 review: a `subformSet` inside a `subformSet` is transparent too: in an `lr-tb` row the
|
||||
/// inner set's member goes on beside the field before the outer set, the outer set's own member
|
||||
/// wraps, and the field after the sets follows it. Each member keeps both sets in its SOM path.
|
||||
#[test]
|
||||
fn nested_subform_sets_in_an_lr_tb_subform_flow_their_members_with_their_siblings() {
|
||||
let body = r#"<subform name="row" layout="lr-tb" w="2in">
|
||||
<field name="a" w="1in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
<subformSet name="outer">
|
||||
<subformSet name="inner">
|
||||
<subform name="m1" w="0.6in" h="0.3in"><field name="x" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
<subform name="m2" w="0.6in" h="0.3in"><field name="y" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
<field name="b" w="0.6in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
</subform>"#;
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap();
|
||||
let at = |name: &str| widgets_named(&form, name).into_iter().next().unwrap();
|
||||
let ((_, ax, ay), (xsom, xx, xy), (ysom, yx, yy), (_, bx, by)) = (at("a"), at("x"), at("y"), at("b"));
|
||||
assert!((xy - ay).abs() < 0.5 && (xx - (ax + 72.0)).abs() < 0.5, "x goes on beside a: a ({ax}, {ay}), x ({xx}, {xy})");
|
||||
assert!(yy > ay + 1.0 && (yx - ax).abs() < 0.5, "y wraps: ({yx}, {yy})");
|
||||
assert!((by - yy).abs() < 0.5 && (bx - (yx + 43.2)).abs() < 0.5, "b follows y: ({bx}, {by})");
|
||||
assert!(xsom.ends_with("row[0].outer[0].inner[0].m1[0].x[0]") && ysom.ends_with("row[0].outer[0].m2[0].y[0]"), "{xsom} {ysom}");
|
||||
}
|
||||
|
||||
/// #5369 review: in a positioned subform, the members of a `subformSet` nested in another sit at
|
||||
/// their own positions, side by side, as the members of one set do.
|
||||
#[test]
|
||||
fn nested_subform_sets_in_a_positioned_subform_place_their_members_where_they_are() {
|
||||
let body = r#"<subform name="box" w="7in" h="3in">
|
||||
<subformSet name="outer">
|
||||
<subformSet name="inner">
|
||||
<subform name="l" x="0in" y="0.5in" w="3in" h="1in"><field name="p" w="1in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
<subform name="r" x="4in" y="0.5in" w="3in" h="1in"><field name="q" w="1in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
</subform>"#;
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap();
|
||||
let at = |name: &str| widgets_named(&form, name).into_iter().next().unwrap();
|
||||
let ((psom, px, py), (_, qx, qy)) = (at("p"), at("q"));
|
||||
assert!((py - qy).abs() < 0.5 && (qx - px - 288.0).abs() < 0.5, "side by side, 4in apart: p ({px}, {py}), q ({qx}, {qy})");
|
||||
assert!(psom.ends_with("box[0].outer[0].inner[0].l[0].p[0]"), "{psom}");
|
||||
}
|
||||
|
||||
/// #5369 review: an empty `subformSet`, and one holding only an empty one, takes no room: in an
|
||||
/// `lr-tb` row the field after it goes on beside the field before it, and in a positioned or a
|
||||
/// `tb` subform the layout goes on as if it weren't there.
|
||||
#[test]
|
||||
fn an_empty_subform_set_takes_no_room() {
|
||||
for (attrs, after_a) in [(r#"layout="lr-tb" w="2in""#, (72.0, 0.0)), (r#"w="7in" h="2in""#, (0.0, 0.0)), (r#"layout="tb" w="7in""#, (0.0, 21.6))]
|
||||
{
|
||||
let body = format!(
|
||||
r#"<subform name="row" {attrs}>
|
||||
<field name="a" w="1in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
<subformSet name="none"/>
|
||||
<subformSet name="outer"><subformSet name="inner"/></subformSet>
|
||||
<field name="b" w="0.6in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
</subform>"#
|
||||
);
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap();
|
||||
let at = |name: &str| widgets_named(&form, name).into_iter().next().unwrap();
|
||||
let ((_, ax, ay), (_, bx, by)) = (at("a"), at("b"));
|
||||
assert!((bx - ax - after_a.0).abs() < 0.5 && (by - ay - after_a.1).abs() < 0.5, "{attrs}: a ({ax}, {ay}), b ({bx}, {by})");
|
||||
}
|
||||
}
|
||||
|
||||
/// #5369 review: a repeating member of a `subformSet` flows instance by instance among its
|
||||
/// parent's other children in an `lr-tb` row, as a repeating child of the row would: the first
|
||||
/// instance beside the field before the set, the others wrapping, each with its own index.
|
||||
#[test]
|
||||
fn a_repeating_member_of_a_subform_set_flows_instance_by_instance() {
|
||||
let body = r#"<subform name="row" layout="lr-tb" w="2in">
|
||||
<field name="a" w="1in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
<subformSet name="grp">
|
||||
<subform name="m" w="0.6in" h="0.3in"><occur initial="3" max="5"/><field name="x" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
</subform>"#;
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap();
|
||||
let (_, ax, ay) = widgets_named(&form, "a").into_iter().next().unwrap();
|
||||
let x = widgets_named(&form, "x");
|
||||
assert_eq!(x.len(), 3, "{x:?}");
|
||||
let places: Vec<(f64, f64)> = x.iter().map(|(_, px, py)| (px - ax, py - ay)).collect();
|
||||
for ((got_x, got_y), (want_x, want_y)) in places.iter().zip([(72.0, 0.0), (0.0, 21.6), (43.2, 21.6)]) {
|
||||
assert!((got_x - want_x).abs() < 0.5 && (got_y - want_y).abs() < 0.5, "{places:?}");
|
||||
}
|
||||
for (i, (som, _, _)) in x.iter().enumerate() {
|
||||
assert!(som.ends_with(&format!("row[0].grp[0].m[{i}].x[0]")), "{som}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #5369 review: a repeating `subformSet` lays its members out once per instance, each instance
|
||||
/// flowing on from the last in an `lr-tb` row, and each member taking its set's instance index.
|
||||
#[test]
|
||||
fn a_repeating_subform_set_flows_each_instance_of_its_members() {
|
||||
let body = r#"<subform name="row" layout="lr-tb" w="2in">
|
||||
<field name="a" w="1in" h="0.3in"><ui><textEdit/></ui></field>
|
||||
<subformSet name="grp"><occur initial="2" max="3"/>
|
||||
<subform name="m" w="0.6in" h="0.3in"><field name="x" w="0.6in" h="0.3in"><ui><textEdit/></ui></field></subform>
|
||||
</subformSet>
|
||||
</subform>"#;
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, body)).unwrap();
|
||||
let (_, ax, ay) = widgets_named(&form, "a").into_iter().next().unwrap();
|
||||
let x = widgets_named(&form, "x");
|
||||
assert_eq!(x.len(), 2, "{x:?}");
|
||||
let places: Vec<(f64, f64)> = x.iter().map(|(_, px, py)| (px - ax, py - ay)).collect();
|
||||
for ((got_x, got_y), (want_x, want_y)) in places.iter().zip([(72.0, 0.0), (0.0, 21.6)]) {
|
||||
assert!((got_x - want_x).abs() < 0.5 && (got_y - want_y).abs() < 0.5, "{places:?}");
|
||||
}
|
||||
for (i, (som, _, _)) in x.iter().enumerate() {
|
||||
assert!(som.ends_with(&format!("row[0].grp[{i}].m[0].x[0]")), "{som}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The pages of `form[0].last[0].f[0]` and of the field `after` that follows `last`, when `last`
|
||||
/// holds `before` ahead of `f` and `after` behind it. A breakAfter acts after its subform, so the
|
||||
/// page of `after` shows whether it broke.
|
||||
|
||||
Reference in new issue
Block a user