use std::path::Path;
use zenith_core::{BytesAssetProvider, DataContext, Diagnostic, dim_to_px};
use zenith_render::{
PdfOptions, render_pdf_multi_with, render_pdf_with, render_png, render_spread_png,
};
use zenith_scene::{Scene, compile_page};
use crate::config::CliPolicyFlags;
use super::assets::{build_asset_provider, build_font_provider, disk_diagnostics};
use super::pipeline::{govern_compile_diagnostics, parse_validate, resolve_page_index};
use super::text_source::resolve_text_sources;
#[derive(Debug)]
pub struct RenderCmdErr {
pub message: String,
pub exit_code: u8,
}
impl RenderCmdErr {
pub(super) fn new(msg: impl Into<String>, exit_code: u8) -> Self {
Self {
message: msg.into(),
exit_code,
}
}
}
#[derive(Debug)]
pub struct SceneArtifact {
pub json: String,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug)]
pub struct PngArtifact {
pub png: Vec<u8>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug)]
pub struct PdfArtifact {
pub pdf: Vec<u8>,
pub diagnostics: Vec<Diagnostic>,
}
pub fn to_scene_json(
src: &str,
project_dir: Option<&Path>,
page: usize,
flags: &CliPolicyFlags,
data: Option<&DataContext>,
) -> Result<SceneArtifact, RenderCmdErr> {
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut text_src_diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut text_src_diagnostics);
let fonts = build_font_provider(&doc, project_dir, false)?;
let page_index = resolve_page_index(&doc, page)?;
let compile_result = compile_page(&doc, &fonts, page_index, data);
let json = compile_result
.scene
.to_json()
.map_err(|e| RenderCmdErr::new(format!("scene serialisation error: {e}"), 2))?;
let mut diagnostics = text_src_diagnostics;
diagnostics.extend(disk_diagnostics(&doc, project_dir));
diagnostics.extend(govern_compile_diagnostics(
compile_result.diagnostics,
&policy,
));
Ok(SceneArtifact { json, diagnostics })
}
pub fn to_png(src: &str, page: usize) -> Result<PngArtifact, RenderCmdErr> {
to_png_with_dir(src, None, page, false, &CliPolicyFlags::default(), None)
}
pub fn to_png_with_dir(
src: &str,
project_dir: Option<&Path>,
page: usize,
locked: bool,
flags: &CliPolicyFlags,
data: Option<&DataContext>,
) -> Result<PngArtifact, RenderCmdErr> {
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut text_src_diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut text_src_diagnostics);
let fonts = build_font_provider(&doc, project_dir, locked)?;
let page_index = resolve_page_index(&doc, page)?;
let assets = match project_dir {
Some(dir) => build_asset_provider(&doc, dir, locked)?,
None => BytesAssetProvider::new(),
};
let compile_result = compile_page(&doc, &fonts, page_index, data);
let png = render_png(&compile_result.scene, &fonts, &assets)
.map_err(|e| RenderCmdErr::new(format!("render error: {e}"), 2))?;
let mut diagnostics = text_src_diagnostics;
diagnostics.extend(disk_diagnostics(&doc, project_dir));
diagnostics.extend(govern_compile_diagnostics(
compile_result.diagnostics,
&policy,
));
Ok(PngArtifact { png, diagnostics })
}
pub fn to_pdf_with_dir(
src: &str,
project_dir: Option<&Path>,
page: usize,
locked: bool,
subset: bool,
flags: &CliPolicyFlags,
data: Option<&DataContext>,
) -> Result<PdfArtifact, RenderCmdErr> {
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut text_src_diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut text_src_diagnostics);
let fonts = build_font_provider(&doc, project_dir, locked)?;
let page_index = resolve_page_index(&doc, page)?;
let assets = match project_dir {
Some(dir) => build_asset_provider(&doc, dir, locked)?,
None => BytesAssetProvider::new(),
};
let compile_result = compile_page(&doc, &fonts, page_index, data);
let pdf = render_pdf_with(
&compile_result.scene,
&fonts,
&assets,
PdfOptions { subset },
);
let mut diagnostics = text_src_diagnostics;
diagnostics.extend(disk_diagnostics(&doc, project_dir));
diagnostics.extend(govern_compile_diagnostics(
compile_result.diagnostics,
&policy,
));
Ok(PdfArtifact { pdf, diagnostics })
}
pub fn to_pdf_all_pages_with_dir(
src: &str,
project_dir: Option<&Path>,
locked: bool,
subset: bool,
flags: &CliPolicyFlags,
data: Option<&DataContext>,
) -> Result<PdfArtifact, RenderCmdErr> {
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut diagnostics);
let fonts = build_font_provider(&doc, project_dir, locked)?;
let page_count = doc.body.pages.len();
if page_count == 0 {
return Err(RenderCmdErr::new("document has no pages to render", 2));
}
let assets = match project_dir {
Some(dir) => build_asset_provider(&doc, dir, locked)?,
None => BytesAssetProvider::new(),
};
let mut scenes: Vec<Scene> = Vec::with_capacity(page_count);
diagnostics.extend(disk_diagnostics(&doc, project_dir));
for page_index in 0..page_count {
let compile_result = compile_page(&doc, &fonts, page_index, data);
scenes.push(compile_result.scene);
diagnostics.extend(govern_compile_diagnostics(
compile_result.diagnostics,
&policy,
));
}
let pdf = render_pdf_multi_with(&scenes, &fonts, &assets, PdfOptions { subset });
Ok(PdfArtifact { pdf, diagnostics })
}
pub fn to_png_all_pages(
src: &str,
project_dir: Option<&Path>,
locked: bool,
flags: &CliPolicyFlags,
data: Option<&DataContext>,
) -> Result<Vec<PngArtifact>, RenderCmdErr> {
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut text_src_diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut text_src_diagnostics);
let fonts = build_font_provider(&doc, project_dir, locked)?;
let page_count = doc.body.pages.len();
if page_count == 0 {
return Err(RenderCmdErr::new("document has no pages to render", 2));
}
let assets = match project_dir {
Some(dir) => build_asset_provider(&doc, dir, locked)?,
None => BytesAssetProvider::new(),
};
let base_diagnostics: Vec<Diagnostic> = text_src_diagnostics
.into_iter()
.chain(disk_diagnostics(&doc, project_dir))
.collect();
let mut artifacts = Vec::with_capacity(page_count);
for page_index in 0..page_count {
let compile_result = compile_page(&doc, &fonts, page_index, data);
let png = render_png(&compile_result.scene, &fonts, &assets)
.map_err(|e| RenderCmdErr::new(format!("render error on page {page_index}: {e}"), 2))?;
let mut diagnostics = base_diagnostics.clone();
diagnostics.extend(govern_compile_diagnostics(
compile_result.diagnostics,
&policy,
));
artifacts.push(PngArtifact { png, diagnostics });
}
Ok(artifacts)
}
#[derive(Clone, Copy)]
pub struct SpreadRenderOpts<'a> {
pub locked: bool,
pub flags: &'a CliPolicyFlags,
pub data: Option<&'a DataContext>,
}
pub fn to_png_spread(
src: &str,
project_dir: Option<&Path>,
page_a: usize,
page_b: usize,
gutter_override: Option<u32>,
opts: SpreadRenderOpts<'_>,
) -> Result<PngArtifact, RenderCmdErr> {
let SpreadRenderOpts {
locked,
flags,
data,
} = opts;
let (mut doc, policy) = parse_validate(src, project_dir, flags)?;
let mut text_src_diagnostics: Vec<Diagnostic> = Vec::new();
resolve_text_sources(&mut doc, project_dir, &mut text_src_diagnostics);
let fonts = build_font_provider(&doc, project_dir, locked)?;
let index_a = resolve_page_index(&doc, page_a)?;
let index_b = resolve_page_index(&doc, page_b)?;
let assets = match project_dir {
Some(dir) => build_asset_provider(&doc, dir, locked)?,
None => BytesAssetProvider::new(),
};
let gutter_px = gutter_override.unwrap_or_else(|| {
doc.spread_gutter
.as_ref()
.and_then(|d| dim_to_px(d.value, &d.unit))
.map(|px| px.max(0.0) as u32)
.unwrap_or(0)
});
let compile_a = compile_page(&doc, &fonts, index_a, data);
let compile_b = compile_page(&doc, &fonts, index_b, data);
let png = render_spread_png(
&compile_a.scene,
&compile_b.scene,
gutter_px,
&fonts,
&assets,
)
.map_err(|e| RenderCmdErr::new(format!("spread render error: {e}"), 2))?;
let mut compile_diagnostics = compile_a.diagnostics;
compile_diagnostics.extend(compile_b.diagnostics);
let mut diagnostics = text_src_diagnostics;
diagnostics.extend(disk_diagnostics(&doc, project_dir));
diagnostics.extend(govern_compile_diagnostics(compile_diagnostics, &policy));
Ok(PngArtifact { png, diagnostics })
}