Skip to main content

rust_pdf_box/pd/
page.rs

1use crate::cos::object::{COSArray, COSDictionary, COSName, COSObject, ObjectId};
2use crate::pd::rectangle::PDRectangle;
3use crate::pd::resources::PDResources;
4
5/// Represents a single page in a PDF document.
6#[derive(Debug, Clone)]
7pub struct PDPage {
8    pub dict: COSDictionary,
9    pub page_id: Option<ObjectId>,
10    pub resources: PDResources,
11}
12
13impl PDPage {
14    /// Creates a new page with the given media box.
15    pub fn new(media_box: PDRectangle) -> Self {
16        let mut dict = COSDictionary::new();
17        dict.set("Type", COSName::new("Page"));
18        dict.set("MediaBox", media_box);
19
20        let resources = PDResources::new();
21        dict.set("Resources", resources.dict.clone());
22
23        Self {
24            dict,
25            page_id: None,
26            resources,
27        }
28    }
29
30    /// Loads a page from an existing page dictionary and its object ID.
31    pub fn from_dict(dict: COSDictionary, page_id: ObjectId) -> Self {
32        let resources_dict = dict.get_dict("Resources").cloned().unwrap_or_default();
33        let resources = PDResources::from_dict(resources_dict);
34
35        Self {
36            dict,
37            page_id: Some(page_id),
38            resources,
39        }
40    }
41
42    pub fn media_box(&self) -> PDRectangle {
43        self.dict
44            .get_array("MediaBox")
45            .and_then(PDRectangle::from_cos_array)
46            .unwrap_or(PDRectangle::A4)
47    }
48
49    pub fn set_media_box(&mut self, rect: PDRectangle) {
50        self.dict.set("MediaBox", rect);
51    }
52
53    pub fn crop_box(&self) -> PDRectangle {
54        self.dict
55            .get_array("CropBox")
56            .and_then(PDRectangle::from_cos_array)
57            .unwrap_or_else(|| self.media_box())
58    }
59
60    pub fn set_crop_box(&mut self, rect: PDRectangle) {
61        self.dict.set("CropBox", rect);
62    }
63
64    pub fn rotation(&self) -> i32 {
65        self.dict.get_integer("Rotate").unwrap_or(0) as i32
66    }
67
68    pub fn set_rotation(&mut self, rotation: i32) {
69        // Standard PDF rotation is in multiples of 90 degrees
70        let normalized = ((rotation % 360) + 360) % 360;
71        self.dict.set("Rotate", normalized as i64);
72    }
73
74    /// Synchronizes the resources dictionary back into the page dictionary.
75    pub fn sync_resources(&mut self) {
76        self.dict.set("Resources", self.resources.dict.clone());
77    }
78
79    /// Retrieves all content stream ObjectIds referenced in `/Contents`.
80    pub fn content_stream_ids(&self) -> Vec<ObjectId> {
81        match self.dict.get("Contents") {
82            Some(COSObject::Reference(id)) => vec![*id],
83            Some(COSObject::Array(arr)) => {
84                arr.iter().filter_map(|item| item.as_reference()).collect()
85            }
86            _ => Vec::new(),
87        }
88    }
89
90    /// Sets a single content stream reference for `/Contents`.
91    pub fn set_contents(&mut self, content_ref: ObjectId) {
92        self.dict.set("Contents", content_ref);
93    }
94
95    /// Sets multiple content streams for `/Contents`.
96    pub fn set_contents_array(&mut self, content_refs: Vec<ObjectId>) {
97        let mut arr = COSArray::new();
98        for id in content_refs {
99            arr.push(id);
100        }
101        self.dict.set("Contents", arr);
102    }
103}