Skip to main content

webfont_generator/result/
mod.rs

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