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