1use crate::error::Result;
31use crate::form::{
32 ContentArea, DrawContent, FieldKind, FormNode, FormNodeId, FormNodeMeta, FormNodeType, FormTree,
33};
34use crate::text::{self, FontFamily};
35use crate::trace::{sites as trace_sites, Reason as TraceReason};
36use crate::types::{LayoutStrategy, Rect, Size, TextAlign};
37use std::sync::{Mutex, OnceLock};
38
39fn resolve_display_value<'a>(value: &'a str, meta: &'a FormNodeMeta) -> std::borrow::Cow<'a, str> {
49 if value.is_empty() {
50 return std::borrow::Cow::Borrowed(value);
51 }
52 if meta.field_kind == FieldKind::Dropdown {
54 if !meta.save_items.is_empty() {
55 if let Some(idx) = meta.save_items.iter().position(|s| s == value) {
56 if let Some(display) = meta.display_items.get(idx) {
57 return std::borrow::Cow::Borrowed(display.as_str());
58 }
59 }
60 }
61 return std::borrow::Cow::Borrowed(value);
62 }
63 if meta.field_kind == FieldKind::NumericEdit {
65 if let Ok(num) = value.parse::<f64>() {
66 let formatted = format!("{}", num);
68 return std::borrow::Cow::Owned(formatted);
69 }
70 }
71 if meta.field_kind == FieldKind::DateTimePicker {
72 if let Some(date) = extract_iso_date_prefix(value) {
73 return std::borrow::Cow::Owned(date.to_string());
74 }
75 }
76 std::borrow::Cow::Borrowed(value)
77}
78
79fn extract_iso_date_prefix(value: &str) -> Option<&str> {
80 let prefix = value.get(0..10)?;
81 let bytes = prefix.as_bytes();
82 if bytes.len() != 10
83 || !bytes[0..4].iter().all(u8::is_ascii_digit)
84 || bytes[4] != b'-'
85 || !bytes[5..7].iter().all(u8::is_ascii_digit)
86 || bytes[7] != b'-'
87 || !bytes[8..10].iter().all(u8::is_ascii_digit)
88 {
89 return None;
90 }
91 Some(prefix)
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub struct LayoutNodeId(pub usize);
97
98#[derive(Debug)]
100pub struct LayoutDom {
101 pub pages: Vec<LayoutPage>,
103}
104
105#[derive(Debug, Clone, Default)]
107pub struct LayoutProfile {
108 pub pages: Vec<LayoutProfilePage>,
110}
111
112#[derive(Debug, Clone)]
114pub struct LayoutProfilePage {
115 pub page_height: f64,
117 pub used_height: f64,
119 pub overflow_to_next: bool,
121 pub first_overflow_element: Option<String>,
123}
124
125impl LayoutDom {
126 pub fn estimated_heap_bytes(&self) -> usize {
136 fn node_bytes(n: &LayoutNode) -> usize {
137 let mut total = n.name.len();
139 total += n.children.capacity() * std::mem::size_of::<LayoutNode>();
141 for child in &n.children {
142 total += node_bytes(child);
143 }
144 for s in &n.display_items {
146 total += s.len();
147 }
148 for s in &n.save_items {
149 total += s.len();
150 }
151 total += match &n.content {
153 LayoutContent::None => 0,
154 LayoutContent::Text(t) => t.len(),
155 LayoutContent::Field { value, .. } => value.len(),
156 LayoutContent::WrappedText { lines, .. } => {
157 lines.iter().map(|l| l.len()).sum::<usize>()
158 }
159 LayoutContent::Image { data, mime_type } => data.len() + mime_type.len(),
160 LayoutContent::Draw(_) => 0,
161 };
162 total
163 }
164
165 let mut total = self.pages.capacity() * std::mem::size_of::<LayoutPage>();
166 for page in &self.pages {
167 total += page.nodes.capacity() * std::mem::size_of::<LayoutNode>();
168 for node in &page.nodes {
169 total += node_bytes(node);
170 }
171 }
172 total
173 }
174}
175
176const MAX_PAGES: usize = 500;
184
185#[derive(Debug)]
187pub struct LayoutPage {
188 pub width: f64,
190 pub height: f64,
192 pub nodes: Vec<LayoutNode>,
194}
195
196#[derive(Debug, Clone)]
198pub struct LayoutNode {
199 pub form_node: FormNodeId,
201 pub rect: Rect,
203 pub name: String,
205 pub content: LayoutContent,
207 pub children: Vec<LayoutNode>,
209 pub style: crate::form::FormNodeStyle,
211 pub display_items: Vec<String>,
213 pub save_items: Vec<String>,
215}
216
217#[derive(Debug, Clone)]
219pub enum LayoutContent {
220 None,
222 Text(String),
224 Field {
226 value: String,
228 field_kind: crate::form::FieldKind,
230 font_size: f64,
232 font_family: FontFamily,
234 },
235 WrappedText {
237 lines: Vec<String>,
239 first_line_of_para: Vec<bool>,
241 font_size: f64,
243 text_align: TextAlign,
245 font_family: FontFamily,
247 space_above_pt: Option<f64>,
249 space_below_pt: Option<f64>,
251 from_field: bool,
253 },
254 Image {
256 data: Vec<u8>,
258 mime_type: String,
260 },
261 Draw(DrawContent),
263}
264
265#[derive(Debug, Clone)]
267struct QueuedNode {
268 id: FormNodeId,
269 break_before: bool,
270 break_after: bool,
271 #[allow(dead_code)]
272 break_target: Option<String>,
273 children_override: Option<Vec<FormNodeId>>,
275 text_lines_override: Option<Vec<String>>,
277 nested_child_overrides: Option<Vec<(FormNodeId, Vec<FormNodeId>)>>,
282}
283
284type FittingResult = (
285 LayoutPage,
286 Vec<QueuedNode>,
287 bool,
288 Option<String>,
289 Option<LayoutProfilePage>,
290);
291
292#[derive(Debug, Default)]
293struct GroundTruthTraceState {
294 last_break_before_read: Option<String>,
295}
296
297static GROUNDTRUTH_TRACE_STATE: OnceLock<Mutex<GroundTruthTraceState>> = OnceLock::new();
298
299fn groundtruth_trace_enabled() -> bool {
300 std::env::var("XFA_TRACE_DOC")
301 .map(|value| !value.is_empty())
302 .unwrap_or(false)
303}
304
305fn groundtruth_trace_state() -> &'static Mutex<GroundTruthTraceState> {
306 GROUNDTRUTH_TRACE_STATE.get_or_init(|| Mutex::new(GroundTruthTraceState::default()))
307}
308
309pub struct LayoutEngine<'a> {
311 form: &'a FormTree,
312}
313
314impl<'a> LayoutEngine<'a> {
315 pub fn new(form: &'a FormTree) -> Self {
317 Self { form }
318 }
319
320 fn trace_node_id(&self, id: FormNodeId) -> String {
321 let node = self.form.get(id);
322 self.form
323 .meta(id)
324 .xfa_id
325 .clone()
326 .filter(|value| !value.is_empty())
327 .or_else(|| (!node.name.is_empty()).then(|| node.name.clone()))
328 .unwrap_or_else(|| id.0.to_string())
329 }
330
331 fn trace_node_name(&self, id: FormNodeId) -> String {
332 let node = self.form.get(id);
333 if !node.name.is_empty() {
334 node.name.clone()
335 } else {
336 self.trace_node_id(id)
337 }
338 }
339
340 fn trace_parent_id(&self, child_id: FormNodeId) -> Option<FormNodeId> {
341 self.form
342 .nodes
343 .iter()
344 .enumerate()
345 .find_map(|(idx, node)| node.children.contains(&child_id).then_some(FormNodeId(idx)))
346 }
347
348 fn trace_path_segment(&self, id: FormNodeId) -> String {
349 let node = self.form.get(id);
350 format!(
351 "{}#{}",
352 Self::form_node_type_name(&node.node_type),
353 self.trace_node_id(id)
354 )
355 }
356
357 fn trace_node_path(&self, id: FormNodeId) -> String {
358 let mut chain = vec![id];
359 let mut cursor = id;
360 while let Some(parent_id) = self.trace_parent_id(cursor) {
361 chain.push(parent_id);
362 cursor = parent_id;
363 }
364 chain.reverse();
365 chain
366 .into_iter()
367 .map(|node_id| self.trace_path_segment(node_id))
368 .collect::<Vec<_>>()
369 .join("/")
370 }
371
372 #[track_caller]
373 fn trace_vertical_state(
374 &self,
375 function: &'static str,
376 current_y: Option<f64>,
377 remaining_space: Option<f64>,
378 node_id: Option<FormNodeId>,
379 message: impl AsRef<str>,
380 ) {
381 if !groundtruth_trace_enabled() {
382 return;
383 }
384
385 let location = std::panic::Location::caller();
386 let (triggering_id, node_type, node_name) = if let Some(node_id) = node_id {
387 let node = self.form.get(node_id);
388 (
389 self.trace_node_id(node_id),
390 Self::form_node_type_name(&node.node_type).to_string(),
391 self.trace_node_name(node_id),
392 )
393 } else {
394 ("-".to_string(), "-".to_string(), "-".to_string())
395 };
396
397 eprintln!(
398 "{}:{} fn={} current_y={} remaining_space={} triggering_element_id={} node_type={} node_name={} {}",
399 location.file(),
400 location.line(),
401 function,
402 current_y
403 .map(|value| format!("{value:.3}"))
404 .unwrap_or_else(|| "-".to_string()),
405 remaining_space
406 .map(|value| format!("{value:.3}"))
407 .unwrap_or_else(|| "-".to_string()),
408 triggering_id,
409 node_type,
410 node_name,
411 message.as_ref(),
412 );
413 }
414
415 #[track_caller]
416 fn trace_break_before_read(
417 &self,
418 current_y: f64,
419 remaining_space: f64,
420 node_id: FormNodeId,
421 break_before: bool,
422 placed_count: usize,
423 header_node_count: usize,
424 ) {
425 if !groundtruth_trace_enabled() {
426 return;
427 }
428
429 let location = std::panic::Location::caller();
430 let preceding = format!(
431 "{}:{} break_before={} placed_count={} header_node_count={}",
432 location.file(),
433 location.line(),
434 break_before,
435 placed_count,
436 header_node_count
437 );
438 if let Ok(mut state) = groundtruth_trace_state().lock() {
439 state.last_break_before_read = Some(preceding);
440 }
441 self.trace_vertical_state(
442 "layout_content_fitting",
443 Some(current_y),
444 Some(remaining_space),
445 Some(node_id),
446 format!(
447 "break_before check break_before={} placed_count={} header_node_count={} event_type=read",
448 break_before,
449 placed_count,
450 header_node_count
451 ),
452 );
453 }
454
455 #[track_caller]
456 fn trace_commit_page_boundary(
457 &self,
458 current_page: usize,
459 used_height: Option<f64>,
460 page_height: Option<f64>,
461 next_item: Option<&QueuedNode>,
462 ) {
463 if !groundtruth_trace_enabled() {
464 return;
465 }
466
467 let remaining_space = match (used_height, page_height) {
468 (Some(used), Some(total)) => Some(total - used),
469 _ => None,
470 };
471 let preceding_break_read = groundtruth_trace_state()
472 .lock()
473 .ok()
474 .and_then(|state| state.last_break_before_read.clone())
475 .unwrap_or_else(|| "none".to_string());
476 let break_before = next_item.map(|queued| queued.break_before).unwrap_or(false);
477 let subform_path = next_item
478 .map(|queued| self.trace_node_path(queued.id))
479 .unwrap_or_else(|| "none".to_string());
480
481 self.trace_vertical_state(
482 "layout_internal",
483 used_height,
484 remaining_space,
485 next_item.map(|queued| queued.id),
486 format!(
487 "event_type=page_commit COMMIT page_boundary page={} -> page={} break_before={} preceding_break_read=\"{}\" subform_path={}",
488 current_page,
489 current_page + 1,
490 break_before,
491 preceding_break_read,
492 subform_path
493 ),
494 );
495 }
496
497 pub fn layout_with_profile(&self, root: FormNodeId) -> Result<(LayoutDom, LayoutProfile)> {
499 let mut profile = LayoutProfile::default();
500 let dom = self.layout_internal(root, Some(&mut profile))?;
501 Ok((dom, profile))
502 }
503
504 pub fn layout(&self, root: FormNodeId) -> Result<LayoutDom> {
518 self.layout_internal(root, None)
519 }
520
521 fn layout_internal(
522 &self,
523 root: FormNodeId,
524 mut profile: Option<&mut LayoutProfile>,
525 ) -> Result<LayoutDom> {
526 self.trace_vertical_state(
527 "layout_internal",
528 Some(0.0),
529 None,
530 Some(root),
531 format!("enter collect_profile={}", profile.is_some()),
532 );
533 let root_node = self.form.get(root);
534 let collect_profile = profile.is_some();
535
536 let (page_areas, raw_content_nodes) = self.extract_page_structure(root_node)?;
537 self.trace_vertical_state(
538 "layout_internal",
539 Some(0.0),
540 None,
541 Some(root),
542 format!(
543 "page structure extracted page_areas={} raw_content_nodes={} event_type=runtime_allocation",
544 page_areas.len(),
545 raw_content_nodes.len()
546 ),
547 );
548 let content_queued = self.queue_content(&raw_content_nodes);
550
551 let mut pages = Vec::new();
552
553 if page_areas.is_empty() {
554 let page_w = root_node.box_model.width.unwrap_or(612.0);
556 let page_h = root_node.box_model.height.unwrap_or(792.0);
557 let area = ContentArea {
558 name: String::new(),
559 x: 0.0,
560 y: 0.0,
561 width: page_w,
562 height: page_h,
563 leader: None,
564 trailer: None,
565 };
566
567 if root_node.layout == LayoutStrategy::TopToBottom {
568 let mut remaining = content_queued;
570 let page_limit = self.estimate_page_limit(&remaining, page_h);
571 while !remaining.is_empty() {
572 if pages.len() >= page_limit {
573 eprintln!(
574 "WARNING: Page limit ({}) reached, truncating layout for {}",
575 page_limit, root_node.name
576 );
577 break;
578 }
579 let (page, rest, consumed_break_only, _, page_profile) = self
580 .layout_content_fitting(
581 &area,
582 &remaining,
583 page_w,
584 page_h,
585 collect_profile,
586 )?;
587 if page.nodes.is_empty() && !consumed_break_only {
588 let forced = self.layout_content_on_page(
590 &area,
591 page_w,
592 page_h,
593 &[remaining[0].id],
594 root_node.layout,
595 )?;
596 let next_remaining = remaining[1..].to_vec();
597 log::debug!(
598 "XFA layout: processing page {}/{} (forced)",
599 pages.len() + 1,
600 page_limit
601 );
602 if let Some(profile) = profile.as_deref_mut() {
603 profile.pages.push(
604 self.profile_page_from_nodes(
605 &forced,
606 area.y,
607 area.height,
608 !next_remaining.is_empty(),
609 next_remaining
610 .first()
611 .map(|qn| self.describe_queued_node(qn)),
612 ),
613 );
614 }
615 pages.push(forced);
616 remaining = next_remaining;
617 } else if consumed_break_only {
618 remaining = rest;
620 } else {
621 log::debug!(
622 "XFA layout: processing page {}/{}",
623 pages.len() + 1,
624 page_limit
625 );
626 if !rest.is_empty() {
627 self.trace_commit_page_boundary(
628 pages.len() + 1,
629 page_profile.as_ref().map(|profile| profile.used_height),
630 page_profile.as_ref().map(|profile| profile.page_height),
631 rest.first(),
632 );
633 }
634 if let (Some(profile), Some(page_profile)) =
635 (profile.as_deref_mut(), page_profile)
636 {
637 profile.pages.push(page_profile);
638 }
639 pages.push(page);
640 remaining = rest;
641 }
642 }
643 } else {
644 let page = self.layout_content_on_page(
646 &area,
647 page_w,
648 page_h,
649 &raw_content_nodes,
650 root_node.layout,
651 )?;
652 if let Some(profile) = profile.as_deref_mut() {
653 profile.pages.push(self.profile_page_from_nodes(
654 &page,
655 area.y,
656 area.height,
657 false,
658 None,
659 ));
660 }
661 pages.push(page);
662 }
663 } else {
664 let multi_positioned = content_queued.len() > 1
677 && content_queued.iter().all(|qn| {
678 let node = self.form.get(qn.id);
679 node.layout == LayoutStrategy::Positioned
680 && matches!(
681 node.node_type,
682 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
683 )
684 && !qn.break_before
685 })
686 && {
687 let pa = &page_areas[0];
688 let ca = primary_content_area(pa);
689 let half_page = ca.height * 0.5;
690 content_queued
691 .iter()
692 .all(|qn| self.compute_extent(qn.id).height <= half_page)
693 };
694
695 let single_positioned_delegate = content_queued.len() == 1 && {
703 let qn = &content_queued[0];
704 let node = self.form.get(qn.id);
705 if node.layout == LayoutStrategy::Positioned
706 && matches!(
707 node.node_type,
708 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
709 )
710 && !qn.break_before
711 && !node.children.is_empty()
712 && node.children.iter().all(|&cid| {
713 let child = self.form.get(cid);
714 child.layout == LayoutStrategy::Positioned
715 })
716 {
717 let pa = &page_areas[0];
719 let ca = primary_content_area(pa);
720 let child_extent = self.compute_extent(qn.id);
721 child_extent.height <= ca.height
722 } else {
723 false
724 }
725 };
726
727 let all_content_positioned = multi_positioned || single_positioned_delegate;
728
729 if all_content_positioned {
730 let pa = &page_areas[0];
731 let ca = primary_content_area(pa);
732 let ids: Vec<FormNodeId> = content_queued.iter().map(|qn| qn.id).collect();
733 let mut page = self.layout_content_on_page(
734 ca,
735 pa.page_width,
736 pa.page_height,
737 &ids,
738 LayoutStrategy::Positioned,
739 )?;
740 let page_profile = if collect_profile {
741 Some(self.profile_page_from_nodes(&page, ca.y, ca.height, false, None))
742 } else {
743 None
744 };
745 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut page)?;
746 if Self::has_visible_content(&page.nodes) {
747 if let (Some(profile), Some(page_profile)) =
748 (profile.as_deref_mut(), page_profile)
749 {
750 profile.pages.push(page_profile);
751 }
752 pages.push(page);
753 }
754 }
755
756 let mut remaining = if all_content_positioned {
758 Vec::new()
759 } else {
760 content_queued
761 };
762 let data_driven_body_queue =
763 self.queued_nodes_have_data_backed_body_content(&remaining);
764 let page_area_continuation_needs_body_content = data_driven_body_queue;
771 for pa in &page_areas {
772 if remaining.is_empty() {
773 break;
774 }
775 if !pages.is_empty()
780 && !self.queued_nodes_can_populate_continuation_page_area(
781 &remaining,
782 page_area_continuation_needs_body_content,
783 )
784 {
785 remaining.clear();
786 break;
787 }
788 let ca = primary_content_area(pa);
789 let (mut placed, rest, consumed_break_only, _, page_profile) = self
790 .layout_content_fitting(
791 ca,
792 &remaining,
793 pa.page_width,
794 pa.page_height,
795 collect_profile,
796 )?;
797 let commit_used_height = page_profile.as_ref().map(|profile| profile.used_height);
798 let commit_page_height = page_profile.as_ref().map(|profile| profile.page_height);
799 let mut page_committed = false;
800 if consumed_break_only {
801 remaining = rest;
802 } else if Self::has_visible_content(&placed.nodes) {
803 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut placed)?;
804 if let (Some(profile), Some(page_profile)) =
805 (profile.as_deref_mut(), page_profile)
806 {
807 profile.pages.push(page_profile);
808 }
809 pages.push(placed);
810 page_committed = true;
811 remaining = rest;
812 } else if !pa.fixed_nodes.is_empty() {
813 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut placed)?;
819 if Self::has_visible_content(&placed.nodes) {
820 if let (Some(profile), Some(page_profile)) =
821 (profile.as_deref_mut(), page_profile)
822 {
823 profile.pages.push(page_profile);
824 }
825 pages.push(placed);
826 page_committed = true;
827 }
828 remaining = rest;
829 } else {
830 remaining = rest;
833 }
834 if page_committed && !remaining.is_empty() {
835 self.trace_commit_page_boundary(
836 pages.len(),
837 commit_used_height,
838 commit_page_height,
839 remaining.first(),
840 );
841 }
842 }
843
844 if !remaining.is_empty() {
846 let last_idx = page_areas.len() - 1;
847 let overflow_ca = primary_content_area(&page_areas[last_idx]);
848 let page_limit =
850 self.estimate_page_limit(&remaining, overflow_ca.height) + pages.len();
851 while !remaining.is_empty() {
852 if pages.len() >= page_limit {
853 eprintln!(
854 "WARNING: Page limit ({}) reached, truncating layout overflow for {}",
855 page_limit, root_node.name
856 );
857 break;
858 }
859 let pa_idx = last_idx;
860 let pa = &page_areas[pa_idx];
861 let ca = primary_content_area(pa);
862
863 let (mut page, rest, consumed_break_only, _, page_profile) = self
864 .layout_content_fitting(
865 ca,
866 &remaining,
867 pa.page_width,
868 pa.page_height,
869 collect_profile,
870 )?;
871 if page.nodes.is_empty() && !consumed_break_only {
872 let forced = self.layout_content_on_page(
873 ca,
874 pa.page_width,
875 pa.page_height,
876 &[remaining[0].id],
877 LayoutStrategy::TopToBottom,
878 )?;
879 let next_remaining = remaining[1..].to_vec();
880 if Self::has_visible_content(&forced.nodes) {
881 let mut forced = forced;
882 let forced_profile = if collect_profile {
883 Some(
884 self.profile_page_from_nodes(
885 &forced,
886 ca.y,
887 ca.height,
888 !next_remaining.is_empty(),
889 next_remaining
890 .first()
891 .map(|qn| self.describe_queued_node(qn)),
892 ),
893 )
894 } else {
895 None
896 };
897 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut forced)?;
898 if let (Some(profile), Some(page_profile)) =
899 (profile.as_deref_mut(), forced_profile)
900 {
901 profile.pages.push(page_profile);
902 }
903 pages.push(forced);
904 }
905 remaining = next_remaining;
906 } else if consumed_break_only {
907 remaining = rest;
908 } else {
909 if Self::has_visible_content(&page.nodes) {
910 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut page)?;
911 if !rest.is_empty() {
912 self.trace_commit_page_boundary(
913 pages.len() + 1,
914 page_profile.as_ref().map(|profile| profile.used_height),
915 page_profile.as_ref().map(|profile| profile.page_height),
916 rest.first(),
917 );
918 }
919 if let (Some(profile), Some(page_profile)) =
920 (profile.as_deref_mut(), page_profile)
921 {
922 profile.pages.push(page_profile);
923 }
924 pages.push(page);
925 }
926 remaining = rest;
927 }
928 }
929 }
930 }
931
932 Ok(LayoutDom { pages })
933 }
934
935 fn queued_nodes_can_populate_continuation_page_area(
940 &self,
941 nodes: &[QueuedNode],
942 needs_body_content: bool,
943 ) -> bool {
944 if !needs_body_content {
945 return true;
946 }
947
948 nodes
949 .iter()
950 .any(|node| self.queued_node_can_populate_continuation_page_area(node))
951 }
952
953 fn queued_node_can_populate_continuation_page_area(&self, node: &QueuedNode) -> bool {
954 self.queued_node_has_explicit_page_anchor(node)
955 || Self::queued_node_is_split_remainder(node)
956 || self.queued_node_has_data_backed_body_content(node)
957 }
958
959 fn queued_node_has_explicit_page_anchor(&self, node: &QueuedNode) -> bool {
960 node.break_before
961 || node.break_target.is_some()
962 || self.form.meta(node.id).page_break_before
963 }
964
965 fn queued_node_is_split_remainder(node: &QueuedNode) -> bool {
966 node.text_lines_override.is_some()
967 || node.children_override.is_some()
968 || node
969 .nested_child_overrides
970 .as_ref()
971 .is_some_and(|overrides| !overrides.is_empty())
972 }
973
974 fn queued_nodes_have_data_backed_body_content(&self, nodes: &[QueuedNode]) -> bool {
975 nodes
976 .iter()
977 .any(|node| self.queued_node_has_data_backed_body_content(node))
978 }
979
980 fn queued_node_has_data_backed_body_content(&self, node: &QueuedNode) -> bool {
981 self.subtree_has_data_backed_body_content(
982 node.id,
983 node.children_override.as_deref(),
984 node.nested_child_overrides.as_deref(),
985 false,
986 )
987 }
988
989 fn subtree_has_data_backed_body_content(
994 &self,
995 id: FormNodeId,
996 children_override: Option<&[FormNodeId]>,
997 nested_overrides: Option<&[(FormNodeId, Vec<FormNodeId>)]>,
998 inherited_data_context: bool,
999 ) -> bool {
1000 if self.is_layout_hidden(id) {
1001 return false;
1002 }
1003
1004 let node = self.form.get(id);
1005 let meta = self.form.meta(id);
1006 let own_data_context = !meta.data_bind_none && meta.data_bind_ref.is_some();
1007 let data_context = inherited_data_context || own_data_context;
1008
1009 match &node.node_type {
1010 FormNodeType::Field { value } => data_context && !value.trim().is_empty(),
1011 FormNodeType::Root
1012 | FormNodeType::Subform
1013 | FormNodeType::Area
1014 | FormNodeType::ExclGroup
1015 | FormNodeType::SubformSet => {
1016 let children = children_override.unwrap_or(&node.children);
1017 let expanded = if children_override.is_some() {
1018 children.to_vec()
1019 } else {
1020 self.expand_occur(children)
1021 };
1022 expanded.iter().any(|&child_id| {
1023 let child_override = nested_overrides
1024 .and_then(|overrides| {
1025 overrides
1026 .iter()
1027 .find(|(override_id, _)| *override_id == child_id)
1028 })
1029 .map(|(_, child_override)| child_override.as_slice());
1030 self.subtree_has_data_backed_body_content(
1031 child_id,
1032 child_override,
1033 nested_overrides,
1034 data_context,
1035 )
1036 })
1037 }
1038 FormNodeType::PageSet
1039 | FormNodeType::PageArea { .. }
1040 | FormNodeType::Draw(_)
1041 | FormNodeType::Image { .. } => false,
1042 }
1043 }
1044
1045 fn estimate_page_limit(&self, content: &[QueuedNode], page_height: f64) -> usize {
1049 if page_height <= 0.0 {
1050 return MAX_PAGES;
1051 }
1052 let total_height: f64 = content
1053 .iter()
1054 .map(|qn| {
1055 self.compute_extent_with_available_and_override(
1056 qn.id,
1057 None,
1058 qn.children_override.as_deref(),
1059 )
1060 .height
1061 })
1062 .sum();
1063 let estimated = (total_height / page_height).ceil() as usize;
1064 (estimated * 2).clamp(10, MAX_PAGES)
1065 }
1066
1067 fn has_visible_content(nodes: &[LayoutNode]) -> bool {
1073 nodes.iter().any(|n| {
1074 !matches!(n.content, LayoutContent::None) || Self::has_visible_content(&n.children)
1075 })
1076 }
1077
1078 fn is_layout_hidden(&self, id: FormNodeId) -> bool {
1087 let meta = self.form.meta(id);
1088 if meta.presence.is_layout_hidden() {
1089 return true;
1090 }
1091 if meta.content_area_break {
1096 let node = self.form.get(id);
1097 let h = node.box_model.height.unwrap_or(f64::MAX);
1098 return h < 50.0;
1099 }
1100 false
1101 }
1102
1103 fn queue_content(&self, children: &[FormNodeId]) -> Vec<QueuedNode> {
1106 let expanded = self.expand_occur(children);
1107 expanded
1108 .into_iter()
1109 .filter(|&id| !self.is_layout_hidden(id))
1110 .map(|id| {
1111 let meta = self.form.meta(id);
1112 self.trace_vertical_state(
1113 "queue_content",
1114 None,
1115 None,
1116 Some(id),
1117 format!(
1118 "copy FormNodeMeta -> QueuedNode break_before={} break_after={} break_target={} event_type=copy_merge",
1119 meta.page_break_before,
1120 meta.page_break_after,
1121 meta.break_target.clone().unwrap_or_default()
1122 ),
1123 );
1124 QueuedNode {
1125 id,
1126 break_before: meta.page_break_before,
1127 break_after: meta.page_break_after,
1128 break_target: meta.break_target.clone(),
1129 children_override: None,
1130 text_lines_override: None,
1131 nested_child_overrides: None,
1132 }
1133 })
1134 .collect()
1135 }
1136
1137 fn subtree_is_blank(&self, id: FormNodeId) -> bool {
1138 let node = self.form.get(id);
1139 match &node.node_type {
1140 FormNodeType::Field { value } => value.is_empty(),
1141 FormNodeType::Draw(ref content) => {
1142 if let DrawContent::Text(text) = content {
1143 text.is_empty()
1144 } else {
1145 false
1146 }
1147 }
1148 FormNodeType::Image { data, .. } => data.is_empty(),
1149 FormNodeType::Root | FormNodeType::PageSet | FormNodeType::PageArea { .. } => true,
1150 FormNodeType::Subform
1152 | FormNodeType::Area
1153 | FormNodeType::ExclGroup
1154 | FormNodeType::SubformSet => node.children.iter().all(|&c| self.subtree_is_blank(c)),
1155 }
1156 }
1157
1158 #[allow(dead_code)]
1166 fn keep_links_content(&self, current_id: FormNodeId, next_id: FormNodeId) -> bool {
1167 let cur_meta = self.form.meta(current_id);
1168 let nxt_meta = self.form.meta(next_id);
1169 cur_meta.keep_next_content_area
1170 || nxt_meta.keep_previous_content_area
1171 || self.is_spacer_keep_with_next(current_id)
1172 }
1173
1174 #[allow(dead_code)]
1177 fn keep_chain_height(&self, children: &[FormNodeId], start_idx: usize, available: Size) -> f64 {
1178 let mut total = 0.0;
1179 for i in start_idx..children.len() {
1180 let sz = self.compute_extent_with_available(children[i], Some(available));
1181 total += sz.height;
1182 if i + 1 < children.len() && !self.keep_links_content(children[i], children[i + 1]) {
1183 break;
1184 }
1185 }
1186 total
1187 }
1188
1189 #[allow(dead_code)]
1192 fn queued_keep_chain_height(
1193 &self,
1194 children: &[QueuedNode],
1195 start_idx: usize,
1196 available: Size,
1197 ) -> f64 {
1198 let mut total = 0.0;
1199 for i in start_idx..children.len() {
1200 let sz = self.compute_extent_with_available(children[i].id, Some(available));
1201 total += sz.height;
1202 if i + 1 < children.len()
1203 && !self.keep_links_content(children[i].id, children[i + 1].id)
1204 {
1205 break;
1206 }
1207 }
1208 total
1209 }
1210
1211 fn is_spacer_keep_with_next(&self, id: FormNodeId) -> bool {
1214 let meta = self.form.meta(id);
1215 meta.keep_intact_content_area && self.subtree_is_blank(id)
1216 }
1217
1218 fn visible_keep_chain_height(
1226 &self,
1227 visible_ids: &[(usize, FormNodeId)],
1228 start_idx: usize,
1229 available: Size,
1230 ) -> (f64, usize) {
1231 self.trace_vertical_state(
1232 "visible_keep_chain_height",
1233 Some(0.0),
1234 Some(available.height),
1235 visible_ids.get(start_idx).map(|(_, id)| *id),
1236 format!(
1237 "enter start_idx={} visible_ids={}",
1238 start_idx,
1239 visible_ids.len()
1240 ),
1241 );
1242 let mut total = 0.0;
1243 let mut count = 0usize;
1244 for i in start_idx..visible_ids.len() {
1245 let (_, id) = visible_ids[i];
1246 let sz = self.compute_extent_with_available(id, Some(available));
1247 total += sz.height;
1248 count += 1;
1249 self.trace_vertical_state(
1250 "visible_keep_chain_height",
1251 Some(total),
1252 Some(available.height - total),
1253 Some(id),
1254 format!(
1255 "accumulate keep chain index={} node_height={:.3} running_total={:.3} count={} event_type=read",
1256 i,
1257 sz.height,
1258 total,
1259 count
1260 ),
1261 );
1262 if i + 1 < visible_ids.len() {
1264 let (_, next_id) = visible_ids[i + 1];
1265 let cur_meta = self.form.meta(id);
1266 let nxt_meta = self.form.meta(next_id);
1267 let keep = cur_meta.keep_next_content_area
1268 || nxt_meta.keep_previous_content_area
1269 || (cur_meta.keep_intact_content_area && self.subtree_is_blank(id));
1270 self.trace_vertical_state(
1271 "visible_keep_chain_height",
1272 Some(total),
1273 Some(available.height - total),
1274 Some(id),
1275 format!(
1276 "keep-chain continuation check next={} cur.keep_next={} next.keep_previous={} cur.keep_intact_blank={} result={} event_type=read",
1277 self.trace_node_id(next_id),
1278 cur_meta.keep_next_content_area,
1279 nxt_meta.keep_previous_content_area,
1280 cur_meta.keep_intact_content_area && self.subtree_is_blank(id),
1281 keep
1282 ),
1283 );
1284 if !keep {
1285 break;
1286 }
1287 }
1288 }
1289 self.trace_vertical_state(
1290 "visible_keep_chain_height",
1291 Some(total),
1292 Some(available.height - total),
1293 visible_ids.get(start_idx).map(|(_, id)| *id),
1294 format!(
1295 "return chain_height={:.3} chain_len={} event_type=read",
1296 total, count
1297 ),
1298 );
1299 (total, count)
1300 }
1301
1302 fn extract_page_structure(
1303 &self,
1304 root: &FormNode,
1305 ) -> Result<(Vec<PageAreaInfo>, Vec<FormNodeId>)> {
1306 let mut page_areas = Vec::new();
1307 let mut content_nodes = Vec::new();
1308
1309 for &child_id in &root.children {
1310 let child = self.form.get(child_id);
1311 match &child.node_type {
1312 FormNodeType::PageSet => {
1313 for &pa_id in &child.children {
1314 let pa_node = self.form.get(pa_id);
1315 if let FormNodeType::PageArea { content_areas } = &pa_node.node_type {
1316 let pa_meta = self.form.meta(pa_id);
1317 let fixed: Vec<FormNodeId> = pa_node
1318 .children
1319 .iter()
1320 .copied()
1321 .filter(|&cid| {
1322 matches!(
1323 self.form.get(cid).node_type,
1324 FormNodeType::Draw(..)
1325 | FormNodeType::Subform
1326 | FormNodeType::Area
1327 | FormNodeType::ExclGroup
1328 )
1329 })
1330 .collect();
1331 page_areas.push(PageAreaInfo {
1332 name: pa_node.name.clone(),
1333 xfa_id: pa_meta.xfa_id.clone(),
1334 content_areas: content_areas.clone(),
1335 page_width: pa_node.box_model.width.unwrap_or(612.0),
1336 page_height: pa_node.box_model.height.unwrap_or(792.0),
1337 fixed_nodes: fixed,
1338 });
1339 }
1340 }
1341 }
1342 FormNodeType::PageArea { content_areas } => {
1343 let pa_meta = self.form.meta(child_id);
1344 let fixed: Vec<FormNodeId> = child
1345 .children
1346 .iter()
1347 .copied()
1348 .filter(|&cid| {
1349 matches!(
1350 self.form.get(cid).node_type,
1351 FormNodeType::Draw(..)
1352 | FormNodeType::Subform
1353 | FormNodeType::Area
1354 | FormNodeType::ExclGroup
1355 )
1356 })
1357 .collect();
1358 page_areas.push(PageAreaInfo {
1359 name: child.name.clone(),
1360 xfa_id: pa_meta.xfa_id.clone(),
1361 content_areas: content_areas.clone(),
1362 page_width: child.box_model.width.unwrap_or(612.0),
1363 page_height: child.box_model.height.unwrap_or(792.0),
1364 fixed_nodes: fixed,
1365 });
1366 }
1367 FormNodeType::Subform
1376 | FormNodeType::Area
1378 | FormNodeType::ExclGroup => {
1380 let has_pageset = child
1386 .children
1387 .iter()
1388 .any(|&cid| matches!(self.form.get(cid).node_type, FormNodeType::PageSet));
1389 if has_pageset {
1390 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
1391 page_areas.extend(inner_areas);
1392 content_nodes.extend(inner_content);
1393 } else if child.layout == LayoutStrategy::TopToBottom {
1394 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
1397 if !inner_areas.is_empty() {
1398 page_areas.extend(inner_areas);
1399 content_nodes.extend(inner_content);
1400 } else {
1401 content_nodes.push(child_id);
1402 }
1403 } else {
1404 content_nodes.push(child_id);
1405 }
1406 }
1407 FormNodeType::SubformSet => {
1411 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
1412 page_areas.extend(inner_areas);
1413 content_nodes.extend(inner_content);
1414 }
1415 _ => {
1416 content_nodes.push(child_id);
1417 }
1418 }
1419 }
1420
1421 Ok((page_areas, content_nodes))
1422 }
1423
1424 fn layout_content_on_page(
1425 &self,
1426 content_area: &ContentArea,
1427 page_width: f64,
1428 page_height: f64,
1429 content_ids: &[FormNodeId],
1430 strategy: LayoutStrategy,
1431 ) -> Result<LayoutPage> {
1432 let mut page = LayoutPage {
1433 width: page_width,
1434 height: page_height,
1435 nodes: Vec::new(),
1436 };
1437
1438 let available = Size {
1439 width: content_area.width,
1440 height: content_area.height,
1441 };
1442
1443 let nodes = self.layout_children(content_ids, available, strategy)?;
1444
1445 for mut node in nodes {
1447 node.rect.x += content_area.x;
1448 node.rect.y += content_area.y;
1449 page.nodes.push(node);
1450 }
1451
1452 Ok(page)
1453 }
1454
1455 fn profile_page_from_nodes(
1456 &self,
1457 page: &LayoutPage,
1458 content_y: f64,
1459 usable_height: f64,
1460 overflow_to_next: bool,
1461 first_overflow_element: Option<String>,
1462 ) -> LayoutProfilePage {
1463 let used_height = page
1464 .nodes
1465 .iter()
1466 .map(|node| (node.rect.y + node.rect.height) - content_y)
1467 .fold(0.0_f64, f64::max)
1468 .clamp(0.0, usable_height.max(0.0));
1469
1470 LayoutProfilePage {
1471 page_height: usable_height.max(0.0),
1472 used_height,
1473 overflow_to_next,
1474 first_overflow_element,
1475 }
1476 }
1477
1478 fn prepend_fixed_nodes(&self, fixed_ids: &[FormNodeId], page: &mut LayoutPage) -> Result<()> {
1482 if fixed_ids.is_empty() {
1483 return Ok(());
1484 }
1485 let fixed_laid = self.layout_positioned(fixed_ids)?;
1486 let mut merged = fixed_laid;
1487 merged.append(&mut page.nodes);
1488 page.nodes = merged;
1489 Ok(())
1490 }
1491
1492 #[allow(clippy::type_complexity)]
1500 fn layout_content_fitting(
1501 &self,
1502 content_area: &ContentArea,
1503 content_ids: &[QueuedNode],
1504 page_width: f64,
1505 page_height: f64,
1506 profile_enabled: bool,
1507 ) -> Result<FittingResult> {
1508 let mut page = LayoutPage {
1509 width: page_width,
1510 height: page_height,
1511 nodes: Vec::new(),
1512 };
1513
1514 let mut leader_height = 0.0;
1527 let mut trailer_height = 0.0;
1528
1529 if let Some(leader_id) = content_area.leader {
1530 let leader_size = self.compute_extent(leader_id);
1531 leader_height = leader_size.height;
1532 let leader_node = self.form.get(leader_id);
1533 let node = self.layout_single_node(leader_id, leader_node, 0.0, 0.0, None)?;
1534 let mut offset = node;
1535 offset.rect.x += content_area.x;
1536 offset.rect.y += content_area.y;
1537 page.nodes.push(offset);
1538 }
1539
1540 if let Some(trailer_id) = content_area.trailer {
1541 let trailer_size = self.compute_extent(trailer_id);
1542 trailer_height = trailer_size.height;
1543 let trailer_y = content_area.height - trailer_height;
1545 let trailer_node = self.form.get(trailer_id);
1546 let node = self.layout_single_node(trailer_id, trailer_node, 0.0, trailer_y, None)?;
1547 let mut offset = node;
1548 offset.rect.x += content_area.x;
1549 offset.rect.y += content_area.y;
1550 page.nodes.push(offset);
1551 }
1552
1553 let effective_ca_height = {
1561 let ca_bottom = content_area.y + content_area.height;
1562 let gap_below_ca = page_height - ca_bottom;
1563 if gap_below_ca < 36.0 {
1567 let remaining_page = page_height - content_area.y;
1568 content_area.height.max(remaining_page)
1569 } else {
1570 content_area.height
1571 }
1572 };
1573 let content_height = effective_ca_height - leader_height - trailer_height;
1574 let available = Size {
1575 width: content_area.width,
1576 height: content_height,
1577 };
1578
1579 let mut y_cursor = leader_height;
1580 let mut placed_count = 0;
1581 let mut split_remaining: Vec<QueuedNode> = Vec::new();
1582 let content_bottom = leader_height + content_height;
1583 let mut consumed_break_only = false;
1584 let break_target = None;
1585 let mut max_used_bottom = 0.0_f64;
1586
1587 let header_node_count = (if content_area.leader.is_some() { 1 } else { 0 })
1589 + (if content_area.trailer.is_some() { 1 } else { 0 });
1590
1591 let visible_ids: Vec<(usize, FormNodeId)> = content_ids
1594 .iter()
1595 .enumerate()
1596 .filter(|(_, qn)| !self.is_layout_hidden(qn.id))
1597 .map(|(i, qn)| (i, qn.id))
1598 .collect();
1599 let mut vis_pos = 0; self.trace_vertical_state(
1602 "layout_content_fitting",
1603 Some(y_cursor),
1604 Some(content_height),
1605 content_ids.first().map(|queued| queued.id),
1606 format!(
1607 "enter page_width={:.3} page_height={:.3} content_area={} content_nodes={} leader_height={:.3} trailer_height={:.3} effective_ca_height={:.3}",
1608 page_width,
1609 page_height,
1610 content_area.name,
1611 content_ids.len(),
1612 leader_height,
1613 trailer_height,
1614 effective_ca_height
1615 ),
1616 );
1617 self.trace_vertical_state(
1618 "layout_content_fitting",
1619 Some(y_cursor),
1620 Some(content_height),
1621 content_ids.first().map(|queued| queued.id),
1622 format!(
1623 "visible_ids prepared count={} event_type=runtime_allocation",
1624 visible_ids.len()
1625 ),
1626 );
1627
1628 for (idx, qn) in content_ids.iter().enumerate() {
1629 let child_id = qn.id;
1630
1631 if self.is_layout_hidden(child_id) {
1633 placed_count += 1;
1634 continue;
1635 }
1636
1637 self.trace_vertical_state(
1638 "layout_content_fitting",
1639 Some(y_cursor),
1640 Some(content_bottom - y_cursor),
1641 Some(child_id),
1642 format!(
1643 "loop entry idx={} placed_count={} vis_pos={} break_before={} break_after={}",
1644 idx, placed_count, vis_pos, qn.break_before, qn.break_after
1645 ),
1646 );
1647
1648 self.trace_break_before_read(
1652 y_cursor,
1653 content_bottom - y_cursor,
1654 child_id,
1655 qn.break_before,
1656 placed_count,
1657 header_node_count,
1658 );
1659 if qn.break_before && placed_count > 0 {
1660 let only_blanks = page.nodes.len() <= header_node_count;
1664 if only_blanks {
1665 consumed_break_only = true;
1666 }
1667 break;
1668 }
1669
1670 let child = self.form.get(child_id);
1671 let child_size = if let Some(ref override_lines) = qn.text_lines_override {
1672 let style_lh = self.form.meta(child_id).style.line_height_pt;
1673 let lh = style_lh.unwrap_or_else(|| child.font.line_height_pt());
1674 let w = self.compute_extent(child_id).width;
1675 Size {
1676 width: w,
1677 height: override_lines.len() as f64 * lh,
1678 }
1679 } else {
1680 self.compute_extent_with_available_and_override(
1681 child_id,
1682 Some(available),
1683 qn.children_override.as_deref(),
1684 )
1685 };
1686
1687 if placed_count > 0 && vis_pos < visible_ids.len() {
1695 let (chain_height, chain_len) =
1696 self.visible_keep_chain_height(&visible_ids, vis_pos, available);
1697 let remaining_on_page = content_bottom - y_cursor;
1698 let is_single_splittable = chain_len == 1 && self.can_split(child_id);
1699 self.trace_vertical_state(
1700 "layout_content_fitting",
1701 Some(y_cursor),
1702 Some(remaining_on_page),
1703 Some(child_id),
1704 format!(
1705 "keep look-ahead chain_height={:.3} chain_len={} remaining_on_page={:.3} content_height={:.3} is_single_splittable={} event_type=read",
1706 chain_height,
1707 chain_len,
1708 remaining_on_page,
1709 content_height,
1710 is_single_splittable
1711 ),
1712 );
1713 if !is_single_splittable
1714 && chain_height > remaining_on_page
1715 && chain_height <= content_height
1716 {
1717 self.trace_vertical_state(
1719 "layout_content_fitting",
1720 Some(y_cursor),
1721 Some(remaining_on_page),
1722 Some(child_id),
1723 "keep look-ahead fired: current chain does not fit remaining space but fits on a fresh page event_type=rule_decision",
1724 );
1725 break;
1726 }
1727 }
1730
1731 if y_cursor + child_size.height > content_bottom {
1732 let remaining_height = content_bottom - y_cursor;
1733
1734 let trace_som = self.trace_path_segment(child_id);
1742 trace_sites::paginate(
1743 &trace_som,
1744 TraceReason::PaginateDeferToNextPageMinH,
1745 remaining_height,
1746 child_size.height,
1747 );
1748
1749 if remaining_height > 0.0
1751 && (qn.text_lines_override.is_some() || self.is_splittable_text_leaf(child_id))
1752 {
1753 let lines = if let Some(ref ol) = qn.text_lines_override {
1754 ol.clone()
1755 } else {
1756 let txt = match &child.node_type {
1757 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
1758 FormNodeType::Field { value } => value.as_str(),
1759 _ => "",
1760 };
1761 let child_style = &self.form.meta(child_id).style;
1762 let para_margins = child_style
1763 .margin_left_pt
1764 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
1765 + child_style
1766 .margin_right_pt
1767 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
1768 let child_border_w = child_style
1769 .border_width_pt
1770 .unwrap_or(child.box_model.border_width);
1771 let insets_w = child.box_model.margins.horizontal()
1772 + child_border_w * 2.0
1773 + para_margins;
1774 let max_w = (child_size.width - insets_w).max(1.0);
1775 text::wrap_text(
1776 txt,
1777 max_w,
1778 &child.font,
1779 child_style.text_indent_pt.unwrap_or(0.0),
1780 child_style.line_height_pt,
1781 )
1782 .lines
1783 };
1784 let (partial, rest_nodes) =
1785 self.split_text_node(child_id, y_cursor, remaining_height, &lines)?;
1786 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
1787 if profile_enabled {
1788 max_used_bottom = max_used_bottom
1789 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
1790 }
1791 let mut offset_node = partial;
1792 offset_node.rect.x += content_area.x;
1793 offset_node.rect.y += content_area.y;
1794 trace_sites::paginate(
1799 &trace_som,
1800 TraceReason::PaginateSplit,
1801 offset_node.rect.height,
1802 child_size.height,
1803 );
1804 page.nodes.push(offset_node);
1805 placed_count += 1;
1806 split_remaining = rest_nodes;
1807 } else if placed_count > 0 {
1808 break;
1809 }
1810 } else if remaining_height > 0.0 && self.can_split(child_id) {
1812 let (partial, rest_nodes) = self.split_tb_node(
1813 child_id,
1814 y_cursor,
1815 remaining_height,
1816 available,
1817 qn.children_override.as_deref(),
1818 qn.nested_child_overrides.as_deref(),
1819 )?;
1820
1821 let partial_fits = partial.rect.height <= remaining_height + 1.0;
1822 let split_productive = !partial.children.is_empty()
1823 && (partial_fits || partial.children.len() > 1);
1824 if !partial.children.is_empty() && (partial_fits || split_productive) {
1825 if profile_enabled {
1826 max_used_bottom = max_used_bottom
1827 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
1828 }
1829 let mut offset_node = partial;
1830 offset_node.rect.x += content_area.x;
1831 offset_node.rect.y += content_area.y;
1832 trace_sites::paginate(
1836 &trace_som,
1837 TraceReason::PaginateSplit,
1838 offset_node.rect.height,
1839 child_size.height,
1840 );
1841 page.nodes.push(offset_node);
1842 placed_count += 1;
1843 split_remaining = rest_nodes;
1844 } else if placed_count > 0 {
1845 break;
1849 }
1850 } else if idx == 0 || page.nodes.len() <= header_node_count {
1851 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
1853 if profile_enabled {
1854 max_used_bottom = max_used_bottom
1855 .max((y_cursor + child_size.height - leader_height).max(0.0));
1856 }
1857 let node = self.layout_single_node_with_extent(
1858 child_id,
1859 child,
1860 x,
1861 y_cursor,
1862 child_size,
1863 qn.children_override.as_deref(),
1864 )?;
1865 let mut offset_node = node;
1866 offset_node.rect.x += content_area.x;
1867 offset_node.rect.y += content_area.y;
1868 page.nodes.push(offset_node);
1869 placed_count += 1;
1870 }
1871 break;
1872 }
1873
1874 if self.has_inner_break(child_id) && self.can_split(child_id) {
1878 let (partial, rest_nodes) = self.split_tb_node(
1879 child_id,
1880 y_cursor,
1881 content_height,
1882 available,
1883 qn.children_override.as_deref(),
1884 qn.nested_child_overrides.as_deref(),
1885 )?;
1886 let remaining_on_page = content_bottom - y_cursor;
1887 if !partial.children.is_empty() && partial.rect.height <= remaining_on_page {
1888 if profile_enabled {
1889 max_used_bottom = max_used_bottom
1890 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
1891 }
1892 let mut offset_node = partial;
1893 offset_node.rect.x += content_area.x;
1894 offset_node.rect.y += content_area.y;
1895 page.nodes.push(offset_node);
1896 placed_count += 1;
1897 split_remaining = rest_nodes;
1898 } else if placed_count > 0 {
1899 break;
1900 } else {
1901 if !partial.children.is_empty() {
1902 if profile_enabled {
1903 max_used_bottom = max_used_bottom
1904 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
1905 }
1906 let mut offset_node = partial;
1907 offset_node.rect.x += content_area.x;
1908 offset_node.rect.y += content_area.y;
1909 page.nodes.push(offset_node);
1910 }
1911 placed_count += 1;
1912 split_remaining = rest_nodes;
1913 }
1914 break;
1915 }
1916
1917 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
1919 let node = if let Some(ref override_lines) = qn.text_lines_override {
1920 let child_style = &self.form.meta(child_id).style;
1921 LayoutNode {
1922 form_node: child_id,
1923 rect: Rect::new(x, y_cursor, child_size.width, child_size.height),
1924 name: child.name.clone(),
1925 content: LayoutContent::WrappedText {
1926 lines: override_lines.clone(),
1927 first_line_of_para: vec![false; override_lines.len()],
1928 font_size: child.font.size,
1929 text_align: child.font.text_align,
1930 font_family: child.font.typeface,
1931 space_above_pt: child_style.space_above_pt,
1932 space_below_pt: child_style.space_below_pt,
1933 from_field: matches!(child.node_type, FormNodeType::Field { .. }),
1934 },
1935 children: Vec::new(),
1936 style: self.form.meta(child_id).style.clone(),
1937 display_items: self.form.meta(child_id).display_items.clone(),
1938 save_items: self.form.meta(child_id).save_items.clone(),
1939 }
1940 } else {
1941 self.layout_single_node_with_extent(
1942 child_id,
1943 child,
1944 x,
1945 y_cursor,
1946 child_size,
1947 qn.children_override.as_deref(),
1948 )?
1949 };
1950 if profile_enabled {
1951 max_used_bottom =
1952 max_used_bottom.max((y_cursor + child_size.height - leader_height).max(0.0));
1953 }
1954 let mut offset_node = node;
1955 offset_node.rect.x += content_area.x;
1956 offset_node.rect.y += content_area.y;
1957 page.nodes.push(offset_node);
1958
1959 y_cursor += child_size.height;
1960 placed_count += 1;
1961 vis_pos += 1;
1962
1963 self.trace_vertical_state(
1964 "layout_content_fitting",
1965 Some(y_cursor),
1966 Some(content_bottom - y_cursor),
1967 Some(child_id),
1968 format!(
1969 "placed node child_height={:.3} new_y_cursor={:.3} placed_count={} vis_pos={}",
1970 child_size.height, y_cursor, placed_count, vis_pos
1971 ),
1972 );
1973
1974 if qn.break_after {
1975 self.trace_vertical_state(
1976 "layout_content_fitting",
1977 Some(y_cursor),
1978 Some(content_bottom - y_cursor),
1979 Some(child_id),
1980 format!("break_after check break_after={}", qn.break_after),
1981 );
1982 break;
1983 }
1984 }
1985
1986 let mut remaining = split_remaining;
1987 remaining.extend(content_ids[placed_count..].iter().cloned());
1988 self.trace_vertical_state(
1989 "layout_content_fitting",
1990 Some(y_cursor),
1991 Some(content_bottom - y_cursor),
1992 remaining.first().map(|queued| queued.id),
1993 format!(
1994 "return remaining_nodes={} consumed_break_only={} first_remaining={}",
1995 remaining.len(),
1996 consumed_break_only,
1997 remaining
1998 .first()
1999 .map(|queued| self.describe_queued_node(queued))
2000 .unwrap_or_else(|| "none".to_string())
2001 ),
2002 );
2003 let page_profile = if profile_enabled {
2004 Some(LayoutProfilePage {
2005 page_height: content_height.max(0.0),
2006 used_height: max_used_bottom.clamp(0.0, content_height.max(0.0)),
2007 overflow_to_next: !remaining.is_empty() && !consumed_break_only,
2008 first_overflow_element: if !remaining.is_empty() && !consumed_break_only {
2009 Some(self.describe_queued_node(&remaining[0]))
2010 } else {
2011 None
2012 },
2013 })
2014 } else {
2015 None
2016 };
2017 Ok((
2018 page,
2019 remaining,
2020 consumed_break_only,
2021 break_target,
2022 page_profile,
2023 ))
2024 }
2025
2026 fn describe_queued_node(&self, queued: &QueuedNode) -> String {
2027 let node = self.form.get(queued.id);
2028 let identifier = self
2029 .form
2030 .meta(queued.id)
2031 .xfa_id
2032 .clone()
2033 .filter(|id| !id.is_empty())
2034 .or_else(|| (!node.name.is_empty()).then(|| node.name.clone()))
2035 .unwrap_or_else(|| queued.id.0.to_string());
2036 let height = self
2037 .compute_extent_with_available_and_override(
2038 queued.id,
2039 None,
2040 queued.children_override.as_deref(),
2041 )
2042 .height;
2043 format!(
2044 "{}#{} (h={height:.1})",
2045 Self::form_node_type_name(&node.node_type),
2046 identifier
2047 )
2048 }
2049
2050 fn form_node_type_name(node_type: &FormNodeType) -> &'static str {
2051 match node_type {
2052 FormNodeType::Root => "root",
2053 FormNodeType::PageSet => "pageSet",
2054 FormNodeType::PageArea { .. } => "pageArea",
2055 FormNodeType::Subform => "subform",
2056 FormNodeType::Area => "area",
2057 FormNodeType::ExclGroup => "exclGroup",
2058 FormNodeType::SubformSet => "subformSet",
2059 FormNodeType::Field { .. } => "field",
2060 FormNodeType::Draw(_) => "draw",
2061 FormNodeType::Image { .. } => "image",
2062 }
2063 }
2064
2065 fn can_split(&self, id: FormNodeId) -> bool {
2076 let node = self.form.get(id);
2077 if node.children.is_empty() {
2078 return self.is_splittable_text_leaf(id);
2079 }
2080 if let Some(explicit_h) = node.box_model.height {
2081 if node.layout == LayoutStrategy::TopToBottom {
2084 let extent = self.compute_extent(id);
2085 return extent.height > explicit_h;
2086 }
2087 return false;
2088 }
2089 if node.layout == LayoutStrategy::Positioned && node.box_model.max_height < f64::MAX {
2092 return false;
2093 }
2094 matches!(
2095 node.layout,
2096 LayoutStrategy::TopToBottom
2097 | LayoutStrategy::LeftToRightTB
2098 | LayoutStrategy::RightToLeftTB
2099 | LayoutStrategy::Table
2100 | LayoutStrategy::Positioned
2101 )
2102 }
2103
2104 fn is_splittable_text_leaf(&self, id: FormNodeId) -> bool {
2109 let node = self.form.get(id);
2110 if !node.children.is_empty() {
2111 return false;
2112 }
2113 if self.form.meta(id).keep_intact_content_area {
2114 return false;
2115 }
2116 let style = &self.form.meta(id).style;
2117 let para_margins = style
2118 .margin_left_pt
2119 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2120 + style
2121 .margin_right_pt
2122 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2123 match &node.node_type {
2124 FormNodeType::Draw(DrawContent::Text(t)) => {
2125 let line_count = text::wrap_text(
2126 t,
2127 (node.box_model.content_width() - para_margins).max(1.0),
2128 &node.font,
2129 style.text_indent_pt.unwrap_or(0.0),
2130 style.line_height_pt,
2131 )
2132 .lines
2133 .len();
2134 line_count > 1
2135 }
2136 FormNodeType::Field { value } if !value.is_empty() => {
2137 let line_count = text::wrap_text(
2138 value,
2139 (node.box_model.content_width() - para_margins).max(1.0),
2140 &node.font,
2141 style.text_indent_pt.unwrap_or(0.0),
2142 style.line_height_pt,
2143 )
2144 .lines
2145 .len();
2146 line_count > 1
2147 }
2148 _ => false,
2149 }
2150 }
2151
2152 fn split_text_node(
2157 &self,
2158 id: FormNodeId,
2159 y_offset: f64,
2160 remaining_height: f64,
2161 lines: &[String],
2162 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
2163 let node = self.form.get(id);
2164 let style_lh = self.form.meta(id).style.line_height_pt;
2165 let lh = style_lh.unwrap_or_else(|| node.font.line_height_pt());
2166 let split_points = text::text_split_points(lines.len(), lh);
2167
2168 let mut split_at = 0;
2169 for &sp in &split_points {
2170 if sp <= remaining_height + 0.5 {
2171 split_at += 1;
2172 } else {
2173 break;
2174 }
2175 }
2176
2177 if split_at == 0 {
2178 let full_height = lines.len() as f64 * lh;
2179 let split_style = &self.form.meta(id).style;
2180 let full_node = LayoutNode {
2181 form_node: id,
2182 rect: Rect::new(0.0, y_offset, self.compute_extent(id).width, full_height),
2183 name: node.name.clone(),
2184 content: LayoutContent::WrappedText {
2185 lines: lines.to_vec(),
2186 first_line_of_para: vec![false; lines.len()],
2187 font_size: node.font.size,
2188 text_align: node.font.text_align,
2189 font_family: node.font.typeface,
2190 space_above_pt: split_style.space_above_pt,
2191 space_below_pt: split_style.space_below_pt,
2192 from_field: matches!(node.node_type, FormNodeType::Field { .. }),
2193 },
2194 children: Vec::new(),
2195 style: self.form.meta(id).style.clone(),
2196 display_items: self.form.meta(id).display_items.clone(),
2197 save_items: self.form.meta(id).save_items.clone(),
2198 };
2199 return Ok((full_node, Vec::new()));
2200 }
2201
2202 let top_lines: Vec<String> = lines[..split_at].to_vec();
2203 let bottom_lines: Vec<String> = lines[split_at..].to_vec();
2204 let partial_height = split_at as f64 * lh;
2205 let node_width = self.compute_extent(id).width;
2206 let split_style = &self.form.meta(id).style;
2207
2208 let partial_node = LayoutNode {
2209 form_node: id,
2210 rect: Rect::new(0.0, y_offset, node_width, partial_height),
2211 name: node.name.clone(),
2212 content: LayoutContent::WrappedText {
2213 lines: top_lines.clone(),
2214 first_line_of_para: vec![false; top_lines.len()],
2215 font_size: node.font.size,
2216 text_align: node.font.text_align,
2217 font_family: node.font.typeface,
2218 space_above_pt: split_style.space_above_pt,
2219 space_below_pt: split_style.space_below_pt,
2220 from_field: matches!(node.node_type, FormNodeType::Field { .. }),
2221 },
2222 children: Vec::new(),
2223 style: self.form.meta(id).style.clone(),
2224 display_items: self.form.meta(id).display_items.clone(),
2225 save_items: self.form.meta(id).save_items.clone(),
2226 };
2227
2228 let rest = if bottom_lines.is_empty() {
2229 Vec::new()
2230 } else {
2231 vec![QueuedNode {
2232 id,
2233 break_before: false,
2234 break_after: self.form.meta(id).page_break_after,
2235 break_target: None,
2236 children_override: None,
2237 text_lines_override: Some(bottom_lines),
2238 nested_child_overrides: None,
2239 }]
2240 };
2241
2242 Ok((partial_node, rest))
2243 }
2244
2245 fn has_inner_break(&self, id: FormNodeId) -> bool {
2249 let node = self.form.get(id);
2250 if !matches!(
2251 node.layout,
2252 LayoutStrategy::TopToBottom
2253 | LayoutStrategy::LeftToRightTB
2254 | LayoutStrategy::RightToLeftTB
2255 ) {
2256 return false;
2257 }
2258 let expanded = self.expand_occur(&node.children);
2259 expanded
2260 .iter()
2261 .any(|&cid| self.form.meta(cid).page_break_before)
2262 }
2263
2264 fn split_tb_node(
2275 &self,
2276 id: FormNodeId,
2277 y_offset: f64,
2278 remaining_height: f64,
2279 _available: Size,
2280 children_override: Option<&[FormNodeId]>,
2281 nested_overrides: Option<&[(FormNodeId, Vec<FormNodeId>)]>,
2282 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
2283 let node = self.form.get(id);
2284
2285 if node.layout == LayoutStrategy::Positioned {
2287 return self.split_positioned_node(id, y_offset, remaining_height, children_override);
2288 }
2289
2290 let node_children = children_override.unwrap_or(&node.children);
2291 let expanded_children = if children_override.is_some() {
2295 node_children.to_vec()
2296 } else {
2297 self.expand_occur(node_children)
2298 };
2299
2300 let mut placed_children = Vec::new();
2301 let mut child_y = 0.0;
2302 let mut split_idx = 0;
2303 let mut last_valid_split = 0;
2305 let mut last_valid_y = 0.0_f64;
2306 let mut split_rest_override: Option<Vec<QueuedNode>> = None;
2307
2308 for (i, &child_id) in expanded_children.iter().enumerate() {
2309 let child = self.form.get(child_id);
2310 let child_co = nested_overrides
2313 .and_then(|no| no.iter().find(|(nid, _)| *nid == child_id))
2314 .map(|(_, co)| co.as_slice());
2315 let child_size = self.compute_extent_with_override(child_id, child_co);
2316 let child_meta = self.form.meta(child_id);
2317
2318 if child_meta.keep_intact_content_area
2321 && child_y + child_size.height > remaining_height
2322 && !placed_children.is_empty()
2323 {
2324 split_idx = i;
2325 break;
2326 }
2327
2328 if child_y + child_size.height > remaining_height
2331 && !child_meta.keep_intact_content_area
2332 && self.is_splittable_text_leaf(child_id)
2333 {
2334 let child_remaining = (remaining_height - child_y).max(0.0);
2335 if child_remaining > 0.0 {
2336 let cnode = self.form.get(child_id);
2337 let txt = match &cnode.node_type {
2338 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
2339 FormNodeType::Field { value } => value.as_str(),
2340 _ => "",
2341 };
2342 let cstyle = &self.form.meta(child_id).style;
2343 let cpara = cstyle
2344 .margin_left_pt
2345 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2346 + cstyle
2347 .margin_right_pt
2348 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2349 let cborder_w = cstyle
2350 .border_width_pt
2351 .unwrap_or(cnode.box_model.border_width);
2352 let insets_w = cnode.box_model.margins.horizontal() + cborder_w * 2.0 + cpara;
2353 let max_w = (self.compute_extent(child_id).width - insets_w).max(1.0);
2354 let wrapped = text::wrap_text(
2355 txt,
2356 max_w,
2357 &cnode.font,
2358 cstyle.text_indent_pt.unwrap_or(0.0),
2359 cstyle.line_height_pt,
2360 );
2361 let (partial_child, child_rest) =
2362 self.split_text_node(child_id, child_y, child_remaining, &wrapped.lines)?;
2363
2364 if partial_child.rect.height > 0.0
2365 && partial_child.rect.height <= child_remaining + 0.5
2366 {
2367 placed_children.push(partial_child);
2368 child_y += placed_children.last().unwrap().rect.height;
2369 split_idx = i + 1;
2370
2371 let mut rest: Vec<QueuedNode> = child_rest;
2372 rest.extend(expanded_children[i + 1..].iter().map(|&cid| QueuedNode {
2373 id: cid,
2374 break_before: self.form.meta(cid).page_break_before,
2375 break_after: self.form.meta(cid).page_break_after,
2376 break_target: None,
2377 children_override: None,
2378 text_lines_override: None,
2379 nested_child_overrides: None,
2380 }));
2381 split_rest_override = Some(rest);
2382 break;
2383 }
2384 }
2385 }
2386
2387 if child_y + child_size.height > remaining_height
2391 && !child_meta.keep_intact_content_area
2392 && self.can_split(child_id)
2393 {
2394 let child_remaining = (remaining_height - child_y).max(0.0);
2395 if child_remaining > 0.0 {
2396 let child_available = Size {
2397 width: child.box_model.content_width().min(child_size.width),
2398 height: child.box_model.content_height().min(child_size.height),
2399 };
2400 let (partial_child, child_rest) = self.split_tb_node(
2401 child_id,
2402 child_y,
2403 child_remaining,
2404 child_available,
2405 child_co,
2406 nested_overrides,
2407 )?;
2408
2409 let partial_fits = partial_child.rect.height <= child_remaining + 1.0;
2410 let split_productive = !partial_child.children.is_empty()
2411 && (partial_fits || partial_child.children.len() > 1);
2412
2413 if split_productive {
2414 placed_children.push(partial_child);
2415 child_y += placed_children.last().unwrap().rect.height;
2416 split_idx = i + 1;
2417
2418 let (child_override, child_nested) = if child_rest.len() == 1
2434 && child_rest[0].id == child_id
2435 && child_rest[0].children_override.is_some()
2436 {
2437 let qn = child_rest.into_iter().next().unwrap();
2438 (qn.children_override, qn.nested_child_overrides)
2439 } else {
2440 let mut ids = Vec::new();
2441 let mut nested = Vec::new();
2442 for qn in child_rest {
2443 ids.push(qn.id);
2444 if let Some(co) = qn.children_override {
2445 nested.push((qn.id, co));
2446 }
2447 if let Some(nco) = qn.nested_child_overrides {
2448 nested.extend(nco);
2449 }
2450 }
2451 (
2452 Some(ids),
2453 if nested.is_empty() {
2454 None
2455 } else {
2456 Some(nested)
2457 },
2458 )
2459 };
2460
2461 let mut rest = vec![QueuedNode {
2462 id: child_id,
2463 break_before: false,
2464 break_after: self.form.meta(child_id).page_break_after,
2465 break_target: None,
2466 children_override: child_override,
2467 text_lines_override: None,
2468 nested_child_overrides: child_nested,
2469 }];
2470 rest.extend(expanded_children[i + 1..].iter().map(|&cid| QueuedNode {
2471 id: cid,
2472 break_before: self.form.meta(cid).page_break_before,
2473 break_after: self.form.meta(cid).page_break_after,
2474 break_target: None,
2475 children_override: None,
2476 text_lines_override: None,
2477 nested_child_overrides: None,
2478 }));
2479 split_rest_override = Some(rest);
2480 break;
2481 }
2482 }
2483 }
2484
2485 if child_y + child_size.height > remaining_height + 0.5 && !placed_children.is_empty() {
2488 if last_valid_split > 0 && last_valid_split < placed_children.len() {
2490 placed_children.truncate(last_valid_split);
2492 child_y = last_valid_y;
2493 split_idx = last_valid_split;
2494 } else {
2495 split_idx = i;
2496 }
2497 break;
2498 }
2499
2500 let child_node = self.layout_single_node(child_id, child, 0.0, child_y, child_co)?;
2501 placed_children.push(child_node);
2502 child_y += child_size.height;
2503 split_idx = i + 1;
2504
2505 let has_keep = child_meta.keep_next_content_area;
2507 let next_has_keep_prev = if i + 1 < expanded_children.len() {
2508 self.form
2509 .meta(expanded_children[i + 1])
2510 .keep_previous_content_area
2511 } else {
2512 false
2513 };
2514 if !has_keep && !next_has_keep_prev {
2515 last_valid_split = split_idx;
2516 last_valid_y = child_y;
2517 }
2518 }
2519
2520 let content = match &node.node_type {
2521 FormNodeType::Field { value } => {
2522 let meta = self.form.meta(id);
2523 LayoutContent::Field {
2524 value: resolve_display_value(value, meta).to_string(),
2525 field_kind: meta.field_kind,
2526 font_size: node.font.size,
2527 font_family: node.font.typeface,
2528 }
2529 }
2530 FormNodeType::Draw(DrawContent::Text(content)) => LayoutContent::Text(content.clone()),
2531 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
2532 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
2533 data: data.clone(),
2534 mime_type: mime_type.clone(),
2535 },
2536 _ => LayoutContent::None,
2537 };
2538
2539 let partial_width = self
2541 .compute_extent_with_override(id, children_override)
2542 .width;
2543
2544 let partial_node = LayoutNode {
2545 form_node: id,
2546 rect: Rect::new(0.0, y_offset, partial_width, child_y),
2547 name: node.name.clone(),
2548 content,
2549 children: placed_children,
2550 style: self.form.meta(id).style.clone(),
2551 display_items: self.form.meta(id).display_items.clone(),
2552 save_items: self.form.meta(id).save_items.clone(),
2553 };
2554
2555 let rest = split_rest_override.unwrap_or_else(|| {
2556 expanded_children[split_idx..]
2557 .iter()
2558 .map(|&cid| QueuedNode {
2559 id: cid,
2560 break_before: self.form.meta(cid).page_break_before,
2561 break_after: self.form.meta(cid).page_break_after,
2562 break_target: None,
2563 children_override: None,
2564 text_lines_override: None,
2565 nested_child_overrides: None,
2566 })
2567 .collect()
2568 });
2569 Ok((partial_node, rest))
2570 }
2571
2572 fn split_positioned_node(
2585 &self,
2586 id: FormNodeId,
2587 y_offset: f64,
2588 remaining_height: f64,
2589 children_override: Option<&[FormNodeId]>,
2590 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
2591 let node = self.form.get(id);
2592 let node_children = children_override.unwrap_or(&node.children);
2593 let expanded_children = if children_override.is_some() {
2596 node_children.to_vec()
2597 } else {
2598 self.expand_occur(node_children)
2599 };
2600
2601 let mut sorted: Vec<FormNodeId> = expanded_children.clone();
2603 sorted.sort_by(|&a, &b| {
2604 let ay = self.form.get(a).box_model.y;
2605 let by = self.form.get(b).box_model.y;
2606 ay.partial_cmp(&by).unwrap_or(std::cmp::Ordering::Equal)
2607 });
2608
2609 let y_base = if children_override.is_some() {
2613 sorted
2614 .first()
2615 .map(|&cid| self.form.get(cid).box_model.y)
2616 .unwrap_or(0.0)
2617 } else {
2618 0.0
2619 };
2620 let parent_margin_x = node.box_model.margins.left;
2621 let parent_margin_y = node.box_model.margins.top;
2622
2623 let mut placed_children = Vec::new();
2624 let mut rest_children = Vec::new();
2625 let mut max_placed_bottom = 0.0_f64;
2626
2627 for &child_id in &sorted {
2628 let child = self.form.get(child_id);
2629 let child_size = self.compute_extent(child_id);
2630 let shifted_y = child.box_model.y - y_base + parent_margin_y;
2631 let child_bottom = shifted_y + child_size.height;
2632
2633 if child_bottom <= remaining_height + 1.0 {
2634 let child_node = self.layout_single_node(
2636 child_id,
2637 child,
2638 child.box_model.x + parent_margin_x,
2639 shifted_y,
2640 None,
2641 )?;
2642 max_placed_bottom = max_placed_bottom.max(child_bottom);
2643 placed_children.push(child_node);
2644 } else {
2645 rest_children.push(child_id);
2647 }
2648 }
2649
2650 if placed_children.is_empty() && !sorted.is_empty() {
2656 let first_id = sorted[0];
2657 let first = self.form.get(first_id);
2658 let first_size = self.compute_extent(first_id);
2659 let shifted_y = first.box_model.y - y_base + parent_margin_y;
2660 let child_node = self.layout_single_node(
2661 first_id,
2662 first,
2663 first.box_model.x + parent_margin_x,
2664 shifted_y,
2665 None,
2666 )?;
2667 max_placed_bottom = shifted_y + first_size.height;
2668 placed_children.push(child_node);
2669 rest_children.retain(|&cid| cid != first_id);
2671 }
2672
2673 let content = match &node.node_type {
2674 FormNodeType::Field { value } => {
2675 let meta = self.form.meta(id);
2676 LayoutContent::Field {
2677 value: resolve_display_value(value, meta).to_string(),
2678 field_kind: meta.field_kind,
2679 font_size: node.font.size,
2680 font_family: node.font.typeface,
2681 }
2682 }
2683 FormNodeType::Draw(DrawContent::Text(content)) => LayoutContent::Text(content.clone()),
2684 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
2685 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
2686 data: data.clone(),
2687 mime_type: mime_type.clone(),
2688 },
2689 _ => LayoutContent::None,
2690 };
2691
2692 let partial_width = self
2693 .compute_extent_with_override(id, children_override)
2694 .width;
2695
2696 let partial_node = LayoutNode {
2697 form_node: id,
2698 rect: Rect::new(0.0, y_offset, partial_width, max_placed_bottom),
2699 name: node.name.clone(),
2700 content,
2701 children: placed_children,
2702 style: self.form.meta(id).style.clone(),
2703 display_items: self.form.meta(id).display_items.clone(),
2704 save_items: self.form.meta(id).save_items.clone(),
2705 };
2706
2707 let rest = if rest_children.is_empty() {
2713 Vec::new()
2714 } else {
2715 vec![QueuedNode {
2716 id,
2717 break_before: false,
2718 break_after: self.form.meta(id).page_break_after,
2719 break_target: None,
2720 children_override: Some(rest_children),
2721 text_lines_override: None,
2722 nested_child_overrides: None,
2723 }]
2724 };
2725
2726 Ok((partial_node, rest))
2727 }
2728
2729 fn layout_children(
2743 &self,
2744 children: &[FormNodeId],
2745 available: Size,
2746 strategy: LayoutStrategy,
2747 ) -> Result<Vec<LayoutNode>> {
2748 let expanded = self.expand_occur(children);
2749 match strategy {
2750 LayoutStrategy::Positioned => self.layout_positioned(&expanded),
2751 LayoutStrategy::TopToBottom => self.layout_tb(&expanded, available),
2752 LayoutStrategy::LeftToRightTB => self.layout_lr_tb(&expanded, available),
2753 LayoutStrategy::RightToLeftTB => self.layout_rl_tb(&expanded, available),
2754 LayoutStrategy::Table => self.layout_table(&expanded, available),
2755 LayoutStrategy::Row => self.layout_row(&expanded, available),
2756 }
2757 }
2758
2759 fn expand_occur(&self, children: &[FormNodeId]) -> Vec<FormNodeId> {
2772 let mut expanded = Vec::new();
2773 for &child_id in children {
2774 if self.is_layout_hidden(child_id) {
2775 continue;
2776 }
2777 let child = self.form.get(child_id);
2778 let count = if child.occur.is_repeating()
2780 && child.occur.count() > child.occur.min
2781 && self.has_field_descendants(child_id)
2782 && self.subtree_is_blank(child_id)
2783 {
2784 child.occur.min
2785 } else {
2786 child.occur.count()
2787 };
2788 let count = if count == 0
2792 && matches!(
2793 child.node_type,
2794 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
2795 )
2796 && self.has_field_descendants(child_id)
2797 {
2798 1
2799 } else {
2800 count
2801 };
2802 for _ in 0..count {
2803 expanded.push(child_id);
2804 }
2805 }
2806 expanded
2807 }
2808
2809 fn has_field_descendants(&self, id: FormNodeId) -> bool {
2811 let node = self.form.get(id);
2812 match &node.node_type {
2813 FormNodeType::Field { .. } | FormNodeType::Draw(..) | FormNodeType::Image { .. } => {
2814 true
2815 }
2816 FormNodeType::Subform
2818 | FormNodeType::Area
2819 | FormNodeType::ExclGroup
2820 | FormNodeType::SubformSet => {
2821 node.children.iter().any(|&c| self.has_field_descendants(c))
2822 }
2823 _ => false,
2824 }
2825 }
2826
2827 fn layout_positioned(&self, children: &[FormNodeId]) -> Result<Vec<LayoutNode>> {
2835 use crate::form::AnchorType;
2836
2837 let mut nodes = Vec::new();
2838 for &child_id in children {
2839 let child = self.form.get(child_id);
2840 let anchor = self.form.meta(child_id).anchor_type;
2841
2842 let extent = self.compute_extent(child_id);
2844 let w = extent.width;
2845 let h = extent.height;
2846
2847 let (dx, dy) = match anchor {
2848 AnchorType::TopLeft => (0.0, 0.0),
2849 AnchorType::TopCenter => (-w / 2.0, 0.0),
2850 AnchorType::TopRight => (-w, 0.0),
2851 AnchorType::MiddleLeft => (0.0, -h / 2.0),
2852 AnchorType::MiddleCenter => (-w / 2.0, -h / 2.0),
2853 AnchorType::MiddleRight => (-w, -h / 2.0),
2854 AnchorType::BottomLeft => (0.0, -h),
2855 AnchorType::BottomCenter => (-w / 2.0, -h),
2856 AnchorType::BottomRight => (-w, -h),
2857 };
2858
2859 let node = self.layout_single_node_with_extent(
2860 child_id,
2861 child,
2862 child.box_model.x + dx,
2863 child.box_model.y + dy,
2864 extent,
2865 None,
2866 )?;
2867 nodes.push(node);
2868 }
2869 Ok(nodes)
2870 }
2871
2872 fn child_h_align_offset(&self, child_id: FormNodeId, child_w: f64, parent_w: f64) -> f64 {
2875 match self.form.meta(child_id).style.h_align {
2876 Some(TextAlign::Center) => ((parent_w - child_w) / 2.0).max(0.0),
2877 Some(TextAlign::Right) => (parent_w - child_w).max(0.0),
2878 _ => 0.0,
2879 }
2880 }
2881
2882 fn shift_row_h_align(&self, row: &mut [LayoutNode], row_width: f64, parent_width: f64) {
2886 let align = row
2887 .first()
2888 .and_then(|n| self.form.meta(n.form_node).style.h_align);
2889 let offset = match align {
2890 Some(TextAlign::Center) => ((parent_width - row_width) / 2.0).max(0.0),
2891 Some(TextAlign::Right) => (parent_width - row_width).max(0.0),
2892 _ => return,
2893 };
2894 for node in row {
2895 node.rect.x += offset;
2896 }
2897 }
2898
2899 fn layout_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
2902 let mut nodes = Vec::new();
2903 let mut y_cursor = 0.0;
2904
2905 for &child_id in children {
2906 let child = self.form.get(child_id);
2907 let child_size = self.compute_extent_with_available(child_id, Some(available));
2908 let child_bottom = y_cursor + child_size.height;
2909
2910 if child_bottom > available.height + 0.5 {
2914 let remaining_height = (available.height - y_cursor).max(0.0);
2915
2916 if remaining_height > 0.0 && self.is_splittable_text_leaf(child_id) {
2921 let txt = match &child.node_type {
2922 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
2923 FormNodeType::Field { value } => value.as_str(),
2924 _ => "",
2925 };
2926 let child_style = &self.form.meta(child_id).style;
2927 let para_margins = child_style
2928 .margin_left_pt
2929 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2930 + child_style
2931 .margin_right_pt
2932 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2933 let child_border_w = child_style
2934 .border_width_pt
2935 .unwrap_or(child.box_model.border_width);
2936 let insets_w =
2937 child.box_model.margins.horizontal() + child_border_w * 2.0 + para_margins;
2938 let max_w = (child_size.width - insets_w).max(1.0);
2939 let wrapped = text::wrap_text(
2940 txt,
2941 max_w,
2942 &child.font,
2943 child_style.text_indent_pt.unwrap_or(0.0),
2944 child_style.line_height_pt,
2945 );
2946 let (partial, _) =
2947 self.split_text_node(child_id, y_cursor, remaining_height, &wrapped.lines)?;
2948
2949 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
2950 let x = self.child_h_align_offset(
2951 child_id,
2952 partial.rect.width,
2953 available.width,
2954 );
2955 let mut partial = partial;
2956 partial.rect.x = x;
2957 nodes.push(partial);
2958 } else if nodes.is_empty() {
2959 let x =
2961 self.child_h_align_offset(child_id, child_size.width, available.width);
2962 let node = self.layout_single_node_with_extent(
2963 child_id, child, x, y_cursor, child_size, None,
2964 )?;
2965 nodes.push(node);
2966 }
2967 } else if remaining_height > 0.0 && self.can_split(child_id) {
2968 let (partial, _) = self.split_tb_node(
2969 child_id,
2970 y_cursor,
2971 remaining_height,
2972 available,
2973 None,
2974 None,
2975 )?;
2976 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
2977 let x = self.child_h_align_offset(
2978 child_id,
2979 partial.rect.width,
2980 available.width,
2981 );
2982 let mut partial = partial;
2983 partial.rect.x = x;
2984 nodes.push(partial);
2985 } else if nodes.is_empty() {
2986 let x =
2988 self.child_h_align_offset(child_id, child_size.width, available.width);
2989 let node = self.layout_single_node_with_extent(
2990 child_id, child, x, y_cursor, child_size, None,
2991 )?;
2992 nodes.push(node);
2993 }
2994 } else if nodes.is_empty() {
2995 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
2997 let node = self.layout_single_node_with_extent(
2998 child_id, child, x, y_cursor, child_size, None,
2999 )?;
3000 nodes.push(node);
3001 }
3002
3003 break;
3004 }
3005
3006 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
3007
3008 let node = self
3009 .layout_single_node_with_extent(child_id, child, x, y_cursor, child_size, None)?;
3010 nodes.push(node);
3011
3012 y_cursor = child_bottom;
3013 }
3014 Ok(nodes)
3015 }
3016
3017 fn layout_lr_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3021 let mut nodes = Vec::new();
3022 let mut x_cursor = 0.0;
3023 let mut y_cursor = 0.0;
3024 let mut row_height = 0.0_f64;
3025 let mut row_start = 0_usize;
3026
3027 for &child_id in children {
3028 let child = self.form.get(child_id);
3029 let child_size = self.compute_extent(child_id);
3030
3031 if x_cursor + child_size.width > available.width && x_cursor > 0.0 {
3033 self.shift_row_h_align(&mut nodes[row_start..], x_cursor, available.width);
3034 row_start = nodes.len();
3035 y_cursor += row_height;
3036 x_cursor = 0.0;
3037 row_height = 0.0;
3038 }
3039
3040 let node = self.layout_single_node(child_id, child, x_cursor, y_cursor, None)?;
3041 nodes.push(node);
3042
3043 x_cursor += child_size.width;
3044 row_height = row_height.max(child_size.height);
3045 }
3046 self.shift_row_h_align(&mut nodes[row_start..], x_cursor, available.width);
3047 Ok(nodes)
3048 }
3049
3050 fn layout_rl_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3054 let mut nodes = Vec::new();
3055 let mut x_cursor = available.width;
3056 let mut y_cursor = 0.0;
3057 let mut row_height = 0.0_f64;
3058
3059 for &child_id in children {
3060 let child = self.form.get(child_id);
3061 let child_size = self.compute_extent(child_id);
3062
3063 if x_cursor - child_size.width < 0.0 && x_cursor < available.width {
3065 y_cursor += row_height;
3066 x_cursor = available.width;
3067 row_height = 0.0;
3068 }
3069
3070 let h_align = self.form.meta(child_id).style.h_align;
3072 let x = match h_align {
3073 Some(TextAlign::Left) => {
3074 x_cursor -= child_size.width;
3075 0.0
3076 }
3077 Some(TextAlign::Center) => {
3078 x_cursor -= child_size.width;
3079 ((available.width - child_size.width) / 2.0).max(0.0)
3080 }
3081 _ => {
3082 x_cursor -= child_size.width;
3083 x_cursor
3084 }
3085 };
3086 let node = self.layout_single_node(child_id, child, x, y_cursor, None)?;
3087 nodes.push(node);
3088
3089 row_height = row_height.max(child_size.height);
3090 }
3091 Ok(nodes)
3092 }
3093
3094 fn layout_table(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3099 let max_cells = children
3101 .iter()
3102 .map(|&row_id| self.form.get(row_id).children.len())
3103 .max()
3104 .unwrap_or(0);
3105 if max_cells == 0 {
3106 return Ok(Vec::new());
3107 }
3108 let col_width = available.width / max_cells as f64;
3109 let col_widths: Vec<f64> = vec![col_width; max_cells];
3110 self.layout_table_rows(children, available, &col_widths)
3111 }
3112
3113 fn layout_table_rows(
3120 &self,
3121 children: &[FormNodeId],
3122 available: Size,
3123 col_widths: &[f64],
3124 ) -> Result<Vec<LayoutNode>> {
3125 let expanded = self.expand_occur(children);
3126 let mut nodes = Vec::new();
3127 let mut y_cursor = 0.0;
3128
3129 for &row_id in &expanded {
3130 let row_node = self.form.get(row_id);
3131 let row_children = self.expand_occur(&row_node.children);
3132
3133 let mut cells = Vec::new();
3135 let mut x_cursor = 0.0;
3136 let mut col_idx = 0usize;
3137 let mut max_cell_height = 0.0_f64;
3138
3139 for &cell_id in &row_children {
3140 if col_idx >= col_widths.len() {
3141 break;
3142 }
3143 let cell = self.form.get(cell_id);
3144 let span = cell.col_span;
3145
3146 let cell_width = if span == -1 {
3148 col_widths[col_idx..].iter().sum::<f64>()
3150 } else {
3151 let span_count =
3152 (span.max(1) as usize).min(col_widths.len().saturating_sub(col_idx));
3153 col_widths[col_idx..col_idx + span_count]
3154 .iter()
3155 .sum::<f64>()
3156 };
3157
3158 let cell_available = Size {
3160 width: cell_width,
3161 height: available.height - y_cursor,
3162 };
3163 let cell_height = self
3164 .compute_extent_with_available(cell_id, Some(cell_available))
3165 .height;
3166 let cell_extent = Size {
3167 width: cell_width,
3168 height: cell_height,
3169 };
3170
3171 let cell_node = self.layout_single_node_with_extent(
3172 cell_id,
3173 cell,
3174 x_cursor,
3175 0.0,
3176 cell_extent,
3177 None,
3178 )?;
3179 max_cell_height = max_cell_height.max(cell_extent.height);
3180 cells.push(cell_node);
3181
3182 x_cursor += cell_width;
3183 if span == -1 {
3184 col_idx = col_widths.len();
3185 } else {
3186 col_idx += span.max(1) as usize;
3187 }
3188 }
3189
3190 for cell in &mut cells {
3192 cell.rect.height = max_cell_height;
3193 }
3194
3195 let row_layout = LayoutNode {
3197 form_node: row_id,
3198 rect: Rect::new(0.0, y_cursor, available.width, max_cell_height),
3199 name: row_node.name.clone(),
3200 content: LayoutContent::None,
3201 children: cells,
3202 style: self.form.meta(row_id).style.clone(),
3203 display_items: self.form.meta(row_id).display_items.clone(),
3204 save_items: self.form.meta(row_id).save_items.clone(),
3205 };
3206 nodes.push(row_layout);
3207
3208 y_cursor += max_cell_height;
3209 }
3210 Ok(nodes)
3211 }
3212
3213 fn resolve_column_widths_with_override(
3214 &self,
3215 table_node: &FormNode,
3216 available_width: f64,
3217 children_override: Option<&[FormNodeId]>,
3218 ) -> Vec<f64> {
3219 let specified = &table_node.column_widths;
3220 let node_children = children_override.unwrap_or(&table_node.children);
3221
3222 let max_cols_from_rows = node_children
3224 .iter()
3225 .map(|&row_id| {
3226 let row = self.form.get(row_id);
3227 row.children
3228 .iter()
3229 .map(|&cell_id| {
3230 let cell = self.form.get(cell_id);
3231 cell.col_span.max(1) as usize
3232 })
3233 .sum::<usize>()
3234 })
3235 .max()
3236 .unwrap_or(0);
3237
3238 let num_cols = specified.len().max(max_cols_from_rows);
3239 if num_cols == 0 {
3240 return vec![];
3241 }
3242
3243 let mut widths = Vec::with_capacity(num_cols);
3244 for i in 0..num_cols {
3245 let spec_value = specified.get(i).copied().unwrap_or(-1.0);
3246 if spec_value >= 0.0 {
3247 widths.push(spec_value);
3248 } else {
3249 let mut max_w = 0.0_f64;
3251 for &row_id in node_children {
3252 let row = self.form.get(row_id);
3253 let mut col_idx = 0usize;
3254 for &cell_id in &row.children {
3255 let cell = self.form.get(cell_id);
3256 let span = cell.col_span;
3257 if col_idx == i && span == 1 {
3258 let cell_extent = self.compute_extent(cell_id);
3259 max_w = max_w.max(cell_extent.width);
3260 }
3261 col_idx += span.max(1) as usize;
3262 }
3263 }
3264 widths.push(max_w);
3265 }
3266 }
3267
3268 let total: f64 = widths.iter().sum();
3270 if total > available_width && total > 0.0 {
3271 let scale = available_width / total;
3272 for w in &mut widths {
3273 *w *= scale;
3274 }
3275 }
3276
3277 widths
3278 }
3279
3280 fn layout_row(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3283 let mut nodes = Vec::new();
3284 let mut x_cursor = 0.0;
3285
3286 for &child_id in children {
3287 let child = self.form.get(child_id);
3288 let child_size = self.compute_extent(child_id);
3289
3290 let node = self.layout_single_node(child_id, child, x_cursor, 0.0, None)?;
3291 nodes.push(node);
3292
3293 x_cursor += child_size.width;
3294
3295 if x_cursor > available.width {
3296 break;
3297 }
3298 }
3299 Ok(nodes)
3300 }
3301
3302 fn layout_single_node(
3304 &self,
3305 id: FormNodeId,
3306 node: &FormNode,
3307 x: f64,
3308 y: f64,
3309 children_override: Option<&[FormNodeId]>,
3310 ) -> Result<LayoutNode> {
3311 let extent = self.compute_extent_with_override(id, children_override);
3312 self.layout_single_node_with_extent(id, node, x, y, extent, children_override)
3313 }
3314
3315 fn layout_single_node_with_extent(
3317 &self,
3318 id: FormNodeId,
3319 node: &FormNode,
3320 x: f64,
3321 y: f64,
3322 extent: Size,
3323 children_override: Option<&[FormNodeId]>,
3324 ) -> Result<LayoutNode> {
3325 if self.form.meta(id).presence.is_layout_hidden() {
3329 let hidden_meta = self.form.meta(id);
3330 return Ok(LayoutNode {
3331 form_node: id,
3332 rect: Rect::new(x, y, extent.width, extent.height),
3333 name: node.name.clone(),
3334 content: LayoutContent::None,
3335 children: Vec::new(),
3336 style: Default::default(),
3337 display_items: hidden_meta.display_items.clone(),
3338 save_items: hidden_meta.save_items.clone(),
3339 });
3340 }
3341
3342 let node_style = &self.form.meta(id).style;
3343 let para_margins = node_style
3344 .margin_left_pt
3345 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
3346 + node_style
3347 .margin_right_pt
3348 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
3349
3350 let content = match &node.node_type {
3351 FormNodeType::Field { value } => {
3352 let meta = self.form.meta(id);
3353 let display_val = resolve_display_value(value, meta);
3354 if !display_val.is_empty() && node.children.is_empty() {
3355 let border_w = node_style
3356 .border_width_pt
3357 .unwrap_or(node.box_model.border_width);
3358 let insets_w =
3359 node.box_model.margins.horizontal() + border_w * 2.0 + para_margins;
3360 let max_w = (extent.width - insets_w).max(0.0);
3361 let wrapped = text::wrap_text(
3362 &display_val,
3363 max_w,
3364 &node.font,
3365 node_style.text_indent_pt.unwrap_or(0.0),
3366 node_style.line_height_pt,
3367 );
3368 LayoutContent::WrappedText {
3369 lines: wrapped.lines,
3370 first_line_of_para: wrapped.first_line_of_para,
3371 font_size: node.font.size,
3372 text_align: node.font.text_align,
3373 font_family: node.font.typeface,
3374 space_above_pt: node_style.space_above_pt,
3375 space_below_pt: node_style.space_below_pt,
3376 from_field: true,
3377 }
3378 } else {
3379 LayoutContent::Field {
3380 value: display_val.to_string(),
3381 field_kind: meta.field_kind,
3382 font_size: node.font.size,
3383 font_family: node.font.typeface,
3384 }
3385 }
3386 }
3387 FormNodeType::Draw(DrawContent::Text(content)) => {
3388 if !content.is_empty() && node.children.is_empty() {
3389 let border_w = node_style
3390 .border_width_pt
3391 .unwrap_or(node.box_model.border_width);
3392 let insets_w =
3393 node.box_model.margins.horizontal() + border_w * 2.0 + para_margins;
3394 let max_w = (extent.width - insets_w).max(0.0);
3395 let wrapped = text::wrap_text(
3396 content,
3397 max_w,
3398 &node.font,
3399 node_style.text_indent_pt.unwrap_or(0.0),
3400 node_style.line_height_pt,
3401 );
3402 LayoutContent::WrappedText {
3403 lines: wrapped.lines,
3404 first_line_of_para: wrapped.first_line_of_para,
3405 font_size: node.font.size,
3406 text_align: node.font.text_align,
3407 font_family: node.font.typeface,
3408 space_above_pt: node_style.space_above_pt,
3409 space_below_pt: node_style.space_below_pt,
3410 from_field: false,
3411 }
3412 } else {
3413 LayoutContent::Text(content.clone())
3414 }
3415 }
3416 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
3417 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
3418 data: data.clone(),
3419 mime_type: mime_type.clone(),
3420 },
3421 _ => LayoutContent::None,
3422 };
3423
3424 let child_available = Size {
3425 width: node.box_model.content_width().min(extent.width),
3426 height: node.box_model.content_height().min(extent.height),
3427 };
3428
3429 let node_children = children_override.unwrap_or(&node.children);
3430
3431 let children = if node_children.is_empty() {
3432 Vec::new()
3433 } else if node.layout == LayoutStrategy::Table {
3434 let col_widths = self.resolve_column_widths_with_override(
3437 node,
3438 child_available.width,
3439 children_override,
3440 );
3441 self.layout_table_rows(node_children, child_available, &col_widths)?
3442 } else {
3443 self.layout_children(node_children, child_available, node.layout)?
3444 };
3445 let mut children = children;
3446
3447 if node.layout == LayoutStrategy::Positioned {
3448 let dx = node.box_model.margins.left;
3449 let mut dy = node.box_model.margins.top;
3450 if let Some(override_children) = children_override {
3451 if let Some(y_base) = override_children
3452 .iter()
3453 .map(|&cid| self.form.get(cid).box_model.y)
3454 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
3455 {
3456 dy -= y_base;
3457 }
3458 }
3459 if dx != 0.0 || dy != 0.0 {
3460 for child in &mut children {
3461 child.rect.x += dx;
3462 child.rect.y += dy;
3463 }
3464 }
3465 }
3466
3467 Ok(LayoutNode {
3468 form_node: id,
3469 rect: Rect::new(x, y, extent.width, extent.height),
3470 name: node.name.clone(),
3471 content,
3472 children,
3473 style: self.form.meta(id).style.clone(),
3474 display_items: self.form.meta(id).display_items.clone(),
3475 save_items: self.form.meta(id).save_items.clone(),
3476 })
3477 }
3478
3479 pub fn compute_extent(&self, id: FormNodeId) -> Size {
3484 self.compute_extent_with_available(id, None)
3485 }
3486
3487 pub fn compute_extent_with_override(
3489 &self,
3490 id: FormNodeId,
3491 children_override: Option<&[FormNodeId]>,
3492 ) -> Size {
3493 self.compute_extent_with_available_and_override(id, None, children_override)
3494 }
3495
3496 fn compute_extent_with_available(&self, id: FormNodeId, available: Option<Size>) -> Size {
3503 self.compute_extent_with_available_and_override(id, available, None)
3504 }
3505
3506 fn compute_extent_with_available_and_override(
3507 &self,
3508 id: FormNodeId,
3509 available: Option<Size>,
3510 children_override: Option<&[FormNodeId]>,
3511 ) -> Size {
3512 let node = self.form.get(id);
3513 let bm = &node.box_model;
3514 let ext_style = &self.form.meta(id).style;
3515
3516 let is_tb_with_children = node.layout == LayoutStrategy::TopToBottom
3520 && !children_override.unwrap_or(&node.children).is_empty();
3521 if let (Some(w), Some(h)) = (bm.width, bm.height) {
3522 if !is_tb_with_children {
3523 return Size {
3524 width: w,
3525 height: h,
3526 };
3527 }
3528 }
3529
3530 let mut content_size = Size::default();
3532
3533 let node_children = children_override.unwrap_or(&node.children);
3534
3535 if !node_children.is_empty() {
3536 let expanded = if children_override.is_some() {
3540 node_children.to_vec()
3541 } else {
3542 self.expand_occur(node_children)
3543 };
3544 match node.layout {
3545 LayoutStrategy::TopToBottom => {
3546 for &child_id in &expanded {
3548 let cs = self.compute_extent_with_available(child_id, available);
3549 content_size.width = content_size.width.max(cs.width);
3550 content_size.height += cs.height;
3551 }
3552 }
3553 LayoutStrategy::LeftToRightTB | LayoutStrategy::Row => {
3554 for &child_id in &expanded {
3555 let cs = self.compute_extent_with_available(child_id, available);
3556 content_size.width += cs.width;
3557 content_size.height = content_size.height.max(cs.height);
3558 }
3559 }
3560 LayoutStrategy::Table => {
3561 let avail_w = available.map(|a| a.width).unwrap_or(f64::MAX);
3562 let col_widths =
3563 self.resolve_column_widths_with_override(node, avail_w, children_override);
3564 let table_width: f64 = col_widths.iter().sum();
3565 content_size.width = content_size.width.max(table_width);
3566 for &row_id in &expanded {
3568 let row_extent = self.compute_extent_with_available(row_id, available);
3569 content_size.height += row_extent.height;
3570 }
3571 }
3572 _ => {
3573 let y_base = if children_override.is_some() {
3579 node_children
3580 .iter()
3581 .map(|&cid| self.form.get(cid).box_model.y)
3582 .fold(f64::MAX, f64::min)
3583 } else {
3584 0.0
3585 };
3586 for &child_id in node_children {
3587 let child = self.form.get(child_id);
3588 let cs = self.compute_extent(child_id);
3589 content_size.width = content_size.width.max(child.box_model.x + cs.width);
3590 content_size.height = content_size
3591 .height
3592 .max(child.box_model.y - y_base + cs.height);
3593 }
3594 }
3595 }
3596 } else {
3597 let text_content = match &node.node_type {
3599 FormNodeType::Draw(DrawContent::Text(content)) => Some(content.as_str()),
3600 FormNodeType::Field { value } => Some(value.as_str()),
3601 _ => None,
3602 };
3603
3604 if let Some(txt) = text_content {
3605 if !txt.is_empty() {
3606 let ext_style = &self.form.meta(id).style;
3607 let ext_para = ext_style
3608 .margin_left_pt
3609 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
3610 + ext_style
3611 .margin_right_pt
3612 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
3613 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
3614 let insets_w = bm.margins.horizontal() + border_w * 2.0 + ext_para;
3615 let space_above = ext_style.space_above_pt.unwrap_or(0.0);
3616 let space_below = ext_style.space_below_pt.unwrap_or(0.0);
3617 let text_size = if let Some(w) = bm.width {
3620 let max_text_width = (w - insets_w).max(0.0);
3621 text::wrap_text(
3622 txt,
3623 max_text_width,
3624 &node.font,
3625 ext_style.text_indent_pt.unwrap_or(0.0),
3626 ext_style.line_height_pt,
3627 )
3628 .size
3629 } else if let Some(avail) = available {
3630 let max_text_width = (avail.width - insets_w).max(0.0);
3631 text::wrap_text(
3632 txt,
3633 max_text_width,
3634 &node.font,
3635 ext_style.text_indent_pt.unwrap_or(0.0),
3636 ext_style.line_height_pt,
3637 )
3638 .size
3639 } else {
3640 text::measure_text(txt, &node.font)
3641 };
3642 content_size.width = content_size.width.max(text_size.width);
3643 content_size.height = content_size
3644 .height
3645 .max(text_size.height + space_above + space_below);
3646 }
3647 }
3648 }
3649
3650 if let Some(avail) = available {
3652 if bm.width.is_none() {
3653 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
3654 let insets_w = bm.margins.horizontal() + border_w * 2.0;
3655 content_size.width = content_size.width.max(avail.width - insets_w);
3656 }
3657 }
3658
3659 let mut result = bm.outer_size(content_size);
3660
3661 if is_tb_with_children && bm.height.is_some() {
3667 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
3668 let mut unclamped_h = content_size.height + bm.margins.vertical() + border_w * 2.0;
3669 if let Some(ref cap) = bm.caption {
3670 if matches!(
3671 cap.placement,
3672 crate::types::CaptionPlacement::Top | crate::types::CaptionPlacement::Bottom
3673 ) {
3674 unclamped_h += cap.reserve.unwrap_or(0.0);
3675 }
3676 }
3677 result.height = result.height.max(unclamped_h);
3678 }
3679
3680 result
3681 }
3682}
3683
3684#[allow(dead_code)]
3687fn find_page_area_by_target(page_areas: &[PageAreaInfo], target: &str) -> Option<usize> {
3688 if let Some(idx) = page_areas.iter().position(|pa| pa.name == target) {
3690 return Some(idx);
3691 }
3692 if let Some(idx) = page_areas
3694 .iter()
3695 .position(|pa| pa.xfa_id.as_deref() == Some(target))
3696 {
3697 return Some(idx);
3698 }
3699 if let Some(rest) = target.strip_prefix("pageArea") {
3701 if let Some(idx_str) = rest.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
3702 if let Ok(idx) = idx_str.parse::<usize>() {
3703 if idx < page_areas.len() {
3704 return Some(idx);
3705 }
3706 }
3707 }
3708 if rest.is_empty() && !page_areas.is_empty() {
3710 return Some(0);
3711 }
3712 }
3713 None
3714}
3715
3716fn primary_content_area(pa: &PageAreaInfo) -> &ContentArea {
3718 let max_area = pa
3719 .content_areas
3720 .iter()
3721 .map(|ca| ca.width * ca.height)
3722 .fold(0.0_f64, f64::max);
3723 pa.content_areas
3724 .iter()
3725 .find(|ca| {
3726 let a = ca.width * ca.height;
3727 a >= max_area * 0.90 || pa.content_areas.len() == 1
3728 })
3729 .unwrap_or(&pa.content_areas[0])
3730}
3731
3732struct PageAreaInfo {
3733 name: String,
3735 xfa_id: Option<String>,
3737 content_areas: Vec<ContentArea>,
3738 page_width: f64,
3739 page_height: f64,
3740 fixed_nodes: Vec<FormNodeId>,
3743}
3744
3745#[cfg(test)]
3746mod tests {
3747 use super::*;
3748 use crate::form::Occur;
3749 use crate::text::FontMetrics;
3750 use crate::types::BoxModel;
3751
3752 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
3753 tree.add_node(FormNode {
3754 name: name.to_string(),
3755 node_type: FormNodeType::Field {
3756 value: name.to_string(),
3757 },
3758 box_model: BoxModel {
3759 width: Some(w),
3760 height: Some(h),
3761 max_width: f64::MAX,
3762 max_height: f64::MAX,
3763 ..Default::default()
3764 },
3765 layout: LayoutStrategy::Positioned,
3766 children: vec![],
3767 occur: Occur::once(),
3768 font: FontMetrics::default(),
3769 calculate: None,
3770 validate: None,
3771 column_widths: vec![],
3772 col_span: 1,
3773 })
3774 }
3775
3776 fn make_subform(
3777 tree: &mut FormTree,
3778 name: &str,
3779 strategy: LayoutStrategy,
3780 w: Option<f64>,
3781 h: Option<f64>,
3782 children: Vec<FormNodeId>,
3783 ) -> FormNodeId {
3784 tree.add_node(FormNode {
3785 name: name.to_string(),
3786 node_type: FormNodeType::Subform,
3787 box_model: BoxModel {
3788 width: w,
3789 height: h,
3790 max_width: f64::MAX,
3791 max_height: f64::MAX,
3792 ..Default::default()
3793 },
3794 layout: strategy,
3795 children,
3796 occur: Occur::once(),
3797 font: FontMetrics::default(),
3798 calculate: None,
3799 validate: None,
3800 column_widths: vec![],
3801 col_span: 1,
3802 })
3803 }
3804
3805 fn count_leaf_nodes(page: &LayoutPage) -> usize {
3807 fn count(nodes: &[LayoutNode]) -> usize {
3808 nodes
3809 .iter()
3810 .map(|n| {
3811 if n.children.is_empty() {
3812 1
3813 } else {
3814 count(&n.children)
3815 }
3816 })
3817 .sum()
3818 }
3819 count(&page.nodes)
3820 }
3821
3822 fn make_field_value(
3823 tree: &mut FormTree,
3824 name: &str,
3825 value: &str,
3826 w: f64,
3827 h: f64,
3828 ) -> FormNodeId {
3829 tree.add_node(FormNode {
3830 name: name.to_string(),
3831 node_type: FormNodeType::Field {
3832 value: value.to_string(),
3833 },
3834 box_model: BoxModel {
3835 width: Some(w),
3836 height: Some(h),
3837 max_width: f64::MAX,
3838 max_height: f64::MAX,
3839 ..Default::default()
3840 },
3841 layout: LayoutStrategy::Positioned,
3842 children: vec![],
3843 occur: Occur::once(),
3844 font: FontMetrics::default(),
3845 calculate: None,
3846 validate: None,
3847 column_widths: vec![],
3848 col_span: 1,
3849 })
3850 }
3851
3852 fn make_draw_text(tree: &mut FormTree, name: &str, text: &str, w: f64, h: f64) -> FormNodeId {
3853 tree.add_node(FormNode {
3854 name: name.to_string(),
3855 node_type: FormNodeType::Draw(DrawContent::Text(text.to_string())),
3856 box_model: BoxModel {
3857 width: Some(w),
3858 height: Some(h),
3859 max_width: f64::MAX,
3860 max_height: f64::MAX,
3861 ..Default::default()
3862 },
3863 layout: LayoutStrategy::Positioned,
3864 children: vec![],
3865 occur: Occur::once(),
3866 font: FontMetrics::default(),
3867 calculate: None,
3868 validate: None,
3869 column_widths: vec![],
3870 col_span: 1,
3871 })
3872 }
3873
3874 fn make_page_area(tree: &mut FormTree, name: &str, width: f64, height: f64) -> FormNodeId {
3875 tree.add_node(FormNode {
3876 name: name.to_string(),
3877 node_type: FormNodeType::PageArea {
3878 content_areas: vec![ContentArea {
3879 name: "Body".to_string(),
3880 x: 0.0,
3881 y: 0.0,
3882 width,
3883 height,
3884 leader: None,
3885 trailer: None,
3886 }],
3887 },
3888 box_model: BoxModel {
3889 width: Some(width),
3890 height: Some(height),
3891 max_width: f64::MAX,
3892 max_height: f64::MAX,
3893 ..Default::default()
3894 },
3895 layout: LayoutStrategy::Positioned,
3896 children: vec![],
3897 occur: Occur::once(),
3898 font: FontMetrics::default(),
3899 calculate: None,
3900 validate: None,
3901 column_widths: vec![],
3902 col_span: 1,
3903 })
3904 }
3905
3906 fn make_root(tree: &mut FormTree, children: Vec<FormNodeId>) -> FormNodeId {
3907 tree.add_node(FormNode {
3908 name: "Root".to_string(),
3909 node_type: FormNodeType::Root,
3910 box_model: BoxModel {
3911 max_width: f64::MAX,
3912 max_height: f64::MAX,
3913 ..Default::default()
3914 },
3915 layout: LayoutStrategy::TopToBottom,
3916 children,
3917 occur: Occur::once(),
3918 font: FontMetrics::default(),
3919 calculate: None,
3920 validate: None,
3921 column_widths: vec![],
3922 col_span: 1,
3923 })
3924 }
3925
3926 fn mark_data_bound(tree: &mut FormTree, id: FormNodeId) {
3927 let name = tree.get(id).name.clone();
3928 tree.meta_mut(id).data_bind_ref = Some(format!("$.{name}"));
3929 }
3930
3931 #[test]
3932 fn positioned_layout() {
3933 let mut tree = FormTree::new();
3934 let f1 = tree.add_node(FormNode {
3935 name: "Field1".to_string(),
3936 node_type: FormNodeType::Field {
3937 value: "A".to_string(),
3938 },
3939 box_model: BoxModel {
3940 width: Some(100.0),
3941 height: Some(20.0),
3942 x: 10.0,
3943 y: 30.0,
3944 max_width: f64::MAX,
3945 max_height: f64::MAX,
3946 ..Default::default()
3947 },
3948 layout: LayoutStrategy::Positioned,
3949 children: vec![],
3950 occur: Occur::once(),
3951 font: FontMetrics::default(),
3952 calculate: None,
3953 validate: None,
3954 column_widths: vec![],
3955 col_span: 1,
3956 });
3957 let f2 = tree.add_node(FormNode {
3958 name: "Field2".to_string(),
3959 node_type: FormNodeType::Field {
3960 value: "B".to_string(),
3961 },
3962 box_model: BoxModel {
3963 width: Some(100.0),
3964 height: Some(20.0),
3965 x: 10.0,
3966 y: 60.0,
3967 max_width: f64::MAX,
3968 max_height: f64::MAX,
3969 ..Default::default()
3970 },
3971 layout: LayoutStrategy::Positioned,
3972 children: vec![],
3973 occur: Occur::once(),
3974 font: FontMetrics::default(),
3975 calculate: None,
3976 validate: None,
3977 column_widths: vec![],
3978 col_span: 1,
3979 });
3980 let root = tree.add_node(FormNode {
3981 name: "Root".to_string(),
3982 node_type: FormNodeType::Root,
3983 box_model: BoxModel {
3984 width: Some(612.0),
3985 height: Some(792.0),
3986 max_width: f64::MAX,
3987 max_height: f64::MAX,
3988 ..Default::default()
3989 },
3990 layout: LayoutStrategy::Positioned,
3991 children: vec![f1, f2],
3992 occur: Occur::once(),
3993 font: FontMetrics::default(),
3994 calculate: None,
3995 validate: None,
3996 column_widths: vec![],
3997 col_span: 1,
3998 });
3999
4000 let engine = LayoutEngine::new(&tree);
4001 let result = engine.layout(root).unwrap();
4002
4003 assert_eq!(result.pages.len(), 1);
4004 let page = &result.pages[0];
4005 assert_eq!(page.nodes.len(), 2);
4006 assert_eq!(page.nodes[0].rect.x, 10.0);
4007 assert_eq!(page.nodes[0].rect.y, 30.0);
4008 assert_eq!(page.nodes[1].rect.x, 10.0);
4009 assert_eq!(page.nodes[1].rect.y, 60.0);
4010 }
4011
4012 #[test]
4013 fn positioned_children_offset_by_parent_margins() {
4014 use crate::types::Insets;
4015
4016 let mut tree = FormTree::new();
4017 let child = tree.add_node(FormNode {
4018 name: "Child".to_string(),
4019 node_type: FormNodeType::Field {
4020 value: "Child".to_string(),
4021 },
4022 box_model: BoxModel {
4023 width: Some(80.0),
4024 height: Some(20.0),
4025 x: 0.0,
4026 y: 0.0,
4027 max_width: f64::MAX,
4028 max_height: f64::MAX,
4029 ..Default::default()
4030 },
4031 layout: LayoutStrategy::Positioned,
4032 children: vec![],
4033 occur: Occur::once(),
4034 font: FontMetrics::default(),
4035 calculate: None,
4036 validate: None,
4037 column_widths: vec![],
4038 col_span: 1,
4039 });
4040
4041 let parent = tree.add_node(FormNode {
4042 name: "Parent".to_string(),
4043 node_type: FormNodeType::Subform,
4044 box_model: BoxModel {
4045 width: Some(200.0),
4046 height: Some(100.0),
4047 x: 50.0,
4048 y: 40.0,
4049 margins: Insets {
4050 top: 15.0,
4051 right: 0.0,
4052 bottom: 0.0,
4053 left: 10.0,
4054 },
4055 max_width: f64::MAX,
4056 max_height: f64::MAX,
4057 ..Default::default()
4058 },
4059 layout: LayoutStrategy::Positioned,
4060 children: vec![child],
4061 occur: Occur::once(),
4062 font: FontMetrics::default(),
4063 calculate: None,
4064 validate: None,
4065 column_widths: vec![],
4066 col_span: 1,
4067 });
4068
4069 let root = tree.add_node(FormNode {
4070 name: "Root".to_string(),
4071 node_type: FormNodeType::Root,
4072 box_model: BoxModel {
4073 width: Some(612.0),
4074 height: Some(792.0),
4075 max_width: f64::MAX,
4076 max_height: f64::MAX,
4077 ..Default::default()
4078 },
4079 layout: LayoutStrategy::Positioned,
4080 children: vec![parent],
4081 occur: Occur::once(),
4082 font: FontMetrics::default(),
4083 calculate: None,
4084 validate: None,
4085 column_widths: vec![],
4086 col_span: 1,
4087 });
4088
4089 let engine = LayoutEngine::new(&tree);
4090 let result = engine.layout(root).unwrap();
4091
4092 let parent_node = &result.pages[0].nodes[0];
4093 assert_eq!(parent_node.rect.x, 50.0);
4094 assert_eq!(parent_node.rect.y, 40.0);
4095 assert_eq!(parent_node.children[0].rect.x, 10.0);
4096 assert_eq!(parent_node.children[0].rect.y, 15.0);
4097 }
4098
4099 #[test]
4100 fn tb_layout() {
4101 let mut tree = FormTree::new();
4102 let f1 = make_field(&mut tree, "Field1", 200.0, 30.0);
4103 let f2 = make_field(&mut tree, "Field2", 200.0, 30.0);
4104 let f3 = make_field(&mut tree, "Field3", 200.0, 30.0);
4105
4106 let root = tree.add_node(FormNode {
4107 name: "Root".to_string(),
4108 node_type: FormNodeType::Root,
4109 box_model: BoxModel {
4110 width: Some(612.0),
4111 height: Some(792.0),
4112 max_width: f64::MAX,
4113 max_height: f64::MAX,
4114 ..Default::default()
4115 },
4116 layout: LayoutStrategy::TopToBottom,
4117 children: vec![f1, f2, f3],
4118 occur: Occur::once(),
4119 font: FontMetrics::default(),
4120 calculate: None,
4121 validate: None,
4122 column_widths: vec![],
4123 col_span: 1,
4124 });
4125
4126 let engine = LayoutEngine::new(&tree);
4127 let result = engine.layout(root).unwrap();
4128
4129 assert_eq!(result.pages.len(), 1);
4130 let page = &result.pages[0];
4131 assert_eq!(page.nodes.len(), 3);
4132 assert_eq!(page.nodes[0].rect.y, 0.0);
4133 assert_eq!(page.nodes[1].rect.y, 30.0);
4134 assert_eq!(page.nodes[2].rect.y, 60.0);
4135 }
4136
4137 #[test]
4138 fn lr_tb_wrapping() {
4139 let mut tree = FormTree::new();
4140 let f1 = make_field(&mut tree, "F1", 250.0, 30.0);
4142 let f2 = make_field(&mut tree, "F2", 250.0, 30.0);
4143 let f3 = make_field(&mut tree, "F3", 250.0, 30.0);
4144
4145 let root = tree.add_node(FormNode {
4146 name: "Root".to_string(),
4147 node_type: FormNodeType::Root,
4148 box_model: BoxModel {
4149 width: Some(600.0),
4150 height: Some(792.0),
4151 max_width: f64::MAX,
4152 max_height: f64::MAX,
4153 ..Default::default()
4154 },
4155 layout: LayoutStrategy::LeftToRightTB,
4156 children: vec![f1, f2, f3],
4157 occur: Occur::once(),
4158 font: FontMetrics::default(),
4159 calculate: None,
4160 validate: None,
4161 column_widths: vec![],
4162 col_span: 1,
4163 });
4164
4165 let engine = LayoutEngine::new(&tree);
4166 let result = engine.layout(root).unwrap();
4167
4168 let page = &result.pages[0];
4169 assert_eq!(page.nodes.len(), 3);
4170 assert_eq!(page.nodes[0].rect.x, 0.0);
4172 assert_eq!(page.nodes[0].rect.y, 0.0);
4173 assert_eq!(page.nodes[1].rect.x, 250.0);
4174 assert_eq!(page.nodes[1].rect.y, 0.0);
4175 assert_eq!(page.nodes[2].rect.x, 0.0);
4177 assert_eq!(page.nodes[2].rect.y, 30.0);
4178 }
4179
4180 #[test]
4181 fn nested_subforms() {
4182 let mut tree = FormTree::new();
4183 let f1 = make_field(&mut tree, "Name", 200.0, 25.0);
4184 let f2 = make_field(&mut tree, "Email", 200.0, 25.0);
4185
4186 let sub = make_subform(
4187 &mut tree,
4188 "PersonalInfo",
4189 LayoutStrategy::TopToBottom,
4190 Some(300.0),
4191 Some(100.0),
4192 vec![f1, f2],
4193 );
4194
4195 let root = tree.add_node(FormNode {
4196 name: "Root".to_string(),
4197 node_type: FormNodeType::Root,
4198 box_model: BoxModel {
4199 width: Some(612.0),
4200 height: Some(792.0),
4201 max_width: f64::MAX,
4202 max_height: f64::MAX,
4203 ..Default::default()
4204 },
4205 layout: LayoutStrategy::TopToBottom,
4206 children: vec![sub],
4207 occur: Occur::once(),
4208 font: FontMetrics::default(),
4209 calculate: None,
4210 validate: None,
4211 column_widths: vec![],
4212 col_span: 1,
4213 });
4214
4215 let engine = LayoutEngine::new(&tree);
4216 let result = engine.layout(root).unwrap();
4217
4218 let page = &result.pages[0];
4219 assert_eq!(page.nodes.len(), 1);
4220 let subform = &page.nodes[0];
4221 assert_eq!(subform.name, "PersonalInfo");
4222 assert_eq!(subform.rect.width, 300.0);
4223 assert_eq!(subform.children.len(), 2);
4224 assert_eq!(subform.children[0].rect.y, 0.0);
4225 assert_eq!(subform.children[1].rect.y, 25.0);
4226 }
4227
4228 #[test]
4229 fn page_area_layout() {
4230 let mut tree = FormTree::new();
4231 let f1 = make_field(&mut tree, "Field1", 200.0, 30.0);
4232
4233 let page_area = tree.add_node(FormNode {
4234 name: "Page1".to_string(),
4235 node_type: FormNodeType::PageArea {
4236 content_areas: vec![ContentArea {
4237 name: "Body".to_string(),
4238 x: 36.0,
4239 y: 36.0,
4240 width: 540.0,
4241 height: 720.0,
4242 leader: None,
4243 trailer: None,
4244 }],
4245 },
4246 box_model: BoxModel {
4247 width: Some(612.0),
4248 height: Some(792.0),
4249 max_width: f64::MAX,
4250 max_height: f64::MAX,
4251 ..Default::default()
4252 },
4253 layout: LayoutStrategy::Positioned,
4254 children: vec![],
4255 occur: Occur::once(),
4256 font: FontMetrics::default(),
4257 calculate: None,
4258 validate: None,
4259 column_widths: vec![],
4260 col_span: 1,
4261 });
4262
4263 let root = tree.add_node(FormNode {
4264 name: "Root".to_string(),
4265 node_type: FormNodeType::Root,
4266 box_model: BoxModel {
4267 width: Some(612.0),
4268 height: Some(792.0),
4269 max_width: f64::MAX,
4270 max_height: f64::MAX,
4271 ..Default::default()
4272 },
4273 layout: LayoutStrategy::TopToBottom,
4274 children: vec![page_area, f1],
4275 occur: Occur::once(),
4276 font: FontMetrics::default(),
4277 calculate: None,
4278 validate: None,
4279 column_widths: vec![],
4280 col_span: 1,
4281 });
4282
4283 let engine = LayoutEngine::new(&tree);
4284 let result = engine.layout(root).unwrap();
4285
4286 assert_eq!(result.pages.len(), 1);
4287 let page = &result.pages[0];
4288 assert_eq!(page.nodes[0].rect.x, 36.0);
4290 assert_eq!(page.nodes[0].rect.y, 36.0);
4291 }
4292
4293 #[test]
4294 fn trailing_empty_pagearea_continuation_suppressed() {
4295 let mut tree = FormTree::new();
4296 let first_page = make_page_area(&mut tree, "Page1", 200.0, 80.0);
4297 let continuation_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4298 let record = make_field_value(&mut tree, "record", "data", 180.0, 80.0);
4299 mark_data_bound(&mut tree, record);
4300 let template_only = make_draw_text(&mut tree, "template_only", "template", 180.0, 20.0);
4301 let root = make_root(
4302 &mut tree,
4303 vec![first_page, continuation_page, record, template_only],
4304 );
4305
4306 let engine = LayoutEngine::new(&tree);
4307 let result = engine.layout(root).unwrap();
4308
4309 assert_eq!(result.pages.len(), 1);
4310 assert_eq!(count_leaf_nodes(&result.pages[0]), 1);
4311 }
4312
4313 #[test]
4314 fn static_two_page_form_both_pages_emitted() {
4315 let mut tree = FormTree::new();
4319 let pa1 = make_page_area(&mut tree, "Page1", 200.0, 100.0);
4320 let pa2 = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4321 let page1_content = make_draw_text(&mut tree, "page1_content", "page1", 180.0, 90.0);
4323 let page2_content = make_draw_text(&mut tree, "page2_content", "page2", 180.0, 85.0);
4325 let root = make_root(&mut tree, vec![pa1, pa2, page1_content, page2_content]);
4326
4327 let engine = LayoutEngine::new(&tree);
4328 let result = engine.layout(root).unwrap();
4329
4330 assert_eq!(
4331 result.pages.len(),
4332 2,
4333 "static two-page form must emit 2 pages"
4334 );
4335 assert_eq!(count_leaf_nodes(&result.pages[0]), 1);
4336 assert_eq!(count_leaf_nodes(&result.pages[1]), 1);
4337 }
4338
4339 #[test]
4340 fn valid_multi_page_overflow_unchanged() {
4341 let mut tree = FormTree::new();
4342 let page_area = make_page_area(&mut tree, "Page1", 200.0, 100.0);
4343 let mut children = vec![page_area];
4344 for idx in 0..4 {
4345 let field = make_field_value(&mut tree, &format!("record{idx}"), "data", 180.0, 40.0);
4346 mark_data_bound(&mut tree, field);
4347 children.push(field);
4348 }
4349 let root = make_root(&mut tree, children);
4350
4351 let engine = LayoutEngine::new(&tree);
4352 let result = engine.layout(root).unwrap();
4353
4354 assert_eq!(result.pages.len(), 2);
4355 assert_eq!(result.pages.iter().map(count_leaf_nodes).sum::<usize>(), 4);
4356 }
4357
4358 #[test]
4359 fn template_only_page_kept_when_explicitly_required() {
4360 let mut tree = FormTree::new();
4361 let first_page = make_page_area(&mut tree, "Page1", 200.0, 80.0);
4362 let anchored_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4363 let record = make_field_value(&mut tree, "record", "data", 180.0, 80.0);
4364 mark_data_bound(&mut tree, record);
4365 let anchored_draw = make_draw_text(&mut tree, "anchored_draw", "next page", 180.0, 20.0);
4366 tree.meta_mut(anchored_draw).page_break_before = true;
4367 let root = make_root(
4368 &mut tree,
4369 vec![first_page, anchored_page, record, anchored_draw],
4370 );
4371
4372 let engine = LayoutEngine::new(&tree);
4373 let result = engine.layout(root).unwrap();
4374
4375 assert_eq!(result.pages.len(), 2);
4376 assert_eq!(count_leaf_nodes(&result.pages[1]), 1);
4377 }
4378
4379 #[test]
4380 fn single_page_form_remains_single_page() {
4381 let mut tree = FormTree::new();
4382 let page_area = make_page_area(&mut tree, "Page1", 200.0, 100.0);
4383 let record = make_field_value(&mut tree, "record", "data", 180.0, 40.0);
4384 mark_data_bound(&mut tree, record);
4385 let root = make_root(&mut tree, vec![page_area, record]);
4386
4387 let engine = LayoutEngine::new(&tree);
4388 let result = engine.layout(root).unwrap();
4389
4390 assert_eq!(result.pages.len(), 1);
4391 assert_eq!(count_leaf_nodes(&result.pages[0]), 1);
4392 }
4393
4394 #[test]
4395 fn continuation_with_remaining_body_still_emits() {
4396 let mut tree = FormTree::new();
4397 let first_page = make_page_area(&mut tree, "Page1", 200.0, 60.0);
4398 let continuation_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4399 let first_record = make_field_value(&mut tree, "first_record", "data", 180.0, 60.0);
4400 let second_record = make_field_value(&mut tree, "second_record", "data", 180.0, 40.0);
4401 mark_data_bound(&mut tree, first_record);
4402 mark_data_bound(&mut tree, second_record);
4403 let root = make_root(
4404 &mut tree,
4405 vec![first_page, continuation_page, first_record, second_record],
4406 );
4407
4408 let engine = LayoutEngine::new(&tree);
4409 let result = engine.layout(root).unwrap();
4410
4411 assert_eq!(result.pages.len(), 2);
4412 assert_eq!(count_leaf_nodes(&result.pages[1]), 1);
4413 }
4414
4415 #[test]
4416 fn growable_extent() {
4417 let mut tree = FormTree::new();
4418 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
4419 let f2 = make_field(&mut tree, "F2", 150.0, 20.0);
4420
4421 let sub = make_subform(
4423 &mut tree,
4424 "Container",
4425 LayoutStrategy::TopToBottom,
4426 None,
4427 None,
4428 vec![f1, f2],
4429 );
4430
4431 let engine = LayoutEngine::new(&tree);
4432 let extent = engine.compute_extent(sub);
4433
4434 assert_eq!(extent.width, 150.0);
4436 assert_eq!(extent.height, 40.0);
4437 }
4438
4439 #[test]
4440 fn rl_tb_layout() {
4441 let mut tree = FormTree::new();
4442 let f1 = make_field(&mut tree, "F1", 100.0, 30.0);
4443 let f2 = make_field(&mut tree, "F2", 100.0, 30.0);
4444
4445 let root = tree.add_node(FormNode {
4446 name: "Root".to_string(),
4447 node_type: FormNodeType::Root,
4448 box_model: BoxModel {
4449 width: Some(400.0),
4450 height: Some(400.0),
4451 max_width: f64::MAX,
4452 max_height: f64::MAX,
4453 ..Default::default()
4454 },
4455 layout: LayoutStrategy::RightToLeftTB,
4456 children: vec![f1, f2],
4457 occur: Occur::once(),
4458 font: FontMetrics::default(),
4459 calculate: None,
4460 validate: None,
4461 column_widths: vec![],
4462 col_span: 1,
4463 });
4464
4465 let engine = LayoutEngine::new(&tree);
4466 let result = engine.layout(root).unwrap();
4467 let page = &result.pages[0];
4468
4469 assert_eq!(page.nodes[0].rect.x, 300.0); assert_eq!(page.nodes[1].rect.x, 200.0); }
4473
4474 #[test]
4477 fn growable_clamped_by_min() {
4478 let mut tree = FormTree::new();
4480 let f1 = make_field(&mut tree, "F1", 50.0, 10.0);
4481
4482 let sub = tree.add_node(FormNode {
4483 name: "Container".to_string(),
4484 node_type: FormNodeType::Subform,
4485 box_model: BoxModel {
4486 width: None,
4487 height: None,
4488 min_width: 200.0,
4489 min_height: 100.0,
4490 max_width: f64::MAX,
4491 max_height: f64::MAX,
4492 ..Default::default()
4493 },
4494 layout: LayoutStrategy::TopToBottom,
4495 children: vec![f1],
4496 occur: Occur::once(),
4497 font: FontMetrics::default(),
4498 calculate: None,
4499 validate: None,
4500 column_widths: vec![],
4501 col_span: 1,
4502 });
4503
4504 let engine = LayoutEngine::new(&tree);
4505 let extent = engine.compute_extent(sub);
4506
4507 assert_eq!(extent.width, 200.0);
4509 assert_eq!(extent.height, 100.0);
4510 }
4511
4512 #[test]
4513 fn growable_clamped_by_max() {
4514 let mut tree = FormTree::new();
4516 let f1 = make_field(&mut tree, "F1", 500.0, 300.0);
4517
4518 let sub = tree.add_node(FormNode {
4519 name: "Container".to_string(),
4520 node_type: FormNodeType::Subform,
4521 box_model: BoxModel {
4522 width: None,
4523 height: None,
4524 min_width: 0.0,
4525 min_height: 0.0,
4526 max_width: 200.0,
4527 max_height: 100.0,
4528 ..Default::default()
4529 },
4530 layout: LayoutStrategy::TopToBottom,
4531 children: vec![f1],
4532 occur: Occur::once(),
4533 font: FontMetrics::default(),
4534 calculate: None,
4535 validate: None,
4536 column_widths: vec![],
4537 col_span: 1,
4538 });
4539
4540 let engine = LayoutEngine::new(&tree);
4541 let extent = engine.compute_extent(sub);
4542
4543 assert_eq!(extent.width, 200.0);
4545 assert_eq!(extent.height, 100.0);
4546 }
4547
4548 #[test]
4549 fn partially_growable_width_fixed() {
4550 let mut tree = FormTree::new();
4552 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
4553 let f2 = make_field(&mut tree, "F2", 100.0, 25.0);
4554
4555 let sub = tree.add_node(FormNode {
4556 name: "Container".to_string(),
4557 node_type: FormNodeType::Subform,
4558 box_model: BoxModel {
4559 width: Some(300.0),
4560 height: None,
4561 max_width: f64::MAX,
4562 max_height: f64::MAX,
4563 ..Default::default()
4564 },
4565 layout: LayoutStrategy::TopToBottom,
4566 children: vec![f1, f2],
4567 occur: Occur::once(),
4568 font: FontMetrics::default(),
4569 calculate: None,
4570 validate: None,
4571 column_widths: vec![],
4572 col_span: 1,
4573 });
4574
4575 let engine = LayoutEngine::new(&tree);
4576 let extent = engine.compute_extent(sub);
4577
4578 assert_eq!(extent.width, 300.0);
4580 assert_eq!(extent.height, 50.0);
4581 }
4582
4583 #[test]
4584 fn partially_growable_height_fixed() {
4585 let mut tree = FormTree::new();
4587 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
4588 let f2 = make_field(&mut tree, "F2", 150.0, 25.0);
4589
4590 let sub = tree.add_node(FormNode {
4591 name: "Container".to_string(),
4592 node_type: FormNodeType::Subform,
4593 box_model: BoxModel {
4594 width: None,
4595 height: Some(200.0),
4596 max_width: f64::MAX,
4597 max_height: f64::MAX,
4598 ..Default::default()
4599 },
4600 layout: LayoutStrategy::TopToBottom,
4601 children: vec![f1, f2],
4602 occur: Occur::once(),
4603 font: FontMetrics::default(),
4604 calculate: None,
4605 validate: None,
4606 column_widths: vec![],
4607 col_span: 1,
4608 });
4609
4610 let engine = LayoutEngine::new(&tree);
4611 let extent = engine.compute_extent(sub);
4612
4613 assert_eq!(extent.width, 150.0);
4615 assert_eq!(extent.height, 200.0);
4616 }
4617
4618 #[test]
4619 fn growable_fills_available_width_in_tb() {
4620 let mut tree = FormTree::new();
4622 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
4623
4624 let growable_sub = tree.add_node(FormNode {
4625 name: "GrowableSub".to_string(),
4626 node_type: FormNodeType::Subform,
4627 box_model: BoxModel {
4628 width: None,
4629 height: None,
4630 max_width: f64::MAX,
4631 max_height: f64::MAX,
4632 ..Default::default()
4633 },
4634 layout: LayoutStrategy::TopToBottom,
4635 children: vec![f1],
4636 occur: Occur::once(),
4637 font: FontMetrics::default(),
4638 calculate: None,
4639 validate: None,
4640 column_widths: vec![],
4641 col_span: 1,
4642 });
4643
4644 let root = tree.add_node(FormNode {
4645 name: "Root".to_string(),
4646 node_type: FormNodeType::Root,
4647 box_model: BoxModel {
4648 width: Some(500.0),
4649 height: Some(400.0),
4650 max_width: f64::MAX,
4651 max_height: f64::MAX,
4652 ..Default::default()
4653 },
4654 layout: LayoutStrategy::TopToBottom,
4655 children: vec![growable_sub],
4656 occur: Occur::once(),
4657 font: FontMetrics::default(),
4658 calculate: None,
4659 validate: None,
4660 column_widths: vec![],
4661 col_span: 1,
4662 });
4663
4664 let engine = LayoutEngine::new(&tree);
4665 let result = engine.layout(root).unwrap();
4666
4667 let page = &result.pages[0];
4668 assert_eq!(page.nodes[0].rect.width, 500.0);
4670 assert_eq!(page.nodes[0].rect.height, 25.0);
4672 }
4673
4674 #[test]
4675 fn growable_fill_capped_by_max() {
4676 let mut tree = FormTree::new();
4678 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
4679
4680 let growable_sub = tree.add_node(FormNode {
4681 name: "GrowableSub".to_string(),
4682 node_type: FormNodeType::Subform,
4683 box_model: BoxModel {
4684 width: None,
4685 height: None,
4686 max_width: 300.0,
4687 max_height: f64::MAX,
4688 ..Default::default()
4689 },
4690 layout: LayoutStrategy::TopToBottom,
4691 children: vec![f1],
4692 occur: Occur::once(),
4693 font: FontMetrics::default(),
4694 calculate: None,
4695 validate: None,
4696 column_widths: vec![],
4697 col_span: 1,
4698 });
4699
4700 let root = tree.add_node(FormNode {
4701 name: "Root".to_string(),
4702 node_type: FormNodeType::Root,
4703 box_model: BoxModel {
4704 width: Some(500.0),
4705 height: Some(400.0),
4706 max_width: f64::MAX,
4707 max_height: f64::MAX,
4708 ..Default::default()
4709 },
4710 layout: LayoutStrategy::TopToBottom,
4711 children: vec![growable_sub],
4712 occur: Occur::once(),
4713 font: FontMetrics::default(),
4714 calculate: None,
4715 validate: None,
4716 column_widths: vec![],
4717 col_span: 1,
4718 });
4719
4720 let engine = LayoutEngine::new(&tree);
4721 let result = engine.layout(root).unwrap();
4722
4723 let page = &result.pages[0];
4724 assert_eq!(page.nodes[0].rect.width, 300.0);
4726 }
4727
4728 #[test]
4729 fn growable_with_margins_in_tb() {
4730 use crate::types::Insets;
4732 let mut tree = FormTree::new();
4733 let f1 = make_field(&mut tree, "F1", 50.0, 20.0);
4734
4735 let growable_sub = tree.add_node(FormNode {
4736 name: "GrowableSub".to_string(),
4737 node_type: FormNodeType::Subform,
4738 box_model: BoxModel {
4739 width: None,
4740 height: None,
4741 margins: Insets {
4742 top: 5.0,
4743 right: 10.0,
4744 bottom: 5.0,
4745 left: 10.0,
4746 },
4747 max_width: f64::MAX,
4748 max_height: f64::MAX,
4749 ..Default::default()
4750 },
4751 layout: LayoutStrategy::TopToBottom,
4752 children: vec![f1],
4753 occur: Occur::once(),
4754 font: FontMetrics::default(),
4755 calculate: None,
4756 validate: None,
4757 column_widths: vec![],
4758 col_span: 1,
4759 });
4760
4761 let root = tree.add_node(FormNode {
4762 name: "Root".to_string(),
4763 node_type: FormNodeType::Root,
4764 box_model: BoxModel {
4765 width: Some(400.0),
4766 height: Some(300.0),
4767 max_width: f64::MAX,
4768 max_height: f64::MAX,
4769 ..Default::default()
4770 },
4771 layout: LayoutStrategy::TopToBottom,
4772 children: vec![growable_sub],
4773 occur: Occur::once(),
4774 font: FontMetrics::default(),
4775 calculate: None,
4776 validate: None,
4777 column_widths: vec![],
4778 col_span: 1,
4779 });
4780
4781 let engine = LayoutEngine::new(&tree);
4782 let result = engine.layout(root).unwrap();
4783
4784 let page = &result.pages[0];
4785 assert_eq!(page.nodes[0].rect.width, 400.0);
4787 assert_eq!(page.nodes[0].rect.height, 30.0);
4789 }
4790
4791 #[test]
4792 fn nested_growable_containers() {
4793 let mut tree = FormTree::new();
4795 let f1 = make_field(&mut tree, "F1", 80.0, 20.0);
4796
4797 let inner = tree.add_node(FormNode {
4798 name: "Inner".to_string(),
4799 node_type: FormNodeType::Subform,
4800 box_model: BoxModel {
4801 width: None,
4802 height: None,
4803 min_width: 150.0,
4804 max_width: f64::MAX,
4805 max_height: f64::MAX,
4806 ..Default::default()
4807 },
4808 layout: LayoutStrategy::TopToBottom,
4809 children: vec![f1],
4810 occur: Occur::once(),
4811 font: FontMetrics::default(),
4812 calculate: None,
4813 validate: None,
4814 column_widths: vec![],
4815 col_span: 1,
4816 });
4817
4818 let outer = tree.add_node(FormNode {
4820 name: "Outer".to_string(),
4821 node_type: FormNodeType::Subform,
4822 box_model: BoxModel {
4823 width: None,
4824 height: None,
4825 max_width: 400.0,
4826 max_height: f64::MAX,
4827 ..Default::default()
4828 },
4829 layout: LayoutStrategy::TopToBottom,
4830 children: vec![inner],
4831 occur: Occur::once(),
4832 font: FontMetrics::default(),
4833 calculate: None,
4834 validate: None,
4835 column_widths: vec![],
4836 col_span: 1,
4837 });
4838
4839 let engine = LayoutEngine::new(&tree);
4840 let extent = engine.compute_extent(outer);
4841
4842 assert_eq!(extent.width, 150.0);
4845 assert_eq!(extent.height, 20.0);
4846 }
4847
4848 #[test]
4849 fn min_max_in_lr_tb_layout() {
4850 let mut tree = FormTree::new();
4852
4853 let f1 = tree.add_node(FormNode {
4854 name: "F1".to_string(),
4855 node_type: FormNodeType::Field {
4856 value: "A".to_string(),
4857 },
4858 box_model: BoxModel {
4859 width: None,
4860 height: Some(30.0),
4861 min_width: 200.0,
4862 max_width: f64::MAX,
4863 max_height: f64::MAX,
4864 ..Default::default()
4865 },
4866 layout: LayoutStrategy::Positioned,
4867 children: vec![],
4868 occur: Occur::once(),
4869 font: FontMetrics::default(),
4870 calculate: None,
4871 validate: None,
4872 column_widths: vec![],
4873 col_span: 1,
4874 });
4875
4876 let f2 = make_field(&mut tree, "F2", 200.0, 30.0);
4877
4878 let root = tree.add_node(FormNode {
4879 name: "Root".to_string(),
4880 node_type: FormNodeType::Root,
4881 box_model: BoxModel {
4882 width: Some(500.0),
4883 height: Some(400.0),
4884 max_width: f64::MAX,
4885 max_height: f64::MAX,
4886 ..Default::default()
4887 },
4888 layout: LayoutStrategy::LeftToRightTB,
4889 children: vec![f1, f2],
4890 occur: Occur::once(),
4891 font: FontMetrics::default(),
4892 calculate: None,
4893 validate: None,
4894 column_widths: vec![],
4895 col_span: 1,
4896 });
4897
4898 let engine = LayoutEngine::new(&tree);
4899 let result = engine.layout(root).unwrap();
4900
4901 let page = &result.pages[0];
4902 assert_eq!(page.nodes[0].rect.width, 200.0);
4904 assert_eq!(page.nodes[1].rect.x, 200.0);
4906 }
4907
4908 #[test]
4911 fn occur_default_once() {
4912 let mut tree = FormTree::new();
4914 let f1 = make_field(&mut tree, "F1", 100.0, 30.0);
4915
4916 let root = tree.add_node(FormNode {
4917 name: "Root".to_string(),
4918 node_type: FormNodeType::Root,
4919 box_model: BoxModel {
4920 width: Some(400.0),
4921 height: Some(400.0),
4922 max_width: f64::MAX,
4923 max_height: f64::MAX,
4924 ..Default::default()
4925 },
4926 layout: LayoutStrategy::TopToBottom,
4927 children: vec![f1],
4928 occur: Occur::once(),
4929 font: FontMetrics::default(),
4930 calculate: None,
4931 validate: None,
4932 column_widths: vec![],
4933 col_span: 1,
4934 });
4935
4936 let engine = LayoutEngine::new(&tree);
4937 let result = engine.layout(root).unwrap();
4938 assert_eq!(result.pages[0].nodes.len(), 1);
4939 }
4940
4941 #[test]
4942 fn occur_repeating_tb() {
4943 let mut tree = FormTree::new();
4945 let f1 = tree.add_node(FormNode {
4946 name: "Row".to_string(),
4947 node_type: FormNodeType::Subform,
4948 box_model: BoxModel {
4949 width: Some(200.0),
4950 height: Some(30.0),
4951 max_width: f64::MAX,
4952 max_height: f64::MAX,
4953 ..Default::default()
4954 },
4955 layout: LayoutStrategy::Positioned,
4956 children: vec![],
4957 occur: Occur::repeating(1, Some(10), 3),
4958 font: FontMetrics::default(),
4959 calculate: None,
4960 validate: None,
4961 column_widths: vec![],
4962 col_span: 1,
4963 });
4964
4965 let root = tree.add_node(FormNode {
4966 name: "Root".to_string(),
4967 node_type: FormNodeType::Root,
4968 box_model: BoxModel {
4969 width: Some(400.0),
4970 height: Some(400.0),
4971 max_width: f64::MAX,
4972 max_height: f64::MAX,
4973 ..Default::default()
4974 },
4975 layout: LayoutStrategy::TopToBottom,
4976 children: vec![f1],
4977 occur: Occur::once(),
4978 font: FontMetrics::default(),
4979 calculate: None,
4980 validate: None,
4981 column_widths: vec![],
4982 col_span: 1,
4983 });
4984
4985 let engine = LayoutEngine::new(&tree);
4986 let result = engine.layout(root).unwrap();
4987 let page = &result.pages[0];
4988
4989 assert_eq!(page.nodes.len(), 3);
4991 assert_eq!(page.nodes[0].rect.y, 0.0);
4992 assert_eq!(page.nodes[1].rect.y, 30.0);
4993 assert_eq!(page.nodes[2].rect.y, 60.0);
4994 }
4995
4996 #[test]
4997 fn occur_repeating_lr_tb() {
4998 let mut tree = FormTree::new();
5000 let f1 = tree.add_node(FormNode {
5001 name: "Cell".to_string(),
5002 node_type: FormNodeType::Field {
5003 value: "X".to_string(),
5004 },
5005 box_model: BoxModel {
5006 width: Some(100.0),
5007 height: Some(30.0),
5008 max_width: f64::MAX,
5009 max_height: f64::MAX,
5010 ..Default::default()
5011 },
5012 layout: LayoutStrategy::Positioned,
5013 children: vec![],
5014 occur: Occur::repeating(1, None, 5),
5015 font: FontMetrics::default(),
5016 calculate: None,
5017 validate: None,
5018 column_widths: vec![],
5019 col_span: 1,
5020 });
5021
5022 let root = tree.add_node(FormNode {
5023 name: "Root".to_string(),
5024 node_type: FormNodeType::Root,
5025 box_model: BoxModel {
5026 width: Some(350.0),
5027 height: Some(400.0),
5028 max_width: f64::MAX,
5029 max_height: f64::MAX,
5030 ..Default::default()
5031 },
5032 layout: LayoutStrategy::LeftToRightTB,
5033 children: vec![f1],
5034 occur: Occur::once(),
5035 font: FontMetrics::default(),
5036 calculate: None,
5037 validate: None,
5038 column_widths: vec![],
5039 col_span: 1,
5040 });
5041
5042 let engine = LayoutEngine::new(&tree);
5043 let result = engine.layout(root).unwrap();
5044 let page = &result.pages[0];
5045
5046 assert_eq!(page.nodes.len(), 5);
5049 assert_eq!(page.nodes[0].rect.x, 0.0);
5050 assert_eq!(page.nodes[0].rect.y, 0.0);
5051 assert_eq!(page.nodes[1].rect.x, 100.0);
5052 assert_eq!(page.nodes[2].rect.x, 200.0);
5053 assert_eq!(page.nodes[3].rect.x, 0.0);
5054 assert_eq!(page.nodes[3].rect.y, 30.0);
5055 assert_eq!(page.nodes[4].rect.x, 100.0);
5056 assert_eq!(page.nodes[4].rect.y, 30.0);
5057 }
5058
5059 #[test]
5060 fn occur_min_enforced() {
5061 let occur = Occur::repeating(2, Some(5), 2);
5063 assert_eq!(occur.count(), 2);
5064 assert!(occur.is_repeating());
5065 }
5066
5067 #[test]
5068 fn occur_max_caps_initial() {
5069 let occur = Occur::repeating(1, Some(3), 5);
5071 assert_eq!(occur.count(), 3);
5072 }
5073
5074 #[test]
5075 fn occur_initial_raised_to_min() {
5076 let occur = Occur::repeating(3, Some(10), 1);
5078 assert_eq!(occur.count(), 3);
5079 }
5080
5081 #[test]
5082 fn occur_unlimited_max() {
5083 let occur = Occur::repeating(0, None, 5);
5084 assert_eq!(occur.count(), 5);
5085 assert!(occur.is_repeating());
5086 }
5087
5088 #[test]
5089 fn occur_mixed_children() {
5090 let mut tree = FormTree::new();
5092 let header = make_field(&mut tree, "Header", 200.0, 40.0);
5093 let row = tree.add_node(FormNode {
5094 name: "DataRow".to_string(),
5095 node_type: FormNodeType::Subform,
5096 box_model: BoxModel {
5097 width: Some(200.0),
5098 height: Some(25.0),
5099 max_width: f64::MAX,
5100 max_height: f64::MAX,
5101 ..Default::default()
5102 },
5103 layout: LayoutStrategy::Positioned,
5104 children: vec![],
5105 occur: Occur::repeating(1, Some(10), 4),
5106 font: FontMetrics::default(),
5107 calculate: None,
5108 validate: None,
5109 column_widths: vec![],
5110 col_span: 1,
5111 });
5112 let footer = make_field(&mut tree, "Footer", 200.0, 30.0);
5113
5114 let root = tree.add_node(FormNode {
5115 name: "Root".to_string(),
5116 node_type: FormNodeType::Root,
5117 box_model: BoxModel {
5118 width: Some(400.0),
5119 height: Some(600.0),
5120 max_width: f64::MAX,
5121 max_height: f64::MAX,
5122 ..Default::default()
5123 },
5124 layout: LayoutStrategy::TopToBottom,
5125 children: vec![header, row, footer],
5126 occur: Occur::once(),
5127 font: FontMetrics::default(),
5128 calculate: None,
5129 validate: None,
5130 column_widths: vec![],
5131 col_span: 1,
5132 });
5133
5134 let engine = LayoutEngine::new(&tree);
5135 let result = engine.layout(root).unwrap();
5136 let page = &result.pages[0];
5137
5138 assert_eq!(page.nodes.len(), 6);
5140 assert_eq!(page.nodes[0].name, "Header");
5141 assert_eq!(page.nodes[0].rect.y, 0.0);
5142 assert_eq!(page.nodes[1].name, "DataRow");
5144 assert_eq!(page.nodes[1].rect.y, 40.0);
5145 assert_eq!(page.nodes[2].rect.y, 65.0);
5146 assert_eq!(page.nodes[3].rect.y, 90.0);
5147 assert_eq!(page.nodes[4].rect.y, 115.0);
5148 assert_eq!(page.nodes[5].name, "Footer");
5150 assert_eq!(page.nodes[5].rect.y, 140.0);
5151 }
5152
5153 #[test]
5154 fn occur_growable_extent() {
5155 let mut tree = FormTree::new();
5157 let row = tree.add_node(FormNode {
5158 name: "Row".to_string(),
5159 node_type: FormNodeType::Subform,
5160 box_model: BoxModel {
5161 width: Some(150.0),
5162 height: Some(20.0),
5163 max_width: f64::MAX,
5164 max_height: f64::MAX,
5165 ..Default::default()
5166 },
5167 layout: LayoutStrategy::Positioned,
5168 children: vec![],
5169 occur: Occur::repeating(1, None, 5),
5170 font: FontMetrics::default(),
5171 calculate: None,
5172 validate: None,
5173 column_widths: vec![],
5174 col_span: 1,
5175 });
5176
5177 let container = tree.add_node(FormNode {
5178 name: "Container".to_string(),
5179 node_type: FormNodeType::Subform,
5180 box_model: BoxModel {
5181 width: None,
5182 height: None,
5183 max_width: f64::MAX,
5184 max_height: f64::MAX,
5185 ..Default::default()
5186 },
5187 layout: LayoutStrategy::TopToBottom,
5188 children: vec![row],
5189 occur: Occur::once(),
5190 font: FontMetrics::default(),
5191 calculate: None,
5192 validate: None,
5193 column_widths: vec![],
5194 col_span: 1,
5195 });
5196
5197 let engine = LayoutEngine::new(&tree);
5198 let extent = engine.compute_extent(container);
5199
5200 assert_eq!(extent.width, 150.0);
5202 assert_eq!(extent.height, 100.0);
5203 }
5204
5205 #[test]
5208 fn pagination_single_page_no_overflow() {
5209 let mut tree = FormTree::new();
5211 let f1 = make_field(&mut tree, "F1", 200.0, 30.0);
5212 let f2 = make_field(&mut tree, "F2", 200.0, 30.0);
5213
5214 let root = tree.add_node(FormNode {
5215 name: "Root".to_string(),
5216 node_type: FormNodeType::Root,
5217 box_model: BoxModel {
5218 width: Some(400.0),
5219 height: Some(200.0),
5220 max_width: f64::MAX,
5221 max_height: f64::MAX,
5222 ..Default::default()
5223 },
5224 layout: LayoutStrategy::TopToBottom,
5225 children: vec![f1, f2],
5226 occur: Occur::once(),
5227 font: FontMetrics::default(),
5228 calculate: None,
5229 validate: None,
5230 column_widths: vec![],
5231 col_span: 1,
5232 });
5233
5234 let engine = LayoutEngine::new(&tree);
5235 let result = engine.layout(root).unwrap();
5236
5237 assert_eq!(result.pages.len(), 1);
5238 assert_eq!(result.pages[0].nodes.len(), 2);
5239 }
5240
5241 #[test]
5242 fn pagination_overflow_creates_pages() {
5243 let mut tree = FormTree::new();
5245 let mut fields = Vec::new();
5246 for i in 0..10 {
5247 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 30.0));
5248 }
5249
5250 let root = tree.add_node(FormNode {
5251 name: "Root".to_string(),
5252 node_type: FormNodeType::Root,
5253 box_model: BoxModel {
5254 width: Some(400.0),
5255 height: Some(100.0),
5256 max_width: f64::MAX,
5257 max_height: f64::MAX,
5258 ..Default::default()
5259 },
5260 layout: LayoutStrategy::TopToBottom,
5261 children: fields,
5262 occur: Occur::once(),
5263 font: FontMetrics::default(),
5264 calculate: None,
5265 validate: None,
5266 column_widths: vec![],
5267 col_span: 1,
5268 });
5269
5270 let engine = LayoutEngine::new(&tree);
5271 let result = engine.layout(root).unwrap();
5272
5273 assert_eq!(result.pages.len(), 4);
5276 assert_eq!(result.pages[0].nodes.len(), 3);
5277 assert_eq!(result.pages[1].nodes.len(), 3);
5278 assert_eq!(result.pages[2].nodes.len(), 3);
5279 assert_eq!(result.pages[3].nodes.len(), 1);
5280 }
5281
5282 #[test]
5283 fn pagination_profile_reports_height_usage_and_overflow_target() {
5284 let mut tree = FormTree::new();
5285 let mut fields = Vec::new();
5286 for i in 0..4 {
5287 let field = make_field(&mut tree, &format!("F{i}"), 200.0, 30.0);
5288 tree.meta_mut(field).xfa_id = Some(format!("row_{i}"));
5289 fields.push(field);
5290 }
5291
5292 let root = tree.add_node(FormNode {
5293 name: "Root".to_string(),
5294 node_type: FormNodeType::Root,
5295 box_model: BoxModel {
5296 width: Some(400.0),
5297 height: Some(100.0),
5298 max_width: f64::MAX,
5299 max_height: f64::MAX,
5300 ..Default::default()
5301 },
5302 layout: LayoutStrategy::TopToBottom,
5303 children: fields,
5304 occur: Occur::once(),
5305 font: FontMetrics::default(),
5306 calculate: None,
5307 validate: None,
5308 column_widths: vec![],
5309 col_span: 1,
5310 });
5311
5312 let engine = LayoutEngine::new(&tree);
5313 let (layout, profile) = engine.layout_with_profile(root).unwrap();
5314
5315 assert_eq!(layout.pages.len(), 2);
5316 assert_eq!(profile.pages.len(), 2);
5317 assert_eq!(profile.pages[0].page_height, 100.0);
5318 assert_eq!(profile.pages[0].used_height, 90.0);
5319 assert!(profile.pages[0].overflow_to_next);
5320 assert_eq!(
5321 profile.pages[0].first_overflow_element.as_deref(),
5322 Some("field#row_3 (h=30.0)")
5323 );
5324 assert_eq!(profile.pages[1].used_height, 30.0);
5325 assert!(!profile.pages[1].overflow_to_next);
5326 assert!(profile.pages[1].first_overflow_element.is_none());
5327 }
5328
5329 #[test]
5330 fn pagination_with_page_area() {
5331 let mut tree = FormTree::new();
5333 let mut fields = Vec::new();
5334 for i in 0..6 {
5335 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 50.0));
5336 }
5337
5338 let page_area = tree.add_node(FormNode {
5339 name: "Page1".to_string(),
5340 node_type: FormNodeType::PageArea {
5341 content_areas: vec![ContentArea {
5342 name: "Body".to_string(),
5343 x: 20.0,
5344 y: 20.0,
5345 width: 360.0,
5346 height: 160.0, leader: None,
5348 trailer: None,
5349 }],
5350 },
5351 box_model: BoxModel {
5352 width: Some(400.0),
5353 height: Some(200.0),
5354 max_width: f64::MAX,
5355 max_height: f64::MAX,
5356 ..Default::default()
5357 },
5358 layout: LayoutStrategy::Positioned,
5359 children: vec![],
5360 occur: Occur::once(),
5361 font: FontMetrics::default(),
5362 calculate: None,
5363 validate: None,
5364 column_widths: vec![],
5365 col_span: 1,
5366 });
5367
5368 let mut root_children = vec![page_area];
5369 root_children.extend(fields);
5370
5371 let root = tree.add_node(FormNode {
5372 name: "Root".to_string(),
5373 node_type: FormNodeType::Root,
5374 box_model: BoxModel {
5375 width: Some(400.0),
5376 height: Some(200.0),
5377 max_width: f64::MAX,
5378 max_height: f64::MAX,
5379 ..Default::default()
5380 },
5381 layout: LayoutStrategy::TopToBottom,
5382 children: root_children,
5383 occur: Occur::once(),
5384 font: FontMetrics::default(),
5385 calculate: None,
5386 validate: None,
5387 column_widths: vec![],
5388 col_span: 1,
5389 });
5390
5391 let engine = LayoutEngine::new(&tree);
5392 let result = engine.layout(root).unwrap();
5393
5394 assert_eq!(result.pages.len(), 2);
5396 assert_eq!(result.pages[0].nodes.len(), 3);
5397 assert_eq!(result.pages[1].nodes.len(), 3);
5398
5399 assert_eq!(result.pages[0].nodes[0].rect.x, 20.0);
5401 assert_eq!(result.pages[0].nodes[0].rect.y, 20.0);
5402 assert_eq!(result.pages[0].nodes[1].rect.y, 70.0); }
5404
5405 #[test]
5406 fn pagination_with_occur_repeating() {
5407 let mut tree = FormTree::new();
5409 let row = tree.add_node(FormNode {
5410 name: "DataRow".to_string(),
5411 node_type: FormNodeType::Subform,
5412 box_model: BoxModel {
5413 width: Some(200.0),
5414 height: Some(25.0),
5415 max_width: f64::MAX,
5416 max_height: f64::MAX,
5417 ..Default::default()
5418 },
5419 layout: LayoutStrategy::Positioned,
5420 children: vec![],
5421 occur: Occur::repeating(1, None, 8),
5422 font: FontMetrics::default(),
5423 calculate: None,
5424 validate: None,
5425 column_widths: vec![],
5426 col_span: 1,
5427 });
5428
5429 let root = tree.add_node(FormNode {
5430 name: "Root".to_string(),
5431 node_type: FormNodeType::Root,
5432 box_model: BoxModel {
5433 width: Some(400.0),
5434 height: Some(100.0),
5435 max_width: f64::MAX,
5436 max_height: f64::MAX,
5437 ..Default::default()
5438 },
5439 layout: LayoutStrategy::TopToBottom,
5440 children: vec![row],
5441 occur: Occur::once(),
5442 font: FontMetrics::default(),
5443 calculate: None,
5444 validate: None,
5445 column_widths: vec![],
5446 col_span: 1,
5447 });
5448
5449 let engine = LayoutEngine::new(&tree);
5450 let result = engine.layout(root).unwrap();
5451
5452 assert_eq!(result.pages.len(), 2);
5454 assert_eq!(result.pages[0].nodes.len(), 4);
5455 assert_eq!(result.pages[1].nodes.len(), 4);
5456 }
5457
5458 #[test]
5459 fn pagination_oversized_item_forced() {
5460 let mut tree = FormTree::new();
5462 let f1 = make_field(&mut tree, "Big", 200.0, 200.0); let f2 = make_field(&mut tree, "Small", 200.0, 30.0);
5464
5465 let root = tree.add_node(FormNode {
5466 name: "Root".to_string(),
5467 node_type: FormNodeType::Root,
5468 box_model: BoxModel {
5469 width: Some(400.0),
5470 height: Some(100.0), max_width: f64::MAX,
5472 max_height: f64::MAX,
5473 ..Default::default()
5474 },
5475 layout: LayoutStrategy::TopToBottom,
5476 children: vec![f1, f2],
5477 occur: Occur::once(),
5478 font: FontMetrics::default(),
5479 calculate: None,
5480 validate: None,
5481 column_widths: vec![],
5482 col_span: 1,
5483 });
5484
5485 let engine = LayoutEngine::new(&tree);
5486 let result = engine.layout(root).unwrap();
5487
5488 assert_eq!(result.pages.len(), 2);
5490 assert_eq!(result.pages[0].nodes[0].name, "Big");
5491 assert_eq!(result.pages[1].nodes[0].name, "Small");
5492 }
5493
5494 #[test]
5495 fn pagination_page_dimensions_correct() {
5496 let mut tree = FormTree::new();
5498 let mut fields = Vec::new();
5499 for i in 0..5 {
5500 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 50.0));
5501 }
5502
5503 let root = tree.add_node(FormNode {
5504 name: "Root".to_string(),
5505 node_type: FormNodeType::Root,
5506 box_model: BoxModel {
5507 width: Some(500.0),
5508 height: Some(120.0),
5509 max_width: f64::MAX,
5510 max_height: f64::MAX,
5511 ..Default::default()
5512 },
5513 layout: LayoutStrategy::TopToBottom,
5514 children: fields,
5515 occur: Occur::once(),
5516 font: FontMetrics::default(),
5517 calculate: None,
5518 validate: None,
5519 column_widths: vec![],
5520 col_span: 1,
5521 });
5522
5523 let engine = LayoutEngine::new(&tree);
5524 let result = engine.layout(root).unwrap();
5525
5526 for page in &result.pages {
5527 assert_eq!(page.width, 500.0);
5528 assert_eq!(page.height, 120.0);
5529 }
5530 }
5531
5532 #[test]
5535 fn split_subform_across_pages() {
5536 let mut tree = FormTree::new();
5540 let header = make_field(&mut tree, "Header", 300.0, 40.0);
5541
5542 let mut sub_children = Vec::new();
5543 for i in 0..6 {
5544 sub_children.push(make_field(&mut tree, &format!("Row{i}"), 300.0, 30.0));
5545 }
5546
5547 let subform = tree.add_node(FormNode {
5548 name: "DataBlock".to_string(),
5549 node_type: FormNodeType::Subform,
5550 box_model: BoxModel {
5551 width: Some(300.0),
5552 height: None, max_width: f64::MAX,
5554 max_height: f64::MAX,
5555 ..Default::default()
5556 },
5557 layout: LayoutStrategy::TopToBottom,
5558 children: sub_children,
5559 occur: Occur::once(),
5560 font: FontMetrics::default(),
5561 calculate: None,
5562 validate: None,
5563 column_widths: vec![],
5564 col_span: 1,
5565 });
5566
5567 let root = tree.add_node(FormNode {
5568 name: "Root".to_string(),
5569 node_type: FormNodeType::Root,
5570 box_model: BoxModel {
5571 width: Some(400.0),
5572 height: Some(160.0), max_width: f64::MAX,
5574 max_height: f64::MAX,
5575 ..Default::default()
5576 },
5577 layout: LayoutStrategy::TopToBottom,
5578 children: vec![header, subform],
5579 occur: Occur::once(),
5580 font: FontMetrics::default(),
5581 calculate: None,
5582 validate: None,
5583 column_widths: vec![],
5584 col_span: 1,
5585 });
5586
5587 let engine = LayoutEngine::new(&tree);
5588 let result = engine.layout(root).unwrap();
5589
5590 assert!(result.pages.len() >= 2);
5593 let p1 = &result.pages[0];
5595 assert_eq!(p1.nodes[0].name, "Header");
5596 assert_eq!(p1.nodes[1].name, "DataBlock");
5597 let split_sub = &p1.nodes[1];
5598 assert_eq!(split_sub.children.len(), 4); let p2 = &result.pages[1];
5602 assert_eq!(p2.nodes.len(), 2);
5603 }
5604
5605 #[test]
5606 fn split_preserves_node_positions() {
5607 let mut tree = FormTree::new();
5609 let mut sub_children = Vec::new();
5610 for i in 0..4 {
5611 sub_children.push(make_field(&mut tree, &format!("Row{i}"), 200.0, 25.0));
5612 }
5613
5614 let subform = tree.add_node(FormNode {
5615 name: "Block".to_string(),
5616 node_type: FormNodeType::Subform,
5617 box_model: BoxModel {
5618 width: Some(200.0),
5619 height: None,
5620 max_width: f64::MAX,
5621 max_height: f64::MAX,
5622 ..Default::default()
5623 },
5624 layout: LayoutStrategy::TopToBottom,
5625 children: sub_children,
5626 occur: Occur::once(),
5627 font: FontMetrics::default(),
5628 calculate: None,
5629 validate: None,
5630 column_widths: vec![],
5631 col_span: 1,
5632 });
5633
5634 let root = tree.add_node(FormNode {
5635 name: "Root".to_string(),
5636 node_type: FormNodeType::Root,
5637 box_model: BoxModel {
5638 width: Some(400.0),
5639 height: Some(60.0), max_width: f64::MAX,
5641 max_height: f64::MAX,
5642 ..Default::default()
5643 },
5644 layout: LayoutStrategy::TopToBottom,
5645 children: vec![subform],
5646 occur: Occur::once(),
5647 font: FontMetrics::default(),
5648 calculate: None,
5649 validate: None,
5650 column_widths: vec![],
5651 col_span: 1,
5652 });
5653
5654 let engine = LayoutEngine::new(&tree);
5655 let result = engine.layout(root).unwrap();
5656
5657 let split_sub = &result.pages[0].nodes[0];
5659 assert_eq!(split_sub.children.len(), 2);
5660 assert_eq!(split_sub.children[0].rect.y, 0.0);
5661 assert_eq!(split_sub.children[1].rect.y, 25.0);
5662 }
5663
5664 #[test]
5665 fn split_recurses_into_oversized_first_child() {
5666 let mut tree = FormTree::new();
5667
5668 let mut rows = Vec::new();
5669 for i in 0..6 {
5670 rows.push(make_field(&mut tree, &format!("Row{i}"), 300.0, 30.0));
5671 }
5672
5673 let inner = tree.add_node(FormNode {
5674 name: "InnerBlock".to_string(),
5675 node_type: FormNodeType::Subform,
5676 box_model: BoxModel {
5677 width: Some(300.0),
5678 height: None,
5679 max_width: f64::MAX,
5680 max_height: f64::MAX,
5681 ..Default::default()
5682 },
5683 layout: LayoutStrategy::TopToBottom,
5684 children: rows,
5685 occur: Occur::once(),
5686 font: FontMetrics::default(),
5687 calculate: None,
5688 validate: None,
5689 column_widths: vec![],
5690 col_span: 1,
5691 });
5692
5693 let outer = tree.add_node(FormNode {
5694 name: "OuterBlock".to_string(),
5695 node_type: FormNodeType::Subform,
5696 box_model: BoxModel {
5697 width: Some(300.0),
5698 height: None,
5699 max_width: f64::MAX,
5700 max_height: f64::MAX,
5701 ..Default::default()
5702 },
5703 layout: LayoutStrategy::TopToBottom,
5704 children: vec![inner],
5705 occur: Occur::once(),
5706 font: FontMetrics::default(),
5707 calculate: None,
5708 validate: None,
5709 column_widths: vec![],
5710 col_span: 1,
5711 });
5712
5713 let root = tree.add_node(FormNode {
5714 name: "Root".to_string(),
5715 node_type: FormNodeType::Root,
5716 box_model: BoxModel {
5717 width: Some(400.0),
5718 height: Some(100.0),
5719 max_width: f64::MAX,
5720 max_height: f64::MAX,
5721 ..Default::default()
5722 },
5723 layout: LayoutStrategy::TopToBottom,
5724 children: vec![outer],
5725 occur: Occur::once(),
5726 font: FontMetrics::default(),
5727 calculate: None,
5728 validate: None,
5729 column_widths: vec![],
5730 col_span: 1,
5731 });
5732
5733 let engine = LayoutEngine::new(&tree);
5734 let result = engine.layout(root).unwrap();
5735
5736 assert_eq!(result.pages.len(), 2);
5737 assert_eq!(result.pages[0].nodes[0].name, "OuterBlock");
5738 assert_eq!(result.pages[0].nodes[0].children[0].name, "InnerBlock");
5739 assert_eq!(result.pages[0].nodes[0].children[0].children.len(), 3);
5740 }
5741
5742 #[test]
5743 fn no_split_for_non_tb_layout() {
5744 let mut tree = FormTree::new();
5746 let header = make_field(&mut tree, "Header", 300.0, 80.0);
5747
5748 let f1 = tree.add_node(FormNode {
5749 name: "Child1".to_string(),
5750 node_type: FormNodeType::Field {
5751 value: "A".to_string(),
5752 },
5753 box_model: BoxModel {
5754 width: Some(100.0),
5755 height: Some(50.0),
5756 x: 0.0,
5757 y: 0.0,
5758 max_width: f64::MAX,
5759 max_height: f64::MAX,
5760 ..Default::default()
5761 },
5762 layout: LayoutStrategy::Positioned,
5763 children: vec![],
5764 occur: Occur::once(),
5765 font: FontMetrics::default(),
5766 calculate: None,
5767 validate: None,
5768 column_widths: vec![],
5769 col_span: 1,
5770 });
5771
5772 let subform = tree.add_node(FormNode {
5773 name: "PositionedBlock".to_string(),
5774 node_type: FormNodeType::Subform,
5775 box_model: BoxModel {
5776 width: Some(200.0),
5777 height: Some(100.0), max_width: f64::MAX,
5779 max_height: f64::MAX,
5780 ..Default::default()
5781 },
5782 layout: LayoutStrategy::Positioned, children: vec![f1],
5784 occur: Occur::once(),
5785 font: FontMetrics::default(),
5786 calculate: None,
5787 validate: None,
5788 column_widths: vec![],
5789 col_span: 1,
5790 });
5791
5792 let root = tree.add_node(FormNode {
5793 name: "Root".to_string(),
5794 node_type: FormNodeType::Root,
5795 box_model: BoxModel {
5796 width: Some(400.0),
5797 height: Some(100.0), max_width: f64::MAX,
5799 max_height: f64::MAX,
5800 ..Default::default()
5801 },
5802 layout: LayoutStrategy::TopToBottom,
5803 children: vec![header, subform],
5804 occur: Occur::once(),
5805 font: FontMetrics::default(),
5806 calculate: None,
5807 validate: None,
5808 column_widths: vec![],
5809 col_span: 1,
5810 });
5811
5812 let engine = LayoutEngine::new(&tree);
5813 let result = engine.layout(root).unwrap();
5814
5815 assert_eq!(result.pages.len(), 2);
5817 assert_eq!(result.pages[0].nodes[0].name, "Header");
5818 assert_eq!(result.pages[1].nodes[0].name, "PositionedBlock");
5819 }
5820
5821 #[test]
5822 fn layout_tb_splits_on_overflow() {
5823 let mut tree = FormTree::new();
5824 let mut children = Vec::new();
5825 for i in 0..10 {
5826 children.push(make_field(&mut tree, &format!("F{i}"), 200.0, 100.0));
5827 }
5828 let parent = make_subform(
5829 &mut tree,
5830 "Parent",
5831 LayoutStrategy::TopToBottom,
5832 Some(200.0),
5833 None,
5834 children,
5835 );
5836
5837 let engine = LayoutEngine::new(&tree);
5838 let parent_children = tree.get(parent).children.clone();
5839 let nodes = engine
5840 .layout_tb(
5841 &parent_children,
5842 Size {
5843 width: 200.0,
5844 height: 300.0,
5845 },
5846 )
5847 .unwrap();
5848
5849 assert_eq!(nodes.len(), 3);
5851 assert!(nodes.iter().all(|n| n.rect.y + n.rect.height <= 301.0));
5852 }
5853
5854 #[test]
5855 fn can_split_checks() {
5856 let mut tree = FormTree::new();
5857 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
5858
5859 let tb_sub = tree.add_node(FormNode {
5860 name: "TB".to_string(),
5861 node_type: FormNodeType::Subform,
5862 box_model: BoxModel {
5863 max_width: f64::MAX,
5864 max_height: f64::MAX,
5865 ..Default::default()
5866 },
5867 layout: LayoutStrategy::TopToBottom,
5868 children: vec![f1],
5869 occur: Occur::once(),
5870 font: FontMetrics::default(),
5871 calculate: None,
5872 validate: None,
5873 column_widths: vec![],
5874 col_span: 1,
5875 });
5876
5877 let pos_sub = tree.add_node(FormNode {
5878 name: "Pos".to_string(),
5879 node_type: FormNodeType::Subform,
5880 box_model: BoxModel {
5881 max_width: f64::MAX,
5882 max_height: f64::MAX,
5883 ..Default::default()
5884 },
5885 layout: LayoutStrategy::Positioned,
5886 children: vec![f1],
5887 occur: Occur::once(),
5888 font: FontMetrics::default(),
5889 calculate: None,
5890 validate: None,
5891 column_widths: vec![],
5892 col_span: 1,
5893 });
5894
5895 let empty_sub = tree.add_node(FormNode {
5896 name: "Empty".to_string(),
5897 node_type: FormNodeType::Subform,
5898 box_model: BoxModel {
5899 max_width: f64::MAX,
5900 max_height: f64::MAX,
5901 ..Default::default()
5902 },
5903 layout: LayoutStrategy::TopToBottom,
5904 children: vec![],
5905 occur: Occur::once(),
5906 font: FontMetrics::default(),
5907 calculate: None,
5908 validate: None,
5909 column_widths: vec![],
5910 col_span: 1,
5911 });
5912
5913 let engine = LayoutEngine::new(&tree);
5914 assert!(engine.can_split(tb_sub));
5915 assert!(engine.can_split(pos_sub));
5917 assert!(!engine.can_split(empty_sub));
5918 }
5919
5920 #[test]
5921 fn positioned_subform_paginates_across_pages() {
5922 let mut tree = FormTree::new();
5925
5926 let mut fields = Vec::new();
5929 for i in 0..10 {
5930 let f = tree.add_node(FormNode {
5931 name: format!("F{i}"),
5932 node_type: FormNodeType::Field {
5933 value: format!("Value{i}"),
5934 },
5935 box_model: BoxModel {
5936 width: Some(200.0),
5937 height: Some(60.0),
5938 x: 10.0,
5939 y: i as f64 * 80.0,
5940 max_width: f64::MAX,
5941 max_height: f64::MAX,
5942 ..Default::default()
5943 },
5944 layout: LayoutStrategy::Positioned,
5945 children: vec![],
5946 occur: Occur::once(),
5947 font: FontMetrics::default(),
5948 calculate: None,
5949 validate: None,
5950 column_widths: vec![],
5951 col_span: 1,
5952 });
5953 fields.push(f);
5954 }
5955
5956 let positioned = tree.add_node(FormNode {
5958 name: "PositionedBody".to_string(),
5959 node_type: FormNodeType::Subform,
5960 box_model: BoxModel {
5961 width: Some(400.0),
5962 max_width: f64::MAX,
5963 max_height: f64::MAX,
5964 ..Default::default()
5965 },
5966 layout: LayoutStrategy::Positioned,
5967 children: fields,
5968 occur: Occur::once(),
5969 font: FontMetrics::default(),
5970 calculate: None,
5971 validate: None,
5972 column_widths: vec![],
5973 col_span: 1,
5974 });
5975
5976 let page_area = tree.add_node(FormNode {
5978 name: "Page1".to_string(),
5979 node_type: FormNodeType::PageArea {
5980 content_areas: vec![ContentArea {
5981 name: "Body".to_string(),
5982 x: 0.0,
5983 y: 0.0,
5984 width: 400.0,
5985 height: 400.0,
5986 leader: None,
5987 trailer: None,
5988 }],
5989 },
5990 box_model: BoxModel {
5991 width: Some(400.0),
5992 height: Some(400.0),
5993 max_width: f64::MAX,
5994 max_height: f64::MAX,
5995 ..Default::default()
5996 },
5997 layout: LayoutStrategy::Positioned,
5998 children: vec![],
5999 occur: Occur::once(),
6000 font: FontMetrics::default(),
6001 calculate: None,
6002 validate: None,
6003 column_widths: vec![],
6004 col_span: 1,
6005 });
6006
6007 let root = tree.add_node(FormNode {
6008 name: "Root".to_string(),
6009 node_type: FormNodeType::Root,
6010 box_model: BoxModel {
6011 max_width: f64::MAX,
6012 max_height: f64::MAX,
6013 ..Default::default()
6014 },
6015 layout: LayoutStrategy::TopToBottom,
6016 children: vec![page_area, positioned],
6017 occur: Occur::once(),
6018 font: FontMetrics::default(),
6019 calculate: None,
6020 validate: None,
6021 column_widths: vec![],
6022 col_span: 1,
6023 });
6024
6025 let engine = LayoutEngine::new(&tree);
6026 let result = engine.layout(root).unwrap();
6027
6028 assert!(
6034 result.pages.len() >= 2,
6035 "Expected at least 2 pages, got {}",
6036 result.pages.len()
6037 );
6038
6039 let page1_children = count_leaf_nodes(&result.pages[0]);
6041 let page2_children = count_leaf_nodes(&result.pages[1]);
6042 assert!(page1_children > 0, "Page 1 should have content");
6043 assert!(page2_children > 0, "Page 2 should have content");
6044 assert_eq!(
6045 page1_children + page2_children,
6046 10,
6047 "All 10 fields should be placed across pages"
6048 );
6049 }
6050
6051 #[test]
6052 fn positioned_split_preserves_parent_margin_offset() {
6053 use crate::types::Insets;
6054
6055 let mut tree = FormTree::new();
6056
6057 let first = tree.add_node(FormNode {
6058 name: "First".to_string(),
6059 node_type: FormNodeType::Field {
6060 value: "First".to_string(),
6061 },
6062 box_model: BoxModel {
6063 width: Some(100.0),
6064 height: Some(40.0),
6065 x: 0.0,
6066 y: 0.0,
6067 max_width: f64::MAX,
6068 max_height: f64::MAX,
6069 ..Default::default()
6070 },
6071 layout: LayoutStrategy::Positioned,
6072 children: vec![],
6073 occur: Occur::once(),
6074 font: FontMetrics::default(),
6075 calculate: None,
6076 validate: None,
6077 column_widths: vec![],
6078 col_span: 1,
6079 });
6080 let second = tree.add_node(FormNode {
6081 name: "Second".to_string(),
6082 node_type: FormNodeType::Field {
6083 value: "Second".to_string(),
6084 },
6085 box_model: BoxModel {
6086 width: Some(100.0),
6087 height: Some(40.0),
6088 x: 0.0,
6089 y: 60.0,
6090 max_width: f64::MAX,
6091 max_height: f64::MAX,
6092 ..Default::default()
6093 },
6094 layout: LayoutStrategy::Positioned,
6095 children: vec![],
6096 occur: Occur::once(),
6097 font: FontMetrics::default(),
6098 calculate: None,
6099 validate: None,
6100 column_widths: vec![],
6101 col_span: 1,
6102 });
6103
6104 let positioned = tree.add_node(FormNode {
6105 name: "PositionedBody".to_string(),
6106 node_type: FormNodeType::Subform,
6107 box_model: BoxModel {
6108 width: Some(140.0),
6109 margins: Insets {
6110 top: 10.0,
6111 right: 0.0,
6112 bottom: 0.0,
6113 left: 8.0,
6114 },
6115 max_width: f64::MAX,
6116 max_height: f64::MAX,
6117 ..Default::default()
6118 },
6119 layout: LayoutStrategy::Positioned,
6120 children: vec![first, second],
6121 occur: Occur::once(),
6122 font: FontMetrics::default(),
6123 calculate: None,
6124 validate: None,
6125 column_widths: vec![],
6126 col_span: 1,
6127 });
6128
6129 let page_area = tree.add_node(FormNode {
6130 name: "Page1".to_string(),
6131 node_type: FormNodeType::PageArea {
6132 content_areas: vec![ContentArea {
6133 name: "Body".to_string(),
6134 x: 0.0,
6135 y: 0.0,
6136 width: 200.0,
6137 height: 70.0,
6138 leader: None,
6139 trailer: None,
6140 }],
6141 },
6142 box_model: BoxModel {
6143 width: Some(200.0),
6144 height: Some(70.0),
6145 max_width: f64::MAX,
6146 max_height: f64::MAX,
6147 ..Default::default()
6148 },
6149 layout: LayoutStrategy::Positioned,
6150 children: vec![],
6151 occur: Occur::once(),
6152 font: FontMetrics::default(),
6153 calculate: None,
6154 validate: None,
6155 column_widths: vec![],
6156 col_span: 1,
6157 });
6158
6159 let root = tree.add_node(FormNode {
6160 name: "Root".to_string(),
6161 node_type: FormNodeType::Root,
6162 box_model: BoxModel {
6163 max_width: f64::MAX,
6164 max_height: f64::MAX,
6165 ..Default::default()
6166 },
6167 layout: LayoutStrategy::TopToBottom,
6168 children: vec![page_area, positioned],
6169 occur: Occur::once(),
6170 font: FontMetrics::default(),
6171 calculate: None,
6172 validate: None,
6173 column_widths: vec![],
6174 col_span: 1,
6175 });
6176
6177 let engine = LayoutEngine::new(&tree);
6178 let result = engine.layout(root).unwrap();
6179
6180 assert_eq!(result.pages.len(), 2);
6181 assert_eq!(result.pages[0].nodes[0].children.len(), 1);
6182 assert_eq!(result.pages[1].nodes[0].children.len(), 1);
6183 assert_eq!(result.pages[0].nodes[0].children[0].rect.x, 8.0);
6184 assert_eq!(result.pages[0].nodes[0].children[0].rect.y, 10.0);
6185 assert_eq!(result.pages[1].nodes[0].children[0].rect.x, 8.0);
6186 assert_eq!(result.pages[1].nodes[0].children[0].rect.y, 10.0);
6187 }
6188
6189 #[test]
6190 fn single_positioned_child_no_overpagination() {
6191 let mut tree = FormTree::new();
6195
6196 let mut fields = Vec::new();
6198 for i in 0..5 {
6199 let f = tree.add_node(FormNode {
6200 name: format!("F{i}"),
6201 node_type: FormNodeType::Field {
6202 value: format!("Value{i}"),
6203 },
6204 box_model: BoxModel {
6205 width: Some(200.0),
6206 height: Some(30.0),
6207 x: 36.0,
6208 y: 36.0 + i as f64 * 40.0,
6209 max_width: f64::MAX,
6210 max_height: f64::MAX,
6211 ..Default::default()
6212 },
6213 layout: LayoutStrategy::Positioned,
6214 children: vec![],
6215 occur: Occur::once(),
6216 font: FontMetrics::default(),
6217 calculate: None,
6218 validate: None,
6219 column_widths: vec![],
6220 col_span: 1,
6221 });
6222 fields.push(f);
6223 }
6224
6225 let positioned = tree.add_node(FormNode {
6227 name: "PageSubform".to_string(),
6228 node_type: FormNodeType::Subform,
6229 box_model: BoxModel {
6230 width: Some(612.0),
6231 height: Some(792.0),
6232 max_width: f64::MAX,
6233 max_height: f64::MAX,
6234 ..Default::default()
6235 },
6236 layout: LayoutStrategy::Positioned,
6237 children: fields,
6238 occur: Occur::once(),
6239 font: FontMetrics::default(),
6240 calculate: None,
6241 validate: None,
6242 column_widths: vec![],
6243 col_span: 1,
6244 });
6245
6246 let page_area = tree.add_node(FormNode {
6247 name: "Page1".to_string(),
6248 node_type: FormNodeType::PageArea {
6249 content_areas: vec![ContentArea {
6250 name: "Body".to_string(),
6251 x: 0.0,
6252 y: 0.0,
6253 width: 612.0,
6254 height: 792.0,
6255 leader: None,
6256 trailer: None,
6257 }],
6258 },
6259 box_model: BoxModel {
6260 width: Some(612.0),
6261 height: Some(792.0),
6262 max_width: f64::MAX,
6263 max_height: f64::MAX,
6264 ..Default::default()
6265 },
6266 layout: LayoutStrategy::Positioned,
6267 children: vec![],
6268 occur: Occur::once(),
6269 font: FontMetrics::default(),
6270 calculate: None,
6271 validate: None,
6272 column_widths: vec![],
6273 col_span: 1,
6274 });
6275
6276 let root = tree.add_node(FormNode {
6277 name: "Root".to_string(),
6278 node_type: FormNodeType::Root,
6279 box_model: BoxModel {
6280 max_width: f64::MAX,
6281 max_height: f64::MAX,
6282 ..Default::default()
6283 },
6284 layout: LayoutStrategy::TopToBottom,
6285 children: vec![page_area, positioned],
6286 occur: Occur::once(),
6287 font: FontMetrics::default(),
6288 calculate: None,
6289 validate: None,
6290 column_widths: vec![],
6291 col_span: 1,
6292 });
6293
6294 let engine = LayoutEngine::new(&tree);
6295 let result = engine.layout(root).unwrap();
6296
6297 assert_eq!(
6298 result.pages.len(),
6299 1,
6300 "Single positioned child fitting on one page should produce 1 page, got {}",
6301 result.pages.len()
6302 );
6303
6304 let leaf_count = count_leaf_nodes(&result.pages[0]);
6306 assert_eq!(leaf_count, 5, "All 5 fields should be placed on page 1");
6307 }
6308
6309 #[test]
6310 fn positioned_inside_tb_no_infinite_pagination() {
6311 let mut tree = FormTree::new();
6326
6327 let mut fields = Vec::new();
6328 for i in 0..8 {
6329 let f = tree.add_node(FormNode {
6330 name: format!("F{i}"),
6331 node_type: FormNodeType::Field {
6332 value: format!("Val{i}"),
6333 },
6334 box_model: BoxModel {
6335 width: Some(200.0),
6336 height: Some(60.0),
6337 x: 10.0,
6338 y: i as f64 * 80.0,
6339 max_width: f64::MAX,
6340 max_height: f64::MAX,
6341 ..Default::default()
6342 },
6343 layout: LayoutStrategy::Positioned,
6344 children: vec![],
6345 occur: Occur::once(),
6346 font: FontMetrics::default(),
6347 calculate: None,
6348 validate: None,
6349 column_widths: vec![],
6350 col_span: 1,
6351 });
6352 fields.push(f);
6353 }
6354
6355 let positioned = tree.add_node(FormNode {
6356 name: "PositionedBody".to_string(),
6357 node_type: FormNodeType::Subform,
6358 box_model: BoxModel {
6359 width: Some(400.0),
6360 max_width: f64::MAX,
6361 max_height: f64::MAX,
6362 ..Default::default()
6363 },
6364 layout: LayoutStrategy::Positioned,
6365 children: fields,
6366 occur: Occur::once(),
6367 font: FontMetrics::default(),
6368 calculate: None,
6369 validate: None,
6370 column_widths: vec![],
6371 col_span: 1,
6372 });
6373
6374 let tb_wrapper = tree.add_node(FormNode {
6377 name: "TbWrapper".to_string(),
6378 node_type: FormNodeType::Subform,
6379 box_model: BoxModel {
6380 width: Some(400.0),
6381 max_width: f64::MAX,
6382 max_height: f64::MAX,
6383 ..Default::default()
6384 },
6385 layout: LayoutStrategy::TopToBottom,
6386 children: vec![positioned],
6387 occur: Occur::once(),
6388 font: FontMetrics::default(),
6389 calculate: None,
6390 validate: None,
6391 column_widths: vec![],
6392 col_span: 1,
6393 });
6394
6395 let page_area = tree.add_node(FormNode {
6396 name: "Page1".to_string(),
6397 node_type: FormNodeType::PageArea {
6398 content_areas: vec![ContentArea {
6399 name: "Body".to_string(),
6400 x: 0.0,
6401 y: 0.0,
6402 width: 400.0,
6403 height: 300.0,
6404 leader: None,
6405 trailer: None,
6406 }],
6407 },
6408 box_model: BoxModel {
6409 width: Some(400.0),
6410 height: Some(300.0),
6411 max_width: f64::MAX,
6412 max_height: f64::MAX,
6413 ..Default::default()
6414 },
6415 layout: LayoutStrategy::Positioned,
6416 children: vec![],
6417 occur: Occur::once(),
6418 font: FontMetrics::default(),
6419 calculate: None,
6420 validate: None,
6421 column_widths: vec![],
6422 col_span: 1,
6423 });
6424
6425 let root = tree.add_node(FormNode {
6426 name: "Root".to_string(),
6427 node_type: FormNodeType::Root,
6428 box_model: BoxModel {
6429 max_width: f64::MAX,
6430 max_height: f64::MAX,
6431 ..Default::default()
6432 },
6433 layout: LayoutStrategy::TopToBottom,
6434 children: vec![page_area, tb_wrapper],
6435 occur: Occur::once(),
6436 font: FontMetrics::default(),
6437 calculate: None,
6438 validate: None,
6439 column_widths: vec![],
6440 col_span: 1,
6441 });
6442
6443 let engine = LayoutEngine::new(&tree);
6444 let result = engine.layout(root).unwrap();
6445
6446 assert!(
6449 result.pages.len() <= 5,
6450 "Expected at most 5 pages for 8 fields across 300pt pages, got {} \
6451 (infinite pagination bug #737)",
6452 result.pages.len()
6453 );
6454 assert!(
6455 result.pages.len() >= 2,
6456 "Expected at least 2 pages, got {}",
6457 result.pages.len()
6458 );
6459
6460 let total_leaves: usize = result.pages.iter().map(|p| count_leaf_nodes(p)).sum();
6462 assert_eq!(
6463 total_leaves, 8,
6464 "All 8 fields should appear across pages, found {}",
6465 total_leaves
6466 );
6467 }
6468
6469 #[test]
6470 fn positioned_subform_with_explicit_height_not_split() {
6471 let mut tree = FormTree::new();
6473 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
6474
6475 let positioned = tree.add_node(FormNode {
6476 name: "FixedBlock".to_string(),
6477 node_type: FormNodeType::Subform,
6478 box_model: BoxModel {
6479 width: Some(200.0),
6480 height: Some(500.0), max_width: f64::MAX,
6482 max_height: f64::MAX,
6483 ..Default::default()
6484 },
6485 layout: LayoutStrategy::Positioned,
6486 children: vec![f1],
6487 occur: Occur::once(),
6488 font: FontMetrics::default(),
6489 calculate: None,
6490 validate: None,
6491 column_widths: vec![],
6492 col_span: 1,
6493 });
6494
6495 let engine = LayoutEngine::new(&tree);
6496 assert!(
6497 !engine.can_split(positioned),
6498 "Positioned subform with explicit height should not be splittable"
6499 );
6500 }
6501
6502 #[test]
6505 fn leader_placed_at_top() {
6506 let mut tree = FormTree::new();
6508 let header = make_field(&mut tree, "PageHeader", 300.0, 30.0);
6509 let f1 = make_field(&mut tree, "Content1", 300.0, 50.0);
6510 let f2 = make_field(&mut tree, "Content2", 300.0, 50.0);
6511
6512 let page_area = tree.add_node(FormNode {
6513 name: "Page1".to_string(),
6514 node_type: FormNodeType::PageArea {
6515 content_areas: vec![ContentArea {
6516 name: "Body".to_string(),
6517 x: 0.0,
6518 y: 0.0,
6519 width: 400.0,
6520 height: 200.0,
6521 leader: Some(header),
6522 trailer: None,
6523 }],
6524 },
6525 box_model: BoxModel {
6526 width: Some(400.0),
6527 height: Some(200.0),
6528 max_width: f64::MAX,
6529 max_height: f64::MAX,
6530 ..Default::default()
6531 },
6532 layout: LayoutStrategy::Positioned,
6533 children: vec![],
6534 occur: Occur::once(),
6535 font: FontMetrics::default(),
6536 calculate: None,
6537 validate: None,
6538 column_widths: vec![],
6539 col_span: 1,
6540 });
6541
6542 let root = tree.add_node(FormNode {
6543 name: "Root".to_string(),
6544 node_type: FormNodeType::Root,
6545 box_model: BoxModel {
6546 width: Some(400.0),
6547 height: Some(200.0),
6548 max_width: f64::MAX,
6549 max_height: f64::MAX,
6550 ..Default::default()
6551 },
6552 layout: LayoutStrategy::TopToBottom,
6553 children: vec![page_area, f1, f2],
6554 occur: Occur::once(),
6555 font: FontMetrics::default(),
6556 calculate: None,
6557 validate: None,
6558 column_widths: vec![],
6559 col_span: 1,
6560 });
6561
6562 let engine = LayoutEngine::new(&tree);
6563 let result = engine.layout(root).unwrap();
6564
6565 let page = &result.pages[0];
6566 assert_eq!(page.nodes[0].name, "PageHeader");
6568 assert_eq!(page.nodes[0].rect.y, 0.0);
6569 assert!(page.nodes.len() >= 2);
6571 let first_content = page.nodes.iter().find(|n| n.name == "Content1").unwrap();
6573 assert_eq!(first_content.rect.y, 30.0);
6574 }
6575
6576 #[test]
6577 fn trailer_placed_at_bottom() {
6578 let mut tree = FormTree::new();
6580 let footer = make_field(&mut tree, "PageFooter", 300.0, 25.0);
6581 let f1 = make_field(&mut tree, "Content1", 300.0, 50.0);
6582
6583 let page_area = tree.add_node(FormNode {
6584 name: "Page1".to_string(),
6585 node_type: FormNodeType::PageArea {
6586 content_areas: vec![ContentArea {
6587 name: "Body".to_string(),
6588 x: 0.0,
6589 y: 0.0,
6590 width: 400.0,
6591 height: 200.0,
6592 leader: None,
6593 trailer: Some(footer),
6594 }],
6595 },
6596 box_model: BoxModel {
6597 width: Some(400.0),
6598 height: Some(200.0),
6599 max_width: f64::MAX,
6600 max_height: f64::MAX,
6601 ..Default::default()
6602 },
6603 layout: LayoutStrategy::Positioned,
6604 children: vec![],
6605 occur: Occur::once(),
6606 font: FontMetrics::default(),
6607 calculate: None,
6608 validate: None,
6609 column_widths: vec![],
6610 col_span: 1,
6611 });
6612
6613 let root = tree.add_node(FormNode {
6614 name: "Root".to_string(),
6615 node_type: FormNodeType::Root,
6616 box_model: BoxModel {
6617 width: Some(400.0),
6618 height: Some(200.0),
6619 max_width: f64::MAX,
6620 max_height: f64::MAX,
6621 ..Default::default()
6622 },
6623 layout: LayoutStrategy::TopToBottom,
6624 children: vec![page_area, f1],
6625 occur: Occur::once(),
6626 font: FontMetrics::default(),
6627 calculate: None,
6628 validate: None,
6629 column_widths: vec![],
6630 col_span: 1,
6631 });
6632
6633 let engine = LayoutEngine::new(&tree);
6634 let result = engine.layout(root).unwrap();
6635
6636 let page = &result.pages[0];
6637 let footer_node = page.nodes.iter().find(|n| n.name == "PageFooter").unwrap();
6639 assert_eq!(footer_node.rect.y, 175.0);
6640 }
6641
6642 #[test]
6643 fn leader_and_trailer_reduce_content_space() {
6644 let mut tree = FormTree::new();
6646 let header = make_field(&mut tree, "Header", 300.0, 30.0);
6647 let footer = make_field(&mut tree, "Footer", 300.0, 20.0);
6648
6649 let mut fields = Vec::new();
6651 for i in 0..5 {
6652 fields.push(make_field(&mut tree, &format!("F{i}"), 300.0, 30.0));
6653 }
6654
6655 let page_area = tree.add_node(FormNode {
6656 name: "Page1".to_string(),
6657 node_type: FormNodeType::PageArea {
6658 content_areas: vec![ContentArea {
6659 name: "Body".to_string(),
6660 x: 0.0,
6661 y: 0.0,
6662 width: 400.0,
6663 height: 200.0, leader: Some(header),
6665 trailer: Some(footer),
6666 }],
6667 },
6668 box_model: BoxModel {
6669 width: Some(400.0),
6670 height: Some(200.0),
6671 max_width: f64::MAX,
6672 max_height: f64::MAX,
6673 ..Default::default()
6674 },
6675 layout: LayoutStrategy::Positioned,
6676 children: vec![],
6677 occur: Occur::once(),
6678 font: FontMetrics::default(),
6679 calculate: None,
6680 validate: None,
6681 column_widths: vec![],
6682 col_span: 1,
6683 });
6684
6685 let mut root_children = vec![page_area];
6686 root_children.extend(fields);
6687
6688 let root = tree.add_node(FormNode {
6689 name: "Root".to_string(),
6690 node_type: FormNodeType::Root,
6691 box_model: BoxModel {
6692 width: Some(400.0),
6693 height: Some(200.0),
6694 max_width: f64::MAX,
6695 max_height: f64::MAX,
6696 ..Default::default()
6697 },
6698 layout: LayoutStrategy::TopToBottom,
6699 children: root_children,
6700 occur: Occur::once(),
6701 font: FontMetrics::default(),
6702 calculate: None,
6703 validate: None,
6704 column_widths: vec![],
6705 col_span: 1,
6706 });
6707
6708 let engine = LayoutEngine::new(&tree);
6709 let result = engine.layout(root).unwrap();
6710
6711 assert_eq!(result.pages.len(), 1);
6713 let page = &result.pages[0];
6714 assert_eq!(page.nodes.len(), 7);
6716 }
6717
6718 #[test]
6719 fn leader_trailer_repeated_on_overflow_pages() {
6720 let mut tree = FormTree::new();
6722 let header = make_field(&mut tree, "Header", 300.0, 30.0);
6723 let footer = make_field(&mut tree, "Footer", 300.0, 20.0);
6724
6725 let mut fields = Vec::new();
6728 for i in 0..8 {
6729 fields.push(make_field(&mut tree, &format!("F{i}"), 300.0, 30.0));
6730 }
6731
6732 let page_area = tree.add_node(FormNode {
6733 name: "Page1".to_string(),
6734 node_type: FormNodeType::PageArea {
6735 content_areas: vec![ContentArea {
6736 name: "Body".to_string(),
6737 x: 0.0,
6738 y: 0.0,
6739 width: 400.0,
6740 height: 200.0,
6741 leader: Some(header),
6742 trailer: Some(footer),
6743 }],
6744 },
6745 box_model: BoxModel {
6746 width: Some(400.0),
6747 height: Some(200.0),
6748 max_width: f64::MAX,
6749 max_height: f64::MAX,
6750 ..Default::default()
6751 },
6752 layout: LayoutStrategy::Positioned,
6753 children: vec![],
6754 occur: Occur::once(),
6755 font: FontMetrics::default(),
6756 calculate: None,
6757 validate: None,
6758 column_widths: vec![],
6759 col_span: 1,
6760 });
6761
6762 let mut root_children = vec![page_area];
6763 root_children.extend(fields);
6764
6765 let root = tree.add_node(FormNode {
6766 name: "Root".to_string(),
6767 node_type: FormNodeType::Root,
6768 box_model: BoxModel {
6769 width: Some(400.0),
6770 height: Some(200.0),
6771 max_width: f64::MAX,
6772 max_height: f64::MAX,
6773 ..Default::default()
6774 },
6775 layout: LayoutStrategy::TopToBottom,
6776 children: root_children,
6777 occur: Occur::once(),
6778 font: FontMetrics::default(),
6779 calculate: None,
6780 validate: None,
6781 column_widths: vec![],
6782 col_span: 1,
6783 });
6784
6785 let engine = LayoutEngine::new(&tree);
6786 let result = engine.layout(root).unwrap();
6787
6788 assert_eq!(result.pages.len(), 2);
6789
6790 for page in &result.pages {
6792 let has_header = page.nodes.iter().any(|n| n.name == "Header");
6793 let has_footer = page.nodes.iter().any(|n| n.name == "Footer");
6794 assert!(has_header, "Page missing header");
6795 assert!(has_footer, "Page missing footer");
6796 }
6797 }
6798
6799 #[test]
6802 fn draw_node_growable_height_from_text() {
6803 let mut tree = FormTree::new();
6805 let draw = tree.add_node(FormNode {
6806 name: "Label".to_string(),
6807 node_type: FormNodeType::Draw(DrawContent::Text("Hello World".to_string())),
6808 box_model: BoxModel {
6809 width: Some(200.0),
6810 height: None, max_width: f64::MAX,
6812 max_height: f64::MAX,
6813 ..Default::default()
6814 },
6815 layout: LayoutStrategy::Positioned,
6816 children: vec![],
6817 occur: Occur::once(),
6818 font: FontMetrics::default(), calculate: None,
6820 validate: None,
6821 column_widths: vec![],
6822 col_span: 1,
6823 });
6824 let root = make_subform(
6825 &mut tree,
6826 "Root",
6827 LayoutStrategy::TopToBottom,
6828 Some(612.0),
6829 Some(792.0),
6830 vec![draw],
6831 );
6832
6833 let engine = LayoutEngine::new(&tree);
6834 let result = engine.layout(root).unwrap();
6835
6836 let page = &result.pages[0];
6837 let label = &page.nodes[0];
6838 assert_eq!(label.rect.height, 12.0);
6841 }
6842
6843 #[test]
6844 fn draw_node_text_wraps_in_narrow_width() {
6845 let mut tree = FormTree::new();
6847 let draw = tree.add_node(FormNode {
6848 name: "Label".to_string(),
6849 node_type: FormNodeType::Draw(DrawContent::Text("Hello World".to_string())),
6850 box_model: BoxModel {
6851 width: Some(40.0), height: None,
6853 max_width: f64::MAX,
6854 max_height: f64::MAX,
6855 ..Default::default()
6856 },
6857 layout: LayoutStrategy::Positioned,
6858 children: vec![],
6859 occur: Occur::once(),
6860 font: FontMetrics::default(),
6861 calculate: None,
6862 validate: None,
6863 column_widths: vec![],
6864 col_span: 1,
6865 });
6866 let root = make_subform(
6867 &mut tree,
6868 "Root",
6869 LayoutStrategy::TopToBottom,
6870 Some(612.0),
6871 Some(792.0),
6872 vec![draw],
6873 );
6874
6875 let engine = LayoutEngine::new(&tree);
6876 let result = engine.layout(root).unwrap();
6877
6878 let label = &result.pages[0].nodes[0];
6879 assert_eq!(label.rect.height, 24.0);
6881 }
6882
6883 #[test]
6884 fn field_produces_wrapped_text_content() {
6885 let mut tree = FormTree::new();
6886 let field = tree.add_node(FormNode {
6887 name: "Name".to_string(),
6888 node_type: FormNodeType::Field {
6889 value: "John".to_string(),
6890 },
6891 box_model: BoxModel {
6892 width: Some(200.0),
6893 height: Some(20.0),
6894 max_width: f64::MAX,
6895 max_height: f64::MAX,
6896 ..Default::default()
6897 },
6898 layout: LayoutStrategy::Positioned,
6899 children: vec![],
6900 occur: Occur::once(),
6901 font: FontMetrics::default(),
6902 calculate: None,
6903 validate: None,
6904 column_widths: vec![],
6905 col_span: 1,
6906 });
6907 let root = make_subform(
6908 &mut tree,
6909 "Root",
6910 LayoutStrategy::TopToBottom,
6911 Some(612.0),
6912 Some(792.0),
6913 vec![field],
6914 );
6915
6916 let engine = LayoutEngine::new(&tree);
6917 let result = engine.layout(root).unwrap();
6918
6919 let node = &result.pages[0].nodes[0];
6920 match &node.content {
6921 LayoutContent::WrappedText {
6922 lines, font_size, ..
6923 } => {
6924 assert_eq!(lines.len(), 1);
6925 assert_eq!(lines[0], "John");
6926 assert_eq!(*font_size, 10.0);
6927 }
6928 other => panic!("Expected WrappedText, got {:?}", other),
6929 }
6930 }
6931
6932 #[test]
6933 fn draw_growable_width_and_height_from_text() {
6934 let mut tree = FormTree::new();
6936 let draw = tree.add_node(FormNode {
6937 name: "Auto".to_string(),
6938 node_type: FormNodeType::Draw(DrawContent::Text("Test".to_string())),
6939 box_model: BoxModel {
6940 width: None,
6941 height: None,
6942 max_width: f64::MAX,
6943 max_height: f64::MAX,
6944 ..Default::default()
6945 },
6946 layout: LayoutStrategy::Positioned,
6947 children: vec![],
6948 occur: Occur::once(),
6949 font: FontMetrics::default(),
6950 calculate: None,
6951 validate: None,
6952 column_widths: vec![],
6953 col_span: 1,
6954 });
6955
6956 let engine = LayoutEngine::new(&tree);
6957 let size = engine.compute_extent(draw);
6958 assert!((size.width - 19.45).abs() < 0.1, "width={}", size.width);
6960 assert_eq!(size.height, 12.0);
6961 }
6962
6963 #[test]
6964 fn custom_font_size_affects_layout() {
6965 let mut tree = FormTree::new();
6966 let draw = tree.add_node(FormNode {
6967 name: "Big".to_string(),
6968 node_type: FormNodeType::Draw(DrawContent::Text("Hi".to_string())),
6969 box_model: BoxModel {
6970 width: None,
6971 height: None,
6972 max_width: f64::MAX,
6973 max_height: f64::MAX,
6974 ..Default::default()
6975 },
6976 layout: LayoutStrategy::Positioned,
6977 children: vec![],
6978 occur: Occur::once(),
6979 font: FontMetrics::new(20.0), calculate: None,
6981 validate: None,
6982 column_widths: vec![],
6983 col_span: 1,
6984 });
6985
6986 let engine = LayoutEngine::new(&tree);
6987 let size = engine.compute_extent(draw);
6988 assert!((size.width - 18.88).abs() < 0.1, "width={}", size.width);
6990 assert_eq!(size.height, 24.0);
6991 }
6992
6993 fn make_cell(tree: &mut FormTree, name: &str, w: f64, h: f64, col_span: i32) -> FormNodeId {
6998 tree.add_node(FormNode {
6999 name: name.to_string(),
7000 node_type: FormNodeType::Field {
7001 value: name.to_string(),
7002 },
7003 box_model: BoxModel {
7004 width: Some(w),
7005 height: Some(h),
7006 max_width: f64::MAX,
7007 max_height: f64::MAX,
7008 ..Default::default()
7009 },
7010 layout: LayoutStrategy::Positioned,
7011 children: vec![],
7012 occur: Occur::once(),
7013 font: FontMetrics::default(),
7014 calculate: None,
7015 validate: None,
7016 column_widths: vec![],
7017 col_span,
7018 })
7019 }
7020
7021 fn make_row(tree: &mut FormTree, name: &str, cells: Vec<FormNodeId>) -> FormNodeId {
7022 tree.add_node(FormNode {
7023 name: name.to_string(),
7024 node_type: FormNodeType::Subform,
7025 box_model: BoxModel {
7026 max_width: f64::MAX,
7027 max_height: f64::MAX,
7028 ..Default::default()
7029 },
7030 layout: LayoutStrategy::Row,
7031 children: cells,
7032 occur: Occur::once(),
7033 font: FontMetrics::default(),
7034 calculate: None,
7035 validate: None,
7036 column_widths: vec![],
7037 col_span: 1,
7038 })
7039 }
7040
7041 fn make_table(
7042 tree: &mut FormTree,
7043 name: &str,
7044 column_widths: Vec<f64>,
7045 rows: Vec<FormNodeId>,
7046 ) -> FormNodeId {
7047 tree.add_node(FormNode {
7048 name: name.to_string(),
7049 node_type: FormNodeType::Subform,
7050 box_model: BoxModel {
7051 max_width: f64::MAX,
7052 max_height: f64::MAX,
7053 ..Default::default()
7054 },
7055 layout: LayoutStrategy::Table,
7056 children: rows,
7057 occur: Occur::once(),
7058 font: FontMetrics::default(),
7059 calculate: None,
7060 validate: None,
7061 column_widths,
7062 col_span: 1,
7063 })
7064 }
7065
7066 #[test]
7067 fn table_basic_fixed_columns() {
7068 let mut tree = FormTree::new();
7069
7070 let c1 = make_cell(&mut tree, "A1", 100.0, 30.0, 1);
7072 let c2 = make_cell(&mut tree, "A2", 150.0, 30.0, 1);
7073 let c3 = make_cell(&mut tree, "A3", 200.0, 30.0, 1);
7074 let r1 = make_row(&mut tree, "Row1", vec![c1, c2, c3]);
7075
7076 let c4 = make_cell(&mut tree, "B1", 100.0, 25.0, 1);
7077 let c5 = make_cell(&mut tree, "B2", 150.0, 25.0, 1);
7078 let c6 = make_cell(&mut tree, "B3", 200.0, 25.0, 1);
7079 let r2 = make_row(&mut tree, "Row2", vec![c4, c5, c6]);
7080
7081 let table = make_table(&mut tree, "Table", vec![100.0, 150.0, 200.0], vec![r1, r2]);
7082
7083 let page_area = make_subform(
7084 &mut tree,
7085 "Page",
7086 LayoutStrategy::TopToBottom,
7087 Some(612.0),
7088 Some(792.0),
7089 vec![table],
7090 );
7091
7092 let engine = LayoutEngine::new(&tree);
7093 let layout = engine.layout(page_area).unwrap();
7094
7095 assert_eq!(layout.pages.len(), 1);
7096 let page = &layout.pages[0];
7097 assert_eq!(page.nodes.len(), 1);
7099 let table_node = &page.nodes[0];
7100 assert_eq!(table_node.name, "Table");
7101
7102 assert_eq!(table_node.children.len(), 2);
7104 let row1 = &table_node.children[0];
7105 let row2 = &table_node.children[1];
7106
7107 assert_eq!(row1.children.len(), 3);
7109 assert_eq!(row1.children[0].rect.x, 0.0);
7110 assert_eq!(row1.children[0].rect.width, 100.0);
7111 assert_eq!(row1.children[1].rect.x, 100.0);
7112 assert_eq!(row1.children[1].rect.width, 150.0);
7113 assert_eq!(row1.children[2].rect.x, 250.0);
7114 assert_eq!(row1.children[2].rect.width, 200.0);
7115
7116 assert_eq!(row2.rect.y, 30.0); assert_eq!(row2.children[0].rect.x, 0.0);
7119 }
7120
7121 #[test]
7122 fn table_auto_columns() {
7123 let mut tree = FormTree::new();
7124
7125 let c1 = make_cell(&mut tree, "A", 80.0, 20.0, 1);
7127 let c2 = make_cell(&mut tree, "B", 120.0, 20.0, 1);
7128 let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7129
7130 let c3 = make_cell(&mut tree, "C", 60.0, 20.0, 1);
7131 let c4 = make_cell(&mut tree, "D", 150.0, 20.0, 1);
7132 let r2 = make_row(&mut tree, "Row2", vec![c3, c4]);
7133
7134 let table = make_table(&mut tree, "Table", vec![-1.0, -1.0], vec![r1, r2]);
7136
7137 let page = make_subform(
7138 &mut tree,
7139 "Page",
7140 LayoutStrategy::TopToBottom,
7141 Some(612.0),
7142 Some(792.0),
7143 vec![table],
7144 );
7145
7146 let engine = LayoutEngine::new(&tree);
7147 let layout = engine.layout(page).unwrap();
7148
7149 let table_node = &layout.pages[0].nodes[0];
7150 let row1 = &table_node.children[0];
7151
7152 assert_eq!(row1.children[0].rect.width, 80.0);
7154 assert_eq!(row1.children[1].rect.width, 150.0);
7155 assert_eq!(row1.children[1].rect.x, 80.0);
7156 }
7157
7158 #[test]
7159 fn table_col_span() {
7160 let mut tree = FormTree::new();
7161
7162 let c1 = make_cell(&mut tree, "Span2", 200.0, 20.0, 2); let c2 = make_cell(&mut tree, "Single", 100.0, 20.0, 1);
7165 let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7166
7167 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7168
7169 let page = make_subform(
7170 &mut tree,
7171 "Page",
7172 LayoutStrategy::TopToBottom,
7173 Some(612.0),
7174 Some(792.0),
7175 vec![table],
7176 );
7177
7178 let engine = LayoutEngine::new(&tree);
7179 let layout = engine.layout(page).unwrap();
7180
7181 let row = &layout.pages[0].nodes[0].children[0];
7182 assert_eq!(row.children[0].rect.width, 200.0);
7184 assert_eq!(row.children[0].rect.x, 0.0);
7185 assert_eq!(row.children[1].rect.x, 200.0);
7187 assert_eq!(row.children[1].rect.width, 100.0);
7188 }
7189
7190 #[test]
7191 fn table_col_span_rest() {
7192 let mut tree = FormTree::new();
7193
7194 let c1 = make_cell(&mut tree, "First", 100.0, 20.0, 1);
7196 let c2 = make_cell(&mut tree, "Rest", 200.0, 20.0, -1); let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7198
7199 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7200
7201 let page = make_subform(
7202 &mut tree,
7203 "Page",
7204 LayoutStrategy::TopToBottom,
7205 Some(612.0),
7206 Some(792.0),
7207 vec![table],
7208 );
7209
7210 let engine = LayoutEngine::new(&tree);
7211 let layout = engine.layout(page).unwrap();
7212
7213 let row = &layout.pages[0].nodes[0].children[0];
7214 assert_eq!(row.children[0].rect.width, 100.0);
7216 assert_eq!(row.children[1].rect.x, 100.0);
7218 assert_eq!(row.children[1].rect.width, 200.0);
7219 }
7220
7221 #[test]
7222 fn table_row_height_equalization() {
7223 let mut tree = FormTree::new();
7224
7225 let c1 = make_cell(&mut tree, "Short", 100.0, 30.0, 1);
7227 let c2 = make_cell(&mut tree, "Tall", 100.0, 50.0, 1);
7228 let c3 = make_cell(&mut tree, "Tiny", 100.0, 20.0, 1);
7229 let r1 = make_row(&mut tree, "Row1", vec![c1, c2, c3]);
7230
7231 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7232
7233 let page = make_subform(
7234 &mut tree,
7235 "Page",
7236 LayoutStrategy::TopToBottom,
7237 Some(612.0),
7238 Some(792.0),
7239 vec![table],
7240 );
7241
7242 let engine = LayoutEngine::new(&tree);
7243 let layout = engine.layout(page).unwrap();
7244
7245 let row = &layout.pages[0].nodes[0].children[0];
7246 assert_eq!(row.children[0].rect.height, 50.0);
7248 assert_eq!(row.children[1].rect.height, 50.0);
7249 assert_eq!(row.children[2].rect.height, 50.0);
7250 assert_eq!(row.rect.height, 50.0);
7252 }
7253
7254 #[test]
7255 fn table_growable_height() {
7256 let mut tree = FormTree::new();
7257
7258 let c1 = make_cell(&mut tree, "A", 100.0, 30.0, 1);
7259 let r1 = make_row(&mut tree, "Row1", vec![c1]);
7260
7261 let c2 = make_cell(&mut tree, "B", 100.0, 40.0, 1);
7262 let r2 = make_row(&mut tree, "Row2", vec![c2]);
7263
7264 let table = make_table(&mut tree, "Table", vec![100.0], vec![r1, r2]);
7266
7267 let engine = LayoutEngine::new(&tree);
7268 let extent = engine.compute_extent(table);
7269
7270 assert_eq!(extent.height, 70.0);
7272 assert_eq!(extent.width, 100.0);
7274 }
7275
7276 #[test]
7277 fn table_empty() {
7278 let mut tree = FormTree::new();
7279 let table = make_table(&mut tree, "EmptyTable", vec![100.0, 200.0], vec![]);
7280
7281 let page = make_subform(
7282 &mut tree,
7283 "Page",
7284 LayoutStrategy::TopToBottom,
7285 Some(612.0),
7286 Some(792.0),
7287 vec![table],
7288 );
7289
7290 let engine = LayoutEngine::new(&tree);
7291 let layout = engine.layout(page).unwrap();
7292
7293 let table_node = &layout.pages[0].nodes[0];
7295 assert_eq!(table_node.children.len(), 0);
7296 }
7297
7298 #[test]
7299 fn table_splits_across_pages() {
7300 let mut tree = FormTree::new();
7301
7302 let mut rows = Vec::new();
7304 for i in 0..10 {
7305 let cell = make_cell(&mut tree, &format!("C{}", i), 200.0, 100.0, 1);
7306 let row = make_row(&mut tree, &format!("Row{}", i), vec![cell]);
7307 rows.push(row);
7308 }
7309
7310 let table = make_table(&mut tree, "Table", vec![200.0], rows);
7312
7313 let page_area = tree.add_node(FormNode {
7315 name: "PageArea".to_string(),
7316 node_type: FormNodeType::PageArea {
7317 content_areas: vec![ContentArea {
7318 name: "Body".to_string(),
7319 x: 0.0,
7320 y: 0.0,
7321 width: 400.0,
7322 height: 400.0,
7323 leader: None,
7324 trailer: None,
7325 }],
7326 },
7327 box_model: BoxModel {
7328 width: Some(400.0),
7329 height: Some(400.0),
7330 max_width: f64::MAX,
7331 max_height: f64::MAX,
7332 ..Default::default()
7333 },
7334 layout: LayoutStrategy::Positioned,
7335 children: vec![],
7336 occur: Occur::once(),
7337 font: FontMetrics::default(),
7338 calculate: None,
7339 validate: None,
7340 column_widths: vec![],
7341 col_span: 1,
7342 });
7343
7344 let root = tree.add_node(FormNode {
7345 name: "Root".to_string(),
7346 node_type: FormNodeType::Root,
7347 box_model: BoxModel {
7348 width: Some(400.0),
7349 height: Some(400.0),
7350 max_width: f64::MAX,
7351 max_height: f64::MAX,
7352 ..Default::default()
7353 },
7354 layout: LayoutStrategy::TopToBottom,
7355 children: vec![page_area, table],
7356 occur: Occur::once(),
7357 font: FontMetrics::default(),
7358 calculate: None,
7359 validate: None,
7360 column_widths: vec![],
7361 col_span: 1,
7362 });
7363
7364 let engine = LayoutEngine::new(&tree);
7365 let result = engine.layout(root).unwrap();
7366
7367 assert_eq!(result.pages.len(), 3);
7369 }
7370
7371 #[test]
7372 fn resolve_display_value_maps_save_to_display() {
7373 let mut meta = FormNodeMeta::default();
7374 meta.field_kind = FieldKind::Dropdown;
7375 meta.display_items = vec![
7376 "United States".to_string(),
7377 "United Kingdom".to_string(),
7378 "Canada".to_string(),
7379 ];
7380 meta.save_items = vec!["US".to_string(), "UK".to_string(), "CA".to_string()];
7381
7382 assert_eq!(resolve_display_value("UK", &meta), "United Kingdom");
7384 assert_eq!(resolve_display_value("CA", &meta), "Canada");
7386 assert_eq!(resolve_display_value("DE", &meta), "DE");
7388 assert_eq!(resolve_display_value("", &meta), "");
7390 }
7391
7392 #[test]
7393 fn resolve_display_value_no_save_items_passthrough() {
7394 let mut meta = FormNodeMeta::default();
7395 meta.field_kind = FieldKind::Dropdown;
7396 meta.display_items = vec!["Red".to_string(), "Green".to_string()];
7397 assert_eq!(resolve_display_value("Red", &meta), "Red");
7399 }
7400
7401 #[test]
7402 fn resolve_display_value_non_dropdown_passthrough() {
7403 let mut meta = FormNodeMeta::default();
7404 meta.field_kind = FieldKind::Text;
7405 meta.save_items = vec!["US".to_string()];
7406 meta.display_items = vec!["United States".to_string()];
7407 assert_eq!(resolve_display_value("US", &meta), "US");
7409 }
7410
7411 #[test]
7412 fn resolve_display_value_numeric_edit_strips_trailing_zeros() {
7413 let mut meta = FormNodeMeta::default();
7414 meta.field_kind = FieldKind::NumericEdit;
7415
7416 assert_eq!(resolve_display_value("1.00000000", &meta), "1");
7417 assert_eq!(resolve_display_value("3.50", &meta), "3.5");
7418 assert_eq!(resolve_display_value("100.00", &meta), "100");
7419 assert_eq!(resolve_display_value("0.12345", &meta), "0.12345");
7420 assert_eq!(resolve_display_value("42", &meta), "42");
7421 assert_eq!(resolve_display_value("abc", &meta), "abc");
7423 assert_eq!(resolve_display_value("", &meta), "");
7424 }
7425
7426 #[test]
7427 fn resolve_display_value_date_time_picker_uses_iso_date_prefix() {
7428 let mut meta = FormNodeMeta::default();
7429 meta.field_kind = FieldKind::DateTimePicker;
7430
7431 assert_eq!(resolve_display_value("2026-04-12", &meta), "2026-04-12");
7432 assert_eq!(
7433 resolve_display_value("2026-04-12T13:45:00Z", &meta),
7434 "2026-04-12"
7435 );
7436 assert_eq!(
7437 resolve_display_value("2026-04-12T13:45:00+02:00", &meta),
7438 "2026-04-12"
7439 );
7440 assert_eq!(resolve_display_value("12/04/2026", &meta), "12/04/2026");
7442 assert_eq!(resolve_display_value("abc", &meta), "abc");
7443 assert_eq!(resolve_display_value("", &meta), "");
7444 }
7445
7446 #[test]
7451 fn estimated_heap_bytes_is_positive_for_non_empty_layout() {
7452 let mut tree = FormTree::new();
7453 let f1 = make_field(&mut tree, "Field1", 100.0, 20.0);
7454 let f2 = make_field(&mut tree, "Field2", 100.0, 20.0);
7455 let root = make_subform(
7456 &mut tree,
7457 "Root",
7458 LayoutStrategy::TopToBottom,
7459 Some(200.0),
7460 Some(200.0),
7461 vec![f1, f2],
7462 );
7463
7464 let engine = LayoutEngine::new(&tree);
7465 let layout = engine.layout(root).unwrap();
7466
7467 assert!(!layout.pages.is_empty(), "expected at least one page");
7469 let bytes = layout.estimated_heap_bytes();
7470 assert!(
7471 bytes > 0,
7472 "estimated_heap_bytes should be > 0 for non-empty layout"
7473 );
7474 }
7475}
7476
7477#[cfg(test)]
7478mod halign_tests {
7479 use super::*;
7480 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
7481 use crate::text::FontMetrics;
7482 use crate::types::{BoxModel, LayoutStrategy, TextAlign};
7483
7484 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
7485 tree.add_node(FormNode {
7486 name: name.to_string(),
7487 node_type: FormNodeType::Field {
7488 value: name.to_string(),
7489 },
7490 box_model: BoxModel {
7491 width: Some(w),
7492 height: Some(h),
7493 max_width: f64::MAX,
7494 max_height: f64::MAX,
7495 ..Default::default()
7496 },
7497 layout: LayoutStrategy::Positioned,
7498 children: vec![],
7499 occur: Occur::once(),
7500 font: FontMetrics::default(),
7501 calculate: None,
7502 validate: None,
7503 column_widths: vec![],
7504 col_span: 1,
7505 })
7506 }
7507
7508 fn make_subform(
7509 tree: &mut FormTree,
7510 name: &str,
7511 strategy: LayoutStrategy,
7512 w: Option<f64>,
7513 h: Option<f64>,
7514 children: Vec<FormNodeId>,
7515 ) -> FormNodeId {
7516 tree.add_node(FormNode {
7517 name: name.to_string(),
7518 node_type: FormNodeType::Subform,
7519 box_model: BoxModel {
7520 width: w,
7521 height: h,
7522 max_width: f64::MAX,
7523 max_height: f64::MAX,
7524 ..Default::default()
7525 },
7526 layout: strategy,
7527 children,
7528 occur: Occur::once(),
7529 font: FontMetrics::default(),
7530 calculate: None,
7531 validate: None,
7532 column_widths: vec![],
7533 col_span: 1,
7534 })
7535 }
7536
7537 #[test]
7540 fn tb_halign_right_offsets_child() {
7541 let mut tree = FormTree::new();
7542 let child = make_field(&mut tree, "A", 200.0, 30.0);
7543 tree.meta_mut(child).style.h_align = Some(TextAlign::Right);
7544
7545 let parent = make_subform(
7546 &mut tree,
7547 "Page",
7548 LayoutStrategy::TopToBottom,
7549 Some(500.0),
7550 Some(500.0),
7551 vec![child],
7552 );
7553
7554 let engine = LayoutEngine::new(&tree);
7555 let layout = engine.layout(parent).unwrap();
7556
7557 let child_node = &layout.pages[0].nodes[0];
7558 assert_eq!(child_node.rect.x, 300.0);
7560 }
7561
7562 #[test]
7564 fn tb_halign_center_centers_child() {
7565 let mut tree = FormTree::new();
7566 let child = make_field(&mut tree, "A", 200.0, 30.0);
7567 tree.meta_mut(child).style.h_align = Some(TextAlign::Center);
7568
7569 let parent = make_subform(
7570 &mut tree,
7571 "Page",
7572 LayoutStrategy::TopToBottom,
7573 Some(500.0),
7574 Some(500.0),
7575 vec![child],
7576 );
7577
7578 let engine = LayoutEngine::new(&tree);
7579 let layout = engine.layout(parent).unwrap();
7580
7581 let child_node = &layout.pages[0].nodes[0];
7582 assert_eq!(child_node.rect.x, 150.0);
7584 }
7585
7586 #[test]
7588 fn tb_halign_default_left() {
7589 let mut tree = FormTree::new();
7590 let child = make_field(&mut tree, "A", 200.0, 30.0);
7591 let parent = make_subform(
7594 &mut tree,
7595 "Page",
7596 LayoutStrategy::TopToBottom,
7597 Some(500.0),
7598 Some(500.0),
7599 vec![child],
7600 );
7601
7602 let engine = LayoutEngine::new(&tree);
7603 let layout = engine.layout(parent).unwrap();
7604
7605 let child_node = &layout.pages[0].nodes[0];
7606 assert_eq!(child_node.rect.x, 0.0);
7607 }
7608
7609 #[test]
7613 fn lr_tb_halign_right_shifts_row() {
7614 let mut tree = FormTree::new();
7615 let a = make_field(&mut tree, "A", 60.0, 20.0);
7616 let b = make_field(&mut tree, "B", 60.0, 20.0);
7617 let c = make_field(&mut tree, "C", 60.0, 20.0);
7618 tree.meta_mut(a).style.h_align = Some(TextAlign::Right);
7619 tree.meta_mut(b).style.h_align = Some(TextAlign::Right);
7620 tree.meta_mut(c).style.h_align = Some(TextAlign::Right);
7621
7622 let parent = make_subform(
7623 &mut tree,
7624 "Page",
7625 LayoutStrategy::LeftToRightTB,
7626 Some(300.0),
7627 Some(300.0),
7628 vec![a, b, c],
7629 );
7630
7631 let engine = LayoutEngine::new(&tree);
7632 let layout = engine.layout(parent).unwrap();
7633 let page = &layout.pages[0];
7634
7635 assert_eq!(page.nodes[0].rect.x, 120.0); assert_eq!(page.nodes[1].rect.x, 180.0); assert_eq!(page.nodes[2].rect.x, 240.0); }
7640
7641 #[test]
7643 fn rl_tb_halign_left_overrides_flow() {
7644 let mut tree = FormTree::new();
7645 let child = make_field(&mut tree, "A", 100.0, 30.0);
7646 tree.meta_mut(child).style.h_align = Some(TextAlign::Left);
7647
7648 let parent = make_subform(
7649 &mut tree,
7650 "Page",
7651 LayoutStrategy::RightToLeftTB,
7652 Some(500.0),
7653 Some(500.0),
7654 vec![child],
7655 );
7656
7657 let engine = LayoutEngine::new(&tree);
7658 let layout = engine.layout(parent).unwrap();
7659
7660 let child_node = &layout.pages[0].nodes[0];
7661 assert_eq!(child_node.rect.x, 0.0);
7663 }
7664
7665 fn make_positioned_field(
7670 tree: &mut FormTree,
7671 name: &str,
7672 x: f64,
7673 y: f64,
7674 w: f64,
7675 h: f64,
7676 ) -> FormNodeId {
7677 tree.add_node(FormNode {
7678 name: name.to_string(),
7679 node_type: FormNodeType::Field {
7680 value: name.to_string(),
7681 },
7682 box_model: BoxModel {
7683 width: Some(w),
7684 height: Some(h),
7685 x,
7686 y,
7687 max_width: f64::MAX,
7688 max_height: f64::MAX,
7689 ..Default::default()
7690 },
7691 layout: LayoutStrategy::Positioned,
7692 children: vec![],
7693 occur: Occur::once(),
7694 font: FontMetrics::default(),
7695 calculate: None,
7696 validate: None,
7697 column_widths: vec![],
7698 col_span: 1,
7699 })
7700 }
7701
7702 fn layout_with_anchor(anchor: crate::form::AnchorType, x: f64, y: f64, w: f64, h: f64) -> Rect {
7703 let mut tree = FormTree::new();
7704 let field = make_positioned_field(&mut tree, "F", x, y, w, h);
7705 tree.meta_mut(field).anchor_type = anchor;
7706 let root = make_subform(
7707 &mut tree,
7708 "Root",
7709 LayoutStrategy::Positioned,
7710 Some(612.0),
7711 Some(792.0),
7712 vec![field],
7713 );
7714 let engine = LayoutEngine::new(&tree);
7715 let result = engine.layout(root).unwrap();
7716 result.pages[0].nodes[0].rect
7717 }
7718
7719 #[test]
7720 fn anchor_top_left_no_adjustment() {
7721 use crate::form::AnchorType;
7722 let r = layout_with_anchor(AnchorType::TopLeft, 100.0, 200.0, 80.0, 40.0);
7723 assert_eq!(r.x, 100.0);
7724 assert_eq!(r.y, 200.0);
7725 assert_eq!(r.width, 80.0);
7726 assert_eq!(r.height, 40.0);
7727 }
7728 #[test]
7729 fn anchor_top_center() {
7730 use crate::form::AnchorType;
7731 let r = layout_with_anchor(AnchorType::TopCenter, 100.0, 200.0, 80.0, 40.0);
7732 assert_eq!(r.x, 60.0);
7733 assert_eq!(r.y, 200.0);
7734 }
7735 #[test]
7736 fn anchor_top_right() {
7737 use crate::form::AnchorType;
7738 let r = layout_with_anchor(AnchorType::TopRight, 100.0, 200.0, 80.0, 40.0);
7739 assert_eq!(r.x, 20.0);
7740 assert_eq!(r.y, 200.0);
7741 }
7742 #[test]
7743 fn anchor_middle_left() {
7744 use crate::form::AnchorType;
7745 let r = layout_with_anchor(AnchorType::MiddleLeft, 100.0, 200.0, 80.0, 40.0);
7746 assert_eq!(r.x, 100.0);
7747 assert_eq!(r.y, 180.0);
7748 }
7749 #[test]
7750 fn anchor_middle_center() {
7751 use crate::form::AnchorType;
7752 let r = layout_with_anchor(AnchorType::MiddleCenter, 100.0, 200.0, 80.0, 40.0);
7753 assert_eq!(r.x, 60.0);
7754 assert_eq!(r.y, 180.0);
7755 }
7756 #[test]
7757 fn anchor_middle_right() {
7758 use crate::form::AnchorType;
7759 let r = layout_with_anchor(AnchorType::MiddleRight, 100.0, 200.0, 80.0, 40.0);
7760 assert_eq!(r.x, 20.0);
7761 assert_eq!(r.y, 180.0);
7762 }
7763 #[test]
7764 fn anchor_bottom_left() {
7765 use crate::form::AnchorType;
7766 let r = layout_with_anchor(AnchorType::BottomLeft, 100.0, 200.0, 80.0, 40.0);
7767 assert_eq!(r.x, 100.0);
7768 assert_eq!(r.y, 160.0);
7769 }
7770 #[test]
7771 fn anchor_bottom_center() {
7772 use crate::form::AnchorType;
7773 let r = layout_with_anchor(AnchorType::BottomCenter, 100.0, 200.0, 80.0, 40.0);
7774 assert_eq!(r.x, 60.0);
7775 assert_eq!(r.y, 160.0);
7776 }
7777 #[test]
7778 fn anchor_bottom_right() {
7779 use crate::form::AnchorType;
7780 let r = layout_with_anchor(AnchorType::BottomRight, 100.0, 200.0, 80.0, 40.0);
7781 assert_eq!(r.x, 20.0);
7782 assert_eq!(r.y, 160.0);
7783 }
7784}
7785
7786#[cfg(test)]
7791mod container_node_tests {
7792 use super::*;
7793 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
7794 use crate::text::FontMetrics;
7795 use crate::types::{BoxModel, LayoutStrategy};
7796
7797 fn make_field(tree: &mut FormTree, name: &str, x: f64, y: f64, w: f64, h: f64) -> FormNodeId {
7798 tree.add_node(FormNode {
7799 name: name.to_string(),
7800 node_type: FormNodeType::Field {
7801 value: name.to_string(),
7802 },
7803 box_model: BoxModel {
7804 width: Some(w),
7805 height: Some(h),
7806 x,
7807 y,
7808 max_width: f64::MAX,
7809 max_height: f64::MAX,
7810 ..Default::default()
7811 },
7812 layout: LayoutStrategy::Positioned,
7813 children: vec![],
7814 occur: Occur::once(),
7815 font: FontMetrics::default(),
7816 calculate: None,
7817 validate: None,
7818 column_widths: vec![],
7819 col_span: 1,
7820 })
7821 }
7822
7823 fn make_container(
7824 tree: &mut FormTree,
7825 name: &str,
7826 node_type: FormNodeType,
7827 strategy: LayoutStrategy,
7828 w: f64,
7829 h: f64,
7830 children: Vec<FormNodeId>,
7831 ) -> FormNodeId {
7832 tree.add_node(FormNode {
7833 name: name.to_string(),
7834 node_type,
7835 box_model: BoxModel {
7836 width: Some(w),
7837 height: Some(h),
7838 max_width: f64::MAX,
7839 max_height: f64::MAX,
7840 ..Default::default()
7841 },
7842 layout: strategy,
7843 children,
7844 occur: Occur::once(),
7845 font: FontMetrics::default(),
7846 calculate: None,
7847 validate: None,
7848 column_widths: vec![],
7849 col_span: 1,
7850 })
7851 }
7852
7853 #[test]
7857 fn area_node_positions_children_absolutely() {
7858 let mut tree = FormTree::new();
7859 let child = make_field(&mut tree, "Child", 10.0, 20.0, 50.0, 15.0);
7860 let area = make_container(
7861 &mut tree,
7862 "MyArea",
7863 FormNodeType::Area,
7864 LayoutStrategy::Positioned,
7865 200.0,
7866 100.0,
7867 vec![child],
7868 );
7869 let root = make_container(
7871 &mut tree,
7872 "Root",
7873 FormNodeType::Subform,
7874 LayoutStrategy::TopToBottom,
7875 200.0,
7876 200.0,
7877 vec![area],
7878 );
7879
7880 let engine = LayoutEngine::new(&tree);
7881 let result = engine.layout(root).unwrap();
7882
7883 assert_eq!(result.pages.len(), 1);
7884 let area_node = &result.pages[0].nodes[0];
7885 assert_eq!(area_node.name, "MyArea");
7886 assert_eq!(area_node.children.len(), 1);
7887 let child_node = &area_node.children[0];
7889 assert_eq!(child_node.name, "Child");
7890 assert_eq!(child_node.rect.x, 10.0);
7891 assert_eq!(child_node.rect.y, 20.0);
7892 }
7893
7894 #[test]
7897 fn excl_group_lays_out_children_top_to_bottom() {
7898 let mut tree = FormTree::new();
7899 let opt_a = make_field(&mut tree, "OptionA", 0.0, 0.0, 100.0, 20.0);
7900 let opt_b = make_field(&mut tree, "OptionB", 0.0, 0.0, 100.0, 20.0);
7901 let excl = make_container(
7902 &mut tree,
7903 "MyGroup",
7904 FormNodeType::ExclGroup,
7905 LayoutStrategy::TopToBottom,
7906 200.0,
7907 100.0,
7908 vec![opt_a, opt_b],
7909 );
7910 let root = make_container(
7911 &mut tree,
7912 "Root",
7913 FormNodeType::Subform,
7914 LayoutStrategy::TopToBottom,
7915 200.0,
7916 200.0,
7917 vec![excl],
7918 );
7919
7920 let engine = LayoutEngine::new(&tree);
7921 let result = engine.layout(root).unwrap();
7922
7923 assert_eq!(result.pages.len(), 1);
7924 let group_node = &result.pages[0].nodes[0];
7925 assert_eq!(group_node.name, "MyGroup");
7926 assert_eq!(group_node.children.len(), 2);
7927 assert_eq!(group_node.children[0].rect.y, 0.0);
7929 assert_eq!(group_node.children[1].rect.y, 20.0);
7930 }
7931
7932 #[test]
7935 fn subform_set_is_transparent_container() {
7936 let mut tree = FormTree::new();
7937 let field_a = make_field(&mut tree, "A", 0.0, 0.0, 100.0, 20.0);
7938 let field_b = make_field(&mut tree, "B", 0.0, 0.0, 100.0, 20.0);
7939 let set = make_container(
7941 &mut tree,
7942 "MySet",
7943 FormNodeType::SubformSet,
7944 LayoutStrategy::TopToBottom,
7945 200.0,
7946 100.0,
7947 vec![field_a, field_b],
7948 );
7949 let root = make_container(
7950 &mut tree,
7951 "Root",
7952 FormNodeType::Subform,
7953 LayoutStrategy::TopToBottom,
7954 200.0,
7955 200.0,
7956 vec![set],
7957 );
7958
7959 let engine = LayoutEngine::new(&tree);
7960 let result = engine.layout(root).unwrap();
7961
7962 assert_eq!(result.pages.len(), 1);
7963 let page = &result.pages[0];
7965 fn count_named<'a>(nodes: &'a [LayoutNode], name: &str) -> usize {
7966 nodes
7967 .iter()
7968 .map(|n| usize::from(n.name == name) + count_named(&n.children, name))
7969 .sum()
7970 }
7971 assert!(
7972 count_named(&page.nodes, "A") >= 1,
7973 "Field A should appear in layout"
7974 );
7975 assert!(
7976 count_named(&page.nodes, "B") >= 1,
7977 "Field B should appear in layout"
7978 );
7979 }
7980}
7981
7982#[cfg(test)]
7995mod keep_chain_tests {
7996 use super::*;
7997 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
7998 use crate::text::FontMetrics;
7999 use crate::types::{BoxModel, LayoutStrategy};
8000
8001 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
8002 tree.add_node(FormNode {
8003 name: name.to_string(),
8004 node_type: FormNodeType::Field {
8005 value: name.to_string(),
8006 },
8007 box_model: BoxModel {
8008 width: Some(w),
8009 height: Some(h),
8010 max_width: f64::MAX,
8011 max_height: f64::MAX,
8012 ..Default::default()
8013 },
8014 layout: LayoutStrategy::Positioned,
8015 children: vec![],
8016 occur: Occur::once(),
8017 font: FontMetrics::default(),
8018 calculate: None,
8019 validate: None,
8020 column_widths: vec![],
8021 col_span: 1,
8022 })
8023 }
8024
8025 #[test]
8038 fn keep_next_pushes_heading_and_body_to_same_page() {
8039 let mut tree = FormTree::new();
8040
8041 let filler = make_field(&mut tree, "Filler", 200.0, 70.0);
8042 let heading = make_field(&mut tree, "Heading", 200.0, 40.0);
8043 let body = make_field(&mut tree, "Body", 200.0, 40.0);
8044
8045 tree.meta_mut(heading).keep_next_content_area = true;
8047
8048 let root = tree.add_node(FormNode {
8049 name: "Root".to_string(),
8050 node_type: FormNodeType::Root,
8051 box_model: BoxModel {
8052 width: Some(200.0),
8053 height: Some(100.0),
8054 max_width: f64::MAX,
8055 max_height: f64::MAX,
8056 ..Default::default()
8057 },
8058 layout: LayoutStrategy::TopToBottom,
8059 children: vec![filler, heading, body],
8060 occur: Occur::once(),
8061 font: FontMetrics::default(),
8062 calculate: None,
8063 validate: None,
8064 column_widths: vec![],
8065 col_span: 1,
8066 });
8067
8068 let engine = LayoutEngine::new(&tree);
8069 let result = engine.layout(root).unwrap();
8070
8071 assert_eq!(
8073 result.pages.len(),
8074 2,
8075 "expected filler on page 1, heading+body on page 2"
8076 );
8077
8078 let p1_names: Vec<&str> = result.pages[0]
8080 .nodes
8081 .iter()
8082 .map(|n| n.name.as_str())
8083 .collect();
8084 assert!(p1_names.contains(&"Filler"), "Filler should be on page 1");
8085 assert!(
8086 !p1_names.contains(&"Heading"),
8087 "Heading should NOT be on page 1"
8088 );
8089 assert!(!p1_names.contains(&"Body"), "Body should NOT be on page 1");
8090
8091 let p2_names: Vec<&str> = result.pages[1]
8093 .nodes
8094 .iter()
8095 .map(|n| n.name.as_str())
8096 .collect();
8097 assert!(p2_names.contains(&"Heading"), "Heading should be on page 2");
8098 assert!(p2_names.contains(&"Body"), "Body should be on page 2");
8099 }
8100}