use std::sync::Arc;
use reflexo::error::prelude::*;
use reflexo::typst::{TypstDocument, TypstHtmlDocument, TypstPagedDocument};
use reflexo_typst2vec::pass::{CommandExecutor, Typst2VecPass};
use reflexo_typst2vec::IntoTypst;
use reflexo_vec2svg::{DynamicLayoutSvgExporter, MultiVecDocument};
use tinymist_task::ExportTask;
use typst::diag::SourceResult;
use typst::foundations::IntoValue;
use typst::utils::LazyHash;
use crate::typst::prelude::*;
use crate::vector::ir::{LayoutRegion, LayoutRegionNode};
use crate::world::{CompilerFeat, CompilerWorld, TaskInputs, WorldComputeGraph};
use crate::TypstDict;
pub type LayoutWidths = EcoVec<typst::layout::Abs>;
pub type PostProcessLayoutFn = Arc<
dyn Fn(&mut Typst2VecPass, TypstDocument, LayoutRegionNode) -> LayoutRegionNode + Send + Sync,
>;
pub type PostProcessLayoutsFn =
Arc<dyn Fn(&mut Typst2VecPass, Vec<LayoutRegion>) -> Vec<LayoutRegion> + Send + Sync>;
#[derive(Clone)]
pub struct ExportDynSvgModuleTask {
pub export: ExportTask,
pub layout_widths: LayoutWidths,
pub command_executor: Arc<dyn CommandExecutor + Send + Sync>,
post_process_layout: Option<PostProcessLayoutFn>,
post_process_layouts: Option<PostProcessLayoutsFn>,
pub target: String,
pub html_format: bool,
}
pub struct DynSvgModuleExport;
impl DynSvgModuleExport {
pub fn run<F: CompilerFeat>(
graph: &Arc<WorldComputeGraph<F>>,
config: &ExportDynSvgModuleTask,
) -> Result<Option<MultiVecDocument>> {
Ok(Some(config.do_export(&graph.snap.world)?))
}
}
impl ExportDynSvgModuleTask {
pub fn new() -> Self {
Self {
export: ExportTask::default(),
layout_widths: LayoutWidths::from_iter(
(0..40).map(|i| {
typst::layout::Abs::pt(750.0) - typst::layout::Abs::pt(i as f64 * 10.0)
}),
),
command_executor: Arc::new(()),
post_process_layout: None,
post_process_layouts: None,
target: "web".to_owned(),
html_format: false,
}
}
pub fn set_layout_widths(&mut self, layout_widths: LayoutWidths) {
self.layout_widths = layout_widths;
}
pub fn set_target(&mut self, target: String) {
self.target = target;
}
pub fn set_command_executor(
&mut self,
command_sanitizer: Arc<dyn CommandExecutor + Send + Sync>,
) {
self.command_executor = command_sanitizer;
}
pub fn set_post_process_layout(
&mut self,
post_process_layout: impl Fn(&mut Typst2VecPass, TypstDocument, LayoutRegionNode) -> LayoutRegionNode
+ Send
+ Sync
+ 'static,
) {
self.post_process_layout = Some(Arc::new(post_process_layout));
}
pub fn set_post_process_layouts(
&mut self,
post_process_layouts: impl Fn(&mut Typst2VecPass, Vec<LayoutRegion>) -> Vec<LayoutRegion>
+ Send
+ Sync
+ 'static,
) {
self.post_process_layouts = Some(Arc::new(post_process_layouts));
}
}
impl Default for ExportDynSvgModuleTask {
fn default() -> Self {
Self::new()
}
}
impl ExportDynSvgModuleTask {
pub fn do_export<F: CompilerFeat>(
&self,
world: &CompilerWorld<F>,
) -> SourceResult<MultiVecDocument> {
let mut svg_exporter = DynamicLayoutSvgExporter::default();
svg_exporter.typst2vec.command_executor = self.command_executor.clone();
self.do_export_with(world, svg_exporter)
}
pub fn do_export_with<F: CompilerFeat>(
&self,
world: &CompilerWorld<F>,
mut svg_exporter: reflexo_vec2svg::DynamicLayoutSvgExporter,
) -> SourceResult<MultiVecDocument> {
let instant_begin = reflexo::time::Instant::now();
for (i, current_width) in self.layout_widths.clone().into_iter().enumerate() {
let instant = reflexo::time::Instant::now();
let world = world.task(TaskInputs {
inputs: Some({
let mut dict = TypstDict::new();
dict.insert("x-page-width".into(), current_width.into_value());
dict.insert("x-target".into(), self.target.clone().into_value());
Arc::new(LazyHash::new(dict))
}),
..Default::default()
});
log::trace!(
"rerendering {i} at {:?}, width={current_width:?} target={}",
instant - instant_begin,
self.target,
);
let output = if self.html_format {
let world = world.html_task();
TypstDocument::Html(Arc::new(
typst::compile::<TypstHtmlDocument>(world.as_ref()).output?,
))
} else {
let world = world.paged_task();
TypstDocument::Paged(Arc::new(
typst::compile::<TypstPagedDocument>(world.as_ref()).output?,
))
};
let mut layout = svg_exporter.render(&output);
if let Some(post_process_layout) = &self.post_process_layout {
layout = post_process_layout(&mut svg_exporter.typst2vec, output.clone(), layout);
}
svg_exporter
.layouts
.push((current_width.into_typst(), layout));
log::trace!("rerendered {i} at {:?}", instant - instant_begin);
}
let mut layouts = vec![LayoutRegion::new_by_scalar(
"width".into(),
svg_exporter.layouts,
)];
if let Some(post_process_layouts) = &self.post_process_layouts {
layouts = post_process_layouts(&mut svg_exporter.typst2vec, layouts);
}
let module = svg_exporter.typst2vec.finalize();
let doc = MultiVecDocument { module, layouts };
let instant = reflexo::time::Instant::now();
log::trace!("multiple layouts finished at {:?}", instant - instant_begin);
Ok(doc)
}
}