otf_pdf_syntax/
serialize.rs1use crate::lexer::is_regular;
7use crate::object::{Dict, Name, Object, PdfString};
8
9pub fn write_object(object: &Object, out: &mut Vec<u8>) {
12 match object {
13 Object::Null => out.extend_from_slice(b"null"),
14 Object::Bool(true) => out.extend_from_slice(b"true"),
15 Object::Bool(false) => out.extend_from_slice(b"false"),
16 Object::Int(i) => out.extend_from_slice(i.to_string().as_bytes()),
17 Object::Real(r) => write_real(*r, out),
18 Object::String(s) => write_string(s, out),
19 Object::Name(n) => write_name(n, out),
20 Object::Array(items) => {
21 out.push(b'[');
22 for (i, item) in items.iter().enumerate() {
23 if i > 0 {
24 out.push(b' ');
25 }
26 write_object(item, out);
27 }
28 out.push(b']');
29 }
30 Object::Dict(d) => write_dict(d, out),
31 Object::Stream(s) => {
32 let mut dict = s.dict.clone();
33 dict.insert("Length", s.raw.len() as i64);
34 write_dict(&dict, out);
35 out.extend_from_slice(b"\nstream\n");
36 out.extend_from_slice(&s.raw);
37 out.extend_from_slice(b"\nendstream");
38 }
39 Object::Ref(id) => {
40 out.extend_from_slice(format!("{} {} R", id.num, id.generation).as_bytes())
41 }
42 }
43}
44
45pub fn to_bytes(object: &Object) -> Vec<u8> {
47 let mut out = Vec::new();
48 write_object(object, &mut out);
49 out
50}
51
52fn write_dict(dict: &Dict, out: &mut Vec<u8>) {
53 out.extend_from_slice(b"<<");
54 for (key, value) in dict.iter() {
55 write_name(key, out);
56 out.push(b' ');
57 write_object(value, out);
58 out.push(b' ');
59 }
60 out.extend_from_slice(b">>");
61}
62
63fn write_real(r: f64, out: &mut Vec<u8>) {
66 if !r.is_finite() {
67 out.push(b'0');
68 return;
69 }
70 let s = format!("{r:.6}");
71 let s = s.trim_end_matches('0').trim_end_matches('.');
72 let s = if s == "-0" || s.is_empty() { "0" } else { s };
73 out.extend_from_slice(s.as_bytes());
74}
75
76fn write_name(name: &Name, out: &mut Vec<u8>) {
77 out.push(b'/');
78 for &b in name.as_bytes() {
79 if is_regular(b) && b != b'#' && (b'!'..=b'~').contains(&b) {
80 out.push(b);
81 } else {
82 out.extend_from_slice(format!("#{b:02X}").as_bytes());
83 }
84 }
85}
86
87fn write_string(s: &PdfString, out: &mut Vec<u8>) {
88 let unprintable = s
90 .bytes
91 .iter()
92 .filter(|&&b| !(0x20..0x7f).contains(&b))
93 .count();
94 if s.hex || unprintable * 4 > s.bytes.len() {
95 out.push(b'<');
96 for b in &s.bytes {
97 out.extend_from_slice(format!("{b:02x}").as_bytes());
98 }
99 out.push(b'>');
100 return;
101 }
102 out.push(b'(');
103 for &b in &s.bytes {
104 match b {
105 b'(' | b')' | b'\\' => out.extend_from_slice(&[b'\\', b]),
106 b'\n' => out.extend_from_slice(b"\\n"),
107 b'\r' => out.extend_from_slice(b"\\r"),
108 0x20..=0x7e => out.push(b),
109 _ => out.extend_from_slice(format!("\\{b:03o}").as_bytes()),
110 }
111 }
112 out.push(b')');
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::limits::{Limits, Strictness};
119 use crate::object::{ObjId, Stream};
120 use crate::parser::Parser;
121
122 fn text(o: &Object) -> String {
123 String::from_utf8(to_bytes(o)).unwrap()
124 }
125
126 fn reparse(o: &Object) -> Object {
127 let limits = Limits::default();
128 Parser::new(&to_bytes(o), 0, &limits, Strictness::Strict)
129 .parse_object()
130 .unwrap()
131 }
132
133 #[test]
134 fn scalars() {
135 assert_eq!(text(&Object::Null), "null");
136 assert_eq!(text(&Object::Bool(false)), "false");
137 assert_eq!(text(&Object::Int(-3)), "-3");
138 assert_eq!(text(&Object::Real(0.5)), "0.5");
139 assert_eq!(text(&Object::Real(2.0)), "2");
140 assert_eq!(text(&Object::Real(-0.0000001)), "0");
141 assert_eq!(text(&Object::Real(f64::NAN)), "0");
142 assert_eq!(text(&Object::Real(1e20)), "100000000000000000000");
143 assert_eq!(text(&Object::Ref(ObjId::new(4, 1))), "4 1 R");
144 }
145
146 #[test]
147 fn names_are_escaped() {
148 assert_eq!(
149 text(&Object::Name(Name::from("A B#(c)"))),
150 "/A#20B#23#28c#29"
151 );
152 assert_eq!(text(&Object::Name(Name::new(vec![0xe9]))), "/#E9");
153 }
154
155 #[test]
156 fn strings() {
157 assert_eq!(
158 text(&Object::String(PdfString::literal("a(b)\\\n"))),
159 "(a\\(b\\)\\\\\\n)"
160 );
161 assert_eq!(
162 text(&Object::String(PdfString::literal(vec![
163 b'a', 0xe9, b'b', b'c', b'd'
164 ]))),
165 "(a\\351bcd)"
166 );
167 assert_eq!(
168 text(&Object::String(PdfString::literal(vec![0, 1, 2]))),
169 "<000102>"
170 );
171 assert_eq!(text(&Object::String(PdfString::hex("A"))), "<41>");
172 }
173
174 #[test]
175 fn containers_round_trip() {
176 let mut d = Dict::new();
177 d.insert("Type", Name::from("Page"));
178 d.insert(
179 "Kids",
180 vec![Object::Ref(ObjId::new(1, 0)), Object::Real(1.25)],
181 );
182 d.insert("S", PdfString::literal(b"(a\xffbc)".to_vec()));
183 let o = Object::Dict(d);
184 assert_eq!(reparse(&o), o);
185 assert_eq!(
186 text(&o),
187 "<</Kids [1 0 R 1.25] /S (\\(a\\377bc\\)) /Type /Page >>"
188 );
189 }
190
191 #[test]
192 fn streams_carry_their_length() {
193 let s = Object::Stream(Stream {
194 dict: Dict::new(),
195 raw: b"abc".to_vec().into(),
196 });
197 assert_eq!(text(&s), "<</Length 3 >>\nstream\nabc\nendstream");
198 }
199}