1use crate::error::Result;
31use crate::form::{
32 ContentArea, DrawContent, FieldKind, FormNode, FormNodeId, FormNodeMeta, FormNodeType, FormTree,
33};
34use crate::text::{self, FontFamily};
35use crate::types::{LayoutStrategy, Rect, Size, TextAlign};
36use std::sync::{Mutex, OnceLock};
37
38fn resolve_display_value<'a>(value: &'a str, meta: &'a FormNodeMeta) -> std::borrow::Cow<'a, str> {
48 if value.is_empty() {
49 return std::borrow::Cow::Borrowed(value);
50 }
51 if meta.field_kind == FieldKind::Dropdown {
53 if !meta.save_items.is_empty() {
54 if let Some(idx) = meta.save_items.iter().position(|s| s == value) {
55 if let Some(display) = meta.display_items.get(idx) {
56 return std::borrow::Cow::Borrowed(display.as_str());
57 }
58 }
59 }
60 return std::borrow::Cow::Borrowed(value);
61 }
62 if meta.field_kind == FieldKind::NumericEdit {
64 if let Ok(num) = value.parse::<f64>() {
65 let formatted = format!("{}", num);
67 return std::borrow::Cow::Owned(formatted);
68 }
69 }
70 if meta.field_kind == FieldKind::DateTimePicker {
71 if let Some(date) = extract_iso_date_prefix(value) {
72 return std::borrow::Cow::Owned(date.to_string());
73 }
74 }
75 std::borrow::Cow::Borrowed(value)
76}
77
78fn extract_iso_date_prefix(value: &str) -> Option<&str> {
79 let prefix = value.get(0..10)?;
80 let bytes = prefix.as_bytes();
81 if bytes.len() != 10
82 || !bytes[0..4].iter().all(u8::is_ascii_digit)
83 || bytes[4] != b'-'
84 || !bytes[5..7].iter().all(u8::is_ascii_digit)
85 || bytes[7] != b'-'
86 || !bytes[8..10].iter().all(u8::is_ascii_digit)
87 {
88 return None;
89 }
90 Some(prefix)
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct LayoutNodeId(pub usize);
96
97#[derive(Debug)]
99pub struct LayoutDom {
100 pub pages: Vec<LayoutPage>,
102}
103
104#[derive(Debug, Clone, Default)]
106pub struct LayoutProfile {
107 pub pages: Vec<LayoutProfilePage>,
109}
110
111#[derive(Debug, Clone)]
113pub struct LayoutProfilePage {
114 pub page_height: f64,
116 pub used_height: f64,
118 pub overflow_to_next: bool,
120 pub first_overflow_element: Option<String>,
122}
123
124impl LayoutDom {
125 pub fn estimated_heap_bytes(&self) -> usize {
135 fn node_bytes(n: &LayoutNode) -> usize {
136 let mut total = n.name.len();
138 total += n.children.capacity() * std::mem::size_of::<LayoutNode>();
140 for child in &n.children {
141 total += node_bytes(child);
142 }
143 for s in &n.display_items {
145 total += s.len();
146 }
147 for s in &n.save_items {
148 total += s.len();
149 }
150 total += match &n.content {
152 LayoutContent::None => 0,
153 LayoutContent::Text(t) => t.len(),
154 LayoutContent::Field { value, .. } => value.len(),
155 LayoutContent::WrappedText { lines, .. } => {
156 lines.iter().map(|l| l.len()).sum::<usize>()
157 }
158 LayoutContent::Image { data, mime_type } => data.len() + mime_type.len(),
159 LayoutContent::Draw(_) => 0,
160 };
161 total
162 }
163
164 let mut total = self.pages.capacity() * std::mem::size_of::<LayoutPage>();
165 for page in &self.pages {
166 total += page.nodes.capacity() * std::mem::size_of::<LayoutNode>();
167 for node in &page.nodes {
168 total += node_bytes(node);
169 }
170 }
171 total
172 }
173}
174
175const MAX_PAGES: usize = 500;
183
184#[derive(Debug, Default)]
186pub struct LayoutPage {
187 pub width: f64,
189 pub height: f64,
191 pub nodes: Vec<LayoutNode>,
193 pub runtime_instantiated: bool,
198}
199
200#[derive(Debug, Clone)]
202pub struct LayoutNode {
203 pub form_node: FormNodeId,
205 pub rect: Rect,
207 pub name: String,
209 pub content: LayoutContent,
211 pub children: Vec<LayoutNode>,
213 pub style: crate::form::FormNodeStyle,
215 pub display_items: Vec<String>,
217 pub save_items: Vec<String>,
219}
220
221#[derive(Debug, Clone)]
223pub enum LayoutContent {
224 None,
226 Text(String),
228 Field {
230 value: String,
232 field_kind: crate::form::FieldKind,
234 font_size: f64,
236 font_family: FontFamily,
238 },
239 WrappedText {
241 lines: Vec<String>,
243 first_line_of_para: Vec<bool>,
245 font_size: f64,
247 text_align: TextAlign,
249 font_family: FontFamily,
251 space_above_pt: Option<f64>,
253 space_below_pt: Option<f64>,
255 from_field: bool,
257 },
258 Image {
260 data: Vec<u8>,
262 mime_type: String,
264 },
265 Draw(DrawContent),
267}
268
269#[derive(Debug, Clone)]
271struct QueuedNode {
272 id: FormNodeId,
273 break_before: bool,
274 break_after: bool,
275 #[allow(dead_code)]
276 break_target: Option<String>,
277 children_override: Option<Vec<FormNodeId>>,
279 text_lines_override: Option<Vec<String>>,
281 nested_child_overrides: Option<Vec<(FormNodeId, Vec<FormNodeId>)>>,
286}
287
288type FittingResult = (
289 LayoutPage,
290 Vec<QueuedNode>,
291 bool,
292 Option<String>,
293 Option<LayoutProfilePage>,
294);
295
296#[derive(Debug, Default)]
297struct GroundTruthTraceState {
298 last_break_before_read: Option<String>,
299}
300
301static GROUNDTRUTH_TRACE_STATE: OnceLock<Mutex<GroundTruthTraceState>> = OnceLock::new();
302
303fn groundtruth_trace_enabled() -> bool {
304 std::env::var("XFA_TRACE_DOC")
305 .map(|value| !value.is_empty())
306 .unwrap_or(false)
307}
308
309fn groundtruth_trace_state() -> &'static Mutex<GroundTruthTraceState> {
310 GROUNDTRUTH_TRACE_STATE.get_or_init(|| Mutex::new(GroundTruthTraceState::default()))
311}
312
313pub struct LayoutEngine<'a> {
315 form: &'a FormTree,
316 static_body_continuation: bool,
336
337 tiny_positioned_coalesce: bool,
355
356 continuation_runtime_keep: bool,
385}
386
387const TINY_POSITIONED_PAGE_FRAC: f64 = 0.12;
391
392impl<'a> LayoutEngine<'a> {
393 pub fn new(form: &'a FormTree) -> Self {
395 Self {
396 form,
397 static_body_continuation: std::env::var("XFA_OVERFLOW_STATIC_BODY_CONTINUATION")
399 .map(|v| {
400 let v = v.trim();
401 !(v.is_empty()
402 || v == "0"
403 || v.eq_ignore_ascii_case("off")
404 || v.eq_ignore_ascii_case("false"))
405 })
406 .unwrap_or(true),
407 tiny_positioned_coalesce: std::env::var("XFA_TINY_POSITIONED_PAGE_COALESCE")
409 .map(|v| {
410 let v = v.trim();
411 !(v.is_empty()
412 || v == "0"
413 || v.eq_ignore_ascii_case("off")
414 || v.eq_ignore_ascii_case("false"))
415 })
416 .unwrap_or(true),
417 continuation_runtime_keep: std::env::var("XFA_CONTINUATION_RUNTIME_INSTANTIATED_KEEP")
419 .map(|v| {
420 let v = v.trim();
421 !(v.is_empty()
422 || v == "0"
423 || v.eq_ignore_ascii_case("off")
424 || v.eq_ignore_ascii_case("false"))
425 })
426 .unwrap_or(true),
427 }
428 }
429
430 fn lookup_overflow_target(&self, anchor: FormNodeId, name: &str) -> Option<FormNodeId> {
441 if name.is_empty() {
442 return None;
443 }
444 if let Some(id) = self.form.find_by_xfa_id(name) {
445 return Some(id);
446 }
447 let mut cursor = Some(anchor);
450 while let Some(node_id) = cursor {
451 let parent = self.trace_parent_id(node_id);
452 let scope = parent.map(|p| self.form.get(p).children.as_slice());
453 let candidates = scope.unwrap_or_else(|| self.form.get(node_id).children.as_slice());
454 for &candidate in candidates {
455 if candidate != node_id && self.form.get(candidate).name == name {
456 return Some(candidate);
457 }
458 }
459 cursor = parent;
460 }
461 None
462 }
463
464 fn resolve_overflow_refs(
472 &self,
473 node_id: FormNodeId,
474 ) -> (Option<FormNodeId>, Option<FormNodeId>) {
475 let meta = self.form.meta(node_id);
476 let leader = meta
477 .overflow_leader
478 .as_deref()
479 .and_then(|name| self.lookup_overflow_target(node_id, name));
480 let trailer = meta
481 .overflow_trailer
482 .as_deref()
483 .and_then(|name| self.lookup_overflow_target(node_id, name));
484 (leader, trailer)
485 }
486
487 fn content_area_with_overflow(
493 ca: &ContentArea,
494 refs: (Option<FormNodeId>, Option<FormNodeId>),
495 ) -> ContentArea {
496 let (leader, trailer) = refs;
497 ContentArea {
498 name: ca.name.clone(),
499 x: ca.x,
500 y: ca.y,
501 width: ca.width,
502 height: ca.height,
503 leader: leader.or(ca.leader),
504 trailer: trailer.or(ca.trailer),
505 }
506 }
507
508 fn collect_all_overflow_subform_ids(&self) -> Vec<FormNodeId> {
520 let mut ids = Vec::new();
521 for (idx, _node) in self.form.nodes.iter().enumerate() {
522 let node_id = FormNodeId(idx);
523 let meta = self.form.meta(node_id);
524 if meta.overflow_leader.is_none() && meta.overflow_trailer.is_none() {
525 continue;
526 }
527 if let Some(name) = meta.overflow_leader.as_deref() {
528 if let Some(target) = self.lookup_overflow_target(node_id, name) {
529 if !ids.contains(&target) {
530 ids.push(target);
531 }
532 }
533 }
534 if let Some(name) = meta.overflow_trailer.as_deref() {
535 if let Some(target) = self.lookup_overflow_target(node_id, name) {
536 if !ids.contains(&target) {
537 ids.push(target);
538 }
539 }
540 }
541 }
542 ids
543 }
544
545 fn resolve_active_overflow_refs(
563 &self,
564 active_node: Option<FormNodeId>,
565 root_refs: (Option<FormNodeId>, Option<FormNodeId>),
566 ) -> (Option<FormNodeId>, Option<FormNodeId>) {
567 let Some(start) = active_node else {
568 return root_refs;
569 };
570 let mut cursor: Option<FormNodeId> = Some(start);
573 while let Some(node_id) = cursor {
574 let meta = self.form.meta(node_id);
575 if meta.overflow_leader.is_some() || meta.overflow_trailer.is_some() {
576 let refs = self.resolve_overflow_refs(node_id);
577 if refs.0.is_some() || refs.1.is_some() {
578 return refs;
579 }
580 }
581 cursor = self.trace_parent_id(node_id);
582 }
583 root_refs
584 }
585
586 fn trace_node_id(&self, id: FormNodeId) -> String {
587 let node = self.form.get(id);
588 self.form
589 .meta(id)
590 .xfa_id
591 .clone()
592 .filter(|value| !value.is_empty())
593 .or_else(|| (!node.name.is_empty()).then(|| node.name.clone()))
594 .unwrap_or_else(|| id.0.to_string())
595 }
596
597 fn trace_node_name(&self, id: FormNodeId) -> String {
598 let node = self.form.get(id);
599 if !node.name.is_empty() {
600 node.name.clone()
601 } else {
602 self.trace_node_id(id)
603 }
604 }
605
606 fn trace_parent_id(&self, child_id: FormNodeId) -> Option<FormNodeId> {
607 self.form
608 .nodes
609 .iter()
610 .enumerate()
611 .find_map(|(idx, node)| node.children.contains(&child_id).then_some(FormNodeId(idx)))
612 }
613
614 fn trace_path_segment(&self, id: FormNodeId) -> String {
615 let node = self.form.get(id);
616 format!(
617 "{}#{}",
618 Self::form_node_type_name(&node.node_type),
619 self.trace_node_id(id)
620 )
621 }
622
623 fn trace_node_path(&self, id: FormNodeId) -> String {
624 let mut chain = vec![id];
625 let mut cursor = id;
626 while let Some(parent_id) = self.trace_parent_id(cursor) {
627 chain.push(parent_id);
628 cursor = parent_id;
629 }
630 chain.reverse();
631 chain
632 .into_iter()
633 .map(|node_id| self.trace_path_segment(node_id))
634 .collect::<Vec<_>>()
635 .join("/")
636 }
637
638 #[track_caller]
639 fn trace_vertical_state(
640 &self,
641 function: &'static str,
642 current_y: Option<f64>,
643 remaining_space: Option<f64>,
644 node_id: Option<FormNodeId>,
645 message: impl AsRef<str>,
646 ) {
647 if !groundtruth_trace_enabled() {
648 return;
649 }
650
651 let location = std::panic::Location::caller();
652 let (triggering_id, node_type, node_name) = if let Some(node_id) = node_id {
653 let node = self.form.get(node_id);
654 (
655 self.trace_node_id(node_id),
656 Self::form_node_type_name(&node.node_type).to_string(),
657 self.trace_node_name(node_id),
658 )
659 } else {
660 ("-".to_string(), "-".to_string(), "-".to_string())
661 };
662
663 eprintln!(
664 "{}:{} fn={} current_y={} remaining_space={} triggering_element_id={} node_type={} node_name={} {}",
665 location.file(),
666 location.line(),
667 function,
668 current_y
669 .map(|value| format!("{value:.3}"))
670 .unwrap_or_else(|| "-".to_string()),
671 remaining_space
672 .map(|value| format!("{value:.3}"))
673 .unwrap_or_else(|| "-".to_string()),
674 triggering_id,
675 node_type,
676 node_name,
677 message.as_ref(),
678 );
679 }
680
681 #[track_caller]
682 fn trace_break_before_read(
683 &self,
684 current_y: f64,
685 remaining_space: f64,
686 node_id: FormNodeId,
687 break_before: bool,
688 placed_count: usize,
689 header_node_count: usize,
690 ) {
691 if !groundtruth_trace_enabled() {
692 return;
693 }
694
695 let location = std::panic::Location::caller();
696 let preceding = format!(
697 "{}:{} break_before={} placed_count={} header_node_count={}",
698 location.file(),
699 location.line(),
700 break_before,
701 placed_count,
702 header_node_count
703 );
704 if let Ok(mut state) = groundtruth_trace_state().lock() {
705 state.last_break_before_read = Some(preceding);
706 }
707 self.trace_vertical_state(
708 "layout_content_fitting",
709 Some(current_y),
710 Some(remaining_space),
711 Some(node_id),
712 format!(
713 "break_before check break_before={} placed_count={} header_node_count={} event_type=read",
714 break_before,
715 placed_count,
716 header_node_count
717 ),
718 );
719 }
720
721 #[track_caller]
722 fn trace_commit_page_boundary(
723 &self,
724 current_page: usize,
725 used_height: Option<f64>,
726 page_height: Option<f64>,
727 next_item: Option<&QueuedNode>,
728 ) {
729 if !groundtruth_trace_enabled() {
730 return;
731 }
732
733 let remaining_space = match (used_height, page_height) {
734 (Some(used), Some(total)) => Some(total - used),
735 _ => None,
736 };
737 let preceding_break_read = groundtruth_trace_state()
738 .lock()
739 .ok()
740 .and_then(|state| state.last_break_before_read.clone())
741 .unwrap_or_else(|| "none".to_string());
742 let break_before = next_item.map(|queued| queued.break_before).unwrap_or(false);
743 let subform_path = next_item
744 .map(|queued| self.trace_node_path(queued.id))
745 .unwrap_or_else(|| "none".to_string());
746
747 self.trace_vertical_state(
748 "layout_internal",
749 used_height,
750 remaining_space,
751 next_item.map(|queued| queued.id),
752 format!(
753 "event_type=page_commit COMMIT page_boundary page={} -> page={} break_before={} preceding_break_read=\"{}\" subform_path={}",
754 current_page,
755 current_page + 1,
756 break_before,
757 preceding_break_read,
758 subform_path
759 ),
760 );
761 }
762
763 pub fn layout_with_profile(&self, root: FormNodeId) -> Result<(LayoutDom, LayoutProfile)> {
765 let mut profile = LayoutProfile::default();
766 let dom = self.layout_internal(root, Some(&mut profile))?;
767 Ok((dom, profile))
768 }
769
770 pub fn layout(&self, root: FormNodeId) -> Result<LayoutDom> {
784 self.layout_internal(root, None)
785 }
786
787 fn layout_internal(
788 &self,
789 root: FormNodeId,
790 mut profile: Option<&mut LayoutProfile>,
791 ) -> Result<LayoutDom> {
792 self.trace_vertical_state(
793 "layout_internal",
794 Some(0.0),
795 None,
796 Some(root),
797 format!("enter collect_profile={}", profile.is_some()),
798 );
799 let root_node = self.form.get(root);
800 let collect_profile = profile.is_some();
801
802 let root_overflow_refs = self.resolve_overflow_refs(root);
807
808 let (page_areas, raw_content_nodes) = self.extract_page_structure(root_node)?;
809 self.trace_vertical_state(
810 "layout_internal",
811 Some(0.0),
812 None,
813 Some(root),
814 format!(
815 "page structure extracted page_areas={} raw_content_nodes={} event_type=runtime_allocation",
816 page_areas.len(),
817 raw_content_nodes.len()
818 ),
819 );
820 let all_overflow_ids = self.collect_all_overflow_subform_ids();
832 let raw_content_nodes: Vec<FormNodeId> = raw_content_nodes
833 .into_iter()
834 .filter(|id| !all_overflow_ids.contains(id))
835 .collect();
836 let content_queued = self.queue_content(&raw_content_nodes);
838
839 let mut pages = Vec::new();
840
841 if page_areas.is_empty() {
842 let page_w = root_node.box_model.width.unwrap_or(612.0);
844 let page_h = root_node.box_model.height.unwrap_or(792.0);
845 let area = ContentArea {
846 name: String::new(),
847 x: 0.0,
848 y: 0.0,
849 width: page_w,
850 height: page_h,
851 leader: None,
852 trailer: None,
853 };
854
855 if root_node.layout == LayoutStrategy::TopToBottom {
856 let mut remaining = content_queued;
858 let page_limit = self.estimate_page_limit(&remaining, page_h);
859 while !remaining.is_empty() {
866 if pages.len() >= page_limit {
867 eprintln!(
868 "WARNING: Page limit ({}) reached, truncating layout for {}",
869 page_limit, root_node.name
870 );
871 break;
872 }
873 let active_refs = self.resolve_active_overflow_refs(
874 remaining.first().map(|qn| qn.id),
875 root_overflow_refs,
876 );
877 let overflow_area = Self::content_area_with_overflow(&area, active_refs);
878 let area_for_page = if pages.is_empty() {
879 &area
880 } else {
881 &overflow_area
882 };
883 let (page, rest, consumed_break_only, _, page_profile) = self
884 .layout_content_fitting(
885 area_for_page,
886 &remaining,
887 page_w,
888 page_h,
889 collect_profile,
890 )?;
891 if page.nodes.is_empty() && !consumed_break_only {
892 let forced = self.layout_content_on_page(
894 area_for_page,
895 page_w,
896 page_h,
897 &[remaining[0].id],
898 root_node.layout,
899 )?;
900 let next_remaining = remaining[1..].to_vec();
901 log::debug!(
902 "XFA layout: processing page {}/{} (forced)",
903 pages.len() + 1,
904 page_limit
905 );
906 if let Some(profile) = profile.as_deref_mut() {
907 profile.pages.push(
908 self.profile_page_from_nodes(
909 &forced,
910 area_for_page.y,
911 area_for_page.height,
912 !next_remaining.is_empty(),
913 next_remaining
914 .first()
915 .map(|qn| self.describe_queued_node(qn)),
916 ),
917 );
918 }
919 pages.push(forced);
920 remaining = next_remaining;
921 } else if consumed_break_only {
922 remaining = rest;
924 } else {
925 log::debug!(
926 "XFA layout: processing page {}/{}",
927 pages.len() + 1,
928 page_limit
929 );
930 if !rest.is_empty() {
931 self.trace_commit_page_boundary(
932 pages.len() + 1,
933 page_profile.as_ref().map(|profile| profile.used_height),
934 page_profile.as_ref().map(|profile| profile.page_height),
935 rest.first(),
936 );
937 }
938 if let (Some(profile), Some(page_profile)) =
939 (profile.as_deref_mut(), page_profile)
940 {
941 profile.pages.push(page_profile);
942 }
943 pages.push(page);
944 remaining = rest;
945 }
946 }
947 } else {
948 let page = self.layout_content_on_page(
950 &area,
951 page_w,
952 page_h,
953 &raw_content_nodes,
954 root_node.layout,
955 )?;
956 if let Some(profile) = profile.as_deref_mut() {
957 profile.pages.push(self.profile_page_from_nodes(
958 &page,
959 area.y,
960 area.height,
961 false,
962 None,
963 ));
964 }
965 pages.push(page);
966 }
967 } else {
968 let multi_positioned = content_queued.len() > 1
981 && content_queued.iter().all(|qn| {
982 let node = self.form.get(qn.id);
983 node.layout == LayoutStrategy::Positioned
984 && matches!(
985 node.node_type,
986 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
987 )
988 && !qn.break_before
989 })
990 && {
991 let pa = &page_areas[0];
992 let ca = primary_content_area(pa);
993 let half_page = ca.height * 0.5;
994 content_queued
995 .iter()
996 .all(|qn| self.compute_extent(qn.id).height <= half_page)
997 };
998
999 let single_positioned_delegate = content_queued.len() == 1 && {
1007 let qn = &content_queued[0];
1008 let node = self.form.get(qn.id);
1009 if node.layout == LayoutStrategy::Positioned
1010 && matches!(
1011 node.node_type,
1012 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
1013 )
1014 && !qn.break_before
1015 && !node.children.is_empty()
1016 && node.children.iter().all(|&cid| {
1017 let child = self.form.get(cid);
1018 child.layout == LayoutStrategy::Positioned
1019 })
1020 {
1021 let pa = &page_areas[0];
1023 let ca = primary_content_area(pa);
1024 let child_extent = self.compute_extent(qn.id);
1025 child_extent.height <= ca.height
1026 } else {
1027 false
1028 }
1029 };
1030
1031 let all_content_positioned = multi_positioned || single_positioned_delegate;
1032
1033 if std::env::var_os("XFA_OF_TRACE").is_some() {
1036 let ca_h = page_areas.first().map(|pa| primary_content_area(pa).height);
1037 let parts: Vec<String> = content_queued
1038 .iter()
1039 .map(|qn| {
1040 let n = self.form.get(qn.id);
1041 format!(
1042 "[{:?} h={:.0} bb={}]",
1043 n.layout,
1044 self.compute_extent(qn.id).height,
1045 qn.break_before
1046 )
1047 })
1048 .collect();
1049 eprintln!(
1050 "XFA_OF_TRACE page_areas={} content_queued={} ca_h={:?} multi_pos={} single_deleg={} all_pos={} nodes={}",
1051 page_areas.len(),
1052 content_queued.len(),
1053 ca_h,
1054 multi_positioned,
1055 single_positioned_delegate,
1056 all_content_positioned,
1057 parts.join(",")
1058 );
1059 }
1060
1061 if all_content_positioned {
1062 let pa = &page_areas[0];
1063 let ca = primary_content_area(pa);
1064 let ids: Vec<FormNodeId> = content_queued.iter().map(|qn| qn.id).collect();
1065 let mut page = self.layout_content_on_page(
1066 ca,
1067 pa.page_width,
1068 pa.page_height,
1069 &ids,
1070 LayoutStrategy::Positioned,
1071 )?;
1072 let page_profile = if collect_profile {
1073 Some(self.profile_page_from_nodes(&page, ca.y, ca.height, false, None))
1074 } else {
1075 None
1076 };
1077 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut page)?;
1078 page.runtime_instantiated = pa.runtime_instantiated;
1079 if Self::has_visible_content(&page.nodes) {
1080 if let (Some(profile), Some(page_profile)) =
1081 (profile.as_deref_mut(), page_profile)
1082 {
1083 profile.pages.push(page_profile);
1084 }
1085 pages.push(page);
1086 }
1087 }
1088
1089 let static_body_queue_eligible = self.static_body_continuation
1095 && self.queued_nodes_all_positioned_body(&content_queued);
1096
1097 let all_pageareas_runtime_instantiated =
1103 !page_areas.is_empty() && page_areas.iter().all(|pa| pa.runtime_instantiated);
1104 let runtime_keep_eligible =
1105 self.continuation_runtime_keep && all_pageareas_runtime_instantiated;
1106
1107 let mut remaining = if all_content_positioned {
1109 Vec::new()
1110 } else {
1111 content_queued
1112 };
1113 let data_driven_body_queue =
1114 self.queued_nodes_have_data_backed_body_content(&remaining);
1115 let page_area_continuation_needs_body_content =
1116 page_areas.len() > 1 || data_driven_body_queue;
1117 for pa in &page_areas {
1118 if remaining.is_empty() {
1119 break;
1120 }
1121 if !(pages.is_empty()
1131 || self.queued_nodes_can_populate_continuation_page_area(
1132 &remaining,
1133 page_area_continuation_needs_body_content,
1134 )
1135 || static_body_queue_eligible
1142 && self.queued_nodes_have_substantial_visible_body(
1143 &remaining,
1144 primary_content_area(pa).height,
1145 )
1146 || (runtime_keep_eligible
1158 && self.queued_nodes_have_visible_body_content(&remaining)))
1159 {
1160 if std::env::var_os("XFA_OF_TRACE").is_some() {
1163 eprintln!(
1164 "XFA_OF_TRACE guard-drop: remaining={} after pages_committed={} (queued_nodes_can_populate_continuation_page_area=false -> continuation suppressed)",
1165 remaining.len(),
1166 pages.len()
1167 );
1168 }
1169 remaining.clear();
1170 break;
1171 }
1172 let ca = primary_content_area(pa);
1173 let (mut placed, rest, consumed_break_only, _, page_profile) = self
1174 .layout_content_fitting(
1175 ca,
1176 &remaining,
1177 pa.page_width,
1178 pa.page_height,
1179 collect_profile,
1180 )?;
1181 let commit_used_height = page_profile.as_ref().map(|profile| profile.used_height);
1182 let commit_page_height = page_profile.as_ref().map(|profile| profile.page_height);
1183 let mut page_committed = false;
1184 if consumed_break_only {
1185 remaining = rest;
1186 } else if Self::has_visible_content(&placed.nodes) {
1187 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut placed)?;
1188 placed.runtime_instantiated = pa.runtime_instantiated;
1189 if let (Some(profile), Some(page_profile)) =
1190 (profile.as_deref_mut(), page_profile)
1191 {
1192 profile.pages.push(page_profile);
1193 }
1194 pages.push(placed);
1195 page_committed = true;
1196 remaining = rest;
1197 } else if !pa.fixed_nodes.is_empty() {
1198 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut placed)?;
1204 placed.runtime_instantiated = pa.runtime_instantiated;
1205 if Self::has_visible_content(&placed.nodes) {
1206 if let (Some(profile), Some(page_profile)) =
1207 (profile.as_deref_mut(), page_profile)
1208 {
1209 profile.pages.push(page_profile);
1210 }
1211 pages.push(placed);
1212 page_committed = true;
1213 }
1214 remaining = rest;
1215 } else {
1216 remaining = rest;
1219 }
1220 if page_committed && !remaining.is_empty() {
1221 self.trace_commit_page_boundary(
1222 pages.len(),
1223 commit_used_height,
1224 commit_page_height,
1225 remaining.first(),
1226 );
1227 }
1228 }
1229
1230 if all_pageareas_runtime_instantiated {
1240 remaining.clear();
1241 }
1242
1243 if !remaining.is_empty() {
1245 let last_idx = page_areas.len() - 1;
1246 let overflow_ca_ref = primary_content_area(&page_areas[last_idx]);
1247 let page_limit =
1254 self.estimate_page_limit(&remaining, overflow_ca_ref.height) + pages.len();
1255 while !remaining.is_empty() {
1256 if pages.len() >= page_limit {
1257 eprintln!(
1258 "WARNING: Page limit ({}) reached, truncating layout overflow for {}",
1259 page_limit, root_node.name
1260 );
1261 break;
1262 }
1263 let pa_idx = last_idx;
1264 let pa = &page_areas[pa_idx];
1265 let active_refs = self.resolve_active_overflow_refs(
1269 remaining.first().map(|qn| qn.id),
1270 root_overflow_refs,
1271 );
1272 let overflow_ca =
1273 Self::content_area_with_overflow(overflow_ca_ref, active_refs);
1274 let ca = &overflow_ca;
1275
1276 let (mut page, rest, consumed_break_only, _, page_profile) = self
1277 .layout_content_fitting(
1278 ca,
1279 &remaining,
1280 pa.page_width,
1281 pa.page_height,
1282 collect_profile,
1283 )?;
1284 if page.nodes.is_empty() && !consumed_break_only {
1285 let forced = self.layout_content_on_page(
1286 ca,
1287 pa.page_width,
1288 pa.page_height,
1289 &[remaining[0].id],
1290 LayoutStrategy::TopToBottom,
1291 )?;
1292 let next_remaining = remaining[1..].to_vec();
1293 if Self::has_visible_content(&forced.nodes) {
1294 let mut forced = forced;
1295 let forced_profile = if collect_profile {
1296 Some(
1297 self.profile_page_from_nodes(
1298 &forced,
1299 ca.y,
1300 ca.height,
1301 !next_remaining.is_empty(),
1302 next_remaining
1303 .first()
1304 .map(|qn| self.describe_queued_node(qn)),
1305 ),
1306 )
1307 } else {
1308 None
1309 };
1310 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut forced)?;
1311 forced.runtime_instantiated = pa.runtime_instantiated;
1312 if let (Some(profile), Some(page_profile)) =
1313 (profile.as_deref_mut(), forced_profile)
1314 {
1315 profile.pages.push(page_profile);
1316 }
1317 pages.push(forced);
1318 }
1319 remaining = next_remaining;
1320 } else if consumed_break_only {
1321 remaining = rest;
1322 } else {
1323 if Self::has_visible_content(&page.nodes) {
1324 self.prepend_fixed_nodes(&pa.fixed_nodes, &mut page)?;
1325 page.runtime_instantiated = pa.runtime_instantiated;
1326 if !rest.is_empty() {
1327 self.trace_commit_page_boundary(
1328 pages.len() + 1,
1329 page_profile.as_ref().map(|profile| profile.used_height),
1330 page_profile.as_ref().map(|profile| profile.page_height),
1331 rest.first(),
1332 );
1333 }
1334 if let (Some(profile), Some(page_profile)) =
1335 (profile.as_deref_mut(), page_profile)
1336 {
1337 profile.pages.push(page_profile);
1338 }
1339 pages.push(page);
1340 }
1341 remaining = rest;
1342 }
1343 }
1344 }
1345 }
1346
1347 let chrome_ids: std::collections::HashSet<FormNodeId> = page_areas
1351 .iter()
1352 .flat_map(|pa| pa.fixed_nodes.iter().copied())
1353 .collect();
1354
1355 if std::env::var_os("XFA_OF_TRACE").is_some() {
1359 for (pi, pg) in pages.iter().enumerate() {
1360 let vis_h: f64 = pg
1361 .nodes
1362 .iter()
1363 .map(|n| n.rect.y + n.rect.height)
1364 .fold(0.0_f64, f64::max)
1365 - pg.nodes.iter().map(|n| n.rect.y).fold(f64::MAX, f64::min);
1366 let names: Vec<String> = pg
1367 .nodes
1368 .iter()
1369 .map(|n| {
1370 format!(
1371 "{}#{}:{:.0}@{:.0}",
1372 n.name, n.form_node.0, n.rect.height, n.rect.y
1373 )
1374 })
1375 .collect();
1376 eprintln!(
1377 "XFA_OF_TRACE page[{pi}] nodes={} h={:.0} span={:.0} rt_inst={} tiny_body={} [{}]",
1378 pg.nodes.len(),
1379 pg.height,
1380 vis_h,
1381 pg.runtime_instantiated,
1382 self.page_body_is_tiny_positioned(pg, &chrome_ids).is_some(),
1383 names.join(", "),
1384 );
1385 }
1386 }
1387
1388 if self.tiny_positioned_coalesce {
1391 let prof_pages = profile.as_mut().map(|p| &mut p.pages);
1392 self.coalesce_tiny_positioned_pages(&mut pages, prof_pages, &chrome_ids);
1393 }
1394
1395 Ok(LayoutDom { pages })
1396 }
1397
1398 fn page_body_is_tiny_positioned(
1409 &self,
1410 page: &LayoutPage,
1411 chrome_ids: &std::collections::HashSet<FormNodeId>,
1412 ) -> Option<Vec<usize>> {
1413 if page.runtime_instantiated || page.height <= 0.0 {
1414 return None;
1415 }
1416 let body: Vec<usize> = page
1417 .nodes
1418 .iter()
1419 .enumerate()
1420 .filter(|(_, n)| !chrome_ids.contains(&n.form_node))
1421 .map(|(i, _)| i)
1422 .collect();
1423 if body.len() != 1 {
1424 return None;
1425 }
1426 let n = &page.nodes[body[0]];
1427 let node = self.form.get(n.form_node);
1428 let is_tiny_positioned = node.layout == LayoutStrategy::Positioned
1429 && matches!(
1430 node.node_type,
1431 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
1432 )
1433 && n.rect.height <= TINY_POSITIONED_PAGE_FRAC * page.height;
1434 is_tiny_positioned.then_some(body)
1435 }
1436
1437 fn coalesce_tiny_positioned_pages(
1444 &self,
1445 pages: &mut Vec<LayoutPage>,
1446 mut profile_pages: Option<&mut Vec<LayoutProfilePage>>,
1447 chrome_ids: &std::collections::HashSet<FormNodeId>,
1448 ) {
1449 if pages.len() < 2 {
1450 return;
1451 }
1452 let targets: Vec<usize> = (0..pages.len() - 1)
1455 .filter(|&i| {
1456 self.page_body_is_tiny_positioned(&pages[i], chrome_ids)
1457 .is_some()
1458 })
1459 .collect();
1460 for &i in targets.iter().rev() {
1461 let body: Vec<LayoutNode> = pages[i]
1464 .nodes
1465 .drain(..)
1466 .filter(|n| !chrome_ids.contains(&n.form_node))
1467 .collect();
1468 pages[i + 1].nodes.extend(body);
1469 pages.remove(i);
1470 if let Some(pp) = profile_pages.as_mut() {
1471 if i < pp.len() {
1472 pp.remove(i);
1473 }
1474 }
1475 }
1476 }
1477
1478 fn queued_nodes_can_populate_continuation_page_area(
1483 &self,
1484 nodes: &[QueuedNode],
1485 needs_body_content: bool,
1486 ) -> bool {
1487 if !needs_body_content {
1488 return true;
1489 }
1490
1491 nodes
1492 .iter()
1493 .any(|node| self.queued_node_can_populate_continuation_page_area(node))
1494 }
1495
1496 fn queued_node_can_populate_continuation_page_area(&self, node: &QueuedNode) -> bool {
1497 self.queued_node_has_explicit_page_anchor(node)
1498 || Self::queued_node_is_split_remainder(node)
1499 || self.queued_node_has_data_backed_body_content(node)
1500 }
1501
1502 fn queued_nodes_have_substantial_visible_body(
1506 &self,
1507 nodes: &[QueuedNode],
1508 content_area_height: f64,
1509 ) -> bool {
1510 nodes
1511 .iter()
1512 .any(|node| self.queued_node_has_substantial_visible_body(node, content_area_height))
1513 }
1514
1515 fn queued_nodes_have_visible_body_content(&self, nodes: &[QueuedNode]) -> bool {
1527 let decision = nodes
1528 .iter()
1529 .any(|node| self.subtree_has_visible_body_content(node.id));
1530 if std::env::var_os("XFA_OF_TRACE").is_some() {
1531 eprintln!(
1532 "XFA_OF_TRACE rt-keep-pred: queued={} visible_body={decision}",
1533 nodes.len()
1534 );
1535 }
1536 decision
1537 }
1538
1539 fn queued_nodes_all_positioned_body(&self, nodes: &[QueuedNode]) -> bool {
1553 !nodes.is_empty()
1554 && nodes.iter().all(|qn| {
1555 let node = self.form.get(qn.id);
1556 node.layout == LayoutStrategy::Positioned
1557 && matches!(
1558 node.node_type,
1559 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
1560 )
1561 })
1562 }
1563
1564 fn queued_node_has_substantial_visible_body(
1572 &self,
1573 node: &QueuedNode,
1574 content_area_height: f64,
1575 ) -> bool {
1576 if content_area_height <= 0.0 {
1577 return false;
1578 }
1579 let visible = self.subtree_has_visible_body_content(node.id);
1580 let ext = self.compute_extent(node.id).height;
1581 let decision = visible && ext >= 0.5 * content_area_height;
1582 if std::env::var_os("XFA_OF_TRACE").is_some() {
1583 eprintln!(
1584 "XFA_OF_TRACE keep-pred: visible={visible} extent={ext:.0} ca_h={content_area_height:.0} floor50={} fits={} -> keep={decision}",
1585 ext >= 0.5 * content_area_height,
1586 ext <= content_area_height
1587 );
1588 }
1589 decision
1590 }
1591
1592 fn subtree_has_visible_body_content(&self, id: FormNodeId) -> bool {
1596 if self.is_layout_hidden(id) {
1597 return false;
1598 }
1599 let node = self.form.get(id);
1600 match &node.node_type {
1601 FormNodeType::Draw(_) | FormNodeType::Image { .. } | FormNodeType::Field { .. } => true,
1602 FormNodeType::Root
1603 | FormNodeType::Subform
1604 | FormNodeType::Area
1605 | FormNodeType::ExclGroup
1606 | FormNodeType::SubformSet => self
1607 .expand_occur(&node.children)
1608 .iter()
1609 .any(|&child_id| self.subtree_has_visible_body_content(child_id)),
1610 FormNodeType::PageSet | FormNodeType::PageArea { .. } => false,
1611 }
1612 }
1613
1614 fn queued_node_has_explicit_page_anchor(&self, node: &QueuedNode) -> bool {
1615 node.break_before
1616 || node.break_target.is_some()
1617 || self.form.meta(node.id).page_break_before
1618 }
1619
1620 fn queued_node_is_split_remainder(node: &QueuedNode) -> bool {
1621 node.text_lines_override.is_some()
1622 || node.children_override.is_some()
1623 || node
1624 .nested_child_overrides
1625 .as_ref()
1626 .is_some_and(|overrides| !overrides.is_empty())
1627 }
1628
1629 fn queued_nodes_have_data_backed_body_content(&self, nodes: &[QueuedNode]) -> bool {
1630 nodes
1631 .iter()
1632 .any(|node| self.queued_node_has_data_backed_body_content(node))
1633 }
1634
1635 fn queued_node_has_data_backed_body_content(&self, node: &QueuedNode) -> bool {
1636 self.subtree_has_data_backed_body_content(
1637 node.id,
1638 node.children_override.as_deref(),
1639 node.nested_child_overrides.as_deref(),
1640 false,
1641 )
1642 }
1643
1644 fn subtree_has_data_backed_body_content(
1649 &self,
1650 id: FormNodeId,
1651 children_override: Option<&[FormNodeId]>,
1652 nested_overrides: Option<&[(FormNodeId, Vec<FormNodeId>)]>,
1653 inherited_data_context: bool,
1654 ) -> bool {
1655 if self.is_layout_hidden(id) {
1656 return false;
1657 }
1658
1659 let node = self.form.get(id);
1660 let meta = self.form.meta(id);
1661 let own_data_context = !meta.data_bind_none && meta.data_bind_ref.is_some();
1662 let data_context = inherited_data_context || own_data_context;
1663
1664 match &node.node_type {
1665 FormNodeType::Field { value } => data_context && !value.trim().is_empty(),
1666 FormNodeType::Root
1667 | FormNodeType::Subform
1668 | FormNodeType::Area
1669 | FormNodeType::ExclGroup
1670 | FormNodeType::SubformSet => {
1671 let children = children_override.unwrap_or(&node.children);
1672 let expanded = if children_override.is_some() {
1673 children.to_vec()
1674 } else {
1675 self.expand_occur(children)
1676 };
1677 expanded.iter().any(|&child_id| {
1678 let child_override = nested_overrides
1679 .and_then(|overrides| {
1680 overrides
1681 .iter()
1682 .find(|(override_id, _)| *override_id == child_id)
1683 })
1684 .map(|(_, child_override)| child_override.as_slice());
1685 self.subtree_has_data_backed_body_content(
1686 child_id,
1687 child_override,
1688 nested_overrides,
1689 data_context,
1690 )
1691 })
1692 }
1693 FormNodeType::PageSet
1694 | FormNodeType::PageArea { .. }
1695 | FormNodeType::Draw(_)
1696 | FormNodeType::Image { .. } => false,
1697 }
1698 }
1699
1700 fn estimate_page_limit(&self, content: &[QueuedNode], page_height: f64) -> usize {
1704 if page_height <= 0.0 {
1705 return MAX_PAGES;
1706 }
1707 let total_height: f64 = content
1708 .iter()
1709 .map(|qn| {
1710 self.compute_extent_with_available_and_override(
1711 qn.id,
1712 None,
1713 qn.children_override.as_deref(),
1714 )
1715 .height
1716 })
1717 .sum();
1718 let estimated = (total_height / page_height).ceil() as usize;
1719 (estimated * 2).clamp(10, MAX_PAGES)
1720 }
1721
1722 fn has_visible_content(nodes: &[LayoutNode]) -> bool {
1728 nodes.iter().any(|n| {
1729 !matches!(n.content, LayoutContent::None) || Self::has_visible_content(&n.children)
1730 })
1731 }
1732
1733 fn is_layout_hidden(&self, id: FormNodeId) -> bool {
1742 let meta = self.form.meta(id);
1743 if meta.presence.is_layout_hidden() {
1744 return true;
1745 }
1746 if meta.content_area_break {
1751 let node = self.form.get(id);
1752 let h = node.box_model.height.unwrap_or(f64::MAX);
1753 return h < 50.0;
1754 }
1755 false
1756 }
1757
1758 fn queue_content(&self, children: &[FormNodeId]) -> Vec<QueuedNode> {
1761 let expanded = self.expand_occur(children);
1762 expanded
1763 .into_iter()
1764 .filter(|&id| !self.is_layout_hidden(id))
1765 .map(|id| {
1766 let meta = self.form.meta(id);
1767 self.trace_vertical_state(
1768 "queue_content",
1769 None,
1770 None,
1771 Some(id),
1772 format!(
1773 "copy FormNodeMeta -> QueuedNode break_before={} break_after={} break_target={} event_type=copy_merge",
1774 meta.page_break_before,
1775 meta.page_break_after,
1776 meta.break_target.clone().unwrap_or_default()
1777 ),
1778 );
1779 QueuedNode {
1780 id,
1781 break_before: meta.page_break_before,
1782 break_after: meta.page_break_after,
1783 break_target: meta.break_target.clone(),
1784 children_override: None,
1785 text_lines_override: None,
1786 nested_child_overrides: None,
1787 }
1788 })
1789 .collect()
1790 }
1791
1792 fn subtree_is_blank(&self, id: FormNodeId) -> bool {
1793 let node = self.form.get(id);
1794 match &node.node_type {
1795 FormNodeType::Field { value } => value.is_empty(),
1796 FormNodeType::Draw(ref content) => {
1797 if let DrawContent::Text(text) = content {
1798 text.is_empty()
1799 } else {
1800 false
1801 }
1802 }
1803 FormNodeType::Image { data, .. } => data.is_empty(),
1804 FormNodeType::Root | FormNodeType::PageSet | FormNodeType::PageArea { .. } => true,
1805 FormNodeType::Subform
1807 | FormNodeType::Area
1808 | FormNodeType::ExclGroup
1809 | FormNodeType::SubformSet => node.children.iter().all(|&c| self.subtree_is_blank(c)),
1810 }
1811 }
1812
1813 #[allow(dead_code)]
1821 fn keep_links_content(&self, current_id: FormNodeId, next_id: FormNodeId) -> bool {
1822 let cur_meta = self.form.meta(current_id);
1823 let nxt_meta = self.form.meta(next_id);
1824 cur_meta.keep_next_content_area
1825 || nxt_meta.keep_previous_content_area
1826 || self.is_spacer_keep_with_next(current_id)
1827 }
1828
1829 #[allow(dead_code)]
1832 fn keep_chain_height(&self, children: &[FormNodeId], start_idx: usize, available: Size) -> f64 {
1833 let mut total = 0.0;
1834 for i in start_idx..children.len() {
1835 let sz = self.compute_extent_with_available(children[i], Some(available));
1836 total += sz.height;
1837 if i + 1 < children.len() && !self.keep_links_content(children[i], children[i + 1]) {
1838 break;
1839 }
1840 }
1841 total
1842 }
1843
1844 #[allow(dead_code)]
1847 fn queued_keep_chain_height(
1848 &self,
1849 children: &[QueuedNode],
1850 start_idx: usize,
1851 available: Size,
1852 ) -> f64 {
1853 let mut total = 0.0;
1854 for i in start_idx..children.len() {
1855 let sz = self.compute_extent_with_available(children[i].id, Some(available));
1856 total += sz.height;
1857 if i + 1 < children.len()
1858 && !self.keep_links_content(children[i].id, children[i + 1].id)
1859 {
1860 break;
1861 }
1862 }
1863 total
1864 }
1865
1866 fn is_spacer_keep_with_next(&self, id: FormNodeId) -> bool {
1869 let meta = self.form.meta(id);
1870 meta.keep_intact_content_area && self.subtree_is_blank(id)
1871 }
1872
1873 fn visible_keep_chain_height(
1881 &self,
1882 visible_ids: &[(usize, FormNodeId)],
1883 start_idx: usize,
1884 available: Size,
1885 ) -> (f64, usize) {
1886 self.trace_vertical_state(
1887 "visible_keep_chain_height",
1888 Some(0.0),
1889 Some(available.height),
1890 visible_ids.get(start_idx).map(|(_, id)| *id),
1891 format!(
1892 "enter start_idx={} visible_ids={}",
1893 start_idx,
1894 visible_ids.len()
1895 ),
1896 );
1897 let mut total = 0.0;
1898 let mut count = 0usize;
1899 for i in start_idx..visible_ids.len() {
1900 let (_, id) = visible_ids[i];
1901 let sz = self.compute_extent_with_available(id, Some(available));
1902 total += sz.height;
1903 count += 1;
1904 self.trace_vertical_state(
1905 "visible_keep_chain_height",
1906 Some(total),
1907 Some(available.height - total),
1908 Some(id),
1909 format!(
1910 "accumulate keep chain index={} node_height={:.3} running_total={:.3} count={} event_type=read",
1911 i,
1912 sz.height,
1913 total,
1914 count
1915 ),
1916 );
1917 if i + 1 < visible_ids.len() {
1919 let (_, next_id) = visible_ids[i + 1];
1920 let cur_meta = self.form.meta(id);
1921 let nxt_meta = self.form.meta(next_id);
1922 let keep = cur_meta.keep_next_content_area
1923 || nxt_meta.keep_previous_content_area
1924 || (cur_meta.keep_intact_content_area && self.subtree_is_blank(id));
1925 self.trace_vertical_state(
1926 "visible_keep_chain_height",
1927 Some(total),
1928 Some(available.height - total),
1929 Some(id),
1930 format!(
1931 "keep-chain continuation check next={} cur.keep_next={} next.keep_previous={} cur.keep_intact_blank={} result={} event_type=read",
1932 self.trace_node_id(next_id),
1933 cur_meta.keep_next_content_area,
1934 nxt_meta.keep_previous_content_area,
1935 cur_meta.keep_intact_content_area && self.subtree_is_blank(id),
1936 keep
1937 ),
1938 );
1939 if !keep {
1940 break;
1941 }
1942 }
1943 }
1944 self.trace_vertical_state(
1945 "visible_keep_chain_height",
1946 Some(total),
1947 Some(available.height - total),
1948 visible_ids.get(start_idx).map(|(_, id)| *id),
1949 format!(
1950 "return chain_height={:.3} chain_len={} event_type=read",
1951 total, count
1952 ),
1953 );
1954 (total, count)
1955 }
1956
1957 fn extract_page_structure(
1958 &self,
1959 root: &FormNode,
1960 ) -> Result<(Vec<PageAreaInfo>, Vec<FormNodeId>)> {
1961 let mut page_areas = Vec::new();
1962 let mut content_nodes = Vec::new();
1963
1964 for &child_id in &root.children {
1965 let child = self.form.get(child_id);
1966 match &child.node_type {
1967 FormNodeType::PageSet => {
1968 for &pa_id in &child.children {
1969 let pa_node = self.form.get(pa_id);
1970 if let FormNodeType::PageArea { content_areas } = &pa_node.node_type {
1971 let pa_meta = self.form.meta(pa_id);
1972 let fixed: Vec<FormNodeId> = pa_node
1973 .children
1974 .iter()
1975 .copied()
1976 .filter(|&cid| {
1977 matches!(
1978 self.form.get(cid).node_type,
1979 FormNodeType::Draw(..)
1980 | FormNodeType::Subform
1981 | FormNodeType::Area
1982 | FormNodeType::ExclGroup
1983 )
1984 })
1985 .collect();
1986 page_areas.push(PageAreaInfo {
1987 name: pa_node.name.clone(),
1988 xfa_id: pa_meta.xfa_id.clone(),
1989 content_areas: content_areas.clone(),
1990 page_width: pa_node.box_model.width.unwrap_or(612.0),
1991 page_height: pa_node.box_model.height.unwrap_or(792.0),
1992 fixed_nodes: fixed,
1993 runtime_instantiated: pa_meta.runtime_instantiated_page,
1994 });
1995 }
1996 }
1997 }
1998 FormNodeType::PageArea { content_areas } => {
1999 let pa_meta = self.form.meta(child_id);
2000 let fixed: Vec<FormNodeId> = child
2001 .children
2002 .iter()
2003 .copied()
2004 .filter(|&cid| {
2005 matches!(
2006 self.form.get(cid).node_type,
2007 FormNodeType::Draw(..)
2008 | FormNodeType::Subform
2009 | FormNodeType::Area
2010 | FormNodeType::ExclGroup
2011 )
2012 })
2013 .collect();
2014 page_areas.push(PageAreaInfo {
2015 name: child.name.clone(),
2016 xfa_id: pa_meta.xfa_id.clone(),
2017 content_areas: content_areas.clone(),
2018 page_width: child.box_model.width.unwrap_or(612.0),
2019 page_height: child.box_model.height.unwrap_or(792.0),
2020 fixed_nodes: fixed,
2021 runtime_instantiated: pa_meta.runtime_instantiated_page,
2022 });
2023 }
2024 FormNodeType::Subform
2033 | FormNodeType::Area
2035 | FormNodeType::ExclGroup => {
2037 let has_pageset = child
2043 .children
2044 .iter()
2045 .any(|&cid| matches!(self.form.get(cid).node_type, FormNodeType::PageSet));
2046 if has_pageset {
2047 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
2048 page_areas.extend(inner_areas);
2049 content_nodes.extend(inner_content);
2050 } else if child.layout == LayoutStrategy::TopToBottom {
2051 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
2054 if !inner_areas.is_empty() {
2055 page_areas.extend(inner_areas);
2056 content_nodes.extend(inner_content);
2057 } else {
2058 content_nodes.push(child_id);
2059 }
2060 } else {
2061 content_nodes.push(child_id);
2062 }
2063 }
2064 FormNodeType::SubformSet => {
2068 let (inner_areas, inner_content) = self.extract_page_structure(child)?;
2069 page_areas.extend(inner_areas);
2070 content_nodes.extend(inner_content);
2071 }
2072 _ => {
2073 content_nodes.push(child_id);
2074 }
2075 }
2076 }
2077
2078 Ok((page_areas, content_nodes))
2079 }
2080
2081 fn layout_content_on_page(
2082 &self,
2083 content_area: &ContentArea,
2084 page_width: f64,
2085 page_height: f64,
2086 content_ids: &[FormNodeId],
2087 strategy: LayoutStrategy,
2088 ) -> Result<LayoutPage> {
2089 let mut page = LayoutPage {
2090 width: page_width,
2091 height: page_height,
2092 nodes: Vec::new(),
2093 runtime_instantiated: false,
2094 };
2095
2096 let available = Size {
2097 width: content_area.width,
2098 height: content_area.height,
2099 };
2100
2101 let nodes = self.layout_children(content_ids, available, strategy)?;
2102
2103 for mut node in nodes {
2105 node.rect.x += content_area.x;
2106 node.rect.y += content_area.y;
2107 page.nodes.push(node);
2108 }
2109
2110 Ok(page)
2111 }
2112
2113 fn profile_page_from_nodes(
2114 &self,
2115 page: &LayoutPage,
2116 content_y: f64,
2117 usable_height: f64,
2118 overflow_to_next: bool,
2119 first_overflow_element: Option<String>,
2120 ) -> LayoutProfilePage {
2121 let used_height = page
2122 .nodes
2123 .iter()
2124 .map(|node| (node.rect.y + node.rect.height) - content_y)
2125 .fold(0.0_f64, f64::max)
2126 .clamp(0.0, usable_height.max(0.0));
2127
2128 LayoutProfilePage {
2129 page_height: usable_height.max(0.0),
2130 used_height,
2131 overflow_to_next,
2132 first_overflow_element,
2133 }
2134 }
2135
2136 fn prepend_fixed_nodes(&self, fixed_ids: &[FormNodeId], page: &mut LayoutPage) -> Result<()> {
2140 if fixed_ids.is_empty() {
2141 return Ok(());
2142 }
2143 let fixed_laid = self.layout_positioned(fixed_ids)?;
2144 let mut merged = fixed_laid;
2145 merged.append(&mut page.nodes);
2146 page.nodes = merged;
2147 Ok(())
2148 }
2149
2150 #[allow(clippy::type_complexity)]
2158 fn layout_content_fitting(
2159 &self,
2160 content_area: &ContentArea,
2161 content_ids: &[QueuedNode],
2162 page_width: f64,
2163 page_height: f64,
2164 profile_enabled: bool,
2165 ) -> Result<FittingResult> {
2166 let mut page = LayoutPage {
2167 width: page_width,
2168 height: page_height,
2169 nodes: Vec::new(),
2170 runtime_instantiated: false,
2171 };
2172
2173 let mut leader_height = 0.0;
2186 let mut trailer_height = 0.0;
2187
2188 if let Some(leader_id) = content_area.leader {
2189 let leader_size = self.compute_extent(leader_id);
2190 leader_height = leader_size.height;
2191 let leader_node = self.form.get(leader_id);
2192 let node = self.layout_single_node(leader_id, leader_node, 0.0, 0.0, None)?;
2193 let mut offset = node;
2194 offset.rect.x += content_area.x;
2195 offset.rect.y += content_area.y;
2196 page.nodes.push(offset);
2197 }
2198
2199 if let Some(trailer_id) = content_area.trailer {
2200 let trailer_size = self.compute_extent(trailer_id);
2201 trailer_height = trailer_size.height;
2202 let trailer_y = content_area.height - trailer_height;
2204 let trailer_node = self.form.get(trailer_id);
2205 let node = self.layout_single_node(trailer_id, trailer_node, 0.0, trailer_y, None)?;
2206 let mut offset = node;
2207 offset.rect.x += content_area.x;
2208 offset.rect.y += content_area.y;
2209 page.nodes.push(offset);
2210 }
2211
2212 let effective_ca_height = {
2220 let ca_bottom = content_area.y + content_area.height;
2221 let gap_below_ca = page_height - ca_bottom;
2222 if gap_below_ca < 36.0 {
2226 let remaining_page = page_height - content_area.y;
2227 content_area.height.max(remaining_page)
2228 } else {
2229 content_area.height
2230 }
2231 };
2232 let content_height = effective_ca_height - leader_height - trailer_height;
2233 let available = Size {
2234 width: content_area.width,
2235 height: content_height,
2236 };
2237
2238 let mut y_cursor = leader_height;
2239 let mut placed_count = 0;
2240 let mut split_remaining: Vec<QueuedNode> = Vec::new();
2241 let content_bottom = leader_height + content_height;
2242 let mut consumed_break_only = false;
2243 let break_target = None;
2244 let mut max_used_bottom = 0.0_f64;
2245
2246 let header_node_count = (if content_area.leader.is_some() { 1 } else { 0 })
2248 + (if content_area.trailer.is_some() { 1 } else { 0 });
2249
2250 let visible_ids: Vec<(usize, FormNodeId)> = content_ids
2253 .iter()
2254 .enumerate()
2255 .filter(|(_, qn)| !self.is_layout_hidden(qn.id))
2256 .map(|(i, qn)| (i, qn.id))
2257 .collect();
2258 let mut vis_pos = 0; self.trace_vertical_state(
2261 "layout_content_fitting",
2262 Some(y_cursor),
2263 Some(content_height),
2264 content_ids.first().map(|queued| queued.id),
2265 format!(
2266 "enter page_width={:.3} page_height={:.3} content_area={} content_nodes={} leader_height={:.3} trailer_height={:.3} effective_ca_height={:.3}",
2267 page_width,
2268 page_height,
2269 content_area.name,
2270 content_ids.len(),
2271 leader_height,
2272 trailer_height,
2273 effective_ca_height
2274 ),
2275 );
2276 self.trace_vertical_state(
2277 "layout_content_fitting",
2278 Some(y_cursor),
2279 Some(content_height),
2280 content_ids.first().map(|queued| queued.id),
2281 format!(
2282 "visible_ids prepared count={} event_type=runtime_allocation",
2283 visible_ids.len()
2284 ),
2285 );
2286
2287 for (idx, qn) in content_ids.iter().enumerate() {
2288 let child_id = qn.id;
2289
2290 if self.is_layout_hidden(child_id) {
2292 placed_count += 1;
2293 continue;
2294 }
2295
2296 self.trace_vertical_state(
2297 "layout_content_fitting",
2298 Some(y_cursor),
2299 Some(content_bottom - y_cursor),
2300 Some(child_id),
2301 format!(
2302 "loop entry idx={} placed_count={} vis_pos={} break_before={} break_after={}",
2303 idx, placed_count, vis_pos, qn.break_before, qn.break_after
2304 ),
2305 );
2306
2307 self.trace_break_before_read(
2311 y_cursor,
2312 content_bottom - y_cursor,
2313 child_id,
2314 qn.break_before,
2315 placed_count,
2316 header_node_count,
2317 );
2318 if qn.break_before && placed_count > 0 {
2319 let only_blanks = page.nodes.len() <= header_node_count;
2323 if only_blanks {
2324 consumed_break_only = true;
2325 }
2326 break;
2327 }
2328
2329 let child = self.form.get(child_id);
2330 let child_size = if let Some(ref override_lines) = qn.text_lines_override {
2331 let style_lh = self.form.meta(child_id).style.line_height_pt;
2332 let lh = style_lh.unwrap_or_else(|| child.font.line_height_pt());
2333 let w = self.compute_extent(child_id).width;
2334 Size {
2335 width: w,
2336 height: override_lines.len() as f64 * lh,
2337 }
2338 } else {
2339 self.compute_extent_with_available_and_override(
2340 child_id,
2341 Some(available),
2342 qn.children_override.as_deref(),
2343 )
2344 };
2345
2346 if placed_count > 0 && vis_pos < visible_ids.len() {
2354 let (chain_height, chain_len) =
2355 self.visible_keep_chain_height(&visible_ids, vis_pos, available);
2356 let remaining_on_page = content_bottom - y_cursor;
2357 let is_single_splittable = chain_len == 1 && self.can_split(child_id);
2358 self.trace_vertical_state(
2359 "layout_content_fitting",
2360 Some(y_cursor),
2361 Some(remaining_on_page),
2362 Some(child_id),
2363 format!(
2364 "keep look-ahead chain_height={:.3} chain_len={} remaining_on_page={:.3} content_height={:.3} is_single_splittable={} event_type=read",
2365 chain_height,
2366 chain_len,
2367 remaining_on_page,
2368 content_height,
2369 is_single_splittable
2370 ),
2371 );
2372 if !is_single_splittable
2373 && chain_height > remaining_on_page
2374 && chain_height <= content_height
2375 {
2376 self.trace_vertical_state(
2378 "layout_content_fitting",
2379 Some(y_cursor),
2380 Some(remaining_on_page),
2381 Some(child_id),
2382 "keep look-ahead fired: current chain does not fit remaining space but fits on a fresh page event_type=rule_decision",
2383 );
2384 break;
2385 }
2386 }
2389
2390 if y_cursor + child_size.height > content_bottom {
2391 let remaining_height = content_bottom - y_cursor;
2392
2393 if remaining_height > 0.0
2395 && (qn.text_lines_override.is_some() || self.is_splittable_text_leaf(child_id))
2396 {
2397 let lines = if let Some(ref ol) = qn.text_lines_override {
2398 ol.clone()
2399 } else {
2400 let txt = match &child.node_type {
2401 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
2402 FormNodeType::Field { value } => value.as_str(),
2403 _ => "",
2404 };
2405 let child_style = &self.form.meta(child_id).style;
2406 let para_margins = child_style
2407 .margin_left_pt
2408 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2409 + child_style
2410 .margin_right_pt
2411 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2412 let child_border_w = child_style
2413 .border_width_pt
2414 .unwrap_or(child.box_model.border_width);
2415 let insets_w = child.box_model.margins.horizontal()
2416 + child_border_w * 2.0
2417 + para_margins;
2418 let max_w = (child_size.width - insets_w).max(1.0);
2419 text::wrap_text(
2420 txt,
2421 max_w,
2422 &child.font,
2423 child_style.text_indent_pt.unwrap_or(0.0),
2424 child_style.line_height_pt,
2425 )
2426 .lines
2427 };
2428 let (partial, rest_nodes) =
2429 self.split_text_node(child_id, y_cursor, remaining_height, &lines)?;
2430 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
2431 if profile_enabled {
2432 max_used_bottom = max_used_bottom
2433 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
2434 }
2435 let mut offset_node = partial;
2436 offset_node.rect.x += content_area.x;
2437 offset_node.rect.y += content_area.y;
2438 page.nodes.push(offset_node);
2439 placed_count += 1;
2440 split_remaining = rest_nodes;
2441 } else if placed_count > 0 {
2442 break;
2443 }
2444 } else if remaining_height > 0.0 && self.can_split(child_id) {
2446 let (partial, rest_nodes) = self.split_tb_node(
2447 child_id,
2448 y_cursor,
2449 remaining_height,
2450 available,
2451 qn.children_override.as_deref(),
2452 qn.nested_child_overrides.as_deref(),
2453 )?;
2454
2455 let partial_fits = partial.rect.height <= remaining_height + 1.0;
2456 let split_productive = !partial.children.is_empty()
2457 && (partial_fits || partial.children.len() > 1);
2458 if !partial.children.is_empty() && (partial_fits || split_productive) {
2459 if profile_enabled {
2460 max_used_bottom = max_used_bottom
2461 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
2462 }
2463 let mut offset_node = partial;
2464 offset_node.rect.x += content_area.x;
2465 offset_node.rect.y += content_area.y;
2466 page.nodes.push(offset_node);
2467 placed_count += 1;
2468 split_remaining = rest_nodes;
2469 } else if placed_count > 0 {
2470 break;
2474 }
2475 } else if idx == 0 || page.nodes.len() <= header_node_count {
2476 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
2478 if profile_enabled {
2479 max_used_bottom = max_used_bottom
2480 .max((y_cursor + child_size.height - leader_height).max(0.0));
2481 }
2482 let node = self.layout_single_node_with_extent(
2483 child_id,
2484 child,
2485 x,
2486 y_cursor,
2487 child_size,
2488 qn.children_override.as_deref(),
2489 )?;
2490 let mut offset_node = node;
2491 offset_node.rect.x += content_area.x;
2492 offset_node.rect.y += content_area.y;
2493 page.nodes.push(offset_node);
2494 placed_count += 1;
2495 }
2496 break;
2497 }
2498
2499 if self.has_inner_break(child_id) && self.can_split(child_id) {
2503 let (partial, rest_nodes) = self.split_tb_node(
2504 child_id,
2505 y_cursor,
2506 content_height,
2507 available,
2508 qn.children_override.as_deref(),
2509 qn.nested_child_overrides.as_deref(),
2510 )?;
2511 let remaining_on_page = content_bottom - y_cursor;
2512 if !partial.children.is_empty() && partial.rect.height <= remaining_on_page {
2513 if profile_enabled {
2514 max_used_bottom = max_used_bottom
2515 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
2516 }
2517 let mut offset_node = partial;
2518 offset_node.rect.x += content_area.x;
2519 offset_node.rect.y += content_area.y;
2520 page.nodes.push(offset_node);
2521 placed_count += 1;
2522 split_remaining = rest_nodes;
2523 } else if placed_count > 0 {
2524 break;
2525 } else {
2526 if !partial.children.is_empty() {
2527 if profile_enabled {
2528 max_used_bottom = max_used_bottom
2529 .max((y_cursor + partial.rect.height - leader_height).max(0.0));
2530 }
2531 let mut offset_node = partial;
2532 offset_node.rect.x += content_area.x;
2533 offset_node.rect.y += content_area.y;
2534 page.nodes.push(offset_node);
2535 }
2536 placed_count += 1;
2537 split_remaining = rest_nodes;
2538 }
2539 break;
2540 }
2541
2542 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
2544 let node = if let Some(ref override_lines) = qn.text_lines_override {
2545 let child_style = &self.form.meta(child_id).style;
2546 LayoutNode {
2547 form_node: child_id,
2548 rect: Rect::new(x, y_cursor, child_size.width, child_size.height),
2549 name: child.name.clone(),
2550 content: LayoutContent::WrappedText {
2551 lines: override_lines.clone(),
2552 first_line_of_para: vec![false; override_lines.len()],
2553 font_size: child.font.size,
2554 text_align: child.font.text_align,
2555 font_family: child.font.typeface,
2556 space_above_pt: child_style.space_above_pt,
2557 space_below_pt: child_style.space_below_pt,
2558 from_field: matches!(child.node_type, FormNodeType::Field { .. }),
2559 },
2560 children: Vec::new(),
2561 style: self.form.meta(child_id).style.clone(),
2562 display_items: self.form.meta(child_id).display_items.clone(),
2563 save_items: self.form.meta(child_id).save_items.clone(),
2564 }
2565 } else {
2566 self.layout_single_node_with_extent(
2567 child_id,
2568 child,
2569 x,
2570 y_cursor,
2571 child_size,
2572 qn.children_override.as_deref(),
2573 )?
2574 };
2575 if profile_enabled {
2576 max_used_bottom =
2577 max_used_bottom.max((y_cursor + child_size.height - leader_height).max(0.0));
2578 }
2579 let mut offset_node = node;
2580 offset_node.rect.x += content_area.x;
2581 offset_node.rect.y += content_area.y;
2582 page.nodes.push(offset_node);
2583
2584 y_cursor += child_size.height;
2585 placed_count += 1;
2586 vis_pos += 1;
2587
2588 self.trace_vertical_state(
2589 "layout_content_fitting",
2590 Some(y_cursor),
2591 Some(content_bottom - y_cursor),
2592 Some(child_id),
2593 format!(
2594 "placed node child_height={:.3} new_y_cursor={:.3} placed_count={} vis_pos={}",
2595 child_size.height, y_cursor, placed_count, vis_pos
2596 ),
2597 );
2598
2599 if qn.break_after {
2600 self.trace_vertical_state(
2601 "layout_content_fitting",
2602 Some(y_cursor),
2603 Some(content_bottom - y_cursor),
2604 Some(child_id),
2605 format!("break_after check break_after={}", qn.break_after),
2606 );
2607 break;
2608 }
2609 }
2610
2611 let mut remaining = split_remaining;
2612 remaining.extend(content_ids[placed_count..].iter().cloned());
2613 self.trace_vertical_state(
2614 "layout_content_fitting",
2615 Some(y_cursor),
2616 Some(content_bottom - y_cursor),
2617 remaining.first().map(|queued| queued.id),
2618 format!(
2619 "return remaining_nodes={} consumed_break_only={} first_remaining={}",
2620 remaining.len(),
2621 consumed_break_only,
2622 remaining
2623 .first()
2624 .map(|queued| self.describe_queued_node(queued))
2625 .unwrap_or_else(|| "none".to_string())
2626 ),
2627 );
2628 let page_profile = if profile_enabled {
2629 Some(LayoutProfilePage {
2630 page_height: content_height.max(0.0),
2631 used_height: max_used_bottom.clamp(0.0, content_height.max(0.0)),
2632 overflow_to_next: !remaining.is_empty() && !consumed_break_only,
2633 first_overflow_element: if !remaining.is_empty() && !consumed_break_only {
2634 Some(self.describe_queued_node(&remaining[0]))
2635 } else {
2636 None
2637 },
2638 })
2639 } else {
2640 None
2641 };
2642 Ok((
2643 page,
2644 remaining,
2645 consumed_break_only,
2646 break_target,
2647 page_profile,
2648 ))
2649 }
2650
2651 fn describe_queued_node(&self, queued: &QueuedNode) -> String {
2652 let node = self.form.get(queued.id);
2653 let identifier = self
2654 .form
2655 .meta(queued.id)
2656 .xfa_id
2657 .clone()
2658 .filter(|id| !id.is_empty())
2659 .or_else(|| (!node.name.is_empty()).then(|| node.name.clone()))
2660 .unwrap_or_else(|| queued.id.0.to_string());
2661 let height = self
2662 .compute_extent_with_available_and_override(
2663 queued.id,
2664 None,
2665 queued.children_override.as_deref(),
2666 )
2667 .height;
2668 format!(
2669 "{}#{} (h={height:.1})",
2670 Self::form_node_type_name(&node.node_type),
2671 identifier
2672 )
2673 }
2674
2675 fn form_node_type_name(node_type: &FormNodeType) -> &'static str {
2676 match node_type {
2677 FormNodeType::Root => "root",
2678 FormNodeType::PageSet => "pageSet",
2679 FormNodeType::PageArea { .. } => "pageArea",
2680 FormNodeType::Subform => "subform",
2681 FormNodeType::Area => "area",
2682 FormNodeType::ExclGroup => "exclGroup",
2683 FormNodeType::SubformSet => "subformSet",
2684 FormNodeType::Field { .. } => "field",
2685 FormNodeType::Draw(_) => "draw",
2686 FormNodeType::Image { .. } => "image",
2687 }
2688 }
2689
2690 fn can_split(&self, id: FormNodeId) -> bool {
2701 let node = self.form.get(id);
2702 if node.children.is_empty() {
2703 return self.is_splittable_text_leaf(id);
2704 }
2705 if let Some(explicit_h) = node.box_model.height {
2706 if node.layout == LayoutStrategy::TopToBottom {
2709 let extent = self.compute_extent(id);
2710 return extent.height > explicit_h;
2711 }
2712 return false;
2713 }
2714 if node.layout == LayoutStrategy::Positioned && node.box_model.max_height < f64::MAX {
2717 return false;
2718 }
2719 matches!(
2720 node.layout,
2721 LayoutStrategy::TopToBottom
2722 | LayoutStrategy::LeftToRightTB
2723 | LayoutStrategy::RightToLeftTB
2724 | LayoutStrategy::Table
2725 | LayoutStrategy::Positioned
2726 )
2727 }
2728
2729 fn is_splittable_text_leaf(&self, id: FormNodeId) -> bool {
2734 let node = self.form.get(id);
2735 if !node.children.is_empty() {
2736 return false;
2737 }
2738 if self.form.meta(id).keep_intact_content_area {
2739 return false;
2740 }
2741 let style = &self.form.meta(id).style;
2742 let para_margins = style
2743 .margin_left_pt
2744 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2745 + style
2746 .margin_right_pt
2747 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2748 match &node.node_type {
2749 FormNodeType::Draw(DrawContent::Text(t)) => {
2750 let line_count = text::wrap_text(
2751 t,
2752 (node.box_model.content_width() - para_margins).max(1.0),
2753 &node.font,
2754 style.text_indent_pt.unwrap_or(0.0),
2755 style.line_height_pt,
2756 )
2757 .lines
2758 .len();
2759 line_count > 1
2760 }
2761 FormNodeType::Field { value } if !value.is_empty() => {
2762 let line_count = text::wrap_text(
2763 value,
2764 (node.box_model.content_width() - para_margins).max(1.0),
2765 &node.font,
2766 style.text_indent_pt.unwrap_or(0.0),
2767 style.line_height_pt,
2768 )
2769 .lines
2770 .len();
2771 line_count > 1
2772 }
2773 _ => false,
2774 }
2775 }
2776
2777 fn split_text_node(
2782 &self,
2783 id: FormNodeId,
2784 y_offset: f64,
2785 remaining_height: f64,
2786 lines: &[String],
2787 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
2788 let node = self.form.get(id);
2789 let style_lh = self.form.meta(id).style.line_height_pt;
2790 let lh = style_lh.unwrap_or_else(|| node.font.line_height_pt());
2791 let split_points = text::text_split_points(lines.len(), lh);
2792
2793 let mut split_at = 0;
2794 for &sp in &split_points {
2795 if sp <= remaining_height + 0.5 {
2796 split_at += 1;
2797 } else {
2798 break;
2799 }
2800 }
2801
2802 if split_at == 0 {
2803 let full_height = lines.len() as f64 * lh;
2804 let split_style = &self.form.meta(id).style;
2805 let full_node = LayoutNode {
2806 form_node: id,
2807 rect: Rect::new(0.0, y_offset, self.compute_extent(id).width, full_height),
2808 name: node.name.clone(),
2809 content: LayoutContent::WrappedText {
2810 lines: lines.to_vec(),
2811 first_line_of_para: vec![false; lines.len()],
2812 font_size: node.font.size,
2813 text_align: node.font.text_align,
2814 font_family: node.font.typeface,
2815 space_above_pt: split_style.space_above_pt,
2816 space_below_pt: split_style.space_below_pt,
2817 from_field: matches!(node.node_type, FormNodeType::Field { .. }),
2818 },
2819 children: Vec::new(),
2820 style: self.form.meta(id).style.clone(),
2821 display_items: self.form.meta(id).display_items.clone(),
2822 save_items: self.form.meta(id).save_items.clone(),
2823 };
2824 return Ok((full_node, Vec::new()));
2825 }
2826
2827 let top_lines: Vec<String> = lines[..split_at].to_vec();
2828 let bottom_lines: Vec<String> = lines[split_at..].to_vec();
2829 let partial_height = split_at as f64 * lh;
2830 let node_width = self.compute_extent(id).width;
2831 let split_style = &self.form.meta(id).style;
2832
2833 let partial_node = LayoutNode {
2834 form_node: id,
2835 rect: Rect::new(0.0, y_offset, node_width, partial_height),
2836 name: node.name.clone(),
2837 content: LayoutContent::WrappedText {
2838 lines: top_lines.clone(),
2839 first_line_of_para: vec![false; top_lines.len()],
2840 font_size: node.font.size,
2841 text_align: node.font.text_align,
2842 font_family: node.font.typeface,
2843 space_above_pt: split_style.space_above_pt,
2844 space_below_pt: split_style.space_below_pt,
2845 from_field: matches!(node.node_type, FormNodeType::Field { .. }),
2846 },
2847 children: Vec::new(),
2848 style: self.form.meta(id).style.clone(),
2849 display_items: self.form.meta(id).display_items.clone(),
2850 save_items: self.form.meta(id).save_items.clone(),
2851 };
2852
2853 let rest = if bottom_lines.is_empty() {
2854 Vec::new()
2855 } else {
2856 vec![QueuedNode {
2857 id,
2858 break_before: false,
2859 break_after: self.form.meta(id).page_break_after,
2860 break_target: None,
2861 children_override: None,
2862 text_lines_override: Some(bottom_lines),
2863 nested_child_overrides: None,
2864 }]
2865 };
2866
2867 Ok((partial_node, rest))
2868 }
2869
2870 fn has_inner_break(&self, id: FormNodeId) -> bool {
2874 let node = self.form.get(id);
2875 if !matches!(
2876 node.layout,
2877 LayoutStrategy::TopToBottom
2878 | LayoutStrategy::LeftToRightTB
2879 | LayoutStrategy::RightToLeftTB
2880 ) {
2881 return false;
2882 }
2883 let expanded = self.expand_occur(&node.children);
2884 expanded
2885 .iter()
2886 .any(|&cid| self.form.meta(cid).page_break_before)
2887 }
2888
2889 fn split_tb_node(
2900 &self,
2901 id: FormNodeId,
2902 y_offset: f64,
2903 remaining_height: f64,
2904 _available: Size,
2905 children_override: Option<&[FormNodeId]>,
2906 nested_overrides: Option<&[(FormNodeId, Vec<FormNodeId>)]>,
2907 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
2908 let node = self.form.get(id);
2909
2910 if node.layout == LayoutStrategy::Positioned {
2912 return self.split_positioned_node(id, y_offset, remaining_height, children_override);
2913 }
2914
2915 let node_children = children_override.unwrap_or(&node.children);
2916 let expanded_children = if children_override.is_some() {
2920 node_children.to_vec()
2921 } else {
2922 self.expand_occur(node_children)
2923 };
2924
2925 let mut placed_children = Vec::new();
2926 let mut child_y = 0.0;
2927 let mut split_idx = 0;
2928 let mut last_valid_split = 0;
2930 let mut last_valid_y = 0.0_f64;
2931 let mut split_rest_override: Option<Vec<QueuedNode>> = None;
2932
2933 for (i, &child_id) in expanded_children.iter().enumerate() {
2934 let child = self.form.get(child_id);
2935 let child_co = nested_overrides
2938 .and_then(|no| no.iter().find(|(nid, _)| *nid == child_id))
2939 .map(|(_, co)| co.as_slice());
2940 let child_size = self.compute_extent_with_override(child_id, child_co);
2941 let child_meta = self.form.meta(child_id);
2942
2943 if child_meta.keep_intact_content_area
2946 && child_y + child_size.height > remaining_height
2947 && !placed_children.is_empty()
2948 {
2949 split_idx = i;
2950 break;
2951 }
2952
2953 if child_y + child_size.height > remaining_height
2956 && !child_meta.keep_intact_content_area
2957 && self.is_splittable_text_leaf(child_id)
2958 {
2959 let child_remaining = (remaining_height - child_y).max(0.0);
2960 if child_remaining > 0.0 {
2961 let cnode = self.form.get(child_id);
2962 let txt = match &cnode.node_type {
2963 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
2964 FormNodeType::Field { value } => value.as_str(),
2965 _ => "",
2966 };
2967 let cstyle = &self.form.meta(child_id).style;
2968 let cpara = cstyle
2969 .margin_left_pt
2970 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
2971 + cstyle
2972 .margin_right_pt
2973 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
2974 let cborder_w = cstyle
2975 .border_width_pt
2976 .unwrap_or(cnode.box_model.border_width);
2977 let insets_w = cnode.box_model.margins.horizontal() + cborder_w * 2.0 + cpara;
2978 let max_w = (self.compute_extent(child_id).width - insets_w).max(1.0);
2979 let wrapped = text::wrap_text(
2980 txt,
2981 max_w,
2982 &cnode.font,
2983 cstyle.text_indent_pt.unwrap_or(0.0),
2984 cstyle.line_height_pt,
2985 );
2986 let (partial_child, child_rest) =
2987 self.split_text_node(child_id, child_y, child_remaining, &wrapped.lines)?;
2988
2989 if partial_child.rect.height > 0.0
2990 && partial_child.rect.height <= child_remaining + 0.5
2991 {
2992 placed_children.push(partial_child);
2993 child_y += placed_children
2995 .last()
2996 .expect("just pushed above")
2997 .rect
2998 .height;
2999 split_idx = i + 1;
3000
3001 let mut rest: Vec<QueuedNode> = child_rest;
3002 rest.extend(expanded_children[i + 1..].iter().map(|&cid| QueuedNode {
3003 id: cid,
3004 break_before: self.form.meta(cid).page_break_before,
3005 break_after: self.form.meta(cid).page_break_after,
3006 break_target: None,
3007 children_override: None,
3008 text_lines_override: None,
3009 nested_child_overrides: None,
3010 }));
3011 split_rest_override = Some(rest);
3012 break;
3013 }
3014 }
3015 }
3016
3017 if child_y + child_size.height > remaining_height
3021 && !child_meta.keep_intact_content_area
3022 && self.can_split(child_id)
3023 {
3024 let child_remaining = (remaining_height - child_y).max(0.0);
3025 if child_remaining > 0.0 {
3026 let child_available = Size {
3027 width: child.box_model.content_width().min(child_size.width),
3028 height: child.box_model.content_height().min(child_size.height),
3029 };
3030 let (partial_child, child_rest) = self.split_tb_node(
3031 child_id,
3032 child_y,
3033 child_remaining,
3034 child_available,
3035 child_co,
3036 nested_overrides,
3037 )?;
3038
3039 let partial_fits = partial_child.rect.height <= child_remaining + 1.0;
3040 let split_productive = !partial_child.children.is_empty()
3041 && (partial_fits || partial_child.children.len() > 1);
3042
3043 if split_productive {
3044 placed_children.push(partial_child);
3045 child_y += placed_children
3047 .last()
3048 .expect("just pushed above")
3049 .rect
3050 .height;
3051 split_idx = i + 1;
3052
3053 let (child_override, child_nested) = if child_rest.len() == 1
3069 && child_rest[0].id == child_id
3070 && child_rest[0].children_override.is_some()
3071 {
3072 let qn = child_rest
3074 .into_iter()
3075 .next()
3076 .expect("child_rest.len() == 1");
3077 (qn.children_override, qn.nested_child_overrides)
3078 } else {
3079 let mut ids = Vec::new();
3080 let mut nested = Vec::new();
3081 for qn in child_rest {
3082 ids.push(qn.id);
3083 if let Some(co) = qn.children_override {
3084 nested.push((qn.id, co));
3085 }
3086 if let Some(nco) = qn.nested_child_overrides {
3087 nested.extend(nco);
3088 }
3089 }
3090 (
3091 Some(ids),
3092 if nested.is_empty() {
3093 None
3094 } else {
3095 Some(nested)
3096 },
3097 )
3098 };
3099
3100 let mut rest = vec![QueuedNode {
3101 id: child_id,
3102 break_before: false,
3103 break_after: self.form.meta(child_id).page_break_after,
3104 break_target: None,
3105 children_override: child_override,
3106 text_lines_override: None,
3107 nested_child_overrides: child_nested,
3108 }];
3109 rest.extend(expanded_children[i + 1..].iter().map(|&cid| QueuedNode {
3110 id: cid,
3111 break_before: self.form.meta(cid).page_break_before,
3112 break_after: self.form.meta(cid).page_break_after,
3113 break_target: None,
3114 children_override: None,
3115 text_lines_override: None,
3116 nested_child_overrides: None,
3117 }));
3118 split_rest_override = Some(rest);
3119 break;
3120 }
3121 }
3122 }
3123
3124 if child_y + child_size.height > remaining_height + 0.5 && !placed_children.is_empty() {
3127 if last_valid_split > 0 && last_valid_split < placed_children.len() {
3129 placed_children.truncate(last_valid_split);
3131 child_y = last_valid_y;
3132 split_idx = last_valid_split;
3133 } else {
3134 split_idx = i;
3135 }
3136 break;
3137 }
3138
3139 let child_node = self.layout_single_node(child_id, child, 0.0, child_y, child_co)?;
3140 placed_children.push(child_node);
3141 child_y += child_size.height;
3142 split_idx = i + 1;
3143
3144 let has_keep = child_meta.keep_next_content_area;
3146 let next_has_keep_prev = if i + 1 < expanded_children.len() {
3147 self.form
3148 .meta(expanded_children[i + 1])
3149 .keep_previous_content_area
3150 } else {
3151 false
3152 };
3153 if !has_keep && !next_has_keep_prev {
3154 last_valid_split = split_idx;
3155 last_valid_y = child_y;
3156 }
3157 }
3158
3159 let content = match &node.node_type {
3160 FormNodeType::Field { value } => {
3161 let meta = self.form.meta(id);
3162 LayoutContent::Field {
3163 value: resolve_display_value(value, meta).to_string(),
3164 field_kind: meta.field_kind,
3165 font_size: node.font.size,
3166 font_family: node.font.typeface,
3167 }
3168 }
3169 FormNodeType::Draw(DrawContent::Text(content)) => LayoutContent::Text(content.clone()),
3170 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
3171 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
3172 data: data.clone(),
3173 mime_type: mime_type.clone(),
3174 },
3175 _ => LayoutContent::None,
3176 };
3177
3178 let partial_width = self
3180 .compute_extent_with_override(id, children_override)
3181 .width;
3182
3183 let partial_node = LayoutNode {
3184 form_node: id,
3185 rect: Rect::new(0.0, y_offset, partial_width, child_y),
3186 name: node.name.clone(),
3187 content,
3188 children: placed_children,
3189 style: self.form.meta(id).style.clone(),
3190 display_items: self.form.meta(id).display_items.clone(),
3191 save_items: self.form.meta(id).save_items.clone(),
3192 };
3193
3194 let rest = split_rest_override.unwrap_or_else(|| {
3195 expanded_children[split_idx..]
3196 .iter()
3197 .map(|&cid| QueuedNode {
3198 id: cid,
3199 break_before: self.form.meta(cid).page_break_before,
3200 break_after: self.form.meta(cid).page_break_after,
3201 break_target: None,
3202 children_override: None,
3203 text_lines_override: None,
3204 nested_child_overrides: None,
3205 })
3206 .collect()
3207 });
3208 Ok((partial_node, rest))
3209 }
3210
3211 fn split_positioned_node(
3224 &self,
3225 id: FormNodeId,
3226 y_offset: f64,
3227 remaining_height: f64,
3228 children_override: Option<&[FormNodeId]>,
3229 ) -> Result<(LayoutNode, Vec<QueuedNode>)> {
3230 let node = self.form.get(id);
3231 let node_children = children_override.unwrap_or(&node.children);
3232 let expanded_children = if children_override.is_some() {
3235 node_children.to_vec()
3236 } else {
3237 self.expand_occur(node_children)
3238 };
3239
3240 let mut sorted: Vec<FormNodeId> = expanded_children.clone();
3242 sorted.sort_by(|&a, &b| {
3243 let ay = self.form.get(a).box_model.y;
3244 let by = self.form.get(b).box_model.y;
3245 ay.partial_cmp(&by).unwrap_or(std::cmp::Ordering::Equal)
3246 });
3247
3248 let y_base = if children_override.is_some() {
3252 sorted
3253 .first()
3254 .map(|&cid| self.form.get(cid).box_model.y)
3255 .unwrap_or(0.0)
3256 } else {
3257 0.0
3258 };
3259 let parent_margin_x = node.box_model.margins.left;
3260 let parent_margin_y = node.box_model.margins.top;
3261
3262 let mut placed_children = Vec::new();
3263 let mut rest_children = Vec::new();
3264 let mut max_placed_bottom = 0.0_f64;
3265
3266 for &child_id in &sorted {
3267 let child = self.form.get(child_id);
3268 let child_size = self.compute_extent(child_id);
3269 let shifted_y = child.box_model.y - y_base + parent_margin_y;
3270 let child_bottom = shifted_y + child_size.height;
3271
3272 if child_bottom <= remaining_height + 1.0 {
3273 let child_node = self.layout_single_node(
3275 child_id,
3276 child,
3277 child.box_model.x + parent_margin_x,
3278 shifted_y,
3279 None,
3280 )?;
3281 max_placed_bottom = max_placed_bottom.max(child_bottom);
3282 placed_children.push(child_node);
3283 } else {
3284 rest_children.push(child_id);
3286 }
3287 }
3288
3289 if placed_children.is_empty() && !sorted.is_empty() {
3295 let first_id = sorted[0];
3296 let first = self.form.get(first_id);
3297 let first_size = self.compute_extent(first_id);
3298 let shifted_y = first.box_model.y - y_base + parent_margin_y;
3299 let child_node = self.layout_single_node(
3300 first_id,
3301 first,
3302 first.box_model.x + parent_margin_x,
3303 shifted_y,
3304 None,
3305 )?;
3306 max_placed_bottom = shifted_y + first_size.height;
3307 placed_children.push(child_node);
3308 rest_children.retain(|&cid| cid != first_id);
3310 }
3311
3312 let content = match &node.node_type {
3313 FormNodeType::Field { value } => {
3314 let meta = self.form.meta(id);
3315 LayoutContent::Field {
3316 value: resolve_display_value(value, meta).to_string(),
3317 field_kind: meta.field_kind,
3318 font_size: node.font.size,
3319 font_family: node.font.typeface,
3320 }
3321 }
3322 FormNodeType::Draw(DrawContent::Text(content)) => LayoutContent::Text(content.clone()),
3323 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
3324 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
3325 data: data.clone(),
3326 mime_type: mime_type.clone(),
3327 },
3328 _ => LayoutContent::None,
3329 };
3330
3331 let partial_width = self
3332 .compute_extent_with_override(id, children_override)
3333 .width;
3334
3335 let partial_node = LayoutNode {
3336 form_node: id,
3337 rect: Rect::new(0.0, y_offset, partial_width, max_placed_bottom),
3338 name: node.name.clone(),
3339 content,
3340 children: placed_children,
3341 style: self.form.meta(id).style.clone(),
3342 display_items: self.form.meta(id).display_items.clone(),
3343 save_items: self.form.meta(id).save_items.clone(),
3344 };
3345
3346 let rest = if rest_children.is_empty() {
3352 Vec::new()
3353 } else {
3354 vec![QueuedNode {
3355 id,
3356 break_before: false,
3357 break_after: self.form.meta(id).page_break_after,
3358 break_target: None,
3359 children_override: Some(rest_children),
3360 text_lines_override: None,
3361 nested_child_overrides: None,
3362 }]
3363 };
3364
3365 Ok((partial_node, rest))
3366 }
3367
3368 fn layout_children(
3382 &self,
3383 children: &[FormNodeId],
3384 available: Size,
3385 strategy: LayoutStrategy,
3386 ) -> Result<Vec<LayoutNode>> {
3387 let expanded = self.expand_occur(children);
3388 match strategy {
3389 LayoutStrategy::Positioned => self.layout_positioned(&expanded),
3390 LayoutStrategy::TopToBottom => self.layout_tb(&expanded, available),
3391 LayoutStrategy::LeftToRightTB => self.layout_lr_tb(&expanded, available),
3392 LayoutStrategy::RightToLeftTB => self.layout_rl_tb(&expanded, available),
3393 LayoutStrategy::Table => self.layout_table(&expanded, available),
3394 LayoutStrategy::Row => self.layout_row(&expanded, available),
3395 }
3396 }
3397
3398 fn expand_occur(&self, children: &[FormNodeId]) -> Vec<FormNodeId> {
3411 let mut expanded = Vec::new();
3412 for &child_id in children {
3413 if self.is_layout_hidden(child_id) {
3414 continue;
3415 }
3416 let child = self.form.get(child_id);
3417 let count = if child.occur.is_repeating()
3419 && child.occur.count() > child.occur.min
3420 && self.has_field_descendants(child_id)
3421 && self.subtree_is_blank(child_id)
3422 {
3423 child.occur.min
3424 } else {
3425 child.occur.count()
3426 };
3427 let count = if count == 0
3431 && matches!(
3432 child.node_type,
3433 FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
3434 )
3435 && self.has_field_descendants(child_id)
3436 {
3437 1
3438 } else {
3439 count
3440 };
3441 for _ in 0..count {
3442 expanded.push(child_id);
3443 }
3444 }
3445 expanded
3446 }
3447
3448 fn has_field_descendants(&self, id: FormNodeId) -> bool {
3450 let node = self.form.get(id);
3451 match &node.node_type {
3452 FormNodeType::Field { .. } | FormNodeType::Draw(..) | FormNodeType::Image { .. } => {
3453 true
3454 }
3455 FormNodeType::Subform
3457 | FormNodeType::Area
3458 | FormNodeType::ExclGroup
3459 | FormNodeType::SubformSet => {
3460 node.children.iter().any(|&c| self.has_field_descendants(c))
3461 }
3462 _ => false,
3463 }
3464 }
3465
3466 fn layout_positioned(&self, children: &[FormNodeId]) -> Result<Vec<LayoutNode>> {
3474 use crate::form::AnchorType;
3475
3476 let mut nodes = Vec::new();
3477 for &child_id in children {
3478 let child = self.form.get(child_id);
3479 let anchor = self.form.meta(child_id).anchor_type;
3480
3481 let extent = self.compute_extent(child_id);
3483 let w = extent.width;
3484 let h = extent.height;
3485
3486 let (dx, dy) = match anchor {
3487 AnchorType::TopLeft => (0.0, 0.0),
3488 AnchorType::TopCenter => (-w / 2.0, 0.0),
3489 AnchorType::TopRight => (-w, 0.0),
3490 AnchorType::MiddleLeft => (0.0, -h / 2.0),
3491 AnchorType::MiddleCenter => (-w / 2.0, -h / 2.0),
3492 AnchorType::MiddleRight => (-w, -h / 2.0),
3493 AnchorType::BottomLeft => (0.0, -h),
3494 AnchorType::BottomCenter => (-w / 2.0, -h),
3495 AnchorType::BottomRight => (-w, -h),
3496 };
3497
3498 let node = self.layout_single_node_with_extent(
3499 child_id,
3500 child,
3501 child.box_model.x + dx,
3502 child.box_model.y + dy,
3503 extent,
3504 None,
3505 )?;
3506 nodes.push(node);
3507 }
3508 Ok(nodes)
3509 }
3510
3511 fn child_h_align_offset(&self, child_id: FormNodeId, child_w: f64, parent_w: f64) -> f64 {
3514 match self.form.meta(child_id).style.h_align {
3515 Some(TextAlign::Center) => ((parent_w - child_w) / 2.0).max(0.0),
3516 Some(TextAlign::Right) => (parent_w - child_w).max(0.0),
3517 _ => 0.0,
3518 }
3519 }
3520
3521 fn shift_row_h_align(&self, row: &mut [LayoutNode], row_width: f64, parent_width: f64) {
3525 let align = row
3526 .first()
3527 .and_then(|n| self.form.meta(n.form_node).style.h_align);
3528 let offset = match align {
3529 Some(TextAlign::Center) => ((parent_width - row_width) / 2.0).max(0.0),
3530 Some(TextAlign::Right) => (parent_width - row_width).max(0.0),
3531 _ => return,
3532 };
3533 for node in row {
3534 node.rect.x += offset;
3535 }
3536 }
3537
3538 fn layout_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3541 let mut nodes = Vec::new();
3542 let mut y_cursor = 0.0;
3543
3544 for &child_id in children {
3545 let child = self.form.get(child_id);
3546 let child_size = self.compute_extent_with_available(child_id, Some(available));
3547 let child_bottom = y_cursor + child_size.height;
3548
3549 if child_bottom > available.height + 0.5 {
3553 let remaining_height = (available.height - y_cursor).max(0.0);
3554
3555 if remaining_height > 0.0 && self.is_splittable_text_leaf(child_id) {
3560 let txt = match &child.node_type {
3561 FormNodeType::Draw(DrawContent::Text(t)) => t.as_str(),
3562 FormNodeType::Field { value } => value.as_str(),
3563 _ => "",
3564 };
3565 let child_style = &self.form.meta(child_id).style;
3566 let para_margins = child_style
3567 .margin_left_pt
3568 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
3569 + child_style
3570 .margin_right_pt
3571 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
3572 let child_border_w = child_style
3573 .border_width_pt
3574 .unwrap_or(child.box_model.border_width);
3575 let insets_w =
3576 child.box_model.margins.horizontal() + child_border_w * 2.0 + para_margins;
3577 let max_w = (child_size.width - insets_w).max(1.0);
3578 let wrapped = text::wrap_text(
3579 txt,
3580 max_w,
3581 &child.font,
3582 child_style.text_indent_pt.unwrap_or(0.0),
3583 child_style.line_height_pt,
3584 );
3585 let (partial, _) =
3586 self.split_text_node(child_id, y_cursor, remaining_height, &wrapped.lines)?;
3587
3588 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
3589 let x = self.child_h_align_offset(
3590 child_id,
3591 partial.rect.width,
3592 available.width,
3593 );
3594 let mut partial = partial;
3595 partial.rect.x = x;
3596 nodes.push(partial);
3597 } else if nodes.is_empty() {
3598 let x =
3600 self.child_h_align_offset(child_id, child_size.width, available.width);
3601 let node = self.layout_single_node_with_extent(
3602 child_id, child, x, y_cursor, child_size, None,
3603 )?;
3604 nodes.push(node);
3605 }
3606 } else if remaining_height > 0.0 && self.can_split(child_id) {
3607 let (partial, _) = self.split_tb_node(
3608 child_id,
3609 y_cursor,
3610 remaining_height,
3611 available,
3612 None,
3613 None,
3614 )?;
3615 if partial.rect.height > 0.0 && partial.rect.height <= remaining_height + 1.0 {
3616 let x = self.child_h_align_offset(
3617 child_id,
3618 partial.rect.width,
3619 available.width,
3620 );
3621 let mut partial = partial;
3622 partial.rect.x = x;
3623 nodes.push(partial);
3624 } else if nodes.is_empty() {
3625 let x =
3627 self.child_h_align_offset(child_id, child_size.width, available.width);
3628 let node = self.layout_single_node_with_extent(
3629 child_id, child, x, y_cursor, child_size, None,
3630 )?;
3631 nodes.push(node);
3632 }
3633 } else if nodes.is_empty() {
3634 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
3636 let node = self.layout_single_node_with_extent(
3637 child_id, child, x, y_cursor, child_size, None,
3638 )?;
3639 nodes.push(node);
3640 }
3641
3642 break;
3643 }
3644
3645 let x = self.child_h_align_offset(child_id, child_size.width, available.width);
3646
3647 let node = self
3648 .layout_single_node_with_extent(child_id, child, x, y_cursor, child_size, None)?;
3649 nodes.push(node);
3650
3651 y_cursor = child_bottom;
3652 }
3653 Ok(nodes)
3654 }
3655
3656 fn layout_lr_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3660 let mut nodes = Vec::new();
3661 let mut x_cursor = 0.0;
3662 let mut y_cursor = 0.0;
3663 let mut row_height = 0.0_f64;
3664 let mut row_start = 0_usize;
3665
3666 for &child_id in children {
3667 let child = self.form.get(child_id);
3668 let child_size = self.compute_extent(child_id);
3669
3670 if x_cursor + child_size.width > available.width && x_cursor > 0.0 {
3672 self.shift_row_h_align(&mut nodes[row_start..], x_cursor, available.width);
3673 row_start = nodes.len();
3674 y_cursor += row_height;
3675 x_cursor = 0.0;
3676 row_height = 0.0;
3677 }
3678
3679 let node = self.layout_single_node(child_id, child, x_cursor, y_cursor, None)?;
3680 nodes.push(node);
3681
3682 x_cursor += child_size.width;
3683 row_height = row_height.max(child_size.height);
3684 }
3685 self.shift_row_h_align(&mut nodes[row_start..], x_cursor, available.width);
3686 Ok(nodes)
3687 }
3688
3689 fn layout_rl_tb(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3693 let mut nodes = Vec::new();
3694 let mut x_cursor = available.width;
3695 let mut y_cursor = 0.0;
3696 let mut row_height = 0.0_f64;
3697
3698 for &child_id in children {
3699 let child = self.form.get(child_id);
3700 let child_size = self.compute_extent(child_id);
3701
3702 if x_cursor - child_size.width < 0.0 && x_cursor < available.width {
3704 y_cursor += row_height;
3705 x_cursor = available.width;
3706 row_height = 0.0;
3707 }
3708
3709 let h_align = self.form.meta(child_id).style.h_align;
3711 let x = match h_align {
3712 Some(TextAlign::Left) => {
3713 x_cursor -= child_size.width;
3714 0.0
3715 }
3716 Some(TextAlign::Center) => {
3717 x_cursor -= child_size.width;
3718 ((available.width - child_size.width) / 2.0).max(0.0)
3719 }
3720 _ => {
3721 x_cursor -= child_size.width;
3722 x_cursor
3723 }
3724 };
3725 let node = self.layout_single_node(child_id, child, x, y_cursor, None)?;
3726 nodes.push(node);
3727
3728 row_height = row_height.max(child_size.height);
3729 }
3730 Ok(nodes)
3731 }
3732
3733 fn layout_table(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3738 let max_cells = children
3740 .iter()
3741 .map(|&row_id| self.form.get(row_id).children.len())
3742 .max()
3743 .unwrap_or(0);
3744 if max_cells == 0 {
3745 return Ok(Vec::new());
3746 }
3747 let col_width = available.width / max_cells as f64;
3748 let col_widths: Vec<f64> = vec![col_width; max_cells];
3749 self.layout_table_rows(children, available, &col_widths)
3750 }
3751
3752 fn layout_table_rows(
3759 &self,
3760 children: &[FormNodeId],
3761 available: Size,
3762 col_widths: &[f64],
3763 ) -> Result<Vec<LayoutNode>> {
3764 let expanded = self.expand_occur(children);
3765 let mut nodes = Vec::new();
3766 let mut y_cursor = 0.0;
3767
3768 for &row_id in &expanded {
3769 let row_node = self.form.get(row_id);
3770 let row_children = self.expand_occur(&row_node.children);
3771
3772 let mut cells = Vec::new();
3774 let mut x_cursor = 0.0;
3775 let mut col_idx = 0usize;
3776 let mut max_cell_height = 0.0_f64;
3777
3778 for &cell_id in &row_children {
3779 if col_idx >= col_widths.len() {
3780 break;
3781 }
3782 let cell = self.form.get(cell_id);
3783 let span = cell.col_span;
3784
3785 let cell_width = if span == -1 {
3787 col_widths[col_idx..].iter().sum::<f64>()
3789 } else {
3790 let span_count =
3791 (span.max(1) as usize).min(col_widths.len().saturating_sub(col_idx));
3792 col_widths[col_idx..col_idx + span_count]
3793 .iter()
3794 .sum::<f64>()
3795 };
3796
3797 let cell_available = Size {
3799 width: cell_width,
3800 height: available.height - y_cursor,
3801 };
3802 let cell_height = self
3803 .compute_extent_with_available(cell_id, Some(cell_available))
3804 .height;
3805 let cell_extent = Size {
3806 width: cell_width,
3807 height: cell_height,
3808 };
3809
3810 let cell_node = self.layout_single_node_with_extent(
3811 cell_id,
3812 cell,
3813 x_cursor,
3814 0.0,
3815 cell_extent,
3816 None,
3817 )?;
3818 max_cell_height = max_cell_height.max(cell_extent.height);
3819 cells.push(cell_node);
3820
3821 x_cursor += cell_width;
3822 if span == -1 {
3823 col_idx = col_widths.len();
3824 } else {
3825 col_idx += span.max(1) as usize;
3826 }
3827 }
3828
3829 for cell in &mut cells {
3831 cell.rect.height = max_cell_height;
3832 }
3833
3834 let row_layout = LayoutNode {
3836 form_node: row_id,
3837 rect: Rect::new(0.0, y_cursor, available.width, max_cell_height),
3838 name: row_node.name.clone(),
3839 content: LayoutContent::None,
3840 children: cells,
3841 style: self.form.meta(row_id).style.clone(),
3842 display_items: self.form.meta(row_id).display_items.clone(),
3843 save_items: self.form.meta(row_id).save_items.clone(),
3844 };
3845 nodes.push(row_layout);
3846
3847 y_cursor += max_cell_height;
3848 }
3849 Ok(nodes)
3850 }
3851
3852 fn resolve_column_widths_with_override(
3853 &self,
3854 table_node: &FormNode,
3855 available_width: f64,
3856 children_override: Option<&[FormNodeId]>,
3857 ) -> Vec<f64> {
3858 let specified = &table_node.column_widths;
3859 let node_children = children_override.unwrap_or(&table_node.children);
3860
3861 let max_cols_from_rows = node_children
3863 .iter()
3864 .map(|&row_id| {
3865 let row = self.form.get(row_id);
3866 row.children
3867 .iter()
3868 .map(|&cell_id| {
3869 let cell = self.form.get(cell_id);
3870 cell.col_span.max(1) as usize
3871 })
3872 .sum::<usize>()
3873 })
3874 .max()
3875 .unwrap_or(0);
3876
3877 let num_cols = specified.len().max(max_cols_from_rows);
3878 if num_cols == 0 {
3879 return vec![];
3880 }
3881
3882 let mut widths = Vec::with_capacity(num_cols);
3883 for i in 0..num_cols {
3884 let spec_value = specified.get(i).copied().unwrap_or(-1.0);
3885 if spec_value >= 0.0 {
3886 widths.push(spec_value);
3887 } else {
3888 let mut max_w = 0.0_f64;
3890 for &row_id in node_children {
3891 let row = self.form.get(row_id);
3892 let mut col_idx = 0usize;
3893 for &cell_id in &row.children {
3894 let cell = self.form.get(cell_id);
3895 let span = cell.col_span;
3896 if col_idx == i && span == 1 {
3897 let cell_extent = self.compute_extent(cell_id);
3898 max_w = max_w.max(cell_extent.width);
3899 }
3900 col_idx += span.max(1) as usize;
3901 }
3902 }
3903 widths.push(max_w);
3904 }
3905 }
3906
3907 let total: f64 = widths.iter().sum();
3909 if total > available_width && total > 0.0 {
3910 let scale = available_width / total;
3911 for w in &mut widths {
3912 *w *= scale;
3913 }
3914 }
3915
3916 widths
3917 }
3918
3919 fn layout_row(&self, children: &[FormNodeId], available: Size) -> Result<Vec<LayoutNode>> {
3922 let mut nodes = Vec::new();
3923 let mut x_cursor = 0.0;
3924
3925 for &child_id in children {
3926 let child = self.form.get(child_id);
3927 let child_size = self.compute_extent(child_id);
3928
3929 let node = self.layout_single_node(child_id, child, x_cursor, 0.0, None)?;
3930 nodes.push(node);
3931
3932 x_cursor += child_size.width;
3933
3934 if x_cursor > available.width {
3935 break;
3936 }
3937 }
3938 Ok(nodes)
3939 }
3940
3941 fn layout_single_node(
3943 &self,
3944 id: FormNodeId,
3945 node: &FormNode,
3946 x: f64,
3947 y: f64,
3948 children_override: Option<&[FormNodeId]>,
3949 ) -> Result<LayoutNode> {
3950 let extent = self.compute_extent_with_override(id, children_override);
3951 self.layout_single_node_with_extent(id, node, x, y, extent, children_override)
3952 }
3953
3954 fn layout_single_node_with_extent(
3956 &self,
3957 id: FormNodeId,
3958 node: &FormNode,
3959 x: f64,
3960 y: f64,
3961 extent: Size,
3962 children_override: Option<&[FormNodeId]>,
3963 ) -> Result<LayoutNode> {
3964 if self.form.meta(id).presence.is_layout_hidden() {
3968 let hidden_meta = self.form.meta(id);
3969 return Ok(LayoutNode {
3970 form_node: id,
3971 rect: Rect::new(x, y, extent.width, extent.height),
3972 name: node.name.clone(),
3973 content: LayoutContent::None,
3974 children: Vec::new(),
3975 style: Default::default(),
3976 display_items: hidden_meta.display_items.clone(),
3977 save_items: hidden_meta.save_items.clone(),
3978 });
3979 }
3980
3981 let node_style = &self.form.meta(id).style;
3982 let para_margins = node_style
3983 .margin_left_pt
3984 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
3985 + node_style
3986 .margin_right_pt
3987 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
3988
3989 let content = match &node.node_type {
3990 FormNodeType::Field { value } => {
3991 let meta = self.form.meta(id);
3992 let display_val = resolve_display_value(value, meta);
3993 if !display_val.is_empty() && node.children.is_empty() {
3994 let border_w = node_style
3995 .border_width_pt
3996 .unwrap_or(node.box_model.border_width);
3997 let insets_w =
3998 node.box_model.margins.horizontal() + border_w * 2.0 + para_margins;
3999 let max_w = (extent.width - insets_w).max(0.0);
4000 let wrapped = text::wrap_text(
4001 &display_val,
4002 max_w,
4003 &node.font,
4004 node_style.text_indent_pt.unwrap_or(0.0),
4005 node_style.line_height_pt,
4006 );
4007 LayoutContent::WrappedText {
4008 lines: wrapped.lines,
4009 first_line_of_para: wrapped.first_line_of_para,
4010 font_size: node.font.size,
4011 text_align: node.font.text_align,
4012 font_family: node.font.typeface,
4013 space_above_pt: node_style.space_above_pt,
4014 space_below_pt: node_style.space_below_pt,
4015 from_field: true,
4016 }
4017 } else {
4018 LayoutContent::Field {
4019 value: display_val.to_string(),
4020 field_kind: meta.field_kind,
4021 font_size: node.font.size,
4022 font_family: node.font.typeface,
4023 }
4024 }
4025 }
4026 FormNodeType::Draw(DrawContent::Text(content)) => {
4027 if !content.is_empty() && node.children.is_empty() {
4028 let border_w = node_style
4029 .border_width_pt
4030 .unwrap_or(node.box_model.border_width);
4031 let insets_w =
4032 node.box_model.margins.horizontal() + border_w * 2.0 + para_margins;
4033 let max_w = (extent.width - insets_w).max(0.0);
4034 let wrapped = text::wrap_text(
4035 content,
4036 max_w,
4037 &node.font,
4038 node_style.text_indent_pt.unwrap_or(0.0),
4039 node_style.line_height_pt,
4040 );
4041 LayoutContent::WrappedText {
4042 lines: wrapped.lines,
4043 first_line_of_para: wrapped.first_line_of_para,
4044 font_size: node.font.size,
4045 text_align: node.font.text_align,
4046 font_family: node.font.typeface,
4047 space_above_pt: node_style.space_above_pt,
4048 space_below_pt: node_style.space_below_pt,
4049 from_field: false,
4050 }
4051 } else {
4052 LayoutContent::Text(content.clone())
4053 }
4054 }
4055 FormNodeType::Draw(dc) => LayoutContent::Draw(dc.clone()),
4056 FormNodeType::Image { data, mime_type } => LayoutContent::Image {
4057 data: data.clone(),
4058 mime_type: mime_type.clone(),
4059 },
4060 _ => LayoutContent::None,
4061 };
4062
4063 let child_available = Size {
4064 width: node.box_model.content_width().min(extent.width),
4065 height: node.box_model.content_height().min(extent.height),
4066 };
4067
4068 let node_children = children_override.unwrap_or(&node.children);
4069
4070 let children = if node_children.is_empty() {
4071 Vec::new()
4072 } else if node.layout == LayoutStrategy::Table {
4073 let col_widths = self.resolve_column_widths_with_override(
4076 node,
4077 child_available.width,
4078 children_override,
4079 );
4080 self.layout_table_rows(node_children, child_available, &col_widths)?
4081 } else {
4082 self.layout_children(node_children, child_available, node.layout)?
4083 };
4084 let mut children = children;
4085
4086 if node.layout == LayoutStrategy::Positioned {
4087 let dx = node.box_model.margins.left;
4088 let mut dy = node.box_model.margins.top;
4089 if let Some(override_children) = children_override {
4090 if let Some(y_base) = override_children
4091 .iter()
4092 .map(|&cid| self.form.get(cid).box_model.y)
4093 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
4094 {
4095 dy -= y_base;
4096 }
4097 }
4098 if dx != 0.0 || dy != 0.0 {
4099 for child in &mut children {
4100 child.rect.x += dx;
4101 child.rect.y += dy;
4102 }
4103 }
4104 }
4105
4106 Ok(LayoutNode {
4107 form_node: id,
4108 rect: Rect::new(x, y, extent.width, extent.height),
4109 name: node.name.clone(),
4110 content,
4111 children,
4112 style: self.form.meta(id).style.clone(),
4113 display_items: self.form.meta(id).display_items.clone(),
4114 save_items: self.form.meta(id).save_items.clone(),
4115 })
4116 }
4117
4118 pub fn compute_extent(&self, id: FormNodeId) -> Size {
4123 self.compute_extent_with_available(id, None)
4124 }
4125
4126 pub fn compute_extent_with_override(
4128 &self,
4129 id: FormNodeId,
4130 children_override: Option<&[FormNodeId]>,
4131 ) -> Size {
4132 self.compute_extent_with_available_and_override(id, None, children_override)
4133 }
4134
4135 fn compute_extent_with_available(&self, id: FormNodeId, available: Option<Size>) -> Size {
4142 self.compute_extent_with_available_and_override(id, available, None)
4143 }
4144
4145 fn compute_extent_with_available_and_override(
4146 &self,
4147 id: FormNodeId,
4148 available: Option<Size>,
4149 children_override: Option<&[FormNodeId]>,
4150 ) -> Size {
4151 let node = self.form.get(id);
4152 let bm = &node.box_model;
4153 let ext_style = &self.form.meta(id).style;
4154
4155 let is_tb_with_children = node.layout == LayoutStrategy::TopToBottom
4159 && !children_override.unwrap_or(&node.children).is_empty();
4160 if let (Some(w), Some(h)) = (bm.width, bm.height) {
4161 if !is_tb_with_children {
4162 return Size {
4163 width: w,
4164 height: h,
4165 };
4166 }
4167 }
4168
4169 let mut content_size = Size::default();
4171
4172 let node_children = children_override.unwrap_or(&node.children);
4173
4174 if !node_children.is_empty() {
4175 let expanded = if children_override.is_some() {
4179 node_children.to_vec()
4180 } else {
4181 self.expand_occur(node_children)
4182 };
4183 match node.layout {
4184 LayoutStrategy::TopToBottom => {
4185 for &child_id in &expanded {
4187 let cs = self.compute_extent_with_available(child_id, available);
4188 content_size.width = content_size.width.max(cs.width);
4189 content_size.height += cs.height;
4190 }
4191 }
4192 LayoutStrategy::LeftToRightTB | LayoutStrategy::Row => {
4193 for &child_id in &expanded {
4194 let cs = self.compute_extent_with_available(child_id, available);
4195 content_size.width += cs.width;
4196 content_size.height = content_size.height.max(cs.height);
4197 }
4198 }
4199 LayoutStrategy::Table => {
4200 let avail_w = available.map(|a| a.width).unwrap_or(f64::MAX);
4201 let col_widths =
4202 self.resolve_column_widths_with_override(node, avail_w, children_override);
4203 let table_width: f64 = col_widths.iter().sum();
4204 content_size.width = content_size.width.max(table_width);
4205 for &row_id in &expanded {
4207 let row_extent = self.compute_extent_with_available(row_id, available);
4208 content_size.height += row_extent.height;
4209 }
4210 }
4211 _ => {
4212 let y_base = if children_override.is_some() {
4218 node_children
4219 .iter()
4220 .map(|&cid| self.form.get(cid).box_model.y)
4221 .fold(f64::MAX, f64::min)
4222 } else {
4223 0.0
4224 };
4225 for &child_id in node_children {
4226 let child = self.form.get(child_id);
4227 let cs = self.compute_extent(child_id);
4228 content_size.width = content_size.width.max(child.box_model.x + cs.width);
4229 content_size.height = content_size
4230 .height
4231 .max(child.box_model.y - y_base + cs.height);
4232 }
4233 }
4234 }
4235 } else {
4236 let text_content = match &node.node_type {
4238 FormNodeType::Draw(DrawContent::Text(content)) => Some(content.as_str()),
4239 FormNodeType::Field { value } => Some(value.as_str()),
4240 _ => None,
4241 };
4242
4243 if let Some(txt) = text_content {
4244 if !txt.is_empty() {
4245 let ext_style = &self.form.meta(id).style;
4246 let ext_para = ext_style
4247 .margin_left_pt
4248 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING)
4249 + ext_style
4250 .margin_right_pt
4251 .unwrap_or(crate::types::DEFAULT_TEXT_PADDING);
4252 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
4253 let insets_w = bm.margins.horizontal() + border_w * 2.0 + ext_para;
4254 let space_above = ext_style.space_above_pt.unwrap_or(0.0);
4255 let space_below = ext_style.space_below_pt.unwrap_or(0.0);
4256 let text_size = if let Some(w) = bm.width {
4259 let max_text_width = (w - insets_w).max(0.0);
4260 text::wrap_text(
4261 txt,
4262 max_text_width,
4263 &node.font,
4264 ext_style.text_indent_pt.unwrap_or(0.0),
4265 ext_style.line_height_pt,
4266 )
4267 .size
4268 } else if let Some(avail) = available {
4269 let max_text_width = (avail.width - insets_w).max(0.0);
4270 text::wrap_text(
4271 txt,
4272 max_text_width,
4273 &node.font,
4274 ext_style.text_indent_pt.unwrap_or(0.0),
4275 ext_style.line_height_pt,
4276 )
4277 .size
4278 } else {
4279 text::measure_text(txt, &node.font)
4280 };
4281 content_size.width = content_size.width.max(text_size.width);
4282 content_size.height = content_size
4283 .height
4284 .max(text_size.height + space_above + space_below);
4285 }
4286 }
4287 }
4288
4289 if let Some(avail) = available {
4291 if bm.width.is_none() {
4292 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
4293 let insets_w = bm.margins.horizontal() + border_w * 2.0;
4294 content_size.width = content_size.width.max(avail.width - insets_w);
4295 }
4296 }
4297
4298 let mut result = bm.outer_size(content_size);
4299
4300 if is_tb_with_children && bm.height.is_some() {
4306 let border_w = ext_style.border_width_pt.unwrap_or(bm.border_width);
4307 let mut unclamped_h = content_size.height + bm.margins.vertical() + border_w * 2.0;
4308 if let Some(ref cap) = bm.caption {
4309 if matches!(
4310 cap.placement,
4311 crate::types::CaptionPlacement::Top | crate::types::CaptionPlacement::Bottom
4312 ) {
4313 unclamped_h += cap.reserve.unwrap_or(0.0);
4314 }
4315 }
4316 result.height = result.height.max(unclamped_h);
4317 }
4318
4319 result
4320 }
4321}
4322
4323#[allow(dead_code)]
4326fn find_page_area_by_target(page_areas: &[PageAreaInfo], target: &str) -> Option<usize> {
4327 if let Some(idx) = page_areas.iter().position(|pa| pa.name == target) {
4329 return Some(idx);
4330 }
4331 if let Some(idx) = page_areas
4333 .iter()
4334 .position(|pa| pa.xfa_id.as_deref() == Some(target))
4335 {
4336 return Some(idx);
4337 }
4338 if let Some(rest) = target.strip_prefix("pageArea") {
4340 if let Some(idx_str) = rest.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
4341 if let Ok(idx) = idx_str.parse::<usize>() {
4342 if idx < page_areas.len() {
4343 return Some(idx);
4344 }
4345 }
4346 }
4347 if rest.is_empty() && !page_areas.is_empty() {
4349 return Some(0);
4350 }
4351 }
4352 None
4353}
4354
4355fn primary_content_area(pa: &PageAreaInfo) -> &ContentArea {
4357 let max_area = pa
4358 .content_areas
4359 .iter()
4360 .map(|ca| ca.width * ca.height)
4361 .fold(0.0_f64, f64::max);
4362 pa.content_areas
4363 .iter()
4364 .find(|ca| {
4365 let a = ca.width * ca.height;
4366 a >= max_area * 0.90 || pa.content_areas.len() == 1
4367 })
4368 .unwrap_or(&pa.content_areas[0])
4369}
4370
4371struct PageAreaInfo {
4372 name: String,
4374 xfa_id: Option<String>,
4376 content_areas: Vec<ContentArea>,
4377 page_width: f64,
4378 page_height: f64,
4379 fixed_nodes: Vec<FormNodeId>,
4382 runtime_instantiated: bool,
4388}
4389
4390#[cfg(test)]
4391mod tests {
4392 use super::*;
4393 use crate::form::Occur;
4394 use crate::text::FontMetrics;
4395 use crate::types::BoxModel;
4396
4397 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
4398 tree.add_node(FormNode {
4399 name: name.to_string(),
4400 node_type: FormNodeType::Field {
4401 value: name.to_string(),
4402 },
4403 box_model: BoxModel {
4404 width: Some(w),
4405 height: Some(h),
4406 max_width: f64::MAX,
4407 max_height: f64::MAX,
4408 ..Default::default()
4409 },
4410 layout: LayoutStrategy::Positioned,
4411 children: vec![],
4412 occur: Occur::once(),
4413 font: FontMetrics::default(),
4414 calculate: None,
4415 validate: None,
4416 column_widths: vec![],
4417 col_span: 1,
4418 })
4419 }
4420
4421 fn make_subform(
4422 tree: &mut FormTree,
4423 name: &str,
4424 strategy: LayoutStrategy,
4425 w: Option<f64>,
4426 h: Option<f64>,
4427 children: Vec<FormNodeId>,
4428 ) -> FormNodeId {
4429 tree.add_node(FormNode {
4430 name: name.to_string(),
4431 node_type: FormNodeType::Subform,
4432 box_model: BoxModel {
4433 width: w,
4434 height: h,
4435 max_width: f64::MAX,
4436 max_height: f64::MAX,
4437 ..Default::default()
4438 },
4439 layout: strategy,
4440 children,
4441 occur: Occur::once(),
4442 font: FontMetrics::default(),
4443 calculate: None,
4444 validate: None,
4445 column_widths: vec![],
4446 col_span: 1,
4447 })
4448 }
4449
4450 fn count_leaf_nodes(page: &LayoutPage) -> usize {
4452 fn count(nodes: &[LayoutNode]) -> usize {
4453 nodes
4454 .iter()
4455 .map(|n| {
4456 if n.children.is_empty() {
4457 1
4458 } else {
4459 count(&n.children)
4460 }
4461 })
4462 .sum()
4463 }
4464 count(&page.nodes)
4465 }
4466
4467 fn make_field_value(
4468 tree: &mut FormTree,
4469 name: &str,
4470 value: &str,
4471 w: f64,
4472 h: f64,
4473 ) -> FormNodeId {
4474 tree.add_node(FormNode {
4475 name: name.to_string(),
4476 node_type: FormNodeType::Field {
4477 value: value.to_string(),
4478 },
4479 box_model: BoxModel {
4480 width: Some(w),
4481 height: Some(h),
4482 max_width: f64::MAX,
4483 max_height: f64::MAX,
4484 ..Default::default()
4485 },
4486 layout: LayoutStrategy::Positioned,
4487 children: vec![],
4488 occur: Occur::once(),
4489 font: FontMetrics::default(),
4490 calculate: None,
4491 validate: None,
4492 column_widths: vec![],
4493 col_span: 1,
4494 })
4495 }
4496
4497 fn make_draw_text(tree: &mut FormTree, name: &str, text: &str, w: f64, h: f64) -> FormNodeId {
4498 tree.add_node(FormNode {
4499 name: name.to_string(),
4500 node_type: FormNodeType::Draw(DrawContent::Text(text.to_string())),
4501 box_model: BoxModel {
4502 width: Some(w),
4503 height: Some(h),
4504 max_width: f64::MAX,
4505 max_height: f64::MAX,
4506 ..Default::default()
4507 },
4508 layout: LayoutStrategy::Positioned,
4509 children: vec![],
4510 occur: Occur::once(),
4511 font: FontMetrics::default(),
4512 calculate: None,
4513 validate: None,
4514 column_widths: vec![],
4515 col_span: 1,
4516 })
4517 }
4518
4519 fn make_page_area(tree: &mut FormTree, name: &str, width: f64, height: f64) -> FormNodeId {
4520 tree.add_node(FormNode {
4521 name: name.to_string(),
4522 node_type: FormNodeType::PageArea {
4523 content_areas: vec![ContentArea {
4524 name: "Body".to_string(),
4525 x: 0.0,
4526 y: 0.0,
4527 width,
4528 height,
4529 leader: None,
4530 trailer: None,
4531 }],
4532 },
4533 box_model: BoxModel {
4534 width: Some(width),
4535 height: Some(height),
4536 max_width: f64::MAX,
4537 max_height: f64::MAX,
4538 ..Default::default()
4539 },
4540 layout: LayoutStrategy::Positioned,
4541 children: vec![],
4542 occur: Occur::once(),
4543 font: FontMetrics::default(),
4544 calculate: None,
4545 validate: None,
4546 column_widths: vec![],
4547 col_span: 1,
4548 })
4549 }
4550
4551 fn make_root(tree: &mut FormTree, children: Vec<FormNodeId>) -> FormNodeId {
4552 tree.add_node(FormNode {
4553 name: "Root".to_string(),
4554 node_type: FormNodeType::Root,
4555 box_model: BoxModel {
4556 max_width: f64::MAX,
4557 max_height: f64::MAX,
4558 ..Default::default()
4559 },
4560 layout: LayoutStrategy::TopToBottom,
4561 children,
4562 occur: Occur::once(),
4563 font: FontMetrics::default(),
4564 calculate: None,
4565 validate: None,
4566 column_widths: vec![],
4567 col_span: 1,
4568 })
4569 }
4570
4571 fn mark_data_bound(tree: &mut FormTree, id: FormNodeId) {
4572 let name = tree.get(id).name.clone();
4573 tree.meta_mut(id).data_bind_ref = Some(format!("$.{name}"));
4574 }
4575
4576 #[test]
4577 fn positioned_layout() {
4578 let mut tree = FormTree::new();
4579 let f1 = tree.add_node(FormNode {
4580 name: "Field1".to_string(),
4581 node_type: FormNodeType::Field {
4582 value: "A".to_string(),
4583 },
4584 box_model: BoxModel {
4585 width: Some(100.0),
4586 height: Some(20.0),
4587 x: 10.0,
4588 y: 30.0,
4589 max_width: f64::MAX,
4590 max_height: f64::MAX,
4591 ..Default::default()
4592 },
4593 layout: LayoutStrategy::Positioned,
4594 children: vec![],
4595 occur: Occur::once(),
4596 font: FontMetrics::default(),
4597 calculate: None,
4598 validate: None,
4599 column_widths: vec![],
4600 col_span: 1,
4601 });
4602 let f2 = tree.add_node(FormNode {
4603 name: "Field2".to_string(),
4604 node_type: FormNodeType::Field {
4605 value: "B".to_string(),
4606 },
4607 box_model: BoxModel {
4608 width: Some(100.0),
4609 height: Some(20.0),
4610 x: 10.0,
4611 y: 60.0,
4612 max_width: f64::MAX,
4613 max_height: f64::MAX,
4614 ..Default::default()
4615 },
4616 layout: LayoutStrategy::Positioned,
4617 children: vec![],
4618 occur: Occur::once(),
4619 font: FontMetrics::default(),
4620 calculate: None,
4621 validate: None,
4622 column_widths: vec![],
4623 col_span: 1,
4624 });
4625 let root = tree.add_node(FormNode {
4626 name: "Root".to_string(),
4627 node_type: FormNodeType::Root,
4628 box_model: BoxModel {
4629 width: Some(612.0),
4630 height: Some(792.0),
4631 max_width: f64::MAX,
4632 max_height: f64::MAX,
4633 ..Default::default()
4634 },
4635 layout: LayoutStrategy::Positioned,
4636 children: vec![f1, f2],
4637 occur: Occur::once(),
4638 font: FontMetrics::default(),
4639 calculate: None,
4640 validate: None,
4641 column_widths: vec![],
4642 col_span: 1,
4643 });
4644
4645 let engine = LayoutEngine::new(&tree);
4646 let result = engine.layout(root).unwrap();
4647
4648 assert_eq!(result.pages.len(), 1);
4649 let page = &result.pages[0];
4650 assert_eq!(page.nodes.len(), 2);
4651 assert_eq!(page.nodes[0].rect.x, 10.0);
4652 assert_eq!(page.nodes[0].rect.y, 30.0);
4653 assert_eq!(page.nodes[1].rect.x, 10.0);
4654 assert_eq!(page.nodes[1].rect.y, 60.0);
4655 }
4656
4657 #[test]
4658 fn positioned_children_offset_by_parent_margins() {
4659 use crate::types::Insets;
4660
4661 let mut tree = FormTree::new();
4662 let child = tree.add_node(FormNode {
4663 name: "Child".to_string(),
4664 node_type: FormNodeType::Field {
4665 value: "Child".to_string(),
4666 },
4667 box_model: BoxModel {
4668 width: Some(80.0),
4669 height: Some(20.0),
4670 x: 0.0,
4671 y: 0.0,
4672 max_width: f64::MAX,
4673 max_height: f64::MAX,
4674 ..Default::default()
4675 },
4676 layout: LayoutStrategy::Positioned,
4677 children: vec![],
4678 occur: Occur::once(),
4679 font: FontMetrics::default(),
4680 calculate: None,
4681 validate: None,
4682 column_widths: vec![],
4683 col_span: 1,
4684 });
4685
4686 let parent = tree.add_node(FormNode {
4687 name: "Parent".to_string(),
4688 node_type: FormNodeType::Subform,
4689 box_model: BoxModel {
4690 width: Some(200.0),
4691 height: Some(100.0),
4692 x: 50.0,
4693 y: 40.0,
4694 margins: Insets {
4695 top: 15.0,
4696 right: 0.0,
4697 bottom: 0.0,
4698 left: 10.0,
4699 },
4700 max_width: f64::MAX,
4701 max_height: f64::MAX,
4702 ..Default::default()
4703 },
4704 layout: LayoutStrategy::Positioned,
4705 children: vec![child],
4706 occur: Occur::once(),
4707 font: FontMetrics::default(),
4708 calculate: None,
4709 validate: None,
4710 column_widths: vec![],
4711 col_span: 1,
4712 });
4713
4714 let root = tree.add_node(FormNode {
4715 name: "Root".to_string(),
4716 node_type: FormNodeType::Root,
4717 box_model: BoxModel {
4718 width: Some(612.0),
4719 height: Some(792.0),
4720 max_width: f64::MAX,
4721 max_height: f64::MAX,
4722 ..Default::default()
4723 },
4724 layout: LayoutStrategy::Positioned,
4725 children: vec![parent],
4726 occur: Occur::once(),
4727 font: FontMetrics::default(),
4728 calculate: None,
4729 validate: None,
4730 column_widths: vec![],
4731 col_span: 1,
4732 });
4733
4734 let engine = LayoutEngine::new(&tree);
4735 let result = engine.layout(root).unwrap();
4736
4737 let parent_node = &result.pages[0].nodes[0];
4738 assert_eq!(parent_node.rect.x, 50.0);
4739 assert_eq!(parent_node.rect.y, 40.0);
4740 assert_eq!(parent_node.children[0].rect.x, 10.0);
4741 assert_eq!(parent_node.children[0].rect.y, 15.0);
4742 }
4743
4744 #[test]
4745 fn tb_layout() {
4746 let mut tree = FormTree::new();
4747 let f1 = make_field(&mut tree, "Field1", 200.0, 30.0);
4748 let f2 = make_field(&mut tree, "Field2", 200.0, 30.0);
4749 let f3 = make_field(&mut tree, "Field3", 200.0, 30.0);
4750
4751 let root = tree.add_node(FormNode {
4752 name: "Root".to_string(),
4753 node_type: FormNodeType::Root,
4754 box_model: BoxModel {
4755 width: Some(612.0),
4756 height: Some(792.0),
4757 max_width: f64::MAX,
4758 max_height: f64::MAX,
4759 ..Default::default()
4760 },
4761 layout: LayoutStrategy::TopToBottom,
4762 children: vec![f1, f2, f3],
4763 occur: Occur::once(),
4764 font: FontMetrics::default(),
4765 calculate: None,
4766 validate: None,
4767 column_widths: vec![],
4768 col_span: 1,
4769 });
4770
4771 let engine = LayoutEngine::new(&tree);
4772 let result = engine.layout(root).unwrap();
4773
4774 assert_eq!(result.pages.len(), 1);
4775 let page = &result.pages[0];
4776 assert_eq!(page.nodes.len(), 3);
4777 assert_eq!(page.nodes[0].rect.y, 0.0);
4778 assert_eq!(page.nodes[1].rect.y, 30.0);
4779 assert_eq!(page.nodes[2].rect.y, 60.0);
4780 }
4781
4782 #[test]
4783 fn lr_tb_wrapping() {
4784 let mut tree = FormTree::new();
4785 let f1 = make_field(&mut tree, "F1", 250.0, 30.0);
4787 let f2 = make_field(&mut tree, "F2", 250.0, 30.0);
4788 let f3 = make_field(&mut tree, "F3", 250.0, 30.0);
4789
4790 let root = tree.add_node(FormNode {
4791 name: "Root".to_string(),
4792 node_type: FormNodeType::Root,
4793 box_model: BoxModel {
4794 width: Some(600.0),
4795 height: Some(792.0),
4796 max_width: f64::MAX,
4797 max_height: f64::MAX,
4798 ..Default::default()
4799 },
4800 layout: LayoutStrategy::LeftToRightTB,
4801 children: vec![f1, f2, f3],
4802 occur: Occur::once(),
4803 font: FontMetrics::default(),
4804 calculate: None,
4805 validate: None,
4806 column_widths: vec![],
4807 col_span: 1,
4808 });
4809
4810 let engine = LayoutEngine::new(&tree);
4811 let result = engine.layout(root).unwrap();
4812
4813 let page = &result.pages[0];
4814 assert_eq!(page.nodes.len(), 3);
4815 assert_eq!(page.nodes[0].rect.x, 0.0);
4817 assert_eq!(page.nodes[0].rect.y, 0.0);
4818 assert_eq!(page.nodes[1].rect.x, 250.0);
4819 assert_eq!(page.nodes[1].rect.y, 0.0);
4820 assert_eq!(page.nodes[2].rect.x, 0.0);
4822 assert_eq!(page.nodes[2].rect.y, 30.0);
4823 }
4824
4825 #[test]
4826 fn nested_subforms() {
4827 let mut tree = FormTree::new();
4828 let f1 = make_field(&mut tree, "Name", 200.0, 25.0);
4829 let f2 = make_field(&mut tree, "Email", 200.0, 25.0);
4830
4831 let sub = make_subform(
4832 &mut tree,
4833 "PersonalInfo",
4834 LayoutStrategy::TopToBottom,
4835 Some(300.0),
4836 Some(100.0),
4837 vec![f1, f2],
4838 );
4839
4840 let root = tree.add_node(FormNode {
4841 name: "Root".to_string(),
4842 node_type: FormNodeType::Root,
4843 box_model: BoxModel {
4844 width: Some(612.0),
4845 height: Some(792.0),
4846 max_width: f64::MAX,
4847 max_height: f64::MAX,
4848 ..Default::default()
4849 },
4850 layout: LayoutStrategy::TopToBottom,
4851 children: vec![sub],
4852 occur: Occur::once(),
4853 font: FontMetrics::default(),
4854 calculate: None,
4855 validate: None,
4856 column_widths: vec![],
4857 col_span: 1,
4858 });
4859
4860 let engine = LayoutEngine::new(&tree);
4861 let result = engine.layout(root).unwrap();
4862
4863 let page = &result.pages[0];
4864 assert_eq!(page.nodes.len(), 1);
4865 let subform = &page.nodes[0];
4866 assert_eq!(subform.name, "PersonalInfo");
4867 assert_eq!(subform.rect.width, 300.0);
4868 assert_eq!(subform.children.len(), 2);
4869 assert_eq!(subform.children[0].rect.y, 0.0);
4870 assert_eq!(subform.children[1].rect.y, 25.0);
4871 }
4872
4873 #[test]
4874 fn page_area_layout() {
4875 let mut tree = FormTree::new();
4876 let f1 = make_field(&mut tree, "Field1", 200.0, 30.0);
4877
4878 let page_area = tree.add_node(FormNode {
4879 name: "Page1".to_string(),
4880 node_type: FormNodeType::PageArea {
4881 content_areas: vec![ContentArea {
4882 name: "Body".to_string(),
4883 x: 36.0,
4884 y: 36.0,
4885 width: 540.0,
4886 height: 720.0,
4887 leader: None,
4888 trailer: None,
4889 }],
4890 },
4891 box_model: BoxModel {
4892 width: Some(612.0),
4893 height: Some(792.0),
4894 max_width: f64::MAX,
4895 max_height: f64::MAX,
4896 ..Default::default()
4897 },
4898 layout: LayoutStrategy::Positioned,
4899 children: vec![],
4900 occur: Occur::once(),
4901 font: FontMetrics::default(),
4902 calculate: None,
4903 validate: None,
4904 column_widths: vec![],
4905 col_span: 1,
4906 });
4907
4908 let root = tree.add_node(FormNode {
4909 name: "Root".to_string(),
4910 node_type: FormNodeType::Root,
4911 box_model: BoxModel {
4912 width: Some(612.0),
4913 height: Some(792.0),
4914 max_width: f64::MAX,
4915 max_height: f64::MAX,
4916 ..Default::default()
4917 },
4918 layout: LayoutStrategy::TopToBottom,
4919 children: vec![page_area, f1],
4920 occur: Occur::once(),
4921 font: FontMetrics::default(),
4922 calculate: None,
4923 validate: None,
4924 column_widths: vec![],
4925 col_span: 1,
4926 });
4927
4928 let engine = LayoutEngine::new(&tree);
4929 let result = engine.layout(root).unwrap();
4930
4931 assert_eq!(result.pages.len(), 1);
4932 let page = &result.pages[0];
4933 assert_eq!(page.nodes[0].rect.x, 36.0);
4935 assert_eq!(page.nodes[0].rect.y, 36.0);
4936 }
4937
4938 #[test]
4939 fn trailing_empty_pagearea_continuation_suppressed() {
4940 let mut tree = FormTree::new();
4941 let first_page = make_page_area(&mut tree, "Page1", 200.0, 80.0);
4942 let continuation_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4943 let record = make_field_value(&mut tree, "record", "data", 180.0, 80.0);
4944 mark_data_bound(&mut tree, record);
4945 let template_only = make_draw_text(&mut tree, "template_only", "template", 180.0, 20.0);
4946 let root = make_root(
4947 &mut tree,
4948 vec![first_page, continuation_page, record, template_only],
4949 );
4950
4951 let engine = LayoutEngine::new(&tree);
4952 let result = engine.layout(root).unwrap();
4953
4954 assert_eq!(result.pages.len(), 1);
4955 assert_eq!(count_leaf_nodes(&result.pages[0]), 1);
4956 }
4957
4958 #[test]
4959 fn valid_multi_page_overflow_unchanged() {
4960 let mut tree = FormTree::new();
4961 let page_area = make_page_area(&mut tree, "Page1", 200.0, 100.0);
4962 let mut children = vec![page_area];
4963 for idx in 0..4 {
4964 let field = make_field_value(&mut tree, &format!("record{idx}"), "data", 180.0, 40.0);
4965 mark_data_bound(&mut tree, field);
4966 children.push(field);
4967 }
4968 let root = make_root(&mut tree, children);
4969
4970 let engine = LayoutEngine::new(&tree);
4971 let result = engine.layout(root).unwrap();
4972
4973 assert_eq!(result.pages.len(), 2);
4974 assert_eq!(result.pages.iter().map(count_leaf_nodes).sum::<usize>(), 4);
4975 }
4976
4977 #[test]
4978 fn template_only_page_kept_when_explicitly_required() {
4979 let mut tree = FormTree::new();
4980 let first_page = make_page_area(&mut tree, "Page1", 200.0, 80.0);
4981 let anchored_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
4982 let record = make_field_value(&mut tree, "record", "data", 180.0, 80.0);
4983 mark_data_bound(&mut tree, record);
4984 let anchored_draw = make_draw_text(&mut tree, "anchored_draw", "next page", 180.0, 20.0);
4985 tree.meta_mut(anchored_draw).page_break_before = true;
4986 let root = make_root(
4987 &mut tree,
4988 vec![first_page, anchored_page, record, anchored_draw],
4989 );
4990
4991 let engine = LayoutEngine::new(&tree);
4992 let result = engine.layout(root).unwrap();
4993
4994 assert_eq!(result.pages.len(), 2);
4995 assert_eq!(count_leaf_nodes(&result.pages[1]), 1);
4996 }
4997
4998 #[test]
4999 fn single_page_form_remains_single_page() {
5000 let mut tree = FormTree::new();
5001 let page_area = make_page_area(&mut tree, "Page1", 200.0, 100.0);
5002 let record = make_field_value(&mut tree, "record", "data", 180.0, 40.0);
5003 mark_data_bound(&mut tree, record);
5004 let root = make_root(&mut tree, vec![page_area, record]);
5005
5006 let engine = LayoutEngine::new(&tree);
5007 let result = engine.layout(root).unwrap();
5008
5009 assert_eq!(result.pages.len(), 1);
5010 assert_eq!(count_leaf_nodes(&result.pages[0]), 1);
5011 }
5012
5013 #[test]
5014 fn continuation_with_remaining_body_still_emits() {
5015 let mut tree = FormTree::new();
5016 let first_page = make_page_area(&mut tree, "Page1", 200.0, 60.0);
5017 let continuation_page = make_page_area(&mut tree, "Page2", 200.0, 100.0);
5018 let first_record = make_field_value(&mut tree, "first_record", "data", 180.0, 60.0);
5019 let second_record = make_field_value(&mut tree, "second_record", "data", 180.0, 40.0);
5020 mark_data_bound(&mut tree, first_record);
5021 mark_data_bound(&mut tree, second_record);
5022 let root = make_root(
5023 &mut tree,
5024 vec![first_page, continuation_page, first_record, second_record],
5025 );
5026
5027 let engine = LayoutEngine::new(&tree);
5028 let result = engine.layout(root).unwrap();
5029
5030 assert_eq!(result.pages.len(), 2);
5031 assert_eq!(count_leaf_nodes(&result.pages[1]), 1);
5032 }
5033
5034 #[test]
5035 fn growable_extent() {
5036 let mut tree = FormTree::new();
5037 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
5038 let f2 = make_field(&mut tree, "F2", 150.0, 20.0);
5039
5040 let sub = make_subform(
5042 &mut tree,
5043 "Container",
5044 LayoutStrategy::TopToBottom,
5045 None,
5046 None,
5047 vec![f1, f2],
5048 );
5049
5050 let engine = LayoutEngine::new(&tree);
5051 let extent = engine.compute_extent(sub);
5052
5053 assert_eq!(extent.width, 150.0);
5055 assert_eq!(extent.height, 40.0);
5056 }
5057
5058 #[test]
5059 fn rl_tb_layout() {
5060 let mut tree = FormTree::new();
5061 let f1 = make_field(&mut tree, "F1", 100.0, 30.0);
5062 let f2 = make_field(&mut tree, "F2", 100.0, 30.0);
5063
5064 let root = tree.add_node(FormNode {
5065 name: "Root".to_string(),
5066 node_type: FormNodeType::Root,
5067 box_model: BoxModel {
5068 width: Some(400.0),
5069 height: Some(400.0),
5070 max_width: f64::MAX,
5071 max_height: f64::MAX,
5072 ..Default::default()
5073 },
5074 layout: LayoutStrategy::RightToLeftTB,
5075 children: vec![f1, f2],
5076 occur: Occur::once(),
5077 font: FontMetrics::default(),
5078 calculate: None,
5079 validate: None,
5080 column_widths: vec![],
5081 col_span: 1,
5082 });
5083
5084 let engine = LayoutEngine::new(&tree);
5085 let result = engine.layout(root).unwrap();
5086 let page = &result.pages[0];
5087
5088 assert_eq!(page.nodes[0].rect.x, 300.0); assert_eq!(page.nodes[1].rect.x, 200.0); }
5092
5093 #[test]
5096 fn growable_clamped_by_min() {
5097 let mut tree = FormTree::new();
5099 let f1 = make_field(&mut tree, "F1", 50.0, 10.0);
5100
5101 let sub = tree.add_node(FormNode {
5102 name: "Container".to_string(),
5103 node_type: FormNodeType::Subform,
5104 box_model: BoxModel {
5105 width: None,
5106 height: None,
5107 min_width: 200.0,
5108 min_height: 100.0,
5109 max_width: f64::MAX,
5110 max_height: f64::MAX,
5111 ..Default::default()
5112 },
5113 layout: LayoutStrategy::TopToBottom,
5114 children: vec![f1],
5115 occur: Occur::once(),
5116 font: FontMetrics::default(),
5117 calculate: None,
5118 validate: None,
5119 column_widths: vec![],
5120 col_span: 1,
5121 });
5122
5123 let engine = LayoutEngine::new(&tree);
5124 let extent = engine.compute_extent(sub);
5125
5126 assert_eq!(extent.width, 200.0);
5128 assert_eq!(extent.height, 100.0);
5129 }
5130
5131 #[test]
5132 fn growable_clamped_by_max() {
5133 let mut tree = FormTree::new();
5135 let f1 = make_field(&mut tree, "F1", 500.0, 300.0);
5136
5137 let sub = tree.add_node(FormNode {
5138 name: "Container".to_string(),
5139 node_type: FormNodeType::Subform,
5140 box_model: BoxModel {
5141 width: None,
5142 height: None,
5143 min_width: 0.0,
5144 min_height: 0.0,
5145 max_width: 200.0,
5146 max_height: 100.0,
5147 ..Default::default()
5148 },
5149 layout: LayoutStrategy::TopToBottom,
5150 children: vec![f1],
5151 occur: Occur::once(),
5152 font: FontMetrics::default(),
5153 calculate: None,
5154 validate: None,
5155 column_widths: vec![],
5156 col_span: 1,
5157 });
5158
5159 let engine = LayoutEngine::new(&tree);
5160 let extent = engine.compute_extent(sub);
5161
5162 assert_eq!(extent.width, 200.0);
5164 assert_eq!(extent.height, 100.0);
5165 }
5166
5167 #[test]
5168 fn partially_growable_width_fixed() {
5169 let mut tree = FormTree::new();
5171 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
5172 let f2 = make_field(&mut tree, "F2", 100.0, 25.0);
5173
5174 let sub = tree.add_node(FormNode {
5175 name: "Container".to_string(),
5176 node_type: FormNodeType::Subform,
5177 box_model: BoxModel {
5178 width: Some(300.0),
5179 height: None,
5180 max_width: f64::MAX,
5181 max_height: f64::MAX,
5182 ..Default::default()
5183 },
5184 layout: LayoutStrategy::TopToBottom,
5185 children: vec![f1, f2],
5186 occur: Occur::once(),
5187 font: FontMetrics::default(),
5188 calculate: None,
5189 validate: None,
5190 column_widths: vec![],
5191 col_span: 1,
5192 });
5193
5194 let engine = LayoutEngine::new(&tree);
5195 let extent = engine.compute_extent(sub);
5196
5197 assert_eq!(extent.width, 300.0);
5199 assert_eq!(extent.height, 50.0);
5200 }
5201
5202 #[test]
5203 fn partially_growable_height_fixed() {
5204 let mut tree = FormTree::new();
5206 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
5207 let f2 = make_field(&mut tree, "F2", 150.0, 25.0);
5208
5209 let sub = tree.add_node(FormNode {
5210 name: "Container".to_string(),
5211 node_type: FormNodeType::Subform,
5212 box_model: BoxModel {
5213 width: None,
5214 height: Some(200.0),
5215 max_width: f64::MAX,
5216 max_height: f64::MAX,
5217 ..Default::default()
5218 },
5219 layout: LayoutStrategy::TopToBottom,
5220 children: vec![f1, f2],
5221 occur: Occur::once(),
5222 font: FontMetrics::default(),
5223 calculate: None,
5224 validate: None,
5225 column_widths: vec![],
5226 col_span: 1,
5227 });
5228
5229 let engine = LayoutEngine::new(&tree);
5230 let extent = engine.compute_extent(sub);
5231
5232 assert_eq!(extent.width, 150.0);
5234 assert_eq!(extent.height, 200.0);
5235 }
5236
5237 #[test]
5238 fn growable_fills_available_width_in_tb() {
5239 let mut tree = FormTree::new();
5241 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
5242
5243 let growable_sub = tree.add_node(FormNode {
5244 name: "GrowableSub".to_string(),
5245 node_type: FormNodeType::Subform,
5246 box_model: BoxModel {
5247 width: None,
5248 height: None,
5249 max_width: f64::MAX,
5250 max_height: f64::MAX,
5251 ..Default::default()
5252 },
5253 layout: LayoutStrategy::TopToBottom,
5254 children: vec![f1],
5255 occur: Occur::once(),
5256 font: FontMetrics::default(),
5257 calculate: None,
5258 validate: None,
5259 column_widths: vec![],
5260 col_span: 1,
5261 });
5262
5263 let root = tree.add_node(FormNode {
5264 name: "Root".to_string(),
5265 node_type: FormNodeType::Root,
5266 box_model: BoxModel {
5267 width: Some(500.0),
5268 height: Some(400.0),
5269 max_width: f64::MAX,
5270 max_height: f64::MAX,
5271 ..Default::default()
5272 },
5273 layout: LayoutStrategy::TopToBottom,
5274 children: vec![growable_sub],
5275 occur: Occur::once(),
5276 font: FontMetrics::default(),
5277 calculate: None,
5278 validate: None,
5279 column_widths: vec![],
5280 col_span: 1,
5281 });
5282
5283 let engine = LayoutEngine::new(&tree);
5284 let result = engine.layout(root).unwrap();
5285
5286 let page = &result.pages[0];
5287 assert_eq!(page.nodes[0].rect.width, 500.0);
5289 assert_eq!(page.nodes[0].rect.height, 25.0);
5291 }
5292
5293 #[test]
5294 fn growable_fill_capped_by_max() {
5295 let mut tree = FormTree::new();
5297 let f1 = make_field(&mut tree, "F1", 100.0, 25.0);
5298
5299 let growable_sub = tree.add_node(FormNode {
5300 name: "GrowableSub".to_string(),
5301 node_type: FormNodeType::Subform,
5302 box_model: BoxModel {
5303 width: None,
5304 height: None,
5305 max_width: 300.0,
5306 max_height: f64::MAX,
5307 ..Default::default()
5308 },
5309 layout: LayoutStrategy::TopToBottom,
5310 children: vec![f1],
5311 occur: Occur::once(),
5312 font: FontMetrics::default(),
5313 calculate: None,
5314 validate: None,
5315 column_widths: vec![],
5316 col_span: 1,
5317 });
5318
5319 let root = tree.add_node(FormNode {
5320 name: "Root".to_string(),
5321 node_type: FormNodeType::Root,
5322 box_model: BoxModel {
5323 width: Some(500.0),
5324 height: Some(400.0),
5325 max_width: f64::MAX,
5326 max_height: f64::MAX,
5327 ..Default::default()
5328 },
5329 layout: LayoutStrategy::TopToBottom,
5330 children: vec![growable_sub],
5331 occur: Occur::once(),
5332 font: FontMetrics::default(),
5333 calculate: None,
5334 validate: None,
5335 column_widths: vec![],
5336 col_span: 1,
5337 });
5338
5339 let engine = LayoutEngine::new(&tree);
5340 let result = engine.layout(root).unwrap();
5341
5342 let page = &result.pages[0];
5343 assert_eq!(page.nodes[0].rect.width, 300.0);
5345 }
5346
5347 #[test]
5348 fn growable_with_margins_in_tb() {
5349 use crate::types::Insets;
5351 let mut tree = FormTree::new();
5352 let f1 = make_field(&mut tree, "F1", 50.0, 20.0);
5353
5354 let growable_sub = tree.add_node(FormNode {
5355 name: "GrowableSub".to_string(),
5356 node_type: FormNodeType::Subform,
5357 box_model: BoxModel {
5358 width: None,
5359 height: None,
5360 margins: Insets {
5361 top: 5.0,
5362 right: 10.0,
5363 bottom: 5.0,
5364 left: 10.0,
5365 },
5366 max_width: f64::MAX,
5367 max_height: f64::MAX,
5368 ..Default::default()
5369 },
5370 layout: LayoutStrategy::TopToBottom,
5371 children: vec![f1],
5372 occur: Occur::once(),
5373 font: FontMetrics::default(),
5374 calculate: None,
5375 validate: None,
5376 column_widths: vec![],
5377 col_span: 1,
5378 });
5379
5380 let root = tree.add_node(FormNode {
5381 name: "Root".to_string(),
5382 node_type: FormNodeType::Root,
5383 box_model: BoxModel {
5384 width: Some(400.0),
5385 height: Some(300.0),
5386 max_width: f64::MAX,
5387 max_height: f64::MAX,
5388 ..Default::default()
5389 },
5390 layout: LayoutStrategy::TopToBottom,
5391 children: vec![growable_sub],
5392 occur: Occur::once(),
5393 font: FontMetrics::default(),
5394 calculate: None,
5395 validate: None,
5396 column_widths: vec![],
5397 col_span: 1,
5398 });
5399
5400 let engine = LayoutEngine::new(&tree);
5401 let result = engine.layout(root).unwrap();
5402
5403 let page = &result.pages[0];
5404 assert_eq!(page.nodes[0].rect.width, 400.0);
5406 assert_eq!(page.nodes[0].rect.height, 30.0);
5408 }
5409
5410 #[test]
5411 fn nested_growable_containers() {
5412 let mut tree = FormTree::new();
5414 let f1 = make_field(&mut tree, "F1", 80.0, 20.0);
5415
5416 let inner = tree.add_node(FormNode {
5417 name: "Inner".to_string(),
5418 node_type: FormNodeType::Subform,
5419 box_model: BoxModel {
5420 width: None,
5421 height: None,
5422 min_width: 150.0,
5423 max_width: f64::MAX,
5424 max_height: f64::MAX,
5425 ..Default::default()
5426 },
5427 layout: LayoutStrategy::TopToBottom,
5428 children: vec![f1],
5429 occur: Occur::once(),
5430 font: FontMetrics::default(),
5431 calculate: None,
5432 validate: None,
5433 column_widths: vec![],
5434 col_span: 1,
5435 });
5436
5437 let outer = tree.add_node(FormNode {
5439 name: "Outer".to_string(),
5440 node_type: FormNodeType::Subform,
5441 box_model: BoxModel {
5442 width: None,
5443 height: None,
5444 max_width: 400.0,
5445 max_height: f64::MAX,
5446 ..Default::default()
5447 },
5448 layout: LayoutStrategy::TopToBottom,
5449 children: vec![inner],
5450 occur: Occur::once(),
5451 font: FontMetrics::default(),
5452 calculate: None,
5453 validate: None,
5454 column_widths: vec![],
5455 col_span: 1,
5456 });
5457
5458 let engine = LayoutEngine::new(&tree);
5459 let extent = engine.compute_extent(outer);
5460
5461 assert_eq!(extent.width, 150.0);
5464 assert_eq!(extent.height, 20.0);
5465 }
5466
5467 #[test]
5468 fn min_max_in_lr_tb_layout() {
5469 let mut tree = FormTree::new();
5471
5472 let f1 = tree.add_node(FormNode {
5473 name: "F1".to_string(),
5474 node_type: FormNodeType::Field {
5475 value: "A".to_string(),
5476 },
5477 box_model: BoxModel {
5478 width: None,
5479 height: Some(30.0),
5480 min_width: 200.0,
5481 max_width: f64::MAX,
5482 max_height: f64::MAX,
5483 ..Default::default()
5484 },
5485 layout: LayoutStrategy::Positioned,
5486 children: vec![],
5487 occur: Occur::once(),
5488 font: FontMetrics::default(),
5489 calculate: None,
5490 validate: None,
5491 column_widths: vec![],
5492 col_span: 1,
5493 });
5494
5495 let f2 = make_field(&mut tree, "F2", 200.0, 30.0);
5496
5497 let root = tree.add_node(FormNode {
5498 name: "Root".to_string(),
5499 node_type: FormNodeType::Root,
5500 box_model: BoxModel {
5501 width: Some(500.0),
5502 height: Some(400.0),
5503 max_width: f64::MAX,
5504 max_height: f64::MAX,
5505 ..Default::default()
5506 },
5507 layout: LayoutStrategy::LeftToRightTB,
5508 children: vec![f1, f2],
5509 occur: Occur::once(),
5510 font: FontMetrics::default(),
5511 calculate: None,
5512 validate: None,
5513 column_widths: vec![],
5514 col_span: 1,
5515 });
5516
5517 let engine = LayoutEngine::new(&tree);
5518 let result = engine.layout(root).unwrap();
5519
5520 let page = &result.pages[0];
5521 assert_eq!(page.nodes[0].rect.width, 200.0);
5523 assert_eq!(page.nodes[1].rect.x, 200.0);
5525 }
5526
5527 #[test]
5530 fn occur_default_once() {
5531 let mut tree = FormTree::new();
5533 let f1 = make_field(&mut tree, "F1", 100.0, 30.0);
5534
5535 let root = tree.add_node(FormNode {
5536 name: "Root".to_string(),
5537 node_type: FormNodeType::Root,
5538 box_model: BoxModel {
5539 width: Some(400.0),
5540 height: Some(400.0),
5541 max_width: f64::MAX,
5542 max_height: f64::MAX,
5543 ..Default::default()
5544 },
5545 layout: LayoutStrategy::TopToBottom,
5546 children: vec![f1],
5547 occur: Occur::once(),
5548 font: FontMetrics::default(),
5549 calculate: None,
5550 validate: None,
5551 column_widths: vec![],
5552 col_span: 1,
5553 });
5554
5555 let engine = LayoutEngine::new(&tree);
5556 let result = engine.layout(root).unwrap();
5557 assert_eq!(result.pages[0].nodes.len(), 1);
5558 }
5559
5560 #[test]
5561 fn occur_repeating_tb() {
5562 let mut tree = FormTree::new();
5564 let f1 = tree.add_node(FormNode {
5565 name: "Row".to_string(),
5566 node_type: FormNodeType::Subform,
5567 box_model: BoxModel {
5568 width: Some(200.0),
5569 height: Some(30.0),
5570 max_width: f64::MAX,
5571 max_height: f64::MAX,
5572 ..Default::default()
5573 },
5574 layout: LayoutStrategy::Positioned,
5575 children: vec![],
5576 occur: Occur::repeating(1, Some(10), 3),
5577 font: FontMetrics::default(),
5578 calculate: None,
5579 validate: None,
5580 column_widths: vec![],
5581 col_span: 1,
5582 });
5583
5584 let root = tree.add_node(FormNode {
5585 name: "Root".to_string(),
5586 node_type: FormNodeType::Root,
5587 box_model: BoxModel {
5588 width: Some(400.0),
5589 height: Some(400.0),
5590 max_width: f64::MAX,
5591 max_height: f64::MAX,
5592 ..Default::default()
5593 },
5594 layout: LayoutStrategy::TopToBottom,
5595 children: vec![f1],
5596 occur: Occur::once(),
5597 font: FontMetrics::default(),
5598 calculate: None,
5599 validate: None,
5600 column_widths: vec![],
5601 col_span: 1,
5602 });
5603
5604 let engine = LayoutEngine::new(&tree);
5605 let result = engine.layout(root).unwrap();
5606 let page = &result.pages[0];
5607
5608 assert_eq!(page.nodes.len(), 3);
5610 assert_eq!(page.nodes[0].rect.y, 0.0);
5611 assert_eq!(page.nodes[1].rect.y, 30.0);
5612 assert_eq!(page.nodes[2].rect.y, 60.0);
5613 }
5614
5615 #[test]
5616 fn occur_repeating_lr_tb() {
5617 let mut tree = FormTree::new();
5619 let f1 = tree.add_node(FormNode {
5620 name: "Cell".to_string(),
5621 node_type: FormNodeType::Field {
5622 value: "X".to_string(),
5623 },
5624 box_model: BoxModel {
5625 width: Some(100.0),
5626 height: Some(30.0),
5627 max_width: f64::MAX,
5628 max_height: f64::MAX,
5629 ..Default::default()
5630 },
5631 layout: LayoutStrategy::Positioned,
5632 children: vec![],
5633 occur: Occur::repeating(1, None, 5),
5634 font: FontMetrics::default(),
5635 calculate: None,
5636 validate: None,
5637 column_widths: vec![],
5638 col_span: 1,
5639 });
5640
5641 let root = tree.add_node(FormNode {
5642 name: "Root".to_string(),
5643 node_type: FormNodeType::Root,
5644 box_model: BoxModel {
5645 width: Some(350.0),
5646 height: Some(400.0),
5647 max_width: f64::MAX,
5648 max_height: f64::MAX,
5649 ..Default::default()
5650 },
5651 layout: LayoutStrategy::LeftToRightTB,
5652 children: vec![f1],
5653 occur: Occur::once(),
5654 font: FontMetrics::default(),
5655 calculate: None,
5656 validate: None,
5657 column_widths: vec![],
5658 col_span: 1,
5659 });
5660
5661 let engine = LayoutEngine::new(&tree);
5662 let result = engine.layout(root).unwrap();
5663 let page = &result.pages[0];
5664
5665 assert_eq!(page.nodes.len(), 5);
5668 assert_eq!(page.nodes[0].rect.x, 0.0);
5669 assert_eq!(page.nodes[0].rect.y, 0.0);
5670 assert_eq!(page.nodes[1].rect.x, 100.0);
5671 assert_eq!(page.nodes[2].rect.x, 200.0);
5672 assert_eq!(page.nodes[3].rect.x, 0.0);
5673 assert_eq!(page.nodes[3].rect.y, 30.0);
5674 assert_eq!(page.nodes[4].rect.x, 100.0);
5675 assert_eq!(page.nodes[4].rect.y, 30.0);
5676 }
5677
5678 #[test]
5679 fn occur_min_enforced() {
5680 let occur = Occur::repeating(2, Some(5), 2);
5682 assert_eq!(occur.count(), 2);
5683 assert!(occur.is_repeating());
5684 }
5685
5686 #[test]
5687 fn occur_max_caps_initial() {
5688 let occur = Occur::repeating(1, Some(3), 5);
5690 assert_eq!(occur.count(), 3);
5691 }
5692
5693 #[test]
5694 fn occur_initial_raised_to_min() {
5695 let occur = Occur::repeating(3, Some(10), 1);
5697 assert_eq!(occur.count(), 3);
5698 }
5699
5700 #[test]
5701 fn occur_unlimited_max() {
5702 let occur = Occur::repeating(0, None, 5);
5703 assert_eq!(occur.count(), 5);
5704 assert!(occur.is_repeating());
5705 }
5706
5707 #[test]
5708 fn occur_mixed_children() {
5709 let mut tree = FormTree::new();
5711 let header = make_field(&mut tree, "Header", 200.0, 40.0);
5712 let row = tree.add_node(FormNode {
5713 name: "DataRow".to_string(),
5714 node_type: FormNodeType::Subform,
5715 box_model: BoxModel {
5716 width: Some(200.0),
5717 height: Some(25.0),
5718 max_width: f64::MAX,
5719 max_height: f64::MAX,
5720 ..Default::default()
5721 },
5722 layout: LayoutStrategy::Positioned,
5723 children: vec![],
5724 occur: Occur::repeating(1, Some(10), 4),
5725 font: FontMetrics::default(),
5726 calculate: None,
5727 validate: None,
5728 column_widths: vec![],
5729 col_span: 1,
5730 });
5731 let footer = make_field(&mut tree, "Footer", 200.0, 30.0);
5732
5733 let root = tree.add_node(FormNode {
5734 name: "Root".to_string(),
5735 node_type: FormNodeType::Root,
5736 box_model: BoxModel {
5737 width: Some(400.0),
5738 height: Some(600.0),
5739 max_width: f64::MAX,
5740 max_height: f64::MAX,
5741 ..Default::default()
5742 },
5743 layout: LayoutStrategy::TopToBottom,
5744 children: vec![header, row, footer],
5745 occur: Occur::once(),
5746 font: FontMetrics::default(),
5747 calculate: None,
5748 validate: None,
5749 column_widths: vec![],
5750 col_span: 1,
5751 });
5752
5753 let engine = LayoutEngine::new(&tree);
5754 let result = engine.layout(root).unwrap();
5755 let page = &result.pages[0];
5756
5757 assert_eq!(page.nodes.len(), 6);
5759 assert_eq!(page.nodes[0].name, "Header");
5760 assert_eq!(page.nodes[0].rect.y, 0.0);
5761 assert_eq!(page.nodes[1].name, "DataRow");
5763 assert_eq!(page.nodes[1].rect.y, 40.0);
5764 assert_eq!(page.nodes[2].rect.y, 65.0);
5765 assert_eq!(page.nodes[3].rect.y, 90.0);
5766 assert_eq!(page.nodes[4].rect.y, 115.0);
5767 assert_eq!(page.nodes[5].name, "Footer");
5769 assert_eq!(page.nodes[5].rect.y, 140.0);
5770 }
5771
5772 #[test]
5773 fn occur_growable_extent() {
5774 let mut tree = FormTree::new();
5776 let row = tree.add_node(FormNode {
5777 name: "Row".to_string(),
5778 node_type: FormNodeType::Subform,
5779 box_model: BoxModel {
5780 width: Some(150.0),
5781 height: Some(20.0),
5782 max_width: f64::MAX,
5783 max_height: f64::MAX,
5784 ..Default::default()
5785 },
5786 layout: LayoutStrategy::Positioned,
5787 children: vec![],
5788 occur: Occur::repeating(1, None, 5),
5789 font: FontMetrics::default(),
5790 calculate: None,
5791 validate: None,
5792 column_widths: vec![],
5793 col_span: 1,
5794 });
5795
5796 let container = tree.add_node(FormNode {
5797 name: "Container".to_string(),
5798 node_type: FormNodeType::Subform,
5799 box_model: BoxModel {
5800 width: None,
5801 height: None,
5802 max_width: f64::MAX,
5803 max_height: f64::MAX,
5804 ..Default::default()
5805 },
5806 layout: LayoutStrategy::TopToBottom,
5807 children: vec![row],
5808 occur: Occur::once(),
5809 font: FontMetrics::default(),
5810 calculate: None,
5811 validate: None,
5812 column_widths: vec![],
5813 col_span: 1,
5814 });
5815
5816 let engine = LayoutEngine::new(&tree);
5817 let extent = engine.compute_extent(container);
5818
5819 assert_eq!(extent.width, 150.0);
5821 assert_eq!(extent.height, 100.0);
5822 }
5823
5824 #[test]
5827 fn pagination_single_page_no_overflow() {
5828 let mut tree = FormTree::new();
5830 let f1 = make_field(&mut tree, "F1", 200.0, 30.0);
5831 let f2 = make_field(&mut tree, "F2", 200.0, 30.0);
5832
5833 let root = tree.add_node(FormNode {
5834 name: "Root".to_string(),
5835 node_type: FormNodeType::Root,
5836 box_model: BoxModel {
5837 width: Some(400.0),
5838 height: Some(200.0),
5839 max_width: f64::MAX,
5840 max_height: f64::MAX,
5841 ..Default::default()
5842 },
5843 layout: LayoutStrategy::TopToBottom,
5844 children: vec![f1, f2],
5845 occur: Occur::once(),
5846 font: FontMetrics::default(),
5847 calculate: None,
5848 validate: None,
5849 column_widths: vec![],
5850 col_span: 1,
5851 });
5852
5853 let engine = LayoutEngine::new(&tree);
5854 let result = engine.layout(root).unwrap();
5855
5856 assert_eq!(result.pages.len(), 1);
5857 assert_eq!(result.pages[0].nodes.len(), 2);
5858 }
5859
5860 #[test]
5861 fn pagination_overflow_creates_pages() {
5862 let mut tree = FormTree::new();
5864 let mut fields = Vec::new();
5865 for i in 0..10 {
5866 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 30.0));
5867 }
5868
5869 let root = tree.add_node(FormNode {
5870 name: "Root".to_string(),
5871 node_type: FormNodeType::Root,
5872 box_model: BoxModel {
5873 width: Some(400.0),
5874 height: Some(100.0),
5875 max_width: f64::MAX,
5876 max_height: f64::MAX,
5877 ..Default::default()
5878 },
5879 layout: LayoutStrategy::TopToBottom,
5880 children: fields,
5881 occur: Occur::once(),
5882 font: FontMetrics::default(),
5883 calculate: None,
5884 validate: None,
5885 column_widths: vec![],
5886 col_span: 1,
5887 });
5888
5889 let engine = LayoutEngine::new(&tree);
5890 let result = engine.layout(root).unwrap();
5891
5892 assert_eq!(result.pages.len(), 4);
5895 assert_eq!(result.pages[0].nodes.len(), 3);
5896 assert_eq!(result.pages[1].nodes.len(), 3);
5897 assert_eq!(result.pages[2].nodes.len(), 3);
5898 assert_eq!(result.pages[3].nodes.len(), 1);
5899 }
5900
5901 #[test]
5902 fn pagination_profile_reports_height_usage_and_overflow_target() {
5903 let mut tree = FormTree::new();
5904 let mut fields = Vec::new();
5905 for i in 0..4 {
5906 let field = make_field(&mut tree, &format!("F{i}"), 200.0, 30.0);
5907 tree.meta_mut(field).xfa_id = Some(format!("row_{i}"));
5908 fields.push(field);
5909 }
5910
5911 let root = tree.add_node(FormNode {
5912 name: "Root".to_string(),
5913 node_type: FormNodeType::Root,
5914 box_model: BoxModel {
5915 width: Some(400.0),
5916 height: Some(100.0),
5917 max_width: f64::MAX,
5918 max_height: f64::MAX,
5919 ..Default::default()
5920 },
5921 layout: LayoutStrategy::TopToBottom,
5922 children: fields,
5923 occur: Occur::once(),
5924 font: FontMetrics::default(),
5925 calculate: None,
5926 validate: None,
5927 column_widths: vec![],
5928 col_span: 1,
5929 });
5930
5931 let engine = LayoutEngine::new(&tree);
5932 let (layout, profile) = engine.layout_with_profile(root).unwrap();
5933
5934 assert_eq!(layout.pages.len(), 2);
5935 assert_eq!(profile.pages.len(), 2);
5936 assert_eq!(profile.pages[0].page_height, 100.0);
5937 assert_eq!(profile.pages[0].used_height, 90.0);
5938 assert!(profile.pages[0].overflow_to_next);
5939 assert_eq!(
5940 profile.pages[0].first_overflow_element.as_deref(),
5941 Some("field#row_3 (h=30.0)")
5942 );
5943 assert_eq!(profile.pages[1].used_height, 30.0);
5944 assert!(!profile.pages[1].overflow_to_next);
5945 assert!(profile.pages[1].first_overflow_element.is_none());
5946 }
5947
5948 #[test]
5949 fn pagination_with_page_area() {
5950 let mut tree = FormTree::new();
5952 let mut fields = Vec::new();
5953 for i in 0..6 {
5954 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 50.0));
5955 }
5956
5957 let page_area = tree.add_node(FormNode {
5958 name: "Page1".to_string(),
5959 node_type: FormNodeType::PageArea {
5960 content_areas: vec![ContentArea {
5961 name: "Body".to_string(),
5962 x: 20.0,
5963 y: 20.0,
5964 width: 360.0,
5965 height: 160.0, leader: None,
5967 trailer: None,
5968 }],
5969 },
5970 box_model: BoxModel {
5971 width: Some(400.0),
5972 height: Some(200.0),
5973 max_width: f64::MAX,
5974 max_height: f64::MAX,
5975 ..Default::default()
5976 },
5977 layout: LayoutStrategy::Positioned,
5978 children: vec![],
5979 occur: Occur::once(),
5980 font: FontMetrics::default(),
5981 calculate: None,
5982 validate: None,
5983 column_widths: vec![],
5984 col_span: 1,
5985 });
5986
5987 let mut root_children = vec![page_area];
5988 root_children.extend(fields);
5989
5990 let root = tree.add_node(FormNode {
5991 name: "Root".to_string(),
5992 node_type: FormNodeType::Root,
5993 box_model: BoxModel {
5994 width: Some(400.0),
5995 height: Some(200.0),
5996 max_width: f64::MAX,
5997 max_height: f64::MAX,
5998 ..Default::default()
5999 },
6000 layout: LayoutStrategy::TopToBottom,
6001 children: root_children,
6002 occur: Occur::once(),
6003 font: FontMetrics::default(),
6004 calculate: None,
6005 validate: None,
6006 column_widths: vec![],
6007 col_span: 1,
6008 });
6009
6010 let engine = LayoutEngine::new(&tree);
6011 let result = engine.layout(root).unwrap();
6012
6013 assert_eq!(result.pages.len(), 2);
6015 assert_eq!(result.pages[0].nodes.len(), 3);
6016 assert_eq!(result.pages[1].nodes.len(), 3);
6017
6018 assert_eq!(result.pages[0].nodes[0].rect.x, 20.0);
6020 assert_eq!(result.pages[0].nodes[0].rect.y, 20.0);
6021 assert_eq!(result.pages[0].nodes[1].rect.y, 70.0); }
6023
6024 #[test]
6025 fn pagination_with_occur_repeating() {
6026 let mut tree = FormTree::new();
6028 let row = tree.add_node(FormNode {
6029 name: "DataRow".to_string(),
6030 node_type: FormNodeType::Subform,
6031 box_model: BoxModel {
6032 width: Some(200.0),
6033 height: Some(25.0),
6034 max_width: f64::MAX,
6035 max_height: f64::MAX,
6036 ..Default::default()
6037 },
6038 layout: LayoutStrategy::Positioned,
6039 children: vec![],
6040 occur: Occur::repeating(1, None, 8),
6041 font: FontMetrics::default(),
6042 calculate: None,
6043 validate: None,
6044 column_widths: vec![],
6045 col_span: 1,
6046 });
6047
6048 let root = tree.add_node(FormNode {
6049 name: "Root".to_string(),
6050 node_type: FormNodeType::Root,
6051 box_model: BoxModel {
6052 width: Some(400.0),
6053 height: Some(100.0),
6054 max_width: f64::MAX,
6055 max_height: f64::MAX,
6056 ..Default::default()
6057 },
6058 layout: LayoutStrategy::TopToBottom,
6059 children: vec![row],
6060 occur: Occur::once(),
6061 font: FontMetrics::default(),
6062 calculate: None,
6063 validate: None,
6064 column_widths: vec![],
6065 col_span: 1,
6066 });
6067
6068 let engine = LayoutEngine::new(&tree);
6069 let result = engine.layout(root).unwrap();
6070
6071 assert_eq!(result.pages.len(), 2);
6073 assert_eq!(result.pages[0].nodes.len(), 4);
6074 assert_eq!(result.pages[1].nodes.len(), 4);
6075 }
6076
6077 #[test]
6078 fn pagination_oversized_item_forced() {
6079 let mut tree = FormTree::new();
6081 let f1 = make_field(&mut tree, "Big", 200.0, 200.0); let f2 = make_field(&mut tree, "Small", 200.0, 30.0);
6083
6084 let root = tree.add_node(FormNode {
6085 name: "Root".to_string(),
6086 node_type: FormNodeType::Root,
6087 box_model: BoxModel {
6088 width: Some(400.0),
6089 height: Some(100.0), max_width: f64::MAX,
6091 max_height: f64::MAX,
6092 ..Default::default()
6093 },
6094 layout: LayoutStrategy::TopToBottom,
6095 children: vec![f1, f2],
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 engine = LayoutEngine::new(&tree);
6105 let result = engine.layout(root).unwrap();
6106
6107 assert_eq!(result.pages.len(), 2);
6109 assert_eq!(result.pages[0].nodes[0].name, "Big");
6110 assert_eq!(result.pages[1].nodes[0].name, "Small");
6111 }
6112
6113 #[test]
6114 fn pagination_page_dimensions_correct() {
6115 let mut tree = FormTree::new();
6117 let mut fields = Vec::new();
6118 for i in 0..5 {
6119 fields.push(make_field(&mut tree, &format!("F{i}"), 200.0, 50.0));
6120 }
6121
6122 let root = tree.add_node(FormNode {
6123 name: "Root".to_string(),
6124 node_type: FormNodeType::Root,
6125 box_model: BoxModel {
6126 width: Some(500.0),
6127 height: Some(120.0),
6128 max_width: f64::MAX,
6129 max_height: f64::MAX,
6130 ..Default::default()
6131 },
6132 layout: LayoutStrategy::TopToBottom,
6133 children: fields,
6134 occur: Occur::once(),
6135 font: FontMetrics::default(),
6136 calculate: None,
6137 validate: None,
6138 column_widths: vec![],
6139 col_span: 1,
6140 });
6141
6142 let engine = LayoutEngine::new(&tree);
6143 let result = engine.layout(root).unwrap();
6144
6145 for page in &result.pages {
6146 assert_eq!(page.width, 500.0);
6147 assert_eq!(page.height, 120.0);
6148 }
6149 }
6150
6151 #[test]
6154 fn split_subform_across_pages() {
6155 let mut tree = FormTree::new();
6159 let header = make_field(&mut tree, "Header", 300.0, 40.0);
6160
6161 let mut sub_children = Vec::new();
6162 for i in 0..6 {
6163 sub_children.push(make_field(&mut tree, &format!("Row{i}"), 300.0, 30.0));
6164 }
6165
6166 let subform = tree.add_node(FormNode {
6167 name: "DataBlock".to_string(),
6168 node_type: FormNodeType::Subform,
6169 box_model: BoxModel {
6170 width: Some(300.0),
6171 height: None, max_width: f64::MAX,
6173 max_height: f64::MAX,
6174 ..Default::default()
6175 },
6176 layout: LayoutStrategy::TopToBottom,
6177 children: sub_children,
6178 occur: Occur::once(),
6179 font: FontMetrics::default(),
6180 calculate: None,
6181 validate: None,
6182 column_widths: vec![],
6183 col_span: 1,
6184 });
6185
6186 let root = tree.add_node(FormNode {
6187 name: "Root".to_string(),
6188 node_type: FormNodeType::Root,
6189 box_model: BoxModel {
6190 width: Some(400.0),
6191 height: Some(160.0), max_width: f64::MAX,
6193 max_height: f64::MAX,
6194 ..Default::default()
6195 },
6196 layout: LayoutStrategy::TopToBottom,
6197 children: vec![header, subform],
6198 occur: Occur::once(),
6199 font: FontMetrics::default(),
6200 calculate: None,
6201 validate: None,
6202 column_widths: vec![],
6203 col_span: 1,
6204 });
6205
6206 let engine = LayoutEngine::new(&tree);
6207 let result = engine.layout(root).unwrap();
6208
6209 assert!(result.pages.len() >= 2);
6212 let p1 = &result.pages[0];
6214 assert_eq!(p1.nodes[0].name, "Header");
6215 assert_eq!(p1.nodes[1].name, "DataBlock");
6216 let split_sub = &p1.nodes[1];
6217 assert_eq!(split_sub.children.len(), 4); let p2 = &result.pages[1];
6221 assert_eq!(p2.nodes.len(), 2);
6222 }
6223
6224 #[test]
6225 fn split_preserves_node_positions() {
6226 let mut tree = FormTree::new();
6228 let mut sub_children = Vec::new();
6229 for i in 0..4 {
6230 sub_children.push(make_field(&mut tree, &format!("Row{i}"), 200.0, 25.0));
6231 }
6232
6233 let subform = tree.add_node(FormNode {
6234 name: "Block".to_string(),
6235 node_type: FormNodeType::Subform,
6236 box_model: BoxModel {
6237 width: Some(200.0),
6238 height: None,
6239 max_width: f64::MAX,
6240 max_height: f64::MAX,
6241 ..Default::default()
6242 },
6243 layout: LayoutStrategy::TopToBottom,
6244 children: sub_children,
6245 occur: Occur::once(),
6246 font: FontMetrics::default(),
6247 calculate: None,
6248 validate: None,
6249 column_widths: vec![],
6250 col_span: 1,
6251 });
6252
6253 let root = tree.add_node(FormNode {
6254 name: "Root".to_string(),
6255 node_type: FormNodeType::Root,
6256 box_model: BoxModel {
6257 width: Some(400.0),
6258 height: Some(60.0), max_width: f64::MAX,
6260 max_height: f64::MAX,
6261 ..Default::default()
6262 },
6263 layout: LayoutStrategy::TopToBottom,
6264 children: vec![subform],
6265 occur: Occur::once(),
6266 font: FontMetrics::default(),
6267 calculate: None,
6268 validate: None,
6269 column_widths: vec![],
6270 col_span: 1,
6271 });
6272
6273 let engine = LayoutEngine::new(&tree);
6274 let result = engine.layout(root).unwrap();
6275
6276 let split_sub = &result.pages[0].nodes[0];
6278 assert_eq!(split_sub.children.len(), 2);
6279 assert_eq!(split_sub.children[0].rect.y, 0.0);
6280 assert_eq!(split_sub.children[1].rect.y, 25.0);
6281 }
6282
6283 #[test]
6284 fn split_recurses_into_oversized_first_child() {
6285 let mut tree = FormTree::new();
6286
6287 let mut rows = Vec::new();
6288 for i in 0..6 {
6289 rows.push(make_field(&mut tree, &format!("Row{i}"), 300.0, 30.0));
6290 }
6291
6292 let inner = tree.add_node(FormNode {
6293 name: "InnerBlock".to_string(),
6294 node_type: FormNodeType::Subform,
6295 box_model: BoxModel {
6296 width: Some(300.0),
6297 height: None,
6298 max_width: f64::MAX,
6299 max_height: f64::MAX,
6300 ..Default::default()
6301 },
6302 layout: LayoutStrategy::TopToBottom,
6303 children: rows,
6304 occur: Occur::once(),
6305 font: FontMetrics::default(),
6306 calculate: None,
6307 validate: None,
6308 column_widths: vec![],
6309 col_span: 1,
6310 });
6311
6312 let outer = tree.add_node(FormNode {
6313 name: "OuterBlock".to_string(),
6314 node_type: FormNodeType::Subform,
6315 box_model: BoxModel {
6316 width: Some(300.0),
6317 height: None,
6318 max_width: f64::MAX,
6319 max_height: f64::MAX,
6320 ..Default::default()
6321 },
6322 layout: LayoutStrategy::TopToBottom,
6323 children: vec![inner],
6324 occur: Occur::once(),
6325 font: FontMetrics::default(),
6326 calculate: None,
6327 validate: None,
6328 column_widths: vec![],
6329 col_span: 1,
6330 });
6331
6332 let root = tree.add_node(FormNode {
6333 name: "Root".to_string(),
6334 node_type: FormNodeType::Root,
6335 box_model: BoxModel {
6336 width: Some(400.0),
6337 height: Some(100.0),
6338 max_width: f64::MAX,
6339 max_height: f64::MAX,
6340 ..Default::default()
6341 },
6342 layout: LayoutStrategy::TopToBottom,
6343 children: vec![outer],
6344 occur: Occur::once(),
6345 font: FontMetrics::default(),
6346 calculate: None,
6347 validate: None,
6348 column_widths: vec![],
6349 col_span: 1,
6350 });
6351
6352 let engine = LayoutEngine::new(&tree);
6353 let result = engine.layout(root).unwrap();
6354
6355 assert_eq!(result.pages.len(), 2);
6356 assert_eq!(result.pages[0].nodes[0].name, "OuterBlock");
6357 assert_eq!(result.pages[0].nodes[0].children[0].name, "InnerBlock");
6358 assert_eq!(result.pages[0].nodes[0].children[0].children.len(), 3);
6359 }
6360
6361 #[test]
6362 fn no_split_for_non_tb_layout() {
6363 let mut tree = FormTree::new();
6365 let header = make_field(&mut tree, "Header", 300.0, 80.0);
6366
6367 let f1 = tree.add_node(FormNode {
6368 name: "Child1".to_string(),
6369 node_type: FormNodeType::Field {
6370 value: "A".to_string(),
6371 },
6372 box_model: BoxModel {
6373 width: Some(100.0),
6374 height: Some(50.0),
6375 x: 0.0,
6376 y: 0.0,
6377 max_width: f64::MAX,
6378 max_height: f64::MAX,
6379 ..Default::default()
6380 },
6381 layout: LayoutStrategy::Positioned,
6382 children: vec![],
6383 occur: Occur::once(),
6384 font: FontMetrics::default(),
6385 calculate: None,
6386 validate: None,
6387 column_widths: vec![],
6388 col_span: 1,
6389 });
6390
6391 let subform = tree.add_node(FormNode {
6392 name: "PositionedBlock".to_string(),
6393 node_type: FormNodeType::Subform,
6394 box_model: BoxModel {
6395 width: Some(200.0),
6396 height: Some(100.0), max_width: f64::MAX,
6398 max_height: f64::MAX,
6399 ..Default::default()
6400 },
6401 layout: LayoutStrategy::Positioned, children: vec![f1],
6403 occur: Occur::once(),
6404 font: FontMetrics::default(),
6405 calculate: None,
6406 validate: None,
6407 column_widths: vec![],
6408 col_span: 1,
6409 });
6410
6411 let root = tree.add_node(FormNode {
6412 name: "Root".to_string(),
6413 node_type: FormNodeType::Root,
6414 box_model: BoxModel {
6415 width: Some(400.0),
6416 height: Some(100.0), max_width: f64::MAX,
6418 max_height: f64::MAX,
6419 ..Default::default()
6420 },
6421 layout: LayoutStrategy::TopToBottom,
6422 children: vec![header, subform],
6423 occur: Occur::once(),
6424 font: FontMetrics::default(),
6425 calculate: None,
6426 validate: None,
6427 column_widths: vec![],
6428 col_span: 1,
6429 });
6430
6431 let engine = LayoutEngine::new(&tree);
6432 let result = engine.layout(root).unwrap();
6433
6434 assert_eq!(result.pages.len(), 2);
6436 assert_eq!(result.pages[0].nodes[0].name, "Header");
6437 assert_eq!(result.pages[1].nodes[0].name, "PositionedBlock");
6438 }
6439
6440 #[test]
6441 fn layout_tb_splits_on_overflow() {
6442 let mut tree = FormTree::new();
6443 let mut children = Vec::new();
6444 for i in 0..10 {
6445 children.push(make_field(&mut tree, &format!("F{i}"), 200.0, 100.0));
6446 }
6447 let parent = make_subform(
6448 &mut tree,
6449 "Parent",
6450 LayoutStrategy::TopToBottom,
6451 Some(200.0),
6452 None,
6453 children,
6454 );
6455
6456 let engine = LayoutEngine::new(&tree);
6457 let parent_children = tree.get(parent).children.clone();
6458 let nodes = engine
6459 .layout_tb(
6460 &parent_children,
6461 Size {
6462 width: 200.0,
6463 height: 300.0,
6464 },
6465 )
6466 .unwrap();
6467
6468 assert_eq!(nodes.len(), 3);
6470 assert!(nodes.iter().all(|n| n.rect.y + n.rect.height <= 301.0));
6471 }
6472
6473 #[test]
6474 fn can_split_checks() {
6475 let mut tree = FormTree::new();
6476 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
6477
6478 let tb_sub = tree.add_node(FormNode {
6479 name: "TB".to_string(),
6480 node_type: FormNodeType::Subform,
6481 box_model: BoxModel {
6482 max_width: f64::MAX,
6483 max_height: f64::MAX,
6484 ..Default::default()
6485 },
6486 layout: LayoutStrategy::TopToBottom,
6487 children: vec![f1],
6488 occur: Occur::once(),
6489 font: FontMetrics::default(),
6490 calculate: None,
6491 validate: None,
6492 column_widths: vec![],
6493 col_span: 1,
6494 });
6495
6496 let pos_sub = tree.add_node(FormNode {
6497 name: "Pos".to_string(),
6498 node_type: FormNodeType::Subform,
6499 box_model: BoxModel {
6500 max_width: f64::MAX,
6501 max_height: f64::MAX,
6502 ..Default::default()
6503 },
6504 layout: LayoutStrategy::Positioned,
6505 children: vec![f1],
6506 occur: Occur::once(),
6507 font: FontMetrics::default(),
6508 calculate: None,
6509 validate: None,
6510 column_widths: vec![],
6511 col_span: 1,
6512 });
6513
6514 let empty_sub = tree.add_node(FormNode {
6515 name: "Empty".to_string(),
6516 node_type: FormNodeType::Subform,
6517 box_model: BoxModel {
6518 max_width: f64::MAX,
6519 max_height: f64::MAX,
6520 ..Default::default()
6521 },
6522 layout: LayoutStrategy::TopToBottom,
6523 children: vec![],
6524 occur: Occur::once(),
6525 font: FontMetrics::default(),
6526 calculate: None,
6527 validate: None,
6528 column_widths: vec![],
6529 col_span: 1,
6530 });
6531
6532 let engine = LayoutEngine::new(&tree);
6533 assert!(engine.can_split(tb_sub));
6534 assert!(engine.can_split(pos_sub));
6536 assert!(!engine.can_split(empty_sub));
6537 }
6538
6539 #[test]
6540 fn positioned_subform_paginates_across_pages() {
6541 let mut tree = FormTree::new();
6544
6545 let mut fields = Vec::new();
6548 for i in 0..10 {
6549 let f = tree.add_node(FormNode {
6550 name: format!("F{i}"),
6551 node_type: FormNodeType::Field {
6552 value: format!("Value{i}"),
6553 },
6554 box_model: BoxModel {
6555 width: Some(200.0),
6556 height: Some(60.0),
6557 x: 10.0,
6558 y: i as f64 * 80.0,
6559 max_width: f64::MAX,
6560 max_height: f64::MAX,
6561 ..Default::default()
6562 },
6563 layout: LayoutStrategy::Positioned,
6564 children: vec![],
6565 occur: Occur::once(),
6566 font: FontMetrics::default(),
6567 calculate: None,
6568 validate: None,
6569 column_widths: vec![],
6570 col_span: 1,
6571 });
6572 fields.push(f);
6573 }
6574
6575 let positioned = tree.add_node(FormNode {
6577 name: "PositionedBody".to_string(),
6578 node_type: FormNodeType::Subform,
6579 box_model: BoxModel {
6580 width: Some(400.0),
6581 max_width: f64::MAX,
6582 max_height: f64::MAX,
6583 ..Default::default()
6584 },
6585 layout: LayoutStrategy::Positioned,
6586 children: fields,
6587 occur: Occur::once(),
6588 font: FontMetrics::default(),
6589 calculate: None,
6590 validate: None,
6591 column_widths: vec![],
6592 col_span: 1,
6593 });
6594
6595 let page_area = tree.add_node(FormNode {
6597 name: "Page1".to_string(),
6598 node_type: FormNodeType::PageArea {
6599 content_areas: vec![ContentArea {
6600 name: "Body".to_string(),
6601 x: 0.0,
6602 y: 0.0,
6603 width: 400.0,
6604 height: 400.0,
6605 leader: None,
6606 trailer: None,
6607 }],
6608 },
6609 box_model: BoxModel {
6610 width: Some(400.0),
6611 height: Some(400.0),
6612 max_width: f64::MAX,
6613 max_height: f64::MAX,
6614 ..Default::default()
6615 },
6616 layout: LayoutStrategy::Positioned,
6617 children: vec![],
6618 occur: Occur::once(),
6619 font: FontMetrics::default(),
6620 calculate: None,
6621 validate: None,
6622 column_widths: vec![],
6623 col_span: 1,
6624 });
6625
6626 let root = tree.add_node(FormNode {
6627 name: "Root".to_string(),
6628 node_type: FormNodeType::Root,
6629 box_model: BoxModel {
6630 max_width: f64::MAX,
6631 max_height: f64::MAX,
6632 ..Default::default()
6633 },
6634 layout: LayoutStrategy::TopToBottom,
6635 children: vec![page_area, positioned],
6636 occur: Occur::once(),
6637 font: FontMetrics::default(),
6638 calculate: None,
6639 validate: None,
6640 column_widths: vec![],
6641 col_span: 1,
6642 });
6643
6644 let engine = LayoutEngine::new(&tree);
6645 let result = engine.layout(root).unwrap();
6646
6647 assert!(
6653 result.pages.len() >= 2,
6654 "Expected at least 2 pages, got {}",
6655 result.pages.len()
6656 );
6657
6658 let page1_children = count_leaf_nodes(&result.pages[0]);
6660 let page2_children = count_leaf_nodes(&result.pages[1]);
6661 assert!(page1_children > 0, "Page 1 should have content");
6662 assert!(page2_children > 0, "Page 2 should have content");
6663 assert_eq!(
6664 page1_children + page2_children,
6665 10,
6666 "All 10 fields should be placed across pages"
6667 );
6668 }
6669
6670 #[test]
6671 fn positioned_split_preserves_parent_margin_offset() {
6672 use crate::types::Insets;
6673
6674 let mut tree = FormTree::new();
6675
6676 let first = tree.add_node(FormNode {
6677 name: "First".to_string(),
6678 node_type: FormNodeType::Field {
6679 value: "First".to_string(),
6680 },
6681 box_model: BoxModel {
6682 width: Some(100.0),
6683 height: Some(40.0),
6684 x: 0.0,
6685 y: 0.0,
6686 max_width: f64::MAX,
6687 max_height: f64::MAX,
6688 ..Default::default()
6689 },
6690 layout: LayoutStrategy::Positioned,
6691 children: vec![],
6692 occur: Occur::once(),
6693 font: FontMetrics::default(),
6694 calculate: None,
6695 validate: None,
6696 column_widths: vec![],
6697 col_span: 1,
6698 });
6699 let second = tree.add_node(FormNode {
6700 name: "Second".to_string(),
6701 node_type: FormNodeType::Field {
6702 value: "Second".to_string(),
6703 },
6704 box_model: BoxModel {
6705 width: Some(100.0),
6706 height: Some(40.0),
6707 x: 0.0,
6708 y: 60.0,
6709 max_width: f64::MAX,
6710 max_height: f64::MAX,
6711 ..Default::default()
6712 },
6713 layout: LayoutStrategy::Positioned,
6714 children: vec![],
6715 occur: Occur::once(),
6716 font: FontMetrics::default(),
6717 calculate: None,
6718 validate: None,
6719 column_widths: vec![],
6720 col_span: 1,
6721 });
6722
6723 let positioned = tree.add_node(FormNode {
6724 name: "PositionedBody".to_string(),
6725 node_type: FormNodeType::Subform,
6726 box_model: BoxModel {
6727 width: Some(140.0),
6728 margins: Insets {
6729 top: 10.0,
6730 right: 0.0,
6731 bottom: 0.0,
6732 left: 8.0,
6733 },
6734 max_width: f64::MAX,
6735 max_height: f64::MAX,
6736 ..Default::default()
6737 },
6738 layout: LayoutStrategy::Positioned,
6739 children: vec![first, second],
6740 occur: Occur::once(),
6741 font: FontMetrics::default(),
6742 calculate: None,
6743 validate: None,
6744 column_widths: vec![],
6745 col_span: 1,
6746 });
6747
6748 let page_area = tree.add_node(FormNode {
6749 name: "Page1".to_string(),
6750 node_type: FormNodeType::PageArea {
6751 content_areas: vec![ContentArea {
6752 name: "Body".to_string(),
6753 x: 0.0,
6754 y: 0.0,
6755 width: 200.0,
6756 height: 70.0,
6757 leader: None,
6758 trailer: None,
6759 }],
6760 },
6761 box_model: BoxModel {
6762 width: Some(200.0),
6763 height: Some(70.0),
6764 max_width: f64::MAX,
6765 max_height: f64::MAX,
6766 ..Default::default()
6767 },
6768 layout: LayoutStrategy::Positioned,
6769 children: vec![],
6770 occur: Occur::once(),
6771 font: FontMetrics::default(),
6772 calculate: None,
6773 validate: None,
6774 column_widths: vec![],
6775 col_span: 1,
6776 });
6777
6778 let root = tree.add_node(FormNode {
6779 name: "Root".to_string(),
6780 node_type: FormNodeType::Root,
6781 box_model: BoxModel {
6782 max_width: f64::MAX,
6783 max_height: f64::MAX,
6784 ..Default::default()
6785 },
6786 layout: LayoutStrategy::TopToBottom,
6787 children: vec![page_area, positioned],
6788 occur: Occur::once(),
6789 font: FontMetrics::default(),
6790 calculate: None,
6791 validate: None,
6792 column_widths: vec![],
6793 col_span: 1,
6794 });
6795
6796 let engine = LayoutEngine::new(&tree);
6797 let result = engine.layout(root).unwrap();
6798
6799 assert_eq!(result.pages.len(), 2);
6800 assert_eq!(result.pages[0].nodes[0].children.len(), 1);
6801 assert_eq!(result.pages[1].nodes[0].children.len(), 1);
6802 assert_eq!(result.pages[0].nodes[0].children[0].rect.x, 8.0);
6803 assert_eq!(result.pages[0].nodes[0].children[0].rect.y, 10.0);
6804 assert_eq!(result.pages[1].nodes[0].children[0].rect.x, 8.0);
6805 assert_eq!(result.pages[1].nodes[0].children[0].rect.y, 10.0);
6806 }
6807
6808 #[test]
6809 fn single_positioned_child_no_overpagination() {
6810 let mut tree = FormTree::new();
6814
6815 let mut fields = Vec::new();
6817 for i in 0..5 {
6818 let f = tree.add_node(FormNode {
6819 name: format!("F{i}"),
6820 node_type: FormNodeType::Field {
6821 value: format!("Value{i}"),
6822 },
6823 box_model: BoxModel {
6824 width: Some(200.0),
6825 height: Some(30.0),
6826 x: 36.0,
6827 y: 36.0 + i as f64 * 40.0,
6828 max_width: f64::MAX,
6829 max_height: f64::MAX,
6830 ..Default::default()
6831 },
6832 layout: LayoutStrategy::Positioned,
6833 children: vec![],
6834 occur: Occur::once(),
6835 font: FontMetrics::default(),
6836 calculate: None,
6837 validate: None,
6838 column_widths: vec![],
6839 col_span: 1,
6840 });
6841 fields.push(f);
6842 }
6843
6844 let positioned = tree.add_node(FormNode {
6846 name: "PageSubform".to_string(),
6847 node_type: FormNodeType::Subform,
6848 box_model: BoxModel {
6849 width: Some(612.0),
6850 height: Some(792.0),
6851 max_width: f64::MAX,
6852 max_height: f64::MAX,
6853 ..Default::default()
6854 },
6855 layout: LayoutStrategy::Positioned,
6856 children: fields,
6857 occur: Occur::once(),
6858 font: FontMetrics::default(),
6859 calculate: None,
6860 validate: None,
6861 column_widths: vec![],
6862 col_span: 1,
6863 });
6864
6865 let page_area = tree.add_node(FormNode {
6866 name: "Page1".to_string(),
6867 node_type: FormNodeType::PageArea {
6868 content_areas: vec![ContentArea {
6869 name: "Body".to_string(),
6870 x: 0.0,
6871 y: 0.0,
6872 width: 612.0,
6873 height: 792.0,
6874 leader: None,
6875 trailer: None,
6876 }],
6877 },
6878 box_model: BoxModel {
6879 width: Some(612.0),
6880 height: Some(792.0),
6881 max_width: f64::MAX,
6882 max_height: f64::MAX,
6883 ..Default::default()
6884 },
6885 layout: LayoutStrategy::Positioned,
6886 children: vec![],
6887 occur: Occur::once(),
6888 font: FontMetrics::default(),
6889 calculate: None,
6890 validate: None,
6891 column_widths: vec![],
6892 col_span: 1,
6893 });
6894
6895 let root = tree.add_node(FormNode {
6896 name: "Root".to_string(),
6897 node_type: FormNodeType::Root,
6898 box_model: BoxModel {
6899 max_width: f64::MAX,
6900 max_height: f64::MAX,
6901 ..Default::default()
6902 },
6903 layout: LayoutStrategy::TopToBottom,
6904 children: vec![page_area, positioned],
6905 occur: Occur::once(),
6906 font: FontMetrics::default(),
6907 calculate: None,
6908 validate: None,
6909 column_widths: vec![],
6910 col_span: 1,
6911 });
6912
6913 let engine = LayoutEngine::new(&tree);
6914 let result = engine.layout(root).unwrap();
6915
6916 assert_eq!(
6917 result.pages.len(),
6918 1,
6919 "Single positioned child fitting on one page should produce 1 page, got {}",
6920 result.pages.len()
6921 );
6922
6923 let leaf_count = count_leaf_nodes(&result.pages[0]);
6925 assert_eq!(leaf_count, 5, "All 5 fields should be placed on page 1");
6926 }
6927
6928 #[test]
6929 fn positioned_inside_tb_no_infinite_pagination() {
6930 let mut tree = FormTree::new();
6945
6946 let mut fields = Vec::new();
6947 for i in 0..8 {
6948 let f = tree.add_node(FormNode {
6949 name: format!("F{i}"),
6950 node_type: FormNodeType::Field {
6951 value: format!("Val{i}"),
6952 },
6953 box_model: BoxModel {
6954 width: Some(200.0),
6955 height: Some(60.0),
6956 x: 10.0,
6957 y: i as f64 * 80.0,
6958 max_width: f64::MAX,
6959 max_height: f64::MAX,
6960 ..Default::default()
6961 },
6962 layout: LayoutStrategy::Positioned,
6963 children: vec![],
6964 occur: Occur::once(),
6965 font: FontMetrics::default(),
6966 calculate: None,
6967 validate: None,
6968 column_widths: vec![],
6969 col_span: 1,
6970 });
6971 fields.push(f);
6972 }
6973
6974 let positioned = tree.add_node(FormNode {
6975 name: "PositionedBody".to_string(),
6976 node_type: FormNodeType::Subform,
6977 box_model: BoxModel {
6978 width: Some(400.0),
6979 max_width: f64::MAX,
6980 max_height: f64::MAX,
6981 ..Default::default()
6982 },
6983 layout: LayoutStrategy::Positioned,
6984 children: fields,
6985 occur: Occur::once(),
6986 font: FontMetrics::default(),
6987 calculate: None,
6988 validate: None,
6989 column_widths: vec![],
6990 col_span: 1,
6991 });
6992
6993 let tb_wrapper = tree.add_node(FormNode {
6996 name: "TbWrapper".to_string(),
6997 node_type: FormNodeType::Subform,
6998 box_model: BoxModel {
6999 width: Some(400.0),
7000 max_width: f64::MAX,
7001 max_height: f64::MAX,
7002 ..Default::default()
7003 },
7004 layout: LayoutStrategy::TopToBottom,
7005 children: vec![positioned],
7006 occur: Occur::once(),
7007 font: FontMetrics::default(),
7008 calculate: None,
7009 validate: None,
7010 column_widths: vec![],
7011 col_span: 1,
7012 });
7013
7014 let page_area = tree.add_node(FormNode {
7015 name: "Page1".to_string(),
7016 node_type: FormNodeType::PageArea {
7017 content_areas: vec![ContentArea {
7018 name: "Body".to_string(),
7019 x: 0.0,
7020 y: 0.0,
7021 width: 400.0,
7022 height: 300.0,
7023 leader: None,
7024 trailer: None,
7025 }],
7026 },
7027 box_model: BoxModel {
7028 width: Some(400.0),
7029 height: Some(300.0),
7030 max_width: f64::MAX,
7031 max_height: f64::MAX,
7032 ..Default::default()
7033 },
7034 layout: LayoutStrategy::Positioned,
7035 children: vec![],
7036 occur: Occur::once(),
7037 font: FontMetrics::default(),
7038 calculate: None,
7039 validate: None,
7040 column_widths: vec![],
7041 col_span: 1,
7042 });
7043
7044 let root = tree.add_node(FormNode {
7045 name: "Root".to_string(),
7046 node_type: FormNodeType::Root,
7047 box_model: BoxModel {
7048 max_width: f64::MAX,
7049 max_height: f64::MAX,
7050 ..Default::default()
7051 },
7052 layout: LayoutStrategy::TopToBottom,
7053 children: vec![page_area, tb_wrapper],
7054 occur: Occur::once(),
7055 font: FontMetrics::default(),
7056 calculate: None,
7057 validate: None,
7058 column_widths: vec![],
7059 col_span: 1,
7060 });
7061
7062 let engine = LayoutEngine::new(&tree);
7063 let result = engine.layout(root).unwrap();
7064
7065 assert!(
7068 result.pages.len() <= 5,
7069 "Expected at most 5 pages for 8 fields across 300pt pages, got {} \
7070 (infinite pagination bug #737)",
7071 result.pages.len()
7072 );
7073 assert!(
7074 result.pages.len() >= 2,
7075 "Expected at least 2 pages, got {}",
7076 result.pages.len()
7077 );
7078
7079 let total_leaves: usize = result.pages.iter().map(count_leaf_nodes).sum();
7081 assert_eq!(
7082 total_leaves, 8,
7083 "All 8 fields should appear across pages, found {}",
7084 total_leaves
7085 );
7086 }
7087
7088 #[test]
7089 fn positioned_subform_with_explicit_height_not_split() {
7090 let mut tree = FormTree::new();
7092 let f1 = make_field(&mut tree, "F1", 100.0, 20.0);
7093
7094 let positioned = tree.add_node(FormNode {
7095 name: "FixedBlock".to_string(),
7096 node_type: FormNodeType::Subform,
7097 box_model: BoxModel {
7098 width: Some(200.0),
7099 height: Some(500.0), max_width: f64::MAX,
7101 max_height: f64::MAX,
7102 ..Default::default()
7103 },
7104 layout: LayoutStrategy::Positioned,
7105 children: vec![f1],
7106 occur: Occur::once(),
7107 font: FontMetrics::default(),
7108 calculate: None,
7109 validate: None,
7110 column_widths: vec![],
7111 col_span: 1,
7112 });
7113
7114 let engine = LayoutEngine::new(&tree);
7115 assert!(
7116 !engine.can_split(positioned),
7117 "Positioned subform with explicit height should not be splittable"
7118 );
7119 }
7120
7121 #[test]
7124 fn leader_placed_at_top() {
7125 let mut tree = FormTree::new();
7127 let header = make_field(&mut tree, "PageHeader", 300.0, 30.0);
7128 let f1 = make_field(&mut tree, "Content1", 300.0, 50.0);
7129 let f2 = make_field(&mut tree, "Content2", 300.0, 50.0);
7130
7131 let page_area = tree.add_node(FormNode {
7132 name: "Page1".to_string(),
7133 node_type: FormNodeType::PageArea {
7134 content_areas: vec![ContentArea {
7135 name: "Body".to_string(),
7136 x: 0.0,
7137 y: 0.0,
7138 width: 400.0,
7139 height: 200.0,
7140 leader: Some(header),
7141 trailer: None,
7142 }],
7143 },
7144 box_model: BoxModel {
7145 width: Some(400.0),
7146 height: Some(200.0),
7147 max_width: f64::MAX,
7148 max_height: f64::MAX,
7149 ..Default::default()
7150 },
7151 layout: LayoutStrategy::Positioned,
7152 children: vec![],
7153 occur: Occur::once(),
7154 font: FontMetrics::default(),
7155 calculate: None,
7156 validate: None,
7157 column_widths: vec![],
7158 col_span: 1,
7159 });
7160
7161 let root = tree.add_node(FormNode {
7162 name: "Root".to_string(),
7163 node_type: FormNodeType::Root,
7164 box_model: BoxModel {
7165 width: Some(400.0),
7166 height: Some(200.0),
7167 max_width: f64::MAX,
7168 max_height: f64::MAX,
7169 ..Default::default()
7170 },
7171 layout: LayoutStrategy::TopToBottom,
7172 children: vec![page_area, f1, f2],
7173 occur: Occur::once(),
7174 font: FontMetrics::default(),
7175 calculate: None,
7176 validate: None,
7177 column_widths: vec![],
7178 col_span: 1,
7179 });
7180
7181 let engine = LayoutEngine::new(&tree);
7182 let result = engine.layout(root).unwrap();
7183
7184 let page = &result.pages[0];
7185 assert_eq!(page.nodes[0].name, "PageHeader");
7187 assert_eq!(page.nodes[0].rect.y, 0.0);
7188 assert!(page.nodes.len() >= 2);
7190 let first_content = page.nodes.iter().find(|n| n.name == "Content1").unwrap();
7192 assert_eq!(first_content.rect.y, 30.0);
7193 }
7194
7195 #[test]
7196 fn trailer_placed_at_bottom() {
7197 let mut tree = FormTree::new();
7199 let footer = make_field(&mut tree, "PageFooter", 300.0, 25.0);
7200 let f1 = make_field(&mut tree, "Content1", 300.0, 50.0);
7201
7202 let page_area = tree.add_node(FormNode {
7203 name: "Page1".to_string(),
7204 node_type: FormNodeType::PageArea {
7205 content_areas: vec![ContentArea {
7206 name: "Body".to_string(),
7207 x: 0.0,
7208 y: 0.0,
7209 width: 400.0,
7210 height: 200.0,
7211 leader: None,
7212 trailer: Some(footer),
7213 }],
7214 },
7215 box_model: BoxModel {
7216 width: Some(400.0),
7217 height: Some(200.0),
7218 max_width: f64::MAX,
7219 max_height: f64::MAX,
7220 ..Default::default()
7221 },
7222 layout: LayoutStrategy::Positioned,
7223 children: vec![],
7224 occur: Occur::once(),
7225 font: FontMetrics::default(),
7226 calculate: None,
7227 validate: None,
7228 column_widths: vec![],
7229 col_span: 1,
7230 });
7231
7232 let root = tree.add_node(FormNode {
7233 name: "Root".to_string(),
7234 node_type: FormNodeType::Root,
7235 box_model: BoxModel {
7236 width: Some(400.0),
7237 height: Some(200.0),
7238 max_width: f64::MAX,
7239 max_height: f64::MAX,
7240 ..Default::default()
7241 },
7242 layout: LayoutStrategy::TopToBottom,
7243 children: vec![page_area, f1],
7244 occur: Occur::once(),
7245 font: FontMetrics::default(),
7246 calculate: None,
7247 validate: None,
7248 column_widths: vec![],
7249 col_span: 1,
7250 });
7251
7252 let engine = LayoutEngine::new(&tree);
7253 let result = engine.layout(root).unwrap();
7254
7255 let page = &result.pages[0];
7256 let footer_node = page.nodes.iter().find(|n| n.name == "PageFooter").unwrap();
7258 assert_eq!(footer_node.rect.y, 175.0);
7259 }
7260
7261 #[test]
7262 fn leader_and_trailer_reduce_content_space() {
7263 let mut tree = FormTree::new();
7265 let header = make_field(&mut tree, "Header", 300.0, 30.0);
7266 let footer = make_field(&mut tree, "Footer", 300.0, 20.0);
7267
7268 let mut fields = Vec::new();
7270 for i in 0..5 {
7271 fields.push(make_field(&mut tree, &format!("F{i}"), 300.0, 30.0));
7272 }
7273
7274 let page_area = tree.add_node(FormNode {
7275 name: "Page1".to_string(),
7276 node_type: FormNodeType::PageArea {
7277 content_areas: vec![ContentArea {
7278 name: "Body".to_string(),
7279 x: 0.0,
7280 y: 0.0,
7281 width: 400.0,
7282 height: 200.0, leader: Some(header),
7284 trailer: Some(footer),
7285 }],
7286 },
7287 box_model: BoxModel {
7288 width: Some(400.0),
7289 height: Some(200.0),
7290 max_width: f64::MAX,
7291 max_height: f64::MAX,
7292 ..Default::default()
7293 },
7294 layout: LayoutStrategy::Positioned,
7295 children: vec![],
7296 occur: Occur::once(),
7297 font: FontMetrics::default(),
7298 calculate: None,
7299 validate: None,
7300 column_widths: vec![],
7301 col_span: 1,
7302 });
7303
7304 let mut root_children = vec![page_area];
7305 root_children.extend(fields);
7306
7307 let root = tree.add_node(FormNode {
7308 name: "Root".to_string(),
7309 node_type: FormNodeType::Root,
7310 box_model: BoxModel {
7311 width: Some(400.0),
7312 height: Some(200.0),
7313 max_width: f64::MAX,
7314 max_height: f64::MAX,
7315 ..Default::default()
7316 },
7317 layout: LayoutStrategy::TopToBottom,
7318 children: root_children,
7319 occur: Occur::once(),
7320 font: FontMetrics::default(),
7321 calculate: None,
7322 validate: None,
7323 column_widths: vec![],
7324 col_span: 1,
7325 });
7326
7327 let engine = LayoutEngine::new(&tree);
7328 let result = engine.layout(root).unwrap();
7329
7330 assert_eq!(result.pages.len(), 1);
7332 let page = &result.pages[0];
7333 assert_eq!(page.nodes.len(), 7);
7335 }
7336
7337 #[test]
7338 fn leader_trailer_repeated_on_overflow_pages() {
7339 let mut tree = FormTree::new();
7341 let header = make_field(&mut tree, "Header", 300.0, 30.0);
7342 let footer = make_field(&mut tree, "Footer", 300.0, 20.0);
7343
7344 let mut fields = Vec::new();
7347 for i in 0..8 {
7348 fields.push(make_field(&mut tree, &format!("F{i}"), 300.0, 30.0));
7349 }
7350
7351 let page_area = tree.add_node(FormNode {
7352 name: "Page1".to_string(),
7353 node_type: FormNodeType::PageArea {
7354 content_areas: vec![ContentArea {
7355 name: "Body".to_string(),
7356 x: 0.0,
7357 y: 0.0,
7358 width: 400.0,
7359 height: 200.0,
7360 leader: Some(header),
7361 trailer: Some(footer),
7362 }],
7363 },
7364 box_model: BoxModel {
7365 width: Some(400.0),
7366 height: Some(200.0),
7367 max_width: f64::MAX,
7368 max_height: f64::MAX,
7369 ..Default::default()
7370 },
7371 layout: LayoutStrategy::Positioned,
7372 children: vec![],
7373 occur: Occur::once(),
7374 font: FontMetrics::default(),
7375 calculate: None,
7376 validate: None,
7377 column_widths: vec![],
7378 col_span: 1,
7379 });
7380
7381 let mut root_children = vec![page_area];
7382 root_children.extend(fields);
7383
7384 let root = tree.add_node(FormNode {
7385 name: "Root".to_string(),
7386 node_type: FormNodeType::Root,
7387 box_model: BoxModel {
7388 width: Some(400.0),
7389 height: Some(200.0),
7390 max_width: f64::MAX,
7391 max_height: f64::MAX,
7392 ..Default::default()
7393 },
7394 layout: LayoutStrategy::TopToBottom,
7395 children: root_children,
7396 occur: Occur::once(),
7397 font: FontMetrics::default(),
7398 calculate: None,
7399 validate: None,
7400 column_widths: vec![],
7401 col_span: 1,
7402 });
7403
7404 let engine = LayoutEngine::new(&tree);
7405 let result = engine.layout(root).unwrap();
7406
7407 assert_eq!(result.pages.len(), 2);
7408
7409 for page in &result.pages {
7411 let has_header = page.nodes.iter().any(|n| n.name == "Header");
7412 let has_footer = page.nodes.iter().any(|n| n.name == "Footer");
7413 assert!(has_header, "Page missing header");
7414 assert!(has_footer, "Page missing footer");
7415 }
7416 }
7417
7418 #[test]
7421 fn draw_node_growable_height_from_text() {
7422 let mut tree = FormTree::new();
7424 let draw = tree.add_node(FormNode {
7425 name: "Label".to_string(),
7426 node_type: FormNodeType::Draw(DrawContent::Text("Hello World".to_string())),
7427 box_model: BoxModel {
7428 width: Some(200.0),
7429 height: None, max_width: f64::MAX,
7431 max_height: f64::MAX,
7432 ..Default::default()
7433 },
7434 layout: LayoutStrategy::Positioned,
7435 children: vec![],
7436 occur: Occur::once(),
7437 font: FontMetrics::default(), calculate: None,
7439 validate: None,
7440 column_widths: vec![],
7441 col_span: 1,
7442 });
7443 let root = make_subform(
7444 &mut tree,
7445 "Root",
7446 LayoutStrategy::TopToBottom,
7447 Some(612.0),
7448 Some(792.0),
7449 vec![draw],
7450 );
7451
7452 let engine = LayoutEngine::new(&tree);
7453 let result = engine.layout(root).unwrap();
7454
7455 let page = &result.pages[0];
7456 let label = &page.nodes[0];
7457 assert_eq!(label.rect.height, 12.0);
7460 }
7461
7462 #[test]
7463 fn draw_node_text_wraps_in_narrow_width() {
7464 let mut tree = FormTree::new();
7466 let draw = tree.add_node(FormNode {
7467 name: "Label".to_string(),
7468 node_type: FormNodeType::Draw(DrawContent::Text("Hello World".to_string())),
7469 box_model: BoxModel {
7470 width: Some(40.0), height: None,
7472 max_width: f64::MAX,
7473 max_height: f64::MAX,
7474 ..Default::default()
7475 },
7476 layout: LayoutStrategy::Positioned,
7477 children: vec![],
7478 occur: Occur::once(),
7479 font: FontMetrics::default(),
7480 calculate: None,
7481 validate: None,
7482 column_widths: vec![],
7483 col_span: 1,
7484 });
7485 let root = make_subform(
7486 &mut tree,
7487 "Root",
7488 LayoutStrategy::TopToBottom,
7489 Some(612.0),
7490 Some(792.0),
7491 vec![draw],
7492 );
7493
7494 let engine = LayoutEngine::new(&tree);
7495 let result = engine.layout(root).unwrap();
7496
7497 let label = &result.pages[0].nodes[0];
7498 assert_eq!(label.rect.height, 24.0);
7500 }
7501
7502 #[test]
7503 fn field_produces_wrapped_text_content() {
7504 let mut tree = FormTree::new();
7505 let field = tree.add_node(FormNode {
7506 name: "Name".to_string(),
7507 node_type: FormNodeType::Field {
7508 value: "John".to_string(),
7509 },
7510 box_model: BoxModel {
7511 width: Some(200.0),
7512 height: Some(20.0),
7513 max_width: f64::MAX,
7514 max_height: f64::MAX,
7515 ..Default::default()
7516 },
7517 layout: LayoutStrategy::Positioned,
7518 children: vec![],
7519 occur: Occur::once(),
7520 font: FontMetrics::default(),
7521 calculate: None,
7522 validate: None,
7523 column_widths: vec![],
7524 col_span: 1,
7525 });
7526 let root = make_subform(
7527 &mut tree,
7528 "Root",
7529 LayoutStrategy::TopToBottom,
7530 Some(612.0),
7531 Some(792.0),
7532 vec![field],
7533 );
7534
7535 let engine = LayoutEngine::new(&tree);
7536 let result = engine.layout(root).unwrap();
7537
7538 let node = &result.pages[0].nodes[0];
7539 match &node.content {
7540 LayoutContent::WrappedText {
7541 lines, font_size, ..
7542 } => {
7543 assert_eq!(lines.len(), 1);
7544 assert_eq!(lines[0], "John");
7545 assert_eq!(*font_size, 10.0);
7546 }
7547 other => panic!("Expected WrappedText, got {:?}", other),
7548 }
7549 }
7550
7551 #[test]
7552 fn draw_growable_width_and_height_from_text() {
7553 let mut tree = FormTree::new();
7555 let draw = tree.add_node(FormNode {
7556 name: "Auto".to_string(),
7557 node_type: FormNodeType::Draw(DrawContent::Text("Test".to_string())),
7558 box_model: BoxModel {
7559 width: None,
7560 height: None,
7561 max_width: f64::MAX,
7562 max_height: f64::MAX,
7563 ..Default::default()
7564 },
7565 layout: LayoutStrategy::Positioned,
7566 children: vec![],
7567 occur: Occur::once(),
7568 font: FontMetrics::default(),
7569 calculate: None,
7570 validate: None,
7571 column_widths: vec![],
7572 col_span: 1,
7573 });
7574
7575 let engine = LayoutEngine::new(&tree);
7576 let size = engine.compute_extent(draw);
7577 assert!((size.width - 19.45).abs() < 0.1, "width={}", size.width);
7579 assert_eq!(size.height, 12.0);
7580 }
7581
7582 #[test]
7583 fn custom_font_size_affects_layout() {
7584 let mut tree = FormTree::new();
7585 let draw = tree.add_node(FormNode {
7586 name: "Big".to_string(),
7587 node_type: FormNodeType::Draw(DrawContent::Text("Hi".to_string())),
7588 box_model: BoxModel {
7589 width: None,
7590 height: None,
7591 max_width: f64::MAX,
7592 max_height: f64::MAX,
7593 ..Default::default()
7594 },
7595 layout: LayoutStrategy::Positioned,
7596 children: vec![],
7597 occur: Occur::once(),
7598 font: FontMetrics::new(20.0), calculate: None,
7600 validate: None,
7601 column_widths: vec![],
7602 col_span: 1,
7603 });
7604
7605 let engine = LayoutEngine::new(&tree);
7606 let size = engine.compute_extent(draw);
7607 assert!((size.width - 18.88).abs() < 0.1, "width={}", size.width);
7609 assert_eq!(size.height, 24.0);
7610 }
7611
7612 fn make_cell(tree: &mut FormTree, name: &str, w: f64, h: f64, col_span: i32) -> FormNodeId {
7617 tree.add_node(FormNode {
7618 name: name.to_string(),
7619 node_type: FormNodeType::Field {
7620 value: name.to_string(),
7621 },
7622 box_model: BoxModel {
7623 width: Some(w),
7624 height: Some(h),
7625 max_width: f64::MAX,
7626 max_height: f64::MAX,
7627 ..Default::default()
7628 },
7629 layout: LayoutStrategy::Positioned,
7630 children: vec![],
7631 occur: Occur::once(),
7632 font: FontMetrics::default(),
7633 calculate: None,
7634 validate: None,
7635 column_widths: vec![],
7636 col_span,
7637 })
7638 }
7639
7640 fn make_row(tree: &mut FormTree, name: &str, cells: Vec<FormNodeId>) -> FormNodeId {
7641 tree.add_node(FormNode {
7642 name: name.to_string(),
7643 node_type: FormNodeType::Subform,
7644 box_model: BoxModel {
7645 max_width: f64::MAX,
7646 max_height: f64::MAX,
7647 ..Default::default()
7648 },
7649 layout: LayoutStrategy::Row,
7650 children: cells,
7651 occur: Occur::once(),
7652 font: FontMetrics::default(),
7653 calculate: None,
7654 validate: None,
7655 column_widths: vec![],
7656 col_span: 1,
7657 })
7658 }
7659
7660 fn make_table(
7661 tree: &mut FormTree,
7662 name: &str,
7663 column_widths: Vec<f64>,
7664 rows: Vec<FormNodeId>,
7665 ) -> FormNodeId {
7666 tree.add_node(FormNode {
7667 name: name.to_string(),
7668 node_type: FormNodeType::Subform,
7669 box_model: BoxModel {
7670 max_width: f64::MAX,
7671 max_height: f64::MAX,
7672 ..Default::default()
7673 },
7674 layout: LayoutStrategy::Table,
7675 children: rows,
7676 occur: Occur::once(),
7677 font: FontMetrics::default(),
7678 calculate: None,
7679 validate: None,
7680 column_widths,
7681 col_span: 1,
7682 })
7683 }
7684
7685 #[test]
7686 fn table_basic_fixed_columns() {
7687 let mut tree = FormTree::new();
7688
7689 let c1 = make_cell(&mut tree, "A1", 100.0, 30.0, 1);
7691 let c2 = make_cell(&mut tree, "A2", 150.0, 30.0, 1);
7692 let c3 = make_cell(&mut tree, "A3", 200.0, 30.0, 1);
7693 let r1 = make_row(&mut tree, "Row1", vec![c1, c2, c3]);
7694
7695 let c4 = make_cell(&mut tree, "B1", 100.0, 25.0, 1);
7696 let c5 = make_cell(&mut tree, "B2", 150.0, 25.0, 1);
7697 let c6 = make_cell(&mut tree, "B3", 200.0, 25.0, 1);
7698 let r2 = make_row(&mut tree, "Row2", vec![c4, c5, c6]);
7699
7700 let table = make_table(&mut tree, "Table", vec![100.0, 150.0, 200.0], vec![r1, r2]);
7701
7702 let page_area = make_subform(
7703 &mut tree,
7704 "Page",
7705 LayoutStrategy::TopToBottom,
7706 Some(612.0),
7707 Some(792.0),
7708 vec![table],
7709 );
7710
7711 let engine = LayoutEngine::new(&tree);
7712 let layout = engine.layout(page_area).unwrap();
7713
7714 assert_eq!(layout.pages.len(), 1);
7715 let page = &layout.pages[0];
7716 assert_eq!(page.nodes.len(), 1);
7718 let table_node = &page.nodes[0];
7719 assert_eq!(table_node.name, "Table");
7720
7721 assert_eq!(table_node.children.len(), 2);
7723 let row1 = &table_node.children[0];
7724 let row2 = &table_node.children[1];
7725
7726 assert_eq!(row1.children.len(), 3);
7728 assert_eq!(row1.children[0].rect.x, 0.0);
7729 assert_eq!(row1.children[0].rect.width, 100.0);
7730 assert_eq!(row1.children[1].rect.x, 100.0);
7731 assert_eq!(row1.children[1].rect.width, 150.0);
7732 assert_eq!(row1.children[2].rect.x, 250.0);
7733 assert_eq!(row1.children[2].rect.width, 200.0);
7734
7735 assert_eq!(row2.rect.y, 30.0); assert_eq!(row2.children[0].rect.x, 0.0);
7738 }
7739
7740 #[test]
7741 fn table_auto_columns() {
7742 let mut tree = FormTree::new();
7743
7744 let c1 = make_cell(&mut tree, "A", 80.0, 20.0, 1);
7746 let c2 = make_cell(&mut tree, "B", 120.0, 20.0, 1);
7747 let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7748
7749 let c3 = make_cell(&mut tree, "C", 60.0, 20.0, 1);
7750 let c4 = make_cell(&mut tree, "D", 150.0, 20.0, 1);
7751 let r2 = make_row(&mut tree, "Row2", vec![c3, c4]);
7752
7753 let table = make_table(&mut tree, "Table", vec![-1.0, -1.0], vec![r1, r2]);
7755
7756 let page = make_subform(
7757 &mut tree,
7758 "Page",
7759 LayoutStrategy::TopToBottom,
7760 Some(612.0),
7761 Some(792.0),
7762 vec![table],
7763 );
7764
7765 let engine = LayoutEngine::new(&tree);
7766 let layout = engine.layout(page).unwrap();
7767
7768 let table_node = &layout.pages[0].nodes[0];
7769 let row1 = &table_node.children[0];
7770
7771 assert_eq!(row1.children[0].rect.width, 80.0);
7773 assert_eq!(row1.children[1].rect.width, 150.0);
7774 assert_eq!(row1.children[1].rect.x, 80.0);
7775 }
7776
7777 #[test]
7778 fn table_col_span() {
7779 let mut tree = FormTree::new();
7780
7781 let c1 = make_cell(&mut tree, "Span2", 200.0, 20.0, 2); let c2 = make_cell(&mut tree, "Single", 100.0, 20.0, 1);
7784 let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7785
7786 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7787
7788 let page = make_subform(
7789 &mut tree,
7790 "Page",
7791 LayoutStrategy::TopToBottom,
7792 Some(612.0),
7793 Some(792.0),
7794 vec![table],
7795 );
7796
7797 let engine = LayoutEngine::new(&tree);
7798 let layout = engine.layout(page).unwrap();
7799
7800 let row = &layout.pages[0].nodes[0].children[0];
7801 assert_eq!(row.children[0].rect.width, 200.0);
7803 assert_eq!(row.children[0].rect.x, 0.0);
7804 assert_eq!(row.children[1].rect.x, 200.0);
7806 assert_eq!(row.children[1].rect.width, 100.0);
7807 }
7808
7809 #[test]
7810 fn table_col_span_rest() {
7811 let mut tree = FormTree::new();
7812
7813 let c1 = make_cell(&mut tree, "First", 100.0, 20.0, 1);
7815 let c2 = make_cell(&mut tree, "Rest", 200.0, 20.0, -1); let r1 = make_row(&mut tree, "Row1", vec![c1, c2]);
7817
7818 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7819
7820 let page = make_subform(
7821 &mut tree,
7822 "Page",
7823 LayoutStrategy::TopToBottom,
7824 Some(612.0),
7825 Some(792.0),
7826 vec![table],
7827 );
7828
7829 let engine = LayoutEngine::new(&tree);
7830 let layout = engine.layout(page).unwrap();
7831
7832 let row = &layout.pages[0].nodes[0].children[0];
7833 assert_eq!(row.children[0].rect.width, 100.0);
7835 assert_eq!(row.children[1].rect.x, 100.0);
7837 assert_eq!(row.children[1].rect.width, 200.0);
7838 }
7839
7840 #[test]
7841 fn table_row_height_equalization() {
7842 let mut tree = FormTree::new();
7843
7844 let c1 = make_cell(&mut tree, "Short", 100.0, 30.0, 1);
7846 let c2 = make_cell(&mut tree, "Tall", 100.0, 50.0, 1);
7847 let c3 = make_cell(&mut tree, "Tiny", 100.0, 20.0, 1);
7848 let r1 = make_row(&mut tree, "Row1", vec![c1, c2, c3]);
7849
7850 let table = make_table(&mut tree, "Table", vec![100.0, 100.0, 100.0], vec![r1]);
7851
7852 let page = make_subform(
7853 &mut tree,
7854 "Page",
7855 LayoutStrategy::TopToBottom,
7856 Some(612.0),
7857 Some(792.0),
7858 vec![table],
7859 );
7860
7861 let engine = LayoutEngine::new(&tree);
7862 let layout = engine.layout(page).unwrap();
7863
7864 let row = &layout.pages[0].nodes[0].children[0];
7865 assert_eq!(row.children[0].rect.height, 50.0);
7867 assert_eq!(row.children[1].rect.height, 50.0);
7868 assert_eq!(row.children[2].rect.height, 50.0);
7869 assert_eq!(row.rect.height, 50.0);
7871 }
7872
7873 #[test]
7874 fn table_growable_height() {
7875 let mut tree = FormTree::new();
7876
7877 let c1 = make_cell(&mut tree, "A", 100.0, 30.0, 1);
7878 let r1 = make_row(&mut tree, "Row1", vec![c1]);
7879
7880 let c2 = make_cell(&mut tree, "B", 100.0, 40.0, 1);
7881 let r2 = make_row(&mut tree, "Row2", vec![c2]);
7882
7883 let table = make_table(&mut tree, "Table", vec![100.0], vec![r1, r2]);
7885
7886 let engine = LayoutEngine::new(&tree);
7887 let extent = engine.compute_extent(table);
7888
7889 assert_eq!(extent.height, 70.0);
7891 assert_eq!(extent.width, 100.0);
7893 }
7894
7895 #[test]
7896 fn table_empty() {
7897 let mut tree = FormTree::new();
7898 let table = make_table(&mut tree, "EmptyTable", vec![100.0, 200.0], vec![]);
7899
7900 let page = make_subform(
7901 &mut tree,
7902 "Page",
7903 LayoutStrategy::TopToBottom,
7904 Some(612.0),
7905 Some(792.0),
7906 vec![table],
7907 );
7908
7909 let engine = LayoutEngine::new(&tree);
7910 let layout = engine.layout(page).unwrap();
7911
7912 let table_node = &layout.pages[0].nodes[0];
7914 assert_eq!(table_node.children.len(), 0);
7915 }
7916
7917 #[test]
7918 fn table_splits_across_pages() {
7919 let mut tree = FormTree::new();
7920
7921 let mut rows = Vec::new();
7923 for i in 0..10 {
7924 let cell = make_cell(&mut tree, &format!("C{}", i), 200.0, 100.0, 1);
7925 let row = make_row(&mut tree, &format!("Row{}", i), vec![cell]);
7926 rows.push(row);
7927 }
7928
7929 let table = make_table(&mut tree, "Table", vec![200.0], rows);
7931
7932 let page_area = tree.add_node(FormNode {
7934 name: "PageArea".to_string(),
7935 node_type: FormNodeType::PageArea {
7936 content_areas: vec![ContentArea {
7937 name: "Body".to_string(),
7938 x: 0.0,
7939 y: 0.0,
7940 width: 400.0,
7941 height: 400.0,
7942 leader: None,
7943 trailer: None,
7944 }],
7945 },
7946 box_model: BoxModel {
7947 width: Some(400.0),
7948 height: Some(400.0),
7949 max_width: f64::MAX,
7950 max_height: f64::MAX,
7951 ..Default::default()
7952 },
7953 layout: LayoutStrategy::Positioned,
7954 children: vec![],
7955 occur: Occur::once(),
7956 font: FontMetrics::default(),
7957 calculate: None,
7958 validate: None,
7959 column_widths: vec![],
7960 col_span: 1,
7961 });
7962
7963 let root = tree.add_node(FormNode {
7964 name: "Root".to_string(),
7965 node_type: FormNodeType::Root,
7966 box_model: BoxModel {
7967 width: Some(400.0),
7968 height: Some(400.0),
7969 max_width: f64::MAX,
7970 max_height: f64::MAX,
7971 ..Default::default()
7972 },
7973 layout: LayoutStrategy::TopToBottom,
7974 children: vec![page_area, table],
7975 occur: Occur::once(),
7976 font: FontMetrics::default(),
7977 calculate: None,
7978 validate: None,
7979 column_widths: vec![],
7980 col_span: 1,
7981 });
7982
7983 let engine = LayoutEngine::new(&tree);
7984 let result = engine.layout(root).unwrap();
7985
7986 assert_eq!(result.pages.len(), 3);
7988 }
7989
7990 #[test]
7991 fn resolve_display_value_maps_save_to_display() {
7992 let meta = FormNodeMeta {
7993 field_kind: FieldKind::Dropdown,
7994 display_items: vec![
7995 "United States".to_string(),
7996 "United Kingdom".to_string(),
7997 "Canada".to_string(),
7998 ],
7999 save_items: vec!["US".to_string(), "UK".to_string(), "CA".to_string()],
8000 ..Default::default()
8001 };
8002
8003 assert_eq!(resolve_display_value("UK", &meta), "United Kingdom");
8005 assert_eq!(resolve_display_value("CA", &meta), "Canada");
8007 assert_eq!(resolve_display_value("DE", &meta), "DE");
8009 assert_eq!(resolve_display_value("", &meta), "");
8011 }
8012
8013 #[test]
8014 fn resolve_display_value_no_save_items_passthrough() {
8015 let meta = FormNodeMeta {
8016 field_kind: FieldKind::Dropdown,
8017 display_items: vec!["Red".to_string(), "Green".to_string()],
8018 ..Default::default()
8019 };
8020 assert_eq!(resolve_display_value("Red", &meta), "Red");
8022 }
8023
8024 #[test]
8025 fn resolve_display_value_non_dropdown_passthrough() {
8026 let meta = FormNodeMeta {
8027 field_kind: FieldKind::Text,
8028 save_items: vec!["US".to_string()],
8029 display_items: vec!["United States".to_string()],
8030 ..Default::default()
8031 };
8032 assert_eq!(resolve_display_value("US", &meta), "US");
8034 }
8035
8036 #[test]
8037 fn resolve_display_value_numeric_edit_strips_trailing_zeros() {
8038 let meta = FormNodeMeta {
8039 field_kind: FieldKind::NumericEdit,
8040 ..Default::default()
8041 };
8042
8043 assert_eq!(resolve_display_value("1.00000000", &meta), "1");
8044 assert_eq!(resolve_display_value("3.50", &meta), "3.5");
8045 assert_eq!(resolve_display_value("100.00", &meta), "100");
8046 assert_eq!(resolve_display_value("0.12345", &meta), "0.12345");
8047 assert_eq!(resolve_display_value("42", &meta), "42");
8048 assert_eq!(resolve_display_value("abc", &meta), "abc");
8050 assert_eq!(resolve_display_value("", &meta), "");
8051 }
8052
8053 #[test]
8054 fn resolve_display_value_date_time_picker_uses_iso_date_prefix() {
8055 let meta = FormNodeMeta {
8056 field_kind: FieldKind::DateTimePicker,
8057 ..Default::default()
8058 };
8059
8060 assert_eq!(resolve_display_value("2026-04-12", &meta), "2026-04-12");
8061 assert_eq!(
8062 resolve_display_value("2026-04-12T13:45:00Z", &meta),
8063 "2026-04-12"
8064 );
8065 assert_eq!(
8066 resolve_display_value("2026-04-12T13:45:00+02:00", &meta),
8067 "2026-04-12"
8068 );
8069 assert_eq!(resolve_display_value("12/04/2026", &meta), "12/04/2026");
8071 assert_eq!(resolve_display_value("abc", &meta), "abc");
8072 assert_eq!(resolve_display_value("", &meta), "");
8073 }
8074
8075 #[test]
8080 fn estimated_heap_bytes_is_positive_for_non_empty_layout() {
8081 let mut tree = FormTree::new();
8082 let f1 = make_field(&mut tree, "Field1", 100.0, 20.0);
8083 let f2 = make_field(&mut tree, "Field2", 100.0, 20.0);
8084 let root = make_subform(
8085 &mut tree,
8086 "Root",
8087 LayoutStrategy::TopToBottom,
8088 Some(200.0),
8089 Some(200.0),
8090 vec![f1, f2],
8091 );
8092
8093 let engine = LayoutEngine::new(&tree);
8094 let layout = engine.layout(root).unwrap();
8095
8096 assert!(!layout.pages.is_empty(), "expected at least one page");
8098 let bytes = layout.estimated_heap_bytes();
8099 assert!(
8100 bytes > 0,
8101 "estimated_heap_bytes should be > 0 for non-empty layout"
8102 );
8103 }
8104}
8105
8106#[cfg(test)]
8107mod halign_tests {
8108 use super::*;
8109 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
8110 use crate::text::FontMetrics;
8111 use crate::types::{BoxModel, LayoutStrategy, TextAlign};
8112
8113 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
8114 tree.add_node(FormNode {
8115 name: name.to_string(),
8116 node_type: FormNodeType::Field {
8117 value: name.to_string(),
8118 },
8119 box_model: BoxModel {
8120 width: Some(w),
8121 height: Some(h),
8122 max_width: f64::MAX,
8123 max_height: f64::MAX,
8124 ..Default::default()
8125 },
8126 layout: LayoutStrategy::Positioned,
8127 children: vec![],
8128 occur: Occur::once(),
8129 font: FontMetrics::default(),
8130 calculate: None,
8131 validate: None,
8132 column_widths: vec![],
8133 col_span: 1,
8134 })
8135 }
8136
8137 fn make_subform(
8138 tree: &mut FormTree,
8139 name: &str,
8140 strategy: LayoutStrategy,
8141 w: Option<f64>,
8142 h: Option<f64>,
8143 children: Vec<FormNodeId>,
8144 ) -> FormNodeId {
8145 tree.add_node(FormNode {
8146 name: name.to_string(),
8147 node_type: FormNodeType::Subform,
8148 box_model: BoxModel {
8149 width: w,
8150 height: h,
8151 max_width: f64::MAX,
8152 max_height: f64::MAX,
8153 ..Default::default()
8154 },
8155 layout: strategy,
8156 children,
8157 occur: Occur::once(),
8158 font: FontMetrics::default(),
8159 calculate: None,
8160 validate: None,
8161 column_widths: vec![],
8162 col_span: 1,
8163 })
8164 }
8165
8166 #[test]
8169 fn tb_halign_right_offsets_child() {
8170 let mut tree = FormTree::new();
8171 let child = make_field(&mut tree, "A", 200.0, 30.0);
8172 tree.meta_mut(child).style.h_align = Some(TextAlign::Right);
8173
8174 let parent = make_subform(
8175 &mut tree,
8176 "Page",
8177 LayoutStrategy::TopToBottom,
8178 Some(500.0),
8179 Some(500.0),
8180 vec![child],
8181 );
8182
8183 let engine = LayoutEngine::new(&tree);
8184 let layout = engine.layout(parent).unwrap();
8185
8186 let child_node = &layout.pages[0].nodes[0];
8187 assert_eq!(child_node.rect.x, 300.0);
8189 }
8190
8191 #[test]
8193 fn tb_halign_center_centers_child() {
8194 let mut tree = FormTree::new();
8195 let child = make_field(&mut tree, "A", 200.0, 30.0);
8196 tree.meta_mut(child).style.h_align = Some(TextAlign::Center);
8197
8198 let parent = make_subform(
8199 &mut tree,
8200 "Page",
8201 LayoutStrategy::TopToBottom,
8202 Some(500.0),
8203 Some(500.0),
8204 vec![child],
8205 );
8206
8207 let engine = LayoutEngine::new(&tree);
8208 let layout = engine.layout(parent).unwrap();
8209
8210 let child_node = &layout.pages[0].nodes[0];
8211 assert_eq!(child_node.rect.x, 150.0);
8213 }
8214
8215 #[test]
8217 fn tb_halign_default_left() {
8218 let mut tree = FormTree::new();
8219 let child = make_field(&mut tree, "A", 200.0, 30.0);
8220 let parent = make_subform(
8223 &mut tree,
8224 "Page",
8225 LayoutStrategy::TopToBottom,
8226 Some(500.0),
8227 Some(500.0),
8228 vec![child],
8229 );
8230
8231 let engine = LayoutEngine::new(&tree);
8232 let layout = engine.layout(parent).unwrap();
8233
8234 let child_node = &layout.pages[0].nodes[0];
8235 assert_eq!(child_node.rect.x, 0.0);
8236 }
8237
8238 #[test]
8242 fn lr_tb_halign_right_shifts_row() {
8243 let mut tree = FormTree::new();
8244 let a = make_field(&mut tree, "A", 60.0, 20.0);
8245 let b = make_field(&mut tree, "B", 60.0, 20.0);
8246 let c = make_field(&mut tree, "C", 60.0, 20.0);
8247 tree.meta_mut(a).style.h_align = Some(TextAlign::Right);
8248 tree.meta_mut(b).style.h_align = Some(TextAlign::Right);
8249 tree.meta_mut(c).style.h_align = Some(TextAlign::Right);
8250
8251 let parent = make_subform(
8252 &mut tree,
8253 "Page",
8254 LayoutStrategy::LeftToRightTB,
8255 Some(300.0),
8256 Some(300.0),
8257 vec![a, b, c],
8258 );
8259
8260 let engine = LayoutEngine::new(&tree);
8261 let layout = engine.layout(parent).unwrap();
8262 let page = &layout.pages[0];
8263
8264 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); }
8269
8270 #[test]
8272 fn rl_tb_halign_left_overrides_flow() {
8273 let mut tree = FormTree::new();
8274 let child = make_field(&mut tree, "A", 100.0, 30.0);
8275 tree.meta_mut(child).style.h_align = Some(TextAlign::Left);
8276
8277 let parent = make_subform(
8278 &mut tree,
8279 "Page",
8280 LayoutStrategy::RightToLeftTB,
8281 Some(500.0),
8282 Some(500.0),
8283 vec![child],
8284 );
8285
8286 let engine = LayoutEngine::new(&tree);
8287 let layout = engine.layout(parent).unwrap();
8288
8289 let child_node = &layout.pages[0].nodes[0];
8290 assert_eq!(child_node.rect.x, 0.0);
8292 }
8293
8294 fn make_positioned_field(
8299 tree: &mut FormTree,
8300 name: &str,
8301 x: f64,
8302 y: f64,
8303 w: f64,
8304 h: f64,
8305 ) -> FormNodeId {
8306 tree.add_node(FormNode {
8307 name: name.to_string(),
8308 node_type: FormNodeType::Field {
8309 value: name.to_string(),
8310 },
8311 box_model: BoxModel {
8312 width: Some(w),
8313 height: Some(h),
8314 x,
8315 y,
8316 max_width: f64::MAX,
8317 max_height: f64::MAX,
8318 ..Default::default()
8319 },
8320 layout: LayoutStrategy::Positioned,
8321 children: vec![],
8322 occur: Occur::once(),
8323 font: FontMetrics::default(),
8324 calculate: None,
8325 validate: None,
8326 column_widths: vec![],
8327 col_span: 1,
8328 })
8329 }
8330
8331 fn layout_with_anchor(anchor: crate::form::AnchorType, x: f64, y: f64, w: f64, h: f64) -> Rect {
8332 let mut tree = FormTree::new();
8333 let field = make_positioned_field(&mut tree, "F", x, y, w, h);
8334 tree.meta_mut(field).anchor_type = anchor;
8335 let root = make_subform(
8336 &mut tree,
8337 "Root",
8338 LayoutStrategy::Positioned,
8339 Some(612.0),
8340 Some(792.0),
8341 vec![field],
8342 );
8343 let engine = LayoutEngine::new(&tree);
8344 let result = engine.layout(root).unwrap();
8345 result.pages[0].nodes[0].rect
8346 }
8347
8348 #[test]
8349 fn anchor_top_left_no_adjustment() {
8350 use crate::form::AnchorType;
8351 let r = layout_with_anchor(AnchorType::TopLeft, 100.0, 200.0, 80.0, 40.0);
8352 assert_eq!(r.x, 100.0);
8353 assert_eq!(r.y, 200.0);
8354 assert_eq!(r.width, 80.0);
8355 assert_eq!(r.height, 40.0);
8356 }
8357 #[test]
8358 fn anchor_top_center() {
8359 use crate::form::AnchorType;
8360 let r = layout_with_anchor(AnchorType::TopCenter, 100.0, 200.0, 80.0, 40.0);
8361 assert_eq!(r.x, 60.0);
8362 assert_eq!(r.y, 200.0);
8363 }
8364 #[test]
8365 fn anchor_top_right() {
8366 use crate::form::AnchorType;
8367 let r = layout_with_anchor(AnchorType::TopRight, 100.0, 200.0, 80.0, 40.0);
8368 assert_eq!(r.x, 20.0);
8369 assert_eq!(r.y, 200.0);
8370 }
8371 #[test]
8372 fn anchor_middle_left() {
8373 use crate::form::AnchorType;
8374 let r = layout_with_anchor(AnchorType::MiddleLeft, 100.0, 200.0, 80.0, 40.0);
8375 assert_eq!(r.x, 100.0);
8376 assert_eq!(r.y, 180.0);
8377 }
8378 #[test]
8379 fn anchor_middle_center() {
8380 use crate::form::AnchorType;
8381 let r = layout_with_anchor(AnchorType::MiddleCenter, 100.0, 200.0, 80.0, 40.0);
8382 assert_eq!(r.x, 60.0);
8383 assert_eq!(r.y, 180.0);
8384 }
8385 #[test]
8386 fn anchor_middle_right() {
8387 use crate::form::AnchorType;
8388 let r = layout_with_anchor(AnchorType::MiddleRight, 100.0, 200.0, 80.0, 40.0);
8389 assert_eq!(r.x, 20.0);
8390 assert_eq!(r.y, 180.0);
8391 }
8392 #[test]
8393 fn anchor_bottom_left() {
8394 use crate::form::AnchorType;
8395 let r = layout_with_anchor(AnchorType::BottomLeft, 100.0, 200.0, 80.0, 40.0);
8396 assert_eq!(r.x, 100.0);
8397 assert_eq!(r.y, 160.0);
8398 }
8399 #[test]
8400 fn anchor_bottom_center() {
8401 use crate::form::AnchorType;
8402 let r = layout_with_anchor(AnchorType::BottomCenter, 100.0, 200.0, 80.0, 40.0);
8403 assert_eq!(r.x, 60.0);
8404 assert_eq!(r.y, 160.0);
8405 }
8406 #[test]
8407 fn anchor_bottom_right() {
8408 use crate::form::AnchorType;
8409 let r = layout_with_anchor(AnchorType::BottomRight, 100.0, 200.0, 80.0, 40.0);
8410 assert_eq!(r.x, 20.0);
8411 assert_eq!(r.y, 160.0);
8412 }
8413}
8414
8415#[cfg(test)]
8420mod container_node_tests {
8421 use super::*;
8422 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
8423 use crate::text::FontMetrics;
8424 use crate::types::{BoxModel, LayoutStrategy};
8425
8426 fn make_field(tree: &mut FormTree, name: &str, x: f64, y: f64, w: f64, h: f64) -> FormNodeId {
8427 tree.add_node(FormNode {
8428 name: name.to_string(),
8429 node_type: FormNodeType::Field {
8430 value: name.to_string(),
8431 },
8432 box_model: BoxModel {
8433 width: Some(w),
8434 height: Some(h),
8435 x,
8436 y,
8437 max_width: f64::MAX,
8438 max_height: f64::MAX,
8439 ..Default::default()
8440 },
8441 layout: LayoutStrategy::Positioned,
8442 children: vec![],
8443 occur: Occur::once(),
8444 font: FontMetrics::default(),
8445 calculate: None,
8446 validate: None,
8447 column_widths: vec![],
8448 col_span: 1,
8449 })
8450 }
8451
8452 fn make_container(
8453 tree: &mut FormTree,
8454 name: &str,
8455 node_type: FormNodeType,
8456 strategy: LayoutStrategy,
8457 w: f64,
8458 h: f64,
8459 children: Vec<FormNodeId>,
8460 ) -> FormNodeId {
8461 tree.add_node(FormNode {
8462 name: name.to_string(),
8463 node_type,
8464 box_model: BoxModel {
8465 width: Some(w),
8466 height: Some(h),
8467 max_width: f64::MAX,
8468 max_height: f64::MAX,
8469 ..Default::default()
8470 },
8471 layout: strategy,
8472 children,
8473 occur: Occur::once(),
8474 font: FontMetrics::default(),
8475 calculate: None,
8476 validate: None,
8477 column_widths: vec![],
8478 col_span: 1,
8479 })
8480 }
8481
8482 #[test]
8486 fn area_node_positions_children_absolutely() {
8487 let mut tree = FormTree::new();
8488 let child = make_field(&mut tree, "Child", 10.0, 20.0, 50.0, 15.0);
8489 let area = make_container(
8490 &mut tree,
8491 "MyArea",
8492 FormNodeType::Area,
8493 LayoutStrategy::Positioned,
8494 200.0,
8495 100.0,
8496 vec![child],
8497 );
8498 let root = make_container(
8500 &mut tree,
8501 "Root",
8502 FormNodeType::Subform,
8503 LayoutStrategy::TopToBottom,
8504 200.0,
8505 200.0,
8506 vec![area],
8507 );
8508
8509 let engine = LayoutEngine::new(&tree);
8510 let result = engine.layout(root).unwrap();
8511
8512 assert_eq!(result.pages.len(), 1);
8513 let area_node = &result.pages[0].nodes[0];
8514 assert_eq!(area_node.name, "MyArea");
8515 assert_eq!(area_node.children.len(), 1);
8516 let child_node = &area_node.children[0];
8518 assert_eq!(child_node.name, "Child");
8519 assert_eq!(child_node.rect.x, 10.0);
8520 assert_eq!(child_node.rect.y, 20.0);
8521 }
8522
8523 #[test]
8526 fn excl_group_lays_out_children_top_to_bottom() {
8527 let mut tree = FormTree::new();
8528 let opt_a = make_field(&mut tree, "OptionA", 0.0, 0.0, 100.0, 20.0);
8529 let opt_b = make_field(&mut tree, "OptionB", 0.0, 0.0, 100.0, 20.0);
8530 let excl = make_container(
8531 &mut tree,
8532 "MyGroup",
8533 FormNodeType::ExclGroup,
8534 LayoutStrategy::TopToBottom,
8535 200.0,
8536 100.0,
8537 vec![opt_a, opt_b],
8538 );
8539 let root = make_container(
8540 &mut tree,
8541 "Root",
8542 FormNodeType::Subform,
8543 LayoutStrategy::TopToBottom,
8544 200.0,
8545 200.0,
8546 vec![excl],
8547 );
8548
8549 let engine = LayoutEngine::new(&tree);
8550 let result = engine.layout(root).unwrap();
8551
8552 assert_eq!(result.pages.len(), 1);
8553 let group_node = &result.pages[0].nodes[0];
8554 assert_eq!(group_node.name, "MyGroup");
8555 assert_eq!(group_node.children.len(), 2);
8556 assert_eq!(group_node.children[0].rect.y, 0.0);
8558 assert_eq!(group_node.children[1].rect.y, 20.0);
8559 }
8560
8561 #[test]
8564 fn subform_set_is_transparent_container() {
8565 let mut tree = FormTree::new();
8566 let field_a = make_field(&mut tree, "A", 0.0, 0.0, 100.0, 20.0);
8567 let field_b = make_field(&mut tree, "B", 0.0, 0.0, 100.0, 20.0);
8568 let set = make_container(
8570 &mut tree,
8571 "MySet",
8572 FormNodeType::SubformSet,
8573 LayoutStrategy::TopToBottom,
8574 200.0,
8575 100.0,
8576 vec![field_a, field_b],
8577 );
8578 let root = make_container(
8579 &mut tree,
8580 "Root",
8581 FormNodeType::Subform,
8582 LayoutStrategy::TopToBottom,
8583 200.0,
8584 200.0,
8585 vec![set],
8586 );
8587
8588 let engine = LayoutEngine::new(&tree);
8589 let result = engine.layout(root).unwrap();
8590
8591 assert_eq!(result.pages.len(), 1);
8592 let page = &result.pages[0];
8594 fn count_named(nodes: &[LayoutNode], name: &str) -> usize {
8595 nodes
8596 .iter()
8597 .map(|n| usize::from(n.name == name) + count_named(&n.children, name))
8598 .sum()
8599 }
8600 assert!(
8601 count_named(&page.nodes, "A") >= 1,
8602 "Field A should appear in layout"
8603 );
8604 assert!(
8605 count_named(&page.nodes, "B") >= 1,
8606 "Field B should appear in layout"
8607 );
8608 }
8609}
8610
8611#[cfg(test)]
8624mod keep_chain_tests {
8625 use super::*;
8626 use crate::form::{FormNode, FormNodeType, FormTree, Occur};
8627 use crate::text::FontMetrics;
8628 use crate::types::{BoxModel, LayoutStrategy};
8629
8630 fn make_field(tree: &mut FormTree, name: &str, w: f64, h: f64) -> FormNodeId {
8631 tree.add_node(FormNode {
8632 name: name.to_string(),
8633 node_type: FormNodeType::Field {
8634 value: name.to_string(),
8635 },
8636 box_model: BoxModel {
8637 width: Some(w),
8638 height: Some(h),
8639 max_width: f64::MAX,
8640 max_height: f64::MAX,
8641 ..Default::default()
8642 },
8643 layout: LayoutStrategy::Positioned,
8644 children: vec![],
8645 occur: Occur::once(),
8646 font: FontMetrics::default(),
8647 calculate: None,
8648 validate: None,
8649 column_widths: vec![],
8650 col_span: 1,
8651 })
8652 }
8653
8654 #[test]
8667 fn keep_next_pushes_heading_and_body_to_same_page() {
8668 let mut tree = FormTree::new();
8669
8670 let filler = make_field(&mut tree, "Filler", 200.0, 70.0);
8671 let heading = make_field(&mut tree, "Heading", 200.0, 40.0);
8672 let body = make_field(&mut tree, "Body", 200.0, 40.0);
8673
8674 tree.meta_mut(heading).keep_next_content_area = true;
8676
8677 let root = tree.add_node(FormNode {
8678 name: "Root".to_string(),
8679 node_type: FormNodeType::Root,
8680 box_model: BoxModel {
8681 width: Some(200.0),
8682 height: Some(100.0),
8683 max_width: f64::MAX,
8684 max_height: f64::MAX,
8685 ..Default::default()
8686 },
8687 layout: LayoutStrategy::TopToBottom,
8688 children: vec![filler, heading, body],
8689 occur: Occur::once(),
8690 font: FontMetrics::default(),
8691 calculate: None,
8692 validate: None,
8693 column_widths: vec![],
8694 col_span: 1,
8695 });
8696
8697 let engine = LayoutEngine::new(&tree);
8698 let result = engine.layout(root).unwrap();
8699
8700 assert_eq!(
8702 result.pages.len(),
8703 2,
8704 "expected filler on page 1, heading+body on page 2"
8705 );
8706
8707 let p1_names: Vec<&str> = result.pages[0]
8709 .nodes
8710 .iter()
8711 .map(|n| n.name.as_str())
8712 .collect();
8713 assert!(p1_names.contains(&"Filler"), "Filler should be on page 1");
8714 assert!(
8715 !p1_names.contains(&"Heading"),
8716 "Heading should NOT be on page 1"
8717 );
8718 assert!(!p1_names.contains(&"Body"), "Body should NOT be on page 1");
8719
8720 let p2_names: Vec<&str> = result.pages[1]
8722 .nodes
8723 .iter()
8724 .map(|n| n.name.as_str())
8725 .collect();
8726 assert!(p2_names.contains(&"Heading"), "Heading should be on page 2");
8727 assert!(p2_names.contains(&"Body"), "Body should be on page 2");
8728 }
8729}