1mod options;
2#[cfg(test)]
3mod tests;
4mod updates;
5
6use std::io::{Error, Result};
7use std::sync::{Arc, OnceLock};
8
9use crate::cache::{CacheState, glyph_set, variant_glyph_set};
10use crate::incremental::sources::{SourceUpdate, refresh_source_cache, replace_source_cache};
11use crate::input::finalize_generate_webfonts_options;
12use crate::output::collect::{CollectedOutput, collect_outputs};
13use crate::pipeline::{
14 TtfGlyphCache, build_content_variant_outputs, build_font_outputs, build_variant_with_cache,
15 build_with_cache,
16};
17use crate::rendering::css::{
18 SharedTemplateData, calc_content_hash, make_content_css_context, make_urls,
19 render_css_with_context,
20};
21use crate::rendering::html::{
22 compile_html_template, make_content_html_context, render_html_with_hbs_context,
23};
24use crate::resolve_content_variant_sources;
25use crate::result::FontOutputs;
26use crate::svg::types::GlyphCache;
27use crate::svg::{
28 VariantGlyphCache, prepare_svg_font, prepare_svg_font_incremental,
29 prepare_variant_svg_family_cached, source_content_hash, svg_options_from_options,
30};
31pub use options::{GenerateWebfontsFromSourcesOptions, SourceFontVariant, SvgSource};
32use options::{ResolvedSourceOptions, resolve};
33#[cfg(feature = "async")]
34pub use updates::SourceRegenerateError;
35pub use updates::{SourceChange, SourceMembership, VariantSourceSet};
36
37struct PersistentCache {
39 state: CacheState,
40 glyphs: Option<SourceCaches>,
42 caches_reset: bool,
45}
46
47#[derive(Default)]
48struct SourceCaches {
49 glyphs: GlyphCache,
50 ttf: TtfGlyphCache,
51 variants: VariantGlyphCache,
52}
53
54pub struct GenerateWebfontsFromSourcesResult {
57 options: ResolvedSourceOptions,
58 fonts: FontOutputs,
59 caches: Option<SourceCaches>,
60 persistent: Option<PersistentCache>,
61 shared: OnceLock<SharedTemplateData>,
62 css: OnceLock<std::result::Result<Arc<String>, String>>,
63 html: OnceLock<std::result::Result<Arc<String>, String>>,
64 ts: OnceLock<Arc<String>>,
65 #[cfg(test)]
66 html_render_count: std::sync::atomic::AtomicUsize,
67}
68
69pub fn generate_from_sources_sync(
91 options: GenerateWebfontsFromSourcesOptions,
92) -> Result<GenerateWebfontsFromSourcesResult> {
93 let threads = options.threads;
94 crate::threads::install(threads, || generate_in_pool(options))
95}
96
97fn generate_in_pool(
98 mut options: GenerateWebfontsFromSourcesOptions,
99) -> Result<GenerateWebfontsFromSourcesResult> {
100 let cache = options.cache.take();
101 let mut options = resolve(options)?;
102 let mut caches = options.common.incremental.then(SourceCaches::default);
103 let (fonts, persistent) = match cache {
104 Some(bytes) => build_cached(&mut options, caches.as_mut(), bytes)?,
105 None => (build(&mut options, caches.as_mut(), &[])?, None),
106 };
107 Ok(GenerateWebfontsFromSourcesResult {
108 options,
109 fonts,
110 caches,
111 persistent,
112 shared: OnceLock::new(),
113 css: OnceLock::new(),
114 html: OnceLock::new(),
115 ts: OnceLock::new(),
116 #[cfg(test)]
117 html_render_count: Default::default(),
118 })
119}
120
121#[cfg(feature = "async")]
124pub async fn generate_from_sources(
125 options: GenerateWebfontsFromSourcesOptions,
126) -> Result<GenerateWebfontsFromSourcesResult> {
127 let runtime = tokio::runtime::Handle::try_current().map_err(|error| {
128 Error::other(format!(
129 "Source generation requires a Tokio runtime: {error}"
130 ))
131 })?;
132 runtime
133 .spawn_blocking(move || generate_from_sources_sync(options))
134 .await
135 .map_err(generation_join_error)?
136}
137
138#[cfg(feature = "async")]
139fn generation_join_error(error: tokio::task::JoinError) -> Error {
140 Error::other(format!("Source generation task failed: {error}"))
141}
142
143fn build_cached(
146 options: &mut ResolvedSourceOptions,
147 caches: Option<&mut SourceCaches>,
148 bytes: Vec<u8>,
149) -> Result<(FontOutputs, Option<PersistentCache>)> {
150 if let Some(variants) = &options.variants {
151 let family = resolve_content_variant_sources(
152 &mut options.common,
153 variants,
154 options.sources.iter().map(Vec::as_slice).collect(),
155 )?;
156 let mut state = CacheState::new_variant(&options.common, variants, &family);
157 if !bytes.is_empty() {
158 state.load(bytes);
159 }
160 let svg_options = svg_options_from_options(&options.common);
161 let built =
162 build_variant_with_cache(&options.common, variants, &svg_options, &family, &state);
163 let (fonts, glyphs, _) = built?;
164 let glyphs = match (caches, glyphs) {
165 (Some(cache), Some(glyphs)) => {
166 cache.variants = glyphs;
167 None
168 }
169 (None, None) => return Ok((fonts, None)),
170 (_, glyphs) => glyphs.map(|variants| SourceCaches {
171 variants,
172 ..Default::default()
173 }),
174 };
175 state.release_glyphs();
176 let persistent = PersistentCache {
177 state,
178 glyphs,
179 caches_reset: false,
180 };
181 return Ok((fonts, Some(persistent)));
182 }
183 finalize_generate_webfonts_options(&mut options.common, &options.sources[0])?;
184 let sources = &options.sources[0];
185 let mut state = CacheState::new(&options.common, sources);
186 if !bytes.is_empty() {
187 state.load(bytes);
188 }
189 let svg_options = svg_options_from_options(&options.common);
190 let mut caches = caches;
191 let ttf = caches.as_deref_mut().map(|cache| &mut cache.ttf);
192 let built = build_with_cache(&options.common, &svg_options, sources, &state, ttf);
193 let (fonts, glyphs, _) = built?;
194 let glyphs = match (caches, glyphs) {
196 (Some(cache), Some(glyphs)) => {
197 cache.glyphs = glyphs;
198 None
199 }
200 (None, None) => return Ok((fonts, None)),
201 (_, glyphs) => glyphs.map(|glyphs| SourceCaches {
202 glyphs,
203 ..Default::default()
204 }),
205 };
206 state.release_glyphs();
207 let persistent = PersistentCache {
208 state,
209 glyphs,
210 caches_reset: false,
211 };
212 Ok((fonts, Some(persistent)))
213}
214
215fn build(
216 options: &mut ResolvedSourceOptions,
217 mut caches: Option<&mut SourceCaches>,
218 updates: &[SourceUpdate],
219) -> Result<FontOutputs> {
220 let family = if let Some(variants) = &options.variants {
222 Some(resolve_content_variant_sources(
223 &mut options.common,
224 variants,
225 options.sources.iter().map(Vec::as_slice).collect(),
226 )?)
227 } else {
228 finalize_generate_webfonts_options(&mut options.common, &options.sources[0])?;
229 None
230 };
231 if family.is_none()
232 && let Some(cache) = caches.as_mut()
233 {
234 for update in updates {
235 match update {
236 SourceUpdate::Set(source) => {
237 refresh_source_cache(
238 &mut cache.glyphs,
239 &source.id,
240 source_content_hash(&source.contents),
241 );
242 }
243 SourceUpdate::Remove(id) => replace_source_cache(&mut cache.glyphs, id, None),
244 }
245 }
246 }
247 let outputs = (|| {
248 let svg_options = svg_options_from_options(&options.common);
249 if let Some(family) = family {
250 let prepared = prepare_variant_svg_family_cached(
251 &svg_options,
252 &family,
253 caches.as_deref_mut().map(|cache| &mut cache.variants),
254 )?;
255 return build_content_variant_outputs(
256 &options.common,
257 options.variants.as_ref().unwrap(),
258 &prepared,
259 );
260 }
261 let sources = &options.sources[0];
262 match caches.as_deref_mut() {
263 Some(cache) => {
264 let prepared =
265 prepare_svg_font_incremental(&svg_options, sources, &mut cache.glyphs)?;
266 build_font_outputs(
267 &options.common,
268 &svg_options,
269 &prepared,
270 Some(&mut cache.ttf),
271 )
272 }
273 None => {
274 let prepared = prepare_svg_font(&svg_options, sources)?;
275 build_font_outputs(&options.common, &svg_options, &prepared, None)
276 }
277 }
278 })();
279 if outputs.is_err()
280 && let Some(cache) = caches
281 {
282 *cache = SourceCaches::default();
283 }
284 outputs
285}
286
287impl GenerateWebfontsFromSourcesResult {
288 pub fn svg_string(&self) -> Option<&str> {
290 self.fonts.svg_font.as_deref().map(String::as_str)
291 }
292 pub fn ttf_bytes(&self) -> Option<&[u8]> {
294 self.fonts.ttf_font.as_deref().map(Vec::as_slice)
295 }
296 pub fn eot_bytes(&self) -> Option<&[u8]> {
298 self.fonts.eot_font.as_deref().map(Vec::as_slice)
299 }
300 pub fn woff_bytes(&self) -> Option<&[u8]> {
302 self.fonts.woff_font.as_deref().map(Vec::as_slice)
303 }
304 pub fn woff2_bytes(&self) -> Option<&[u8]> {
306 self.fonts.woff2_font.as_deref().map(Vec::as_slice)
307 }
308
309 fn shared(&self) -> &SharedTemplateData {
310 self.shared.get_or_init(|| {
311 let sources = self
312 .options
313 .sources
314 .iter()
315 .flatten()
316 .cloned()
317 .collect::<Vec<_>>();
318 SharedTemplateData::new(
319 &self.options.common,
320 self.options.css_template.clone(),
321 calc_content_hash(
322 &self.options.common,
323 self.options.variants.as_ref(),
324 &sources,
325 self.options.css_template.as_deref(),
326 self.options.html_template.as_deref(),
327 ),
328 )
329 })
330 }
331
332 fn css_context(&self) -> serde_json::Map<String, serde_json::Value> {
333 let options = &self.options.common;
334 let shared = self.shared();
335 make_content_css_context(
336 options,
337 self.options.variants.as_ref(),
338 &make_urls(options, &shared.hash, options.css_fonts_url.as_deref()),
339 shared,
340 )
341 }
342
343 pub fn generate_css(&self) -> Result<Arc<String>> {
346 self.css
347 .get_or_init(|| {
348 render_css_with_context(self.shared(), &self.css_context())
349 .map(Arc::new)
350 .map_err(|error| error.to_string())
351 })
352 .clone()
353 .map_err(Error::other)
354 }
355
356 pub fn generate_html(&self) -> Result<Arc<String>> {
359 self.html
360 .get_or_init(|| {
361 self.render_html()
362 .map(Arc::new)
363 .map_err(|error| error.to_string())
364 })
365 .clone()
366 .map_err(Error::other)
367 }
368
369 fn render_html(&self) -> Result<String> {
370 #[cfg(test)]
371 self.html_render_count
372 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
373 let template = compile_html_template(self.options.html_template.clone())?;
374 let css = self.generate_css()?;
375 let sources = self
376 .options
377 .sources
378 .iter()
379 .flatten()
380 .cloned()
381 .collect::<Vec<_>>();
382 let css_ctx = self.options.variants.as_ref().map(|_| self.css_context());
383 let context = make_content_html_context(
384 &self.options.common,
385 self.shared(),
386 &sources,
387 css.as_ref().clone(),
388 css_ctx.as_ref(),
389 );
390 let hbs_context = handlebars::Context::wraps(&context).map_err(Error::other)?;
391 render_html_with_hbs_context(template.registry.as_ref(), &hbs_context, &context)
392 }
393
394 pub fn generate_ts(&self) -> Result<Arc<String>> {
397 Ok(self
398 .ts
399 .get_or_init(|| {
400 Arc::new(crate::rendering::ts::render_ts(
401 &self.options.common,
402 self.options
403 .sources
404 .iter()
405 .flatten()
406 .map(|source| source.glyph_name.as_str()),
407 self.options.variants.is_some(),
408 None,
409 ))
410 })
411 .clone())
412 }
413
414 pub fn cache_bytes(&mut self) -> Option<Vec<u8>> {
419 let PersistentCache {
420 state,
421 glyphs,
422 caches_reset,
423 } = self.persistent.as_mut()?;
424 if *caches_reset {
425 return None;
426 }
427 let caches = glyphs.as_ref().or(self.caches.as_ref())?;
428 let options = &self.options.common;
429 if let Some(variants) = &self.options.variants {
430 let sources = &self.options.sources;
431 let family = resolve_content_variant_sources(
432 &mut options.clone(),
433 variants,
434 sources.iter().map(Vec::as_slice).collect(),
435 )
436 .expect("committed variant sources resolve");
437 state.refresh_glyph_set(variant_glyph_set(&family));
438 return state.update_variants(sources, &caches.variants, &self.fonts, &options.types);
439 }
440 let sources = &self.options.sources[0];
441 state.refresh_glyph_set(glyph_set(options, sources));
442 state.update(sources, &caches.glyphs, &self.fonts, &options.types)
443 }
444
445 pub fn collect_outputs(&self) -> Result<Vec<CollectedOutput>> {
447 let css = self
448 .options
449 .common
450 .css
451 .then(|| self.generate_css())
452 .transpose()?;
453 let html = self
454 .options
455 .common
456 .html
457 .then(|| self.generate_html())
458 .transpose()?;
459 let ts = self
460 .options
461 .common
462 .ts
463 .then(|| self.generate_ts())
464 .transpose()?;
465 Ok(collect_outputs(&self.fonts, css, html, ts))
466 }
467}