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::{ChartDataPoint, ChartSeries, DotPlotGroup};
52pub use model::{ColumnDef, ColumnWidth, FontEntry, SignatureConfig, TextRun};
53pub use model::{Document, Metadata, Node, NodeKind, PageConfig, PageSize};
54pub use style::Style;
55
56use font::FontContext;
57use layout::LayoutEngine;
58use pdf::PdfWriter;
59
60pub fn sign_pdf(pdf_bytes: &[u8], config: &model::SignatureConfig) -> Result<Vec<u8>, FormeError> {
66 pdf::signing::sign_pdf(pdf_bytes, config)
67}
68
69pub fn render(document: &Document) -> Result<Vec<u8>, FormeError> {
75 let mut font_context = FontContext::new();
76 register_document_fonts(&mut font_context, &document.fonts);
77 let engine = LayoutEngine::new();
78 let mut pages = engine.layout(document, &font_context);
79
80 for _ in 0..2 {
82 let needed = digits_for_count(pages.len());
83 if needed == font_context.sentinel_digit_count() {
84 break;
85 }
86 font_context.set_sentinel_digit_count(needed);
87 pages = engine.layout(document, &font_context);
88 }
89
90 let writer = PdfWriter::new();
91 let tagged = document.tagged
92 || document.pdf_ua
93 || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
94 let pdf = writer.write(
95 &pages,
96 &document.metadata,
97 &font_context,
98 tagged,
99 document.pdfa.as_ref(),
100 document.pdf_ua,
101 document.embedded_data.as_deref(),
102 document.flatten_forms,
103 )?;
104 let pdf = if let Some(ref sig_config) = document.signature {
105 pdf::signing::sign_pdf(&pdf, sig_config)?
106 } else {
107 pdf
108 };
109 Ok(pdf)
110}
111
112pub fn render_with_layout(document: &Document) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
119 let mut font_context = FontContext::new();
120 register_document_fonts(&mut font_context, &document.fonts);
121 let engine = LayoutEngine::new();
122 let mut pages = engine.layout(document, &font_context);
123
124 for _ in 0..2 {
126 let needed = digits_for_count(pages.len());
127 if needed == font_context.sentinel_digit_count() {
128 break;
129 }
130 font_context.set_sentinel_digit_count(needed);
131 pages = engine.layout(document, &font_context);
132 }
133
134 let layout_info = LayoutInfo::from_pages(&pages);
135 let writer = PdfWriter::new();
136 let tagged = document.tagged
137 || document.pdf_ua
138 || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
139 let pdf = writer.write(
140 &pages,
141 &document.metadata,
142 &font_context,
143 tagged,
144 document.pdfa.as_ref(),
145 document.pdf_ua,
146 document.embedded_data.as_deref(),
147 document.flatten_forms,
148 )?;
149 let pdf = if let Some(ref sig_config) = document.signature {
150 pdf::signing::sign_pdf(&pdf, sig_config)?
151 } else {
152 pdf
153 };
154 Ok((pdf, layout_info))
155}
156
157fn digits_for_count(n: usize) -> u32 {
159 if n < 10 {
160 1
161 } else if n < 100 {
162 2
163 } else if n < 1000 {
164 3
165 } else {
166 4
167 }
168}
169
170fn register_document_fonts(font_context: &mut FontContext, fonts: &[FontEntry]) {
172 use base64::Engine as _;
173 let b64 = base64::engine::general_purpose::STANDARD;
174
175 for entry in fonts {
176 let bytes = if let Some(comma_pos) = entry.src.find(',') {
177 b64.decode(&entry.src[comma_pos + 1..]).ok()
179 } else {
180 b64.decode(&entry.src).ok()
182 };
183
184 if let Some(data) = bytes {
185 font_context
186 .registry_mut()
187 .register(&entry.family, entry.weight, entry.italic, data);
188 }
189 }
190}
191
192pub fn render_json(json: &str) -> Result<Vec<u8>, FormeError> {
194 let document: Document = serde_json::from_str(json)?;
195 render(&document)
196}
197
198pub fn render_json_with_layout(json: &str) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
200 let document: Document = serde_json::from_str(json)?;
201 render_with_layout(&document)
202}
203
204pub fn render_template(template_json: &str, data_json: &str) -> Result<Vec<u8>, FormeError> {
210 let template: serde_json::Value = serde_json::from_str(template_json)?;
211 let data: serde_json::Value = serde_json::from_str(data_json)?;
212 let resolved = template::evaluate_template(&template, &data)?;
213 let document: Document = serde_json::from_value(resolved)?;
214 render(&document)
215}
216
217pub fn render_template_with_layout(
219 template_json: &str,
220 data_json: &str,
221) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
222 let template: serde_json::Value = serde_json::from_str(template_json)?;
223 let data: serde_json::Value = serde_json::from_str(data_json)?;
224 let resolved = template::evaluate_template(&template, &data)?;
225 let document: Document = serde_json::from_value(resolved)?;
226 render_with_layout(&document)
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_digits_for_count() {
235 assert_eq!(digits_for_count(0), 1);
236 assert_eq!(digits_for_count(1), 1);
237 assert_eq!(digits_for_count(9), 1);
238 assert_eq!(digits_for_count(10), 2);
239 assert_eq!(digits_for_count(99), 2);
240 assert_eq!(digits_for_count(100), 3);
241 assert_eq!(digits_for_count(999), 3);
242 assert_eq!(digits_for_count(1000), 4);
243 }
244}