#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use serde_json::{Map, Value};
use super::css::{
SharedTemplateData, TemplateDependencies, build_css_context, make_src,
render_css_with_hbs_context, render_css_with_src_mutate,
};
use super::files::load_html_template;
use super::html::{
build_html_context, render_default_html_with_styles, render_html_with_hbs_context,
};
use super::shared::{render_with_field_swap, to_io_err};
use crate::result::GenerateWebfontsResult;
use crate::types::FontType;
#[derive(Clone, Default)]
pub(crate) struct RenderCache {
css_no_urls: Option<Arc<String>>,
css_last_urls: Option<HashMap<FontType, String>>,
css_last_result: Option<Arc<String>>,
html_no_urls: Option<Arc<String>>,
html_last_urls: Option<HashMap<FontType, String>>,
html_last_result: Option<Arc<String>>,
}
#[derive(Clone)]
pub(crate) struct CarriedRenderCache {
cache: RenderCache,
css_dependencies: TemplateDependencies,
html_dependencies: TemplateDependencies,
css_template_source: Option<Arc<str>>,
html_template_source: Option<Arc<str>>,
}
pub(crate) struct CachedTemplateData {
pub(crate) shared: SharedTemplateData,
pub(crate) css_context: Map<String, Value>,
pub(crate) css_hbs_context: Mutex<handlebars::Context>,
pub(crate) html_context: Map<String, Value>,
pub(crate) html_hbs_context: Mutex<handlebars::Context>,
pub(crate) html_template: super::html::HtmlTemplate,
pub(crate) render_cache: Mutex<RenderCache>,
#[cfg(test)]
pub(crate) html_render_count: std::sync::atomic::AtomicUsize,
}
impl GenerateWebfontsResult {
pub fn generate_ts(&self) -> std::io::Result<Arc<String>> {
Ok(self
.ts
.get_or_init(|| {
Arc::new(super::ts::render_ts(
&self.options,
self.source_files
.iter()
.map(|source| source.glyph_name.as_str()),
self.options.variants.is_some(),
self.css_context.as_ref(),
))
})
.clone())
}
fn validate_render_urls(
&self,
urls: Option<&HashMap<FontType, String>>,
) -> std::io::Result<()> {
if self.options.variants.is_some()
&& urls.is_some_and(|urls| {
urls.contains_key(&FontType::Svg) || urls.contains_key(&FontType::Eot)
})
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"SVG and EOT URLs are unsupported for variant results.",
));
}
Ok(())
}
#[cfg(test)]
pub(crate) fn has_carried_css_no_urls_for_test(&self) -> bool {
self.carried_render
.as_ref()
.is_some_and(|carried| carried.cache.css_no_urls.is_some())
}
#[cfg(test)]
pub(crate) fn has_carried_html_no_urls_for_test(&self) -> bool {
self.carried_render
.as_ref()
.is_some_and(|carried| carried.cache.html_no_urls.is_some())
}
pub(crate) fn get_cached_io(&self) -> std::io::Result<&CachedTemplateData> {
self.cached
.get_or_init(|| {
let shared = SharedTemplateData::from_files(&self.options, &self.source_files)
.map_err(|e| e.to_string())?;
let css_context = match &self.css_context {
Some(ctx) => ctx.clone(),
None => build_css_context(&self.options, &shared),
};
let html_context = match &self.html_context {
Some(ctx) => ctx.clone(),
None => build_html_context(&self.options, &shared, &self.source_files, None)
.map_err(|e| e.to_string())?,
};
let html_template = load_html_template(&self.options).map_err(|e| e.to_string())?;
let render_cache = self
.carried_render
.as_ref()
.map(|carried| {
if carried.css_template_source != shared.css_template_source {
RenderCache::default()
} else if carried.html_template_source != html_template.source {
RenderCache {
html_no_urls: None,
html_last_urls: None,
html_last_result: None,
..carried.cache.clone()
}
} else {
carried.cache.clone()
}
})
.unwrap_or_default();
let css_hbs_context =
handlebars::Context::wraps(&css_context).map_err(|e| e.to_string())?;
let html_hbs_context =
handlebars::Context::wraps(&html_context).map_err(|e| e.to_string())?;
Ok(CachedTemplateData {
#[cfg(test)]
html_render_count: Default::default(),
shared,
css_context,
css_hbs_context: Mutex::new(css_hbs_context),
html_context,
html_hbs_context: Mutex::new(html_hbs_context),
html_template,
render_cache: Mutex::new(render_cache),
})
})
.as_ref()
.map_err(to_io_err)
}
pub(crate) fn reusable_render_cache(
&self,
names_unchanged: bool,
codepoints_unchanged: bool,
) -> Option<CarriedRenderCache> {
self.render_cache_source().map(|carried| {
let css_deps = carried.css_dependencies;
let css_no_urls_unchanged =
css_deps.can_reuse_css_no_urls(names_unchanged, codepoints_unchanged);
let css_with_urls_unchanged =
css_deps.can_reuse_css_with_urls(names_unchanged, codepoints_unchanged);
let html_no_urls_unchanged = carried.html_dependencies.can_reuse_html(
names_unchanged,
codepoints_unchanged,
css_no_urls_unchanged,
);
let html_with_urls_unchanged = carried.html_dependencies.can_reuse_html(
names_unchanged,
codepoints_unchanged,
css_with_urls_unchanged,
);
let rc = carried.cache;
CarriedRenderCache {
css_dependencies: carried.css_dependencies,
html_dependencies: carried.html_dependencies,
css_template_source: carried.css_template_source,
html_template_source: carried.html_template_source,
cache: RenderCache {
css_no_urls: css_no_urls_unchanged
.then(|| rc.css_no_urls.clone())
.flatten(),
html_no_urls: html_no_urls_unchanged
.then(|| rc.html_no_urls.clone())
.flatten(),
css_last_urls: css_with_urls_unchanged
.then(|| rc.css_last_urls.clone())
.flatten(),
css_last_result: css_with_urls_unchanged
.then(|| rc.css_last_result.clone())
.flatten(),
html_last_urls: html_with_urls_unchanged
.then(|| rc.html_last_urls.clone())
.flatten(),
html_last_result: html_with_urls_unchanged
.then(|| rc.html_last_result.clone())
.flatten(),
},
}
})
}
pub(crate) fn render_cache_source(&self) -> Option<CarriedRenderCache> {
self.cached
.get()
.and_then(|cached| cached.as_ref().ok())
.map(|cached| CarriedRenderCache {
cache: cached.render_cache.lock().unwrap().clone(),
css_dependencies: cached.shared.css_template_dependencies,
html_dependencies: cached.html_template.dependencies,
css_template_source: cached.shared.css_template_source.clone(),
html_template_source: cached.html_template.source.clone(),
})
.or_else(|| self.carried_render.clone())
}
pub fn generate_css_pure(
&self,
urls: Option<HashMap<FontType, String>>,
) -> std::io::Result<String> {
self.generate_css_shared(urls)
.map(|text| text.as_ref().clone())
}
pub(crate) fn generate_css_shared(
&self,
urls: Option<HashMap<FontType, String>>,
) -> std::io::Result<Arc<String>> {
self.validate_render_urls(urls.as_ref())?;
let cached = self.get_cached_io()?;
let mut rc = cached.render_cache.lock().unwrap();
match &urls {
None => {
if let Some(result) = &rc.css_no_urls {
return Ok(result.clone());
}
let ctx = cached.css_hbs_context.lock().unwrap();
let result = Arc::new(render_css_with_hbs_context(
&cached.shared,
&ctx,
&cached.css_context,
)?);
rc.css_no_urls = Some(result.clone());
Ok(result)
}
Some(urls) => {
if !cached.shared.css_template_uses_src {
drop(rc);
return self.generate_css_shared(None);
}
if rc.css_last_urls.as_ref() == Some(urls)
&& let Some(result) = &rc.css_last_result
{
return Ok(result.clone());
}
let src = make_src(&self.options, urls);
let mut ctx = cached.css_hbs_context.lock().unwrap();
let result = Arc::new(render_css_with_src_mutate(
&cached.shared,
&mut ctx,
&cached.css_context,
&src,
)?);
rc.css_last_urls = Some(urls.clone());
rc.css_last_result = Some(result.clone());
Ok(result)
}
}
}
pub fn generate_html_pure(
&self,
urls: Option<HashMap<FontType, String>>,
) -> std::io::Result<String> {
self.generate_html_shared(urls)
.map(|text| text.as_ref().clone())
}
pub(crate) fn generate_html_shared(
&self,
urls: Option<HashMap<FontType, String>>,
) -> std::io::Result<Arc<String>> {
self.validate_render_urls(urls.as_ref())?;
let cached = self.get_cached_io()?;
let mut rc = cached.render_cache.lock().unwrap();
match &urls {
None => {
if let Some(result) = &rc.html_no_urls {
return Ok(result.clone());
}
let ctx = cached.html_hbs_context.lock().unwrap();
#[cfg(test)]
cached
.html_render_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let result = Arc::new(render_html_with_hbs_context(
cached.html_template.registry.as_ref(),
&ctx,
&cached.html_context,
)?);
rc.html_no_urls = Some(result.clone());
Ok(result)
}
Some(urls) => {
if !cached.shared.css_template_uses_src {
drop(rc);
return self.generate_html_shared(None);
}
if rc.html_last_urls.as_ref() == Some(urls)
&& let Some(result) = &rc.html_last_result
{
return Ok(result.clone());
}
#[cfg(test)]
cached
.html_render_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let src = make_src(&self.options, urls);
let styles = {
let mut css_ctx = cached.css_hbs_context.lock().unwrap();
render_css_with_src_mutate(
&cached.shared,
&mut css_ctx,
&cached.css_context,
&src,
)?
};
if cached.html_template.registry.is_none() {
let result = Arc::new(render_default_html_with_styles(
&cached.html_context,
&styles,
));
rc.html_last_urls = Some(urls.clone());
rc.html_last_result = Some(result.clone());
return Ok(result);
}
let mut html_ctx = cached.html_hbs_context.lock().unwrap();
let registry = cached
.html_template
.registry
.as_ref()
.expect("HTML registry should exist for custom template");
let result = Arc::new(render_with_field_swap(
&mut html_ctx,
"styles",
serde_json::Value::String(styles),
|ctx| registry.render_with_context("html", ctx).map_err(to_io_err),
)?);
rc.html_last_urls = Some(urls.clone());
rc.html_last_result = Some(result.clone());
Ok(result)
}
}
}
}