use std::io::{Error, Write};
use pdfgen_macros::const_identifiers;
use crate::{IdManager, ObjId, types::constants};
use super::{
content::{ContentStream, Operation, image::Image, text::Text},
page_tree::PageTree,
primitives::{font::Font, identifier::Identifier, rectangle::Rectangle, resources::Resources},
};
pub struct Page {
id: ObjId<Self>,
parent: ObjId<PageTree>,
resources: Resources,
media_box: Option<Rectangle>,
contents: ContentStream,
}
impl Page {
const_identifiers! {
PAGE,
PARENT,
RESOURCES,
MEDIA_BOX,
CONTENTS,
}
pub fn new(
id: ObjId<Self>,
contents_id: ObjId<ContentStream>,
parent: ObjId<PageTree>,
) -> Self {
Self {
id,
parent,
resources: Resources::default(),
media_box: None,
contents: ContentStream::new(contents_id),
}
}
pub fn set_mediabox(&mut self, media_box: impl Into<Rectangle>) {
self.media_box = Some(media_box.into());
}
pub fn obj_ref(&self) -> ObjId<Self> {
self.id.clone()
}
fn write_mediabox(writer: &mut dyn Write, rect: Rectangle) -> Result<usize, Error> {
Ok(pdfgen_macros::write_chain! {
Self::MEDIA_BOX.write(writer),
rect.write(writer),
})
}
pub fn add_image(&mut self, image: Image) {
let transform = image.transform();
let name = self.resources.add_image(image);
self.contents
.add_content(Operation::DrawImage { name, transform });
}
pub fn add_text(&mut self, text: Text, font_id: ObjId<Font>) {
let font_name = self.resources.add_font(font_id);
self.contents
.add_content(Operation::DrawText { text, font_name });
}
pub(crate) fn content_stream(&self) -> &ContentStream {
&self.contents
}
pub(crate) fn write(
&self,
writer: &mut dyn Write,
id_manager: &mut IdManager,
) -> Result<(usize, Vec<usize>), Error> {
let mut offsets = Vec::with_capacity(self.resources.entries.len());
let mut renderable_resources = self.resources.renderables(id_manager);
let written = pdfgen_macros::write_chain! {
self.id.write_def(writer),
writer.write(constants::NL_MARKER),
writer.write(b"<< "),
Identifier::TYPE.write(writer),
Self::PAGE.write(writer),
writer.write(constants::NL_MARKER),
Self::PARENT.write(writer),
self.parent.write_ref(writer),
writer.write(constants::NL_MARKER),
Self::RESOURCES.write(writer),
self.resources.write_dict(writer, &renderable_resources),
writer.write(constants::NL_MARKER),
if let Some(media_box) = self.media_box {
Self::write_mediabox(writer, media_box),
},
if !self.contents.is_empty() {
Self::CONTENTS.write(writer),
self.contents.obj_ref().write_ref(writer),
writer.write(constants::NL_MARKER),
},
writer.write(b">>"),
writer.write(constants::NL_MARKER),
writer.write(constants::END_OBJ_MARKER),
writer.write(constants::NL_MARKER),
writer.write(constants::NL_MARKER),
for renderable_entry in renderable_resources.iter_mut() {
{
offsets.push(written);
renderable_entry.write_def(writer)
}
},
writer.write(constants::NL_MARKER),
};
Ok((written, offsets))
}
}
#[cfg(test)]
mod tests {
use super::Page;
use crate::{IdManager, types::hierarchy::primitives::rectangle::Rectangle};
#[test]
fn basic_page() {
let mut id_manager = IdManager::new();
let mut page = Page::new(
id_manager.create_id(),
id_manager.create_id(),
id_manager.create_id(),
);
page.set_mediabox(Rectangle::from_units(0.0, 0.0, 100.0, 100.0));
let mut writer = Vec::new();
page.write(&mut writer, &mut id_manager).unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@r"
1 0 obj
<< /Type /Page
/Parent 3 0 R
/Resources << >>
/MediaBox [0 0 100 100]>>
endobj
"
);
}
}