1pub mod barcode;
30pub mod chart;
31pub mod error;
32pub mod font;
33pub mod image_loader;
34pub mod layout;
35pub mod model;
36pub mod pdf;
37pub mod qrcode;
38pub mod style;
39pub mod svg;
40pub mod template;
41pub mod text;
42
43#[cfg(feature = "wasm")]
44pub mod wasm;
45
46#[cfg(feature = "wasm-raw")]
47pub mod wasm_raw;
48
49pub use error::FormeError;
50pub use layout::LayoutInfo;
51pub use model::{
52 CertificationConfig, ColumnDef, ColumnWidth, FontEntry, PatternType, RedactionPattern,
53 RedactionRegion, TextRun,
54};
55pub use model::{ChartDataPoint, ChartSeries, DotPlotGroup};
56pub use model::{Document, Metadata, Node, NodeKind, PageConfig, PageSize};
57pub use style::Style;
58
59use font::FontContext;
60use layout::LayoutEngine;
61use pdf::PdfWriter;
62
63pub fn certify_pdf(
69 pdf_bytes: &[u8],
70 config: &model::CertificationConfig,
71) -> Result<Vec<u8>, FormeError> {
72 pdf::certify::certify_pdf(pdf_bytes, config)
73}
74
75pub fn redact_pdf(
81 pdf_bytes: &[u8],
82 regions: &[model::RedactionRegion],
83) -> Result<Vec<u8>, FormeError> {
84 pdf::redaction::redact_pdf(pdf_bytes, regions)
85}
86
87pub fn find_text_regions(
92 pdf_bytes: &[u8],
93 patterns: &[model::RedactionPattern],
94) -> Result<Vec<RedactionRegion>, FormeError> {
95 pdf::redaction::find_text_regions(pdf_bytes, patterns)
96}
97
98pub fn redact_text(
103 pdf_bytes: &[u8],
104 patterns: &[model::RedactionPattern],
105) -> Result<Vec<u8>, FormeError> {
106 pdf::redaction::redact_text(pdf_bytes, patterns)
107}
108
109pub fn merge_pdfs(pdfs: &[&[u8]]) -> Result<Vec<u8>, FormeError> {
114 pdf::merge::merge_pdfs(pdfs)
115}
116
117pub fn render(document: &Document) -> Result<Vec<u8>, FormeError> {
123 render_with_warnings(document).map(|(pdf, _warnings)| pdf)
124}
125
126pub fn render_with_warnings(document: &Document) -> Result<(Vec<u8>, Vec<String>), FormeError> {
130 let mut font_context = FontContext::new();
131 register_document_fonts(&mut font_context, &document.fonts);
132 let engine = LayoutEngine::new();
133 let mut pages = engine.layout(document, &font_context);
134
135 for _ in 0..2 {
137 let needed = digits_for_count(pages.len());
138 if needed == font_context.sentinel_digit_count() {
139 break;
140 }
141 font_context.set_sentinel_digit_count(needed);
142 pages = engine.layout(document, &font_context);
143 }
144
145 let writer = PdfWriter::new();
146 let tagged = document.tagged
147 || document.pdf_ua
148 || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
149 let (pdf, warnings) = writer.write(
150 &pages,
151 &document.metadata,
152 &font_context,
153 tagged,
154 document.pdfa.as_ref(),
155 document.pdf_ua,
156 document.embedded_data.as_deref(),
157 document.flatten_forms,
158 )?;
159 let pdf = if let Some(ref sig_config) = document.certification {
160 pdf::certify::certify_pdf(&pdf, sig_config)?
161 } else {
162 pdf
163 };
164 Ok((pdf, warnings))
165}
166
167pub fn render_with_layout(
174 document: &Document,
175) -> Result<(Vec<u8>, LayoutInfo, Vec<String>), FormeError> {
176 let mut font_context = FontContext::new();
177 register_document_fonts(&mut font_context, &document.fonts);
178 let engine = LayoutEngine::new();
179 let mut pages = engine.layout(document, &font_context);
180
181 for _ in 0..2 {
183 let needed = digits_for_count(pages.len());
184 if needed == font_context.sentinel_digit_count() {
185 break;
186 }
187 font_context.set_sentinel_digit_count(needed);
188 pages = engine.layout(document, &font_context);
189 }
190
191 let layout_info = LayoutInfo::from_pages(&pages);
192 let writer = PdfWriter::new();
193 let tagged = document.tagged
194 || document.pdf_ua
195 || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
196 let (pdf, warnings) = writer.write(
197 &pages,
198 &document.metadata,
199 &font_context,
200 tagged,
201 document.pdfa.as_ref(),
202 document.pdf_ua,
203 document.embedded_data.as_deref(),
204 document.flatten_forms,
205 )?;
206 let pdf = if let Some(ref sig_config) = document.certification {
207 pdf::certify::certify_pdf(&pdf, sig_config)?
208 } else {
209 pdf
210 };
211 Ok((pdf, layout_info, warnings))
212}
213
214fn digits_for_count(n: usize) -> u32 {
216 if n < 10 {
217 1
218 } else if n < 100 {
219 2
220 } else if n < 1000 {
221 3
222 } else {
223 4
224 }
225}
226
227fn register_document_fonts(font_context: &mut FontContext, fonts: &[FontEntry]) {
229 use base64::Engine as _;
230 let b64 = base64::engine::general_purpose::STANDARD;
231
232 for entry in fonts {
233 let bytes = if let Some(comma_pos) = entry.src.find(',') {
234 b64.decode(&entry.src[comma_pos + 1..]).ok()
236 } else {
237 b64.decode(&entry.src).ok()
239 };
240
241 if let Some(data) = bytes {
242 font_context
243 .registry_mut()
244 .register(&entry.family, entry.weight, entry.italic, data);
245 }
246 }
247}
248
249pub fn render_json(json: &str) -> Result<Vec<u8>, FormeError> {
251 let document: Document = serde_json::from_str(json)?;
252 render(&document)
253}
254
255pub fn render_json_with_layout(
257 json: &str,
258) -> Result<(Vec<u8>, LayoutInfo, Vec<String>), FormeError> {
259 let document: Document = serde_json::from_str(json)?;
260 render_with_layout(&document)
261}
262
263pub fn render_template(template_json: &str, data_json: &str) -> Result<Vec<u8>, FormeError> {
269 let template: serde_json::Value = serde_json::from_str(template_json)?;
270 let data: serde_json::Value = serde_json::from_str(data_json)?;
271 let resolved = template::evaluate_template(&template, &data)?;
272 let document: Document = serde_json::from_value(resolved)?;
273 render(&document)
274}
275
276pub fn render_template_with_layout(
278 template_json: &str,
279 data_json: &str,
280) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
281 let template: serde_json::Value = serde_json::from_str(template_json)?;
282 let data: serde_json::Value = serde_json::from_str(data_json)?;
283 let resolved = template::evaluate_template(&template, &data)?;
284 let document: Document = serde_json::from_value(resolved)?;
285 render_with_layout(&document).map(|(pdf, layout, _warnings)| (pdf, layout))
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_digits_for_count() {
294 assert_eq!(digits_for_count(0), 1);
295 assert_eq!(digits_for_count(1), 1);
296 assert_eq!(digits_for_count(9), 1);
297 assert_eq!(digits_for_count(10), 2);
298 assert_eq!(digits_for_count(99), 2);
299 assert_eq!(digits_for_count(100), 3);
300 assert_eq!(digits_for_count(999), 3);
301 assert_eq!(digits_for_count(1000), 4);
302 }
303}