1use alloc::{boxed::Box, collections::BTreeMap, string::String, sync::Arc, vec::Vec};
24use core::fmt;
25
26use azul_core::{
27 dom::NodeId,
28 geom::{LogicalPosition, LogicalRect, LogicalSize},
29};
30use azul_css::props::layout::fragmentation::{
31 BoxDecorationBreak, BreakInside, Orphans, PageBreak, Widows,
32};
33
34#[cfg(all(feature = "text_layout", feature = "font_loading"))]
35use crate::solver3::display_list::{DisplayList, DisplayListItem};
36
37#[cfg(not(all(feature = "text_layout", feature = "font_loading")))]
39#[derive(Debug, Clone, Default)]
40pub struct DisplayList {
41 pub items: Vec<DisplayListItem>,
42}
43
44#[cfg(not(all(feature = "text_layout", feature = "font_loading")))]
45#[derive(Debug, Clone)]
46pub struct DisplayListItem;
47
48#[derive(Debug, Clone)]
52pub struct PageCounter {
53 pub page_number: usize,
55 pub total_pages: Option<usize>,
57 pub chapter: Option<usize>,
59 pub named_counters: BTreeMap<String, i32>,
61}
62
63impl Default for PageCounter {
64 fn default() -> Self {
65 Self {
66 page_number: 1,
67 total_pages: None,
68 chapter: None,
69 named_counters: BTreeMap::new(),
70 }
71 }
72}
73
74impl PageCounter {
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn with_page_number(mut self, page: usize) -> Self {
80 self.page_number = page;
81 self
82 }
83
84 pub fn with_total_pages(mut self, total: usize) -> Self {
85 self.total_pages = Some(total);
86 self
87 }
88
89 pub fn format_page_number(&self, style: PageNumberStyle) -> String {
91 match style {
92 PageNumberStyle::Decimal => format!("{}", self.page_number),
93 PageNumberStyle::LowerRoman => to_lower_roman(self.page_number),
94 PageNumberStyle::UpperRoman => to_upper_roman(self.page_number),
95 PageNumberStyle::LowerAlpha => to_lower_alpha(self.page_number),
96 PageNumberStyle::UpperAlpha => to_upper_alpha(self.page_number),
97 }
98 }
99
100 pub fn format_page_of_total(&self) -> String {
102 match self.total_pages {
103 Some(total) => format!("Page {} of {}", self.page_number, total),
104 None => format!("Page {}", self.page_number),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum PageNumberStyle {
112 Decimal,
114 LowerRoman,
116 UpperRoman,
118 LowerAlpha,
120 UpperAlpha,
122}
123
124impl Default for PageNumberStyle {
125 fn default() -> Self {
126 Self::Decimal
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum PageSlotPosition {
133 TopLeft,
135 TopCenter,
137 TopRight,
139 BottomLeft,
141 BottomCenter,
143 BottomRight,
145}
146
147#[derive(Clone)]
149pub enum PageSlotContent {
150 Text(String),
152 PageNumber(PageNumberStyle),
154 PageOfTotal,
156 RunningHeader(String),
158 Dynamic(Arc<DynamicSlotContentFn>),
160}
161
162pub struct DynamicSlotContentFn {
164 func: Box<dyn Fn(&PageCounter) -> String + Send + Sync>,
165}
166
167impl DynamicSlotContentFn {
168 pub fn new<F: Fn(&PageCounter) -> String + Send + Sync + 'static>(f: F) -> Self {
169 Self { func: Box::new(f) }
170 }
171
172 pub fn call(&self, counter: &PageCounter) -> String {
173 (self.func)(counter)
174 }
175}
176
177impl fmt::Debug for DynamicSlotContentFn {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(f, "<dynamic content fn>")
180 }
181}
182
183impl fmt::Debug for PageSlotContent {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 match self {
186 PageSlotContent::Text(s) => write!(f, "Text({:?})", s),
187 PageSlotContent::PageNumber(style) => write!(f, "PageNumber({:?})", style),
188 PageSlotContent::PageOfTotal => write!(f, "PageOfTotal"),
189 PageSlotContent::RunningHeader(s) => write!(f, "RunningHeader({:?})", s),
190 PageSlotContent::Dynamic(_) => write!(f, "Dynamic(<fn>)"),
191 }
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct PageSlot {
198 pub position: PageSlotPosition,
200 pub content: PageSlotContent,
202 pub font_size_pt: Option<f32>,
204 pub color: Option<azul_css::props::basic::ColorU>,
206}
207
208#[derive(Debug, Clone)]
210pub struct PageTemplate {
211 pub header_height: f32,
213 pub footer_height: f32,
215 pub slots: Vec<PageSlot>,
217 pub header_on_first_page: bool,
219 pub footer_on_first_page: bool,
221 pub left_page_slots: Option<Vec<PageSlot>>,
223 pub right_page_slots: Option<Vec<PageSlot>>,
225}
226
227impl Default for PageTemplate {
228 fn default() -> Self {
229 Self {
230 header_height: 0.0,
231 footer_height: 0.0,
232 slots: Vec::new(),
233 header_on_first_page: true,
234 footer_on_first_page: true,
235 left_page_slots: None,
236 right_page_slots: None,
237 }
238 }
239}
240
241impl PageTemplate {
242 pub fn new() -> Self {
243 Self::default()
244 }
245
246 pub fn with_page_number_footer(mut self, height: f32) -> Self {
248 self.footer_height = height;
249 self.slots.push(PageSlot {
250 position: PageSlotPosition::BottomCenter,
251 content: PageSlotContent::PageNumber(PageNumberStyle::Decimal),
252 font_size_pt: Some(10.0),
253 color: None,
254 });
255 self
256 }
257
258 pub fn with_page_of_total_footer(mut self, height: f32) -> Self {
260 self.footer_height = height;
261 self.slots.push(PageSlot {
262 position: PageSlotPosition::BottomCenter,
263 content: PageSlotContent::PageOfTotal,
264 font_size_pt: Some(10.0),
265 color: None,
266 });
267 self
268 }
269
270 pub fn with_book_header(mut self, title: String, height: f32) -> Self {
272 self.header_height = height;
273 self.slots.push(PageSlot {
274 position: PageSlotPosition::TopLeft,
275 content: PageSlotContent::Text(title),
276 font_size_pt: Some(10.0),
277 color: None,
278 });
279 self.slots.push(PageSlot {
280 position: PageSlotPosition::TopRight,
281 content: PageSlotContent::PageNumber(PageNumberStyle::Decimal),
282 font_size_pt: Some(10.0),
283 color: None,
284 });
285 self
286 }
287
288 pub fn slots_for_page(&self, page_number: usize) -> &[PageSlot] {
290 let is_left_page = page_number % 2 == 0;
291 if is_left_page {
292 if let Some(ref left_slots) = self.left_page_slots {
293 return left_slots;
294 }
295 } else {
296 if let Some(ref right_slots) = self.right_page_slots {
297 return right_slots;
298 }
299 }
300 &self.slots
301 }
302
303 pub fn show_header(&self, page_number: usize) -> bool {
305 if page_number == 1 && !self.header_on_first_page {
306 return false;
307 }
308 self.header_height > 0.0
309 }
310
311 pub fn show_footer(&self, page_number: usize) -> bool {
313 if page_number == 1 && !self.footer_on_first_page {
314 return false;
315 }
316 self.footer_height > 0.0
317 }
318
319 pub fn content_area_height(&self, page_height: f32, page_number: usize) -> f32 {
321 let header = if self.show_header(page_number) {
322 self.header_height
323 } else {
324 0.0
325 };
326 let footer = if self.show_footer(page_number) {
327 self.footer_height
328 } else {
329 0.0
330 };
331 page_height - header - footer
332 }
333}
334
335#[derive(Debug, Clone)]
339pub enum BoxBreakBehavior {
340 Splittable {
342 min_before_break: f32,
344 min_after_break: f32,
346 },
347 KeepTogether {
349 estimated_height: f32,
351 priority: KeepTogetherPriority,
353 },
354 Monolithic {
356 height: f32,
358 },
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
363pub enum KeepTogetherPriority {
364 Low = 0,
366 Normal = 1,
368 High = 2,
370 Critical = 3,
372}
373
374#[derive(Debug, Clone)]
376pub struct BreakPoint {
377 pub y_position: f32,
379 pub break_class: BreakClass,
381 pub break_before: PageBreak,
383 pub break_after: PageBreak,
385 pub ancestor_avoid_depth: usize,
387 pub preceding_node: Option<NodeId>,
389 pub following_node: Option<NodeId>,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub enum BreakClass {
396 ClassA,
398 ClassB,
400 ClassC,
402}
403
404impl BreakPoint {
405 pub fn is_allowed(&self) -> bool {
407 if is_forced_break(&self.break_before) || is_forced_break(&self.break_after) {
409 return true; }
411
412 if is_avoid_break(&self.break_before) || is_avoid_break(&self.break_after) {
413 return false; }
415
416 if self.ancestor_avoid_depth > 0 {
418 return false;
419 }
420
421 true
423 }
424
425 pub fn is_forced(&self) -> bool {
427 is_forced_break(&self.break_before) || is_forced_break(&self.break_after)
428 }
429}
430
431#[derive(Debug)]
435pub struct PageFragment {
436 pub page_index: usize,
438 pub bounds: LogicalRect,
440 pub items: Vec<DisplayListItem>,
442 pub source_node: Option<NodeId>,
444 pub is_continuation: bool,
446 pub continues_on_next: bool,
448}
449
450#[derive(Debug)]
452pub struct FragmentationLayoutContext {
453 pub page_size: LogicalSize,
455 pub margins: PageMargins,
457 pub template: PageTemplate,
459 pub current_page: usize,
461 pub current_y: f32,
463 pub available_height: f32,
465 pub page_content_height: f32,
467 pub break_inside_avoid_depth: usize,
469 pub orphans: u32,
471 pub widows: u32,
473 pub fragments: Vec<PageFragment>,
475 pub counter: PageCounter,
477 pub defaults: FragmentationDefaults,
479 pub break_points: Vec<BreakPoint>,
481 pub avoid_break_before_next: bool,
483}
484
485#[derive(Debug, Clone, Copy, Default)]
487pub struct PageMargins {
488 pub top: f32,
489 pub right: f32,
490 pub bottom: f32,
491 pub left: f32,
492}
493
494impl PageMargins {
495 pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
496 Self {
497 top,
498 right,
499 bottom,
500 left,
501 }
502 }
503
504 pub fn uniform(margin: f32) -> Self {
505 Self {
506 top: margin,
507 right: margin,
508 bottom: margin,
509 left: margin,
510 }
511 }
512
513 pub fn horizontal(&self) -> f32 {
514 self.left + self.right
515 }
516
517 pub fn vertical(&self) -> f32 {
518 self.top + self.bottom
519 }
520}
521
522#[derive(Debug, Clone)]
524pub struct FragmentationDefaults {
525 pub keep_headers_with_content: bool,
527 pub min_paragraph_lines: u32,
529 pub keep_figures_together: bool,
531 pub keep_table_headers: bool,
533 pub keep_list_markers: bool,
535 pub small_block_threshold_lines: u32,
537 pub default_orphans: u32,
539 pub default_widows: u32,
541}
542
543impl Default for FragmentationDefaults {
544 fn default() -> Self {
545 Self {
546 keep_headers_with_content: true,
547 min_paragraph_lines: 3,
548 keep_figures_together: true,
549 keep_table_headers: true,
550 keep_list_markers: true,
551 small_block_threshold_lines: 3,
552 default_orphans: 2,
553 default_widows: 2,
554 }
555 }
556}
557
558impl FragmentationLayoutContext {
559 pub fn new(page_size: LogicalSize, margins: PageMargins) -> Self {
561 let template = PageTemplate::default();
562
563 let page_content_height =
564 page_size.height - margins.vertical() - template.header_height - template.footer_height;
565
566 Self {
567 page_size,
568 margins,
569 template,
570 current_page: 0,
571 current_y: 0.0,
572 available_height: page_content_height,
573 page_content_height,
574 break_inside_avoid_depth: 0,
575 orphans: 2,
576 widows: 2,
577 fragments: Vec::new(),
578 counter: PageCounter::new(),
579 defaults: FragmentationDefaults::default(),
580 break_points: Vec::new(),
581 avoid_break_before_next: false,
582 }
583 }
584
585 pub fn with_template(mut self, template: PageTemplate) -> Self {
587 self.template = template;
588 self.recalculate_content_height();
589 self
590 }
591
592 pub fn with_defaults(mut self, defaults: FragmentationDefaults) -> Self {
594 self.orphans = defaults.default_orphans;
595 self.widows = defaults.default_widows;
596 self.defaults = defaults;
597 self
598 }
599
600 fn recalculate_content_height(&mut self) {
602 let header = if self.template.show_header(self.current_page + 1) {
603 self.template.header_height
604 } else {
605 0.0
606 };
607 let footer = if self.template.show_footer(self.current_page + 1) {
608 self.template.footer_height
609 } else {
610 0.0
611 };
612 self.page_content_height =
613 self.page_size.height - self.margins.vertical() - header - footer;
614 self.available_height = self.page_content_height - self.current_y;
615 }
616
617 pub fn content_origin(&self) -> LogicalPosition {
619 let header = if self.template.show_header(self.current_page + 1) {
620 self.template.header_height
621 } else {
622 0.0
623 };
624 LogicalPosition {
625 x: self.margins.left,
626 y: self.margins.top + header,
627 }
628 }
629
630 pub fn content_size(&self) -> LogicalSize {
632 LogicalSize {
633 width: self.page_size.width - self.margins.horizontal(),
634 height: self.page_content_height,
635 }
636 }
637
638 pub fn use_space(&mut self, height: f32) {
640 self.current_y += height;
641 self.available_height = (self.page_content_height - self.current_y).max(0.0);
642 }
643
644 pub fn can_fit(&self, height: f32) -> bool {
646 self.available_height >= height
647 }
648
649 pub fn would_fit_on_empty_page(&self, height: f32) -> bool {
651 height <= self.page_content_height
652 }
653
654 pub fn advance_page(&mut self) {
656 self.current_page += 1;
657 self.current_y = 0.0;
658 self.counter.page_number += 1;
659 self.recalculate_content_height();
660 self.avoid_break_before_next = false;
661 }
662
663 pub fn advance_to_left_page(&mut self) {
665 self.advance_page();
666 if self.current_page % 2 != 0 {
667 self.advance_page();
669 }
670 }
671
672 pub fn advance_to_right_page(&mut self) {
674 self.advance_page();
675 if self.current_page % 2 == 0 {
676 self.advance_page();
678 }
679 }
680
681 pub fn enter_avoid_break(&mut self) {
683 self.break_inside_avoid_depth += 1;
684 }
685
686 pub fn exit_avoid_break(&mut self) {
688 self.break_inside_avoid_depth = self.break_inside_avoid_depth.saturating_sub(1);
689 }
690
691 pub fn set_avoid_break_before_next(&mut self) {
693 self.avoid_break_before_next = true;
694 }
695
696 pub fn add_fragment(&mut self, fragment: PageFragment) {
698 self.fragments.push(fragment);
699 }
700
701 pub fn page_count(&self) -> usize {
703 self.current_page + 1
704 }
705
706 pub fn set_total_pages(&mut self, total: usize) {
708 self.counter.total_pages = Some(total);
709 }
710
711 pub fn into_display_lists(self) -> Vec<DisplayList> {
713 let page_count = self.page_count();
714 let mut display_lists: Vec<DisplayList> =
715 (0..page_count).map(|_| DisplayList::default()).collect();
716
717 for fragment in self.fragments {
718 if fragment.page_index < display_lists.len() {
719 display_lists[fragment.page_index]
720 .items
721 .extend(fragment.items);
722 }
723 }
724
725 display_lists
726 }
727
728 pub fn generate_page_chrome(&self, page_index: usize) -> Vec<DisplayListItem> {
730 let mut items = Vec::new();
731 let page_number = page_index + 1;
732
733 let counter = PageCounter {
734 page_number,
735 total_pages: self.counter.total_pages,
736 chapter: self.counter.chapter,
737 named_counters: self.counter.named_counters.clone(),
738 };
739
740 let slots = self.template.slots_for_page(page_number);
741
742 for slot in slots {
743 let _text = match &slot.content {
744 PageSlotContent::Text(s) => s.clone(),
745 PageSlotContent::PageNumber(style) => counter.format_page_number(*style),
746 PageSlotContent::PageOfTotal => counter.format_page_of_total(),
747 PageSlotContent::RunningHeader(s) => s.clone(),
748 PageSlotContent::Dynamic(f) => f.call(&counter),
749 };
750
751 let (_x, _y) = self.slot_position(slot.position, page_number);
753
754 }
758
759 items
760 }
761
762 fn slot_position(&self, position: PageSlotPosition, page_number: usize) -> (f32, f32) {
764 let content_width = self.page_size.width - self.margins.horizontal();
765
766 let x = match position {
767 PageSlotPosition::TopLeft | PageSlotPosition::BottomLeft => self.margins.left,
768 PageSlotPosition::TopCenter | PageSlotPosition::BottomCenter => {
769 self.margins.left + content_width / 2.0
770 }
771 PageSlotPosition::TopRight | PageSlotPosition::BottomRight => {
772 self.page_size.width - self.margins.right
773 }
774 };
775
776 let y = match position {
777 PageSlotPosition::TopLeft
778 | PageSlotPosition::TopCenter
779 | PageSlotPosition::TopRight => self.margins.top + self.template.header_height / 2.0,
780 PageSlotPosition::BottomLeft
781 | PageSlotPosition::BottomCenter
782 | PageSlotPosition::BottomRight => {
783 self.page_size.height - self.margins.bottom - self.template.footer_height / 2.0
784 }
785 };
786
787 (x, y)
788 }
789}
790
791#[derive(Debug, Clone)]
795pub enum BreakDecision {
796 FitOnCurrentPage,
798 MoveToNextPage,
800 SplitAcrossPages {
802 height_on_current: f32,
804 height_remaining: f32,
806 },
807 ForceBreakBefore,
809 ForceBreakAfter,
811}
812
813pub fn decide_break(
815 behavior: &BoxBreakBehavior,
816 ctx: &FragmentationLayoutContext,
817 break_before: PageBreak,
818 break_after: PageBreak,
819) -> BreakDecision {
820 if is_forced_break(&break_before) {
822 if ctx.current_y > 0.0 {
823 return BreakDecision::ForceBreakBefore;
824 }
825 }
826
827 match behavior {
828 BoxBreakBehavior::Monolithic { height } => {
829 decide_monolithic_break(*height, ctx, break_before)
830 }
831 BoxBreakBehavior::KeepTogether {
832 estimated_height,
833 priority,
834 } => decide_keep_together_break(*estimated_height, *priority, ctx, break_before),
835 BoxBreakBehavior::Splittable {
836 min_before_break,
837 min_after_break,
838 } => decide_splittable_break(*min_before_break, *min_after_break, ctx, break_before),
839 }
840}
841
842fn decide_monolithic_break(
843 height: f32,
844 ctx: &FragmentationLayoutContext,
845 break_before: PageBreak,
846) -> BreakDecision {
847 if ctx.can_fit(height) {
849 BreakDecision::FitOnCurrentPage
850 } else if ctx.current_y > 0.0 && ctx.would_fit_on_empty_page(height) {
851 BreakDecision::MoveToNextPage
853 } else {
854 BreakDecision::FitOnCurrentPage
856 }
857}
858
859fn decide_keep_together_break(
860 height: f32,
861 priority: KeepTogetherPriority,
862 ctx: &FragmentationLayoutContext,
863 break_before: PageBreak,
864) -> BreakDecision {
865 if ctx.can_fit(height) {
866 BreakDecision::FitOnCurrentPage
867 } else if ctx.would_fit_on_empty_page(height) {
868 BreakDecision::MoveToNextPage
870 } else {
871 let height_on_current = ctx.available_height;
874 let height_remaining = height - height_on_current;
875 BreakDecision::SplitAcrossPages {
876 height_on_current,
877 height_remaining,
878 }
879 }
880}
881
882fn decide_splittable_break(
883 min_before: f32,
884 min_after: f32,
885 ctx: &FragmentationLayoutContext,
886 break_before: PageBreak,
887) -> BreakDecision {
888 let available = ctx.available_height;
890
891 if available < min_before && ctx.current_y > 0.0 {
892 BreakDecision::MoveToNextPage
894 } else {
895 BreakDecision::FitOnCurrentPage
897 }
898}
899
900fn is_forced_break(page_break: &PageBreak) -> bool {
903 matches!(
904 page_break,
905 PageBreak::Always
906 | PageBreak::Page
907 | PageBreak::Left
908 | PageBreak::Right
909 | PageBreak::Recto
910 | PageBreak::Verso
911 | PageBreak::All
912 )
913}
914
915fn is_avoid_break(page_break: &PageBreak) -> bool {
916 matches!(page_break, PageBreak::Avoid | PageBreak::AvoidPage)
917}
918
919fn to_lower_roman(n: usize) -> String {
921 to_upper_roman(n).to_lowercase()
922}
923
924fn to_upper_roman(mut n: usize) -> String {
925 if n == 0 {
926 return String::from("0");
927 }
928
929 let numerals = [
930 (1000, "M"),
931 (900, "CM"),
932 (500, "D"),
933 (400, "CD"),
934 (100, "C"),
935 (90, "XC"),
936 (50, "L"),
937 (40, "XL"),
938 (10, "X"),
939 (9, "IX"),
940 (5, "V"),
941 (4, "IV"),
942 (1, "I"),
943 ];
944
945 let mut result = String::new();
946 for (value, numeral) in numerals.iter() {
947 while n >= *value {
948 result.push_str(numeral);
949 n -= value;
950 }
951 }
952 result
953}
954
955fn to_lower_alpha(n: usize) -> String {
956 to_upper_alpha(n).to_lowercase()
957}
958
959fn to_upper_alpha(mut n: usize) -> String {
960 if n == 0 {
961 return String::from("0");
962 }
963
964 let mut result = String::new();
965 while n > 0 {
966 n -= 1;
967 result.insert(0, (b'A' + (n % 26) as u8) as char);
968 n /= 26;
969 }
970 result
971}