1use folio_core::{Matrix2D, Rect};
4use folio_cos::{ObjectId, PdfObject};
5
6#[derive(Debug, Clone)]
8pub struct Page {
9 id: ObjectId,
11 dict: PdfObject,
13 page_num: u32,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Rotation {
20 None = 0,
21 Rotate90 = 90,
22 Rotate180 = 180,
23 Rotate270 = 270,
24}
25
26impl Rotation {
27 pub fn from_degrees(degrees: i64) -> Self {
28 match ((degrees % 360) + 360) % 360 {
29 90 => Rotation::Rotate90,
30 180 => Rotation::Rotate180,
31 270 => Rotation::Rotate270,
32 _ => Rotation::None,
33 }
34 }
35
36 pub fn degrees(&self) -> i32 {
37 *self as i32
38 }
39}
40
41impl Page {
42 pub(crate) fn new(id: ObjectId, dict: PdfObject, page_num: u32) -> Self {
43 Self { id, dict, page_num }
44 }
45
46 pub fn id(&self) -> ObjectId {
48 self.id
49 }
50
51 pub fn page_num(&self) -> u32 {
53 self.page_num
54 }
55
56 pub fn dict(&self) -> &PdfObject {
58 &self.dict
59 }
60
61 fn get_rect(&self, key: &[u8]) -> Option<Rect> {
63 let arr = self.dict.dict_get(key)?.as_array()?;
64 if arr.len() >= 4 {
65 Some(Rect::new(
66 arr[0].as_f64()?,
67 arr[1].as_f64()?,
68 arr[2].as_f64()?,
69 arr[3].as_f64()?,
70 ))
71 } else {
72 None
73 }
74 }
75
76 pub fn media_box(&self) -> Rect {
78 self.get_rect(b"MediaBox")
79 .unwrap_or_else(|| Rect::new(0.0, 0.0, 612.0, 792.0)) }
81
82 pub fn crop_box(&self) -> Rect {
84 self.get_rect(b"CropBox")
85 .unwrap_or_else(|| self.media_box())
86 }
87
88 pub fn bleed_box(&self) -> Rect {
90 self.get_rect(b"BleedBox")
91 .unwrap_or_else(|| self.crop_box())
92 }
93
94 pub fn trim_box(&self) -> Rect {
96 self.get_rect(b"TrimBox").unwrap_or_else(|| self.crop_box())
97 }
98
99 pub fn art_box(&self) -> Rect {
101 self.get_rect(b"ArtBox").unwrap_or_else(|| self.crop_box())
102 }
103
104 pub fn rotation(&self) -> Rotation {
106 let degrees = self.dict.dict_get_i64(b"Rotate").unwrap_or(0);
107 Rotation::from_degrees(degrees)
108 }
109
110 pub fn width(&self) -> f64 {
112 let crop = self.crop_box().normalized();
113 match self.rotation() {
114 Rotation::Rotate90 | Rotation::Rotate270 => crop.height().abs(),
115 _ => crop.width().abs(),
116 }
117 }
118
119 pub fn height(&self) -> f64 {
121 let crop = self.crop_box().normalized();
122 match self.rotation() {
123 Rotation::Rotate90 | Rotation::Rotate270 => crop.width().abs(),
124 _ => crop.height().abs(),
125 }
126 }
127
128 pub fn num_annots(&self) -> usize {
130 self.dict
131 .dict_get(b"Annots")
132 .and_then(|o| o.as_array())
133 .map(|a| a.len())
134 .unwrap_or(0)
135 }
136
137 pub fn default_matrix(&self) -> Matrix2D {
142 let crop = self.crop_box().normalized();
143 let rot = self.rotation();
144
145 let base = Matrix2D::translation(-crop.x1, -crop.y1);
146
147 match rot {
148 Rotation::None => base,
149 Rotation::Rotate90 => {
150 let rotate = Matrix2D::new(0.0, -1.0, 1.0, 0.0, 0.0, crop.width());
151 rotate * base
152 }
153 Rotation::Rotate180 => {
154 let rotate = Matrix2D::new(-1.0, 0.0, 0.0, -1.0, crop.width(), crop.height());
155 rotate * base
156 }
157 Rotation::Rotate270 => {
158 let rotate = Matrix2D::new(0.0, 1.0, -1.0, 0.0, crop.height(), 0.0);
159 rotate * base
160 }
161 }
162 }
163
164 pub fn resources(&self) -> Option<&PdfObject> {
166 self.dict.dict_get(b"Resources")
167 }
168
169 pub fn contents(&self) -> Option<&PdfObject> {
171 self.dict.dict_get(b"Contents")
172 }
173
174 pub fn user_unit(&self) -> f64 {
176 self.dict.dict_get_f64(b"UserUnit").unwrap_or(1.0)
177 }
178}