1use pdfboss_core::{Dict, Name, Object};
10
11use crate::error::{Error, Result};
12
13pub fn serialize_object(obj: &Object, out: &mut Vec<u8>) -> Result<()> {
19 match obj {
20 Object::Null => out.extend_from_slice(b"null"),
21 Object::Bool(true) => out.extend_from_slice(b"true"),
22 Object::Bool(false) => out.extend_from_slice(b"false"),
23 Object::Int(i) => out.extend_from_slice(i.to_string().as_bytes()),
24 Object::Real(r) => write_real(*r, out),
25 Object::String(bytes) => write_string(bytes, out),
26 Object::Name(n) => {
27 nul_free(n)?;
28 write_name(&n.0, out);
29 }
30 Object::Array(items) => {
31 out.push(b'[');
32 for (i, item) in items.iter().enumerate() {
33 if i > 0 {
34 out.push(b' ');
35 }
36 serialize_object(item, out)?;
37 }
38 out.push(b']');
39 }
40 Object::Dict(d) => serialize_dict(d, out)?,
41 Object::Ref(r) => {
42 out.extend_from_slice(r.num.to_string().as_bytes());
43 out.push(b' ');
44 out.extend_from_slice(r.gen.to_string().as_bytes());
45 out.extend_from_slice(b" R");
46 }
47 Object::Stream(_) => return Err(Error::NestedStream),
48 }
49 Ok(())
50}
51
52pub fn serialize_dict(dict: &Dict, out: &mut Vec<u8>) -> Result<()> {
55 out.extend_from_slice(b"<<");
56 let mut entries: Vec<(&Name, &Object)> = dict.iter().collect();
57 entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
58 for (key, value) in entries {
59 nul_free(key)?;
60 out.push(b' ');
61 write_name(&key.0, out);
62 out.push(b' ');
63 serialize_object(value, out)?;
64 }
65 out.extend_from_slice(b" >>");
66 Ok(())
67}
68
69pub fn write_name(name: &str, out: &mut Vec<u8>) {
73 out.push(b'/');
74 for &byte in name.as_bytes() {
75 if (0x21..=0x7E).contains(&byte) && !b"()<>[]{}/%#".contains(&byte) {
76 out.push(byte);
77 continue;
78 }
79 out.push(b'#');
80 push_hex(byte, out);
81 }
82}
83
84pub fn write_string(bytes: &[u8], out: &mut Vec<u8>) {
89 let literal = bytes
90 .iter()
91 .all(|&b| (0x20..=0x7E).contains(&b) || matches!(b, b'\n' | b'\r' | b'\t'));
92 if !literal {
93 out.push(b'<');
94 for &byte in bytes {
95 push_hex(byte, out);
96 }
97 out.push(b'>');
98 return;
99 }
100 out.push(b'(');
101 for &byte in bytes {
102 match byte {
103 b'\\' | b'(' | b')' => {
104 out.push(b'\\');
105 out.push(byte);
106 }
107 b'\n' => out.extend_from_slice(b"\\n"),
108 b'\r' => out.extend_from_slice(b"\\r"),
109 b'\t' => out.extend_from_slice(b"\\t"),
110 other => out.push(other),
111 }
112 }
113 out.push(b')');
114}
115
116pub fn write_real(value: f64, out: &mut Vec<u8>) {
120 if !value.is_finite() || value == 0.0 {
121 out.push(b'0');
122 return;
123 }
124 if value.fract() == 0.0 && value.abs() < 9_007_199_254_740_992.0 {
125 out.extend_from_slice((value as i64).to_string().as_bytes());
126 return;
127 }
128 out.extend_from_slice(plain_decimal(&value.to_string()).as_bytes());
129}
130
131pub fn write_real_f32(value: f32, out: &mut Vec<u8>) {
134 if !value.is_finite() || value == 0.0 {
135 out.push(b'0');
136 return;
137 }
138 out.extend_from_slice(plain_decimal(&value.to_string()).as_bytes());
139}
140
141fn nul_free(name: &Name) -> Result<()> {
146 if name.0.bytes().all(|b| b != 0) {
147 return Ok(());
148 }
149 Err(Error::Other(format!(
150 "name {:?} contains NUL, which has no legal escape in a name",
151 name.0
152 )))
153}
154
155const HEX: &[u8; 16] = b"0123456789ABCDEF";
156
157fn push_hex(byte: u8, out: &mut Vec<u8>) {
158 out.push(HEX[usize::from(byte >> 4)]);
159 out.push(HEX[usize::from(byte & 0x0F)]);
160}
161
162fn plain_decimal(formatted: &str) -> String {
163 let expanded = match formatted.split_once(['e', 'E']) {
164 None => formatted.to_string(),
165 Some((mantissa, exponent)) => expand_exponent(mantissa, exponent),
166 };
167 if !expanded.contains('.') {
168 return expanded;
169 }
170 expanded
171 .trim_end_matches('0')
172 .trim_end_matches('.')
173 .to_string()
174}
175
176fn expand_exponent(mantissa: &str, exponent: &str) -> String {
177 let exp: i32 = exponent
178 .parse()
179 .expect("float formatting produced a malformed exponent");
180 let (sign, unsigned) = match mantissa.strip_prefix('-') {
181 Some(rest) => ("-", rest),
182 None => ("", mantissa),
183 };
184 let (int_part, frac_part) = match unsigned.split_once('.') {
185 Some(parts) => parts,
186 None => (unsigned, ""),
187 };
188 let digits = format!("{int_part}{frac_part}");
189 let point = int_part.len() as i32 + exp;
190 if point <= 0 {
191 let zeros = "0".repeat(point.unsigned_abs() as usize);
192 return format!("{sign}0.{zeros}{digits}");
193 }
194 let point = point as usize;
195 if point >= digits.len() {
196 let zeros = "0".repeat(point - digits.len());
197 return format!("{sign}{digits}{zeros}");
198 }
199 format!("{sign}{}.{}", &digits[..point], &digits[point..])
200}
201
202#[cfg(test)]
203mod tests {
204 use pdfboss_core::parser::{NoResolve, Parser};
205 use pdfboss_core::{Dict, Name, ObjRef, Object, Stream};
206
207 use super::*;
208 use crate::error::Error;
209
210 fn name(text: &str) -> Name {
211 Name(text.to_string())
212 }
213
214 fn ser(obj: &Object) -> String {
215 let mut out = Vec::new();
216 serialize_object(obj, &mut out).expect("serializable object");
217 String::from_utf8(out).expect("serialized syntax is UTF-8")
218 }
219
220 fn real_str(value: f64) -> String {
221 let mut out = Vec::new();
222 write_real(value, &mut out);
223 String::from_utf8(out).expect("real syntax is ASCII")
224 }
225
226 fn real32_str(value: f32) -> String {
227 let mut out = Vec::new();
228 write_real_f32(value, &mut out);
229 String::from_utf8(out).expect("real syntax is ASCII")
230 }
231
232 fn name_str(text: &str) -> String {
233 let mut out = Vec::new();
234 write_name(text, &mut out);
235 String::from_utf8(out).expect("name syntax is ASCII")
236 }
237
238 fn string_str(bytes: &[u8]) -> String {
239 let mut out = Vec::new();
240 write_string(bytes, &mut out);
241 String::from_utf8(out).expect("string syntax is ASCII")
242 }
243
244 #[test]
245 fn scalars_serialize_exactly() {
246 assert_eq!(ser(&Object::Null), "null");
247 assert_eq!(ser(&Object::Bool(true)), "true");
248 assert_eq!(ser(&Object::Bool(false)), "false");
249 assert_eq!(ser(&Object::Int(42)), "42");
250 assert_eq!(ser(&Object::Int(-7)), "-7");
251 assert_eq!(ser(&Object::Int(i64::MIN)), "-9223372036854775808");
252 assert_eq!(ser(&Object::Int(i64::MAX)), "9223372036854775807");
253 }
254
255 #[test]
256 fn real_pins_exact_bytes() {
257 assert_eq!(real_str(0.0), "0");
258 assert_eq!(real_str(-0.0), "0");
259 assert_eq!(real_str(f64::NAN), "0");
260 assert_eq!(real_str(f64::INFINITY), "0");
261 assert_eq!(real_str(f64::NEG_INFINITY), "0");
262 assert_eq!(real_str(72.0), "72");
263 assert_eq!(real_str(-72.0), "-72");
264 assert_eq!(real_str(0.5), "0.5");
265 assert_eq!(real_str(-0.25), "-0.25");
266 assert_eq!(real_str(0.1), "0.1");
267 assert_eq!(real_str(1e-7), "0.0000001");
268 assert_eq!(real_str(1.5e-10), "0.00000000015");
269 assert_eq!(real_str(9007199254740991.0), "9007199254740991");
270 assert_eq!(real_str(-9007199254740991.0), "-9007199254740991");
271 assert_eq!(real_str(9007199254740992.0), "9007199254740992");
272 assert_eq!(real_str(1e300), format!("1{}", "0".repeat(300)));
273 }
274
275 #[test]
276 fn real_f32_round_trips_hard_cases() {
277 let cases = [
278 0.1f32,
279 1e-7f32,
280 16777216.0f32,
281 f32::MIN_POSITIVE,
282 3.4e38f32,
283 1073741824.0f32,
284 ];
285 for value in cases {
286 let text = real32_str(value);
287 assert!(
288 !text.contains(['e', 'E']),
289 "{value} produced exponent form {text}"
290 );
291 let parsed: f32 = text.parse().expect("output parses as f32");
292 assert_eq!(parsed.to_bits(), value.to_bits(), "{value} -> {text}");
293 }
294 assert_eq!(real32_str(-0.0f32), "0");
295 assert_eq!(real32_str(f32::NAN), "0");
296 assert_eq!(real32_str(f32::INFINITY), "0");
297 }
298
299 #[test]
300 fn real_f32_pins_exact_bytes() {
301 assert_eq!(real32_str(0.1f32), "0.1");
302 assert_eq!(real32_str(1e-7f32), "0.0000001");
303 assert_eq!(real32_str(16777216.0f32), "16777216");
304 assert_eq!(real32_str(1073741824.0f32), "1073741800");
305 assert_eq!(real32_str(3.4e38f32), format!("34{}", "0".repeat(37)));
306 assert_eq!(
307 real32_str(f32::MIN_POSITIVE),
308 format!("0.{}11754944", "0".repeat(37))
309 );
310 }
311
312 #[test]
313 fn plain_decimal_expands_exponents() {
314 assert_eq!(plain_decimal("1e300"), format!("1{}", "0".repeat(300)));
315 assert_eq!(plain_decimal("3.4e38"), format!("34{}", "0".repeat(37)));
316 assert_eq!(
317 plain_decimal("1.1754944e-38"),
318 format!("0.{}11754944", "0".repeat(37))
319 );
320 assert_eq!(plain_decimal("-2.5e3"), "-2500");
321 assert_eq!(plain_decimal("1.25e2"), "125");
322 assert_eq!(plain_decimal("1.25e1"), "12.5");
323 assert_eq!(plain_decimal("1.25e-1"), "0.125");
324 assert_eq!(plain_decimal("1e-1"), "0.1");
325 assert_eq!(plain_decimal("2.5E3"), "2500");
326 assert_eq!(plain_decimal("0.5"), "0.5");
327 assert_eq!(plain_decimal("1.50"), "1.5");
328 assert_eq!(plain_decimal("2.0"), "2");
329 }
330
331 #[test]
332 fn names_escape_irregular_bytes() {
333 assert_eq!(name_str("Type"), "/Type");
334 assert_eq!(name_str(""), "/");
335 assert_eq!(name_str("A B"), "/A#20B");
336 assert_eq!(name_str("A#B"), "/A#23B");
337 assert_eq!(name_str("A(B)"), "/A#28B#29");
338 assert_eq!(name_str("a/b"), "/a#2Fb");
339 assert_eq!(name_str("x[y]z"), "/x#5By#5Dz");
340 assert_eq!(name_str("{}"), "/#7B#7D");
341 assert_eq!(name_str("<>"), "/#3C#3E");
342 assert_eq!(name_str("%"), "/#25");
343 assert_eq!(name_str("Ä"), "/#C3#84");
344 assert_eq!(name_str("é"), "/#C3#A9");
345 assert_eq!(name_str("\u{7F}"), "/#7F");
346 assert_eq!(name_str("~!$&*+-.;=?@^"), "/~!$&*+-.;=?@^");
347 }
348
349 #[test]
350 fn strings_use_literal_form_with_escapes() {
351 assert_eq!(string_str(b"Hello"), "(Hello)");
352 assert_eq!(string_str(b""), "()");
353 assert_eq!(string_str(b"a(b)c"), "(a\\(b\\)c)");
354 assert_eq!(string_str(b"a\\b"), "(a\\\\b)");
355 assert_eq!(string_str(b"a\tb\nc\rd"), "(a\\tb\\nc\\rd)");
356 }
357
358 #[test]
359 fn strings_fall_back_to_hex_form() {
360 assert_eq!(string_str(&[0x00, 0xFF, 0x41]), "<00FF41>");
361 assert_eq!(string_str(&[0x7F]), "<7F>");
362 assert_eq!(string_str(&[0x1F]), "<1F>");
363 let long: Vec<u8> = (0u8..=255).collect();
364 let hex: String = (0u8..=255).map(|b| format!("{b:02X}")).collect();
365 assert_eq!(string_str(&long), format!("<{hex}>"));
366 }
367
368 #[test]
369 fn arrays_separate_items_with_single_spaces() {
370 assert_eq!(ser(&Object::Array(vec![])), "[]");
371 let arr = Object::Array(vec![
372 Object::Int(1),
373 Object::Real(2.5),
374 Object::Name(name("X")),
375 ]);
376 assert_eq!(ser(&arr), "[1 2.5 /X]");
377 let nested = Object::Array(vec![
378 Object::Array(vec![Object::Int(1), Object::Int(2)]),
379 Object::Array(vec![Object::Int(3)]),
380 ]);
381 assert_eq!(ser(&nested), "[[1 2] [3]]");
382 }
383
384 #[test]
385 fn dicts_sort_keys_bytewise() {
386 assert_eq!(ser(&Object::Dict(Dict::new())), "<< >>");
387
388 let mut d = Dict::new();
389 d.insert(name("Z"), Object::Int(2));
390 d.insert(name("A"), Object::Int(1));
391 assert_eq!(ser(&Object::Dict(d)), "<< /A 1 /Z 2 >>");
392
393 let mut d = Dict::new();
394 d.insert(name("a"), Object::Int(4));
395 d.insert(name("B"), Object::Int(3));
396 d.insert(name("AB"), Object::Int(2));
397 d.insert(name("AA"), Object::Int(1));
398 assert_eq!(ser(&Object::Dict(d)), "<< /AA 1 /AB 2 /B 3 /a 4 >>");
399 }
400
401 #[test]
402 fn nested_containers_serialize_recursively() {
403 let mut inner = Dict::new();
404 inner.insert(name("X"), Object::Int(1));
405 let mut outer = Dict::new();
406 outer.insert(name("D"), Object::Dict(inner));
407 outer.insert(
408 name("Arr"),
409 Object::Array(vec![Object::Null, Object::Bool(false)]),
410 );
411 assert_eq!(
412 ser(&Object::Dict(outer)),
413 "<< /Arr [null false] /D << /X 1 >> >>"
414 );
415 }
416
417 #[test]
418 fn refs_serialize_as_num_gen_r() {
419 assert_eq!(ser(&Object::Ref(ObjRef { num: 12, gen: 3 })), "12 3 R");
420 assert_eq!(ser(&Object::Ref(ObjRef { num: 1, gen: 0 })), "1 0 R");
421 }
422
423 #[test]
424 fn nested_streams_are_errors() {
425 let stream = Object::Stream(Stream {
426 dict: Dict::new(),
427 data: b"x".to_vec(),
428 });
429 let mut out = Vec::new();
430 let top = serialize_object(&stream, &mut out);
431 assert!(matches!(top, Err(Error::NestedStream)));
432
433 let in_array = serialize_object(&Object::Array(vec![stream.clone()]), &mut out);
434 assert!(matches!(in_array, Err(Error::NestedStream)));
435
436 let mut d = Dict::new();
437 d.insert(name("S"), stream);
438 let in_dict = serialize_object(&Object::Dict(d), &mut out);
439 assert!(matches!(in_dict, Err(Error::NestedStream)));
440 }
441
442 #[test]
443 fn serialized_objects_parse_back_equal() {
444 let mut inner = Dict::new();
445 inner.insert(name("Z"), Object::Int(1));
446 inner.insert(
447 name("A"),
448 Object::Array(vec![Object::Real(0.5), Object::Null]),
449 );
450 let mut outer = Dict::new();
451 outer.insert(
452 name("Kids"),
453 Object::Array(vec![Object::Ref(ObjRef { num: 7, gen: 0 })]),
454 );
455 outer.insert(name("Inner"), Object::Dict(inner));
456 outer.insert(name("Weird Name#"), Object::String(b"a(b)\\c".to_vec()));
457
458 let objects = vec![
459 Object::Null,
460 Object::Bool(true),
461 Object::Bool(false),
462 Object::Int(-42),
463 Object::Real(2.5),
464 Object::Real(-0.125),
465 Object::String(b"a(b)\\c".to_vec()),
466 Object::String(b"tab\there".to_vec()),
467 Object::String(vec![0u8, 255, 128]),
468 Object::Name(name("Weird Name#\u{C4}")),
469 Object::Ref(ObjRef { num: 12, gen: 3 }),
470 Object::Array(vec![]),
471 Object::Dict(Dict::new()),
472 Object::Dict(outer),
473 ];
474 for obj in objects {
475 let mut out = Vec::new();
476 serialize_object(&obj, &mut out).expect("serializable object");
477 let parsed = Parser::new(&out)
478 .parse_object(&NoResolve)
479 .expect("serialized bytes parse");
480 assert_eq!(
481 parsed,
482 obj,
483 "round-trip of {}",
484 String::from_utf8_lossy(&out)
485 );
486 }
487 }
488
489 #[test]
490 fn integral_reals_parse_back_as_ints() {
491 let mut out = Vec::new();
492 serialize_object(&Object::Real(72.0), &mut out).expect("serializable object");
493 assert_eq!(out, b"72");
494 let parsed = Parser::new(&out)
495 .parse_object(&NoResolve)
496 .expect("serialized bytes parse");
497 assert_eq!(parsed, Object::Int(72));
498 }
499
500 #[test]
501 fn names_with_nul_bytes_are_rejected() {
502 let mut out = Vec::new();
503 let err = serialize_object(&Object::Name(name("a\0b")), &mut out)
504 .expect_err("a NUL in a name must not serialize");
505 assert!(err.to_string().contains("NUL"), "{err}");
506 let mut d = Dict::new();
507 d.insert(name("a\0b"), Object::Int(1));
508 let mut out = Vec::new();
509 assert!(serialize_dict(&d, &mut out).is_err());
510 }
511}