webfont_generator/rendering/
result.rs1#[cfg(test)]
2mod tests;
3
4use std::collections::HashMap;
5use std::sync::{Arc, 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::files::load_html_template;
14use super::html::{
15 build_html_context, render_default_html_with_styles, 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<Arc<String>>,
28 css_last_urls: Option<HashMap<FontType, String>>,
30 css_last_result: Option<Arc<String>>,
31 html_no_urls: Option<Arc<String>>,
33 html_last_urls: Option<HashMap<FontType, String>>,
35 html_last_result: Option<Arc<String>>,
36}
37
38#[derive(Clone)]
39pub(crate) struct CarriedRenderCache {
40 cache: RenderCache,
41 css_dependencies: TemplateDependencies,
42 html_dependencies: TemplateDependencies,
43 css_template_source: Option<Arc<str>>,
44 html_template_source: Option<Arc<str>>,
45}
46
47pub(crate) struct CachedTemplateData {
48 pub(crate) shared: SharedTemplateData,
49 pub(crate) css_context: Map<String, Value>,
50 pub(crate) css_hbs_context: Mutex<handlebars::Context>,
51 pub(crate) html_context: Map<String, Value>,
52 pub(crate) html_hbs_context: Mutex<handlebars::Context>,
53 pub(crate) html_template: super::html::HtmlTemplate,
54 pub(crate) render_cache: Mutex<RenderCache>,
55 #[cfg(test)]
56 pub(crate) html_render_count: std::sync::atomic::AtomicUsize,
57}
58
59impl GenerateWebfontsResult {
60 fn validate_render_urls(
61 &self,
62 urls: Option<&HashMap<FontType, String>>,
63 ) -> std::io::Result<()> {
64 if self.options.variants.is_some()
65 && urls.is_some_and(|urls| {
66 urls.contains_key(&FontType::Svg) || urls.contains_key(&FontType::Eot)
67 })
68 {
69 return Err(std::io::Error::new(
70 std::io::ErrorKind::InvalidInput,
71 "SVG and EOT URLs are unsupported for variant results.",
72 ));
73 }
74 Ok(())
75 }
76 #[cfg(test)]
77 pub(crate) fn has_carried_css_no_urls_for_test(&self) -> bool {
78 self.carried_render
79 .as_ref()
80 .is_some_and(|carried| carried.cache.css_no_urls.is_some())
81 }
82
83 #[cfg(test)]
84 pub(crate) fn has_carried_html_no_urls_for_test(&self) -> bool {
85 self.carried_render
86 .as_ref()
87 .is_some_and(|carried| carried.cache.html_no_urls.is_some())
88 }
89
90 pub(crate) fn get_cached_io(&self) -> std::io::Result<&CachedTemplateData> {
91 self.cached
92 .get_or_init(|| {
93 let shared = SharedTemplateData::from_files(&self.options, &self.source_files)
94 .map_err(|e| e.to_string())?;
95 let css_context = match &self.css_context {
96 Some(ctx) => ctx.clone(),
97 None => build_css_context(&self.options, &shared),
98 };
99 let html_context = match &self.html_context {
100 Some(ctx) => ctx.clone(),
101 None => build_html_context(&self.options, &shared, &self.source_files, None)
102 .map_err(|e| e.to_string())?,
103 };
104 let html_template = load_html_template(&self.options).map_err(|e| e.to_string())?;
105 let render_cache = self
106 .carried_render
107 .as_ref()
108 .map(|carried| {
109 if carried.css_template_source != shared.css_template_source {
110 RenderCache::default()
112 } else if carried.html_template_source != html_template.source {
113 RenderCache {
114 html_no_urls: None,
115 html_last_urls: None,
116 html_last_result: None,
117 ..carried.cache.clone()
118 }
119 } else {
120 carried.cache.clone()
121 }
122 })
123 .unwrap_or_default();
124 let css_hbs_context =
125 handlebars::Context::wraps(&css_context).map_err(|e| e.to_string())?;
126 let html_hbs_context =
127 handlebars::Context::wraps(&html_context).map_err(|e| e.to_string())?;
128 Ok(CachedTemplateData {
129 #[cfg(test)]
130 html_render_count: Default::default(),
131 shared,
132 css_context,
133 css_hbs_context: Mutex::new(css_hbs_context),
134 html_context,
135 html_hbs_context: Mutex::new(html_hbs_context),
136 html_template,
137 render_cache: Mutex::new(render_cache),
140 })
141 })
142 .as_ref()
143 .map_err(to_io_err)
144 }
145
146 pub(crate) fn reusable_render_cache(
147 &self,
148 names_unchanged: bool,
149 codepoints_unchanged: bool,
150 ) -> Option<CarriedRenderCache> {
151 self.render_cache_source().map(|carried| {
152 let css_deps = carried.css_dependencies;
153 let css_no_urls_unchanged =
154 css_deps.can_reuse_css_no_urls(names_unchanged, codepoints_unchanged);
155 let css_with_urls_unchanged =
156 css_deps.can_reuse_css_with_urls(names_unchanged, codepoints_unchanged);
157 let html_no_urls_unchanged = carried.html_dependencies.can_reuse_html(
158 names_unchanged,
159 codepoints_unchanged,
160 css_no_urls_unchanged,
161 );
162 let html_with_urls_unchanged = carried.html_dependencies.can_reuse_html(
163 names_unchanged,
164 codepoints_unchanged,
165 css_with_urls_unchanged,
166 );
167
168 let rc = carried.cache;
169 CarriedRenderCache {
170 css_dependencies: carried.css_dependencies,
171 html_dependencies: carried.html_dependencies,
172 css_template_source: carried.css_template_source,
173 html_template_source: carried.html_template_source,
174 cache: RenderCache {
175 css_no_urls: css_no_urls_unchanged
176 .then(|| rc.css_no_urls.clone())
177 .flatten(),
178 html_no_urls: html_no_urls_unchanged
179 .then(|| rc.html_no_urls.clone())
180 .flatten(),
181 css_last_urls: css_with_urls_unchanged
182 .then(|| rc.css_last_urls.clone())
183 .flatten(),
184 css_last_result: css_with_urls_unchanged
185 .then(|| rc.css_last_result.clone())
186 .flatten(),
187 html_last_urls: html_with_urls_unchanged
188 .then(|| rc.html_last_urls.clone())
189 .flatten(),
190 html_last_result: html_with_urls_unchanged
191 .then(|| rc.html_last_result.clone())
192 .flatten(),
193 },
194 }
195 })
196 }
197
198 pub(crate) fn render_cache_source(&self) -> Option<CarriedRenderCache> {
199 self.cached
200 .get()
201 .and_then(|cached| cached.as_ref().ok())
202 .map(|cached| CarriedRenderCache {
203 cache: cached.render_cache.lock().unwrap().clone(),
204 css_dependencies: cached.shared.css_template_dependencies,
205 html_dependencies: cached.html_template.dependencies,
206 css_template_source: cached.shared.css_template_source.clone(),
207 html_template_source: cached.html_template.source.clone(),
208 })
209 .or_else(|| self.carried_render.clone())
210 }
211
212 pub fn generate_css_pure(
216 &self,
217 urls: Option<HashMap<FontType, String>>,
218 ) -> std::io::Result<String> {
219 self.generate_css_shared(urls)
220 .map(|text| text.as_ref().clone())
221 }
222
223 pub(crate) fn generate_css_shared(
224 &self,
225 urls: Option<HashMap<FontType, String>>,
226 ) -> std::io::Result<Arc<String>> {
227 self.validate_render_urls(urls.as_ref())?;
228 let cached = self.get_cached_io()?;
229 let mut rc = cached.render_cache.lock().unwrap();
230
231 match &urls {
232 None => {
233 if let Some(result) = &rc.css_no_urls {
234 return Ok(result.clone());
235 }
236 let ctx = cached.css_hbs_context.lock().unwrap();
237 let result = Arc::new(render_css_with_hbs_context(
238 &cached.shared,
239 &ctx,
240 &cached.css_context,
241 )?);
242 rc.css_no_urls = Some(result.clone());
243 Ok(result)
244 }
245 Some(urls) => {
246 if !cached.shared.css_template_uses_src {
248 drop(rc);
249 return self.generate_css_shared(None);
250 }
251 if rc.css_last_urls.as_ref() == Some(urls)
252 && let Some(result) = &rc.css_last_result
253 {
254 return Ok(result.clone());
255 }
256 let src = make_src(&self.options, urls);
257 let mut ctx = cached.css_hbs_context.lock().unwrap();
258 let result = Arc::new(render_css_with_src_mutate(
259 &cached.shared,
260 &mut ctx,
261 &cached.css_context,
262 &src,
263 )?);
264 rc.css_last_urls = Some(urls.clone());
265 rc.css_last_result = Some(result.clone());
266 Ok(result)
267 }
268 }
269 }
270
271 pub fn generate_html_pure(
275 &self,
276 urls: Option<HashMap<FontType, String>>,
277 ) -> std::io::Result<String> {
278 self.generate_html_shared(urls)
279 .map(|text| text.as_ref().clone())
280 }
281
282 pub(crate) fn generate_html_shared(
283 &self,
284 urls: Option<HashMap<FontType, String>>,
285 ) -> std::io::Result<Arc<String>> {
286 self.validate_render_urls(urls.as_ref())?;
287 let cached = self.get_cached_io()?;
288 let mut rc = cached.render_cache.lock().unwrap();
289
290 match &urls {
291 None => {
292 if let Some(result) = &rc.html_no_urls {
293 return Ok(result.clone());
294 }
295 let ctx = cached.html_hbs_context.lock().unwrap();
296 #[cfg(test)]
297 cached
298 .html_render_count
299 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
300 let result = Arc::new(render_html_with_hbs_context(
301 cached.html_template.registry.as_ref(),
302 &ctx,
303 &cached.html_context,
304 )?);
305 rc.html_no_urls = Some(result.clone());
306 Ok(result)
307 }
308 Some(urls) => {
309 if !cached.shared.css_template_uses_src {
311 drop(rc);
312 return self.generate_html_shared(None);
313 }
314 if rc.html_last_urls.as_ref() == Some(urls)
315 && let Some(result) = &rc.html_last_result
316 {
317 return Ok(result.clone());
318 }
319 #[cfg(test)]
321 cached
322 .html_render_count
323 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
324 let src = make_src(&self.options, urls);
325 let styles = {
326 let mut css_ctx = cached.css_hbs_context.lock().unwrap();
327 render_css_with_src_mutate(
328 &cached.shared,
329 &mut css_ctx,
330 &cached.css_context,
331 &src,
332 )?
333 };
334 if cached.html_template.registry.is_none() {
336 let result = Arc::new(render_default_html_with_styles(
337 &cached.html_context,
338 &styles,
339 ));
340 rc.html_last_urls = Some(urls.clone());
341 rc.html_last_result = Some(result.clone());
342 return Ok(result);
343 }
344 let mut html_ctx = cached.html_hbs_context.lock().unwrap();
346 let registry = cached
347 .html_template
348 .registry
349 .as_ref()
350 .expect("HTML registry should exist for custom template");
351 let result = Arc::new(render_with_field_swap(
352 &mut html_ctx,
353 "styles",
354 serde_json::Value::String(styles),
355 |ctx| registry.render_with_context("html", ctx).map_err(to_io_err),
356 )?);
357 rc.html_last_urls = Some(urls.clone());
358 rc.html_last_result = Some(result.clone());
359 Ok(result)
360 }
361 }
362 }
363}