Skip to main content

webfont_generator/result/
mod.rs

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