webfont_generator/memory/
mod.rs1mod options;
2#[cfg(test)]
3mod tests;
4mod updates;
5
6use std::io::{Error, Result};
7use std::sync::{Arc, OnceLock};
8
9use crate::incremental::sources::{SourceUpdate, refresh_source_cache, replace_source_cache};
10use crate::input::finalize_generate_webfonts_options;
11use crate::output::collect::{CollectedOutput, collect_outputs};
12use crate::pipeline::{TtfGlyphCache, build_content_variant_outputs, build_font_outputs};
13use crate::rendering::css::{
14 SharedTemplateData, calc_content_hash, make_content_css_context, make_urls,
15 render_css_with_context,
16};
17use crate::rendering::html::{
18 compile_html_template, make_content_html_context, render_html_with_hbs_context,
19};
20use crate::resolve_content_variant_sources;
21use crate::result::FontOutputs;
22use crate::svg::types::GlyphCache;
23use crate::svg::{
24 VariantGlyphCache, prepare_svg_font, prepare_svg_font_incremental,
25 prepare_variant_svg_family_cached, source_content_hash, svg_options_from_options,
26};
27pub use options::{GenerateWebfontsFromSourcesOptions, SourceFontVariant, SvgSource};
28use options::{ResolvedSourceOptions, resolve};
29#[cfg(feature = "async")]
30pub use updates::SourceRegenerateError;
31pub use updates::{SourceChange, SourceMembership, VariantSourceSet};
32
33#[derive(Default)]
34struct SourceCaches {
35 glyphs: GlyphCache,
36 ttf: TtfGlyphCache,
37 variants: VariantGlyphCache,
38}
39
40pub struct GenerateWebfontsFromSourcesResult {
43 options: ResolvedSourceOptions,
44 fonts: FontOutputs,
45 caches: Option<SourceCaches>,
46 shared: OnceLock<SharedTemplateData>,
47 css: OnceLock<std::result::Result<Arc<String>, String>>,
48 html: OnceLock<std::result::Result<Arc<String>, String>>,
49 #[cfg(test)]
50 html_render_count: std::sync::atomic::AtomicUsize,
51}
52
53pub fn generate_from_sources_sync(
75 options: GenerateWebfontsFromSourcesOptions,
76) -> Result<GenerateWebfontsFromSourcesResult> {
77 let mut options = resolve(options)?;
78 let mut caches = options.common.incremental.then(SourceCaches::default);
79 let fonts = build(&mut options, caches.as_mut(), &[])?;
80 Ok(GenerateWebfontsFromSourcesResult {
81 options,
82 fonts,
83 caches,
84 shared: OnceLock::new(),
85 css: OnceLock::new(),
86 html: OnceLock::new(),
87 #[cfg(test)]
88 html_render_count: Default::default(),
89 })
90}
91
92#[cfg(feature = "async")]
95pub async fn generate_from_sources(
96 options: GenerateWebfontsFromSourcesOptions,
97) -> Result<GenerateWebfontsFromSourcesResult> {
98 let runtime = tokio::runtime::Handle::try_current().map_err(|error| {
99 Error::other(format!(
100 "Source generation requires a Tokio runtime: {error}"
101 ))
102 })?;
103 runtime
104 .spawn_blocking(move || generate_from_sources_sync(options))
105 .await
106 .map_err(generation_join_error)?
107}
108
109#[cfg(feature = "async")]
110fn generation_join_error(error: tokio::task::JoinError) -> Error {
111 Error::other(format!("Source generation task failed: {error}"))
112}
113
114fn build(
115 options: &mut ResolvedSourceOptions,
116 mut caches: Option<&mut SourceCaches>,
117 updates: &[SourceUpdate],
118) -> Result<FontOutputs> {
119 let family = if let Some(variants) = &options.variants {
121 Some(resolve_content_variant_sources(
122 &mut options.common,
123 variants,
124 options.sources.iter().map(Vec::as_slice).collect(),
125 )?)
126 } else {
127 finalize_generate_webfonts_options(&mut options.common, &options.sources[0])?;
128 None
129 };
130 if family.is_none()
131 && let Some(cache) = caches.as_mut()
132 {
133 for update in updates {
134 match update {
135 SourceUpdate::Set(source) => {
136 refresh_source_cache(
137 &mut cache.glyphs,
138 &source.id,
139 source_content_hash(&source.contents),
140 );
141 }
142 SourceUpdate::Remove(id) => replace_source_cache(&mut cache.glyphs, id, None),
143 }
144 }
145 }
146 let outputs = (|| {
147 let svg_options = svg_options_from_options(&options.common);
148 if let Some(family) = family {
149 let prepared = prepare_variant_svg_family_cached(
150 &svg_options,
151 &family,
152 caches.as_deref_mut().map(|cache| &mut cache.variants),
153 )?;
154 return build_content_variant_outputs(
155 &options.common,
156 options.variants.as_ref().unwrap(),
157 &prepared,
158 );
159 }
160 let sources = &options.sources[0];
161 match caches.as_deref_mut() {
162 Some(cache) => {
163 let prepared =
164 prepare_svg_font_incremental(&svg_options, sources, &mut cache.glyphs)?;
165 build_font_outputs(
166 &options.common,
167 &svg_options,
168 &prepared,
169 Some(&mut cache.ttf),
170 )
171 }
172 None => {
173 let prepared = prepare_svg_font(&svg_options, sources)?;
174 build_font_outputs(&options.common, &svg_options, &prepared, None)
175 }
176 }
177 })();
178 if outputs.is_err()
179 && let Some(cache) = caches
180 {
181 *cache = SourceCaches::default();
182 }
183 outputs
184}
185
186impl GenerateWebfontsFromSourcesResult {
187 pub fn svg_string(&self) -> Option<&str> {
189 self.fonts.svg_font.as_deref().map(String::as_str)
190 }
191 pub fn ttf_bytes(&self) -> Option<&[u8]> {
193 self.fonts.ttf_font.as_deref().map(Vec::as_slice)
194 }
195 pub fn eot_bytes(&self) -> Option<&[u8]> {
197 self.fonts.eot_font.as_deref().map(Vec::as_slice)
198 }
199 pub fn woff_bytes(&self) -> Option<&[u8]> {
201 self.fonts.woff_font.as_deref().map(Vec::as_slice)
202 }
203 pub fn woff2_bytes(&self) -> Option<&[u8]> {
205 self.fonts.woff2_font.as_deref().map(Vec::as_slice)
206 }
207
208 fn shared(&self) -> &SharedTemplateData {
209 self.shared.get_or_init(|| {
210 let sources = self
211 .options
212 .sources
213 .iter()
214 .flatten()
215 .cloned()
216 .collect::<Vec<_>>();
217 SharedTemplateData::new(
218 &self.options.common,
219 self.options.css_template.clone(),
220 calc_content_hash(
221 &self.options.common,
222 self.options.variants.as_ref(),
223 &sources,
224 self.options.css_template.as_deref(),
225 self.options.html_template.as_deref(),
226 ),
227 )
228 })
229 }
230
231 fn css_context(&self) -> serde_json::Map<String, serde_json::Value> {
232 let options = &self.options.common;
233 let shared = self.shared();
234 make_content_css_context(
235 options,
236 self.options.variants.as_ref(),
237 &make_urls(options, &shared.hash, options.css_fonts_url.as_deref()),
238 shared,
239 )
240 }
241
242 pub fn generate_css(&self) -> Result<Arc<String>> {
245 self.css
246 .get_or_init(|| {
247 render_css_with_context(self.shared(), &self.css_context())
248 .map(Arc::new)
249 .map_err(|error| error.to_string())
250 })
251 .clone()
252 .map_err(Error::other)
253 }
254
255 pub fn generate_html(&self) -> Result<Arc<String>> {
258 self.html
259 .get_or_init(|| {
260 self.render_html()
261 .map(Arc::new)
262 .map_err(|error| error.to_string())
263 })
264 .clone()
265 .map_err(Error::other)
266 }
267
268 fn render_html(&self) -> Result<String> {
269 #[cfg(test)]
270 self.html_render_count
271 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
272 let template = compile_html_template(self.options.html_template.clone())?;
273 let css = self.generate_css()?;
274 let sources = self
275 .options
276 .sources
277 .iter()
278 .flatten()
279 .cloned()
280 .collect::<Vec<_>>();
281 let css_ctx = self.options.variants.as_ref().map(|_| self.css_context());
282 let context = make_content_html_context(
283 &self.options.common,
284 self.shared(),
285 &sources,
286 css.as_ref().clone(),
287 css_ctx.as_ref(),
288 );
289 let hbs_context = handlebars::Context::wraps(&context).map_err(Error::other)?;
290 render_html_with_hbs_context(template.registry.as_ref(), &hbs_context, &context)
291 }
292
293 pub fn collect_outputs(&self) -> Result<Vec<CollectedOutput>> {
295 let css = self
296 .options
297 .common
298 .css
299 .then(|| self.generate_css())
300 .transpose()?;
301 let html = self
302 .options
303 .common
304 .html
305 .then(|| self.generate_html())
306 .transpose()?;
307 Ok(collect_outputs(&self.fonts, css, html))
308 }
309}