mod text;
mod tree;
use crate::fonts::FontRegistry;
use crate::images::ImageEmbedError;
use lightweight_pdf_core::{Color, Document, FontKey, Watermark};
use lightweight_pdf_layout::{paginate, LayoutCtx, LayoutWarning, PageRender, Rect};
use lightweight_pdf_writer::{ContentBuilder, PdfDocument, PdfPage, Rgb};
use std::collections::{BTreeSet, HashMap};
use text::EmbeddedFont;
#[derive(Debug)]
pub enum RenderError {
Font(lightweight_pdf_fonts::FontError),
Image(ImageEmbedError),
}
impl core::fmt::Display for RenderError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RenderError::Font(e) => write!(f, "font error: {e}"),
RenderError::Image(e) => write!(f, "image error: {e}"),
}
}
}
impl From<lightweight_pdf_fonts::FontError> for RenderError {
fn from(e: lightweight_pdf_fonts::FontError) -> Self {
RenderError::Font(e)
}
}
impl From<ImageEmbedError> for RenderError {
fn from(e: ImageEmbedError) -> Self {
RenderError::Image(e)
}
}
fn to_rgb(c: Color) -> Rgb {
Rgb(c.0, c.1, c.2)
}
fn pdf_rect_y(page_height: f32, y_top: f32, height: f32) -> f32 {
page_height - y_top - height
}
struct RenderCtx<'a> {
page_height: f32,
embedded: &'a HashMap<FontKey, EmbeddedFont>,
pdf: &'a mut PdfDocument,
cb: &'a mut ContentBuilder,
}
fn render_page(
page: &PageRender,
watermark: Option<&Watermark>,
body_area: Rect,
page_width: f32,
page_height: f32,
embedded: &HashMap<FontKey, EmbeddedFont>,
pdf: &mut PdfDocument,
) -> Result<PdfPage, RenderError> {
let mut cb = ContentBuilder::new();
cb.save();
cb.clip_rect(0.0, 0.0, page_width, page_height);
if let Some(watermark) = watermark {
text::draw_watermark(watermark, body_area, page_height, embedded, &mut cb);
}
let mut ctx = RenderCtx {
page_height,
embedded,
pdf,
cb: &mut cb,
};
if let Some(header) = &page.header {
tree::render_node(header, &mut ctx)?;
}
tree::render_node(&page.body, &mut ctx)?;
if let Some(footer) = &page.footer {
tree::render_node(footer, &mut ctx)?;
}
cb.restore();
Ok(PdfPage {
width: page_width,
height: page_height,
content: cb.into_bytes(),
})
}
fn render_document(doc: &Document, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
let ctx = LayoutCtx { resolver: fonts };
let paginated = paginate(doc, &ctx);
let mut used_chars: HashMap<FontKey, BTreeSet<char>> = HashMap::new();
for page in &paginated.pages {
text::collect_chars_in_page(page, &mut used_chars);
}
if let Some(watermark) = &doc.watermark {
used_chars.entry(watermark.font).or_default().extend(watermark.text.chars());
}
let mut pdf = PdfDocument::new();
let embedded = text::embed_fonts(&mut pdf, fonts, &used_chars)?;
for page in &paginated.pages {
let pdf_page = render_page(
page,
doc.watermark.as_ref(),
paginated.body_area,
paginated.page_width,
paginated.page_height,
&embedded,
&mut pdf,
)?;
pdf.add_page(pdf_page);
}
Ok((pdf.write(), paginated.warnings))
}
pub trait DocumentExt {
#[cfg(feature = "default-fonts")]
fn render(&self) -> Result<Vec<u8>, RenderError>;
#[cfg(feature = "default-fonts")]
fn render_with_diagnostics(&self) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>;
fn render_with_fonts(&self, fonts: &FontRegistry) -> Result<Vec<u8>, RenderError>;
fn render_with_fonts_and_diagnostics(&self, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>;
}
impl DocumentExt for Document {
#[cfg(feature = "default-fonts")]
fn render(&self) -> Result<Vec<u8>, RenderError> {
let (bytes, _warnings) = self.render_with_diagnostics()?;
Ok(bytes)
}
#[cfg(feature = "default-fonts")]
fn render_with_diagnostics(&self) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
let fonts = FontRegistry::with_defaults()?;
render_document(self, &fonts)
}
fn render_with_fonts(&self, fonts: &FontRegistry) -> Result<Vec<u8>, RenderError> {
let (bytes, _warnings) = self.render_with_fonts_and_diagnostics(fonts)?;
Ok(bytes)
}
fn render_with_fonts_and_diagnostics(&self, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
render_document(self, fonts)
}
}