quillmark_typst/
lib.rs

1//! # Typst Backend for Quillmark
2//!
3//! This crate provides a complete Typst backend implementation that converts Markdown
4//! documents to PDF and SVG formats via the Typst typesetting system.
5//!
6//! ## Overview
7//!
8//! The primary entry point is the [`TypstBackend`] struct, which implements the
9//! [`Backend`] trait from `quillmark-core`. Users typically interact with this backend
10//! through the high-level `Workflow` API from the `quillmark` crate.
11//!
12//! ## Features
13//!
14//! - Converts CommonMark Markdown to Typst markup
15//! - Compiles Typst documents to PDF and SVG formats
16//! - Provides template filters for YAML data transformation
17//! - Manages fonts, assets, and packages dynamically
18//! - Thread-safe for concurrent rendering
19//!
20//! ## Example Usage
21//!
22//! ```no_run
23//! use quillmark_typst::TypstBackend;
24//! use quillmark_core::{Backend, Quill, OutputFormat};
25//!
26//! let backend = TypstBackend::default();
27//! let quill = Quill::from_path("path/to/quill").unwrap();
28//!
29//! // Use with Workflow API (recommended)
30//! // let workflow = Workflow::new(Box::new(backend), quill);
31//! ```
32//! ## Modules
33//!
34//! - [`convert`] - Markdown to Typst conversion utilities
35//! - [`compile`] - Typst to PDF/SVG compilation functions
36
37pub mod compile;
38pub mod convert;
39mod error_mapping;
40mod filters;
41mod world;
42use filters::{
43    asset_filter, content_filter, date_filter, dict_filter, lines_filter, string_filter,
44};
45use quillmark_core::{Artifact, Backend, Glue, OutputFormat, Quill, RenderError, RenderOptions};
46
47/// Typst backend implementation for Quillmark.
48pub struct TypstBackend;
49
50impl Backend for TypstBackend {
51    fn id(&self) -> &'static str {
52        "typst"
53    }
54
55    fn supported_formats(&self) -> &'static [OutputFormat] {
56        &[OutputFormat::Pdf, OutputFormat::Svg]
57    }
58
59    fn glue_type(&self) -> &'static str {
60        ".typ"
61    }
62
63    fn register_filters(&self, glue: &mut Glue) {
64        // Register basic filters (simplified for now)
65        glue.register_filter("String", string_filter);
66        glue.register_filter("Lines", lines_filter);
67        glue.register_filter("Date", date_filter);
68        glue.register_filter("Dict", dict_filter);
69        glue.register_filter("Content", content_filter);
70        glue.register_filter("Asset", asset_filter);
71    }
72
73    fn compile(
74        &self,
75        glued_content: &str,
76        quill: &Quill,
77        opts: &RenderOptions,
78    ) -> Result<Vec<Artifact>, RenderError> {
79        let format = opts.output_format.unwrap_or(OutputFormat::Pdf);
80
81        // Check if format is supported
82        if !self.supported_formats().contains(&format) {
83            return Err(RenderError::FormatNotSupported {
84                backend: self.id().to_string(),
85                format,
86            });
87        }
88
89        println!("Typst backend compiling for quill: {}", quill.name);
90
91        match format {
92            OutputFormat::Pdf => {
93                let bytes = compile::compile_to_pdf(quill, glued_content)?;
94                Ok(vec![Artifact {
95                    bytes,
96                    output_format: OutputFormat::Pdf,
97                }])
98            }
99            OutputFormat::Svg => {
100                let svg_pages = compile::compile_to_svg(quill, glued_content)?;
101                Ok(svg_pages
102                    .into_iter()
103                    .map(|bytes| Artifact {
104                        bytes,
105                        output_format: OutputFormat::Svg,
106                    })
107                    .collect())
108            }
109            OutputFormat::Txt => Err(RenderError::FormatNotSupported {
110                backend: self.id().to_string(),
111                format: OutputFormat::Txt,
112            }),
113        }
114    }
115}
116
117impl Default for TypstBackend {
118    /// Creates a new [`TypstBackend`] instance.
119    fn default() -> Self {
120        Self
121    }
122}
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_backend_info() {
129        let backend = TypstBackend::default();
130        assert_eq!(backend.id(), "typst");
131        assert_eq!(backend.glue_type(), ".typ");
132        assert!(backend.supported_formats().contains(&OutputFormat::Pdf));
133        assert!(backend.supported_formats().contains(&OutputFormat::Svg));
134    }
135}
136
137// Re-export for compatibility
138pub use TypstBackend as backend;