From db0808e5f265db06cae6eea28c57fc6017f5d622 Mon Sep 17 00:00:00 2001 From: mtmatt Date: Tue, 6 Oct 2026 15:53:58 +0800 Subject: [PATCH] fix(converter): recognize .typ as Typst input and output Typst files use the .typ extension, but only .typst was recognized, so .typ -> pdf failed with "No conversion path". Normalize .typ to the typst node, read it with pandoc's typst reader, and write Typst output as .typ. --- .../src/converter/document/conversion.rs | 3 ++- swift-shifter/src/converter/document/utils.rs | 16 +++++++++++++--- swift-shifter/src/converter/graph.rs | 1 + swift-shifter/src/converter/tests.rs | 8 ++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/swift-shifter/src/converter/document/conversion.rs b/swift-shifter/src/converter/document/conversion.rs index ccd3574..33cb068 100644 --- a/swift-shifter/src/converter/document/conversion.rs +++ b/swift-shifter/src/converter/document/conversion.rs @@ -3,6 +3,7 @@ use crate::converter::document::llm::*; use crate::converter::document::pdf; use crate::converter::document::types::*; use crate::converter::document::utils::*; +use crate::converter::graph::normalize_ext; use std::path::Path; use tauri::Emitter; @@ -369,7 +370,7 @@ pub async fn convert_document( // typst → pdf is best served by `typst compile` directly: it's the native // toolchain, needs no LaTeX/PDF engine, and renders faster than pandoc. - if input_ext == "typst" && target_format == "pdf" { + if normalize_ext(&input_ext) == "typst" && target_format == "pdf" { return convert_typst_to_pdf(app, path, output_dir).await; } diff --git a/swift-shifter/src/converter/document/utils.rs b/swift-shifter/src/converter/document/utils.rs index 444185a..0dc16e8 100644 --- a/swift-shifter/src/converter/document/utils.rs +++ b/swift-shifter/src/converter/document/utils.rs @@ -62,7 +62,7 @@ pub fn ext_to_pandoc_format(ext: &str) -> &str { "md" | "markdown" => "markdown", "txt" => "plain", "tex" | "latex" => "latex", - "typst" => "typst", + "typ" | "typst" => "typst", "epub" => "epub", "pdf" => "pdf", _ => ext, @@ -79,10 +79,12 @@ pub fn ext_to_pandoc_input_format(ext: &str) -> &str { } } -/// Output file extension for a given target format keyword. +/// Output file extension for a given target format keyword. Typst output is +/// written as `.typ`, the extension `typst compile` and editors expect. pub fn target_to_ext(target: &str) -> &str { match target { "latex" => "tex", + "typst" => "typ", _ => target, } } @@ -101,8 +103,16 @@ mod tests { #[test] fn input_format_matches_output_for_non_txt() { - for ext in ["md", "tex", "latex", "typst", "epub", "pdf"] { + for ext in ["md", "tex", "latex", "typ", "typst", "epub", "pdf"] { assert_eq!(ext_to_pandoc_input_format(ext), ext_to_pandoc_format(ext)); } } + + #[test] + fn typst_reads_from_typ_and_writes_typ() { + assert_eq!(ext_to_pandoc_input_format("typ"), "typst"); + assert_eq!(target_to_ext("typst"), "typ"); + assert_eq!(target_to_ext("latex"), "tex"); + assert_eq!(target_to_ext("md"), "md"); + } } diff --git a/swift-shifter/src/converter/graph.rs b/swift-shifter/src/converter/graph.rs index 726eda0..81fcf1b 100644 --- a/swift-shifter/src/converter/graph.rs +++ b/swift-shifter/src/converter/graph.rs @@ -40,6 +40,7 @@ pub fn normalize_ext(ext: &str) -> &str { "tif" => "tiff", "markdown" => "md", "latex" => "tex", + "typ" => "typst", "heif" => "heic", other => other, } diff --git a/swift-shifter/src/converter/tests.rs b/swift-shifter/src/converter/tests.rs index d717dc6..dab671d 100644 --- a/swift-shifter/src/converter/tests.rs +++ b/swift-shifter/src/converter/tests.rs @@ -167,6 +167,7 @@ fn test_detect_output_formats_snapshot() { ("/x.tex", &["md", "html", "pdf", "typst"]), ("/x.latex", &["md", "html", "pdf", "typst"]), ("/x.typst", &["md", "html", "pdf", "tex"]), + ("/x.typ", &["md", "html", "pdf", "tex"]), ("/x.epub", &["pdf", "mobi", "md", "html"]), ("/x.mobi", &["epub", "pdf", "html", "md"]), ("/x.pdf", &["epub", "mobi", "html", "md"]), @@ -220,6 +221,13 @@ fn test_find_path_normalizes_aliases() { assert_eq!(p, vec!["jpg".to_string(), "png".to_string()]); } +#[test] +fn test_find_path_accepts_typ_extension() { + // `.typ` is Typst's real extension; it must route like `.typst`. + let p = crate::converter::graph::find_path("typ", "pdf").unwrap(); + assert_eq!(p, vec!["typst".to_string(), "pdf".to_string()]); +} + #[test] fn test_find_path_unreachable_returns_none() { assert!(crate::converter::graph::find_path("csv", "toml").is_none());