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