1mod text;
30mod tree;
31
32use crate::fonts::FontRegistry;
33use crate::images::ImageEmbedError;
34use lightweight_pdf_core::{Color, Document, FontKey, Watermark};
35use lightweight_pdf_layout::{paginate, LayoutCtx, LayoutWarning, PageRender, Rect};
36use lightweight_pdf_writer::{ContentBuilder, PdfDocument, PdfPage, Rgb};
37use std::collections::{BTreeSet, HashMap};
38use text::EmbeddedFont;
39
40#[derive(Debug)]
41pub enum RenderError {
42 Font(lightweight_pdf_fonts::FontError),
43 Image(ImageEmbedError),
44}
45
46impl core::fmt::Display for RenderError {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 match self {
49 RenderError::Font(e) => write!(f, "font error: {e}"),
50 RenderError::Image(e) => write!(f, "image error: {e}"),
51 }
52 }
53}
54
55impl From<lightweight_pdf_fonts::FontError> for RenderError {
56 fn from(e: lightweight_pdf_fonts::FontError) -> Self {
57 RenderError::Font(e)
58 }
59}
60
61impl From<ImageEmbedError> for RenderError {
62 fn from(e: ImageEmbedError) -> Self {
63 RenderError::Image(e)
64 }
65}
66
67fn to_rgb(c: Color) -> Rgb {
68 Rgb(c.0, c.1, c.2)
69}
70
71fn pdf_rect_y(page_height: f32, y_top: f32, height: f32) -> f32 {
74 page_height - y_top - height
75}
76
77struct RenderCtx<'a> {
84 page_height: f32,
85 embedded: &'a HashMap<FontKey, EmbeddedFont>,
86 pdf: &'a mut PdfDocument,
87 cb: &'a mut ContentBuilder,
88}
89
90fn render_page(
93 page: &PageRender,
94 watermark: Option<&Watermark>,
95 body_area: Rect,
96 page_width: f32,
97 page_height: f32,
98 embedded: &HashMap<FontKey, EmbeddedFont>,
99 pdf: &mut PdfDocument,
100) -> Result<PdfPage, RenderError> {
101 let mut cb = ContentBuilder::new();
102 cb.save();
103 cb.clip_rect(0.0, 0.0, page_width, page_height);
104 if let Some(watermark) = watermark {
108 text::draw_watermark(watermark, body_area, page_height, embedded, &mut cb);
109 }
110 let mut ctx = RenderCtx {
111 page_height,
112 embedded,
113 pdf,
114 cb: &mut cb,
115 };
116 if let Some(header) = &page.header {
117 tree::render_node(header, &mut ctx)?;
118 }
119 tree::render_node(&page.body, &mut ctx)?;
120 if let Some(footer) = &page.footer {
121 tree::render_node(footer, &mut ctx)?;
122 }
123 cb.restore();
124 Ok(PdfPage {
125 width: page_width,
126 height: page_height,
127 content: cb.into_bytes(),
128 })
129}
130
131fn render_document(doc: &Document, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
132 let ctx = LayoutCtx { resolver: fonts };
133 let paginated = paginate(doc, &ctx);
134
135 let mut used_chars: HashMap<FontKey, BTreeSet<char>> = HashMap::new();
136 for page in &paginated.pages {
137 text::collect_chars_in_page(page, &mut used_chars);
138 }
139 if let Some(watermark) = &doc.watermark {
140 used_chars.entry(watermark.font).or_default().extend(watermark.text.chars());
141 }
142
143 let mut pdf = PdfDocument::new();
144 let embedded = text::embed_fonts(&mut pdf, fonts, &used_chars)?;
145
146 for page in &paginated.pages {
147 let pdf_page = render_page(
148 page,
149 doc.watermark.as_ref(),
150 paginated.body_area,
151 paginated.page_width,
152 paginated.page_height,
153 &embedded,
154 &mut pdf,
155 )?;
156 pdf.add_page(pdf_page);
157 }
158
159 Ok((pdf.write(), paginated.warnings))
160}
161
162pub trait DocumentExt {
170 #[cfg(feature = "default-fonts")]
173 fn render(&self) -> Result<Vec<u8>, RenderError>;
174 #[cfg(feature = "default-fonts")]
175 fn render_with_diagnostics(&self) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>;
176
177 fn render_with_fonts(&self, fonts: &FontRegistry) -> Result<Vec<u8>, RenderError>;
178 fn render_with_fonts_and_diagnostics(&self, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>;
179}
180
181impl DocumentExt for Document {
182 #[cfg(feature = "default-fonts")]
183 fn render(&self) -> Result<Vec<u8>, RenderError> {
184 let (bytes, _warnings) = self.render_with_diagnostics()?;
185 Ok(bytes)
186 }
187
188 #[cfg(feature = "default-fonts")]
189 fn render_with_diagnostics(&self) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
190 let fonts = FontRegistry::with_defaults()?;
191 render_document(self, &fonts)
192 }
193
194 fn render_with_fonts(&self, fonts: &FontRegistry) -> Result<Vec<u8>, RenderError> {
195 let (bytes, _warnings) = self.render_with_fonts_and_diagnostics(fonts)?;
196 Ok(bytes)
197 }
198
199 fn render_with_fonts_and_diagnostics(&self, fonts: &FontRegistry) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError> {
200 render_document(self, fonts)
201 }
202}