1use std::{collections::BTreeMap, sync::Arc};
37
38use azul_core::geom::{LogicalPosition, LogicalRect, LogicalSize};
39use azul_css::props::{
40 basic::ColorU,
41 layout::fragmentation::{BreakInside, PageBreak},
42};
43
44#[derive(Debug, Clone)]
52pub struct PageGeometer {
53 pub page_size: LogicalSize,
55 pub page_margins: PageMargins,
57 pub header_height: f32,
59 pub footer_height: f32,
61 pub current_y: f32,
63}
64
65#[derive(Debug, Clone, Copy, Default)]
67pub struct PageMargins {
68 pub top: f32,
69 pub right: f32,
70 pub bottom: f32,
71 pub left: f32,
72}
73
74impl PageMargins {
75 pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
76 Self {
77 top,
78 right,
79 bottom,
80 left,
81 }
82 }
83
84 pub fn uniform(margin: f32) -> Self {
85 Self {
86 top: margin,
87 right: margin,
88 bottom: margin,
89 left: margin,
90 }
91 }
92}
93
94impl PageGeometer {
95 pub fn new(page_size: LogicalSize, margins: PageMargins) -> Self {
97 Self {
98 page_size,
99 page_margins: margins,
100 header_height: 0.0,
101 footer_height: 0.0,
102 current_y: 0.0,
103 }
104 }
105
106 pub fn with_header_footer(mut self, header: f32, footer: f32) -> Self {
108 self.header_height = header;
109 self.footer_height = footer;
110 self
111 }
112
113 pub fn content_height(&self) -> f32 {
115 self.page_size.height
116 - self.page_margins.top
117 - self.page_margins.bottom
118 - self.header_height
119 - self.footer_height
120 }
121
122 pub fn content_width(&self) -> f32 {
124 self.page_size.width - self.page_margins.left - self.page_margins.right
125 }
126
127 pub fn page_for_y(&self, y: f32) -> usize {
129 let content_h = self.content_height();
130 if content_h <= 0.0 {
131 return 0;
132 }
133
134 let full_page_slot = content_h + self.dead_zone_height();
136 (y / full_page_slot).floor() as usize
137 }
138
139 pub fn page_content_start_y(&self, page_index: usize) -> f32 {
141 let full_page_slot = self.content_height() + self.dead_zone_height();
142 page_index as f32 * full_page_slot
143 }
144
145 pub fn page_content_end_y(&self, page_index: usize) -> f32 {
147 self.page_content_start_y(page_index) + self.content_height()
148 }
149
150 pub fn dead_zone_height(&self) -> f32 {
152 self.footer_height + self.page_margins.bottom + self.page_margins.top + self.header_height
153 }
154
155 pub fn next_page_start_y(&self, current_y: f32) -> f32 {
157 let current_page = self.page_for_y(current_y);
158 self.page_content_start_y(current_page + 1)
159 }
160
161 pub fn crosses_page_break(&self, start_y: f32, end_y: f32) -> bool {
163 let start_page = self.page_for_y(start_y);
164 let end_page = self.page_for_y(end_y - 0.01); start_page != end_page
166 }
167
168 pub fn remaining_on_page(&self, y: f32) -> f32 {
170 let page = self.page_for_y(y);
171 let page_end = self.page_content_end_y(page);
172 (page_end - y).max(0.0)
173 }
174
175 pub fn can_fit(&self, y: f32, height: f32) -> bool {
177 self.remaining_on_page(y) >= height
178 }
179
180 pub fn page_break_offset(&self, y: f32, height: f32) -> f32 {
183 if self.can_fit(y, height) {
184 return 0.0;
185 }
186
187 let next_start = self.next_page_start_y(y);
189 next_start - y
190 }
191
192 pub fn page_count(&self, total_content_height: f32) -> usize {
194 if total_content_height <= 0.0 {
195 return 1;
196 }
197 self.page_for_y(total_content_height - 0.01) + 1
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum BreakBehavior {
204 Splittable,
206 AvoidBreak,
208 Monolithic,
210}
211
212#[derive(Debug, Clone)]
214pub struct BreakEvaluation {
215 pub force_break_before: bool,
217 pub force_break_after: bool,
219 pub behavior: BreakBehavior,
221 pub orphans: u32,
223 pub widows: u32,
225}
226
227impl Default for BreakEvaluation {
228 fn default() -> Self {
229 Self {
230 force_break_before: false,
231 force_break_after: false,
232 behavior: BreakBehavior::Splittable,
233 orphans: 2,
234 widows: 2,
235 }
236 }
237}
238
239pub fn is_forced_break(page_break: PageBreak) -> bool {
241 matches!(
242 page_break,
243 PageBreak::Always
244 | PageBreak::Page
245 | PageBreak::Left
246 | PageBreak::Right
247 | PageBreak::Recto
248 | PageBreak::Verso
249 | PageBreak::All
250 )
251}
252
253pub fn is_avoid_break(page_break: PageBreak) -> bool {
255 matches!(page_break, PageBreak::Avoid | PageBreak::AvoidPage)
256}
257
258#[derive(Debug, Clone)]
260pub struct RepeatedTableHeader {
261 pub inject_at_y: f32,
263 pub header_items: Vec<usize>, pub header_height: f32,
267}
268
269#[derive(Debug)]
273pub struct PaginationContext<'a> {
274 pub geometer: &'a PageGeometer,
276 pub break_avoid_depth: usize,
278 pub repeated_headers: Vec<RepeatedTableHeader>,
280}
281
282impl<'a> PaginationContext<'a> {
283 pub fn new(geometer: &'a PageGeometer) -> Self {
284 Self {
285 geometer,
286 break_avoid_depth: 0,
287 repeated_headers: Vec::new(),
288 }
289 }
290
291 pub fn enter_avoid_break(&mut self) {
293 self.break_avoid_depth += 1;
294 }
295
296 pub fn exit_avoid_break(&mut self) {
298 self.break_avoid_depth = self.break_avoid_depth.saturating_sub(1);
299 }
300
301 pub fn is_avoiding_breaks(&self) -> bool {
303 self.break_avoid_depth > 0
304 }
305
306 pub fn register_repeated_header(
308 &mut self,
309 inject_at_y: f32,
310 header_items: Vec<usize>,
311 header_height: f32,
312 ) {
313 self.repeated_headers.push(RepeatedTableHeader {
314 inject_at_y,
315 header_items,
316 header_height,
317 });
318 }
319}
320
321pub fn calculate_pagination_offset(
336 geometer: &PageGeometer,
337 main_pen: f32,
338 child_height: f32,
339 break_eval: &BreakEvaluation,
340 is_avoiding_breaks: bool,
341) -> f32 {
342 if break_eval.force_break_before {
344 let remaining = geometer.remaining_on_page(main_pen);
345 if remaining < geometer.content_height() {
346 return geometer.page_break_offset(main_pen, f32::MAX);
348 }
349 }
350
351 let remaining = geometer.remaining_on_page(main_pen);
353
354 if break_eval.behavior == BreakBehavior::Monolithic {
356 if child_height <= remaining {
357 return 0.0;
359 }
360 if child_height <= geometer.content_height() {
361 return geometer.page_break_offset(main_pen, child_height);
363 }
364 return 0.0;
366 }
367
368 if break_eval.behavior == BreakBehavior::AvoidBreak || is_avoiding_breaks {
370 if child_height <= remaining {
371 return 0.0;
373 }
374 if child_height <= geometer.content_height() {
375 return geometer.page_break_offset(main_pen, child_height);
377 }
378 }
380
381 let min_before_break = 20.0; if remaining < min_before_break && remaining < geometer.content_height() {
385 return geometer.page_break_offset(main_pen, child_height);
387 }
388
389 0.0
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
432pub enum MarginBoxPosition {
433 TopLeftCorner,
435 TopLeft,
436 TopCenter,
437 TopRight,
438 TopRightCorner,
439 LeftTop,
441 LeftMiddle,
442 LeftBottom,
443 RightTop,
445 RightMiddle,
446 RightBottom,
447 BottomLeftCorner,
449 BottomLeft,
450 BottomCenter,
451 BottomRight,
452 BottomRightCorner,
453}
454
455impl MarginBoxPosition {
456 pub fn is_top(&self) -> bool {
458 matches!(
459 self,
460 Self::TopLeftCorner
461 | Self::TopLeft
462 | Self::TopCenter
463 | Self::TopRight
464 | Self::TopRightCorner
465 )
466 }
467
468 pub fn is_bottom(&self) -> bool {
470 matches!(
471 self,
472 Self::BottomLeftCorner
473 | Self::BottomLeft
474 | Self::BottomCenter
475 | Self::BottomRight
476 | Self::BottomRightCorner
477 )
478 }
479}
480
481#[derive(Debug, Clone)]
491pub struct RunningElement {
492 pub name: String,
494 pub display_items: Vec<super::display_list::DisplayListItem>,
496 pub size: LogicalSize,
498 pub source_page: usize,
500}
501
502#[derive(Clone)]
507pub enum MarginBoxContent {
508 None,
510 RunningElement(String),
512 NamedString(String),
514 PageCounter,
516 PagesCounter,
518 PageCounterFormatted { format: CounterFormat },
520 Combined(Vec<MarginBoxContent>),
522 Text(String),
524 Custom(Arc<dyn Fn(PageInfo) -> String + Send + Sync>),
526}
527
528impl std::fmt::Debug for MarginBoxContent {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 match self {
531 Self::None => write!(f, "None"),
532 Self::RunningElement(s) => f.debug_tuple("RunningElement").field(s).finish(),
533 Self::NamedString(s) => f.debug_tuple("NamedString").field(s).finish(),
534 Self::PageCounter => write!(f, "PageCounter"),
535 Self::PagesCounter => write!(f, "PagesCounter"),
536 Self::PageCounterFormatted { format } => f
537 .debug_struct("PageCounterFormatted")
538 .field("format", format)
539 .finish(),
540 Self::Combined(v) => f.debug_tuple("Combined").field(v).finish(),
541 Self::Text(s) => f.debug_tuple("Text").field(s).finish(),
542 Self::Custom(_) => write!(f, "Custom(<fn>)"),
543 }
544 }
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq)]
549pub enum CounterFormat {
550 Decimal,
551 DecimalLeadingZero,
552 LowerRoman,
553 UpperRoman,
554 LowerAlpha,
555 UpperAlpha,
556 LowerGreek,
557}
558
559impl Default for CounterFormat {
560 fn default() -> Self {
561 Self::Decimal
562 }
563}
564
565impl CounterFormat {
566 pub fn format(&self, n: usize) -> String {
568 match self {
569 Self::Decimal => n.to_string(),
570 Self::DecimalLeadingZero => format!("{:02}", n),
571 Self::LowerRoman => to_roman(n, false),
572 Self::UpperRoman => to_roman(n, true),
573 Self::LowerAlpha => to_alpha(n, false),
574 Self::UpperAlpha => to_alpha(n, true),
575 Self::LowerGreek => to_greek(n),
576 }
577 }
578}
579
580fn to_roman(mut n: usize, uppercase: bool) -> String {
582 if n == 0 {
583 return "0".to_string();
584 }
585
586 let numerals = [
587 (1000, "m"),
588 (900, "cm"),
589 (500, "d"),
590 (400, "cd"),
591 (100, "c"),
592 (90, "xc"),
593 (50, "l"),
594 (40, "xl"),
595 (10, "x"),
596 (9, "ix"),
597 (5, "v"),
598 (4, "iv"),
599 (1, "i"),
600 ];
601
602 let mut result = String::new();
603 for (value, numeral) in &numerals {
604 while n >= *value {
605 result.push_str(numeral);
606 n -= value;
607 }
608 }
609
610 if uppercase {
611 result.to_uppercase()
612 } else {
613 result
614 }
615}
616
617fn to_alpha(n: usize, uppercase: bool) -> String {
619 if n == 0 {
620 return "0".to_string();
621 }
622
623 let mut result = String::new();
624 let mut remaining = n;
625
626 while remaining > 0 {
627 remaining -= 1;
628 let c = ((remaining % 26) as u8 + if uppercase { b'A' } else { b'a' }) as char;
629 result.insert(0, c);
630 remaining /= 26;
631 }
632
633 result
634}
635
636fn to_greek(n: usize) -> String {
638 const GREEK: &[char] = &[
639 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ',
640 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
641 ];
642 if n == 0 {
643 return "0".to_string();
644 }
645 if n <= GREEK.len() {
646 return GREEK[n - 1].to_string();
647 }
648
649 let mut result = String::new();
651 let mut remaining = n;
652 while remaining > 0 {
653 remaining -= 1;
654 result.insert(0, GREEK[remaining % GREEK.len()]);
655 remaining /= GREEK.len();
656 }
657 result
658}
659
660#[derive(Debug, Clone, Copy)]
662pub struct PageInfo {
663 pub page_number: usize,
665 pub total_pages: usize,
667 pub is_first: bool,
669 pub is_last: bool,
671 pub is_left: bool,
673 pub is_right: bool,
675 pub is_blank: bool,
677}
678
679impl PageInfo {
680 pub fn new(page_number: usize, total_pages: usize) -> Self {
682 Self {
683 page_number,
684 total_pages,
685 is_first: page_number == 1,
686 is_last: total_pages > 0 && page_number == total_pages,
687 is_left: page_number % 2 == 0, is_right: page_number % 2 == 1, is_blank: false,
690 }
691 }
692}
693
694#[derive(Debug, Clone)]
699pub struct HeaderFooterConfig {
700 pub show_header: bool,
702 pub show_footer: bool,
704 pub header_height: f32,
706 pub footer_height: f32,
708 pub header_content: MarginBoxContent,
710 pub footer_content: MarginBoxContent,
712 pub font_size: f32,
714 pub text_color: ColorU,
716 pub skip_first_page: bool,
718}
719
720impl Default for HeaderFooterConfig {
721 fn default() -> Self {
722 Self {
723 show_header: false,
724 show_footer: false,
725 header_height: 30.0,
726 footer_height: 30.0,
727 header_content: MarginBoxContent::None,
728 footer_content: MarginBoxContent::None,
729 font_size: 10.0,
730 text_color: ColorU {
731 r: 0,
732 g: 0,
733 b: 0,
734 a: 255,
735 },
736 skip_first_page: false,
737 }
738 }
739}
740
741impl HeaderFooterConfig {
742 pub fn with_page_numbers() -> Self {
744 Self {
745 show_footer: true,
746 footer_content: MarginBoxContent::Combined(vec![
747 MarginBoxContent::Text("Page ".to_string()),
748 MarginBoxContent::PageCounter,
749 MarginBoxContent::Text(" of ".to_string()),
750 MarginBoxContent::PagesCounter,
751 ]),
752 ..Default::default()
753 }
754 }
755
756 pub fn with_header_and_footer_page_numbers() -> Self {
758 Self {
759 show_header: true,
760 show_footer: true,
761 header_content: MarginBoxContent::Combined(vec![
762 MarginBoxContent::Text("Page ".to_string()),
763 MarginBoxContent::PageCounter,
764 ]),
765 footer_content: MarginBoxContent::Combined(vec![
766 MarginBoxContent::Text("Page ".to_string()),
767 MarginBoxContent::PageCounter,
768 MarginBoxContent::Text(" of ".to_string()),
769 MarginBoxContent::PagesCounter,
770 ]),
771 ..Default::default()
772 }
773 }
774
775 pub fn with_header_text(mut self, text: impl Into<String>) -> Self {
777 self.show_header = true;
778 self.header_content = MarginBoxContent::Text(text.into());
779 self
780 }
781
782 pub fn with_footer_text(mut self, text: impl Into<String>) -> Self {
784 self.show_footer = true;
785 self.footer_content = MarginBoxContent::Text(text.into());
786 self
787 }
788
789 pub fn generate_content(&self, content: &MarginBoxContent, info: PageInfo) -> String {
791 match content {
792 MarginBoxContent::None => String::new(),
793 MarginBoxContent::Text(s) => s.clone(),
794 MarginBoxContent::PageCounter => info.page_number.to_string(),
795 MarginBoxContent::PagesCounter => {
796 if info.total_pages > 0 {
797 info.total_pages.to_string()
798 } else {
799 "?".to_string()
800 }
801 }
802 MarginBoxContent::PageCounterFormatted { format } => format.format(info.page_number),
803 MarginBoxContent::Combined(parts) => parts
804 .iter()
805 .map(|p| self.generate_content(p, info))
806 .collect(),
807 MarginBoxContent::NamedString(name) => {
808 format!("[string:{}]", name)
810 }
811 MarginBoxContent::RunningElement(name) => {
812 format!("[element:{}]", name)
814 }
815 MarginBoxContent::Custom(f) => f(info),
816 }
817 }
818
819 pub fn header_text(&self, info: PageInfo) -> String {
821 if !self.show_header {
822 return String::new();
823 }
824 if self.skip_first_page && info.is_first {
825 return String::new();
826 }
827 self.generate_content(&self.header_content, info)
828 }
829
830 pub fn footer_text(&self, info: PageInfo) -> String {
832 if !self.show_footer {
833 return String::new();
834 }
835 if self.skip_first_page && info.is_first {
836 return String::new();
837 }
838 self.generate_content(&self.footer_content, info)
839 }
840}
841
842#[derive(Debug, Clone, Default)]
847pub struct PageTemplate {
848 pub margin_boxes: BTreeMap<MarginBoxPosition, MarginBoxContent>,
850 pub margins: PageMargins,
852 pub named_strings: BTreeMap<String, String>,
854 pub running_elements: BTreeMap<String, RunningElement>,
856}
857
858impl PageTemplate {
859 pub fn new() -> Self {
861 Self::default()
862 }
863
864 pub fn set_margin_box(&mut self, position: MarginBoxPosition, content: MarginBoxContent) {
866 self.margin_boxes.insert(position, content);
867 }
868
869 pub fn with_centered_page_numbers() -> Self {
871 let mut template = Self::new();
872 template.set_margin_box(
873 MarginBoxPosition::BottomCenter,
874 MarginBoxContent::PageCounter,
875 );
876 template
877 }
878
879 pub fn with_page_x_of_y() -> Self {
881 let mut template = Self::new();
882 template.set_margin_box(
883 MarginBoxPosition::BottomRight,
884 MarginBoxContent::Combined(vec![
885 MarginBoxContent::Text("Page ".to_string()),
886 MarginBoxContent::PageCounter,
887 MarginBoxContent::Text(" of ".to_string()),
888 MarginBoxContent::PagesCounter,
889 ]),
890 );
891 template
892 }
893}
894
895#[derive(Debug, Clone)]
924pub struct FakePageConfig {
925 pub show_header: bool,
927 pub show_footer: bool,
929 pub header_text: Option<String>,
931 pub footer_text: Option<String>,
933 pub header_page_number: bool,
935 pub footer_page_number: bool,
937 pub header_total_pages: bool,
939 pub footer_total_pages: bool,
941 pub number_format: CounterFormat,
943 pub skip_first_page: bool,
945 pub header_height: f32,
947 pub footer_height: f32,
949 pub font_size: f32,
951 pub text_color: ColorU,
953}
954
955impl Default for FakePageConfig {
956 fn default() -> Self {
957 Self {
958 show_header: false,
959 show_footer: false,
960 header_text: None,
961 footer_text: None,
962 header_page_number: false,
963 footer_page_number: false,
964 header_total_pages: false,
965 footer_total_pages: false,
966 number_format: CounterFormat::Decimal,
967 skip_first_page: false,
968 header_height: 30.0,
969 footer_height: 30.0,
970 font_size: 10.0,
971 text_color: ColorU {
972 r: 0,
973 g: 0,
974 b: 0,
975 a: 255,
976 },
977 }
978 }
979}
980
981impl FakePageConfig {
982 pub fn new() -> Self {
984 Self::default()
985 }
986
987 pub fn with_footer_page_numbers(mut self) -> Self {
989 self.show_footer = true;
990 self.footer_page_number = true;
991 self.footer_total_pages = true;
992 self
993 }
994
995 pub fn with_header_page_numbers(mut self) -> Self {
997 self.show_header = true;
998 self.header_page_number = true;
999 self
1000 }
1001
1002 pub fn with_header_and_footer_page_numbers(mut self) -> Self {
1004 self.show_header = true;
1005 self.show_footer = true;
1006 self.header_page_number = true;
1007 self.footer_page_number = true;
1008 self.footer_total_pages = true;
1009 self
1010 }
1011
1012 pub fn with_header_text(mut self, text: impl Into<String>) -> Self {
1014 self.show_header = true;
1015 self.header_text = Some(text.into());
1016 self
1017 }
1018
1019 pub fn with_footer_text(mut self, text: impl Into<String>) -> Self {
1021 self.show_footer = true;
1022 self.footer_text = Some(text.into());
1023 self
1024 }
1025
1026 pub fn with_number_format(mut self, format: CounterFormat) -> Self {
1028 self.number_format = format;
1029 self
1030 }
1031
1032 pub fn skip_first_page(mut self, skip: bool) -> Self {
1034 self.skip_first_page = skip;
1035 self
1036 }
1037
1038 pub fn with_header_height(mut self, height: f32) -> Self {
1040 self.header_height = height;
1041 self
1042 }
1043
1044 pub fn with_footer_height(mut self, height: f32) -> Self {
1046 self.footer_height = height;
1047 self
1048 }
1049
1050 pub fn with_font_size(mut self, size: f32) -> Self {
1052 self.font_size = size;
1053 self
1054 }
1055
1056 pub fn with_text_color(mut self, color: ColorU) -> Self {
1058 self.text_color = color;
1059 self
1060 }
1061
1062 pub fn to_header_footer_config(&self) -> HeaderFooterConfig {
1067 HeaderFooterConfig {
1068 show_header: self.show_header,
1069 show_footer: self.show_footer,
1070 header_height: self.header_height,
1071 footer_height: self.footer_height,
1072 header_content: self.build_header_content(),
1073 footer_content: self.build_footer_content(),
1074 skip_first_page: self.skip_first_page,
1075 font_size: self.font_size,
1076 text_color: self.text_color,
1077 }
1078 }
1079
1080 fn build_header_content(&self) -> MarginBoxContent {
1082 let mut parts = Vec::new();
1083
1084 if let Some(ref text) = self.header_text {
1086 parts.push(MarginBoxContent::Text(text.clone()));
1087 if self.header_page_number {
1088 parts.push(MarginBoxContent::Text(" - ".to_string()));
1089 }
1090 }
1091
1092 if self.header_page_number {
1094 if self.number_format == CounterFormat::Decimal {
1095 parts.push(MarginBoxContent::Text("Page ".to_string()));
1096 parts.push(MarginBoxContent::PageCounter);
1097 } else {
1098 parts.push(MarginBoxContent::Text("Page ".to_string()));
1099 parts.push(MarginBoxContent::PageCounterFormatted {
1100 format: self.number_format,
1101 });
1102 }
1103
1104 if self.header_total_pages {
1105 parts.push(MarginBoxContent::Text(" of ".to_string()));
1106 parts.push(MarginBoxContent::PagesCounter);
1107 }
1108 }
1109
1110 if parts.is_empty() {
1111 MarginBoxContent::None
1112 } else if parts.len() == 1 {
1113 parts.pop().unwrap()
1114 } else {
1115 MarginBoxContent::Combined(parts)
1116 }
1117 }
1118
1119 fn build_footer_content(&self) -> MarginBoxContent {
1121 let mut parts = Vec::new();
1122
1123 if let Some(ref text) = self.footer_text {
1125 parts.push(MarginBoxContent::Text(text.clone()));
1126 if self.footer_page_number {
1127 parts.push(MarginBoxContent::Text(" - ".to_string()));
1128 }
1129 }
1130
1131 if self.footer_page_number {
1133 if self.number_format == CounterFormat::Decimal {
1134 parts.push(MarginBoxContent::Text("Page ".to_string()));
1135 parts.push(MarginBoxContent::PageCounter);
1136 } else {
1137 parts.push(MarginBoxContent::Text("Page ".to_string()));
1138 parts.push(MarginBoxContent::PageCounterFormatted {
1139 format: self.number_format,
1140 });
1141 }
1142
1143 if self.footer_total_pages {
1144 parts.push(MarginBoxContent::Text(" of ".to_string()));
1145 parts.push(MarginBoxContent::PagesCounter);
1146 }
1147 }
1148
1149 if parts.is_empty() {
1150 MarginBoxContent::None
1151 } else if parts.len() == 1 {
1152 parts.pop().unwrap()
1153 } else {
1154 MarginBoxContent::Combined(parts)
1155 }
1156 }
1157}
1158
1159#[derive(Debug, Clone)]
1161pub struct TableHeaderInfo {
1162 pub table_node_index: usize,
1164 pub table_start_y: f32,
1166 pub table_end_y: f32,
1168 pub thead_items: Vec<super::display_list::DisplayListItem>,
1170 pub thead_height: f32,
1172 pub thead_offset_y: f32,
1174}
1175
1176#[derive(Debug, Default, Clone)]
1178pub struct TableHeaderTracker {
1179 pub tables: Vec<TableHeaderInfo>,
1181}
1182
1183impl TableHeaderTracker {
1184 pub fn new() -> Self {
1185 Self::default()
1186 }
1187
1188 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
1190 self.tables.push(info);
1191 }
1192
1193 pub fn get_repeated_headers_for_page(
1198 &self,
1199 page_index: usize,
1200 page_top_y: f32,
1201 page_bottom_y: f32,
1202 ) -> Vec<(f32, &[super::display_list::DisplayListItem], f32)> {
1203 let mut headers = Vec::new();
1204
1205 for table in &self.tables {
1206 let table_starts_before_page = table.table_start_y < page_top_y;
1208 let table_continues_on_page = table.table_end_y > page_top_y;
1209
1210 if table_starts_before_page && table_continues_on_page {
1211 headers.push((
1214 0.0, table.thead_items.as_slice(),
1216 table.thead_height,
1217 ));
1218 }
1219 }
1220
1221 headers
1222 }
1223}