1use crate::error::{PdfError, PdfResult};
2use flate2::Compression;
3use flate2::read::ZlibDecoder;
4use flate2::write::ZlibEncoder;
5use std::collections::BTreeMap;
6use std::fmt;
7use std::io::{Read, Write};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct ObjectId {
12 pub num: u32,
13 pub generation: u16,
14}
15
16impl ObjectId {
17 pub const fn new(num: u32, generation: u16) -> Self {
18 Self { num, generation }
19 }
20}
21
22impl fmt::Display for ObjectId {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{} {} R", self.num, self.generation)
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum StringFormat {
31 Literal,
32 Hex,
33}
34
35#[derive(Clone, PartialEq, Eq)]
37pub struct COSString {
38 pub data: Vec<u8>,
39 pub format: StringFormat,
40}
41
42impl COSString {
43 pub fn literal(data: impl Into<Vec<u8>>) -> Self {
44 Self {
45 data: data.into(),
46 format: StringFormat::Literal,
47 }
48 }
49
50 pub fn hex(data: impl Into<Vec<u8>>) -> Self {
51 Self {
52 data: data.into(),
53 format: StringFormat::Hex,
54 }
55 }
56
57 pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
58 std::str::from_utf8(&self.data)
59 }
60
61 pub fn to_string_lossy(&self) -> String {
62 if self.data.len() >= 2 && self.data[0] == 0xFE && self.data[1] == 0xFF {
64 let u16s: Vec<u16> = self.data[2..]
65 .chunks_exact(2)
66 .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
67 .collect();
68 String::from_utf16_lossy(&u16s)
69 } else {
70 String::from_utf8_lossy(&self.data).into_owned()
71 }
72 }
73}
74
75impl fmt::Debug for COSString {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self.format {
78 StringFormat::Literal => write!(f, "COSString(\"{}\")", self.to_string_lossy()),
79 StringFormat::Hex => {
80 let hex_str: String = self.data.iter().map(|b| format!("{:02X}", b)).collect();
81 write!(f, "COSString(<{}>)", hex_str)
82 }
83 }
84 }
85}
86
87#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub struct COSName(pub String);
90
91impl COSName {
92 pub fn new(name: impl Into<String>) -> Self {
93 let mut s = name.into();
94 if s.starts_with('/') {
95 s.remove(0);
96 }
97 Self(s)
98 }
99
100 pub fn as_str(&self) -> &str {
101 &self.0
102 }
103}
104
105impl fmt::Debug for COSName {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(f, "/{}", self.0)
108 }
109}
110
111impl fmt::Display for COSName {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "/{}", self.0)
114 }
115}
116
117impl<T: Into<String>> From<T> for COSName {
118 fn from(s: T) -> Self {
119 Self::new(s)
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Default)]
125pub struct COSArray(pub Vec<COSObject>);
126
127impl COSArray {
128 pub fn new() -> Self {
129 Self(Vec::new())
130 }
131
132 pub fn with_capacity(capacity: usize) -> Self {
133 Self(Vec::with_capacity(capacity))
134 }
135
136 pub fn push<T: Into<COSObject>>(&mut self, obj: T) {
137 self.0.push(obj.into());
138 }
139
140 pub fn len(&self) -> usize {
141 self.0.len()
142 }
143
144 pub fn is_empty(&self) -> bool {
145 self.0.is_empty()
146 }
147
148 pub fn get(&self, index: usize) -> Option<&COSObject> {
149 self.0.get(index)
150 }
151
152 pub fn iter(&self) -> std::slice::Iter<'_, COSObject> {
153 self.0.iter()
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Default)]
159pub struct COSDictionary {
160 pub entries: BTreeMap<COSName, COSObject>,
161}
162
163impl COSDictionary {
164 pub fn new() -> Self {
165 Self {
166 entries: BTreeMap::new(),
167 }
168 }
169
170 pub fn get(&self, key: &str) -> Option<&COSObject> {
171 let name = COSName::new(key);
172 self.entries.get(&name)
173 }
174
175 pub fn get_mut(&mut self, key: &str) -> Option<&mut COSObject> {
176 let name = COSName::new(key);
177 self.entries.get_mut(&name)
178 }
179
180 pub fn set<K: Into<COSName>, V: Into<COSObject>>(&mut self, key: K, val: V) {
181 self.entries.insert(key.into(), val.into());
182 }
183
184 pub fn remove(&mut self, key: &str) -> Option<COSObject> {
185 let name = COSName::new(key);
186 self.entries.remove(&name)
187 }
188
189 pub fn contains_key(&self, key: &str) -> bool {
190 self.get(key).is_some()
191 }
192
193 pub fn get_name(&self, key: &str) -> Option<&str> {
194 match self.get(key) {
195 Some(COSObject::Name(n)) => Some(n.as_str()),
196 _ => None,
197 }
198 }
199
200 pub fn get_integer(&self, key: &str) -> Option<i64> {
201 match self.get(key) {
202 Some(COSObject::Integer(i)) => Some(*i),
203 _ => None,
204 }
205 }
206
207 pub fn get_float(&self, key: &str) -> Option<f64> {
208 match self.get(key) {
209 Some(COSObject::Real(f)) => Some(*f),
210 Some(COSObject::Integer(i)) => Some(*i as f64),
211 _ => None,
212 }
213 }
214
215 pub fn get_bool(&self, key: &str) -> Option<bool> {
216 match self.get(key) {
217 Some(COSObject::Boolean(b)) => Some(*b),
218 _ => None,
219 }
220 }
221
222 pub fn get_string(&self, key: &str) -> Option<&COSString> {
223 match self.get(key) {
224 Some(COSObject::String(s)) => Some(s),
225 _ => None,
226 }
227 }
228
229 pub fn get_dict(&self, key: &str) -> Option<&COSDictionary> {
230 match self.get(key) {
231 Some(COSObject::Dictionary(d)) => Some(d),
232 _ => None,
233 }
234 }
235
236 pub fn get_dict_mut(&mut self, key: &str) -> Option<&mut COSDictionary> {
237 let name = COSName::new(key);
238 match self.entries.get_mut(&name) {
239 Some(COSObject::Dictionary(d)) => Some(d),
240 _ => None,
241 }
242 }
243
244 pub fn get_array(&self, key: &str) -> Option<&COSArray> {
245 match self.get(key) {
246 Some(COSObject::Array(a)) => Some(a),
247 _ => None,
248 }
249 }
250
251 pub fn get_reference(&self, key: &str) -> Option<ObjectId> {
252 match self.get(key) {
253 Some(COSObject::Reference(id)) => Some(*id),
254 _ => None,
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq)]
261pub struct COSStream {
262 pub dict: COSDictionary,
263 pub data: Vec<u8>,
264}
265
266impl COSStream {
267 pub fn new(dict: COSDictionary, data: Vec<u8>) -> Self {
268 let mut s = Self { dict, data };
269 s.update_length();
270 s
271 }
272
273 pub fn update_length(&mut self) {
274 self.dict.set(
275 COSName::new("Length"),
276 COSObject::Integer(self.data.len() as i64),
277 );
278 }
279
280 pub fn decode(&self) -> PdfResult<Vec<u8>> {
282 let filter = self.dict.get_name("Filter");
283 match filter {
284 None => Ok(self.data.clone()),
285 Some("FlateDecode") => {
286 let mut decoder = ZlibDecoder::new(&self.data[..]);
287 let mut decoded = Vec::new();
288 decoder.read_to_end(&mut decoded).map_err(|e| {
289 PdfError::StreamCompression(format!("Flate decompression error: {e}"))
290 })?;
291 Ok(decoded)
292 }
293 Some(other) => Err(PdfError::Unsupported(format!(
294 "Unsupported stream filter: {other}"
295 ))),
296 }
297 }
298
299 pub fn encode_flate(&mut self) -> PdfResult<()> {
301 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
302 encoder
303 .write_all(&self.data)
304 .map_err(|e| PdfError::StreamCompression(format!("Flate compression error: {e}")))?;
305 let compressed = encoder.finish().map_err(|e| {
306 PdfError::StreamCompression(format!("Flate compression finish error: {e}"))
307 })?;
308
309 self.data = compressed;
310 self.dict
311 .set(COSName::new("Filter"), COSName::new("FlateDecode"));
312 self.update_length();
313 Ok(())
314 }
315}
316
317#[derive(Debug, Clone, PartialEq)]
319pub enum COSObject {
320 Null,
321 Boolean(bool),
322 Integer(i64),
323 Real(f64),
324 String(COSString),
325 Name(COSName),
326 Array(COSArray),
327 Dictionary(COSDictionary),
328 Stream(COSStream),
329 Reference(ObjectId),
330}
331
332impl COSObject {
333 pub const fn null() -> Self {
334 Self::Null
335 }
336
337 pub fn type_name(&self) -> &'static str {
338 match self {
339 Self::Null => "null",
340 Self::Boolean(_) => "boolean",
341 Self::Integer(_) => "integer",
342 Self::Real(_) => "real",
343 Self::String(_) => "string",
344 Self::Name(_) => "name",
345 Self::Array(_) => "array",
346 Self::Dictionary(_) => "dictionary",
347 Self::Stream(_) => "stream",
348 Self::Reference(_) => "reference",
349 }
350 }
351
352 pub fn as_integer(&self) -> Option<i64> {
353 match self {
354 Self::Integer(i) => Some(*i),
355 _ => None,
356 }
357 }
358
359 pub fn as_float(&self) -> Option<f64> {
360 match self {
361 Self::Real(f) => Some(*f),
362 Self::Integer(i) => Some(*i as f64),
363 _ => None,
364 }
365 }
366
367 pub fn as_bool(&self) -> Option<bool> {
368 match self {
369 Self::Boolean(b) => Some(*b),
370 _ => None,
371 }
372 }
373
374 pub fn as_name(&self) -> Option<&str> {
375 match self {
376 Self::Name(n) => Some(n.as_str()),
377 _ => None,
378 }
379 }
380
381 pub fn as_string(&self) -> Option<&COSString> {
382 match self {
383 Self::String(s) => Some(s),
384 _ => None,
385 }
386 }
387
388 pub fn as_dict(&self) -> Option<&COSDictionary> {
389 match self {
390 Self::Dictionary(d) => Some(d),
391 _ => None,
392 }
393 }
394
395 pub fn as_dict_mut(&mut self) -> Option<&mut COSDictionary> {
396 match self {
397 Self::Dictionary(d) => Some(d),
398 _ => None,
399 }
400 }
401
402 pub fn as_array(&self) -> Option<&COSArray> {
403 match self {
404 Self::Array(a) => Some(a),
405 _ => None,
406 }
407 }
408
409 pub fn as_stream(&self) -> Option<&COSStream> {
410 match self {
411 Self::Stream(s) => Some(s),
412 _ => None,
413 }
414 }
415
416 pub fn as_reference(&self) -> Option<ObjectId> {
417 match self {
418 Self::Reference(id) => Some(*id),
419 _ => None,
420 }
421 }
422}
423
424impl From<bool> for COSObject {
426 fn from(b: bool) -> Self {
427 Self::Boolean(b)
428 }
429}
430
431impl From<i64> for COSObject {
432 fn from(i: i64) -> Self {
433 Self::Integer(i)
434 }
435}
436
437impl From<i32> for COSObject {
438 fn from(i: i32) -> Self {
439 Self::Integer(i as i64)
440 }
441}
442
443impl From<u32> for COSObject {
444 fn from(i: u32) -> Self {
445 Self::Integer(i as i64)
446 }
447}
448
449impl From<usize> for COSObject {
450 fn from(i: usize) -> Self {
451 Self::Integer(i as i64)
452 }
453}
454
455impl From<f64> for COSObject {
456 fn from(f: f64) -> Self {
457 Self::Real(f)
458 }
459}
460
461impl From<f32> for COSObject {
462 fn from(f: f32) -> Self {
463 Self::Real(f as f64)
464 }
465}
466
467impl From<COSName> for COSObject {
468 fn from(n: COSName) -> Self {
469 Self::Name(n)
470 }
471}
472
473impl From<COSString> for COSObject {
474 fn from(s: COSString) -> Self {
475 Self::String(s)
476 }
477}
478
479impl From<COSDictionary> for COSObject {
480 fn from(d: COSDictionary) -> Self {
481 Self::Dictionary(d)
482 }
483}
484
485impl From<COSArray> for COSObject {
486 fn from(a: COSArray) -> Self {
487 Self::Array(a)
488 }
489}
490
491impl From<COSStream> for COSObject {
492 fn from(s: COSStream) -> Self {
493 Self::Stream(s)
494 }
495}
496
497impl From<ObjectId> for COSObject {
498 fn from(id: ObjectId) -> Self {
499 Self::Reference(id)
500 }
501}
502
503impl From<&str> for COSObject {
504 fn from(s: &str) -> Self {
505 Self::String(COSString::literal(s))
506 }
507}
508
509impl From<String> for COSObject {
510 fn from(s: String) -> Self {
511 Self::String(COSString::literal(s))
512 }
513}