1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 vec::Vec,
4};
5
6use crate::{
7 id_tree::{NodeDataContainer, NodeId},
8 styled_dom::NodeHierarchyItem,
9 ui_solver::PositionedRectangle,
10 window::LogicalRect,
11};
12
13#[derive(Debug, Clone)]
15pub struct PaginatedNode {
16 pub id: NodeId,
18 pub rect: LogicalRect,
20 pub children: Vec<PaginatedNode>,
22}
23
24#[derive(Debug)]
26pub struct PaginatedPage {
27 pub root: Option<PaginatedNode>,
29
30 pub nodes: BTreeMap<NodeId, PaginatedNode>,
32}
33
34pub fn paginate_layout_result<'a>(
36 node_hierarchy: &crate::id_tree::NodeDataContainerRef<'a, NodeHierarchyItem>,
37 rects: &crate::id_tree::NodeDataContainerRef<'a, PositionedRectangle>,
38 page_height: f32,
39) -> Vec<PaginatedPage> {
40 let mut pages = Vec::new();
41
42 let max_height = (0..rects.len())
44 .map(|i| {
45 let r = &rects[NodeId::new(i)];
46 r.position.get_static_offset().y + r.size.height
47 })
48 .fold(0.0, f32::max);
49
50 let num_pages = (max_height / page_height).ceil() as usize;
52 if num_pages == 0 {
53 return pages;
54 }
55
56 let mut page_node_sets: Vec<BTreeSet<NodeId>> = Vec::with_capacity(num_pages);
58 for page_idx in 0..num_pages {
59 page_node_sets.push(BTreeSet::new());
60 }
61
62 for node_id in (0..rects.len()).map(NodeId::new) {
64 let r = &rects[node_id];
65 let node_top = r.position.get_static_offset().y;
66 let node_bottom = node_top + r.size.height;
67
68 for page_idx in 0..num_pages {
70 let page_start = page_idx as f32 * page_height;
71 let page_end = page_start + page_height;
72
73 if !(node_bottom <= page_start || node_top >= page_end) {
75 page_node_sets[page_idx].insert(node_id);
76 }
77 }
78 }
79
80 for page_idx in 0..num_pages {
82 let mut complete_set = page_node_sets[page_idx].clone();
83 let mut ancestors_to_add = Vec::new();
84
85 for &node_id in &page_node_sets[page_idx] {
87 let mut current = node_id;
88 while let Some(parent_id) = node_hierarchy[current].parent_id() {
89 if !complete_set.contains(&parent_id) {
90 ancestors_to_add.push(parent_id);
91 complete_set.insert(parent_id);
92 }
93 current = parent_id;
94 }
95 }
96
97 for ancestor in ancestors_to_add {
99 page_node_sets[page_idx].insert(ancestor);
100 }
101 }
102
103 for page_idx in 0..num_pages {
105 let page_start = page_idx as f32 * page_height;
106 let page_end = page_start + page_height;
107
108 if page_node_sets[page_idx].is_empty() {
110 continue;
111 }
112
113 let mut nodes_map = BTreeMap::new();
114 let root_id = NodeId::new(0);
115
116 if page_node_sets[page_idx].contains(&root_id) {
118 let root_node = build_paginated_node(
119 root_id,
120 page_start,
121 page_end,
122 node_hierarchy,
123 rects,
124 &page_node_sets[page_idx],
125 &mut nodes_map,
126 );
127
128 pages.push(PaginatedPage {
129 root: Some(root_node),
130 nodes: nodes_map,
131 });
132 } else {
133 let visible_roots = find_visible_roots(&page_node_sets[page_idx], node_hierarchy);
135
136 for &root_id in &visible_roots {
138 let root_node = build_paginated_node(
139 root_id,
140 page_start,
141 page_end,
142 node_hierarchy,
143 rects,
144 &page_node_sets[page_idx],
145 &mut nodes_map,
146 );
147
148 if nodes_map.len() == 1 {
150 pages.push(PaginatedPage {
151 root: Some(root_node),
152 nodes: nodes_map.clone(),
153 });
154 } else {
155 if let Some(page) = pages.last_mut() {
157 page.nodes.insert(root_id, root_node);
158 }
159 }
160 }
161 }
162 }
163
164 pages
165}
166
167fn find_visible_roots(
169 visible_nodes: &BTreeSet<NodeId>,
170 node_hierarchy: &crate::id_tree::NodeDataContainerRef<NodeHierarchyItem>,
171) -> Vec<NodeId> {
172 let mut roots = Vec::new();
173
174 for &node_id in visible_nodes {
175 let mut has_visible_parent = false;
177 let mut current = node_id;
178
179 while let Some(parent_id) = node_hierarchy[current].parent_id() {
180 if visible_nodes.contains(&parent_id) {
181 has_visible_parent = true;
182 break;
183 }
184 current = parent_id;
185 }
186
187 if !has_visible_parent {
188 roots.push(node_id);
189 }
190 }
191
192 roots
193}
194
195fn build_paginated_node(
197 node_id: NodeId,
198 page_start: f32,
199 page_end: f32,
200 node_hierarchy: &crate::id_tree::NodeDataContainerRef<NodeHierarchyItem>,
201 rects: &crate::id_tree::NodeDataContainerRef<PositionedRectangle>,
202 visible_nodes: &BTreeSet<NodeId>,
203 nodes_map: &mut BTreeMap<NodeId, PaginatedNode>,
204) -> PaginatedNode {
205 if let Some(existing) = nodes_map.get(&node_id) {
207 return existing.clone();
208 }
209
210 let rect = &rects[node_id];
211 let node_top = rect.position.get_static_offset().y;
212 let node_bottom = node_top + rect.size.height;
213
214 let visible_top = node_top.max(page_start);
216 let visible_bottom = node_bottom.min(page_end);
217 let visible_height = visible_bottom - visible_top;
218
219 let mut new_rect = rect.clone();
221 if node_top < page_start || node_bottom > page_end {
222 new_rect.size.height = visible_height;
224 new_rect.position.translate_vertical(page_start - node_top);
225 } else {
226 new_rect.position.translate_vertical(-page_start);
228 }
229
230 let mut paginated_node = PaginatedNode {
232 id: node_id,
233 rect: LogicalRect {
234 origin: new_rect.position.get_static_offset(),
235 size: new_rect.size,
236 },
237 children: Vec::new(),
238 };
239
240 nodes_map.insert(node_id, paginated_node.clone());
242
243 let mut child_id_opt = node_hierarchy[node_id].first_child_id(node_id);
245 while let Some(child_id) = child_id_opt {
246 if visible_nodes.contains(&child_id) {
247 let child_node = build_paginated_node(
248 child_id,
249 page_start,
250 page_end,
251 node_hierarchy,
252 rects,
253 visible_nodes,
254 nodes_map,
255 );
256
257 paginated_node.children.push(child_node.clone());
258 nodes_map.insert(child_id, child_node);
259 }
260
261 child_id_opt = node_hierarchy[child_id].next_sibling_id();
263 }
264
265 nodes_map.insert(node_id, paginated_node.clone());
267
268 paginated_node
269}
270
271#[cfg(test)]
272mod pagination_tests {
273 use alloc::{collections::BTreeSet, vec::Vec};
274
275 use crate::{
276 id_tree::{NodeDataContainer, NodeId},
277 pagination::paginate_layout_result,
278 styled_dom::NodeHierarchyItem,
279 ui_solver::{
280 PositionInfo, PositionInfoInner, PositionedRectangle, ResolvedOffsets,
281 StyleBoxShadowOffsets,
282 },
283 window::LogicalSize,
284 };
285
286 fn create_test_node_hierarchy(count: usize) -> NodeDataContainer<NodeHierarchyItem> {
299 #![allow(unused_mut)]
302 let mut items = vec![NodeHierarchyItem::zeroed(); count];
303 if count == 0 {
304 return NodeDataContainer::new(items);
305 }
306 items[0].parent = 0;
308 items[0].previous_sibling = 0;
309 items[0].next_sibling = 0;
310
311 if count > 1 {
312 items[0].last_child = count;
313 }
314 for i in 1..count {
315 items[i].parent = 1;
316 items[i].last_child = 0;
317 if i == 1 {
318 items[i].previous_sibling = 0;
319 } else {
320 items[i].previous_sibling = i as usize;
321 }
322 if i == count - 1 {
323 items[i].next_sibling = 0;
324 } else {
325 items[i].next_sibling = (i + 2) as usize;
326 }
327 }
328 NodeDataContainer::new(items)
329 }
330
331 fn create_rects(config: &[(f32, f32, f32, f32)]) -> NodeDataContainer<PositionedRectangle> {
332 let mut out = Vec::new();
333 for &(x, y, w, h) in config {
334 let rect = PositionedRectangle {
335 position: PositionInfo::Static(PositionInfoInner {
336 x_offset: x,
337 y_offset: y,
338 static_x_offset: x,
339 static_y_offset: y,
340 }),
341 size: LogicalSize::new(w, h),
342 padding: ResolvedOffsets::zero(),
343 margin: ResolvedOffsets::zero(),
344 border_widths: ResolvedOffsets::zero(),
345 box_shadow: StyleBoxShadowOffsets::default(),
346 box_sizing: azul_css::LayoutBoxSizing::BorderBox,
347 overflow_x: azul_css::LayoutOverflow::Auto,
348 overflow_y: azul_css::LayoutOverflow::Auto,
349 resolved_text_layout_options: None,
350 };
351 out.push(rect);
352 }
353 NodeDataContainer::new(out)
354 }
355
356 fn page_node_ids(page: &crate::pagination::PaginatedPage) -> BTreeSet<NodeId> {
358 page.nodes.keys().copied().collect()
359 }
360
361 #[test]
362 fn test_basic_pagination() {
363 let hier = create_test_node_hierarchy(3);
365 let rects = create_rects(&[
366 (0.0, 0.0, 100.0, 50.0),
367 (0.0, 50.0, 100.0, 50.0),
368 (0.0, 100.0, 100.0, 50.0),
369 ]);
370
371 let pages = paginate_layout_result(&hier.as_ref(), &rects.as_ref(), 75.0);
372 assert_eq!(pages.len(), 2);
373
374 let p0 = page_node_ids(&pages[0]);
375 let want0 = BTreeSet::from([NodeId::new(0), NodeId::new(1)]);
376 assert_eq!(p0, want0, "page0 node set");
377
378 let p1 = page_node_ids(&pages[1]);
380 let want1 = BTreeSet::from([NodeId::new(0), NodeId::new(1), NodeId::new(2)]);
381 assert_eq!(p1, want1, "page1 node set");
382 }
383
384 #[test]
385 fn test_single_page_layout() {
386 let hierarchy = create_test_node_hierarchy(3);
388 let rects = create_rects(&[
389 (0.0, 0.0, 100.0, 30.0),
390 (0.0, 30.0, 100.0, 30.0),
391 (0.0, 60.0, 100.0, 30.0),
392 ]);
393 let pages = paginate_layout_result(&hierarchy.as_ref(), &rects.as_ref(), 100.0);
395 assert_eq!(pages.len(), 1);
397 let p0_ids = page_node_ids(&pages[0]);
398 let expected = BTreeSet::from([NodeId::new(0), NodeId::new(1), NodeId::new(2)]);
399 assert_eq!(p0_ids, expected, "all 3 nodes on the single page");
400 }
401
402 #[test]
403 fn test_node_on_multiple_pages() {
404 let hierarchy = create_test_node_hierarchy(3);
407 let rects = create_rects(&[
408 (0.0, 0.0, 100.0, 250.0), (0.0, 50.0, 100.0, 50.0), (0.0, 150.0, 100.0, 50.0), ]);
412 let pages = paginate_layout_result(&hierarchy.as_ref(), &rects.as_ref(), 100.0);
414 assert_eq!(pages.len(), 3);
415
416 let p0_ids = page_node_ids(&pages[0]);
418 let want0 = BTreeSet::from([NodeId::new(0), NodeId::new(1)]);
420 assert_eq!(p0_ids, want0, "page0 nodes");
421
422 let p1_ids = page_node_ids(&pages[1]);
426 let want1 = BTreeSet::from([NodeId::new(0), NodeId::new(2)]);
427 assert_eq!(p1_ids, want1, "page1 nodes");
428
429 let p2_ids = page_node_ids(&pages[2]);
431 let want2 = BTreeSet::from([NodeId::new(0)]);
432 assert_eq!(p2_ids, want2, "page2 nodes");
433 }
434
435 #[test]
436 fn test_parent_child_relationships() {
437 let mut items = create_test_node_hierarchy(5);
449 items.internal[1].parent = 1;
452 items.internal[2].parent = 2;
454 items.internal[3].parent = 1;
456 items.internal[4].parent = 4;
458
459 let r = create_rects(&[
460 (0.0, 0.0, 100.0, 200.0), (10.0, 10.0, 80.0, 90.0), (20.0, 50.0, 60.0, 40.0), (10.0, 110.0, 80.0, 90.0), (20.0, 150.0, 60.0, 40.0), ]);
467 let pages = paginate_layout_result(&items.as_ref(), &r.as_ref(), 100.0);
469 assert_eq!(pages.len(), 2);
470
471 let p0 = page_node_ids(&pages[0]);
475 let want0 = BTreeSet::from([NodeId::new(0), NodeId::new(1), NodeId::new(2)]);
476 assert_eq!(
477 p0, want0,
478 "page0 nodes (child node3 not included if it’s out of geometry)"
479 );
480
481 let p1 = page_node_ids(&pages[1]);
483 let want1 = BTreeSet::from([NodeId::new(0), NodeId::new(3), NodeId::new(4)]);
484 assert_eq!(p1, want1, "page1 nodes");
485 }
486
487 #[test]
488 fn test_exact_page_boundaries() {
489 let h = create_test_node_hierarchy(4);
492 let r = create_rects(&[
493 (0.0, 0.0, 100.0, 100.0), (0.0, 100.0, 100.0, 100.0), (0.0, 200.0, 100.0, 100.0), (0.0, 0.0, 100.0, 300.0), ]);
498 let pages = paginate_layout_result(&h.as_ref(), &r.as_ref(), 100.0);
499 assert_eq!(pages.len(), 3);
500
501 let p0 = page_node_ids(&pages[0]);
502 let want0 = BTreeSet::from([NodeId::new(0), NodeId::new(3)]);
503 assert_eq!(p0, want0);
504
505 let p1 = page_node_ids(&pages[1]);
507 let want1 = BTreeSet::from([NodeId::new(0), NodeId::new(1), NodeId::new(3)]);
508 assert_eq!(p1, want1);
509
510 let p2 = page_node_ids(&pages[2]);
512 let want2 = BTreeSet::from([NodeId::new(0), NodeId::new(2), NodeId::new(3)]);
513 assert_eq!(p2, want2);
514 }
515
516 #[test]
517 fn test_partially_visible_node() {
518 let h = create_test_node_hierarchy(3);
522 let r = create_rects(&[
523 (0.0, 0.0, 100.0, 50.0),
524 (0.0, 75.0, 100.0, 50.0),
525 (0.0, 150.0, 100.0, 50.0),
526 ]);
527 let pages = paginate_layout_result(&h.as_ref(), &r.as_ref(), 100.0);
528 assert_eq!(pages.len(), 2);
529
530 let p0 = page_node_ids(&pages[0]);
531 let want0 = BTreeSet::from([NodeId::new(0), NodeId::new(1)]);
532 assert_eq!(p0, want0, "page0 node set includes partial node1");
533
534 let p1 = page_node_ids(&pages[1]);
536 let want1 = BTreeSet::from([NodeId::new(0), NodeId::new(1), NodeId::new(2)]);
537 assert_eq!(
538 p1, want1,
539 "page1 node set includes partial node1 plus node2"
540 );
541 }
542
543 #[test]
544 fn test_large_document_pagination() {
545 let n = 20;
547 let hierarchy = create_test_node_hierarchy(n);
548 let mut cfg = Vec::with_capacity(n);
549 for i in 0..n {
550 cfg.push((0.0, i as f32 * 30.0, 100.0, 30.0));
551 }
552 let rects = create_rects(&cfg);
553 let pages = paginate_layout_result(&hierarchy.as_ref(), &rects.as_ref(), 100.0);
554
555 assert_eq!(pages.len(), 6);
557
558 let mut page_nodes = vec![BTreeSet::new(); 6];
565 for (page_index, page) in pages.iter().enumerate() {
566 page_nodes[page_index] = page_node_ids(page);
567 }
568
569 for i in 0..6 {
575 assert!(!page_nodes[i].is_empty(), "page{i} shouldn't be empty here");
576 }
577
578 }
586}