Skip to main content

oxidize_pdf/
page_tree.rs

1//! Page Tree Implementation according to ISO 32000-1 Section 7.7.3
2//!
3//! The page tree is a hierarchical structure that organizes pages in a PDF document.
4//! It supports inheritance of properties from parent nodes to child nodes, allowing
5//! efficient sharing of common resources and attributes.
6
7use crate::error::Result;
8use crate::geometry::Rectangle;
9use crate::objects::{Dictionary, Object};
10use crate::page::Page;
11
12/// Page tree node types
13#[derive(Debug, Clone)]
14pub enum PageTreeNode {
15    /// Internal node containing other nodes
16    Pages(PagesNode),
17    /// Leaf node containing actual page content
18    Page(PageNode),
19}
20
21/// Internal page tree node (Type = Pages)
22#[derive(Debug, Clone)]
23pub struct PagesNode {
24    /// Child nodes (can be Pages or Page nodes)
25    pub kids: Vec<PageTreeNode>,
26    /// Number of leaf nodes (pages) under this node
27    pub count: usize,
28    /// Inheritable attributes
29    pub attributes: InheritableAttributes,
30    /// Parent node reference (None for root)
31    pub parent: Option<Box<PageTreeNode>>,
32}
33
34/// Leaf page node (Type = Page)
35#[derive(Debug, Clone)]
36pub struct PageNode {
37    /// Page content streams
38    pub contents: Vec<Object>,
39    /// Inheritable attributes (may override parent)
40    pub attributes: InheritableAttributes,
41    /// Parent node reference
42    pub parent: Option<Box<PagesNode>>,
43    /// Annotations on this page
44    pub annotations: Vec<Object>,
45    /// Page-specific metadata
46    pub metadata: Option<Dictionary>,
47}
48
49/// Attributes that can be inherited through the page tree
50#[derive(Debug, Clone, Default)]
51pub struct InheritableAttributes {
52    /// Resources dictionary (fonts, images, etc.)
53    pub resources: Option<Dictionary>,
54    /// Media box - defines the page size
55    pub media_box: Option<Rectangle>,
56    /// Crop box - visible region of the page
57    pub crop_box: Option<Rectangle>,
58    /// Rotation in degrees (0, 90, 180, 270)
59    pub rotate: Option<i32>,
60}
61
62impl InheritableAttributes {
63    /// Create new inheritable attributes
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Merge with parent attributes (parent attributes are used if not overridden)
69    pub fn merge_with_parent(&self, parent: &InheritableAttributes) -> InheritableAttributes {
70        InheritableAttributes {
71            resources: self.resources.clone().or_else(|| parent.resources.clone()),
72            media_box: self.media_box.or(parent.media_box),
73            crop_box: self.crop_box.or(parent.crop_box),
74            rotate: self.rotate.or(parent.rotate),
75        }
76    }
77
78    /// Convert to dictionary for PDF output
79    pub fn to_dict(&self) -> Dictionary {
80        let mut dict = Dictionary::new();
81
82        if let Some(ref resources) = self.resources {
83            dict.set("Resources", Object::Dictionary(resources.clone()));
84        }
85
86        if let Some(media_box) = self.media_box {
87            dict.set(
88                "MediaBox",
89                Object::Array(vec![
90                    Object::Real(media_box.lower_left.x),
91                    Object::Real(media_box.lower_left.y),
92                    Object::Real(media_box.upper_right.x),
93                    Object::Real(media_box.upper_right.y),
94                ]),
95            );
96        }
97
98        if let Some(crop_box) = self.crop_box {
99            dict.set(
100                "CropBox",
101                Object::Array(vec![
102                    Object::Real(crop_box.lower_left.x),
103                    Object::Real(crop_box.lower_left.y),
104                    Object::Real(crop_box.upper_right.x),
105                    Object::Real(crop_box.upper_right.y),
106                ]),
107            );
108        }
109
110        if let Some(rotate) = self.rotate {
111            dict.set("Rotate", Object::Integer(rotate as i64));
112        }
113
114        dict
115    }
116}
117
118/// Page tree structure for organizing pages hierarchically
119pub struct PageTree {
120    /// Root node of the tree
121    root: PagesNode,
122    /// Maximum number of children per internal node
123    max_kids: usize,
124}
125
126impl PageTree {
127    /// Create a new page tree
128    pub fn new() -> Self {
129        Self {
130            root: PagesNode {
131                kids: Vec::new(),
132                count: 0,
133                attributes: InheritableAttributes::new(),
134                parent: None,
135            },
136            max_kids: 10, // Typical value for balanced trees
137        }
138    }
139
140    /// Set the maximum number of children per internal node
141    pub fn set_max_kids(&mut self, max: usize) {
142        self.max_kids = max.max(2); // Minimum of 2 for a valid tree
143    }
144
145    /// Add a page to the tree
146    pub fn add_page(&mut self, page: Page) -> Result<()> {
147        use crate::geometry::Point;
148
149        // Create media box from page dimensions
150        let media_box = Rectangle::new(
151            Point::new(0.0, 0.0),
152            Point::new(page.width(), page.height()),
153        );
154
155        let page_node = PageNode {
156            contents: vec![],
157            attributes: InheritableAttributes {
158                media_box: Some(media_box),
159                crop_box: None,  // Not set by default
160                rotate: Some(0), // Default rotation
161                resources: None, // Will be set from page resources
162            },
163            parent: None,
164            annotations: Vec::new(),
165            metadata: None,
166        };
167
168        self.add_page_node(page_node);
169        self.root.count += 1;
170        Ok(())
171    }
172
173    /// Add a page node to the tree
174    fn add_page_node(&mut self, page: PageNode) {
175        // For simplicity, add directly to root
176        // In a full implementation, this would balance the tree
177        self.root.kids.push(PageTreeNode::Page(page));
178    }
179
180    /// Get the total number of pages
181    pub fn page_count(&self) -> usize {
182        self.root.count
183    }
184
185    /// Get a page by index (0-based)
186    pub fn get_page(&self, index: usize) -> Option<PageNode> {
187        self.get_page_from_node_clone(&self.root, index)
188    }
189
190    /// Recursively find a page by index (returns a clone)
191    #[allow(clippy::only_used_in_recursion)]
192    fn get_page_from_node_clone(&self, pages: &PagesNode, index: usize) -> Option<PageNode> {
193        let mut current_index = index;
194        for kid in &pages.kids {
195            match kid {
196                PageTreeNode::Page(page) => {
197                    if current_index == 0 {
198                        return Some(page.clone());
199                    }
200                    current_index -= 1;
201                }
202                PageTreeNode::Pages(pages) => {
203                    let kid_count = pages.count;
204                    if current_index < kid_count {
205                        return self.get_page_from_node_clone(pages, current_index);
206                    }
207                    current_index -= kid_count;
208                }
209            }
210        }
211        None
212    }
213
214    /// Get the number of pages under a node
215    #[allow(dead_code)]
216    fn get_node_count(&self, node: &PageTreeNode) -> usize {
217        match node {
218            PageTreeNode::Page(_) => 1,
219            PageTreeNode::Pages(pages) => pages.count,
220        }
221    }
222
223    /// Convert the page tree to a PDF dictionary structure
224    pub fn to_dict(&self) -> Dictionary {
225        self.node_to_dict(&PageTreeNode::Pages(self.root.clone()))
226    }
227
228    /// Convert a node to a dictionary
229    #[allow(clippy::only_used_in_recursion)]
230    fn node_to_dict(&self, node: &PageTreeNode) -> Dictionary {
231        let mut dict = Dictionary::new();
232
233        match node {
234            PageTreeNode::Page(page) => {
235                dict.set("Type", Object::Name("Page".to_string()));
236
237                // Add inheritable attributes
238                let attrs = page.attributes.to_dict();
239                for (key, value) in attrs.iter() {
240                    dict.set(key, value.clone());
241                }
242
243                // Add contents
244                if !page.contents.is_empty() {
245                    if page.contents.len() == 1 {
246                        dict.set("Contents", page.contents[0].clone());
247                    } else {
248                        dict.set("Contents", Object::Array(page.contents.clone()));
249                    }
250                }
251
252                // Add annotations
253                if !page.annotations.is_empty() {
254                    dict.set("Annots", Object::Array(page.annotations.clone()));
255                }
256            }
257            PageTreeNode::Pages(pages) => {
258                dict.set("Type", Object::Name("Pages".to_string()));
259                dict.set("Count", Object::Integer(pages.count as i64));
260
261                // Add inheritable attributes
262                let attrs = pages.attributes.to_dict();
263                for (key, value) in attrs.iter() {
264                    dict.set(key, value.clone());
265                }
266
267                // Add kids
268                let kids: Vec<Object> = pages
269                    .kids
270                    .iter()
271                    .map(|kid| Object::Dictionary(self.node_to_dict(kid)))
272                    .collect();
273                dict.set("Kids", Object::Array(kids));
274            }
275        }
276
277        dict
278    }
279
280    /// Balance the tree to optimize performance
281    pub fn balance(&mut self) {
282        // This would implement tree balancing logic
283        // For now, we keep a simple flat structure
284    }
285
286    /// Set inheritable attributes at the root level
287    pub fn set_default_media_box(&mut self, rect: Rectangle) {
288        self.root.attributes.media_box = Some(rect);
289    }
290
291    /// Set default resources for all pages
292    pub fn set_default_resources(&mut self, resources: Dictionary) {
293        self.root.attributes.resources = Some(resources);
294    }
295
296    /// Find all pages matching a predicate
297    pub fn find_pages<F>(&self, predicate: F) -> Vec<usize>
298    where
299        F: Fn(&PageNode) -> bool,
300    {
301        let mut results = Vec::new();
302        self.find_pages_in_node(&self.root, &predicate, 0, &mut results);
303        results
304    }
305
306    #[allow(clippy::only_used_in_recursion)]
307    fn find_pages_in_node<F>(
308        &self,
309        pages: &PagesNode,
310        predicate: &F,
311        base_index: usize,
312        results: &mut Vec<usize>,
313    ) -> usize
314    where
315        F: Fn(&PageNode) -> bool,
316    {
317        let mut current_index = base_index;
318        let mut count = 0;
319
320        for kid in &pages.kids {
321            match kid {
322                PageTreeNode::Page(page) => {
323                    if predicate(page) {
324                        results.push(current_index);
325                    }
326                    current_index += 1;
327                    count += 1;
328                }
329                PageTreeNode::Pages(inner_pages) => {
330                    let inner_count =
331                        self.find_pages_in_node(inner_pages, predicate, current_index, results);
332                    current_index += inner_count;
333                    count += inner_count;
334                }
335            }
336        }
337
338        count
339    }
340}
341
342impl Default for PageTree {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348/// Builder for constructing page trees with inherited attributes
349pub struct PageTreeBuilder {
350    tree: PageTree,
351    default_attributes: InheritableAttributes,
352}
353
354impl Default for PageTreeBuilder {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360impl PageTreeBuilder {
361    /// Create a new page tree builder
362    pub fn new() -> Self {
363        Self {
364            tree: PageTree::new(),
365            default_attributes: InheritableAttributes::new(),
366        }
367    }
368
369    /// Set default media box for all pages
370    pub fn with_media_box(mut self, rect: Rectangle) -> Self {
371        self.default_attributes.media_box = Some(rect);
372        self.tree.set_default_media_box(rect);
373        self
374    }
375
376    /// Set default resources
377    pub fn with_resources(mut self, resources: Dictionary) -> Self {
378        self.default_attributes.resources = Some(resources.clone());
379        self.tree.set_default_resources(resources);
380        self
381    }
382
383    /// Set default rotation
384    pub fn with_rotation(mut self, degrees: i32) -> Self {
385        self.default_attributes.rotate = Some(degrees);
386        self
387    }
388
389    /// Add a page to the tree
390    pub fn add_page(mut self, page: Page) -> Self {
391        self.tree.add_page(page).unwrap_or_else(|e| {
392            tracing::debug!("Warning: Failed to add page: {}", e);
393        });
394        self
395    }
396
397    /// Build the final page tree
398    pub fn build(mut self) -> PageTree {
399        self.tree.balance();
400        self.tree
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407    use crate::geometry::Point;
408
409    #[test]
410    fn test_inheritable_attributes_merge() {
411        let parent = InheritableAttributes {
412            resources: Some(Dictionary::new()),
413            media_box: Some(Rectangle::new(
414                Point::new(0.0, 0.0),
415                Point::new(612.0, 792.0),
416            )),
417            crop_box: None,
418            rotate: Some(0),
419        };
420
421        let child = InheritableAttributes {
422            resources: None,
423            media_box: None,
424            crop_box: Some(Rectangle::new(
425                Point::new(10.0, 10.0),
426                Point::new(602.0, 782.0),
427            )),
428            rotate: Some(90),
429        };
430
431        let merged = child.merge_with_parent(&parent);
432        assert!(merged.resources.is_some());
433        assert!(merged.media_box.is_some());
434        assert!(merged.crop_box.is_some());
435        assert_eq!(merged.rotate, Some(90));
436    }
437
438    #[test]
439    fn test_page_tree_creation() {
440        let tree = PageTree::new();
441        assert_eq!(tree.page_count(), 0);
442    }
443
444    #[test]
445    fn test_page_tree_add_page() {
446        let mut tree = PageTree::new();
447        let page = Page::new(612.0, 792.0);
448        tree.add_page(page).unwrap();
449        assert_eq!(tree.page_count(), 1);
450    }
451
452    #[test]
453    fn test_page_tree_builder() {
454        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(612.0, 792.0));
455        let tree = PageTreeBuilder::new()
456            .with_media_box(rect)
457            .with_rotation(0)
458            .add_page(Page::new(612.0, 792.0))
459            .add_page(Page::new(612.0, 792.0))
460            .build();
461
462        assert_eq!(tree.page_count(), 2);
463    }
464
465    #[test]
466    fn test_page_tree_to_dict() {
467        let mut tree = PageTree::new();
468        tree.add_page(Page::new(612.0, 792.0)).unwrap();
469
470        let dict = tree.to_dict();
471        assert_eq!(dict.get("Type"), Some(&Object::Name("Pages".to_string())));
472        assert_eq!(dict.get("Count"), Some(&Object::Integer(1)));
473        assert!(dict.get("Kids").is_some());
474    }
475
476    #[test]
477    fn test_find_pages() {
478        let mut tree = PageTree::new();
479
480        // Add pages with different sizes
481        tree.add_page(Page::new(612.0, 792.0)).unwrap(); // Letter
482        tree.add_page(Page::new(595.0, 842.0)).unwrap(); // A4
483        tree.add_page(Page::new(612.0, 792.0)).unwrap(); // Letter
484
485        // Find all letter-sized pages
486        let letter_pages = tree.find_pages(|page| {
487            page.attributes
488                .media_box
489                .map(|mb| mb.upper_right.x == 612.0 && mb.upper_right.y == 792.0)
490                .unwrap_or(false)
491        });
492
493        assert_eq!(letter_pages.len(), 2);
494        assert_eq!(letter_pages[0], 0);
495        assert_eq!(letter_pages[1], 2);
496    }
497
498    #[test]
499    fn test_attributes_to_dict() {
500        let attrs = InheritableAttributes {
501            resources: Some(Dictionary::new()),
502            media_box: Some(Rectangle::new(
503                Point::new(0.0, 0.0),
504                Point::new(612.0, 792.0),
505            )),
506            crop_box: None,
507            rotate: Some(90),
508        };
509
510        let dict = attrs.to_dict();
511        assert!(dict.get("Resources").is_some());
512        assert!(dict.get("MediaBox").is_some());
513        assert_eq!(dict.get("Rotate"), Some(&Object::Integer(90)));
514    }
515
516    #[test]
517    fn test_max_kids_setting() {
518        let mut tree = PageTree::new();
519        tree.set_max_kids(5);
520        assert_eq!(tree.max_kids, 5);
521
522        tree.set_max_kids(1); // Should be clamped to minimum of 2
523        assert_eq!(tree.max_kids, 2);
524    }
525
526    #[test]
527    fn test_page_tree_get_page() {
528        let mut tree = PageTree::new();
529
530        // Add multiple pages
531        tree.add_page(Page::new(612.0, 792.0)).unwrap();
532        tree.add_page(Page::new(595.0, 842.0)).unwrap();
533        tree.add_page(Page::new(420.0, 595.0)).unwrap();
534
535        // Get pages by index
536        let page0 = tree.get_page(0);
537        assert!(page0.is_some());
538        let page0 = page0.unwrap();
539        assert_eq!(page0.attributes.media_box.unwrap().upper_right.x, 612.0);
540
541        let page1 = tree.get_page(1);
542        assert!(page1.is_some());
543        let page1 = page1.unwrap();
544        assert_eq!(page1.attributes.media_box.unwrap().upper_right.x, 595.0);
545
546        let page2 = tree.get_page(2);
547        assert!(page2.is_some());
548        let page2 = page2.unwrap();
549        assert_eq!(page2.attributes.media_box.unwrap().upper_right.x, 420.0);
550
551        // Out of bounds
552        assert!(tree.get_page(3).is_none());
553        assert!(tree.get_page(100).is_none());
554    }
555
556    #[test]
557    fn test_page_tree_empty() {
558        let tree = PageTree::new();
559        assert_eq!(tree.page_count(), 0);
560        assert!(tree.get_page(0).is_none());
561
562        let dict = tree.to_dict();
563        assert_eq!(dict.get("Type"), Some(&Object::Name("Pages".to_string())));
564        assert_eq!(dict.get("Count"), Some(&Object::Integer(0)));
565    }
566
567    #[test]
568    fn test_page_tree_large_number_of_pages() {
569        let mut tree = PageTree::new();
570
571        // Add 100 pages
572        for _ in 0..100 {
573            tree.add_page(Page::new(612.0, 792.0)).unwrap();
574        }
575
576        assert_eq!(tree.page_count(), 100);
577
578        // Check first, middle, and last pages
579        assert!(tree.get_page(0).is_some());
580        assert!(tree.get_page(49).is_some());
581        assert!(tree.get_page(99).is_some());
582        assert!(tree.get_page(100).is_none());
583    }
584
585    #[test]
586    fn test_page_node_creation() {
587        let page_node = PageNode {
588            contents: vec![Object::Integer(1), Object::Integer(2)],
589            attributes: InheritableAttributes::new(),
590            parent: None,
591            annotations: vec![Object::Name("Annot".to_string())],
592            metadata: Some(Dictionary::new()),
593        };
594
595        assert_eq!(page_node.contents.len(), 2);
596        assert_eq!(page_node.annotations.len(), 1);
597        assert!(page_node.metadata.is_some());
598    }
599
600    #[test]
601    fn test_pages_node_creation() {
602        let pages_node = PagesNode {
603            kids: vec![],
604            count: 0,
605            attributes: InheritableAttributes::new(),
606            parent: None,
607        };
608
609        assert_eq!(pages_node.kids.len(), 0);
610        assert_eq!(pages_node.count, 0);
611    }
612
613    #[test]
614    fn test_inheritable_attributes_default() {
615        let attrs = InheritableAttributes::default();
616        assert!(attrs.resources.is_none());
617        assert!(attrs.media_box.is_none());
618        assert!(attrs.crop_box.is_none());
619        assert!(attrs.rotate.is_none());
620    }
621
622    #[test]
623    fn test_inheritable_attributes_complete() {
624        let attrs = InheritableAttributes {
625            resources: Some(Dictionary::new()),
626            media_box: Some(Rectangle::new(
627                Point::new(0.0, 0.0),
628                Point::new(612.0, 792.0),
629            )),
630            crop_box: Some(Rectangle::new(
631                Point::new(36.0, 36.0),
632                Point::new(576.0, 756.0),
633            )),
634            rotate: Some(90),
635        };
636
637        let dict = attrs.to_dict();
638        assert!(dict.get("Resources").is_some());
639        assert!(dict.get("MediaBox").is_some());
640        assert!(dict.get("CropBox").is_some());
641        assert_eq!(dict.get("Rotate"), Some(&Object::Integer(90)));
642    }
643
644    #[test]
645    fn test_merge_with_parent_override() {
646        let parent = InheritableAttributes {
647            resources: Some(Dictionary::new()),
648            media_box: Some(Rectangle::new(
649                Point::new(0.0, 0.0),
650                Point::new(612.0, 792.0),
651            )),
652            crop_box: Some(Rectangle::new(
653                Point::new(0.0, 0.0),
654                Point::new(612.0, 792.0),
655            )),
656            rotate: Some(0),
657        };
658
659        let mut child_resources = Dictionary::new();
660        child_resources.set("Font", Object::Name("F1".to_string()));
661
662        let child = InheritableAttributes {
663            resources: Some(child_resources.clone()),
664            media_box: Some(Rectangle::new(
665                Point::new(0.0, 0.0),
666                Point::new(595.0, 842.0),
667            )),
668            crop_box: None,
669            rotate: Some(180),
670        };
671
672        let merged = child.merge_with_parent(&parent);
673
674        // Child values should override parent
675        assert_eq!(
676            merged.resources.unwrap().get("Font"),
677            Some(&Object::Name("F1".to_string()))
678        );
679        assert_eq!(merged.media_box.unwrap().upper_right.x, 595.0);
680        assert_eq!(merged.crop_box.unwrap().upper_right.x, 612.0); // From parent
681        assert_eq!(merged.rotate, Some(180));
682    }
683
684    #[test]
685    fn test_merge_with_parent_inherit_all() {
686        let parent = InheritableAttributes {
687            resources: Some(Dictionary::new()),
688            media_box: Some(Rectangle::new(
689                Point::new(0.0, 0.0),
690                Point::new(612.0, 792.0),
691            )),
692            crop_box: Some(Rectangle::new(
693                Point::new(36.0, 36.0),
694                Point::new(576.0, 756.0),
695            )),
696            rotate: Some(90),
697        };
698
699        let child = InheritableAttributes::new();
700        let merged = child.merge_with_parent(&parent);
701
702        // All values should be inherited from parent
703        assert!(merged.resources.is_some());
704        assert_eq!(merged.media_box.unwrap().upper_right.x, 612.0);
705        assert_eq!(merged.crop_box.unwrap().upper_right.x, 576.0);
706        assert_eq!(merged.rotate, Some(90));
707    }
708
709    #[test]
710    fn test_page_tree_builder_comprehensive() {
711        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(595.0, 842.0));
712        let mut resources = Dictionary::new();
713        resources.set("Font", Object::Name("F1".to_string()));
714
715        let tree = PageTreeBuilder::new()
716            .with_media_box(rect)
717            .with_resources(resources.clone())
718            .with_rotation(90)
719            .add_page(Page::new(595.0, 842.0))
720            .add_page(Page::new(595.0, 842.0))
721            .add_page(Page::new(595.0, 842.0))
722            .build();
723
724        assert_eq!(tree.page_count(), 3);
725        assert_eq!(tree.root.attributes.media_box.unwrap().upper_right.x, 595.0);
726        // Note: rotate is not set at root level in current implementation
727        assert!(tree.root.attributes.resources.is_some());
728    }
729
730    #[test]
731    fn test_find_pages_complex_predicate() {
732        let mut tree = PageTree::new();
733
734        // Add pages with different sizes
735        tree.add_page(Page::new(612.0, 792.0)).unwrap(); // Letter
736        tree.add_page(Page::new(595.0, 842.0)).unwrap(); // A4
737        tree.add_page(Page::new(420.0, 595.0)).unwrap(); // A5
738        tree.add_page(Page::new(612.0, 792.0)).unwrap(); // Letter
739        tree.add_page(Page::new(595.0, 842.0)).unwrap(); // A4
740
741        // Find all A4 pages
742        let a4_pages = tree.find_pages(|page| {
743            page.attributes
744                .media_box
745                .map(|mb| mb.upper_right.x == 595.0 && mb.upper_right.y == 842.0)
746                .unwrap_or(false)
747        });
748
749        assert_eq!(a4_pages.len(), 2);
750        assert_eq!(a4_pages[0], 1);
751        assert_eq!(a4_pages[1], 4);
752
753        // Find all pages smaller than A4
754        let small_pages = tree.find_pages(|page| {
755            page.attributes
756                .media_box
757                .map(|mb| mb.upper_right.x < 595.0 || mb.upper_right.y < 842.0)
758                .unwrap_or(false)
759        });
760
761        assert_eq!(small_pages.len(), 3); // 2 Letter + 1 A5
762    }
763
764    #[test]
765    fn test_page_tree_node_to_dict_page() {
766        let tree = PageTree::new();
767        let page_node = PageNode {
768            contents: vec![Object::Integer(1)],
769            attributes: InheritableAttributes {
770                media_box: Some(Rectangle::new(
771                    Point::new(0.0, 0.0),
772                    Point::new(612.0, 792.0),
773                )),
774                crop_box: None,
775                rotate: Some(0),
776                resources: None,
777            },
778            parent: None,
779            annotations: vec![Object::Name("Annot1".to_string())],
780            metadata: None,
781        };
782
783        let dict = tree.node_to_dict(&PageTreeNode::Page(page_node));
784        assert_eq!(dict.get("Type"), Some(&Object::Name("Page".to_string())));
785        assert_eq!(dict.get("Contents"), Some(&Object::Integer(1)));
786        assert!(dict.get("Annots").is_some());
787        assert!(dict.get("MediaBox").is_some());
788    }
789
790    #[test]
791    fn test_page_tree_node_to_dict_pages() {
792        let tree = PageTree::new();
793        let pages_node = PagesNode {
794            kids: vec![],
795            count: 5,
796            attributes: InheritableAttributes::new(),
797            parent: None,
798        };
799
800        let dict = tree.node_to_dict(&PageTreeNode::Pages(pages_node));
801        assert_eq!(dict.get("Type"), Some(&Object::Name("Pages".to_string())));
802        assert_eq!(dict.get("Count"), Some(&Object::Integer(5)));
803        assert!(dict.get("Kids").is_some());
804    }
805
806    #[test]
807    fn test_rotation_values() {
808        let attrs = InheritableAttributes {
809            resources: None,
810            media_box: None,
811            crop_box: None,
812            rotate: Some(270), // Valid rotation
813        };
814
815        let dict = attrs.to_dict();
816        assert_eq!(dict.get("Rotate"), Some(&Object::Integer(270)));
817
818        // Test all valid rotation values
819        for rotation in &[0, 90, 180, 270] {
820            let attrs = InheritableAttributes {
821                resources: None,
822                media_box: None,
823                crop_box: None,
824                rotate: Some(*rotation),
825            };
826            let dict = attrs.to_dict();
827            assert_eq!(dict.get("Rotate"), Some(&Object::Integer(*rotation as i64)));
828        }
829    }
830
831    #[test]
832    fn test_balance_method() {
833        let mut tree = PageTree::new();
834
835        // Add many pages
836        for _ in 0..50 {
837            tree.add_page(Page::new(612.0, 792.0)).unwrap();
838        }
839
840        let count_before = tree.page_count();
841        tree.balance();
842        let count_after = tree.page_count();
843
844        // Balance should not change page count
845        assert_eq!(count_before, count_after);
846        assert_eq!(count_after, 50);
847    }
848
849    #[test]
850    fn test_set_default_methods() {
851        let mut tree = PageTree::new();
852
853        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(420.0, 595.0));
854        tree.set_default_media_box(rect);
855        assert_eq!(tree.root.attributes.media_box.unwrap().upper_right.x, 420.0);
856
857        let mut resources = Dictionary::new();
858        resources.set(
859            "ProcSet",
860            Object::Array(vec![Object::Name("PDF".to_string())]),
861        );
862        tree.set_default_resources(resources.clone());
863        assert!(tree.root.attributes.resources.is_some());
864        assert_eq!(
865            tree.root
866                .attributes
867                .resources
868                .as_ref()
869                .unwrap()
870                .get("ProcSet"),
871            Some(&Object::Array(vec![Object::Name("PDF".to_string())]))
872        );
873    }
874}