diff --git a/Cargo.lock b/Cargo.lock index ce2dcee..973ddfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,6 +418,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "env_logger" version = "0.10.0" @@ -709,6 +715,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -794,6 +809,7 @@ dependencies = [ "async-process", "clap", "futures", + "itertools", "lazy_static", "mdbook", "pulldown-cmark", diff --git a/Cargo.toml b/Cargo.toml index e360c1a..b082a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ sha2 = "0.10.8" syntect = { version = "5.1.0", default-features = false, features = ["yaml-load", "default-themes", "regex-onig", "html", "default-syntaxes"] } tokio = { version = "1.33.0", features = ["rt"] } toml = "0.5.2" +itertools = "*" \ No newline at end of file diff --git a/example-book/src/indented/indented.md b/example-book/src/indented/indented.md index 24f6907..2ce26bc 100644 --- a/example-book/src/indented/indented.md +++ b/example-book/src/indented/indented.md @@ -4,4 +4,11 @@ #block(fill: red.lighten(70%), stroke: red, inset: 1em)[ This is a block ] +``` + +```typ +Page break: +#pagebreak() +#set page(header: "HEADER") +Some text ``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a0e6ec6..80de845 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,9 @@ use sha2::{Digest, Sha256}; use std::fs::{self, File}; use std::future::Future; use std::io::Write; +use std::iter; use std::path::PathBuf; +use std::str::FromStr; use anyhow::anyhow; use lazy_static::lazy_static; @@ -63,7 +65,7 @@ struct PreprocessSettings { highlight_inline: bool, typst_default: bool, render: bool, - warn_not_specified: bool + warn_not_specified: bool, } pub struct TypstHighlight; @@ -71,9 +73,10 @@ pub struct TypstHighlight; fn get_setting(preprocessor: Option<&toml::map::Map>, name: &str) -> bool { preprocessor .and_then(|typst_cfg| { - typst_cfg - .get(name) - .map(|v| v.as_bool().expect(&("Incorrect argument at".to_owned() + name))) + typst_cfg.get(name).map(|v| { + v.as_bool() + .expect(&("Incorrect argument at".to_owned() + name)) + }) }) .unwrap_or(false) } @@ -91,7 +94,12 @@ impl Preprocessor for TypstHighlight { let render = get_setting(prep, "render"); let warn_not_specified = get_setting(prep, "warn_not_specified"); - let settings = PreprocessSettings{ highlight_inline, typst_default, render, warn_not_specified }; + let settings = PreprocessSettings { + highlight_inline, + typst_default, + render, + warn_not_specified, + }; book.sections.iter_mut().try_for_each(|section| { let mut build_dir = ctx.root.clone(); @@ -114,9 +122,10 @@ fn process_chapter( build_dir: &PathBuf, ) -> Result<()> { if let BookItem::Chapter(chapter) = section { - chapter.sub_items.iter_mut().try_for_each(|section| { - process_chapter(section, settings, build_dir) - })?; + chapter + .sub_items + .iter_mut() + .try_for_each(|section| process_chapter(section, settings, build_dir))?; let events = new_cmark_parser(&chapter.content, false); let mut new_events = Vec::new(); @@ -166,20 +175,11 @@ fn process_chapter( chapter.name.clone(), !lang.contains("nopreamble"), ); + let file = file.to_str().unwrap(); compile_errors.extend(err); - html += format!( - r#"
Rendered image
"# - ).as_str(); + html += format!("").as_str(); } new_events.push(Event::Html( format!(r#"
{}
"#, html) @@ -208,22 +208,57 @@ fn process_chapter( } } - let mut buf = String::with_capacity(chapter.content.len()); - cmark(new_events.into_iter(), &mut buf) - .map_err(|err| anyhow!("Markdown serialization failed: {}", err))?; - let runtime = tokio::runtime::Builder::new_current_thread() .build() .unwrap(); runtime.block_on(async { join_all(compile_errors).await }); + // Okay, all images are rendered now, so it's time to replace file names with true ones! + + let new_events = new_events.into_iter().map(|e| { + match e { + Event::Html(s) if s.contains(" { + const PATTLENGTH: usize = "').expect("Someone who inserts crazy tags forgot to close the bracket"); + let file = PathBuf::from_str(&s[start+PATTLENGTH..end]).expect("Problem when decoding path"); + + let inner = get_images(file).map(|name| { + format!( + r#"
Rendered image
"#)}).collect::(); + + let new_s = s[..start].to_owned() + inner.as_str() + &s[end+1..]; + + Event::Html(new_s.into()) + }, + e => e + } + }); + + let mut buf = String::with_capacity(chapter.content.len()); + cmark(new_events.into_iter(), &mut buf) + .map_err(|err| anyhow!("Markdown serialization failed: {}", err))?; + chapter.content = buf; } Ok(()) } -fn get_lang<'a>(t: &'a Tag, settings: &PreprocessSettings, chapter: Option<&str>) -> Option<&'a str> { +fn get_lang<'a>( + t: &'a Tag, + settings: &PreprocessSettings, + chapter: Option<&str>, +) -> Option<&'a str> { let default = if settings.typst_default { Some("typ") } else { @@ -231,16 +266,17 @@ fn get_lang<'a>(t: &'a Tag, settings: &PreprocessSettings, chapter: Option<&str> }; if let Tag::CodeBlock(ref kind) = *t { match kind { - CodeBlockKind::Fenced(kind) => (!kind.is_empty()).then(|| kind.as_ref()) - .or_else(|| { + CodeBlockKind::Fenced(kind) => { + (!kind.is_empty()).then(|| kind.as_ref()).or_else(|| { if settings.warn_not_specified { if let Some(chapter) = chapter { eprintln!("Codeblock language not specified in {}", chapter) } } default - }), - CodeBlockKind::Indented => default + }) + } + CodeBlockKind::Indented => default, } } else { None @@ -271,11 +307,8 @@ fn highlight(s: CowStr, inline: bool) -> String { for line in LinesWithEndings::from(&s) { let regions = highlighter.highlight_line(line, &SYNTAX).unwrap(); - append_highlighted_html_for_styled_line( - ®ions[..], - IncludeBackground::No, - &mut html, - ).unwrap(); + append_highlighted_html_for_styled_line(®ions[..], IncludeBackground::No, &mut html) + .unwrap(); } html.push_str("\n"); @@ -295,22 +328,45 @@ fn sha256_hash(input: &str) -> String { format!("{:x}", res) } +fn get_images(src: PathBuf) -> impl Iterator { + let mut n = 1; + let fbase = src.file_name().unwrap().to_str().unwrap().to_owned(); + + iter::from_fn(move || { + let path = src.clone(); + let path = path.with_file_name(fbase.clone() + format!("-{n}.svg").as_str()); + + if path.exists() { + n += 1; + Some(path.file_name().unwrap().to_string_lossy().into_owned()) + } else { + None + } + }) + .fuse() +} + fn render_block( src: String, mut dir: PathBuf, mut build_dir: PathBuf, name: String, preamble: bool, -) -> (String, Option>) { +) -> (PathBuf, Option>) { let filename = sha256_hash(&src); let mut output = dir.clone(); output.push("typst-img"); - output.push(filename.clone() + ".svg"); + let mut check = output.clone(); + let mut cut_output = output.clone(); + cut_output.push(filename.clone()); + + output.push(filename.clone() + "-{n}.svg"); + check.push(filename.clone() + "-{1}.svg"); let mut command = None; - if !output.exists() { - fs::create_dir_all(&output.parent().unwrap()).expect("Can't create a dir"); + if !check.exists() { + fs::create_dir_all(output.parent().unwrap()).expect("Can't create a dir"); dir.push("typst-src"); fs::create_dir_all(&dir).expect("Can't create a dir"); dir.push(filename.clone() + ".typ"); @@ -330,16 +386,16 @@ fn render_block( .arg(&output); build_dir.push("fonts"); - + if build_dir.exists() { res = res.arg("--font-path").arg(build_dir) } - + let res = res.output(); command = Some(async move { let output = res.await.expect("Failed").stderr; - + if !output.is_empty() { let stderr = std::io::stderr(); let mut handle = stderr.lock(); @@ -349,5 +405,5 @@ fn render_block( }); } - (filename, command) + (cut_output, command) }