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, 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 render(document: &Document) -> Result<Vec<u8>, FormeError> {
65 let mut font_context = FontContext::new();
66 register_document_fonts(&mut font_context, &document.fonts);
67 let engine = LayoutEngine::new();
68 let pages = engine.layout(document, &font_context);
69 let writer = PdfWriter::new();
70 let tagged = document.tagged || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
71 writer.write(
72 &pages,
73 &document.metadata,
74 &font_context,
75 tagged,
76 document.pdfa.as_ref(),
77 document.embedded_data.as_deref(),
78 )
79}
80
81pub fn render_with_layout(document: &Document) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
86 let mut font_context = FontContext::new();
87 register_document_fonts(&mut font_context, &document.fonts);
88 let engine = LayoutEngine::new();
89 let pages = engine.layout(document, &font_context);
90 let layout_info = LayoutInfo::from_pages(&pages);
91 let writer = PdfWriter::new();
92 let tagged = document.tagged || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
93 let pdf = writer.write(
94 &pages,
95 &document.metadata,
96 &font_context,
97 tagged,
98 document.pdfa.as_ref(),
99 document.embedded_data.as_deref(),
100 )?;
101 Ok((pdf, layout_info))
102}
103
104fn register_document_fonts(font_context: &mut FontContext, fonts: &[FontEntry]) {
106 use base64::Engine as _;
107 let b64 = base64::engine::general_purpose::STANDARD;
108
109 for entry in fonts {
110 let bytes = if let Some(comma_pos) = entry.src.find(',') {
111 b64.decode(&entry.src[comma_pos + 1..]).ok()
113 } else {
114 b64.decode(&entry.src).ok()
116 };
117
118 if let Some(data) = bytes {
119 font_context
120 .registry_mut()
121 .register(&entry.family, entry.weight, entry.italic, data);
122 }
123 }
124}
125
126pub fn render_json(json: &str) -> Result<Vec<u8>, FormeError> {
128 let document: Document = serde_json::from_str(json)?;
129 render(&document)
130}
131
132pub fn render_json_with_layout(json: &str) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
134 let document: Document = serde_json::from_str(json)?;
135 render_with_layout(&document)
136}
137
138pub fn render_template(template_json: &str, data_json: &str) -> Result<Vec<u8>, FormeError> {
144 let template: serde_json::Value = serde_json::from_str(template_json)?;
145 let data: serde_json::Value = serde_json::from_str(data_json)?;
146 let resolved = template::evaluate_template(&template, &data)?;
147 let document: Document = serde_json::from_value(resolved)?;
148 render(&document)
149}
150
151pub fn render_template_with_layout(
153 template_json: &str,
154 data_json: &str,
155) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
156 let template: serde_json::Value = serde_json::from_str(template_json)?;
157 let data: serde_json::Value = serde_json::from_str(data_json)?;
158 let resolved = template::evaluate_template(&template, &data)?;
159 let document: Document = serde_json::from_value(resolved)?;
160 render_with_layout(&document)
161}