Skip to main content

graphitepdf_kit/
objects.rs

1use crate::error::{GraphitePdfKitError, Result};
2use std::io::Write;
3
4#[cfg(feature = "tracing")]
5use tracing::instrument;
6
7/// PDF object types as specified in PDF 1.7 specification.
8#[derive(Clone, Debug, PartialEq)]
9pub enum Object {
10    /// Null object.
11    Null,
12    /// Boolean object.
13    Boolean(bool),
14    /// Integer object.
15    Integer(i64),
16    /// Real (floating-point) object.
17    Real(f64),
18    /// String object (binary data, typically 8-bit or Unicode).
19    String(Vec<u8>),
20    /// Name object.
21    Name(String),
22    /// Array object, containing other PDF objects.
23    Array(Vec<Object>),
24    /// Dictionary object, key-value pairs.
25    Dict(Vec<(String, Object)>),
26    /// Indirect reference object reference by object number.
27    Ref(u64),
28    /// Stream object, dictionary + data.
29    Stream {
30        dict: Vec<(String, Object)>,
31        data: Vec<u8>,
32    },
33}
34
35impl Object {
36    /// Creates a new Name object.
37    pub fn name(s: impl Into<String>) -> Self {
38        Object::Name(s.into())
39    }
40
41    /// Creates a new Dictionary object from key-value pairs.
42    pub fn dict(entries: impl IntoIterator<Item = (impl Into<String>, impl Into<Object>)>) -> Self {
43        Object::Dict(
44            entries
45                .into_iter()
46                .map(|(k, v)| (k.into(), v.into()))
47                .collect(),
48        )
49    }
50
51    /// Creates a new Array object.
52    pub fn array(items: impl IntoIterator<Item = impl Into<Object>>) -> Self {
53        Object::Array(items.into_iter().map(|i| i.into()).collect())
54    }
55
56    /// Creates a new String object from bytes.
57    pub fn string(s: impl AsRef<[u8]>) -> Self {
58        Object::String(s.as_ref().to_vec())
59    }
60
61    /// Creates a new UTF-16BE encoded String object.
62    pub fn string_utf16(s: &str) -> Self {
63        let mut bytes = vec![0xFE, 0xFF]; // BOM
64        for c in s.chars() {
65            let code = c as u16;
66            bytes.push((code >> 8) as u8);
67            bytes.push((code & 0xFF) as u8);
68        }
69        Object::String(bytes)
70    }
71
72    /// Creates a new Integer object.
73    pub fn integer(n: i64) -> Self {
74        Object::Integer(n)
75    }
76
77    /// Creates a new Real object.
78    pub fn real(n: f64) -> Self {
79        Object::Real(n)
80    }
81
82    /// Inserts a key-value pair into a dictionary object.
83    /// Returns an error if `self` is not a Dict.
84    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Object>) -> Result<()> {
85        match self {
86            Object::Dict(entries) => {
87                let key_str = key.into();
88                entries.retain(|(k, _)| k != &key_str);
89                entries.push((key_str, value.into()));
90                Ok(())
91            }
92            _ => Err(GraphitePdfKitError::InvalidObject(
93                "Cannot insert into non-Dictionary object".to_string(),
94            )),
95        }
96    }
97
98    /// Gets a value from a dictionary object by key.
99    /// Returns None if the object is not a Dict or key doesn't exist.
100    pub fn get(&self, key: &str) -> Option<&Object> {
101        match self {
102            Object::Dict(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
103            _ => None,
104        }
105    }
106
107    /// Pushes an item to an array object.
108    /// Returns an error if `self` is not an Array.
109    pub fn push(&mut self, item: impl Into<Object>) -> Result<()> {
110        match self {
111            Object::Array(items) => {
112                items.push(item.into());
113                Ok(())
114            }
115            _ => Err(GraphitePdfKitError::InvalidObject(
116                "Cannot push into non-Array object".to_string(),
117            )),
118        }
119    }
120
121    /// Writes the object to a writer as PDF syntax.
122    #[cfg_attr(feature = "tracing", instrument(skip(writer)))]
123    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
124        match self {
125            Object::Null => writer.write_all(b"null")?,
126            Object::Boolean(true) => writer.write_all(b"true")?,
127            Object::Boolean(false) => writer.write_all(b"false")?,
128            Object::Integer(n) => write!(writer, "{n}")?,
129            Object::Real(n) => {
130                if n.is_infinite() || n.is_nan() {
131                    return Err(GraphitePdfKitError::InvalidObject(
132                        "cannot serialize infinite or NaN real number".to_string(),
133                    ));
134                }
135                write!(writer, "{n}")?;
136            }
137            Object::String(s) => {
138                writer.write_all(b"(")?;
139                for byte in s {
140                    match byte {
141                        b'(' | b')' | b'\\' => {
142                            writer.write_all(b"\\")?;
143                            writer.write_all(&[*byte])?;
144                        }
145                        0x08 => writer.write_all(b"\\b")?,
146                        0x09 => writer.write_all(b"\\t")?,
147                        0x0a => writer.write_all(b"\\n")?,
148                        0x0c => writer.write_all(b"\\f")?,
149                        0x0d => writer.write_all(b"\\r")?,
150                        _ => writer.write_all(&[*byte])?,
151                    }
152                }
153                writer.write_all(b")")?;
154            }
155            Object::Name(name) => {
156                writer.write_all(b"/")?;
157                for byte in name.bytes() {
158                    match byte {
159                        b'0'..=b'9'
160                        | b'A'..=b'Z'
161                        | b'a'..=b'z'
162                        | b'_'
163                        | b';'
164                        | b':'
165                        | b'@'
166                        | b'&'
167                        | b'$'
168                        | b'#'
169                        | b'%'
170                        | b'+'
171                        | b'-'
172                        | b'.'
173                        | b'!'
174                        | b'~'
175                        | b'*'
176                        | b'/'
177                        | b'?' => {
178                            writer.write_all(&[byte])?;
179                        }
180                        _ => {
181                            write!(writer, "#{:02x}", byte)?;
182                        }
183                    }
184                }
185            }
186            Object::Array(items) => {
187                writer.write_all(b"[")?;
188                for (i, item) in items.iter().enumerate() {
189                    if i > 0 {
190                        writer.write_all(b" ")?;
191                    }
192                    item.write(writer)?;
193                }
194                writer.write_all(b"]")?;
195            }
196            Object::Dict(entries) => {
197                writer.write_all(b"<<")?;
198                for (i, (key, value)) in entries.iter().enumerate() {
199                    if i > 0 {
200                        writer.write_all(b"\n")?;
201                    }
202                    writer.write_all(b"/")?;
203                    writer.write_all(key.as_bytes())?;
204                    writer.write_all(b" ")?;
205                    value.write(writer)?;
206                }
207                writer.write_all(b">>")?;
208            }
209            Object::Ref(n) => {
210                write!(writer, "{n} 0 R")?;
211            }
212            Object::Stream { dict, data } => {
213                let mut dict_with_length = dict.clone();
214                dict_with_length.push(("Length".to_string(), Object::Integer(data.len() as i64)));
215                Object::Dict(dict_with_length).write(writer)?;
216                writer.write_all(b"\nstream\n")?;
217                writer.write_all(data)?;
218                writer.write_all(b"\nendstream")?;
219            }
220        }
221        Ok(())
222    }
223}
224
225impl From<bool> for Object {
226    fn from(value: bool) -> Self {
227        Object::Boolean(value)
228    }
229}
230
231impl From<i64> for Object {
232    fn from(value: i64) -> Self {
233        Object::Integer(value)
234    }
235}
236
237impl From<i32> for Object {
238    fn from(value: i32) -> Self {
239        Object::Integer(value as i64)
240    }
241}
242
243impl From<f64> for Object {
244    fn from(value: f64) -> Self {
245        Object::Real(value)
246    }
247}
248
249impl From<f32> for Object {
250    fn from(value: f32) -> Self {
251        Object::Real(value as f64)
252    }
253}
254
255impl From<&str> for Object {
256    fn from(value: &str) -> Self {
257        Object::string(value.as_bytes())
258    }
259}
260
261impl From<String> for Object {
262    fn from(value: String) -> Self {
263        Object::string(value.into_bytes())
264    }
265}
266
267impl<T: Into<Object>> From<Vec<T>> for Object {
268    fn from(value: Vec<T>) -> Self {
269        Object::Array(value.into_iter().map(|v| v.into()).collect())
270    }
271}
272
273impl<'a, T: Into<Object> + Clone> From<&'a [T]> for Object {
274    fn from(value: &'a [T]) -> Self {
275        Object::Array(value.iter().map(|v| v.clone().into()).collect())
276    }
277}