pub mod barcode;
pub mod chart;
pub mod error;
pub mod font;
pub mod image_loader;
pub mod layout;
pub mod model;
pub mod pdf;
pub mod qrcode;
pub mod style;
pub mod svg;
pub mod template;
pub mod text;
#[cfg(feature = "wasm")]
pub mod wasm;
#[cfg(feature = "wasm-raw")]
pub mod wasm_raw;
pub use error::FormeError;
pub use layout::LayoutInfo;
pub use model::{ChartDataPoint, ChartSeries, DotPlotGroup};
pub use model::{ColumnDef, ColumnWidth, FontEntry, TextRun};
pub use model::{Document, Metadata, Node, NodeKind, PageConfig, PageSize};
pub use style::Style;
use font::FontContext;
use layout::LayoutEngine;
use pdf::PdfWriter;
pub fn render(document: &Document) -> Result<Vec<u8>, FormeError> {
let mut font_context = FontContext::new();
register_document_fonts(&mut font_context, &document.fonts);
let engine = LayoutEngine::new();
let pages = engine.layout(document, &font_context);
let writer = PdfWriter::new();
let tagged = document.tagged || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
writer.write(
&pages,
&document.metadata,
&font_context,
tagged,
document.pdfa.as_ref(),
document.embedded_data.as_deref(),
)
}
pub fn render_with_layout(document: &Document) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
let mut font_context = FontContext::new();
register_document_fonts(&mut font_context, &document.fonts);
let engine = LayoutEngine::new();
let pages = engine.layout(document, &font_context);
let layout_info = LayoutInfo::from_pages(&pages);
let writer = PdfWriter::new();
let tagged = document.tagged || matches!(document.pdfa, Some(model::PdfAConformance::A2a));
let pdf = writer.write(
&pages,
&document.metadata,
&font_context,
tagged,
document.pdfa.as_ref(),
document.embedded_data.as_deref(),
)?;
Ok((pdf, layout_info))
}
fn register_document_fonts(font_context: &mut FontContext, fonts: &[FontEntry]) {
use base64::Engine as _;
let b64 = base64::engine::general_purpose::STANDARD;
for entry in fonts {
let bytes = if let Some(comma_pos) = entry.src.find(',') {
b64.decode(&entry.src[comma_pos + 1..]).ok()
} else {
b64.decode(&entry.src).ok()
};
if let Some(data) = bytes {
font_context
.registry_mut()
.register(&entry.family, entry.weight, entry.italic, data);
}
}
}
pub fn render_json(json: &str) -> Result<Vec<u8>, FormeError> {
let document: Document = serde_json::from_str(json)?;
render(&document)
}
pub fn render_json_with_layout(json: &str) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
let document: Document = serde_json::from_str(json)?;
render_with_layout(&document)
}
pub fn render_template(template_json: &str, data_json: &str) -> Result<Vec<u8>, FormeError> {
let template: serde_json::Value = serde_json::from_str(template_json)?;
let data: serde_json::Value = serde_json::from_str(data_json)?;
let resolved = template::evaluate_template(&template, &data)?;
let document: Document = serde_json::from_value(resolved)?;
render(&document)
}
pub fn render_template_with_layout(
template_json: &str,
data_json: &str,
) -> Result<(Vec<u8>, LayoutInfo), FormeError> {
let template: serde_json::Value = serde_json::from_str(template_json)?;
let data: serde_json::Value = serde_json::from_str(data_json)?;
let resolved = template::evaluate_template(&template, &data)?;
let document: Document = serde_json::from_value(resolved)?;
render_with_layout(&document)
}