use pdfboss_core::{Dict, Document, FastMap, Name, ObjRef, Object, Rect, Stream};
use crate::error::{Error, Result};
use crate::update::{core_error, deflate};
use crate::writer::Writer;
pub struct Importer<'w, 's> {
writer: &'w mut Writer,
source: &'s Document,
map: FastMap<ObjRef, ObjRef>,
pending: Vec<ObjRef>,
substitutions: FastMap<ObjRef, Object>,
compress: bool,
}
impl<'w, 's> Importer<'w, 's> {
pub fn new(writer: &'w mut Writer, source: &'s Document) -> Result<Importer<'w, 's>> {
if source.is_locked() {
return Err(Error::EncryptedBase);
}
let compress = writer.compress();
Ok(Importer {
writer,
source,
map: FastMap::default(),
pending: Vec::new(),
substitutions: FastMap::default(),
compress,
})
}
pub fn reference(&mut self, r: ObjRef) -> ObjRef {
if let Some(copied) = self.map.get(&r) {
return *copied;
}
let copied = self.writer.reserve();
self.map.insert(r, copied);
self.pending.push(r);
copied
}
pub fn copy(&mut self, obj: &Object) -> Result<Object> {
Ok(match obj {
Object::Ref(r) => Object::Ref(self.reference(*r)),
Object::Dict(d) => Object::Dict(self.copy_dict(d)?),
Object::Array(items) => Object::Array(
items
.iter()
.map(|item| self.copy(item))
.collect::<Result<Vec<Object>>>()?,
),
Object::Stream(_) => return Err(Error::NestedStream),
other => other.clone(),
})
}
pub(crate) fn copy_dict(&mut self, dict: &Dict) -> Result<Dict> {
let mut out = Dict::new();
for (key, value) in dict.iter() {
out.insert(key.clone(), self.copy(value)?);
}
Ok(out)
}
fn copy_stream(&mut self, stream: &Stream) -> Result<Object> {
let mut dict = stream.dict.clone();
dict.remove("Length");
let mut dict = self.copy_dict(&dict)?;
let data = if self.compress && dict.get("Filter").is_none() {
dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
deflate(&stream.data)
} else {
stream.data.clone()
};
Ok(Object::Stream(Stream { dict, data }))
}
pub fn substitute(&mut self, r: ObjRef, body: Object) {
self.substitutions.insert(r, body);
}
pub fn finish(&mut self) -> Result<()> {
while let Some(r) = self.pending.pop() {
let target = self.map[&r];
let body = match self.substitutions.remove(&r) {
Some(body) => body,
None => match self.source.get(r).map_err(core_error)? {
Object::Stream(s) => self.copy_stream(&s)?,
other => self.copy(&other)?,
},
};
self.writer.fill(target, body)?;
}
Ok(())
}
pub fn document(&mut self) -> Result<ObjRef> {
let root = self
.source
.xref()
.trailer
.get_ref("Root")
.ok_or(Error::MissingRoot)?;
let new_root = self.reference(root);
self.finish()?;
Ok(new_root)
}
pub fn page(&mut self, index: usize, parent: ObjRef) -> Result<ObjRef> {
let page = self.source.page(index).map_err(core_error)?;
let mut dict = page.dict().clone();
dict.remove("Parent");
dict.insert(name("Type"), Object::Name(name("Page")));
dict.insert(name("Resources"), Object::Dict(page.resources.clone()));
dict.insert(name("MediaBox"), rect_array(page.media_box));
if page.rotate != 0 {
dict.insert(name("Rotate"), Object::Int(i64::from(page.rotate)));
}
if page.crop_box != page.media_box {
dict.insert(name("CropBox"), rect_array(page.crop_box));
}
let mut translated = self.copy_dict(&dict)?;
translated.insert(name("Parent"), Object::Ref(parent));
let target = match page.object_ref() {
Some(r) if !self.map.contains_key(&r) => {
let target = self.reference(r);
self.substitute(r, Object::Dict(translated));
target
}
_ => {
let target = self.writer.reserve();
self.writer.fill(target, Object::Dict(translated))?;
target
}
};
self.finish()?;
Ok(target)
}
}
pub(crate) fn rect_array(rect: Rect) -> Object {
Object::Array(
[rect.x0, rect.y0, rect.x1, rect.y1]
.iter()
.map(|v| Object::Real(widen(*v)))
.collect(),
)
}
fn widen(value: f32) -> f64 {
value
.to_string()
.parse()
.expect("a float's own Display output parses back as a float")
}
fn name(text: &str) -> Name {
Name(text.to_string())
}
#[cfg(test)]
mod tests {
use pdfboss_core::Rect;
use super::*;
use crate::ser::serialize_object;
fn real_at(array: &Object, index: usize) -> String {
let Object::Array(items) = array else {
panic!("expected an array, got {array:?}");
};
let mut out = Vec::new();
serialize_object(&items[index], &mut out).expect("a real serializes");
String::from_utf8(out).expect("real syntax is ASCII")
}
#[test]
fn rect_array_keeps_the_f32_shortest_decimal() {
let rect = Rect::new(0.1, 0.0, 300.0, 400.0);
let array = rect_array(rect);
assert_eq!(real_at(&array, 0), "0.1");
}
}