1use crate::area::{Area, AreaId, AreaTree, AreaType};
6use fop_types::{Length, Point, Rect, Result, Size};
7
8pub struct PageBreaker {
10 page_width: Length,
12
13 page_height: Length,
15
16 margins: [Length; 4],
18}
19
20impl PageBreaker {
21 pub fn new(page_width: Length, page_height: Length, margins: [Length; 4]) -> Self {
23 Self {
24 page_width,
25 page_height,
26 margins,
27 }
28 }
29
30 pub fn content_height(&self) -> Length {
32 self.page_height - self.margins[0] - self.margins[2] }
34
35 pub fn content_height_with_footnotes(&self, footnote_height: Length) -> Length {
37 self.content_height() - footnote_height
38 }
39
40 pub fn content_width(&self) -> Length {
42 self.page_width - self.margins[1] - self.margins[3] }
44
45 pub fn break_into_pages(
47 &self,
48 area_tree: &mut AreaTree,
49 root_id: AreaId,
50 ) -> Result<Vec<AreaId>> {
51 let mut page_ids = Vec::new();
52 let content_height = self.content_height();
53 let _content_width = self.content_width();
54
55 let children = area_tree.children(root_id);
57
58 if children.is_empty() {
59 let page_id = self.create_page(area_tree)?;
61 page_ids.push(page_id);
62 return Ok(page_ids);
63 }
64
65 let mut current_page_id = self.create_page(area_tree)?;
67 page_ids.push(current_page_id);
68
69 let mut current_height = Length::ZERO;
70
71 for (idx, child_id) in children.iter().enumerate() {
72 let (child_height, break_before_opt, break_after_opt) =
74 if let Some(child_node) = area_tree.get(*child_id) {
75 (
76 child_node.area.height(),
77 child_node.area.break_before,
78 child_node.area.break_after,
79 )
80 } else {
81 continue;
82 };
83
84 let mut force_break_before = false;
86 let mut need_even_page_before = false;
87 let mut need_odd_page_before = false;
88
89 if let Some(break_before) = break_before_opt {
90 if break_before.forces_page_break() {
91 force_break_before = true;
92 need_even_page_before = break_before.requires_even_page();
93 need_odd_page_before = break_before.requires_odd_page();
94 }
95 }
96
97 if need_even_page_before {
99 let current_page_num = page_ids.len();
100 if current_page_num % 2 == 1 {
101 current_page_id = self.create_page(area_tree)?;
103 page_ids.push(current_page_id);
104 }
105 } else if need_odd_page_before {
106 let current_page_num = page_ids.len();
107 if current_page_num % 2 == 0 {
108 current_page_id = self.create_page(area_tree)?;
110 page_ids.push(current_page_id);
111 }
112 }
113
114 let can_break = self.can_break_before(area_tree, *child_id, idx, &children);
116
117 if ((current_height + child_height > content_height
119 && current_height > Length::ZERO
120 && can_break)
121 || force_break_before)
122 && current_height > Length::ZERO
123 {
124 current_page_id = self.create_page(area_tree)?;
126 page_ids.push(current_page_id);
127 current_height = Length::ZERO;
128 }
129
130 current_height += child_height;
132
133 if let Some(break_after) = break_after_opt {
135 if break_after.forces_page_break() {
136 current_page_id = self.create_page(area_tree)?;
138 page_ids.push(current_page_id);
139 current_height = Length::ZERO;
140
141 if break_after.requires_even_page() {
143 let current_page_num = page_ids.len();
144 if current_page_num % 2 == 1 {
145 current_page_id = self.create_page(area_tree)?;
147 page_ids.push(current_page_id);
148 }
149 } else if break_after.requires_odd_page() {
150 let current_page_num = page_ids.len();
151 if current_page_num % 2 == 0 {
152 current_page_id = self.create_page(area_tree)?;
154 page_ids.push(current_page_id);
155 }
156 }
157 }
158 }
159 }
160
161 Ok(page_ids)
162 }
163
164 fn can_break_before(
166 &self,
167 area_tree: &AreaTree,
168 area_id: AreaId,
169 index: usize,
170 all_children: &[AreaId],
171 ) -> bool {
172 let current_area = match area_tree.get(area_id) {
174 Some(node) => &node.area,
175 None => return true, };
177
178 if let Some(constraint) = ¤t_area.keep_constraint {
180 if constraint.must_keep_together() {
181 return false;
185 }
186
187 if constraint.must_keep_with_previous() && index > 0 {
189 return false;
191 }
192 }
193
194 if index > 0 {
196 if let Some(prev_area_id) = all_children.get(index - 1) {
197 if let Some(prev_node) = area_tree.get(*prev_area_id) {
198 if let Some(constraint) = &prev_node.area.keep_constraint {
199 if constraint.must_keep_with_next() {
200 return false;
202 }
203 }
204 }
205 }
206 }
207
208 let orphans = current_area.orphans;
212 if orphans > 0 && index > 0 {
213 if let Some(prev_area_id) = all_children.get(index - 1) {
215 let line_count = self.count_line_areas(area_tree, *prev_area_id);
216 if line_count > 0 && line_count < orphans {
217 return false;
219 }
220 }
221 }
222
223 let widows = current_area.widows;
227 if widows > 0 {
228 let line_count = self.count_line_areas(area_tree, area_id);
229 if line_count > 0 && line_count < widows {
230 return false;
232 }
233 }
234
235 true
237 }
238
239 #[allow(clippy::only_used_in_recursion)]
244 fn count_line_areas(&self, area_tree: &AreaTree, area_id: AreaId) -> i32 {
245 let mut count = 0;
246
247 if let Some(node) = area_tree.get(area_id) {
249 if matches!(
251 node.area.area_type,
252 AreaType::Line | AreaType::Text | AreaType::Inline
253 ) {
254 count += 1;
255 }
256
257 let children = area_tree.children(area_id);
259 for child_id in children {
260 count += self.count_line_areas(area_tree, child_id);
261 }
262 }
263
264 count
265 }
266
267 fn create_page(&self, area_tree: &mut AreaTree) -> Result<AreaId> {
269 let page_rect =
271 Rect::from_point_size(Point::ZERO, Size::new(self.page_width, self.page_height));
272 let page_area = Area::new(AreaType::Page, page_rect);
273 let page_id = area_tree.add_area(page_area);
274
275 let region_rect = Rect::from_point_size(
277 Point::new(self.margins[3], self.margins[0]), Size::new(self.content_width(), self.content_height()),
279 );
280 let region_area = Area::new(AreaType::Region, region_rect);
281 let region_id = area_tree.add_area(region_area);
282
283 area_tree
285 .append_child(page_id, region_id)
286 .map_err(fop_types::FopError::Generic)?;
287
288 Ok(page_id)
289 }
290
291 pub fn place_footnotes(&self, area_tree: &mut AreaTree, page_id: AreaId) -> Result<()> {
293 let footnotes = match area_tree.get_footnotes(page_id) {
294 Some(f) if !f.is_empty() => f.clone(),
295 _ => return Ok(()), };
297
298 let footnote_total_height = area_tree.footnote_height(page_id);
300 let separator_y = self.margins[0] + self.content_height() - footnote_total_height;
301
302 let separator_rect = Rect::from_point_size(
304 Point::new(self.margins[3], separator_y),
305 Size::new(Length::from_pt(72.0), Length::from_pt(1.0)), );
307 let mut separator_area = Area::new(AreaType::FootnoteSeparator, separator_rect);
308
309 use crate::area::{BorderStyle, TraitSet};
311 use fop_types::Color;
312 let traits = TraitSet {
313 border_width: Some([
314 Length::from_pt(1.0),
315 Length::ZERO,
316 Length::ZERO,
317 Length::ZERO,
318 ]),
319 border_color: Some([Color::BLACK, Color::BLACK, Color::BLACK, Color::BLACK]),
320 border_style: Some([
321 BorderStyle::Solid,
322 BorderStyle::None,
323 BorderStyle::None,
324 BorderStyle::None,
325 ]),
326 ..Default::default()
327 };
328 separator_area.traits = traits;
329
330 let separator_id = area_tree.add_area(separator_area);
331 area_tree
332 .append_child(page_id, separator_id)
333 .map_err(fop_types::FopError::Generic)?;
334
335 let mut current_y = separator_y + Length::from_pt(7.0); for footnote_id in footnotes {
339 let footnote_height = if let Some(footnote_node) = area_tree.get(footnote_id) {
341 footnote_node.area.height()
342 } else {
343 continue;
344 };
345
346 if let Some(footnote_node) = area_tree.get_mut(footnote_id) {
347 footnote_node.area.geometry.x = self.margins[3];
349 footnote_node.area.geometry.y = current_y;
350 }
351
352 area_tree
354 .append_child(page_id, footnote_id)
355 .map_err(fop_types::FopError::Generic)?;
356
357 current_y += footnote_height;
358 }
359
360 Ok(())
361 }
362
363 pub fn fits_on_page(&self, current_height: Length, content_height: Length) -> bool {
365 current_height + content_height <= self.content_height()
366 }
367
368 pub fn split_area(
370 &self,
371 area_tree: &mut AreaTree,
372 area_id: AreaId,
373 available_height: Length,
374 ) -> Result<Option<AreaId>> {
375 if let Some(area_node) = area_tree.get(area_id) {
377 let area_height = area_node.area.height();
378
379 if area_height <= available_height {
381 return Ok(None);
382 }
383
384 let remaining_height = area_height - available_height;
386 let mut continuation_area = area_node.area.clone();
387 continuation_area.geometry.height = remaining_height;
388
389 let continuation_id = area_tree.add_area(continuation_area);
390 Ok(Some(continuation_id))
391 } else {
392 Ok(None)
393 }
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_page_breaker_dimensions() {
403 let breaker = PageBreaker::new(
404 Length::from_mm(210.0), Length::from_mm(297.0), [
407 Length::from_mm(20.0), Length::from_mm(20.0), Length::from_mm(20.0), Length::from_mm(20.0), ],
412 );
413
414 let content_width = breaker.content_width();
415 let content_height = breaker.content_height();
416
417 assert_eq!(content_width, Length::from_mm(170.0)); assert_eq!(content_height, Length::from_mm(257.0)); }
420
421 #[test]
422 fn test_create_empty_page() {
423 let breaker = PageBreaker::new(
424 Length::from_pt(595.0),
425 Length::from_pt(842.0),
426 [Length::from_pt(72.0); 4],
427 );
428
429 let mut tree = AreaTree::new();
430 let page_id = breaker
431 .create_page(&mut tree)
432 .expect("test: should succeed");
433
434 assert!(tree.get(page_id).is_some());
435 let page_node = tree.get(page_id).expect("test: should succeed");
436 assert_eq!(page_node.area.area_type, AreaType::Page);
437 }
438
439 #[test]
440 fn test_break_empty_tree() {
441 let breaker = PageBreaker::new(
442 Length::from_pt(595.0),
443 Length::from_pt(842.0),
444 [Length::from_pt(72.0); 4],
445 );
446
447 let mut tree = AreaTree::new();
448 let root = Area::new(
449 AreaType::Block,
450 Rect::from_point_size(Point::ZERO, Size::new(Length::ZERO, Length::ZERO)),
451 );
452 let root_id = tree.add_area(root);
453
454 let pages = breaker
455 .break_into_pages(&mut tree, root_id)
456 .expect("test: should succeed");
457
458 assert_eq!(pages.len(), 1);
460 }
461
462 #[test]
463 fn test_overflow_detection() {
464 let breaker = PageBreaker::new(
465 Length::from_pt(595.0),
466 Length::from_pt(842.0),
467 [Length::from_pt(72.0); 4], );
469
470 assert_eq!(breaker.content_height(), Length::from_pt(698.0));
472
473 assert!(breaker.fits_on_page(Length::from_pt(100.0), Length::from_pt(200.0)));
475 assert!(!breaker.fits_on_page(Length::from_pt(600.0), Length::from_pt(200.0)));
476 }
477
478 #[test]
479 fn test_multi_page_breaking() {
480 let breaker = PageBreaker::new(
481 Length::from_pt(595.0),
482 Length::from_pt(842.0),
483 [Length::from_pt(72.0); 4],
484 );
485
486 let mut tree = AreaTree::new();
487 let root = Area::new(
488 AreaType::Block,
489 Rect::from_point_size(Point::ZERO, Size::new(Length::ZERO, Length::ZERO)),
490 );
491 let root_id = tree.add_area(root);
492
493 for _ in 0..5 {
495 let block = Area::new(
496 AreaType::Block,
497 Rect::from_point_size(
498 Point::ZERO,
499 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
500 ),
501 );
502 let block_id = tree.add_area(block);
503 tree.append_child(root_id, block_id)
504 .expect("test: should succeed");
505 }
506
507 let pages = breaker
508 .break_into_pages(&mut tree, root_id)
509 .expect("test: should succeed");
510
511 assert!(pages.len() >= 2);
514 }
515
516 #[test]
517 fn test_split_area() {
518 let breaker = PageBreaker::new(
519 Length::from_pt(595.0),
520 Length::from_pt(842.0),
521 [Length::from_pt(72.0); 4],
522 );
523
524 let mut tree = AreaTree::new();
525
526 let large_block = Area::new(
528 AreaType::Block,
529 Rect::from_point_size(
530 Point::ZERO,
531 Size::new(Length::from_pt(400.0), Length::from_pt(800.0)),
532 ),
533 );
534 let block_id = tree.add_area(large_block);
535
536 let continuation = breaker
538 .split_area(&mut tree, block_id, Length::from_pt(300.0))
539 .expect("test: should succeed");
540
541 assert!(continuation.is_some());
542 }
543
544 #[test]
545 fn test_keep_with_previous_prevents_break() {
546 use crate::layout::{Keep, KeepConstraint};
547
548 let breaker = PageBreaker::new(
549 Length::from_pt(595.0),
550 Length::from_pt(842.0),
551 [Length::from_pt(72.0); 4],
552 );
553
554 let mut tree = AreaTree::new();
555
556 let block1 = Area::new(
558 AreaType::Block,
559 Rect::from_point_size(
560 Point::ZERO,
561 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
562 ),
563 );
564 let block1_id = tree.add_area(block1);
565
566 let mut constraint = KeepConstraint::new();
567 constraint.keep_with_previous = Keep::Always;
568
569 let block2 = Area::new(
570 AreaType::Block,
571 Rect::from_point_size(
572 Point::ZERO,
573 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
574 ),
575 )
576 .with_keep_constraint(constraint);
577 let block2_id = tree.add_area(block2);
578
579 let children = vec![block1_id, block2_id];
580
581 assert!(breaker.can_break_before(&tree, block1_id, 0, &children));
583
584 assert!(!breaker.can_break_before(&tree, block2_id, 1, &children));
586 }
587
588 #[test]
589 fn test_keep_with_next_prevents_break() {
590 use crate::layout::{Keep, KeepConstraint};
591
592 let breaker = PageBreaker::new(
593 Length::from_pt(595.0),
594 Length::from_pt(842.0),
595 [Length::from_pt(72.0); 4],
596 );
597
598 let mut tree = AreaTree::new();
599
600 let mut constraint = KeepConstraint::new();
602 constraint.keep_with_next = Keep::Always;
603
604 let block1 = Area::new(
605 AreaType::Block,
606 Rect::from_point_size(
607 Point::ZERO,
608 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
609 ),
610 )
611 .with_keep_constraint(constraint);
612 let block1_id = tree.add_area(block1);
613
614 let block2 = Area::new(
615 AreaType::Block,
616 Rect::from_point_size(
617 Point::ZERO,
618 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
619 ),
620 );
621 let block2_id = tree.add_area(block2);
622
623 let children = vec![block1_id, block2_id];
624
625 assert!(!breaker.can_break_before(&tree, block2_id, 1, &children));
627 }
628
629 #[test]
630 fn test_keep_together_prevents_break() {
631 use crate::layout::{Keep, KeepConstraint};
632
633 let breaker = PageBreaker::new(
634 Length::from_pt(595.0),
635 Length::from_pt(842.0),
636 [Length::from_pt(72.0); 4],
637 );
638
639 let mut tree = AreaTree::new();
640
641 let mut constraint = KeepConstraint::new();
642 constraint.keep_together = Keep::Always;
643
644 let block = Area::new(
645 AreaType::Block,
646 Rect::from_point_size(
647 Point::ZERO,
648 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
649 ),
650 )
651 .with_keep_constraint(constraint);
652 let block_id = tree.add_area(block);
653
654 let children = vec![block_id];
655
656 assert!(!breaker.can_break_before(&tree, block_id, 0, &children));
658 }
659
660 #[test]
661 fn test_no_keep_allows_break() {
662 let breaker = PageBreaker::new(
663 Length::from_pt(595.0),
664 Length::from_pt(842.0),
665 [Length::from_pt(72.0); 4],
666 );
667
668 let mut tree = AreaTree::new();
669
670 let block1 = Area::new(
672 AreaType::Block,
673 Rect::from_point_size(
674 Point::ZERO,
675 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
676 ),
677 );
678 let block1_id = tree.add_area(block1);
679
680 let block2 = Area::new(
681 AreaType::Block,
682 Rect::from_point_size(
683 Point::ZERO,
684 Size::new(Length::from_pt(400.0), Length::from_pt(200.0)),
685 ),
686 );
687 let block2_id = tree.add_area(block2);
688
689 let children = vec![block1_id, block2_id];
690
691 assert!(breaker.can_break_before(&tree, block1_id, 0, &children));
693 assert!(breaker.can_break_before(&tree, block2_id, 1, &children));
694 }
695}
696
697#[cfg(test)]
698mod extended_tests {
699 use super::*;
700 use crate::area::AreaType;
701
702 fn make_breaker() -> PageBreaker {
703 PageBreaker::new(
704 Length::from_pt(595.0),
705 Length::from_pt(842.0),
706 [Length::from_pt(72.0); 4],
707 )
708 }
709
710 #[test]
711 fn test_content_width_calculation() {
712 let breaker = make_breaker();
714 assert_eq!(breaker.content_width(), Length::from_pt(451.0));
715 }
716
717 #[test]
718 fn test_content_height_calculation() {
719 let breaker = make_breaker();
721 assert_eq!(breaker.content_height(), Length::from_pt(698.0));
722 }
723
724 #[test]
725 fn test_content_height_with_footnotes_reduces_available() {
726 let breaker = make_breaker();
727 let base = breaker.content_height();
728 let with_footnote = breaker.content_height_with_footnotes(Length::from_pt(50.0));
729 assert_eq!(with_footnote, base - Length::from_pt(50.0));
730 }
731
732 #[test]
733 fn test_fits_on_page_exactly_at_boundary() {
734 let breaker = make_breaker();
735 let content_h = breaker.content_height();
736 assert!(breaker.fits_on_page(Length::ZERO, content_h));
738 }
739
740 #[test]
741 fn test_fits_on_page_over_boundary_does_not_fit() {
742 let breaker = make_breaker();
743 let content_h = breaker.content_height();
744 assert!(!breaker.fits_on_page(Length::from_pt(1.0), content_h));
745 }
746
747 #[test]
748 fn test_split_area_fits_returns_none() {
749 let breaker = make_breaker();
750 let mut tree = AreaTree::new();
751 let area = Area::new(
752 AreaType::Block,
753 Rect::from_point_size(
754 Point::ZERO,
755 Size::new(Length::from_pt(400.0), Length::from_pt(100.0)),
756 ),
757 );
758 let area_id = tree.add_area(area);
759
760 let result = breaker
762 .split_area(&mut tree, area_id, Length::from_pt(200.0))
763 .expect("test: should succeed");
764 assert!(result.is_none());
765 }
766
767 #[test]
768 fn test_split_area_overflow_creates_continuation() {
769 let breaker = make_breaker();
770 let mut tree = AreaTree::new();
771 let area = Area::new(
772 AreaType::Block,
773 Rect::from_point_size(
774 Point::ZERO,
775 Size::new(Length::from_pt(400.0), Length::from_pt(300.0)),
776 ),
777 );
778 let area_id = tree.add_area(area);
779
780 let continuation = breaker
782 .split_area(&mut tree, area_id, Length::from_pt(200.0))
783 .expect("test: should succeed");
784 assert!(continuation.is_some());
785
786 let cont_id = continuation.expect("test: should succeed");
787 let cont_node = tree.get(cont_id).expect("test: should succeed");
788 assert_eq!(cont_node.area.height(), Length::from_pt(100.0));
790 }
791
792 #[test]
793 fn test_break_into_pages_single_block_fits() {
794 let breaker = make_breaker();
795 let mut tree = AreaTree::new();
796 let root = Area::new(
797 AreaType::Block,
798 Rect::from_point_size(Point::ZERO, Size::new(Length::ZERO, Length::ZERO)),
799 );
800 let root_id = tree.add_area(root);
801
802 let small_block = Area::new(
803 AreaType::Block,
804 Rect::from_point_size(
805 Point::ZERO,
806 Size::new(Length::from_pt(400.0), Length::from_pt(100.0)),
807 ),
808 );
809 let block_id = tree.add_area(small_block);
810 tree.append_child(root_id, block_id)
811 .expect("test: should succeed");
812
813 let pages = breaker
814 .break_into_pages(&mut tree, root_id)
815 .expect("test: should succeed");
816 assert_eq!(pages.len(), 1);
818 }
819
820 #[test]
821 fn test_count_line_areas_in_block_with_children() {
822 let breaker = make_breaker();
823 let mut tree = AreaTree::new();
824
825 let block = Area::new(
827 AreaType::Block,
828 Rect::from_point_size(
829 Point::ZERO,
830 Size::new(Length::from_pt(400.0), Length::from_pt(50.0)),
831 ),
832 );
833 let block_id = tree.add_area(block);
834
835 let line1 = Area::new(
836 AreaType::Line,
837 Rect::from_point_size(
838 Point::ZERO,
839 Size::new(Length::from_pt(400.0), Length::from_pt(12.0)),
840 ),
841 );
842 let line2 = Area::new(
843 AreaType::Line,
844 Rect::from_point_size(
845 Point::ZERO,
846 Size::new(Length::from_pt(400.0), Length::from_pt(12.0)),
847 ),
848 );
849 let line1_id = tree.add_area(line1);
850 let line2_id = tree.add_area(line2);
851
852 tree.append_child(block_id, line1_id)
853 .expect("test: should succeed");
854 tree.append_child(block_id, line2_id)
855 .expect("test: should succeed");
856
857 let count = breaker.count_line_areas(&tree, block_id);
858 assert_eq!(count, 2, "Block with 2 line children should count 2");
859 }
860
861 #[test]
862 fn test_page_has_region_child() {
863 let breaker = make_breaker();
864 let mut tree = AreaTree::new();
865 let page_id = breaker
866 .create_page(&mut tree)
867 .expect("test: should succeed");
868
869 let children = tree.children(page_id);
870 assert_eq!(children.len(), 1, "Page should have one region child");
871
872 let region_node = tree.get(children[0]).expect("test: should succeed");
873 assert_eq!(region_node.area.area_type, AreaType::Region);
874 }
875
876 #[test]
877 fn test_asymmetric_margins() {
878 let breaker = PageBreaker::new(
879 Length::from_pt(612.0), Length::from_pt(792.0), [
882 Length::from_pt(72.0), Length::from_pt(54.0), Length::from_pt(72.0), Length::from_pt(54.0), ],
887 );
888
889 assert_eq!(breaker.content_width(), Length::from_pt(504.0));
892 assert_eq!(breaker.content_height(), Length::from_pt(648.0));
893 }
894}