webfont_generator/rendering/
result.rs1#[cfg(test)]
2mod tests;
3
4use std::collections::HashMap;
5use std::sync::Mutex;
6
7use serde_json::{Map, Value};
8
9use super::css::{
10 SharedTemplateData, TemplateDependencies, build_css_context, make_src,
11 render_css_with_hbs_context, render_css_with_src_mutate,
12};
13use super::html::{
14 build_html_context, build_html_registry_and_dependencies, render_default_html_with_styles,
15 render_html_with_hbs_context,
16};
17use super::shared::{render_with_field_swap, to_io_err};
18use crate::result::GenerateWebfontsResult;
19use crate::types::FontType;
20
21#[derive(Clone, Default)]
25pub(crate) struct RenderCache {
26 css_no_urls: Option<String>,
28 css_last_urls: Option<HashMap<FontType, String>>,
30 css_last_result: Option<String>,
31 html_no_urls: Option<String>,
33 html_last_urls: Option<HashMap<FontType, String>>,
35 html_last_result: Option<String>,
36}
37
38#[derive(Clone)]
39pub(crate) struct CarriedRenderCache {
40 cache: RenderCache,
41 css_dependencies: TemplateDependencies,
42 html_dependencies: TemplateDependencies,
43}
44
45pub(crate) struct CachedTemplateData {
46 pub(crate) shared: SharedTemplateData,
47 pub(crate) css_context: Map<String, Value>,
48 pub(crate) css_hbs_context: Mutex<handlebars::Context>,
49 pub(crate) html_context: Map<String, Value>,
50 pub(crate) html_hbs_context: Mutex<handlebars::Context>,
51 pub(crate) html_template_dependencies: TemplateDependencies,
52 pub(crate) html_registry: Option<handlebars::Handlebars<'static>>,
53 pub(crate) render_cache: Mutex<RenderCache>,
54}
55
56impl GenerateWebfontsResult {
57 fn validate_render_urls(
58 &self,
59 urls: Option<&HashMap<FontType, String>>,
60 ) -> std::io::Result<()> {
61 if self.options.variants.is_some()
62 && urls.is_some_and(|urls| {
63 urls.contains_key(&FontType::Svg) || urls.contains_key(&FontType::Eot)
64 })
65 {
66 return Err(std::io::Error::new(
67 std::io::ErrorKind::InvalidInput,
68 "SVG and EOT URLs are unsupported for variant results.",
69 ));
70 }
71 Ok(())
72 }
73 #[cfg(test)]
74 pub(crate) fn has_carried_css_no_urls_for_test(&self) -> bool {
75 self.carried_render
76 .as_ref()
77 .is_some_and(|carried| carried.cache.css_no_urls.is_some())
78 }
79
80 #[cfg(test)]
81 pub(crate) fn has_carried_html_no_urls_for_test(&self) -> bool {
82 self.carried_render
83 .as_ref()
84 .is_some_and(|carried| carried.cache.html_no_urls.is_some())
85 }
86
87 pub(crate) fn get_cached_io(&self) -> std::io::Result<&CachedTemplateData> {
88 self.cached
89 .get_or_init(|| {
90 let shared = SharedTemplateData::new(&self.options, &self.source_files)
91 .map_err(|e| e.to_string())?;
92 let css_context = match &self.css_context {
93 Some(ctx) => ctx.clone(),
94 None => build_css_context(&self.options, &shared),
95 };
96 let html_context = match &self.html_context {
97 Some(ctx) => ctx.clone(),
98 None => build_html_context(&self.options, &shared, &self.source_files, None)
99 .map_err(|e| e.to_string())?,
100 };
101 let (html_registry, html_template_dependencies) =
102 build_html_registry_and_dependencies(&self.options)
103 .map_err(|e| e.to_string())?;
104 let css_hbs_context =
105 handlebars::Context::wraps(&css_context).map_err(|e| e.to_string())?;
106 let html_hbs_context =
107 handlebars::Context::wraps(&html_context).map_err(|e| e.to_string())?;
108 Ok(CachedTemplateData {
109 shared,
110 css_context,
111 css_hbs_context: Mutex::new(css_hbs_context),
112 html_context,
113 html_hbs_context: Mutex::new(html_hbs_context),
114 html_template_dependencies,
115 html_registry,
116 render_cache: Mutex::new(
119 self.carried_render
120 .as_ref()
121 .map(|carried| carried.cache.clone())
122 .unwrap_or_default(),
123 ),
124 })
125 })
126 .as_ref()
127 .map_err(to_io_err)
128 }
129
130 pub(crate) fn reusable_render_cache(
131 &self,
132 names_unchanged: bool,
133 codepoints_unchanged: bool,
134 ) -> Option<CarriedRenderCache> {
135 self.render_cache_source().map(|carried| {
136 let css_deps = carried.css_dependencies;
137 let css_no_urls_unchanged =
138 css_deps.can_reuse_css_no_urls(names_unchanged, codepoints_unchanged);
139 let css_with_urls_unchanged =
140 css_deps.can_reuse_css_with_urls(names_unchanged, codepoints_unchanged);
141 let html_no_urls_unchanged = carried.html_dependencies.can_reuse_html(
142 names_unchanged,
143 codepoints_unchanged,
144 css_no_urls_unchanged,
145 );
146 let html_with_urls_unchanged = carried.html_dependencies.can_reuse_html(
147 names_unchanged,
148 codepoints_unchanged,
149 css_with_urls_unchanged,
150 );
151
152 let rc = carried.cache;
153 CarriedRenderCache {
154 css_dependencies: carried.css_dependencies,
155 html_dependencies: carried.html_dependencies,
156 cache: RenderCache {
157 css_no_urls: css_no_urls_unchanged
158 .then(|| rc.css_no_urls.clone())
159 .flatten(),
160 html_no_urls: html_no_urls_unchanged
161 .then(|| rc.html_no_urls.clone())
162 .flatten(),
163 css_last_urls: css_with_urls_unchanged
164 .then(|| rc.css_last_urls.clone())
165 .flatten(),
166 css_last_result: css_with_urls_unchanged
167 .then(|| rc.css_last_result.clone())
168 .flatten(),
169 html_last_urls: html_with_urls_unchanged
170 .then(|| rc.html_last_urls.clone())
171 .flatten(),
172 html_last_result: html_with_urls_unchanged
173 .then(|| rc.html_last_result.clone())
174 .flatten(),
175 },
176 }
177 })
178 }
179
180 pub(crate) fn render_cache_source(&self) -> Option<CarriedRenderCache> {
181 self.cached
182 .get()
183 .and_then(|cached| cached.as_ref().ok())
184 .map(|cached| CarriedRenderCache {
185 cache: cached.render_cache.lock().unwrap().clone(),
186 css_dependencies: cached.shared.css_template_dependencies,
187 html_dependencies: cached.html_template_dependencies,
188 })
189 .or_else(|| self.carried_render.clone())
190 }
191
192 pub fn generate_css_pure(
196 &self,
197 urls: Option<HashMap<FontType, String>>,
198 ) -> std::io::Result<String> {
199 self.validate_render_urls(urls.as_ref())?;
200 let cached = self.get_cached_io()?;
201 let mut rc = cached.render_cache.lock().unwrap();
202
203 match &urls {
204 None => {
205 if let Some(result) = &rc.css_no_urls {
206 return Ok(result.clone());
207 }
208 let ctx = cached.css_hbs_context.lock().unwrap();
209 let result =
210 render_css_with_hbs_context(&cached.shared, &ctx, &cached.css_context)?;
211 rc.css_no_urls = Some(result.clone());
212 Ok(result)
213 }
214 Some(urls) => {
215 if !cached.shared.css_template_uses_src {
217 drop(rc);
218 return self.generate_css_pure(None);
219 }
220 if rc.css_last_urls.as_ref() == Some(urls)
221 && let Some(result) = &rc.css_last_result
222 {
223 return Ok(result.clone());
224 }
225 let src = make_src(&self.options, urls);
226 let mut ctx = cached.css_hbs_context.lock().unwrap();
227 let result = render_css_with_src_mutate(
228 &cached.shared,
229 &mut ctx,
230 &cached.css_context,
231 &src,
232 )?;
233 rc.css_last_urls = Some(urls.clone());
234 rc.css_last_result = Some(result.clone());
235 Ok(result)
236 }
237 }
238 }
239
240 pub fn generate_html_pure(
244 &self,
245 urls: Option<HashMap<FontType, String>>,
246 ) -> std::io::Result<String> {
247 self.validate_render_urls(urls.as_ref())?;
248 let cached = self.get_cached_io()?;
249 let mut rc = cached.render_cache.lock().unwrap();
250
251 match &urls {
252 None => {
253 if let Some(result) = &rc.html_no_urls {
254 return Ok(result.clone());
255 }
256 let ctx = cached.html_hbs_context.lock().unwrap();
257 let result = render_html_with_hbs_context(
258 cached.html_registry.as_ref(),
259 &ctx,
260 &cached.html_context,
261 )?;
262 rc.html_no_urls = Some(result.clone());
263 Ok(result)
264 }
265 Some(urls) => {
266 if !cached.shared.css_template_uses_src {
268 drop(rc);
269 return self.generate_html_pure(None);
270 }
271 if rc.html_last_urls.as_ref() == Some(urls)
272 && let Some(result) = &rc.html_last_result
273 {
274 return Ok(result.clone());
275 }
276 let src = make_src(&self.options, urls);
278 let styles = {
279 let mut css_ctx = cached.css_hbs_context.lock().unwrap();
280 render_css_with_src_mutate(
281 &cached.shared,
282 &mut css_ctx,
283 &cached.css_context,
284 &src,
285 )?
286 };
287 if self.options.html_template.is_none() {
289 let result = render_default_html_with_styles(&cached.html_context, &styles);
290 rc.html_last_urls = Some(urls.clone());
291 rc.html_last_result = Some(result.clone());
292 return Ok(result);
293 }
294 let mut html_ctx = cached.html_hbs_context.lock().unwrap();
296 let registry = cached
297 .html_registry
298 .as_ref()
299 .expect("HTML registry should exist for custom template");
300 let result = render_with_field_swap(
301 &mut html_ctx,
302 "styles",
303 serde_json::Value::String(styles),
304 |ctx| registry.render_with_context("html", ctx).map_err(to_io_err),
305 )?;
306 rc.html_last_urls = Some(urls.clone());
307 rc.html_last_result = Some(result.clone());
308 Ok(result)
309 }
310 }
311 }
312}