use std::fmt::Write as _;
use std::path::Path;
use std::time::Instant;
use anyhow::{Result, bail};
use log::info;
use typst::layout::PagedDocument;
use super::markup::SourceMap;
use super::world::{FontCache, MluxWorld};
use crate::tile::{SourceMappingParams, TiledDocument, VisualLine, extract_visual_lines_with_map};
pub struct BuildParams<'a> {
pub theme_name: &'a str,
pub theme_text: &'a str,
pub data_files: crate::theme::DataFiles,
pub markdown: &'a str,
pub base_dir: Option<&'a Path>,
pub width_pt: f64,
pub sidebar_width_pt: f64,
pub tile_height_pt: f64,
pub ppi: f32,
pub fonts: &'a FontCache,
pub allow_remote_images: bool,
}
struct CompiledContent<'f> {
world: MluxWorld<'f>,
document: PagedDocument,
source_map: SourceMap,
}
fn compile_content<'f>(params: &BuildParams<'f>) -> Result<CompiledContent<'f>> {
let image_paths = super::markup::extract_image_paths(params.markdown);
let (mut image_files, image_errors) =
crate::image::load_images(&image_paths, params.base_dir, params.allow_remote_images);
for err in &image_errors {
log::warn!("{err}");
}
let diagrams = crate::diagram::extract_diagrams(params.markdown);
let mermaid_colors = crate::theme::mermaid_colors(params.theme_name);
for (key, svg) in crate::diagram::render_diagrams(&diagrams, mermaid_colors) {
image_files.insert(key, svg);
}
let loaded_set = image_files.key_set();
let (content_text, source_map) =
super::markup::markdown_to_typst(params.markdown, Some(&loaded_set));
let world = MluxWorld::new(
params.theme_text,
params.data_files,
&content_text,
params.width_pt,
params.fonts,
image_files,
);
let document = super::render::compile_document(&world)?;
Ok(CompiledContent {
world,
document,
source_map,
})
}
pub fn build_and_dump(params: &BuildParams<'_>) -> Result<()> {
let compiled = compile_content(params)?;
let source_text = compiled.world.main_source().text();
eprintln!(
"=== Generated main.typ ({} lines) ===",
source_text.lines().count()
);
for (i, line) in source_text.lines().enumerate() {
eprintln!("{:>4} | {}", i + 1, line);
}
eprintln!();
super::render::dump_document(&compiled.document);
Ok(())
}
pub fn build_tiled_document(params: &BuildParams<'_>) -> Result<TiledDocument> {
let start = Instant::now();
let CompiledContent {
world: content_world,
document,
source_map,
} = compile_content(params)?;
let mapping_params = SourceMappingParams {
source: content_world.main_source(),
content_offset: content_world.content_offset(),
source_map: &source_map,
md_source: params.markdown,
};
let visual_lines = extract_visual_lines_with_map(&document, params.ppi, Some(&mapping_params));
if document.pages.is_empty() {
bail!("[BUG] document has no pages");
}
let page_height_pt = document.pages[0].frame.size().y.to_pt();
let sidebar_source = generate_sidebar_typst(
&visual_lines,
params.sidebar_width_pt,
page_height_pt,
params.theme_name,
);
let sidebar_world = MluxWorld::new_raw(&sidebar_source, params.fonts);
let sidebar_doc = super::render::compile_document(&sidebar_world)?;
let tiled_doc = TiledDocument::new(
&document,
&sidebar_doc,
visual_lines,
params.tile_height_pt,
params.ppi,
)?;
info!(
"build_tiled_document completed in {:.1}ms",
start.elapsed().as_secs_f64() * 1000.0
);
Ok(tiled_doc)
}
pub fn generate_sidebar_typst(
lines: &[VisualLine],
sidebar_width_pt: f64,
page_height_pt: f64,
theme_name: &str,
) -> String {
let (bg, fg) = crate::theme::sidebar_colors(theme_name);
let mut src = String::new();
writeln!(
src,
"#set page(width: {sidebar_width_pt}pt, height: {page_height_pt}pt, margin: 0pt, fill: rgb(\"{bg}\"))"
)
.unwrap();
writeln!(
src,
"#set text(font: \"DejaVu Sans Mono\", size: 8pt, fill: rgb(\"{fg}\"))"
)
.unwrap();
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
let dy = line.y_pt;
writeln!(
src,
"#place(top + right, dy: {dy}pt - 6pt, dx: -4pt)[#text(size: 8pt)[{line_num}]]"
)
.unwrap();
}
src
}