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}