use crate::encrypt::Encryption;
use crate::form::AcroForm;
use crate::signature::{SignatureField, SignaturePlaceholder};
use crate::watermark::{HeaderFooter, Watermark, WatermarkLayer};
use crate::object::{ObjRef, PdfDict, PdfObject, PdfStream};
use crate::outline::Outline;
use crate::page::PageBuilder;
use crate::writer::PdfWriter;
pub struct LinkAnnotation {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub url: String,
}
impl LinkAnnotation {
pub fn new(x: f64, y: f64, width: f64, height: f64, url: impl Into<String>) -> Self {
Self { x, y, width, height, url: url.into() }
}
}
struct BuiltPage {
builder: PageBuilder,
links: Vec<LinkAnnotation>,
}
#[derive(Default)]
pub struct DocInfo {
pub title: Option<String>,
pub author: Option<String>,
pub subject: Option<String>,
pub keywords: Option<String>,
pub creator: String,
}
pub struct Document {
pages: Vec<BuiltPage>,
info: DocInfo,
outline: Option<Outline>,
form: Option<AcroForm>,
initial_page: usize,
watermark: Option<Watermark>,
header: Option<HeaderFooter>,
footer: Option<HeaderFooter>,
encryption: Option<Encryption>,
signature_fields: Vec<SignatureField>,
}
impl Document {
pub fn new() -> Self {
Self {
pages: Vec::new(),
info: DocInfo { creator: "pdfox 0.1.0".into(), ..Default::default() },
outline: None,
form: None,
initial_page: 0,
watermark: None,
header: None,
footer: None,
encryption: None,
signature_fields: Vec::new(),
}
}
pub fn title(mut self, t: impl Into<String>) -> Self {
self.info.title = Some(t.into());
self
}
pub fn author(mut self, a: impl Into<String>) -> Self {
self.info.author = Some(a.into());
self
}
pub fn subject(mut self, s: impl Into<String>) -> Self {
self.info.subject = Some(s.into());
self
}
pub fn keywords(mut self, k: impl Into<String>) -> Self {
self.info.keywords = Some(k.into());
self
}
pub fn open_at(mut self, page: usize) -> Self {
self.initial_page = page;
self
}
pub fn outline(mut self, outline: Outline) -> Self {
self.outline = Some(outline);
self
}
pub fn form(mut self, form: AcroForm) -> Self {
self.form = Some(form);
self
}
pub fn page<F>(mut self, f: F) -> Self
where F: FnOnce(&mut PageBuilder) {
let mut builder = PageBuilder::a4();
f(&mut builder);
self.pages.push(BuiltPage { builder, links: Vec::new() });
self
}
pub fn page_sized<F>(mut self, width: f64, height: f64, f: F) -> Self
where F: FnOnce(&mut PageBuilder) {
let mut builder = PageBuilder::new(width, height);
f(&mut builder);
self.pages.push(BuiltPage { builder, links: Vec::new() });
self
}
pub fn page_with_links<F>(mut self, f: F, links: Vec<LinkAnnotation>) -> Self
where F: FnOnce(&mut PageBuilder) {
let mut builder = PageBuilder::a4();
f(&mut builder);
self.pages.push(BuiltPage { builder, links });
self
}
pub fn add_pages(mut self, builders: Vec<PageBuilder>) -> Self {
for builder in builders {
self.pages.push(BuiltPage { builder, links: Vec::new() });
}
self
}
pub fn watermark(mut self, w: Watermark) -> Self { self.watermark = Some(w); self }
pub fn header(mut self, h: HeaderFooter) -> Self { self.header = Some(h); self }
pub fn footer(mut self, f: HeaderFooter) -> Self { self.footer = Some(f); self }
pub fn encrypt(mut self, e: Encryption) -> Self { self.encryption = Some(e); self }
pub fn signature(mut self, s: SignatureField) -> Self {
self.signature_fields.push(s); self
}
pub fn build(self) -> Vec<u8> {
self.build_inner().0
}
pub fn build_signed(self) -> (Vec<u8>, Vec<SignaturePlaceholder>) {
self.build_inner()
}
fn build_inner(self) -> (Vec<u8>, Vec<SignaturePlaceholder>) {
let total_pages = self.pages.len();
let mut writer = PdfWriter::new();
writer.write_header();
let catalog_ref = writer.reserve();
let pages_ref = writer.reserve();
let info_ref = writer.reserve();
writer.write_object(info_ref, &PdfObject::Dictionary(self.build_info_dict()));
let mut page_refs: Vec<ObjRef> = Vec::new();
let mut form_widget_placement: Vec<(usize, ObjRef)> = Vec::new();
for _ in &self.pages {
page_refs.push(writer.reserve());
}
let mut acroform_dict: Option<PdfDict> = None;
if let Some(ref form) = self.form {
if !form.is_empty() {
let (afd, placements) = form.write(&mut writer, &page_refs);
acroform_dict = Some(afd);
form_widget_placement = placements;
}
}
let mut sig_widget_refs: Vec<(usize, ObjRef)> = Vec::new();
let mut sig_field_refs: Vec<ObjRef> = Vec::new();
let mut placeholders: Vec<SignaturePlaceholder> = Vec::new();
for sig in &self.signature_fields {
let (widget_ref, field_ref, ph) = sig.write(&mut writer, &page_refs);
let page_idx = sig.appearance.as_ref().map_or(0, |a| a.page);
sig_widget_refs.push((page_idx, widget_ref));
sig_field_refs.push(field_ref);
placeholders.push(ph);
}
for (page_idx, built_page) in self.pages.into_iter().enumerate() {
let page_ref = page_refs[page_idx];
let BuiltPage { builder, links } = built_page;
let page_w = builder.width;
let page_h = builder.height;
let (content_bytes, images, mut resources) = builder.finish();
let mut content_refs: Vec<ObjRef> = Vec::new();
if let Some(ref wm) = self.watermark {
let mut font_dict = match resources.get("Font") {
Some(PdfObject::Dictionary(d)) => d.clone(),
_ => PdfDict::new(),
};
font_dict.set("WmF1Reg", PdfObject::Dictionary(wm.font_resource()));
resources.set("Font", PdfObject::Dictionary(font_dict));
let mut extgs = PdfDict::new();
extgs.set("WmGS", PdfObject::Dictionary(wm.ext_gstate()));
resources.set("ExtGState", PdfObject::Dictionary(extgs));
if matches!(wm.layer, WatermarkLayer::Behind) {
let wm_bytes = wm.render_to_stream(page_w, page_h);
content_refs.push(writer.add_stream(PdfStream::new_compressed(wm_bytes)));
}
}
content_refs.push(writer.add_stream(PdfStream::new_compressed(content_bytes)));
if let Some(ref wm) = self.watermark {
if matches!(wm.layer, WatermarkLayer::Over) {
let wm_bytes = wm.render_to_stream(page_w, page_h);
content_refs.push(writer.add_stream(PdfStream::new_compressed(wm_bytes)));
}
}
if let Some(ref hf) = self.header {
let mut font_dict = match resources.get("Font") {
Some(PdfObject::Dictionary(d)) => d.clone(),
_ => PdfDict::new(),
};
for (key, fdict) in hf.font_resources() {
font_dict.set(key, PdfObject::Dictionary(fdict));
}
resources.set("Font", PdfObject::Dictionary(font_dict));
let hf_bytes = hf.render(page_w, page_h, page_idx + 1, total_pages, true);
content_refs.push(writer.add_stream(PdfStream::new_compressed(hf_bytes)));
}
if let Some(ref hf) = self.footer {
let mut font_dict = match resources.get("Font") {
Some(PdfObject::Dictionary(d)) => d.clone(),
_ => PdfDict::new(),
};
for (key, fdict) in hf.font_resources() {
font_dict.set(key, PdfObject::Dictionary(fdict));
}
resources.set("Font", PdfObject::Dictionary(font_dict));
let hf_bytes = hf.render(page_w, page_h, page_idx + 1, total_pages, false);
content_refs.push(writer.add_stream(PdfStream::new_compressed(hf_bytes)));
}
if !images.is_empty() {
let mut xobj_dict = PdfDict::new();
for (key, img) in images {
let img_ref = writer.add_stream(img.to_xobject_stream());
xobj_dict.set(key, PdfObject::Reference(img_ref));
}
resources.set("XObject", PdfObject::Dictionary(xobj_dict));
}
let mut annot_refs: Vec<ObjRef> = Vec::new();
for link in &links {
let annot = build_link_annotation(link, page_ref);
annot_refs.push(writer.add_object(PdfObject::Dictionary(annot)));
}
for &(fidx, fref) in &form_widget_placement {
if fidx == page_idx { annot_refs.push(fref); }
}
for &(pidx, wref) in &sig_widget_refs {
if pidx == page_idx { annot_refs.push(wref); }
}
let mut page_dict = PdfDict::new();
page_dict.set("Type", PdfObject::name("Page"));
page_dict.set("Parent", PdfObject::Reference(pages_ref));
page_dict.set("MediaBox", PdfObject::Array(vec![
PdfObject::Integer(0), PdfObject::Integer(0),
PdfObject::Real(page_w), PdfObject::Real(page_h),
]));
let contents_val = if content_refs.len() == 1 {
PdfObject::Reference(content_refs[0])
} else {
PdfObject::Array(content_refs.iter().map(|r| PdfObject::Reference(*r)).collect())
};
page_dict.set("Contents", contents_val);
page_dict.set("Resources", PdfObject::Dictionary(resources));
if !annot_refs.is_empty() {
let refs: Vec<PdfObject> = annot_refs.iter().map(|r| PdfObject::Reference(*r)).collect();
page_dict.set("Annots", PdfObject::Array(refs));
}
writer.write_object(page_ref, &PdfObject::Dictionary(page_dict));
}
let mut pages_dict = PdfDict::new();
pages_dict.set("Type", PdfObject::name("Pages"));
pages_dict.set("Count", PdfObject::Integer(page_refs.len() as i64));
pages_dict.set("Kids", PdfObject::Array(
page_refs.iter().map(|r| PdfObject::Reference(*r)).collect()
));
writer.write_object(pages_ref, &PdfObject::Dictionary(pages_dict));
let outline_ref = self.outline.as_ref().and_then(|o| {
if o.is_empty() { None } else { Some(o.write(&mut writer, &page_refs)) }
});
let mut catalog = PdfDict::new();
catalog.set("Type", PdfObject::name("Catalog"));
catalog.set("Pages", PdfObject::Reference(pages_ref));
if let Some(oref) = outline_ref {
catalog.set("Outlines", PdfObject::Reference(oref));
catalog.set("PageMode", PdfObject::name("UseOutlines"));
}
let mut all_fields: Vec<PdfObject> = Vec::new();
if let Some(mut afd) = acroform_dict {
if let Some(PdfObject::Array(existing)) = afd.get("Fields").cloned() {
all_fields.extend(existing);
}
for &fref in &sig_field_refs {
all_fields.push(PdfObject::Reference(fref));
}
afd.set("Fields", PdfObject::Array(all_fields.clone()));
catalog.set("AcroForm", PdfObject::Dictionary(afd));
} else if !sig_field_refs.is_empty() {
let mut afd = PdfDict::new();
afd.set("Fields", PdfObject::Array(
sig_field_refs.iter().map(|r| PdfObject::Reference(*r)).collect()
));
afd.set("SigFlags", PdfObject::Integer(3)); catalog.set("AcroForm", PdfObject::Dictionary(afd));
}
if self.initial_page > 0 {
if let Some(&dest_page) = page_refs.get(self.initial_page) {
catalog.set("OpenAction", PdfObject::Array(vec![
PdfObject::Reference(dest_page),
PdfObject::name("Fit"),
]));
}
}
let mut vp = PdfDict::new();
vp.set("HideToolbar", PdfObject::Boolean(false));
vp.set("FitWindow", PdfObject::Boolean(true));
catalog.set("ViewerPreferences", PdfObject::Dictionary(vp));
writer.write_object(catalog_ref, &PdfObject::Dictionary(catalog));
let enc_ref = self.encryption.as_ref().map(|enc| {
let doc_id: Vec<u8> = (0u8..16).map(|i| i ^ (catalog_ref.id as u8)).collect();
let enc_dict = enc.build_encrypt_dict(&doc_id);
writer.add_object(PdfObject::Dictionary(enc_dict))
});
let pdf_bytes = writer.finalize_with_encrypt(catalog_ref, Some(info_ref), enc_ref);
(pdf_bytes, placeholders)
}
fn build_info_dict(&self) -> PdfDict {
let mut d = PdfDict::new();
d.set("Creator", PdfObject::string(self.info.creator.as_str()));
if let Some(ref t) = self.info.title { d.set("Title", PdfObject::string(t.as_str())); }
if let Some(ref a) = self.info.author { d.set("Author", PdfObject::string(a.as_str())); }
if let Some(ref s) = self.info.subject { d.set("Subject", PdfObject::string(s.as_str())); }
if let Some(ref k) = self.info.keywords { d.set("Keywords", PdfObject::string(k.as_str())); }
d
}
}
fn build_link_annotation(link: &LinkAnnotation, _page_ref: ObjRef) -> PdfDict {
let mut dict = PdfDict::new();
dict.set("Type", PdfObject::name("Annot"));
dict.set("Subtype", PdfObject::name("Link"));
dict.set("Rect", PdfObject::Array(vec![
PdfObject::Real(link.x),
PdfObject::Real(link.y),
PdfObject::Real(link.x + link.width),
PdfObject::Real(link.y + link.height),
]));
dict.set("Border", PdfObject::Array(vec![
PdfObject::Integer(0), PdfObject::Integer(0), PdfObject::Integer(0),
]));
let mut action = PdfDict::new();
action.set("Type", PdfObject::name("Action"));
action.set("S", PdfObject::name("URI"));
action.set("URI", PdfObject::string(link.url.as_str()));
dict.set("A", PdfObject::Dictionary(action));
dict
}