Skip to main content

quillmark_core/
backend.rs

1//! # Backend Trait
2//!
3//! Backend trait for implementing output format backends.
4//!
5//! ## Overview
6//!
7//! The [`Backend`] trait defines the interface that backends must implement
8//! to support different output formats (PDF, SVG, TXT, etc.).
9//!
10//! ## Trait Definition
11//!
12//! ```rust,ignore
13//! pub trait Backend: Send + Sync {
14//!     fn id(&self) -> &'static str;
15//!     fn supported_formats(&self) -> &'static [OutputFormat];
16//!     fn plate_extension_types(&self) -> &'static [&'static str];
17//!     fn allow_auto_plate(&self) -> bool;
18//!     fn compile(
19//!         &self,
20//!         plated: &str,
21//!         quill: &Quill,
22//!         opts: &RenderOptions,
23//!     ) -> Result<RenderResult, RenderError>;
24//! }
25//! ```
26//!
27//! ## Implementation Guide
28//!
29//! ### Required Methods
30//!
31//! #### `id()`
32//! Return a unique backend identifier (e.g., "typst", "latex").
33//!
34//! #### `supported_formats()`
35//! Return a slice of [`OutputFormat`] variants this backend supports.
36//!
37//! #### `plate_extension_types()`
38//! Return the file extensions for plate files (e.g., &[".typ"], &[".tex"]).
39//! Return an empty array to disable custom plate files.
40//!
41//! #### `allow_auto_plate()`
42//! Return whether automatic JSON plate generation is allowed.
43//!
44//! #### `compile()`
45//! Compile plated content into final artifacts.
46//!
47//! ```no_run
48//! # use quillmark_core::{Quill, RenderOptions, Artifact, OutputFormat, RenderError, RenderResult};
49//! # struct MyBackend;
50//! # impl MyBackend {
51//! fn compile(
52//!     &self,
53//!     plated: &str,
54//!     quill: &Quill,
55//!     opts: &RenderOptions,
56//! ) -> Result<RenderResult, RenderError> {
57//!     // 1. Create compilation environment
58//!     // 2. Load assets from quill
59//!     // 3. Compile plated content
60//!     // 4. Handle errors and map to Diagnostics
61//!     // 5. Return RenderResult with artifacts and output format
62//!     # let compiled_pdf = vec![];
63//!     # let format = OutputFormat::Pdf;
64//!     
65//!     let artifacts = vec![Artifact {
66//!         bytes: compiled_pdf,
67//!         output_format: format,
68//!     }];
69//!     
70//!     Ok(RenderResult::new(artifacts, format))
71//! }
72//! # }
73//! ```
74//!
75//! ## Example Implementation
76//!
77//! See `backends/quillmark-typst` for a complete backend implementation example.
78//!
79//! ## Thread Safety
80//!
81//! The [`Backend`] trait requires `Send + Sync` to enable concurrent rendering.
82//! All backend implementations must be thread-safe.
83
84use crate::error::RenderError;
85use crate::value::QuillValue;
86use crate::{CompiledDocument, Diagnostic, OutputFormat, Quill, RenderOptions, Severity};
87use std::collections::HashMap;
88
89/// Backend trait for rendering different output formats
90pub trait Backend: Send + Sync {
91    /// Get the backend identifier (e.g., "typst", "latex")
92    fn id(&self) -> &'static str;
93
94    /// Get supported output formats
95    fn supported_formats(&self) -> &'static [OutputFormat];
96
97    /// Get the plate file extensions accepted by this backend (e.g., &[".typ", ".tex"])
98    /// Returns an empty array to disable custom plate files.
99    fn plate_extension_types(&self) -> &'static [&'static str];
100
101    /// Compile the plate content with JSON data into final artifacts.
102    ///
103    /// # Arguments
104    ///
105    /// * `plate_content` - The plate file content (e.g., Typst source)
106    /// * `quill` - The quill template containing assets and configuration
107    /// * `opts` - Render options including output format
108    /// * `json_data` - JSON value containing the document data
109    fn compile(
110        &self,
111        plate_content: &str,
112        quill: &Quill,
113        opts: &RenderOptions,
114        json_data: &serde_json::Value,
115    ) -> Result<crate::RenderResult, RenderError>;
116
117    /// Compile a document to an opaque backend-specific handle for selective page rendering.
118    ///
119    /// Default implementation returns a "not supported" error so existing backends do not
120    /// need to implement this method.
121    fn compile_to_document(
122        &self,
123        _plate_content: &str,
124        _quill: &Quill,
125        _json_data: &serde_json::Value,
126    ) -> Result<CompiledDocument, RenderError> {
127        Err(RenderError::UnsupportedBackend {
128            diag: Box::new(
129                Diagnostic::new(
130                    Severity::Error,
131                    format!(
132                        "Backend '{}' does not support compile_to_document()",
133                        self.id()
134                    ),
135                )
136                .with_code("backend::compile_to_document_not_supported".to_string()),
137            ),
138        })
139    }
140
141    /// Render selected pages from a previously compiled document.
142    ///
143    /// - `pages = None` renders all pages in document order.
144    /// - `pages = Some(&[])` renders zero pages.
145    ///
146    /// Default implementation returns a "not supported" error so existing backends do not
147    /// need to implement this method.
148    fn render_pages(
149        &self,
150        _doc: &CompiledDocument,
151        _pages: Option<&[usize]>,
152        _format: OutputFormat,
153        _ppi: Option<f32>,
154    ) -> Result<crate::RenderResult, RenderError> {
155        Err(RenderError::UnsupportedBackend {
156            diag: Box::new(
157                Diagnostic::new(
158                    Severity::Error,
159                    format!("Backend '{}' does not support render_pages()", self.id()),
160                )
161                .with_code("backend::render_pages_not_supported".to_string()),
162            ),
163        })
164    }
165
166    /// Provide an embedded default Quill for this backend.
167    ///
168    /// Returns `None` if the backend does not provide a default Quill.
169    /// The returned Quill will be registered with the name `__default__`
170    /// during backend registration if no default Quill already exists.
171    ///
172    /// # Example
173    ///
174    /// ```no_run
175    /// # use quillmark_core::{Backend, Quill, FileTreeNode};
176    /// # use std::collections::HashMap;
177    /// # struct MyBackend;
178    /// # impl Backend for MyBackend {
179    /// #     fn id(&self) -> &'static str { "my" }
180    /// #     fn supported_formats(&self) -> &'static [quillmark_core::OutputFormat] { &[] }
181    /// #     fn plate_extension_types(&self) -> &'static [&'static str] { &[] }
182    /// #     fn compile(&self, _: &str, _: &Quill, _: &quillmark_core::RenderOptions, _: &serde_json::Value) -> Result<quillmark_core::RenderResult, quillmark_core::RenderError> { todo!() }
183    /// fn default_quill(&self) -> Option<Quill> {
184    ///     // Build embedded default Quill from files
185    ///     let mut files = HashMap::new();
186    ///     files.insert("Quill.yaml".to_string(), FileTreeNode::File {
187    ///         contents: b"Quill:\n  name: __default__\n  backend: my\n".to_vec(),
188    ///     });
189    ///     let root = FileTreeNode::Directory { files };
190    ///     Quill::from_tree(root).ok()
191    /// }
192    /// # }
193    /// ```
194    fn default_quill(&self) -> Option<Quill> {
195        None
196    }
197
198    /// Transform field values according to backend-specific rules.
199    ///
200    /// This method is called before JSON serialization to allow backends
201    /// to transform field values. For example, the Typst backend converts
202    /// markdown fields to Typst markup based on schema type annotations.
203    ///
204    /// The default implementation returns fields unchanged.
205    ///
206    /// # Arguments
207    ///
208    /// * `fields` - The normalized document fields
209    /// * `schema` - The Quill schema (JSON Schema) for field type information
210    ///
211    /// # Returns
212    ///
213    /// Transformed fields ready for JSON serialization
214    ///
215    /// # Example
216    ///
217    /// ```no_run
218    /// # use quillmark_core::{QuillValue, Backend};
219    /// # use std::collections::HashMap;
220    /// # struct MyBackend;
221    /// # impl MyBackend {
222    /// fn transform_fields(
223    ///     &self,
224    ///     fields: &HashMap<String, QuillValue>,
225    ///     schema: &QuillValue,
226    /// ) -> HashMap<String, QuillValue> {
227    ///     // Transform markdown fields to backend-specific format
228    ///     fields.clone()
229    /// }
230    /// # }
231    /// ```
232    fn transform_fields(
233        &self,
234        fields: &HashMap<String, QuillValue>,
235        _schema: &QuillValue,
236    ) -> HashMap<String, QuillValue> {
237        // Default: return fields unchanged
238        fields.clone()
239    }
240}