1use std::{collections::BTreeMap, sync::Arc};
41
42use azul_core::geom::LogicalSize;
43use azul_css::props::{
44 basic::ColorU,
45 layout::fragmentation::PageBreak,
46};
47
48#[derive(Debug, Clone)]
56pub struct PageGeometer {
57 pub page_size: LogicalSize,
59 pub page_margins: PageMargins,
61 pub header_height: f32,
63 pub footer_height: f32,
65 pub current_y: f32,
67}
68
69#[derive(Debug, Clone, Copy, Default)]
71pub struct PageMargins {
72 pub top: f32,
73 pub right: f32,
74 pub bottom: f32,
75 pub left: f32,
76}
77
78impl PageMargins {
79 pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
80 Self {
81 top,
82 right,
83 bottom,
84 left,
85 }
86 }
87
88 pub fn uniform(margin: f32) -> Self {
89 Self {
90 top: margin,
91 right: margin,
92 bottom: margin,
93 left: margin,
94 }
95 }
96}
97
98impl PageGeometer {
99 pub fn new(page_size: LogicalSize, margins: PageMargins) -> Self {
101 Self {
102 page_size,
103 page_margins: margins,
104 header_height: 0.0,
105 footer_height: 0.0,
106 current_y: 0.0,
107 }
108 }
109
110 pub fn with_header_footer(mut self, header: f32, footer: f32) -> Self {
112 self.header_height = header;
113 self.footer_height = footer;
114 self
115 }
116
117 pub fn content_height(&self) -> f32 {
119 self.page_size.height
120 - self.page_margins.top
121 - self.page_margins.bottom
122 - self.header_height
123 - self.footer_height
124 }
125
126 pub fn content_width(&self) -> f32 {
128 self.page_size.width - self.page_margins.left - self.page_margins.right
129 }
130
131 pub fn page_for_y(&self, y: f32) -> usize {
135 if y < 0.0 {
136 return 0;
137 }
138 let content_h = self.content_height();
139 if content_h <= 0.0 {
140 return 0;
141 }
142
143 let full_page_slot = content_h + self.dead_zone_height();
145 (y / full_page_slot).floor() as usize
146 }
147
148 pub fn page_content_start_y(&self, page_index: usize) -> f32 {
150 let full_page_slot = self.content_height() + self.dead_zone_height();
151 page_index as f32 * full_page_slot
152 }
153
154 pub fn page_content_end_y(&self, page_index: usize) -> f32 {
156 self.page_content_start_y(page_index) + self.content_height()
157 }
158
159 pub fn dead_zone_height(&self) -> f32 {
161 self.footer_height + self.page_margins.bottom + self.page_margins.top + self.header_height
162 }
163
164 pub fn next_page_start_y(&self, current_y: f32) -> f32 {
166 let current_page = self.page_for_y(current_y);
167 self.page_content_start_y(current_page + 1)
168 }
169
170 pub fn crosses_page_break(&self, start_y: f32, end_y: f32) -> bool {
172 let start_page = self.page_for_y(start_y);
173 let end_page = self.page_for_y(end_y - 0.01); start_page != end_page
175 }
176
177 pub fn remaining_on_page(&self, y: f32) -> f32 {
179 let page = self.page_for_y(y);
180 let page_end = self.page_content_end_y(page);
181 (page_end - y).max(0.0)
182 }
183
184 pub fn can_fit(&self, y: f32, height: f32) -> bool {
186 self.remaining_on_page(y) >= height
187 }
188
189 pub fn page_break_offset(&self, y: f32, height: f32) -> f32 {
192 if self.can_fit(y, height) {
193 return 0.0;
194 }
195
196 let next_start = self.next_page_start_y(y);
198 next_start - y
199 }
200
201 pub fn page_count(&self, total_content_height: f32) -> usize {
203 if total_content_height <= 0.0 {
204 return 1;
205 }
206 self.page_for_y(total_content_height - 0.01) + 1
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub enum BreakBehavior {
213 Splittable,
215 AvoidBreak,
217 Monolithic,
219}
220
221#[derive(Debug, Clone)]
223pub struct BreakEvaluation {
224 pub force_break_before: bool,
226 pub force_break_after: bool,
228 pub behavior: BreakBehavior,
230 pub orphans: u32,
232 pub widows: u32,
234}
235
236impl Default for BreakEvaluation {
237 fn default() -> Self {
238 Self {
239 force_break_before: false,
240 force_break_after: false,
241 behavior: BreakBehavior::Splittable,
242 orphans: 2,
243 widows: 2,
244 }
245 }
246}
247
248pub fn is_forced_break(page_break: PageBreak) -> bool {
250 matches!(
251 page_break,
252 PageBreak::Always
253 | PageBreak::Page
254 | PageBreak::Left
255 | PageBreak::Right
256 | PageBreak::Recto
257 | PageBreak::Verso
258 | PageBreak::All
259 )
260}
261
262pub fn is_avoid_break(page_break: PageBreak) -> bool {
264 matches!(page_break, PageBreak::Avoid | PageBreak::AvoidPage)
265}
266
267#[derive(Debug, Clone)]
269pub struct RepeatedTableHeader {
270 pub inject_at_y: f32,
272 pub header_items: Vec<usize>, pub header_height: f32,
276}
277
278#[derive(Debug)]
282pub struct PaginationContext<'a> {
283 pub geometer: &'a PageGeometer,
285 pub break_avoid_depth: usize,
287 pub repeated_headers: Vec<RepeatedTableHeader>,
289}
290
291impl<'a> PaginationContext<'a> {
292 pub fn new(geometer: &'a PageGeometer) -> Self {
293 Self {
294 geometer,
295 break_avoid_depth: 0,
296 repeated_headers: Vec::new(),
297 }
298 }
299
300 pub fn enter_avoid_break(&mut self) {
302 self.break_avoid_depth += 1;
303 }
304
305 pub fn exit_avoid_break(&mut self) {
307 self.break_avoid_depth = self.break_avoid_depth.saturating_sub(1);
308 }
309
310 pub fn is_avoiding_breaks(&self) -> bool {
312 self.break_avoid_depth > 0
313 }
314
315 pub fn register_repeated_header(
317 &mut self,
318 inject_at_y: f32,
319 header_items: Vec<usize>,
320 header_height: f32,
321 ) {
322 self.repeated_headers.push(RepeatedTableHeader {
323 inject_at_y,
324 header_items,
325 header_height,
326 });
327 }
328}
329
330pub fn calculate_pagination_offset(
345 geometer: &PageGeometer,
346 main_pen: f32,
347 child_height: f32,
348 break_eval: &BreakEvaluation,
349 is_avoiding_breaks: bool,
350) -> f32 {
351 if break_eval.force_break_before {
353 let remaining = geometer.remaining_on_page(main_pen);
354 if remaining < geometer.content_height() {
355 return geometer.page_break_offset(main_pen, f32::MAX);
357 }
358 }
359
360 let remaining = geometer.remaining_on_page(main_pen);
362
363 if break_eval.behavior == BreakBehavior::Monolithic {
366 if child_height <= remaining {
367 return 0.0;
369 }
370 if child_height <= geometer.content_height() {
371 return geometer.page_break_offset(main_pen, child_height);
373 }
374 return 0.0;
376 }
377
378 if break_eval.behavior == BreakBehavior::AvoidBreak || is_avoiding_breaks {
380 if child_height <= remaining {
381 return 0.0;
383 }
384 if child_height <= geometer.content_height() {
385 return geometer.page_break_offset(main_pen, child_height);
387 }
388 }
390
391 let min_before_break = 20.0; if remaining < min_before_break && remaining < geometer.content_height() {
395 return geometer.page_break_offset(main_pen, child_height);
397 }
398
399 0.0
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
442pub enum MarginBoxPosition {
443 TopLeftCorner,
445 TopLeft,
446 TopCenter,
447 TopRight,
448 TopRightCorner,
449 LeftTop,
451 LeftMiddle,
452 LeftBottom,
453 RightTop,
455 RightMiddle,
456 RightBottom,
457 BottomLeftCorner,
459 BottomLeft,
460 BottomCenter,
461 BottomRight,
462 BottomRightCorner,
463}
464
465impl MarginBoxPosition {
466 pub fn is_top(&self) -> bool {
468 matches!(
469 self,
470 Self::TopLeftCorner
471 | Self::TopLeft
472 | Self::TopCenter
473 | Self::TopRight
474 | Self::TopRightCorner
475 )
476 }
477
478 pub fn is_bottom(&self) -> bool {
480 matches!(
481 self,
482 Self::BottomLeftCorner
483 | Self::BottomLeft
484 | Self::BottomCenter
485 | Self::BottomRight
486 | Self::BottomRightCorner
487 )
488 }
489}
490
491#[derive(Debug, Clone)]
501pub struct RunningElement {
502 pub name: String,
504 pub display_items: Vec<super::display_list::DisplayListItem>,
506 pub size: LogicalSize,
508 pub source_page: usize,
510}
511
512#[derive(Clone)]
517pub enum MarginBoxContent {
518 None,
520 RunningElement(String),
522 NamedString(String),
524 PageCounter,
526 PagesCounter,
528 PageCounterFormatted { format: CounterFormat },
530 Combined(Vec<MarginBoxContent>),
532 Text(String),
534 Custom(Arc<dyn Fn(PageInfo) -> String + Send + Sync>),
536}
537
538impl std::fmt::Debug for MarginBoxContent {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 match self {
541 Self::None => write!(f, "None"),
542 Self::RunningElement(s) => f.debug_tuple("RunningElement").field(s).finish(),
543 Self::NamedString(s) => f.debug_tuple("NamedString").field(s).finish(),
544 Self::PageCounter => write!(f, "PageCounter"),
545 Self::PagesCounter => write!(f, "PagesCounter"),
546 Self::PageCounterFormatted { format } => f
547 .debug_struct("PageCounterFormatted")
548 .field("format", format)
549 .finish(),
550 Self::Combined(v) => f.debug_tuple("Combined").field(v).finish(),
551 Self::Text(s) => f.debug_tuple("Text").field(s).finish(),
552 Self::Custom(_) => write!(f, "Custom(<fn>)"),
553 }
554 }
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq)]
559pub enum CounterFormat {
560 Decimal,
561 DecimalLeadingZero,
562 LowerRoman,
563 UpperRoman,
564 LowerAlpha,
565 UpperAlpha,
566 LowerGreek,
567}
568
569impl Default for CounterFormat {
570 fn default() -> Self {
571 Self::Decimal
572 }
573}
574
575impl CounterFormat {
576 pub fn format(&self, n: usize) -> String {
578 match self {
579 Self::Decimal => n.to_string(),
580 Self::DecimalLeadingZero => format!("{:02}", n),
581 Self::LowerRoman => to_roman(n, false),
582 Self::UpperRoman => to_roman(n, true),
583 Self::LowerAlpha => to_alpha(n, false),
584 Self::UpperAlpha => to_alpha(n, true),
585 Self::LowerGreek => to_greek(n),
586 }
587 }
588}
589
590fn to_roman(mut n: usize, uppercase: bool) -> String {
592 if n == 0 {
593 return "0".to_string();
594 }
595
596 let numerals = [
597 (1000, "m"),
598 (900, "cm"),
599 (500, "d"),
600 (400, "cd"),
601 (100, "c"),
602 (90, "xc"),
603 (50, "l"),
604 (40, "xl"),
605 (10, "x"),
606 (9, "ix"),
607 (5, "v"),
608 (4, "iv"),
609 (1, "i"),
610 ];
611
612 let mut result = String::new();
613 for (value, numeral) in &numerals {
614 while n >= *value {
615 result.push_str(numeral);
616 n -= value;
617 }
618 }
619
620 if uppercase {
621 result.to_uppercase()
622 } else {
623 result
624 }
625}
626
627fn to_alpha(n: usize, uppercase: bool) -> String {
629 if n == 0 {
630 return "0".to_string();
631 }
632
633 let mut result = String::new();
634 let mut remaining = n;
635
636 while remaining > 0 {
637 remaining -= 1;
638 let c = ((remaining % 26) as u8 + if uppercase { b'A' } else { b'a' }) as char;
639 result.insert(0, c);
640 remaining /= 26;
641 }
642
643 result
644}
645
646fn to_greek(n: usize) -> String {
648 const GREEK: &[char] = &[
649 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ',
650 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
651 ];
652 if n == 0 {
653 return "0".to_string();
654 }
655 if n <= GREEK.len() {
656 return GREEK[n - 1].to_string();
657 }
658
659 let mut result = String::new();
661 let mut remaining = n;
662 while remaining > 0 {
663 remaining -= 1;
664 result.insert(0, GREEK[remaining % GREEK.len()]);
665 remaining /= GREEK.len();
666 }
667 result
668}
669
670#[derive(Debug, Clone, Copy)]
672pub struct PageInfo {
673 pub page_number: usize,
675 pub total_pages: usize,
677 pub is_first: bool,
679 pub is_last: bool,
681 pub is_left: bool,
683 pub is_right: bool,
685 pub is_blank: bool,
687}
688
689impl PageInfo {
690 pub fn new(page_number: usize, total_pages: usize) -> Self {
692 Self {
693 page_number,
694 total_pages,
695 is_first: page_number == 1,
696 is_last: total_pages > 0 && page_number == total_pages,
697 is_left: page_number % 2 == 0, is_right: page_number % 2 == 1, is_blank: false,
700 }
701 }
702}
703
704const DEFAULT_HEADER_FOOTER_HEIGHT: f32 = 30.0;
706
707const DEFAULT_HEADER_FOOTER_FONT_SIZE: f32 = 10.0;
709
710#[derive(Debug, Clone)]
715pub struct HeaderFooterConfig {
716 pub show_header: bool,
718 pub show_footer: bool,
720 pub header_height: f32,
722 pub footer_height: f32,
724 pub header_content: MarginBoxContent,
726 pub footer_content: MarginBoxContent,
728 pub font_size: f32,
730 pub text_color: ColorU,
732 pub skip_first_page: bool,
734}
735
736impl Default for HeaderFooterConfig {
737 fn default() -> Self {
738 Self {
739 show_header: false,
740 show_footer: false,
741 header_height: DEFAULT_HEADER_FOOTER_HEIGHT,
742 footer_height: DEFAULT_HEADER_FOOTER_HEIGHT,
743 header_content: MarginBoxContent::None,
744 footer_content: MarginBoxContent::None,
745 font_size: DEFAULT_HEADER_FOOTER_FONT_SIZE,
746 text_color: ColorU {
747 r: 0,
748 g: 0,
749 b: 0,
750 a: 255,
751 },
752 skip_first_page: false,
753 }
754 }
755}
756
757impl HeaderFooterConfig {
758 pub fn with_page_numbers() -> Self {
760 Self {
761 show_footer: true,
762 footer_content: MarginBoxContent::Combined(vec![
763 MarginBoxContent::Text("Page ".to_string()),
764 MarginBoxContent::PageCounter,
765 MarginBoxContent::Text(" of ".to_string()),
766 MarginBoxContent::PagesCounter,
767 ]),
768 ..Default::default()
769 }
770 }
771
772 pub fn with_header_and_footer_page_numbers() -> Self {
774 Self {
775 show_header: true,
776 show_footer: true,
777 header_content: MarginBoxContent::Combined(vec![
778 MarginBoxContent::Text("Page ".to_string()),
779 MarginBoxContent::PageCounter,
780 ]),
781 footer_content: MarginBoxContent::Combined(vec![
782 MarginBoxContent::Text("Page ".to_string()),
783 MarginBoxContent::PageCounter,
784 MarginBoxContent::Text(" of ".to_string()),
785 MarginBoxContent::PagesCounter,
786 ]),
787 ..Default::default()
788 }
789 }
790
791 pub fn with_header_text(mut self, text: impl Into<String>) -> Self {
793 self.show_header = true;
794 self.header_content = MarginBoxContent::Text(text.into());
795 self
796 }
797
798 pub fn with_footer_text(mut self, text: impl Into<String>) -> Self {
800 self.show_footer = true;
801 self.footer_content = MarginBoxContent::Text(text.into());
802 self
803 }
804
805 pub fn generate_content(&self, content: &MarginBoxContent, info: PageInfo) -> String {
807 match content {
808 MarginBoxContent::None => String::new(),
809 MarginBoxContent::Text(s) => s.clone(),
810 MarginBoxContent::PageCounter => info.page_number.to_string(),
811 MarginBoxContent::PagesCounter => {
812 if info.total_pages > 0 {
813 info.total_pages.to_string()
814 } else {
815 "?".to_string()
816 }
817 }
818 MarginBoxContent::PageCounterFormatted { format } => format.format(info.page_number),
819 MarginBoxContent::Combined(parts) => parts
820 .iter()
821 .map(|p| self.generate_content(p, info))
822 .collect(),
823 MarginBoxContent::NamedString(name) => {
824 format!("[string:{}]", name)
826 }
827 MarginBoxContent::RunningElement(name) => {
828 format!("[element:{}]", name)
830 }
831 MarginBoxContent::Custom(f) => f(info),
832 }
833 }
834
835 pub fn header_text(&self, info: PageInfo) -> String {
837 if !self.show_header {
838 return String::new();
839 }
840 if self.skip_first_page && info.is_first {
841 return String::new();
842 }
843 self.generate_content(&self.header_content, info)
844 }
845
846 pub fn footer_text(&self, info: PageInfo) -> String {
848 if !self.show_footer {
849 return String::new();
850 }
851 if self.skip_first_page && info.is_first {
852 return String::new();
853 }
854 self.generate_content(&self.footer_content, info)
855 }
856}
857
858#[derive(Debug, Clone, Default)]
863pub struct PageTemplate {
864 pub margin_boxes: BTreeMap<MarginBoxPosition, MarginBoxContent>,
866 pub margins: PageMargins,
868 pub named_strings: BTreeMap<String, String>,
870 pub running_elements: BTreeMap<String, RunningElement>,
872}
873
874impl PageTemplate {
875 pub fn new() -> Self {
877 Self::default()
878 }
879
880 pub fn set_margin_box(&mut self, position: MarginBoxPosition, content: MarginBoxContent) {
882 self.margin_boxes.insert(position, content);
883 }
884
885 pub fn with_centered_page_numbers() -> Self {
887 let mut template = Self::new();
888 template.set_margin_box(
889 MarginBoxPosition::BottomCenter,
890 MarginBoxContent::PageCounter,
891 );
892 template
893 }
894
895 pub fn with_page_x_of_y() -> Self {
897 let mut template = Self::new();
898 template.set_margin_box(
899 MarginBoxPosition::BottomRight,
900 MarginBoxContent::Combined(vec![
901 MarginBoxContent::Text("Page ".to_string()),
902 MarginBoxContent::PageCounter,
903 MarginBoxContent::Text(" of ".to_string()),
904 MarginBoxContent::PagesCounter,
905 ]),
906 );
907 template
908 }
909}
910
911#[derive(Debug, Clone)]
936pub struct FakePageConfig {
937 pub show_header: bool,
939 pub show_footer: bool,
941 pub header_text: Option<String>,
943 pub footer_text: Option<String>,
945 pub header_page_number: bool,
947 pub footer_page_number: bool,
949 pub header_total_pages: bool,
951 pub footer_total_pages: bool,
953 pub number_format: CounterFormat,
955 pub skip_first_page: bool,
957 pub header_height: f32,
959 pub footer_height: f32,
961 pub font_size: f32,
963 pub text_color: ColorU,
965}
966
967impl Default for FakePageConfig {
968 fn default() -> Self {
969 Self {
970 show_header: false,
971 show_footer: false,
972 header_text: None,
973 footer_text: None,
974 header_page_number: false,
975 footer_page_number: false,
976 header_total_pages: false,
977 footer_total_pages: false,
978 number_format: CounterFormat::Decimal,
979 skip_first_page: false,
980 header_height: DEFAULT_HEADER_FOOTER_HEIGHT,
981 footer_height: DEFAULT_HEADER_FOOTER_HEIGHT,
982 font_size: DEFAULT_HEADER_FOOTER_FONT_SIZE,
983 text_color: ColorU {
984 r: 0,
985 g: 0,
986 b: 0,
987 a: 255,
988 },
989 }
990 }
991}
992
993impl FakePageConfig {
994 pub fn new() -> Self {
996 Self::default()
997 }
998
999 pub fn with_footer_page_numbers(mut self) -> Self {
1001 self.show_footer = true;
1002 self.footer_page_number = true;
1003 self.footer_total_pages = true;
1004 self
1005 }
1006
1007 pub fn with_header_page_numbers(mut self) -> Self {
1009 self.show_header = true;
1010 self.header_page_number = true;
1011 self
1012 }
1013
1014 pub fn with_header_and_footer_page_numbers(mut self) -> Self {
1016 self.show_header = true;
1017 self.show_footer = true;
1018 self.header_page_number = true;
1019 self.footer_page_number = true;
1020 self.footer_total_pages = true;
1021 self
1022 }
1023
1024 pub fn with_header_text(mut self, text: impl Into<String>) -> Self {
1026 self.show_header = true;
1027 self.header_text = Some(text.into());
1028 self
1029 }
1030
1031 pub fn with_footer_text(mut self, text: impl Into<String>) -> Self {
1033 self.show_footer = true;
1034 self.footer_text = Some(text.into());
1035 self
1036 }
1037
1038 pub fn with_number_format(mut self, format: CounterFormat) -> Self {
1040 self.number_format = format;
1041 self
1042 }
1043
1044 pub fn skip_first_page(mut self, skip: bool) -> Self {
1046 self.skip_first_page = skip;
1047 self
1048 }
1049
1050 pub fn with_header_height(mut self, height: f32) -> Self {
1052 self.header_height = height;
1053 self
1054 }
1055
1056 pub fn with_footer_height(mut self, height: f32) -> Self {
1058 self.footer_height = height;
1059 self
1060 }
1061
1062 pub fn with_font_size(mut self, size: f32) -> Self {
1064 self.font_size = size;
1065 self
1066 }
1067
1068 pub fn with_text_color(mut self, color: ColorU) -> Self {
1070 self.text_color = color;
1071 self
1072 }
1073
1074 pub fn to_header_footer_config(&self) -> HeaderFooterConfig {
1079 HeaderFooterConfig {
1080 show_header: self.show_header,
1081 show_footer: self.show_footer,
1082 header_height: self.header_height,
1083 footer_height: self.footer_height,
1084 header_content: self.build_header_content(),
1085 footer_content: self.build_footer_content(),
1086 skip_first_page: self.skip_first_page,
1087 font_size: self.font_size,
1088 text_color: self.text_color,
1089 }
1090 }
1091
1092 fn build_header_content(&self) -> MarginBoxContent {
1094 Self::build_margin_content(
1095 &self.header_text,
1096 self.header_page_number,
1097 self.header_total_pages,
1098 self.number_format,
1099 )
1100 }
1101
1102 fn build_footer_content(&self) -> MarginBoxContent {
1104 Self::build_margin_content(
1105 &self.footer_text,
1106 self.footer_page_number,
1107 self.footer_total_pages,
1108 self.number_format,
1109 )
1110 }
1111
1112 fn build_margin_content(
1114 text: &Option<String>,
1115 page_number: bool,
1116 total_pages: bool,
1117 number_format: CounterFormat,
1118 ) -> MarginBoxContent {
1119 let mut parts = Vec::new();
1120
1121 if let Some(ref text) = text {
1122 parts.push(MarginBoxContent::Text(text.clone()));
1123 if page_number {
1124 parts.push(MarginBoxContent::Text(" - ".to_string()));
1125 }
1126 }
1127
1128 if page_number {
1129 if number_format == CounterFormat::Decimal {
1130 parts.push(MarginBoxContent::Text("Page ".to_string()));
1131 parts.push(MarginBoxContent::PageCounter);
1132 } else {
1133 parts.push(MarginBoxContent::Text("Page ".to_string()));
1134 parts.push(MarginBoxContent::PageCounterFormatted {
1135 format: number_format,
1136 });
1137 }
1138
1139 if total_pages {
1140 parts.push(MarginBoxContent::Text(" of ".to_string()));
1141 parts.push(MarginBoxContent::PagesCounter);
1142 }
1143 }
1144
1145 if parts.is_empty() {
1146 MarginBoxContent::None
1147 } else if parts.len() == 1 {
1148 parts.pop().unwrap()
1149 } else {
1150 MarginBoxContent::Combined(parts)
1151 }
1152 }
1153}
1154
1155#[derive(Debug, Clone)]
1157pub struct TableHeaderInfo {
1158 pub table_node_index: usize,
1160 pub table_start_y: f32,
1162 pub table_end_y: f32,
1164 pub thead_items: Vec<super::display_list::DisplayListItem>,
1166 pub thead_height: f32,
1168 pub thead_offset_y: f32,
1170}
1171
1172#[derive(Debug, Default, Clone)]
1174pub struct TableHeaderTracker {
1175 pub tables: Vec<TableHeaderInfo>,
1177}
1178
1179impl TableHeaderTracker {
1180 pub fn new() -> Self {
1181 Self::default()
1182 }
1183
1184 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
1186 self.tables.push(info);
1187 }
1188
1189 pub fn get_repeated_headers_for_page(
1194 &self,
1195 page_index: usize,
1196 page_top_y: f32,
1197 page_bottom_y: f32,
1198 ) -> Vec<(f32, &[super::display_list::DisplayListItem], f32)> {
1199 let mut headers = Vec::new();
1200
1201 for table in &self.tables {
1202 let table_starts_before_page = table.table_start_y < page_top_y;
1204 let table_continues_on_page = table.table_end_y > page_top_y;
1205
1206 if table_starts_before_page && table_continues_on_page {
1207 headers.push((
1210 0.0, table.thead_items.as_slice(),
1212 table.thead_height,
1213 ));
1214 }
1215 }
1216
1217 headers
1218 }
1219}