1use alloc::{boxed::Box, collections::BTreeMap, string::String, sync::Arc, vec::Vec};
28use core::fmt;
29
30use azul_core::{
31 dom::NodeId,
32 geom::{LogicalPosition, LogicalRect, LogicalSize},
33};
34use azul_css::props::layout::fragmentation::{
35 BoxDecorationBreak, BreakInside, Orphans, PageBreak, Widows,
36};
37
38#[cfg(all(feature = "text_layout", feature = "font_loading"))]
39use crate::solver3::display_list::{DisplayList, DisplayListItem};
40
41#[cfg(not(all(feature = "text_layout", feature = "font_loading")))]
43#[derive(Debug, Clone, Default)]
44pub struct DisplayList {
45 pub items: Vec<DisplayListItem>,
46}
47
48#[cfg(not(all(feature = "text_layout", feature = "font_loading")))]
49#[derive(Debug, Clone)]
50pub struct DisplayListItem;
51
52#[derive(Debug, Clone)]
56pub struct PageCounter {
57 pub page_number: usize,
59 pub total_pages: Option<usize>,
61 pub chapter: Option<usize>,
63 pub named_counters: BTreeMap<String, i32>,
65}
66
67impl Default for PageCounter {
68 fn default() -> Self {
69 Self {
70 page_number: 1,
71 total_pages: None,
72 chapter: None,
73 named_counters: BTreeMap::new(),
74 }
75 }
76}
77
78impl PageCounter {
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn with_page_number(mut self, page: usize) -> Self {
84 self.page_number = page;
85 self
86 }
87
88 pub fn with_total_pages(mut self, total: usize) -> Self {
89 self.total_pages = Some(total);
90 self
91 }
92
93 pub fn format_page_number(&self, style: PageNumberStyle) -> String {
95 match style {
96 PageNumberStyle::Decimal => format!("{}", self.page_number),
97 PageNumberStyle::LowerRoman => to_lower_roman(self.page_number),
98 PageNumberStyle::UpperRoman => to_upper_roman(self.page_number),
99 PageNumberStyle::LowerAlpha => to_lower_alpha(self.page_number),
100 PageNumberStyle::UpperAlpha => to_upper_alpha(self.page_number),
101 }
102 }
103
104 pub fn format_page_of_total(&self) -> String {
106 match self.total_pages {
107 Some(total) => format!("Page {} of {}", self.page_number, total),
108 None => format!("Page {}", self.page_number),
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum PageNumberStyle {
116 Decimal,
118 LowerRoman,
120 UpperRoman,
122 LowerAlpha,
124 UpperAlpha,
126}
127
128impl Default for PageNumberStyle {
129 fn default() -> Self {
130 Self::Decimal
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum PageSlotPosition {
137 TopLeft,
139 TopCenter,
141 TopRight,
143 BottomLeft,
145 BottomCenter,
147 BottomRight,
149}
150
151#[derive(Clone)]
153pub enum PageSlotContent {
154 Text(String),
156 PageNumber(PageNumberStyle),
158 PageOfTotal,
160 RunningHeader(String),
162 Dynamic(Arc<DynamicSlotContentFn>),
164}
165
166pub struct DynamicSlotContentFn {
178 func: Box<dyn Fn(&PageCounter) -> String + Send + Sync>,
179}
180
181impl DynamicSlotContentFn {
182 pub fn new<F: Fn(&PageCounter) -> String + Send + Sync + 'static>(f: F) -> Self {
183 Self { func: Box::new(f) }
184 }
185
186 pub fn call(&self, counter: &PageCounter) -> String {
187 (self.func)(counter)
188 }
189}
190
191impl fmt::Debug for DynamicSlotContentFn {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 write!(f, "<dynamic content fn>")
194 }
195}
196
197impl fmt::Debug for PageSlotContent {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 PageSlotContent::Text(s) => write!(f, "Text({:?})", s),
201 PageSlotContent::PageNumber(style) => write!(f, "PageNumber({:?})", style),
202 PageSlotContent::PageOfTotal => write!(f, "PageOfTotal"),
203 PageSlotContent::RunningHeader(s) => write!(f, "RunningHeader({:?})", s),
204 PageSlotContent::Dynamic(_) => write!(f, "Dynamic(<fn>)"),
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct PageSlot {
212 pub position: PageSlotPosition,
214 pub content: PageSlotContent,
216 pub font_size_pt: Option<f32>,
218 pub color: Option<azul_css::props::basic::ColorU>,
220}
221
222#[derive(Debug, Clone)]
224pub struct PageTemplate {
225 pub header_height: f32,
227 pub footer_height: f32,
229 pub slots: Vec<PageSlot>,
231 pub header_on_first_page: bool,
233 pub footer_on_first_page: bool,
235 pub left_page_slots: Option<Vec<PageSlot>>,
237 pub right_page_slots: Option<Vec<PageSlot>>,
239}
240
241impl Default for PageTemplate {
242 fn default() -> Self {
243 Self {
244 header_height: 0.0,
245 footer_height: 0.0,
246 slots: Vec::new(),
247 header_on_first_page: true,
248 footer_on_first_page: true,
249 left_page_slots: None,
250 right_page_slots: None,
251 }
252 }
253}
254
255const DEFAULT_SLOT_FONT_SIZE_PT: f32 = 10.0;
257
258impl PageTemplate {
259 pub fn new() -> Self {
260 Self::default()
261 }
262
263 pub fn with_page_number_footer(mut self, height: f32) -> Self {
265 self.footer_height = height;
266 self.slots.push(PageSlot {
267 position: PageSlotPosition::BottomCenter,
268 content: PageSlotContent::PageNumber(PageNumberStyle::Decimal),
269 font_size_pt: Some(DEFAULT_SLOT_FONT_SIZE_PT),
270 color: None,
271 });
272 self
273 }
274
275 pub fn with_page_of_total_footer(mut self, height: f32) -> Self {
277 self.footer_height = height;
278 self.slots.push(PageSlot {
279 position: PageSlotPosition::BottomCenter,
280 content: PageSlotContent::PageOfTotal,
281 font_size_pt: Some(DEFAULT_SLOT_FONT_SIZE_PT),
282 color: None,
283 });
284 self
285 }
286
287 pub fn with_book_header(mut self, title: String, height: f32) -> Self {
289 self.header_height = height;
290 self.slots.push(PageSlot {
291 position: PageSlotPosition::TopLeft,
292 content: PageSlotContent::Text(title),
293 font_size_pt: Some(DEFAULT_SLOT_FONT_SIZE_PT),
294 color: None,
295 });
296 self.slots.push(PageSlot {
297 position: PageSlotPosition::TopRight,
298 content: PageSlotContent::PageNumber(PageNumberStyle::Decimal),
299 font_size_pt: Some(DEFAULT_SLOT_FONT_SIZE_PT),
300 color: None,
301 });
302 self
303 }
304
305 pub fn slots_for_page(&self, page_number: usize) -> &[PageSlot] {
307 let override_slots = if page_number % 2 == 0 {
308 self.left_page_slots.as_deref()
309 } else {
310 self.right_page_slots.as_deref()
311 };
312 override_slots.unwrap_or(&self.slots)
313 }
314
315 pub fn show_header(&self, page_number: usize) -> bool {
317 if page_number == 1 && !self.header_on_first_page {
318 return false;
319 }
320 self.header_height > 0.0
321 }
322
323 pub fn show_footer(&self, page_number: usize) -> bool {
325 if page_number == 1 && !self.footer_on_first_page {
326 return false;
327 }
328 self.footer_height > 0.0
329 }
330
331 pub fn content_area_height(&self, page_height: f32, page_number: usize) -> f32 {
333 let header = if self.show_header(page_number) {
334 self.header_height
335 } else {
336 0.0
337 };
338 let footer = if self.show_footer(page_number) {
339 self.footer_height
340 } else {
341 0.0
342 };
343 page_height - header - footer
344 }
345}
346
347#[derive(Debug, Clone)]
351pub enum BoxBreakBehavior {
352 Splittable {
354 min_before_break: f32,
356 min_after_break: f32,
358 },
359 KeepTogether {
361 estimated_height: f32,
363 priority: KeepTogetherPriority,
365 },
366 Monolithic {
368 height: f32,
370 },
371}
372
373#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
375pub enum KeepTogetherPriority {
376 Low = 0,
378 Normal = 1,
380 High = 2,
382 Critical = 3,
384}
385
386#[derive(Debug, Clone)]
388pub struct BreakPoint {
389 pub y_position: f32,
391 pub break_class: BreakClass,
393 pub break_before: PageBreak,
395 pub break_after: PageBreak,
397 pub ancestor_avoid_depth: usize,
399 pub preceding_node: Option<NodeId>,
401 pub following_node: Option<NodeId>,
403}
404
405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407pub enum BreakClass {
408 ClassA,
410 ClassB,
413 ClassC,
415}
416
417impl BreakPoint {
418 pub fn is_allowed(&self) -> bool {
420 if is_forced_break(&self.break_before) || is_forced_break(&self.break_after) {
422 return true; }
424
425 if is_avoid_break(&self.break_before) || is_avoid_break(&self.break_after) {
426 return false; }
428
429 if self.ancestor_avoid_depth > 0 {
431 return false;
432 }
433
434 true
436 }
437
438 pub fn is_forced(&self) -> bool {
440 is_forced_break(&self.break_before) || is_forced_break(&self.break_after)
441 }
442}
443
444#[derive(Debug)]
448pub struct PageFragment {
449 pub page_index: usize,
451 pub bounds: LogicalRect,
453 pub items: Vec<DisplayListItem>,
455 pub source_node: Option<NodeId>,
457 pub is_continuation: bool,
459 pub continues_on_next: bool,
461}
462
463#[derive(Debug)]
465pub struct FragmentationLayoutContext {
466 pub page_size: LogicalSize,
468 pub margins: PageMargins,
470 pub template: PageTemplate,
472 pub current_page: usize,
474 pub current_y: f32,
476 pub available_height: f32,
478 pub page_content_height: f32,
480 pub break_inside_avoid_depth: usize,
482 pub orphans: u32,
484 pub widows: u32,
486 pub fragments: Vec<PageFragment>,
488 pub counter: PageCounter,
490 pub defaults: FragmentationDefaults,
492 pub break_points: Vec<BreakPoint>,
494 pub avoid_break_before_next: bool,
496}
497
498#[derive(Debug, Clone, Copy, Default)]
500pub struct PageMargins {
501 pub top: f32,
502 pub right: f32,
503 pub bottom: f32,
504 pub left: f32,
505}
506
507impl PageMargins {
508 pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
509 Self {
510 top,
511 right,
512 bottom,
513 left,
514 }
515 }
516
517 pub fn uniform(margin: f32) -> Self {
518 Self {
519 top: margin,
520 right: margin,
521 bottom: margin,
522 left: margin,
523 }
524 }
525
526 pub fn horizontal(&self) -> f32 {
527 self.left + self.right
528 }
529
530 pub fn vertical(&self) -> f32 {
531 self.top + self.bottom
532 }
533}
534
535#[derive(Debug, Clone)]
537pub struct FragmentationDefaults {
538 pub keep_headers_with_content: bool,
540 pub min_paragraph_lines: u32,
542 pub keep_figures_together: bool,
544 pub keep_table_headers: bool,
546 pub keep_list_markers: bool,
548 pub small_block_threshold_lines: u32,
550 pub default_orphans: u32,
552 pub default_widows: u32,
554}
555
556impl Default for FragmentationDefaults {
557 fn default() -> Self {
558 Self {
559 keep_headers_with_content: true,
560 min_paragraph_lines: 3,
561 keep_figures_together: true,
562 keep_table_headers: true,
563 keep_list_markers: true,
564 small_block_threshold_lines: 3,
565 default_orphans: 2,
566 default_widows: 2,
567 }
568 }
569}
570
571impl FragmentationLayoutContext {
572 pub fn new(page_size: LogicalSize, margins: PageMargins) -> Self {
574 let template = PageTemplate::default();
575
576 let page_content_height =
577 page_size.height - margins.vertical() - template.header_height - template.footer_height;
578
579 Self {
580 page_size,
581 margins,
582 template,
583 current_page: 0,
584 current_y: 0.0,
585 available_height: page_content_height,
586 page_content_height,
587 break_inside_avoid_depth: 0,
588 orphans: 2,
589 widows: 2,
590 fragments: Vec::new(),
591 counter: PageCounter::new(),
592 defaults: FragmentationDefaults::default(),
593 break_points: Vec::new(),
594 avoid_break_before_next: false,
595 }
596 }
597
598 pub fn with_template(mut self, template: PageTemplate) -> Self {
600 self.template = template;
601 self.recalculate_content_height();
602 self
603 }
604
605 pub fn with_defaults(mut self, defaults: FragmentationDefaults) -> Self {
607 self.orphans = defaults.default_orphans;
608 self.widows = defaults.default_widows;
609 self.defaults = defaults;
610 self
611 }
612
613 fn recalculate_content_height(&mut self) {
615 let page_height = self.page_size.height - self.margins.vertical();
616 self.page_content_height =
617 self.template.content_area_height(page_height, self.current_page + 1);
618 self.available_height = self.page_content_height - self.current_y;
619 }
620
621 pub fn content_origin(&self) -> LogicalPosition {
623 let header = if self.template.show_header(self.current_page + 1) {
624 self.template.header_height
625 } else {
626 0.0
627 };
628 LogicalPosition {
629 x: self.margins.left,
630 y: self.margins.top + header,
631 }
632 }
633
634 pub fn content_size(&self) -> LogicalSize {
636 LogicalSize {
637 width: self.page_size.width - self.margins.horizontal(),
638 height: self.page_content_height,
639 }
640 }
641
642 pub fn use_space(&mut self, height: f32) {
644 self.current_y += height;
645 self.available_height = (self.page_content_height - self.current_y).max(0.0);
646 }
647
648 pub fn can_fit(&self, height: f32) -> bool {
650 self.available_height >= height
651 }
652
653 pub fn would_fit_on_empty_page(&self, height: f32) -> bool {
655 height <= self.page_content_height
656 }
657
658 pub fn advance_page(&mut self) {
660 self.current_page += 1;
661 self.current_y = 0.0;
662 self.counter.page_number += 1;
663 self.recalculate_content_height();
664 self.avoid_break_before_next = false;
665 }
666
667 pub fn advance_to_left_page(&mut self) {
673 self.advance_page();
674 if self.current_page % 2 != 0 {
675 self.advance_page();
677 }
678 }
679
680 pub fn advance_to_right_page(&mut self) {
686 self.advance_page();
687 if self.current_page % 2 == 0 {
688 self.advance_page();
690 }
691 }
692
693 pub fn enter_avoid_break(&mut self) {
695 self.break_inside_avoid_depth += 1;
696 }
697
698 pub fn exit_avoid_break(&mut self) {
700 self.break_inside_avoid_depth = self.break_inside_avoid_depth.saturating_sub(1);
701 }
702
703 pub fn set_avoid_break_before_next(&mut self) {
705 self.avoid_break_before_next = true;
706 }
707
708 pub fn add_fragment(&mut self, fragment: PageFragment) {
710 self.fragments.push(fragment);
711 }
712
713 pub fn page_count(&self) -> usize {
715 self.current_page + 1
716 }
717
718 pub fn set_total_pages(&mut self, total: usize) {
720 self.counter.total_pages = Some(total);
721 }
722
723 pub fn into_display_lists(self) -> Vec<DisplayList> {
725 let page_count = self.page_count();
726 let mut display_lists: Vec<DisplayList> =
727 (0..page_count).map(|_| DisplayList::default()).collect();
728
729 for fragment in self.fragments {
730 if fragment.page_index < display_lists.len() {
731 display_lists[fragment.page_index]
732 .items
733 .extend(fragment.items);
734 }
735 }
736
737 display_lists
738 }
739
740 pub fn generate_page_chrome(&self, page_index: usize) -> Vec<DisplayListItem> {
742 let mut items = Vec::new();
743 let page_number = page_index + 1;
744
745 let counter = PageCounter {
746 page_number,
747 total_pages: self.counter.total_pages,
748 chapter: self.counter.chapter,
749 named_counters: self.counter.named_counters.clone(),
750 };
751
752 let slots = self.template.slots_for_page(page_number);
753
754 for slot in slots {
755 let _text = match &slot.content {
756 PageSlotContent::Text(s) => s.clone(),
757 PageSlotContent::PageNumber(style) => counter.format_page_number(*style),
758 PageSlotContent::PageOfTotal => counter.format_page_of_total(),
759 PageSlotContent::RunningHeader(s) => s.clone(),
760 PageSlotContent::Dynamic(f) => f.call(&counter),
761 };
762
763 let (_x, _y) = self.slot_position(slot.position, page_number);
765
766 }
770
771 items
772 }
773
774 fn slot_position(&self, position: PageSlotPosition, page_number: usize) -> (f32, f32) {
776 let content_width = self.page_size.width - self.margins.horizontal();
777
778 let x = match position {
779 PageSlotPosition::TopLeft | PageSlotPosition::BottomLeft => self.margins.left,
780 PageSlotPosition::TopCenter | PageSlotPosition::BottomCenter => {
781 self.margins.left + content_width / 2.0
782 }
783 PageSlotPosition::TopRight | PageSlotPosition::BottomRight => {
784 self.page_size.width - self.margins.right
785 }
786 };
787
788 let y = match position {
789 PageSlotPosition::TopLeft
790 | PageSlotPosition::TopCenter
791 | PageSlotPosition::TopRight => self.margins.top + self.template.header_height / 2.0,
792 PageSlotPosition::BottomLeft
793 | PageSlotPosition::BottomCenter
794 | PageSlotPosition::BottomRight => {
795 self.page_size.height - self.margins.bottom - self.template.footer_height / 2.0
796 }
797 };
798
799 (x, y)
800 }
801}
802
803#[derive(Debug, Clone)]
807pub enum BreakDecision {
808 FitOnCurrentPage,
810 MoveToNextPage,
812 SplitAcrossPages {
814 height_on_current: f32,
816 height_remaining: f32,
818 },
819 ForceBreakBefore,
821 ForceBreakAfter,
823}
824
825pub fn decide_break(
827 behavior: &BoxBreakBehavior,
828 ctx: &FragmentationLayoutContext,
829 break_before: PageBreak,
830 break_after: PageBreak,
831) -> BreakDecision {
832 if is_forced_break(&break_before) {
834 if ctx.current_y > 0.0 {
835 return BreakDecision::ForceBreakBefore;
836 }
837 }
838
839 match behavior {
840 BoxBreakBehavior::Monolithic { height } => {
841 decide_monolithic_break(*height, ctx, break_before)
842 }
843 BoxBreakBehavior::KeepTogether {
844 estimated_height,
845 priority,
846 } => decide_keep_together_break(*estimated_height, *priority, ctx, break_before),
847 BoxBreakBehavior::Splittable {
848 min_before_break,
849 min_after_break,
850 } => decide_splittable_break(*min_before_break, *min_after_break, ctx, break_before),
851 }
852}
853
854fn decide_monolithic_break(
855 height: f32,
856 ctx: &FragmentationLayoutContext,
857 _break_before: PageBreak,
858) -> BreakDecision {
859 if ctx.can_fit(height) {
861 BreakDecision::FitOnCurrentPage
862 } else if ctx.current_y > 0.0 && ctx.would_fit_on_empty_page(height) {
863 BreakDecision::MoveToNextPage
865 } else {
866 BreakDecision::FitOnCurrentPage
868 }
869}
870
871fn decide_keep_together_break(
872 height: f32,
873 _priority: KeepTogetherPriority,
874 ctx: &FragmentationLayoutContext,
875 _break_before: PageBreak,
876) -> BreakDecision {
877 if ctx.can_fit(height) {
878 BreakDecision::FitOnCurrentPage
879 } else if ctx.would_fit_on_empty_page(height) {
880 BreakDecision::MoveToNextPage
882 } else {
883 let height_on_current = ctx.available_height;
886 let height_remaining = height - height_on_current;
887 BreakDecision::SplitAcrossPages {
888 height_on_current,
889 height_remaining,
890 }
891 }
892}
893
894fn decide_splittable_break(
895 min_before: f32,
896 _min_after: f32,
897 ctx: &FragmentationLayoutContext,
898 _break_before: PageBreak,
899) -> BreakDecision {
900 let available = ctx.available_height;
902
903 if available < min_before && ctx.current_y > 0.0 {
904 BreakDecision::MoveToNextPage
906 } else {
907 BreakDecision::FitOnCurrentPage
909 }
910}
911
912fn is_forced_break(page_break: &PageBreak) -> bool {
915 matches!(
916 page_break,
917 PageBreak::Always
918 | PageBreak::Page
919 | PageBreak::Left
920 | PageBreak::Right
921 | PageBreak::Recto
922 | PageBreak::Verso
923 | PageBreak::All
924 )
925}
926
927fn is_avoid_break(page_break: &PageBreak) -> bool {
928 matches!(page_break, PageBreak::Avoid | PageBreak::AvoidPage)
929}
930
931fn to_lower_roman(n: usize) -> String {
937 to_upper_roman(n).to_lowercase()
938}
939
940fn to_upper_roman(mut n: usize) -> String {
941 if n == 0 {
942 return String::from("0");
943 }
944
945 let numerals = [
946 (1000, "M"),
947 (900, "CM"),
948 (500, "D"),
949 (400, "CD"),
950 (100, "C"),
951 (90, "XC"),
952 (50, "L"),
953 (40, "XL"),
954 (10, "X"),
955 (9, "IX"),
956 (5, "V"),
957 (4, "IV"),
958 (1, "I"),
959 ];
960
961 let mut result = String::new();
962 for (value, numeral) in numerals.iter() {
963 while n >= *value {
964 result.push_str(numeral);
965 n -= value;
966 }
967 }
968 result
969}
970
971fn to_lower_alpha(n: usize) -> String {
972 to_upper_alpha(n).to_lowercase()
973}
974
975fn to_upper_alpha(mut n: usize) -> String {
976 if n == 0 {
977 return String::from("0");
978 }
979
980 let mut result = String::new();
981 while n > 0 {
982 n -= 1;
983 result.insert(0, (b'A' + (n % 26) as u8) as char);
984 n /= 26;
985 }
986 result
987}