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