use super::document::PdfDocument;
use super::object::{Dict, Object};
use crate::Result;
use std::collections::HashMap;
pub struct PdfSerializer<'a> {
doc: &'a PdfDocument,
}
impl<'a> PdfSerializer<'a> {
pub fn new(doc: &'a PdfDocument) -> Self {
PdfSerializer { doc }
}
pub fn serialize(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
out.extend_from_slice(&self.doc.version);
out.extend_from_slice(b"\n%\xe2\xe3\xcf\xd3\n");
let mut ids = self.doc.object_ids();
let encrypt_objid = match self.doc.trailer.get("Encrypt") {
Some(Object::Ref(objid, _)) => Some(*objid),
_ => None,
};
if let Some(eid) = encrypt_objid {
ids.retain(|&id| id != eid);
}
let maxobj = ids.iter().copied().max().unwrap_or(0);
let mut offsets: HashMap<u32, usize> = HashMap::new();
for &objid in &ids {
let obj = match self.doc.get_object(objid) {
Ok(o) => o,
Err(_) => continue, };
if obj.type_name() == Some("XRef") {
continue;
}
offsets.insert(objid, out.len());
out.extend_from_slice(format!("{} 0 obj", objid).as_bytes());
if obj.type_name() == Some("ObjStm") {
out.extend_from_slice(b"(deleted)");
} else {
self.write_object(&mut out, &obj)?;
}
if out
.last()
.map(|b| b.is_ascii_alphanumeric())
.unwrap_or(false)
{
out.push(b'\n');
}
out.extend_from_slice(b"endobj\n");
}
let startxref = out.len();
out.extend_from_slice(b"xref\n");
out.extend_from_slice(format!("0 {}\n", maxobj + 1).as_bytes());
for objid in 0..=maxobj {
match offsets.get(&objid) {
Some(&off) => {
out.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
}
None => {
out.extend_from_slice(b"0000000000 65535 f \n");
}
}
}
let mut trailer = self.doc.trailer.clone();
trailer.remove("Prev");
trailer.remove("XRefStm");
trailer.remove("Encrypt");
trailer.remove("DecodeParms");
trailer.remove("Filter");
trailer.remove("Index");
trailer.remove("W");
trailer.remove("Length");
trailer.remove("Type");
trailer.insert("Size".into(), Object::Int(maxobj as i64 + 1));
out.extend_from_slice(b"trailer\n");
self.write_object(&mut out, &Object::Dict(trailer))?;
out.extend_from_slice(format!("\nstartxref\n{}\n%%EOF", startxref).as_bytes());
Ok(out)
}
fn write_object(&self, out: &mut Vec<u8>, obj: &Object) -> Result<()> {
match obj {
Object::Null => out.extend_from_slice(b"null"),
Object::Bool(b) => {
sep_alnum(out);
out.extend_from_slice(if *b { b"true" } else { b"false" });
}
Object::Int(n) => {
sep_alnum(out);
out.extend_from_slice(n.to_string().as_bytes());
}
Object::Real(r) => {
sep_alnum(out);
out.extend_from_slice(format_real(*r).as_bytes());
}
Object::Str(s) => {
out.push(b'(');
out.extend_from_slice(&escape_string(s));
out.push(b')');
}
Object::Name(n) => {
out.push(b'/');
out.extend_from_slice(&escape_name(n));
}
Object::Array(a) => {
out.push(b'[');
for item in a {
self.write_object(out, item)?;
}
out.push(b']');
}
Object::Dict(d) => self.write_dict(out, d)?,
Object::Ref(objid, _) => {
sep_alnum(out);
out.extend_from_slice(format!("{} 0 R", objid).as_bytes());
}
Object::Stream(s) => {
self.write_dict(out, &s.dict)?;
out.extend_from_slice(b"stream\n");
out.extend_from_slice(&s.rawdata);
out.extend_from_slice(b"\nendstream");
}
Object::Keyword(k) => {
sep_alnum(out);
out.extend_from_slice(k);
}
}
Ok(())
}
fn write_dict(&self, out: &mut Vec<u8>, dict: &Dict) -> Result<()> {
out.extend_from_slice(b"<<");
for (key, val) in dict {
out.push(b'/');
out.extend_from_slice(&escape_name(key));
self.write_object(out, val)?;
}
out.extend_from_slice(b">>");
Ok(())
}
}
fn sep_alnum(out: &mut Vec<u8>) {
if out
.last()
.map(|b| b.is_ascii_alphanumeric())
.unwrap_or(false)
{
out.push(b' ');
}
}
fn escape_string(s: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(s.len());
for &b in s {
match b {
b'\\' => out.extend_from_slice(b"\\\\"),
b'(' => out.extend_from_slice(b"\\("),
b')' => out.extend_from_slice(b"\\)"),
b'\n' => out.extend_from_slice(b"\\n"),
b'\r' => out.extend_from_slice(b"\\r"),
_ => out.push(b),
}
}
out
}
fn escape_name(n: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(n.len());
for &b in n.as_bytes() {
if b.is_ascii_alphanumeric() {
out.push(b);
} else {
out.extend_from_slice(format!("#{:02x}", b).as_bytes());
}
}
out
}
fn format_real(r: f64) -> String {
if r == r.trunc() && r.abs() < 1e15 {
return format!("{}", r as i64);
}
let mut s = format!("{:.6}", r);
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
s
}