Skip to main content

webfont_generator/result/
mod.rs

1mod regeneration;
2#[cfg(test)]
3mod tests;
4
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex, OnceLock};
7
8use serde_json::{Map, Value};
9
10use crate::incremental::RegenerationState;
11use crate::input::{LoadedSvgFile, ResolvedGenerateWebfontsOptions};
12use crate::rendering::{CachedTemplateData, CarriedRenderCache};
13#[cfg(feature = "bench")]
14use crate::types::GlyphChange;
15
16/// Rendered bytes for each requested output format. Held by [`GenerateWebfontsResult`] and
17/// produced by the generator's format pipeline; grouping them lets an incremental regenerate
18/// refresh every format in a single assignment.
19#[derive(Clone, Default)]
20pub(crate) struct FontOutputs {
21    pub(crate) svg_font: Option<Arc<String>>,
22    pub(crate) ttf_font: Option<Arc<Vec<u8>>>,
23    pub(crate) woff_font: Option<Arc<Vec<u8>>>,
24    pub(crate) woff2_font: Option<Arc<Vec<u8>>>,
25    pub(crate) eot_font: Option<Arc<Vec<u8>>>,
26}
27
28/// Failure from asynchronous regeneration. Ordinary regeneration errors retain the input result
29/// so callers can recover it with [`RegenerateError::into_result`] and retry.
30pub struct RegenerateError {
31    result: Option<Box<GenerateWebfontsResult>>,
32    source: std::io::Error,
33}
34
35impl RegenerateError {
36    pub(crate) fn new(result: Option<GenerateWebfontsResult>, source: std::io::Error) -> Self {
37        Self {
38            result: result.map(Box::new),
39            source,
40        }
41    }
42
43    /// Return the result that was consumed by the failed operation. This is `None` only if the
44    /// blocking task was cancelled by the runtime before it could return the result.
45    pub fn into_result(self) -> Option<GenerateWebfontsResult> {
46        self.result.map(|result| *result)
47    }
48
49    /// Return both the recoverable result and the underlying I/O error.
50    pub fn into_parts(self) -> (Option<GenerateWebfontsResult>, std::io::Error) {
51        (self.result.map(|result| *result), self.source)
52    }
53}
54
55impl std::fmt::Debug for RegenerateError {
56    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        formatter
58            .debug_struct("RegenerateError")
59            .field("recoverable", &self.result.is_some())
60            .field("source", &self.source)
61            .finish()
62    }
63}
64
65impl std::fmt::Display for RegenerateError {
66    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        self.source.fmt(formatter)
68    }
69}
70
71impl std::error::Error for RegenerateError {
72    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73        Some(&self.source)
74    }
75}
76
77/// Result of a successful `generateWebfonts` call. Exposes the generated
78/// font bytes (or `null` for formats that were not requested) and methods to
79/// render the CSS and HTML preview.
80pub struct GenerateWebfontsResult {
81    pub(crate) cached: OnceLock<Result<CachedTemplateData, String>>,
82    /// Render-cache entries carried across an incremental `regenerate` to seed the rebuilt
83    /// [`CachedTemplateData`], so CSS/HTML that
84    /// doesn't depend on what changed isn't re-rendered. `None` for a normal build.
85    pub(crate) carried_render: Option<CarriedRenderCache>,
86    pub(crate) css_context: Option<Map<String, Value>>,
87    pub(crate) fonts: FontOutputs,
88    pub(crate) html_context: Option<Map<String, Value>>,
89    pub(crate) options: Arc<ResolvedGenerateWebfontsOptions>,
90    pub(crate) regeneration_state: Arc<Mutex<Option<RegenerationState>>>,
91    pub(crate) source_files: Arc<Vec<LoadedSvgFile>>,
92}
93
94impl GenerateWebfontsResult {
95    pub(crate) fn snapshot_for_regeneration(&self, state: RegenerationState) -> Self {
96        Self {
97            cached: OnceLock::new(),
98            carried_render: self.render_cache_source(),
99            css_context: self.css_context.clone(),
100            fonts: self.fonts.clone(),
101            html_context: self.html_context.clone(),
102            options: self.options.clone(),
103            regeneration_state: Arc::new(Mutex::new(Some(state))),
104            source_files: self.source_files.clone(),
105        }
106    }
107
108    pub(crate) fn require_ordinary_regeneration(&self) -> std::io::Result<()> {
109        if self.options.variants.is_some() {
110            return Err(std::io::Error::new(
111                std::io::ErrorKind::Unsupported,
112                "Single file lists do not support variant results; supply complete variant file sets.",
113            ));
114        }
115        Ok(())
116    }
117
118    pub(crate) fn take_regeneration_state(&self) -> std::io::Result<RegenerationState> {
119        if !self.options.incremental {
120            return Err(std::io::Error::new(
121                std::io::ErrorKind::InvalidInput,
122                "regenerate requires the font to be generated with `incremental` enabled.",
123            ));
124        }
125        self.regeneration_state
126            .lock()
127            .unwrap()
128            .take()
129            .ok_or_else(|| {
130                std::io::Error::new(
131                    std::io::ErrorKind::WouldBlock,
132                    "This result is already regenerating or has been replaced.",
133                )
134            })
135    }
136
137    #[cfg(feature = "bench")]
138    pub(crate) fn restore_regeneration_state(&self, state: RegenerationState) {
139        *self.regeneration_state.lock().unwrap() = Some(state);
140    }
141
142    pub(crate) fn seed_written_outputs(&self, written_outputs: HashMap<String, [u8; 16]>) {
143        if let Some(state) = self.regeneration_state.lock().unwrap().as_mut() {
144            state.written_outputs = written_outputs;
145        }
146    }
147
148    #[doc(hidden)]
149    #[cfg(feature = "bench")]
150    pub fn regenerate_owned_for_bench(
151        &self,
152        files: &crate::RegenerationFiles,
153        changes: &[(String, GlyphChange)],
154    ) -> std::io::Result<Self> {
155        let state = self.take_regeneration_state()?;
156        let mut replacement = self.snapshot_for_regeneration(state);
157        match replacement.regenerate(files, changes) {
158            Ok(()) => Ok(replacement),
159            Err(error) => {
160                if let Ok(state) = replacement.take_regeneration_state() {
161                    self.restore_regeneration_state(state);
162                }
163                Err(error)
164            }
165        }
166    }
167
168    /// Returns the EOT font bytes, if generated.
169    pub fn eot_bytes(&self) -> Option<&[u8]> {
170        self.fonts.eot_font.as_ref().map(|v| v.as_ref().as_slice())
171    }
172
173    /// Returns the SVG font string, if generated.
174    pub fn svg_string(&self) -> Option<&str> {
175        self.fonts.svg_font.as_ref().map(|v| v.as_ref().as_str())
176    }
177
178    /// Returns the TTF font bytes, if generated.
179    pub fn ttf_bytes(&self) -> Option<&[u8]> {
180        self.fonts.ttf_font.as_ref().map(|v| v.as_ref().as_slice())
181    }
182
183    /// Returns the WOFF font bytes, if generated.
184    pub fn woff_bytes(&self) -> Option<&[u8]> {
185        self.fonts.woff_font.as_ref().map(|v| v.as_ref().as_slice())
186    }
187
188    /// Returns the WOFF2 font bytes, if generated.
189    pub fn woff2_bytes(&self) -> Option<&[u8]> {
190        self.fonts
191            .woff2_font
192            .as_ref()
193            .map(|v| v.as_ref().as_slice())
194    }
195}