use crate::{IdManager, ObjId};
use super::{
constants,
hierarchy::{
catalog::Catalog, cross_reference_table::CrossReferenceTable, primitives::object::Object,
trailer::WriteTrailer,
},
page::Page,
};
use std::io::{self, Write};
pub struct PdfWriter<W: Write> {
inner: W,
current_offset: usize,
cross_reference_table: CrossReferenceTable,
}
impl<W: Write> PdfWriter<W> {
const PDF_HEADER: &[u8] = b"%PDF-2.0";
const EOF_MARKER: &[u8] = b"%%EOF";
pub fn new(inner: W) -> Self {
PdfWriter {
inner,
current_offset: 1,
cross_reference_table: CrossReferenceTable::default(),
}
}
pub fn write_header(&mut self) -> Result<(), io::Error> {
self.current_offset += self.inner.write(Self::PDF_HEADER)?;
self.current_offset += self.inner.write(constants::NL_MARKER)?;
Ok(())
}
pub(crate) fn write_object(&mut self, obj: &dyn Object) -> Result<(), io::Error> {
self.cross_reference_table.add_object(self.current_offset);
self.current_offset += obj.write_def(&mut self.inner)?;
self.current_offset += obj.write_content(&mut self.inner)?;
self.current_offset += obj.write_end(&mut self.inner)?;
self.current_offset += self.inner.write(constants::NL_MARKER)?;
Ok(())
}
pub fn write_crt(&mut self) -> Result<(), io::Error> {
self.cross_reference_table.write(&mut self.inner)?;
Ok(())
}
pub fn write_trailer(&mut self, root: ObjId<Catalog>) -> Result<(), io::Error> {
self.cross_reference_table.write_trailer(
&mut self.inner,
self.current_offset,
self.cross_reference_table.len(),
root,
self.cross_reference_table.offsets_hash()?,
)?;
Ok(())
}
pub fn write_eof(&mut self) -> Result<(), io::Error> {
self.inner.write_all(Self::EOF_MARKER)
}
pub(crate) fn write_page(
&mut self,
page: &Page,
id_manager: &mut IdManager,
) -> Result<(), io::Error> {
self.cross_reference_table.add_object(self.current_offset);
let (bytes_written, offsets) = page.write(&mut self.inner, id_manager)?;
for offset in offsets {
self.cross_reference_table
.add_object(self.current_offset + offset);
}
self.current_offset += bytes_written;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
IdManager, ObjId,
types::{constants, pdf_writer::PdfWriter},
};
use super::Object;
#[derive(Debug)]
struct Dummy(ObjId);
impl Object for Dummy {
fn write_def(&self, writer: &mut dyn std::io::Write) -> Result<usize, std::io::Error> {
Ok(pdfgen_macros::write_chain! {
self.0.write_def(writer),
writer.write(constants::NL_MARKER),
})
}
fn write_content(&self, writer: &mut dyn std::io::Write) -> Result<usize, std::io::Error> {
writer.write(b"FirstLine\nSecondLine\n")
}
}
#[test]
fn write_header() {
let mut writer = Vec::new();
let mut pdf_writer = PdfWriter::new(&mut writer);
pdf_writer.write_header().unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@"%PDF-2.0"
);
}
#[test]
fn write_eof() {
let mut writer = Vec::new();
let mut pdf_writer = PdfWriter::new(&mut writer);
pdf_writer.write_eof().unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@"%%EOF"
);
}
#[test]
fn write_object() {
let mut writer = Vec::new();
let mut pdf_writer = PdfWriter::new(&mut writer);
let mut id_manager = IdManager::new();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@r"
1 0 obj
FirstLine
SecondLine
endobj
"
);
}
#[test]
fn write_crt() {
let mut writer = Vec::new();
let mut pdf_writer = PdfWriter::new(&mut writer);
let mut id_manager = IdManager::new();
pdf_writer.write_header().unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
pdf_writer.write_crt().unwrap();
pdf_writer.write_eof().unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@r"
%PDF-2.0
1 0 obj
FirstLine
SecondLine
endobj
2 0 obj
FirstLine
SecondLine
endobj
3 0 obj
FirstLine
SecondLine
endobj
4 0 obj
FirstLine
SecondLine
endobj
xref
0 4
0000000010 00000 n
0000000047 00000 n
0000000084 00000 n
0000000121 00000 n
%%EOF
"
);
}
#[test]
fn write_trailer() {
let mut writer = Vec::new();
let mut pdf_writer = PdfWriter::new(&mut writer);
let mut id_manager = IdManager::new();
pdf_writer.write_header().unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
let dummy = Dummy(id_manager.create_id());
pdf_writer.write_object(&dummy).unwrap();
pdf_writer.write_crt().unwrap();
pdf_writer.write_trailer(id_manager.create_id()).unwrap();
pdf_writer.write_eof().unwrap();
let output = String::from_utf8(writer).unwrap();
insta::assert_snapshot!(
output,
@r"
%PDF-2.0
1 0 obj
FirstLine
SecondLine
endobj
2 0 obj
FirstLine
SecondLine
endobj
3 0 obj
FirstLine
SecondLine
endobj
4 0 obj
FirstLine
SecondLine
endobj
xref
0 4
0000000010 00000 n
0000000047 00000 n
0000000084 00000 n
0000000121 00000 n
trailer
<< /Size 4
/Root 5 0 R
/ID [<ffb2e086bea707d8d867d4a23074276b>
<ffb2e086bea707d8d867d4a23074276b>
]
>>
startxref
158
%%EOF
"
);
}
}