use std::rc::Rc;
use xfa_layout_engine::form::{FormNode, FormNodeId, FormNodeType, FormTree, Occur};
use xfa_layout_engine::layout::LayoutEngine;
use xfa_layout_engine::text::FontMetrics;
use xfa_layout_engine::trace::{with_sink, RecordingSink};
use xfa_layout_engine::types::{BoxModel, LayoutStrategy};
fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
tree.add_node(FormNode {
name: name.to_string(),
node_type: FormNodeType::Field {
value: name.to_string(),
},
box_model: BoxModel {
width: Some(w),
height: Some(h),
max_width: f64::MAX,
max_height: f64::MAX,
..Default::default()
},
layout: LayoutStrategy::Positioned,
children: vec![],
occur: Occur::once(),
font: FontMetrics::default(),
calculate: None,
validate: None,
column_widths: vec![],
col_span: 1,
})
}
fn make_subform(
tree: &mut FormTree,
name: &str,
strategy: LayoutStrategy,
w: Option<f64>,
h: Option<f64>,
children: Vec<FormNodeId>,
) -> FormNodeId {
tree.add_node(FormNode {
name: name.to_string(),
node_type: FormNodeType::Subform,
box_model: BoxModel {
width: w,
height: h,
max_width: f64::MAX,
max_height: f64::MAX,
..Default::default()
},
layout: strategy,
children,
occur: Occur::once(),
font: FontMetrics::default(),
calculate: None,
validate: None,
column_widths: vec![],
col_span: 1,
})
}
fn make_root(
tree: &mut FormTree,
width: f64,
height: f64,
children: Vec<FormNodeId>,
) -> FormNodeId {
tree.add_node(FormNode {
name: "Root".to_string(),
node_type: FormNodeType::Root,
box_model: BoxModel {
width: Some(width),
height: Some(height),
max_width: f64::MAX,
max_height: f64::MAX,
..Default::default()
},
layout: LayoutStrategy::TopToBottom,
children,
occur: Occur::once(),
font: FontMetrics::default(),
calculate: None,
validate: None,
column_widths: vec![],
col_span: 1,
})
}
#[test]
fn paginate_defer_anchor_fires_on_overflow() {
let mut tree = FormTree::new();
let row1 = make_field(&mut tree, "Row1", 200.0, 30.0);
let row2 = make_field(&mut tree, "Row2", 200.0, 30.0);
let tall = make_field(&mut tree, "Tall", 200.0, 80.0);
let group = make_subform(
&mut tree,
"Group",
LayoutStrategy::TopToBottom,
Some(200.0),
None,
vec![row1, row2, tall],
);
let root = make_root(&mut tree, 200.0, 100.0, vec![group]);
let engine = LayoutEngine::new(&tree);
let sink: Rc<RecordingSink> = Rc::new(RecordingSink::new());
let result =
with_sink(sink.clone(), || engine.layout(root)).expect("layout should produce some result");
let events = sink.events();
let defer_fires = events
.iter()
.filter(|e| {
e.phase.tag() == "paginate" && e.reason.tag() == "paginate_defer_to_next_page_min_h"
})
.count();
assert!(
defer_fires >= 1,
"expected paginate_defer_to_next_page_min_h to fire on overflow; events={events:?}"
);
assert!(
!result.pages.is_empty(),
"layout should still produce pages with trace anchors wired in"
);
}
#[test]
fn paginate_trace_silent_when_no_sink_installed() {
let mut tree = FormTree::new();
let row1 = make_field(&mut tree, "Row1", 200.0, 30.0);
let tall = make_field(&mut tree, "Tall", 200.0, 80.0);
let group = make_subform(
&mut tree,
"Group",
LayoutStrategy::TopToBottom,
Some(200.0),
None,
vec![row1, tall],
);
let root = make_root(&mut tree, 200.0, 100.0, vec![group]);
let engine = LayoutEngine::new(&tree);
let result = engine
.layout(root)
.expect("layout should run without a sink");
assert!(!result.pages.is_empty());
}