use pdf_writer::types::{ActionType, AnnotationType, FunctionShadingType};
use pdf_writer::{Filter, Finish, Pdf, Rect as PdfRect, Ref, Str};
use zenith_core::{AssetProvider, FontProvider};
use zenith_scene::Scene;
use super::content::{
ALPHA_PREFIX, FONT_PREFIX, IMAGE_PREFIX, LinkAnnot, PageResources, SHADING_PREFIX, name,
translate,
};
use super::font::{self, FontPlan};
use super::gradient::AxialGradient;
#[derive(Clone, Copy)]
pub struct PdfOptions {
pub subset: bool,
}
impl Default for PdfOptions {
fn default() -> Self {
Self { subset: true }
}
}
#[must_use]
pub fn render_pdf(scene: &Scene, fonts: &dyn FontProvider, assets: &dyn AssetProvider) -> Vec<u8> {
render_pdf_multi(std::slice::from_ref(scene), fonts, assets)
}
#[must_use]
pub fn render_pdf_with(
scene: &Scene,
fonts: &dyn FontProvider,
assets: &dyn AssetProvider,
options: PdfOptions,
) -> Vec<u8> {
render_pdf_multi_with(std::slice::from_ref(scene), fonts, assets, options)
}
#[must_use]
pub fn render_pdf_multi(
scenes: &[Scene],
fonts: &dyn FontProvider,
assets: &dyn AssetProvider,
) -> Vec<u8> {
render_pdf_multi_with(scenes, fonts, assets, PdfOptions::default())
}
#[must_use]
pub fn render_pdf_multi_with(
scenes: &[Scene],
fonts: &dyn FontProvider,
assets: &dyn AssetProvider,
options: PdfOptions,
) -> Vec<u8> {
let mut pdf = Pdf::new();
let catalog_id = Ref::new(1);
let page_tree_id = Ref::new(2);
let usage = font::collect_usage(scenes);
let font_plan = font::build_plan(&usage, fonts, options.subset);
let font_base: i32 = 3;
let mut next: i32 = font_base + (font_plan.fonts.len() as i32) * font::REFS_PER_FONT;
let mut alloc = || {
let r = Ref::new(next);
next += 1;
r
};
let mut pages: Vec<PreparedPage<'_>> = Vec::with_capacity(scenes.len());
for scene in scenes {
pages.push(prepare_page(scene, fonts, assets, &font_plan, &mut alloc));
}
pdf.catalog(catalog_id).pages(page_tree_id);
pdf.pages(page_tree_id)
.kids(pages.iter().map(|p| p.page_id))
.count(pages.len() as i32);
for (idx, font) in font_plan.fonts.iter().enumerate() {
let refs = font::font_refs_at(font_base, idx);
font::write_font(&mut pdf, font, &refs);
}
for prepared in pages {
write_prepared_page(&mut pdf, page_tree_id, prepared);
}
pdf.finish()
}
struct PreparedPage<'a> {
page_id: Ref,
content_id: Ref,
content: Vec<u8>,
scene: &'a Scene,
res: PageResources,
annot_ids: Vec<Ref>,
alpha_ids: Vec<Ref>,
gradient_refs: Vec<GradientRefs>,
image_refs: Vec<ImageRefs>,
}
fn prepare_page<'a>(
scene: &'a Scene,
fonts: &dyn FontProvider,
assets: &dyn AssetProvider,
font_plan: &FontPlan,
alloc: &mut impl FnMut() -> Ref,
) -> PreparedPage<'a> {
let page_id = alloc();
let content_id = alloc();
let (content, res) = translate(scene, fonts, assets, font_plan);
let annot_ids: Vec<Ref> = res.links.iter().map(|_| alloc()).collect();
let alpha_ids: Vec<Ref> = res.alphas.iter().map(|_| alloc()).collect();
let gradient_refs: Vec<GradientRefs> = res
.gradients
.iter()
.map(|g| {
let shading = alloc();
let function = alloc();
let seg_count = g.stops.len().saturating_sub(1);
let sub_functions = if g.stops.len() > 2 {
(0..seg_count).map(|_| alloc()).collect()
} else {
Vec::new()
};
GradientRefs {
shading,
function,
sub_functions,
}
})
.collect();
let image_refs: Vec<ImageRefs> = res
.images
.iter()
.map(|img| ImageRefs {
image: alloc(),
smask: if img.alpha_flate.is_some() {
Some(alloc())
} else {
None
},
})
.collect();
PreparedPage {
page_id,
content_id,
content: content.finish().into_vec(),
scene,
res,
annot_ids,
alpha_ids,
gradient_refs,
image_refs,
}
}
fn write_prepared_page(pdf: &mut Pdf, page_tree_id: Ref, prepared: PreparedPage<'_>) {
let PreparedPage {
page_id,
content_id,
content,
scene,
res,
annot_ids,
alpha_ids,
gradient_refs,
image_refs,
} = prepared;
write_page(
pdf,
PageWrite {
page_id,
page_tree_id,
content_id,
scene,
res: &res,
annot_ids: &annot_ids,
alpha_ids: &alpha_ids,
gradient_refs: &gradient_refs,
image_refs: &image_refs,
},
);
pdf.stream(content_id, &content);
write_link_annotations(pdf, scene, &res.links, &annot_ids);
write_alpha_states(pdf, &res, &alpha_ids);
write_gradients(pdf, &res, &gradient_refs);
write_images(pdf, &res, &image_refs);
}
fn write_link_annotations(pdf: &mut Pdf, scene: &Scene, links: &[LinkAnnot], annot_ids: &[Ref]) {
let h = scene.height as f32;
for (link, id) in links.iter().zip(annot_ids) {
let x0 = link.x0 as f32;
let x1 = link.x1 as f32;
let y_top = h - link.y0 as f32;
let y_bottom = h - link.y1 as f32;
let mut annot = pdf.annotation(*id);
annot.subtype(AnnotationType::Link);
annot.rect(PdfRect::new(x0, y_bottom, x1, y_top));
annot.border(0.0, 0.0, 0.0, None);
annot
.action()
.action_type(ActionType::Uri)
.uri(Str(link.url.as_bytes()));
annot.finish();
}
}
struct GradientRefs {
shading: Ref,
function: Ref,
sub_functions: Vec<Ref>,
}
struct ImageRefs {
image: Ref,
smask: Option<Ref>,
}
#[derive(Clone, Copy)]
struct PageWrite<'a> {
page_id: Ref,
page_tree_id: Ref,
content_id: Ref,
scene: &'a Scene,
res: &'a PageResources,
annot_ids: &'a [Ref],
alpha_ids: &'a [Ref],
gradient_refs: &'a [GradientRefs],
image_refs: &'a [ImageRefs],
}
fn write_page(pdf: &mut Pdf, ctx: PageWrite<'_>) {
let PageWrite {
page_id,
page_tree_id,
content_id,
scene,
res,
annot_ids,
alpha_ids,
gradient_refs,
image_refs,
} = ctx;
let w = scene.width as f32;
let h = scene.height as f32;
let media = PdfRect::new(0.0, 0.0, w, h);
let mut page = pdf.page(page_id);
page.parent(page_tree_id);
page.media_box(media);
match scene.trim {
Some(t) => {
let x0 = t.x as f32;
let x1 = (t.x + t.w) as f32;
let y0 = (scene.height - (t.y + t.h)) as f32;
let y1 = (scene.height - t.y) as f32;
page.trim_box(PdfRect::new(x0, y0, x1, y1));
page.bleed_box(media);
page.crop_box(media);
}
None => {
page.trim_box(media);
page.bleed_box(media);
page.crop_box(media);
}
}
page.contents(content_id);
if !annot_ids.is_empty() {
page.annotations(annot_ids.iter().copied());
}
let mut resources = page.resources();
if !res.font_indices.is_empty() {
let mut fonts = resources.fonts();
for &idx in &res.font_indices {
let nm = name(FONT_PREFIX, idx);
fonts.pair(nm.as_name(), font::font_refs_at(3, idx).type0_ref());
}
fonts.finish();
}
if !res.alphas.is_empty() {
let mut gs = resources.ext_g_states();
for (i, r) in alpha_ids.iter().enumerate() {
let nm = name(ALPHA_PREFIX, i);
gs.pair(nm.as_name(), *r);
}
gs.finish();
}
if !res.gradients.is_empty() {
let mut sh = resources.shadings();
for (i, gr) in gradient_refs.iter().enumerate() {
let nm = name(SHADING_PREFIX, i);
sh.pair(nm.as_name(), gr.shading);
}
sh.finish();
}
if !res.images.is_empty() {
let mut xo = resources.x_objects();
for (i, ir) in image_refs.iter().enumerate() {
let nm = name(IMAGE_PREFIX, i);
xo.pair(nm.as_name(), ir.image);
}
xo.finish();
}
resources.finish();
page.finish();
}
fn write_alpha_states(pdf: &mut Pdf, res: &PageResources, alpha_ids: &[Ref]) {
for (a, r) in res.alphas.iter().zip(alpha_ids) {
let factor = f32::from(*a) / 255.0;
let mut gs = pdf.ext_graphics(*r);
gs.non_stroking_alpha(factor);
gs.stroking_alpha(factor);
gs.finish();
}
}
fn write_gradients(pdf: &mut Pdf, res: &PageResources, refs: &[GradientRefs]) {
for (g, gr) in res.gradients.iter().zip(refs) {
write_gradient_function(pdf, gr, g);
let mut shading = pdf.function_shading(gr.shading);
shading.shading_type(FunctionShadingType::Axial);
shading.color_space().device_rgb();
shading.coords(g.coords);
shading.function(gr.function);
shading.extend([true, true]);
shading.finish();
}
}
fn write_gradient_function(pdf: &mut Pdf, gr: &GradientRefs, g: &AxialGradient) {
if g.stops.len() <= 2 {
let c0 = g.stops.first().map(|s| s.1).unwrap_or([0.0, 0.0, 0.0]);
let c1 = g.stops.get(1).map(|s| s.1).unwrap_or(c0);
write_linear_segment(pdf, gr.function, c0, c1);
return;
}
for (k, sub) in gr.sub_functions.iter().enumerate() {
let c0 = g.stops.get(k).map(|s| s.1).unwrap_or([0.0, 0.0, 0.0]);
let c1 = g.stops.get(k + 1).map(|s| s.1).unwrap_or(c0);
write_linear_segment(pdf, *sub, c0, c1);
}
let last = g.stops.len() - 1;
let bounds: Vec<f32> = g
.stops
.get(1..last)
.unwrap_or(&[])
.iter()
.map(|s| s.0)
.collect();
let mut encode: Vec<f32> = Vec::with_capacity(gr.sub_functions.len() * 2);
for _ in &gr.sub_functions {
encode.push(0.0);
encode.push(1.0);
}
let mut stitch = pdf.stitching_function(gr.function);
stitch.domain([0.0, 1.0]);
stitch.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
stitch.functions(gr.sub_functions.iter().copied());
stitch.bounds(bounds);
stitch.encode(encode);
stitch.finish();
}
fn write_linear_segment(pdf: &mut Pdf, id: Ref, c0: [f32; 3], c1: [f32; 3]) {
let mut f = pdf.exponential_function(id);
f.domain([0.0, 1.0]);
f.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
f.c0(c0);
f.c1(c1);
f.n(1.0);
f.finish();
}
fn write_images(pdf: &mut Pdf, res: &PageResources, refs: &[ImageRefs]) {
for (img, ir) in res.images.iter().zip(refs) {
let w = img.width as i32;
let h = img.height as i32;
let mut xobj = pdf.image_xobject(ir.image, &img.rgb_flate);
xobj.filter(Filter::FlateDecode);
xobj.width(w);
xobj.height(h);
xobj.color_space().device_rgb();
xobj.bits_per_component(8);
if let Some(smask) = ir.smask {
xobj.s_mask(smask);
}
xobj.finish();
if let (Some(smask), Some(alpha)) = (ir.smask, &img.alpha_flate) {
let mut sm = pdf.image_xobject(smask, alpha);
sm.filter(Filter::FlateDecode);
sm.width(w);
sm.height(h);
sm.color_space().device_gray();
sm.bits_per_component(8);
sm.finish();
}
}
}