pdf_objects/
serializer.rs1use std::fmt::Write;
2
3use crate::types::{PdfDictionary, PdfFile, PdfObject, PdfString, PdfValue};
4
5pub fn serialize_pdf(file: &PdfFile) -> Vec<u8> {
6 let mut output = Vec::new();
7 output.extend_from_slice(
8 format!("%PDF-{}\n%\u{00FF}\u{00FF}\u{00FF}\u{00FF}\n", file.version).as_bytes(),
9 );
10
11 let mut offsets = std::collections::BTreeMap::new();
12 for (object_ref, object) in &file.objects {
13 let offset = output.len();
14 offsets.insert(object_ref.object_number, offset);
15 output.extend_from_slice(
16 format!(
17 "{} {} obj\n",
18 object_ref.object_number, object_ref.generation
19 )
20 .as_bytes(),
21 );
22 match object {
23 PdfObject::Value(value) => {
24 output.extend_from_slice(serialize_value(value).as_bytes());
25 output.extend_from_slice(b"\nendobj\n");
26 }
27 PdfObject::Stream(stream) => {
28 let mut dict = stream.dict.clone();
29 dict.insert(
30 "Length".to_string(),
31 PdfValue::Integer(stream.data.len() as i64),
32 );
33 output.extend_from_slice(serialize_dictionary(&dict).as_bytes());
34 output.extend_from_slice(b"\nstream\n");
35 output.extend_from_slice(&stream.data);
36 if !stream.data.ends_with(b"\n") {
37 output.push(b'\n');
38 }
39 output.extend_from_slice(b"endstream\nendobj\n");
40 }
41 }
42 }
43
44 let startxref = output.len();
45 let size = file.max_object_number + 1;
46 output.extend_from_slice(format!("xref\n0 {}\n", size).as_bytes());
47 output.extend_from_slice(b"0000000000 65535 f \n");
48 for object_number in 1..=file.max_object_number {
49 if let Some(offset) = offsets.get(&object_number).copied() {
50 output.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
51 } else {
52 output.extend_from_slice(b"0000000000 65535 f \n");
53 }
54 }
55
56 let mut trailer = file.trailer.clone();
57 trailer.insert("Size".to_string(), PdfValue::Integer(size as i64));
58 trailer.remove("Prev");
59 trailer.remove("XRefStm");
60 output.extend_from_slice(b"trailer\n");
61 output.extend_from_slice(serialize_dictionary(&trailer).as_bytes());
62 output.extend_from_slice(format!("\nstartxref\n{startxref}\n%%EOF\n").as_bytes());
63 output
64}
65
66pub fn serialize_value(value: &PdfValue) -> String {
67 match value {
68 PdfValue::Null => "null".to_string(),
69 PdfValue::Bool(value) => value.to_string(),
70 PdfValue::Integer(value) => value.to_string(),
71 PdfValue::Number(value) => {
72 if value.fract() == 0.0 {
73 format!("{:.0}", value)
74 } else {
75 let mut number = format!("{value:.6}");
76 while number.contains('.') && number.ends_with('0') {
77 number.pop();
78 }
79 if number.ends_with('.') {
80 number.pop();
81 }
82 number
83 }
84 }
85 PdfValue::Name(name) => {
86 let mut encoded = String::from("/");
87 for byte in name.bytes() {
88 if byte == b'#'
89 || byte <= b' '
90 || byte >= 0x7F
91 || matches!(
92 byte,
93 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
94 )
95 {
96 encoded.push_str(&format!("#{:02X}", byte));
97 } else {
98 encoded.push(byte as char);
99 }
100 }
101 encoded
102 }
103 PdfValue::String(string) => serialize_string(string),
104 PdfValue::Array(values) => format!(
105 "[{}]",
106 values
107 .iter()
108 .map(serialize_value)
109 .collect::<Vec<_>>()
110 .join(" ")
111 ),
112 PdfValue::Dictionary(dictionary) => serialize_dictionary(dictionary),
113 PdfValue::Reference(object_ref) => {
114 format!("{} {} R", object_ref.object_number, object_ref.generation)
115 }
116 }
117}
118
119pub fn serialize_dictionary(dictionary: &PdfDictionary) -> String {
120 let mut output = String::from("<<");
121 for (key, value) in dictionary {
122 write!(output, "/{} {}", key, serialize_value(value))
123 .expect("string writes should succeed");
124 output.push(' ');
125 }
126 output.push_str(">>");
127 output
128}
129
130pub fn serialize_string(string: &PdfString) -> String {
131 let mut output = String::from("(");
132 for byte in &string.0 {
133 match byte {
134 b'(' | b')' | b'\\' => {
135 output.push('\\');
136 output.push(*byte as char);
137 }
138 b'\n' => output.push_str("\\n"),
139 b'\r' => output.push_str("\\r"),
140 b'\t' => output.push_str("\\t"),
141 0x08 => output.push_str("\\b"),
142 0x0C => output.push_str("\\f"),
143 byte if byte.is_ascii_graphic() || *byte == b' ' => output.push(*byte as char),
144 other => output.push_str(&format!("\\{:03o}", other)),
145 }
146 }
147 output.push(')');
148 output
149}