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