1use std::collections::BTreeMap;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::{Error, Rect, Result};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub struct ObjectId(pub u32, pub u16);
10
11impl fmt::Display for ObjectId {
12 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 write!(f, "{} {} R", self.0, self.1)
14 }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct PdfName(pub String);
20
21impl PdfName {
22 pub fn new(s: impl Into<String>) -> Self {
23 Self(s.into())
24 }
25
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29}
30
31impl fmt::Display for PdfName {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "/{}", self.0)
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct PdfString(pub Vec<u8>);
40
41impl PdfString {
42 pub fn new(bytes: Vec<u8>) -> Self {
43 Self(bytes)
44 }
45
46 pub fn as_bytes(&self) -> &[u8] {
47 &self.0
48 }
49
50 pub fn to_string_lossy(&self) -> String {
51 String::from_utf8_lossy(&self.0).into_owned()
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
57pub struct PdfDict(pub BTreeMap<PdfName, PdfObject>);
58
59impl PdfDict {
60 pub fn new() -> Self {
61 Self(BTreeMap::new())
62 }
63
64 pub fn get(&self, key: &str) -> Option<&PdfObject> {
65 self.0.get(&PdfName(key.to_string()))
66 }
67
68 pub fn insert(&mut self, key: PdfName, value: PdfObject) {
69 self.0.insert(key, value);
70 }
71
72 pub fn get_name(&self, key: &str) -> Result<&str> {
73 match self.get(key) {
74 Some(PdfObject::Name(n)) => Ok(n.as_str()),
75 Some(other) => Err(Error::TypeMismatch {
76 expected: "Name",
77 actual: other.type_name(),
78 }),
79 None => Err(Error::MissingKey(key.to_string())),
80 }
81 }
82
83 pub fn get_i64(&self, key: &str) -> Result<i64> {
84 match self.get(key) {
85 Some(PdfObject::Integer(n)) => Ok(*n),
86 Some(other) => Err(Error::TypeMismatch {
87 expected: "Integer",
88 actual: other.type_name(),
89 }),
90 None => Err(Error::MissingKey(key.to_string())),
91 }
92 }
93
94 pub fn get_f64(&self, key: &str) -> Result<f64> {
95 match self.get(key) {
96 Some(PdfObject::Real(n)) => Ok(*n),
97 Some(PdfObject::Integer(n)) => Ok(*n as f64),
98 Some(other) => Err(Error::TypeMismatch {
99 expected: "number",
100 actual: other.type_name(),
101 }),
102 None => Err(Error::MissingKey(key.to_string())),
103 }
104 }
105
106 pub fn get_array(&self, key: &str) -> Result<&[PdfObject]> {
107 match self.get(key) {
108 Some(PdfObject::Array(a)) => Ok(a),
109 Some(other) => Err(Error::TypeMismatch {
110 expected: "Array",
111 actual: other.type_name(),
112 }),
113 None => Err(Error::MissingKey(key.to_string())),
114 }
115 }
116
117 pub fn get_dict(&self, key: &str) -> Result<&PdfDict> {
118 match self.get(key) {
119 Some(PdfObject::Dict(d)) => Ok(d),
120 Some(other) => Err(Error::TypeMismatch {
121 expected: "Dict",
122 actual: other.type_name(),
123 }),
124 None => Err(Error::MissingKey(key.to_string())),
125 }
126 }
127
128 pub fn get_ref(&self, key: &str) -> Result<ObjectId> {
129 match self.get(key) {
130 Some(PdfObject::Ref(id)) => Ok(*id),
131 Some(other) => Err(Error::TypeMismatch {
132 expected: "Ref",
133 actual: other.type_name(),
134 }),
135 None => Err(Error::MissingKey(key.to_string())),
136 }
137 }
138
139 pub fn get_rect(&self, key: &str) -> Result<Rect> {
140 let arr = self.get_array(key)?;
141 if arr.len() != 4 {
142 return Err(Error::TypeMismatch {
143 expected: "4-element array (Rect)",
144 actual: "wrong-length array",
145 });
146 }
147 let to_f64 = |obj: &PdfObject| -> Result<f64> {
148 match obj {
149 PdfObject::Real(n) => Ok(*n),
150 PdfObject::Integer(n) => Ok(*n as f64),
151 _ => Err(Error::TypeMismatch {
152 expected: "number",
153 actual: obj.type_name(),
154 }),
155 }
156 };
157 Ok(Rect::new(
158 to_f64(&arr[0])?,
159 to_f64(&arr[1])?,
160 to_f64(&arr[2])?,
161 to_f64(&arr[3])?,
162 ))
163 }
164}
165
166impl Default for PdfDict {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[derive(Debug, Clone, PartialEq)]
174pub struct PdfStream {
175 pub dict: PdfDict,
176 pub data: Arc<[u8]>,
177}
178
179impl PdfStream {
180 pub fn new(dict: PdfDict, data: Vec<u8>) -> Self {
181 Self {
182 dict,
183 data: data.into(),
184 }
185 }
186}
187
188#[derive(Debug, Clone, PartialEq)]
190pub enum PdfObject {
191 Null,
192 Bool(bool),
193 Integer(i64),
194 Real(f64),
195 String(PdfString),
196 Name(PdfName),
197 Array(Vec<PdfObject>),
198 Dict(PdfDict),
199 Stream(PdfStream),
200 Ref(ObjectId),
201}
202
203impl PdfObject {
204 pub fn type_name(&self) -> &'static str {
205 match self {
206 PdfObject::Null => "Null",
207 PdfObject::Bool(_) => "Bool",
208 PdfObject::Integer(_) => "Integer",
209 PdfObject::Real(_) => "Real",
210 PdfObject::String(_) => "String",
211 PdfObject::Name(_) => "Name",
212 PdfObject::Array(_) => "Array",
213 PdfObject::Dict(_) => "Dict",
214 PdfObject::Stream(_) => "Stream",
215 PdfObject::Ref(_) => "Ref",
216 }
217 }
218
219 pub fn as_i64(&self) -> Result<i64> {
220 match self {
221 PdfObject::Integer(n) => Ok(*n),
222 _ => Err(Error::TypeMismatch {
223 expected: "Integer",
224 actual: self.type_name(),
225 }),
226 }
227 }
228
229 pub fn as_f64(&self) -> Result<f64> {
230 match self {
231 PdfObject::Real(n) => Ok(*n),
232 PdfObject::Integer(n) => Ok(*n as f64),
233 _ => Err(Error::TypeMismatch {
234 expected: "number",
235 actual: self.type_name(),
236 }),
237 }
238 }
239
240 pub fn as_name(&self) -> Result<&str> {
241 match self {
242 PdfObject::Name(n) => Ok(n.as_str()),
243 _ => Err(Error::TypeMismatch {
244 expected: "Name",
245 actual: self.type_name(),
246 }),
247 }
248 }
249
250 pub fn as_str(&self) -> Result<&PdfString> {
251 match self {
252 PdfObject::String(s) => Ok(s),
253 _ => Err(Error::TypeMismatch {
254 expected: "String",
255 actual: self.type_name(),
256 }),
257 }
258 }
259
260 pub fn as_array(&self) -> Result<&[PdfObject]> {
261 match self {
262 PdfObject::Array(a) => Ok(a),
263 _ => Err(Error::TypeMismatch {
264 expected: "Array",
265 actual: self.type_name(),
266 }),
267 }
268 }
269
270 pub fn as_dict(&self) -> Result<&PdfDict> {
271 match self {
272 PdfObject::Dict(d) => Ok(d),
273 _ => Err(Error::TypeMismatch {
274 expected: "Dict",
275 actual: self.type_name(),
276 }),
277 }
278 }
279
280 pub fn as_stream(&self) -> Result<&PdfStream> {
281 match self {
282 PdfObject::Stream(s) => Ok(s),
283 _ => Err(Error::TypeMismatch {
284 expected: "Stream",
285 actual: self.type_name(),
286 }),
287 }
288 }
289
290 pub fn as_ref(&self) -> Result<ObjectId> {
291 match self {
292 PdfObject::Ref(id) => Ok(*id),
293 _ => Err(Error::TypeMismatch {
294 expected: "Ref",
295 actual: self.type_name(),
296 }),
297 }
298 }
299
300 pub fn is_null(&self) -> bool {
301 matches!(self, PdfObject::Null)
302 }
303}
304
305impl fmt::Display for PdfObject {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self {
308 PdfObject::Null => write!(f, "null"),
309 PdfObject::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
310 PdfObject::Integer(n) => write!(f, "{n}"),
311 PdfObject::Real(n) => write!(f, "{n}"),
312 PdfObject::String(s) => write!(f, "({})", s.to_string_lossy()),
313 PdfObject::Name(n) => write!(f, "{n}"),
314 PdfObject::Array(a) => {
315 write!(f, "[")?;
316 for (i, obj) in a.iter().enumerate() {
317 if i > 0 {
318 write!(f, " ")?;
319 }
320 write!(f, "{obj}")?;
321 }
322 write!(f, "]")
323 }
324 PdfObject::Dict(d) => {
325 write!(f, "<< ")?;
326 for (k, v) in &d.0 {
327 write!(f, "{k} {v} ")?;
328 }
329 write!(f, ">>")
330 }
331 PdfObject::Stream(s) => {
332 write!(f, "<< ")?;
333 for (k, v) in &s.dict.0 {
334 write!(f, "{k} {v} ")?;
335 }
336 write!(f, ">> stream({} bytes)", s.data.len())
337 }
338 PdfObject::Ref(id) => write!(f, "{id}"),
339 }
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn dict_accessors() {
349 let mut d = PdfDict::new();
350 d.insert(PdfName::new("Type"), PdfObject::Name(PdfName::new("Page")));
351 d.insert(PdfName::new("Count"), PdfObject::Integer(5));
352
353 assert_eq!(d.get_name("Type").unwrap(), "Page");
354 assert_eq!(d.get_i64("Count").unwrap(), 5);
355 assert!(d.get_name("Missing").is_err());
356 }
357
358 #[test]
359 fn dict_get_rect() {
360 let mut d = PdfDict::new();
361 d.insert(
362 PdfName::new("MediaBox"),
363 PdfObject::Array(vec![
364 PdfObject::Integer(0),
365 PdfObject::Integer(0),
366 PdfObject::Real(612.0),
367 PdfObject::Real(792.0),
368 ]),
369 );
370 let r = d.get_rect("MediaBox").unwrap();
371 assert!((r.x1 - 612.0).abs() < 1e-10);
372 assert!((r.y1 - 792.0).abs() < 1e-10);
373 }
374
375 #[test]
376 fn object_display() {
377 let obj = PdfObject::Dict(PdfDict::new());
378 assert_eq!(format!("{obj}"), "<< >>");
379
380 let obj = PdfObject::Ref(ObjectId(12, 0));
381 assert_eq!(format!("{obj}"), "12 0 R");
382 }
383}