1mod block_layout;
6mod inline_layout;
7mod page_layout;
8mod page_master;
9mod table_layout;
10pub(super) mod types;
11
12use crate::area::{Area, AreaContent, AreaTree, AreaType, TraitSet};
13use crate::layout::{
14 extract_clear, extract_column_count, extract_column_gap, extract_space_after, extract_traits,
15 BlockLayoutContext, BorderCollapse, ColumnInfo, ColumnWidth, ListLayout, PageNumberResolver,
16 TableLayout, TableLayoutMode,
17};
18use fop_core::{FoArena, FoNodeData, NodeId, PropertyId};
19use fop_types::{FontRegistry, Length, Point, Rect, Result, Size};
20
21use types::{FloatManager, MarkerMap};
22
23pub use types::{ClearSide, FloatSide, MultiColumnLayout};
24
25pub struct LayoutEngine {
27 pub(super) page_width: Length,
29
30 pub(super) page_height: Length,
32
33 #[allow(dead_code)]
35 pub(super) font_registry: FontRegistry,
36
37 pub(super) streaming_mode: bool,
40}
41
42impl LayoutEngine {
43 pub fn new() -> Self {
45 Self {
46 page_width: Length::from_mm(210.0), page_height: Length::from_mm(297.0), font_registry: FontRegistry::new(),
49 streaming_mode: false,
50 }
51 }
52
53 pub fn with_streaming_mode(mut self, enabled: bool) -> Self {
58 self.streaming_mode = enabled;
59 self
60 }
61
62 pub fn is_streaming_mode(&self) -> bool {
64 self.streaming_mode
65 }
66
67 pub fn layout(&self, fo_tree: &FoArena) -> Result<AreaTree> {
69 let mut area_tree = AreaTree::new();
70 let mut resolver = PageNumberResolver::new();
71 let mut marker_map = MarkerMap::new();
72
73 if let Some((root_id, _)) = fo_tree.root() {
75 self.layout_node(
76 fo_tree,
77 root_id,
78 &mut area_tree,
79 None,
80 &mut resolver,
81 &mut marker_map,
82 )?;
83 }
84
85 self.resolve_citations(&mut area_tree, &resolver)?;
87
88 Ok(area_tree)
89 }
90
91 fn resolve_citations(
93 &self,
94 area_tree: &mut AreaTree,
95 resolver: &PageNumberResolver,
96 ) -> Result<()> {
97 for (area_id, ref_id) in resolver.get_citations() {
98 if let Some(page_number) = resolver.get_page_number(ref_id) {
99 if let Some(area_node) = area_tree.get_mut(*area_id) {
101 area_node.area.content = Some(AreaContent::Text(page_number.to_string()));
102 }
103 } else {
104 if let Some(area_node) = area_tree.get_mut(*area_id) {
106 area_node.area.content = Some(AreaContent::Text("??".to_string()));
107 }
108 }
109 }
110 Ok(())
111 }
112
113 pub(in crate::layout::engine) fn layout_node(
115 &self,
116 fo_tree: &FoArena,
117 node_id: NodeId,
118 area_tree: &mut AreaTree,
119 parent_area: Option<crate::area::AreaId>,
120 resolver: &mut PageNumberResolver,
121 marker_map: &mut MarkerMap,
122 ) -> Result<Option<crate::area::AreaId>> {
123 let node = fo_tree
124 .get(node_id)
125 .ok_or_else(|| fop_types::FopError::Generic(format!("Node {} not found", node_id)))?;
126
127 let area_id = match &node.data {
128 FoNodeData::Root => {
129 let children = fo_tree.children(node_id);
131 for child_id in children {
132 self.layout_node(
133 fo_tree,
134 child_id,
135 area_tree,
136 parent_area,
137 resolver,
138 marker_map,
139 )?;
140 }
141 None
142 }
143
144 FoNodeData::LayoutMasterSet => {
145 None
147 }
148
149 FoNodeData::PageSequence {
150 properties,
151 master_reference,
152 ..
153 } => {
154 let master_ref = master_reference.clone();
156 let geom = self.extract_page_region_geometry(fo_tree, &master_ref);
157
158 let page_rect = Rect::from_point_size(
160 Point::ZERO,
161 Size::new(geom.page_width, geom.page_height),
162 );
163
164 let mut traits = TraitSet::default();
165 if let Ok(color) = properties.get(PropertyId::BackgroundColor) {
166 traits.background_color = color.as_color();
167 }
168
169 let area = Area::new(AreaType::Page, page_rect).with_traits(traits);
170 let area_id = area_tree.add_area(area);
171
172 if let Some(id) = &node.id {
174 resolver.register_element(id.clone(), area_id);
175 }
176
177 let children = fo_tree.children(node_id);
180 let mut static_before_id = None;
181 let mut static_after_id = None;
182 let mut static_start_id = None;
183 let mut static_end_id = None;
184 let mut flow_id = None;
185
186 for child_id in children.clone() {
187 if let Some(child) = fo_tree.get(child_id) {
188 match &child.data {
189 FoNodeData::StaticContent { flow_name, .. } => {
190 match flow_name.as_str() {
191 "xsl-region-before" => static_before_id = Some(child_id),
192 "xsl-region-after" => static_after_id = Some(child_id),
193 "xsl-region-start" => static_start_id = Some(child_id),
194 "xsl-region-end" => static_end_id = Some(child_id),
195 _ => {}
196 }
197 }
198 FoNodeData::Flow { .. } => {
199 flow_id = Some(child_id);
200 }
201 _ => {}
202 }
203 }
204 }
205
206 marker_map.clear();
208
209 if let Some(flow_node_id) = flow_id {
211 self.collect_markers(fo_tree, flow_node_id, marker_map);
212 }
213
214 if let Some(header_id) = static_before_id {
216 self.layout_static_content_in_rect(
217 fo_tree,
218 header_id,
219 area_tree,
220 area_id,
221 geom.before_rect,
222 AreaType::Header,
223 resolver,
224 marker_map,
225 )?;
226 }
227
228 if let Some(footer_id) = static_after_id {
230 self.layout_static_content_in_rect(
231 fo_tree,
232 footer_id,
233 area_tree,
234 area_id,
235 geom.after_rect,
236 AreaType::Footer,
237 resolver,
238 marker_map,
239 )?;
240 }
241
242 if let Some(start_id) = static_start_id {
244 self.layout_static_content_in_rect(
245 fo_tree,
246 start_id,
247 area_tree,
248 area_id,
249 geom.start_rect,
250 AreaType::SidebarStart,
251 resolver,
252 marker_map,
253 )?;
254 }
255
256 if let Some(end_id) = static_end_id {
258 self.layout_static_content_in_rect(
259 fo_tree,
260 end_id,
261 area_tree,
262 area_id,
263 geom.end_rect,
264 AreaType::SidebarEnd,
265 resolver,
266 marker_map,
267 )?;
268 }
269
270 if let Some(flow_node_id) = flow_id {
272 self.layout_flow_in_rect(
273 fo_tree,
274 flow_node_id,
275 area_tree,
276 area_id,
277 geom.body_rect,
278 resolver,
279 marker_map,
280 )?;
281 }
282
283 self.place_footnotes_for_page(area_tree, area_id, geom.body_rect)?;
285
286 resolver.set_current_page(resolver.current_page() + 1);
288
289 Some(area_id)
290 }
291
292 FoNodeData::Flow { properties, .. } => {
293 let flow_rect = Rect::from_point_size(
295 Point::new(Length::from_pt(72.0), Length::from_pt(72.0)), Size::new(
297 self.page_width - Length::from_pt(144.0),
298 self.page_height - Length::from_pt(144.0),
299 ),
300 );
301
302 let mut traits = TraitSet::default();
303 if let Ok(color) = properties.get(PropertyId::Color) {
304 traits.color = color.as_color();
305 }
306
307 let area = Area::new(AreaType::Region, flow_rect).with_traits(traits);
308 let area_id = area_tree.add_area(area);
309
310 if let Some(parent) = parent_area {
311 area_tree
312 .append_child(parent, area_id)
313 .map_err(fop_types::FopError::Generic)?;
314 }
315
316 let column_count = extract_column_count(properties);
318 let column_gap = extract_column_gap(properties);
319
320 let children = fo_tree.children(node_id);
321
322 if column_count > 1 {
323 let mut multi_col =
325 MultiColumnLayout::new(column_count, column_gap, flow_rect.width)
326 .with_max_height(flow_rect.height);
327
328 for child_id in children {
329 self.layout_block_multicolumn(
330 fo_tree,
331 child_id,
332 area_tree,
333 area_id,
334 &mut multi_col,
335 resolver,
336 )?;
337 }
338 } else {
339 let mut block_ctx = BlockLayoutContext::new(flow_rect.width);
341 let mut float_manager = FloatManager::new();
342 let is_odd_page = resolver.current_page() % 2 == 1;
343
344 for child_id in children {
345 float_manager.remove_floats_above(block_ctx.current_y);
347
348 let child_is_float = fo_tree
349 .get(child_id)
350 .map(|n| matches!(n.data, FoNodeData::Float { .. }))
351 .unwrap_or(false);
352
353 if child_is_float {
354 self.layout_float_in_flow(
355 fo_tree,
356 child_id,
357 area_tree,
358 area_id,
359 block_ctx.current_y,
360 flow_rect.width,
361 is_odd_page,
362 &mut float_manager,
363 resolver,
364 )?;
365 } else {
366 if let Some(child_node) = fo_tree.get(child_id) {
368 if let Some(props) = child_node.data.properties() {
369 let clear = extract_clear(props);
370 block_ctx.current_y = float_manager
371 .get_clear_position(clear, block_ctx.current_y);
372 }
373 }
374
375 let (left_offset, avail_width) =
376 float_manager.available_width(block_ctx.current_y, flow_rect.width);
377
378 if let Some(child_area_id) = self.layout_block_float_aware(
379 fo_tree,
380 child_id,
381 area_tree,
382 area_id,
383 block_ctx.current_y,
384 avail_width,
385 left_offset,
386 resolver,
387 )? {
388 if let Some(child_area) = area_tree.get(child_area_id) {
389 block_ctx.current_y =
391 child_area.area.geometry.y + child_area.area.height();
392 }
393 }
394 }
395 }
396
397 float_manager.clear();
398 }
399
400 Some(area_id)
401 }
402
403 FoNodeData::Table { properties } => {
404 let layout_mode = if let Ok(prop) = properties.get(PropertyId::TableLayout) {
406 if let Some(enum_val) = prop.as_enum() {
407 if enum_val == 9 {
409 TableLayoutMode::Auto
410 } else {
411 TableLayoutMode::Fixed
412 }
413 } else if prop.is_auto() {
414 TableLayoutMode::Auto
415 } else {
416 TableLayoutMode::Fixed
417 }
418 } else {
419 TableLayoutMode::Fixed };
421
422 let border_collapse = if let Ok(prop) = properties.get(PropertyId::BorderCollapse) {
424 if let Some(enum_val) = prop.as_enum() {
425 if enum_val == 28 {
427 BorderCollapse::Collapse
428 } else {
429 BorderCollapse::Separate
430 }
431 } else if let Some(string_val) = prop.as_string() {
432 if string_val == "collapse" {
433 BorderCollapse::Collapse
434 } else {
435 BorderCollapse::Separate
436 }
437 } else {
438 BorderCollapse::Separate
439 }
440 } else {
441 BorderCollapse::Separate };
443
444 let border_spacing = if let Ok(prop) = properties.get(PropertyId::BorderSpacing) {
446 prop.as_length().unwrap_or(Length::from_pt(0.0))
447 } else {
448 Length::from_pt(0.0) };
450
451 let available_width = self.page_width - Length::from_pt(144.0); let table_layout = TableLayout::new(available_width)
454 .with_border_spacing(border_spacing)
455 .with_layout_mode(layout_mode)
456 .with_border_collapse(border_collapse);
457
458 let children = fo_tree.children(node_id);
460 let mut column_widths = Vec::new();
461 let mut header_id = None;
462 let mut footer_id = None;
463 let mut body_ids = Vec::new();
464
465 for child_id in children.clone() {
466 if let Some(child) = fo_tree.get(child_id) {
467 match &child.data {
468 FoNodeData::TableColumn { .. } => {
469 if let Some(props) = child.data.properties() {
471 if let Ok(width) = props.get(PropertyId::ColumnWidth) {
472 if let Some(len) = width.as_length() {
473 column_widths.push(ColumnWidth::Fixed(len));
474 } else if width.is_auto() {
475 column_widths.push(ColumnWidth::Auto);
476 }
477 } else {
478 column_widths.push(ColumnWidth::Auto);
479 }
480 }
481 }
482 FoNodeData::TableHeader { .. } => {
483 header_id = Some(child_id);
484 }
485 FoNodeData::TableFooter { .. } => {
486 footer_id = Some(child_id);
487 }
488 FoNodeData::TableBody { .. } => {
489 body_ids.push(child_id);
490 }
491 _ => {}
492 }
493 }
494 }
495
496 if column_widths.is_empty() {
498 column_widths.push(ColumnWidth::Proportional(1.0));
499 }
500
501 let computed_widths = match layout_mode {
503 TableLayoutMode::Fixed => table_layout.compute_fixed_widths(&column_widths),
504 TableLayoutMode::Auto => {
505 let mut column_info: Vec<ColumnInfo> = column_widths
507 .iter()
508 .map(|width_spec| ColumnInfo::new(width_spec.clone()))
509 .collect();
510
511 let grid = table_layout.create_grid(1, column_info.len());
514 table_layout.update_column_info_from_grid(&mut column_info, &grid);
515
516 table_layout.compute_auto_widths(&column_info)
517 }
518 };
519
520 let table_height = Length::from_pt(100.0); let table_rect =
523 Rect::new(Length::ZERO, Length::ZERO, available_width, table_height);
524
525 let mut traits = TraitSet::default();
526 if let Ok(color) = properties.get(PropertyId::BackgroundColor) {
527 traits.background_color = color.as_color();
528 }
529
530 let area = Area::new(AreaType::Block, table_rect).with_traits(traits);
531 let table_id = area_tree.add_area(area);
532
533 if let Some(parent) = parent_area {
534 area_tree
535 .append_child(parent, table_id)
536 .map_err(fop_types::FopError::Generic)?;
537 }
538
539 self.layout_table(
541 fo_tree,
542 area_tree,
543 table_id,
544 header_id,
545 footer_id,
546 &body_ids,
547 &computed_widths,
548 resolver,
549 )?;
550
551 Some(table_id)
552 }
553
554 FoNodeData::ExternalGraphic {
555 src,
556 content_width,
557 content_height,
558 scaling,
559 properties,
560 } => {
561 if let Some(parent) = parent_area {
563 self.layout_external_graphic(
564 fo_tree,
565 node_id,
566 src,
567 content_width.as_deref(),
568 content_height.as_deref(),
569 scaling.as_deref(),
570 properties,
571 area_tree,
572 parent,
573 )
574 } else {
575 None
576 }
577 }
578
579 FoNodeData::ListBlock { properties } => {
580 let provisional_distance = properties
582 .get(PropertyId::ProvisionalDistanceBetweenStarts)
583 .ok()
584 .and_then(|v| v.as_length())
585 .unwrap_or_else(|| Length::from_pt(24.0));
586
587 let provisional_label_sep = properties
589 .get(PropertyId::ProvisionalLabelSeparation)
590 .ok()
591 .and_then(|v| v.as_length())
592 .unwrap_or_else(|| Length::from_pt(6.0));
593
594 let list_space_after = extract_space_after(properties);
596
597 let available_width = if let Some(parent) = parent_area {
599 area_tree
600 .get(parent)
601 .map(|n| n.area.width())
602 .filter(|w| *w > Length::ZERO)
603 .unwrap_or_else(|| self.page_width - Length::from_pt(144.0))
604 } else {
605 self.page_width - Length::from_pt(144.0)
606 };
607
608 let label_end = provisional_distance - provisional_label_sep;
613 let body_start = provisional_distance;
614
615 let list_layout = ListLayout::new(available_width)
616 .with_label_width(label_end)
617 .with_label_separation(provisional_label_sep)
618 .with_body_start(body_start);
619
620 let mut traits = TraitSet::default();
621 if let Ok(color) = properties.get(PropertyId::Color) {
622 traits.color = color.as_color();
623 }
624 if let Ok(bg) = properties.get(PropertyId::BackgroundColor) {
625 traits.background_color = bg.as_color();
626 }
627
628 let list_rect = Rect::new(
630 Length::ZERO,
631 Length::ZERO,
632 available_width,
633 Length::from_pt(10.0),
634 );
635
636 let area = Area::new(AreaType::Block, list_rect).with_traits(traits);
637 let list_id = area_tree.add_area(area);
638
639 if let Some(parent) = parent_area {
640 area_tree
641 .append_child(parent, list_id)
642 .map_err(fop_types::FopError::Generic)?;
643 }
644
645 let children = fo_tree.children(node_id);
647 let mut item_y = Length::ZERO;
648 let mut item_index = 0usize;
649
650 for child_id in children.iter() {
651 if let Some(child) = fo_tree.get(*child_id) {
652 if matches!(child.data, FoNodeData::ListItem { .. }) {
653 item_index += 1;
654 item_y = self.layout_list_item(
655 fo_tree,
656 *child_id,
657 area_tree,
658 list_id,
659 item_y,
660 item_index,
661 &list_layout,
662 resolver,
663 )?;
664 }
665 }
666 }
667
668 let total_height = item_y + list_space_after;
670 if let Some(list_node) = area_tree.get_mut(list_id) {
671 list_node.area.geometry.height = total_height.max(Length::from_pt(10.0));
672 }
673
674 Some(list_id)
675 }
676
677 FoNodeData::Float { properties } => {
678 let float_side = if let Ok(prop) = properties.get(PropertyId::Float) {
680 if let Some(enum_val) = prop.as_enum() {
681 match enum_val {
684 66 => FloatSide::Left,
685 96 => FloatSide::Right,
686 104 => FloatSide::Start,
687 45 => FloatSide::End,
688 _ => FloatSide::None,
689 }
690 } else if let Some(string_val) = prop.as_string() {
691 match string_val {
692 "left" => FloatSide::Left,
693 "right" => FloatSide::Right,
694 "start" => FloatSide::Start,
695 "end" => FloatSide::End,
696 "inside" => FloatSide::Inside,
697 "outside" => FloatSide::Outside,
698 _ => FloatSide::None,
699 }
700 } else {
701 FloatSide::None
702 }
703 } else {
704 FloatSide::None
705 };
706
707 if float_side == FloatSide::None {
709 return Ok(None);
710 }
711
712 let traits = extract_traits(properties);
714
715 let container_w = self.page_width - Length::from_pt(144.0);
717 let float_width = self.measure_float_width(fo_tree, node_id, container_w);
718
719 let float_rect = Rect::from_point_size(
722 Point::ZERO,
723 Size::new(float_width, Length::from_pt(1.0)),
724 );
725
726 let area = Area::new(AreaType::FloatArea, float_rect).with_traits(traits);
727 let float_area_id = area_tree.add_area(area);
728
729 if let Some(parent) = parent_area {
730 area_tree
731 .append_child(parent, float_area_id)
732 .map_err(fop_types::FopError::Generic)?;
733 }
734
735 let children = fo_tree.children(node_id);
737 let mut float_ctx = BlockLayoutContext::new(float_width);
738
739 for child_id in children {
740 if let Some(child_area_id) = self.layout_block(
741 fo_tree,
742 child_id,
743 area_tree,
744 float_area_id,
745 float_ctx.current_y,
746 float_width,
747 resolver,
748 )? {
749 if let Some(child_area) = area_tree.get(child_area_id) {
750 float_ctx.current_y =
751 child_area.area.geometry.y + child_area.area.height();
752 }
753 }
754 }
755
756 let float_height = if float_ctx.current_y > Length::ZERO {
758 float_ctx.current_y
759 } else {
760 Length::from_pt(50.0)
761 };
762 if let Some(float_area_node) = area_tree.get_mut(float_area_id) {
763 float_area_node.area.geometry.height = float_height;
764 }
765
766 Some(float_area_id)
767 }
768
769 FoNodeData::BlockContainer { .. } => {
770 let children = fo_tree.children(node_id);
773 for child_id in children {
774 self.layout_node(
775 fo_tree,
776 child_id,
777 area_tree,
778 parent_area,
779 resolver,
780 marker_map,
781 )?;
782 }
783 None
784 }
785
786 FoNodeData::Wrapper { .. }
787 | FoNodeData::Declarations
788 | FoNodeData::ColorProfile { .. }
789 | FoNodeData::PageSequenceMaster { .. }
790 | FoNodeData::UnsupportedElement { .. } => {
791 None
793 }
794
795 _ => {
796 None
798 }
799 };
800
801 Ok(area_id)
802 }
803}
804
805impl Default for LayoutEngine {
806 fn default() -> Self {
807 Self::new()
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use super::types::{FloatInfo, FloatSide};
814 use super::*;
815 use fop_core::{FoNode, FoNodeData, PropertyList, PropertyValue};
816
817 #[test]
818 fn test_engine_creation() {
819 let engine = LayoutEngine::new();
820 assert_eq!(engine.page_width, Length::from_mm(210.0));
821 assert_eq!(engine.page_height, Length::from_mm(297.0));
822 }
823
824 #[test]
825 fn test_simple_layout() {
826 let mut fo_tree = FoArena::new();
827
828 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
830
831 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
832 master_reference: "A4".to_string(),
833 format: "1".to_string(),
834 grouping_separator: None,
835 grouping_size: None,
836 properties: PropertyList::new(),
837 }));
838 fo_tree
839 .append_child(root, page_seq)
840 .expect("test: should succeed");
841
842 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
843 flow_name: "xsl-region-body".to_string(),
844 properties: PropertyList::new(),
845 }));
846 fo_tree
847 .append_child(page_seq, flow)
848 .expect("test: should succeed");
849
850 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
851 properties: PropertyList::new(),
852 }));
853 fo_tree
854 .append_child(flow, block)
855 .expect("test: should succeed");
856
857 let engine = LayoutEngine::new();
859 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
860
861 assert!(!area_tree.is_empty());
863 }
864
865 #[test]
866 fn test_static_content_header() {
867 let mut fo_tree = FoArena::new();
868
869 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
871
872 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
873 master_reference: "A4".to_string(),
874 format: "1".to_string(),
875 grouping_separator: None,
876 grouping_size: None,
877 properties: PropertyList::new(),
878 }));
879 fo_tree
880 .append_child(root, page_seq)
881 .expect("test: should succeed");
882
883 let header = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
885 flow_name: "xsl-region-before".to_string(),
886 properties: PropertyList::new(),
887 }));
888 fo_tree
889 .append_child(page_seq, header)
890 .expect("test: should succeed");
891
892 let header_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
894 properties: PropertyList::new(),
895 }));
896 fo_tree
897 .append_child(header, header_block)
898 .expect("test: should succeed");
899
900 let header_text =
902 fo_tree.add_node(FoNode::new(FoNodeData::Text("Header Text".to_string())));
903 fo_tree
904 .append_child(header_block, header_text)
905 .expect("test: should succeed");
906
907 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
909 flow_name: "xsl-region-body".to_string(),
910 properties: PropertyList::new(),
911 }));
912 fo_tree
913 .append_child(page_seq, flow)
914 .expect("test: should succeed");
915
916 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
917 properties: PropertyList::new(),
918 }));
919 fo_tree
920 .append_child(flow, block)
921 .expect("test: should succeed");
922
923 let engine = LayoutEngine::new();
925 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
926
927 let mut has_header = false;
929 for (_, node) in area_tree.iter() {
930 if matches!(node.area.area_type, AreaType::Header) {
931 has_header = true;
932 break;
933 }
934 }
935 assert!(has_header, "Should have created a header area");
936 }
937
938 #[test]
939 fn test_static_content_footer() {
940 let mut fo_tree = FoArena::new();
941
942 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
944
945 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
946 master_reference: "A4".to_string(),
947 format: "1".to_string(),
948 grouping_separator: None,
949 grouping_size: None,
950 properties: PropertyList::new(),
951 }));
952 fo_tree
953 .append_child(root, page_seq)
954 .expect("test: should succeed");
955
956 let footer = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
958 flow_name: "xsl-region-after".to_string(),
959 properties: PropertyList::new(),
960 }));
961 fo_tree
962 .append_child(page_seq, footer)
963 .expect("test: should succeed");
964
965 let footer_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
967 properties: PropertyList::new(),
968 }));
969 fo_tree
970 .append_child(footer, footer_block)
971 .expect("test: should succeed");
972
973 let footer_text =
975 fo_tree.add_node(FoNode::new(FoNodeData::Text("Footer Text".to_string())));
976 fo_tree
977 .append_child(footer_block, footer_text)
978 .expect("test: should succeed");
979
980 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
982 flow_name: "xsl-region-body".to_string(),
983 properties: PropertyList::new(),
984 }));
985 fo_tree
986 .append_child(page_seq, flow)
987 .expect("test: should succeed");
988
989 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
990 properties: PropertyList::new(),
991 }));
992 fo_tree
993 .append_child(flow, block)
994 .expect("test: should succeed");
995
996 let engine = LayoutEngine::new();
998 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
999
1000 let mut has_footer = false;
1002 for (_, node) in area_tree.iter() {
1003 if matches!(node.area.area_type, AreaType::Footer) {
1004 has_footer = true;
1005 break;
1006 }
1007 }
1008 assert!(has_footer, "Should have created a footer area");
1009 }
1010
1011 #[test]
1012 fn test_static_content_both_header_and_footer() {
1013 let mut fo_tree = FoArena::new();
1014
1015 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1017
1018 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1019 master_reference: "A4".to_string(),
1020 format: "1".to_string(),
1021 grouping_separator: None,
1022 grouping_size: None,
1023 properties: PropertyList::new(),
1024 }));
1025 fo_tree
1026 .append_child(root, page_seq)
1027 .expect("test: should succeed");
1028
1029 let header = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
1031 flow_name: "xsl-region-before".to_string(),
1032 properties: PropertyList::new(),
1033 }));
1034 fo_tree
1035 .append_child(page_seq, header)
1036 .expect("test: should succeed");
1037
1038 let header_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1039 properties: PropertyList::new(),
1040 }));
1041 fo_tree
1042 .append_child(header, header_block)
1043 .expect("test: should succeed");
1044
1045 let footer = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
1047 flow_name: "xsl-region-after".to_string(),
1048 properties: PropertyList::new(),
1049 }));
1050 fo_tree
1051 .append_child(page_seq, footer)
1052 .expect("test: should succeed");
1053
1054 let footer_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1055 properties: PropertyList::new(),
1056 }));
1057 fo_tree
1058 .append_child(footer, footer_block)
1059 .expect("test: should succeed");
1060
1061 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1063 flow_name: "xsl-region-body".to_string(),
1064 properties: PropertyList::new(),
1065 }));
1066 fo_tree
1067 .append_child(page_seq, flow)
1068 .expect("test: should succeed");
1069
1070 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1071 properties: PropertyList::new(),
1072 }));
1073 fo_tree
1074 .append_child(flow, block)
1075 .expect("test: should succeed");
1076
1077 let engine = LayoutEngine::new();
1079 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1080
1081 let mut has_header = false;
1083 let mut has_footer = false;
1084 for (_, node) in area_tree.iter() {
1085 match node.area.area_type {
1086 AreaType::Header => has_header = true,
1087 AreaType::Footer => has_footer = true,
1088 _ => {}
1089 }
1090 }
1091 assert!(has_header, "Should have created a header area");
1092 assert!(has_footer, "Should have created a footer area");
1093 }
1094
1095 #[test]
1096 fn test_float_manager_basic() {
1097 let mut fm = FloatManager::new();
1098 let container_width = Length::from_pt(400.0);
1099
1100 let (left_off, avail) = fm.available_width(Length::ZERO, container_width);
1102 assert_eq!(left_off, Length::ZERO);
1103 assert_eq!(avail, container_width);
1104
1105 let float_area_id = crate::area::AreaId::from_index(0);
1107 fm.add_float(
1108 FloatInfo {
1109 area_id: float_area_id,
1110 side: FloatSide::Left,
1111 top: Length::ZERO,
1112 bottom: Length::from_pt(50.0),
1113 width: Length::from_pt(100.0),
1114 },
1115 true, );
1117
1118 let (left_off, avail) = fm.available_width(Length::from_pt(10.0), container_width);
1120 assert_eq!(left_off, Length::from_pt(100.0));
1121 assert_eq!(avail, Length::from_pt(300.0));
1122
1123 let (left_off, avail) = fm.available_width(Length::from_pt(60.0), container_width);
1125 assert_eq!(left_off, Length::ZERO);
1126 assert_eq!(avail, container_width);
1127 }
1128
1129 #[test]
1130 fn test_float_manager_right_float() {
1131 let mut fm = FloatManager::new();
1132 let container_width = Length::from_pt(400.0);
1133
1134 let float_area_id = crate::area::AreaId::from_index(0);
1136 fm.add_float(
1137 FloatInfo {
1138 area_id: float_area_id,
1139 side: FloatSide::Right,
1140 top: Length::ZERO,
1141 bottom: Length::from_pt(80.0),
1142 width: Length::from_pt(120.0),
1143 },
1144 true,
1145 );
1146
1147 let (left_off, avail) = fm.available_width(Length::from_pt(20.0), container_width);
1149 assert_eq!(left_off, Length::ZERO);
1150 assert_eq!(avail, Length::from_pt(280.0));
1151 }
1152
1153 #[test]
1154 fn test_float_manager_remove_expired() {
1155 let mut fm = FloatManager::new();
1156 let container_width = Length::from_pt(400.0);
1157
1158 let float_area_id = crate::area::AreaId::from_index(0);
1159 fm.add_float(
1160 FloatInfo {
1161 area_id: float_area_id,
1162 side: FloatSide::Left,
1163 top: Length::ZERO,
1164 bottom: Length::from_pt(50.0),
1165 width: Length::from_pt(100.0),
1166 },
1167 true,
1168 );
1169
1170 fm.remove_floats_above(Length::from_pt(60.0));
1172
1173 let (left_off, avail) = fm.available_width(Length::from_pt(60.0), container_width);
1175 assert_eq!(left_off, Length::ZERO);
1176 assert_eq!(avail, container_width);
1177 }
1178
1179 #[test]
1180 fn test_layout_with_float_node() {
1181 let mut fo_tree = FoArena::new();
1182
1183 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1185
1186 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1187 master_reference: "A4".to_string(),
1188 format: "1".to_string(),
1189 grouping_separator: None,
1190 grouping_size: None,
1191 properties: PropertyList::new(),
1192 }));
1193 fo_tree
1194 .append_child(root, page_seq)
1195 .expect("test: should succeed");
1196
1197 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1198 flow_name: "xsl-region-body".to_string(),
1199 properties: PropertyList::new(),
1200 }));
1201 fo_tree
1202 .append_child(page_seq, flow)
1203 .expect("test: should succeed");
1204
1205 let mut float_props = PropertyList::new();
1207 float_props.set(PropertyId::Float, PropertyValue::Enum(66));
1208 let float_node = fo_tree.add_node(FoNode::new(FoNodeData::Float {
1209 properties: float_props,
1210 }));
1211 fo_tree
1212 .append_child(flow, float_node)
1213 .expect("test: should succeed");
1214
1215 let float_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1217 properties: PropertyList::new(),
1218 }));
1219 fo_tree
1220 .append_child(float_node, float_block)
1221 .expect("test: should succeed");
1222 let float_text =
1223 fo_tree.add_node(FoNode::new(FoNodeData::Text("Float content".to_string())));
1224 fo_tree
1225 .append_child(float_block, float_text)
1226 .expect("test: should succeed");
1227
1228 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1230 properties: PropertyList::new(),
1231 }));
1232 fo_tree
1233 .append_child(flow, block)
1234 .expect("test: should succeed");
1235 let text = fo_tree.add_node(FoNode::new(FoNodeData::Text("Normal text".to_string())));
1236 fo_tree
1237 .append_child(block, text)
1238 .expect("test: should succeed");
1239
1240 let engine = LayoutEngine::new();
1242 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1243
1244 assert!(!area_tree.is_empty());
1246
1247 let mut has_float_area = false;
1249 for (_, node) in area_tree.iter() {
1250 if matches!(node.area.area_type, AreaType::FloatArea) {
1251 has_float_area = true;
1252 break;
1253 }
1254 }
1255 assert!(has_float_area, "Should have created a FloatArea");
1256 }
1257
1258 #[test]
1259 fn test_float_manager_clear_position() {
1260 let mut fm = FloatManager::new();
1261
1262 let float_area_id = crate::area::AreaId::from_index(0);
1264 fm.add_float(
1265 FloatInfo {
1266 area_id: float_area_id,
1267 side: FloatSide::Left,
1268 top: Length::ZERO,
1269 bottom: Length::from_pt(80.0),
1270 width: Length::from_pt(100.0),
1271 },
1272 true,
1273 );
1274
1275 let float_area_id2 = crate::area::AreaId::from_index(1);
1277 fm.add_float(
1278 FloatInfo {
1279 area_id: float_area_id2,
1280 side: FloatSide::Right,
1281 top: Length::ZERO,
1282 bottom: Length::from_pt(60.0),
1283 width: Length::from_pt(80.0),
1284 },
1285 true,
1286 );
1287
1288 let current_y = Length::from_pt(10.0);
1289
1290 assert_eq!(
1292 fm.get_clear_position(ClearSide::Left, current_y),
1293 Length::from_pt(80.0)
1294 );
1295
1296 assert_eq!(
1298 fm.get_clear_position(ClearSide::Right, current_y),
1299 Length::from_pt(60.0)
1300 );
1301
1302 assert_eq!(
1304 fm.get_clear_position(ClearSide::Both, current_y),
1305 Length::from_pt(80.0)
1306 );
1307
1308 assert_eq!(fm.get_clear_position(ClearSide::None, current_y), current_y);
1310 }
1311
1312 #[test]
1313 fn test_layout_with_float_and_clear() {
1314 let mut fo_tree = FoArena::new();
1315
1316 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1318
1319 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1320 master_reference: "A4".to_string(),
1321 format: "1".to_string(),
1322 grouping_separator: None,
1323 grouping_size: None,
1324 properties: PropertyList::new(),
1325 }));
1326 fo_tree
1327 .append_child(root, page_seq)
1328 .expect("test: should succeed");
1329
1330 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1331 flow_name: "xsl-region-body".to_string(),
1332 properties: PropertyList::new(),
1333 }));
1334 fo_tree
1335 .append_child(page_seq, flow)
1336 .expect("test: should succeed");
1337
1338 let mut float_props = PropertyList::new();
1340 float_props.set(PropertyId::Float, PropertyValue::Enum(96)); let float_node = fo_tree.add_node(FoNode::new(FoNodeData::Float {
1342 properties: float_props,
1343 }));
1344 fo_tree
1345 .append_child(flow, float_node)
1346 .expect("test: should succeed");
1347
1348 let float_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1349 properties: PropertyList::new(),
1350 }));
1351 fo_tree
1352 .append_child(float_node, float_block)
1353 .expect("test: should succeed");
1354
1355 let mut clear_props = PropertyList::new();
1357 clear_props.set(PropertyId::Clear, PropertyValue::Enum(96)); let clear_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1359 properties: clear_props,
1360 }));
1361 fo_tree
1362 .append_child(flow, clear_block)
1363 .expect("test: should succeed");
1364 let text = fo_tree.add_node(FoNode::new(FoNodeData::Text("After float".to_string())));
1365 fo_tree
1366 .append_child(clear_block, text)
1367 .expect("test: should succeed");
1368
1369 let engine = LayoutEngine::new();
1371 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1372
1373 assert!(!area_tree.is_empty());
1374
1375 let mut has_float = false;
1377 for (_, node) in area_tree.iter() {
1378 if matches!(node.area.area_type, AreaType::FloatArea) {
1379 has_float = true;
1380 break;
1381 }
1382 }
1383 assert!(has_float, "Expected a FloatArea in the area tree");
1384 }
1385
1386 #[test]
1387 fn test_layout_with_footnote() {
1388 let mut fo_tree = FoArena::new();
1389
1390 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1392
1393 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1394 master_reference: "A4".to_string(),
1395 format: "1".to_string(),
1396 grouping_separator: None,
1397 grouping_size: None,
1398 properties: PropertyList::new(),
1399 }));
1400 fo_tree
1401 .append_child(root, page_seq)
1402 .expect("test: should succeed");
1403
1404 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1405 flow_name: "xsl-region-body".to_string(),
1406 properties: PropertyList::new(),
1407 }));
1408 fo_tree
1409 .append_child(page_seq, flow)
1410 .expect("test: should succeed");
1411
1412 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1413 properties: PropertyList::new(),
1414 }));
1415 fo_tree
1416 .append_child(flow, block)
1417 .expect("test: should succeed");
1418
1419 let text_before = fo_tree.add_node(FoNode::new(FoNodeData::Text("Text with ".to_string())));
1421 fo_tree
1422 .append_child(block, text_before)
1423 .expect("test: should succeed");
1424
1425 let footnote = fo_tree.add_node(FoNode::new(FoNodeData::Footnote {
1427 properties: PropertyList::new(),
1428 }));
1429 fo_tree
1430 .append_child(block, footnote)
1431 .expect("test: should succeed");
1432
1433 let ref_mark = fo_tree.add_node(FoNode::new(FoNodeData::Inline {
1435 properties: PropertyList::new(),
1436 }));
1437 fo_tree
1438 .append_child(footnote, ref_mark)
1439 .expect("test: should succeed");
1440
1441 let ref_text = fo_tree.add_node(FoNode::new(FoNodeData::Text("1".to_string())));
1442 fo_tree
1443 .append_child(ref_mark, ref_text)
1444 .expect("test: should succeed");
1445
1446 let footnote_body = fo_tree.add_node(FoNode::new(FoNodeData::FootnoteBody {
1448 properties: PropertyList::new(),
1449 }));
1450 fo_tree
1451 .append_child(footnote, footnote_body)
1452 .expect("test: should succeed");
1453
1454 let body_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1456 properties: PropertyList::new(),
1457 }));
1458 fo_tree
1459 .append_child(footnote_body, body_block)
1460 .expect("test: should succeed");
1461
1462 let body_text = fo_tree.add_node(FoNode::new(FoNodeData::Text(
1463 "1. The footnote text.".to_string(),
1464 )));
1465 fo_tree
1466 .append_child(body_block, body_text)
1467 .expect("test: should succeed");
1468
1469 let engine = LayoutEngine::new();
1471 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1472
1473 assert!(!area_tree.is_empty());
1475
1476 let mut has_footnote_area = false;
1478 for (_, node) in area_tree.iter() {
1479 if matches!(node.area.area_type, AreaType::Footnote) {
1480 has_footnote_area = true;
1481 break;
1482 }
1483 }
1484 assert!(has_footnote_area, "Should have created a Footnote area");
1485 }
1486
1487 #[test]
1488 fn test_static_content_sidebar_start() {
1489 let mut fo_tree = FoArena::new();
1490
1491 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1492
1493 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1494 master_reference: "A4".to_string(),
1495 format: "1".to_string(),
1496 grouping_separator: None,
1497 grouping_size: None,
1498 properties: PropertyList::new(),
1499 }));
1500 fo_tree
1501 .append_child(root, page_seq)
1502 .expect("test: should succeed");
1503
1504 let sidebar_start = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
1506 flow_name: "xsl-region-start".to_string(),
1507 properties: PropertyList::new(),
1508 }));
1509 fo_tree
1510 .append_child(page_seq, sidebar_start)
1511 .expect("test: should succeed");
1512
1513 let sidebar_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1514 properties: PropertyList::new(),
1515 }));
1516 fo_tree
1517 .append_child(sidebar_start, sidebar_block)
1518 .expect("test: should succeed");
1519
1520 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1522 flow_name: "xsl-region-body".to_string(),
1523 properties: PropertyList::new(),
1524 }));
1525 fo_tree
1526 .append_child(page_seq, flow)
1527 .expect("test: should succeed");
1528
1529 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1530 properties: PropertyList::new(),
1531 }));
1532 fo_tree
1533 .append_child(flow, block)
1534 .expect("test: should succeed");
1535
1536 let engine = LayoutEngine::new();
1537 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1538
1539 let mut has_sidebar_start = false;
1540 for (_, node) in area_tree.iter() {
1541 if matches!(node.area.area_type, AreaType::SidebarStart) {
1542 has_sidebar_start = true;
1543 break;
1544 }
1545 }
1546 assert!(has_sidebar_start, "Should have created a SidebarStart area");
1547 }
1548
1549 #[test]
1550 fn test_static_content_sidebar_end() {
1551 let mut fo_tree = FoArena::new();
1552
1553 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1554
1555 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1556 master_reference: "A4".to_string(),
1557 format: "1".to_string(),
1558 grouping_separator: None,
1559 grouping_size: None,
1560 properties: PropertyList::new(),
1561 }));
1562 fo_tree
1563 .append_child(root, page_seq)
1564 .expect("test: should succeed");
1565
1566 let sidebar_end = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
1568 flow_name: "xsl-region-end".to_string(),
1569 properties: PropertyList::new(),
1570 }));
1571 fo_tree
1572 .append_child(page_seq, sidebar_end)
1573 .expect("test: should succeed");
1574
1575 let sidebar_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1576 properties: PropertyList::new(),
1577 }));
1578 fo_tree
1579 .append_child(sidebar_end, sidebar_block)
1580 .expect("test: should succeed");
1581
1582 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1584 flow_name: "xsl-region-body".to_string(),
1585 properties: PropertyList::new(),
1586 }));
1587 fo_tree
1588 .append_child(page_seq, flow)
1589 .expect("test: should succeed");
1590
1591 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1592 properties: PropertyList::new(),
1593 }));
1594 fo_tree
1595 .append_child(flow, block)
1596 .expect("test: should succeed");
1597
1598 let engine = LayoutEngine::new();
1599 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1600
1601 let mut has_sidebar_end = false;
1602 for (_, node) in area_tree.iter() {
1603 if matches!(node.area.area_type, AreaType::SidebarEnd) {
1604 has_sidebar_end = true;
1605 break;
1606 }
1607 }
1608 assert!(has_sidebar_end, "Should have created a SidebarEnd area");
1609 }
1610
1611 #[test]
1612 fn test_page_region_geometry_from_simple_page_master() {
1613 use fop_core::PropertyValue;
1614 use fop_types::Length;
1615
1616 let mut fo_tree = FoArena::new();
1617
1618 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1622
1623 let lms = fo_tree.add_node(FoNode::new(FoNodeData::LayoutMasterSet));
1624 fo_tree
1625 .append_child(root, lms)
1626 .expect("test: should succeed");
1627
1628 let mut spm_props = PropertyList::new();
1629 spm_props.set(
1630 PropertyId::PageWidth,
1631 PropertyValue::Length(Length::from_pt(595.0)),
1632 );
1633 spm_props.set(
1634 PropertyId::PageHeight,
1635 PropertyValue::Length(Length::from_pt(842.0)),
1636 );
1637 spm_props.set(
1638 PropertyId::MarginTop,
1639 PropertyValue::Length(Length::from_pt(50.0)),
1640 );
1641 spm_props.set(
1642 PropertyId::MarginBottom,
1643 PropertyValue::Length(Length::from_pt(50.0)),
1644 );
1645 spm_props.set(
1646 PropertyId::MarginLeft,
1647 PropertyValue::Length(Length::from_pt(60.0)),
1648 );
1649 spm_props.set(
1650 PropertyId::MarginRight,
1651 PropertyValue::Length(Length::from_pt(60.0)),
1652 );
1653
1654 let spm = fo_tree.add_node(FoNode::new(FoNodeData::SimplePageMaster {
1655 master_name: "test-master".to_string(),
1656 properties: spm_props,
1657 }));
1658 fo_tree
1659 .append_child(lms, spm)
1660 .expect("test: should succeed");
1661
1662 let mut rb_props = PropertyList::new();
1664 rb_props.set(
1665 PropertyId::Extent,
1666 PropertyValue::Length(Length::from_pt(30.0)),
1667 );
1668 let rb = fo_tree.add_node(FoNode::new(FoNodeData::RegionBefore {
1669 properties: rb_props,
1670 }));
1671 fo_tree.append_child(spm, rb).expect("test: should succeed");
1672
1673 let mut ra_props = PropertyList::new();
1675 ra_props.set(
1676 PropertyId::Extent,
1677 PropertyValue::Length(Length::from_pt(20.0)),
1678 );
1679 let ra = fo_tree.add_node(FoNode::new(FoNodeData::RegionAfter {
1680 properties: ra_props,
1681 }));
1682 fo_tree.append_child(spm, ra).expect("test: should succeed");
1683
1684 let mut rs_props = PropertyList::new();
1686 rs_props.set(
1687 PropertyId::Extent,
1688 PropertyValue::Length(Length::from_pt(50.0)),
1689 );
1690 let rs = fo_tree.add_node(FoNode::new(FoNodeData::RegionStart {
1691 properties: rs_props,
1692 }));
1693 fo_tree.append_child(spm, rs).expect("test: should succeed");
1694
1695 let mut re_props = PropertyList::new();
1697 re_props.set(
1698 PropertyId::Extent,
1699 PropertyValue::Length(Length::from_pt(40.0)),
1700 );
1701 let re = fo_tree.add_node(FoNode::new(FoNodeData::RegionEnd {
1702 properties: re_props,
1703 }));
1704 fo_tree.append_child(spm, re).expect("test: should succeed");
1705
1706 let body_region = fo_tree.add_node(FoNode::new(FoNodeData::RegionBody {
1708 properties: PropertyList::new(),
1709 }));
1710 fo_tree
1711 .append_child(spm, body_region)
1712 .expect("test: should succeed");
1713
1714 let engine = LayoutEngine::new();
1715 let geom = engine.extract_page_region_geometry(&fo_tree, "test-master");
1716
1717 assert_eq!(geom.page_width, Length::from_pt(595.0));
1719 assert_eq!(geom.page_height, Length::from_pt(842.0));
1720
1721 assert_eq!(geom.before_rect.x, Length::from_pt(60.0));
1724 assert_eq!(geom.before_rect.y, Length::from_pt(50.0));
1725 assert_eq!(geom.before_rect.width, Length::from_pt(475.0));
1726 assert_eq!(geom.before_rect.height, Length::from_pt(30.0));
1727
1728 assert_eq!(geom.after_rect.x, Length::from_pt(60.0));
1730 assert_eq!(geom.after_rect.y, Length::from_pt(772.0));
1731 assert_eq!(geom.after_rect.width, Length::from_pt(475.0));
1732 assert_eq!(geom.after_rect.height, Length::from_pt(20.0));
1733
1734 assert_eq!(geom.start_rect.x, Length::from_pt(60.0));
1737 assert_eq!(geom.start_rect.y, Length::from_pt(80.0));
1738 assert_eq!(geom.start_rect.width, Length::from_pt(50.0));
1739 assert_eq!(geom.start_rect.height, Length::from_pt(692.0));
1740
1741 assert_eq!(geom.end_rect.x, Length::from_pt(495.0));
1743 assert_eq!(geom.end_rect.y, Length::from_pt(80.0));
1744 assert_eq!(geom.end_rect.width, Length::from_pt(40.0));
1745 assert_eq!(geom.end_rect.height, Length::from_pt(692.0));
1746
1747 assert_eq!(geom.body_rect.x, Length::from_pt(110.0));
1749 assert_eq!(geom.body_rect.y, Length::from_pt(80.0));
1750 assert_eq!(geom.body_rect.width, Length::from_pt(385.0));
1751 assert_eq!(geom.body_rect.height, Length::from_pt(692.0));
1752 }
1753
1754 #[test]
1755 fn test_all_five_regions_layout() {
1756 let mut fo_tree = FoArena::new();
1757
1758 let root = fo_tree.add_node(FoNode::new(FoNodeData::Root));
1759
1760 let page_seq = fo_tree.add_node(FoNode::new(FoNodeData::PageSequence {
1761 master_reference: "A4".to_string(),
1762 format: "1".to_string(),
1763 grouping_separator: None,
1764 grouping_size: None,
1765 properties: PropertyList::new(),
1766 }));
1767 fo_tree
1768 .append_child(root, page_seq)
1769 .expect("test: should succeed");
1770
1771 for flow_name in &[
1773 "xsl-region-before",
1774 "xsl-region-after",
1775 "xsl-region-start",
1776 "xsl-region-end",
1777 ] {
1778 let sc = fo_tree.add_node(FoNode::new(FoNodeData::StaticContent {
1779 flow_name: flow_name.to_string(),
1780 properties: PropertyList::new(),
1781 }));
1782 fo_tree
1783 .append_child(page_seq, sc)
1784 .expect("test: should succeed");
1785 let sc_block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1786 properties: PropertyList::new(),
1787 }));
1788 fo_tree
1789 .append_child(sc, sc_block)
1790 .expect("test: should succeed");
1791 }
1792
1793 let flow = fo_tree.add_node(FoNode::new(FoNodeData::Flow {
1795 flow_name: "xsl-region-body".to_string(),
1796 properties: PropertyList::new(),
1797 }));
1798 fo_tree
1799 .append_child(page_seq, flow)
1800 .expect("test: should succeed");
1801
1802 let block = fo_tree.add_node(FoNode::new(FoNodeData::Block {
1803 properties: PropertyList::new(),
1804 }));
1805 fo_tree
1806 .append_child(flow, block)
1807 .expect("test: should succeed");
1808
1809 let engine = LayoutEngine::new();
1810 let area_tree = engine.layout(&fo_tree).expect("test: should succeed");
1811
1812 let mut has_header = false;
1813 let mut has_footer = false;
1814 let mut has_sidebar_start = false;
1815 let mut has_sidebar_end = false;
1816 let mut has_region = false;
1817
1818 for (_, node) in area_tree.iter() {
1819 match node.area.area_type {
1820 AreaType::Header => has_header = true,
1821 AreaType::Footer => has_footer = true,
1822 AreaType::SidebarStart => has_sidebar_start = true,
1823 AreaType::SidebarEnd => has_sidebar_end = true,
1824 AreaType::Region => has_region = true,
1825 _ => {}
1826 }
1827 }
1828
1829 assert!(has_header, "Should have a header area");
1830 assert!(has_footer, "Should have a footer area");
1831 assert!(has_sidebar_start, "Should have a sidebar-start area");
1832 assert!(has_sidebar_end, "Should have a sidebar-end area");
1833 assert!(has_region, "Should have a body region area");
1834 }
1835
1836 #[allow(dead_code)]
1838 fn parse_fo_length_str(s: &str) -> Option<Length> {
1839 if let Some(v) = s.strip_suffix("pt") {
1840 v.parse::<f64>().ok().map(Length::from_pt)
1841 } else if let Some(v) = s.strip_suffix("mm") {
1842 v.parse::<f64>().ok().map(Length::from_mm)
1843 } else if let Some(v) = s.strip_suffix("cm") {
1844 v.parse::<f64>().ok().map(Length::from_cm)
1845 } else if let Some(v) = s.strip_suffix("in") {
1846 v.parse::<f64>().ok().map(Length::from_inch)
1847 } else if let Some(v) = s.strip_suffix("px") {
1848 v.parse::<f64>().ok().map(|px| Length::from_pt(px * 0.75))
1849 } else {
1850 None
1851 }
1852 }
1853}