use oxideav_core::vector::VectorFrame;
use crate::objects::{Dict, Document, Object, ObjectId, Stream};
use crate::resources::ResourceCollector;
pub struct PageBuild {
pub page_id: ObjectId,
pub pages_tree_id: ObjectId,
pub catalog_id: ObjectId,
}
pub struct PagesBuild {
pub catalog_id: ObjectId,
pub pages_tree_id: ObjectId,
pub page_ids: Vec<ObjectId>,
}
pub struct PageInput<'a> {
pub width: f32,
pub height: f32,
pub content_bytes: Vec<u8>,
pub resources: ResourceCollector,
pub frame: &'a VectorFrame,
}
pub fn build_page(
doc: &mut Document,
frame: &VectorFrame,
content_bytes: Vec<u8>,
resources: &ResourceCollector,
) -> PageBuild {
let catalog_id = doc.allocate_id();
let pages_id = doc.allocate_id();
let page_id = doc.allocate_id();
let resources_id = doc.allocate_id();
let contents_id = doc.allocate_id();
let media_box = media_box_array(frame.width, frame.height);
doc.add_object(
catalog_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Catalog".into()))
.with("Pages", Object::Reference(pages_id)),
),
);
doc.add_object(
pages_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Pages".into()))
.with("Kids", Object::Array(vec![Object::Reference(page_id)]))
.with("Count", Object::Integer(1)),
),
);
doc.add_object(
page_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Page".into()))
.with("Parent", Object::Reference(pages_id))
.with("MediaBox", media_box)
.with("Resources", Object::Reference(resources_id))
.with("Contents", Object::Reference(contents_id)),
),
);
let res_obj = resources.flatten_into_resources_dict(doc);
doc.add_object(resources_id, res_obj);
let mut content_dict = Dict::new();
let _ = &mut content_dict; doc.add_object(
contents_id,
Object::Stream(Stream::new(content_dict, content_bytes)),
);
doc.root = Some(catalog_id);
PageBuild {
page_id,
pages_tree_id: pages_id,
catalog_id,
}
}
pub fn build_pages(doc: &mut Document, inputs: Vec<PageInput<'_>>) -> PagesBuild {
let catalog_id = doc.allocate_id();
let pages_id = doc.allocate_id();
let page_ids: Vec<ObjectId> = (0..inputs.len()).map(|_| doc.allocate_id()).collect();
doc.add_object(
catalog_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Catalog".into()))
.with("Pages", Object::Reference(pages_id)),
),
);
let kids = page_ids.iter().map(|id| Object::Reference(*id)).collect();
doc.add_object(
pages_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Pages".into()))
.with("Kids", Object::Array(kids))
.with("Count", Object::Integer(page_ids.len() as i64)),
),
);
for (page_id, input) in page_ids.iter().zip(inputs) {
let resources_id = doc.allocate_id();
let contents_id = doc.allocate_id();
let media_box = media_box_array(input.width, input.height);
doc.add_object(
*page_id,
Object::Dict(
Dict::new()
.with("Type", Object::Name("Page".into()))
.with("Parent", Object::Reference(pages_id))
.with("MediaBox", media_box)
.with("Resources", Object::Reference(resources_id))
.with("Contents", Object::Reference(contents_id)),
),
);
let res_obj = input.resources.flatten_into_resources_dict(doc);
doc.add_object(resources_id, res_obj);
doc.add_object(
contents_id,
Object::Stream(Stream::new(Dict::new(), input.content_bytes)),
);
}
doc.root = Some(catalog_id);
PagesBuild {
catalog_id,
pages_tree_id: pages_id,
page_ids,
}
}
fn media_box_array(width: f32, height: f32) -> Object {
Object::Array(vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(width as f64),
Object::Real(height as f64),
])
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::time::TimeBase;
use oxideav_core::vector::Group;
fn empty_frame(w: f32, h: f32) -> VectorFrame {
VectorFrame {
width: w,
height: h,
view_box: None,
root: Group::default(),
pts: None,
time_base: TimeBase::new(1, 1),
}
}
#[test]
fn build_page_assembles_catalog_pages_page_resources_contents() {
let frame = empty_frame(100.0, 50.0);
let mut doc = Document::new();
let res = ResourceCollector::new();
let pb = build_page(&mut doc, &frame, b"% empty\n".to_vec(), &res);
assert_eq!(pb.catalog_id.number, 1);
assert_eq!(pb.pages_tree_id.number, 2);
assert_eq!(pb.page_id.number, 3);
assert_eq!(doc.object_count(), 5);
}
#[test]
fn build_pages_emits_one_page_per_input() {
let f1 = empty_frame(200.0, 100.0);
let f2 = empty_frame(300.0, 400.0);
let inputs = vec![
PageInput {
width: f1.width,
height: f1.height,
content_bytes: b"% page 1\n".to_vec(),
resources: ResourceCollector::new(),
frame: &f1,
},
PageInput {
width: f2.width,
height: f2.height,
content_bytes: b"% page 2\n".to_vec(),
resources: ResourceCollector::new(),
frame: &f2,
},
];
let mut doc = Document::new();
let pb = build_pages(&mut doc, inputs);
assert_eq!(pb.page_ids.len(), 2);
assert_eq!(doc.object_count(), 8);
let mut bytes = Vec::new();
doc.write_to(&mut bytes).unwrap();
let s = String::from_utf8_lossy(&bytes);
assert!(s.contains("/MediaBox [0 0 200 100]"));
assert!(s.contains("/MediaBox [0 0 300 400]"));
assert!(s.contains("/Count 2"));
}
}