1mod codec;
16#[cfg(all(test, feature = "fs"))]
17mod tests;
18
19use std::array::from_fn;
20use std::collections::HashMap;
21#[cfg(feature = "fs")]
22use std::fs::{self, File};
23#[cfg(feature = "fs")]
24use std::io::{ErrorKind, Read, Result};
25use std::mem::take;
26#[cfg(feature = "fs")]
27use std::ops::Deref;
28use std::ops::Range;
29#[cfg(feature = "fs")]
30use std::path::{Path, PathBuf};
31#[cfg(feature = "fs")]
32use std::process;
33#[cfg(feature = "fs")]
34use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
35use std::sync::{Arc, OnceLock};
36
37use flate2::Crc;
38use md5::compute;
39use serde::Serialize;
40use serde_json::{Value, json, to_vec};
41
42use self::codec::{Reader, Writer};
43use crate::input::{
44 LoadedSvgSource, ResolvedGenerationOptions, ResolvedVariants, VariantFamilySources,
45 VariantGlyphSource,
46};
47use crate::rendering::font_settings_json;
48use crate::result::FontOutputs;
49use crate::svg::types::{CachedGlyph, CachedProcessedGlyph, GlyphCache, SvgOptions};
50use crate::svg::{VariantGlyphCache, source_content_hash};
51use crate::types::FontType;
52
53const MAGIC: &[u8; 8] = b"WFGCACHE";
54const FORMAT_VERSION: u32 = 1;
56const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");
57#[cfg(feature = "fs")]
58const EXTENSION: &str = "wfcache";
59const ENTRIES_PER_FORMAT: usize = 2;
62
63const FONT_TYPES: [FontType; 5] = [
64 FontType::Svg,
65 FontType::Ttf,
66 FontType::Eot,
67 FontType::Woff,
68 FontType::Woff2,
69];
70
71fn font_type_tag(font_type: FontType) -> u8 {
72 FONT_TYPES
73 .iter()
74 .position(|candidate| *candidate == font_type)
75 .expect("every font type is listed") as u8
76}
77
78#[derive(Clone)]
80pub(crate) enum CachedFont {
81 Svg(Arc<String>),
82 Ttf(Arc<Vec<u8>>),
83 Eot(Arc<Vec<u8>>),
84 Woff(Arc<Vec<u8>>),
85 Woff2(Arc<Vec<u8>>),
86}
87
88impl CachedFont {
89 pub(crate) fn font_type(&self) -> FontType {
90 match self {
91 Self::Svg(_) => FontType::Svg,
92 Self::Ttf(_) => FontType::Ttf,
93 Self::Eot(_) => FontType::Eot,
94 Self::Woff(_) => FontType::Woff,
95 Self::Woff2(_) => FontType::Woff2,
96 }
97 }
98
99 fn bytes(&self) -> &[u8] {
100 match self {
101 Self::Svg(text) => text.as_bytes(),
102 Self::Ttf(bytes) | Self::Eot(bytes) | Self::Woff(bytes) | Self::Woff2(bytes) => bytes,
103 }
104 }
105
106 fn from_outputs(outputs: &FontOutputs, font_type: FontType) -> Option<Self> {
107 Some(match font_type {
108 FontType::Svg => Self::Svg(outputs.svg_font.clone()?),
109 FontType::Ttf => Self::Ttf(outputs.ttf_font.clone()?),
110 FontType::Eot => Self::Eot(outputs.eot_font.clone()?),
111 FontType::Woff => Self::Woff(outputs.woff_font.clone()?),
112 FontType::Woff2 => Self::Woff2(outputs.woff2_font.clone()?),
113 })
114 }
115
116 fn decode(font_type: FontType, bytes: Vec<u8>) -> Option<Self> {
117 Some(match font_type {
118 FontType::Svg => Self::Svg(Arc::new(String::from_utf8(bytes).ok()?)),
119 FontType::Ttf => Self::Ttf(Arc::new(bytes)),
120 FontType::Eot => Self::Eot(Arc::new(bytes)),
121 FontType::Woff => Self::Woff(Arc::new(bytes)),
122 FontType::Woff2 => Self::Woff2(Arc::new(bytes)),
123 })
124 }
125
126 pub(crate) fn place(self, outputs: &mut FontOutputs) {
128 match self {
129 Self::Svg(text) => outputs.svg_font = Some(text),
130 Self::Ttf(bytes) => outputs.ttf_font = Some(bytes),
131 Self::Eot(bytes) => outputs.eot_font = Some(bytes),
132 Self::Woff(bytes) => outputs.woff_font = Some(bytes),
133 Self::Woff2(bytes) => outputs.woff2_font = Some(bytes),
134 }
135 }
136}
137
138#[derive(Clone)]
139struct FontEntry {
140 key: [u8; 16],
141 glyph_set: [u8; 16],
143 font: CachedFont,
144}
145
146#[cfg(feature = "fs")]
148#[derive(Default)]
149#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
150pub struct ClearCacheReport {
151 pub files_removed: usize,
153 pub bytes_removed: u64,
155}
156
157#[cfg(feature = "fs")]
161pub fn clear_cache(cache_location: impl AsRef<Path>) -> Result<ClearCacheReport> {
162 let entries = match fs::read_dir(cache_location) {
163 Ok(entries) => entries,
164 Err(error) if error.kind() == ErrorKind::NotFound => return Ok(ClearCacheReport::default()),
165 Err(error) => return Err(error),
166 };
167 let mut report = ClearCacheReport::default();
168 for entry in entries {
169 let entry = entry?;
170 let path = entry.path();
171 if !entry.file_type()?.is_file() || !created_by_cache(&path) {
172 continue;
173 }
174 let size = entry.metadata().map(|metadata| metadata.len()).unwrap_or(0);
175 match fs::remove_file(&path) {
176 Ok(()) => {
177 report.files_removed += 1;
178 report.bytes_removed += size;
179 }
180 Err(error) if error.kind() == ErrorKind::NotFound => {}
182 Err(error) => return Err(error),
183 }
184 }
185 Ok(report)
186}
187
188#[derive(Default)]
190pub(crate) struct Glyphs {
191 processed_signature: Option<[u8; 16]>,
192 parsed: HashMap<[u8; 16], Arc<CachedGlyph>>,
193 processed: HashMap<[u8; 16], CachedProcessedGlyph>,
194 variant_processed: HashMap<[u8; 16], Vec<(u64, CachedProcessedGlyph)>>,
196}
197
198pub(crate) struct CacheState {
201 fingerprint: [u8; 16],
202 loaded: Option<([u32; 2], usize)>,
205 pending: Option<(Vec<u8>, Range<usize>)>,
209 glyphs: OnceLock<Option<Glyphs>>,
211 fonts: Vec<FontEntry>,
212 glyph_set: [u8; 16],
213 #[cfg(feature = "in_memory")]
216 settings_digests: [[u8; 16]; 5],
217 font_keys: [[u8; 16]; 5],
219}
220
221impl CacheState {
222 pub(crate) fn new(options: &ResolvedGenerationOptions, sources: &[LoadedSvgSource]) -> Self {
223 let settings = font_settings_json(options);
224 Self::with_identity(&settings, &settings, glyph_set(options, sources))
225 }
226
227 pub(crate) fn new_variant<Files: AsRef<[LoadedSvgSource]>>(
230 options: &ResolvedGenerationOptions,
231 variants: &ResolvedVariants,
232 family: &VariantFamilySources<Files>,
233 ) -> Self {
234 let settings = font_settings_json(options);
235 let with_variants = |value: Value| {
236 let mut settings = settings.clone();
237 let object = settings.as_object_mut().expect("settings are an object");
238 object.insert("variants".to_owned(), value);
239 settings
240 };
241 let identity = with_variants(json!(true));
245 let designs: Vec<_> = variants
246 .variants
247 .iter()
248 .map(|variant| json!([variant.name, variant.weight]))
249 .collect();
250 let keys = with_variants(json!({ "default": variants.default_index, "designs": designs }));
251 Self::with_identity(&identity, &keys, variant_glyph_set(family))
252 }
253
254 fn with_identity(identity: &Value, keys: &Value, glyph_set: [u8; 16]) -> Self {
257 let mut glyph_settings = with_format_options(identity, &[]);
261 if let Some(settings) = glyph_settings.as_object_mut() {
262 settings.remove("colorSlots");
263 }
264 let fingerprint = digest(&(FORMAT_VERSION, ENGINE_VERSION, &glyph_settings));
265 let settings_digests = format_settings_digests(keys);
266 Self {
267 fingerprint,
268 loaded: None,
269 pending: None,
270 glyphs: OnceLock::new(),
271 fonts: Vec::new(),
272 glyph_set,
273 #[cfg(feature = "in_memory")]
274 settings_digests,
275 font_keys: font_keys(&settings_digests, &glyph_set),
276 }
277 }
278
279 #[cfg(feature = "in_memory")]
280 pub(crate) fn refresh_glyph_set(&mut self, glyph_set: [u8; 16]) {
283 self.glyph_set = glyph_set;
284 self.font_keys = font_keys(&self.settings_digests, &self.glyph_set);
285 }
286
287 pub(crate) fn load(&mut self, bytes: Vec<u8>) {
289 if let Some((checksums, glyphs)) = self.decode(&bytes) {
290 self.loaded = Some((checksums, bytes.len()));
291 self.pending = Some((bytes, glyphs));
292 } else {
293 self.fonts.clear();
294 }
295 }
296
297 pub(crate) fn glyphs(&self) -> Option<&Glyphs> {
300 self.glyphs
301 .get_or_init(|| match &self.pending {
302 None => Some(Glyphs::default()),
303 Some((bytes, range)) => {
304 let section = &bytes[range.clone()];
305 let expected = self.loaded.map(|(checksums, _)| checksums[0]);
306 (Some(checksum(section)) == expected)
307 .then(|| decode_glyphs(section))
308 .flatten()
309 }
310 })
311 .as_ref()
312 }
313
314 fn replace_glyphs(&mut self, glyphs: Glyphs) {
317 if !matches!(self.glyphs.get(), Some(Some(_))) {
318 self.loaded = None;
319 }
320 self.pending = None;
321 self.glyphs = OnceLock::from(Some(glyphs));
322 }
323
324 #[cfg(feature = "in_memory")]
327 pub(crate) fn release_glyphs(&mut self) {
328 self.replace_glyphs(Glyphs::default());
329 }
330
331 fn font_key(&self, font_type: FontType) -> [u8; 16] {
332 self.font_keys[usize::from(font_type_tag(font_type))]
333 }
334
335 pub(crate) fn font_hits(&self, types: &[FontType]) -> Vec<CachedFont> {
337 types
338 .iter()
339 .filter_map(|font_type| {
340 let key = self.font_key(*font_type);
341 self.fonts
342 .iter()
343 .find(|entry| entry.font.font_type() == *font_type && entry.key == key)
344 .map(|entry| entry.font.clone())
345 })
346 .collect()
347 }
348
349 pub(crate) fn hydrate(
351 &self,
352 svg_options: &SvgOptions,
353 sources: &[LoadedSvgSource],
354 ) -> GlyphCache {
355 let mut cache = GlyphCache::default();
356 let Some(glyphs) = self.glyphs() else {
357 return cache;
358 };
359 for source in sources {
360 let color = svg_options.selects_color(&source.glyph_name);
362 let hash = source_content_hash(&source.contents);
363 if let Some(glyph) = glyphs.parsed.get(&hash)
364 && glyph.color_layers.is_some() == color
365 {
366 cache.by_content_hash.insert(hash, glyph.clone());
367 }
368 if let Some(glyph) = glyphs.processed.get(&hash)
369 && glyph.color_layers.is_some() == color
370 {
371 cache
372 .processed_entries
373 .insert(source.id.clone(), glyph.clone());
374 }
375 }
376 cache.processed_signature = glyphs.processed_signature;
377 cache
378 }
379
380 pub(crate) fn hydrate_variants<Files: AsRef<[LoadedSvgSource]>>(
382 &self,
383 svg_options: &SvgOptions,
384 variants: &[Files],
385 ) -> VariantGlyphCache {
386 let Some(glyphs) = self.glyphs() else {
387 return VariantGlyphCache::default();
388 };
389 let mut cache = VariantGlyphCache {
390 signature: glyphs.processed_signature,
391 ..Default::default()
392 };
393 for (index, files) in variants.iter().enumerate() {
394 let mut parsed = GlyphCache::default();
395 for source in files.as_ref() {
396 let color = svg_options.selects_color(&source.glyph_name);
397 let hash = source_content_hash(&source.contents);
398 let Some(glyph) = glyphs
399 .parsed
400 .get(&hash)
401 .filter(|glyph| glyph.color_layers.is_some() == color)
402 else {
403 continue;
404 };
405 parsed.entries.insert(source.id.clone(), glyph.clone());
407 parsed.content_hashes.insert(source.id.clone(), hash);
408 parsed.by_content_hash.insert(hash, glyph.clone());
409 let processed = glyphs.variant_processed.get(&hash).into_iter().flatten();
410 for (advance, glyph) in
411 processed.filter(|(_, glyph)| glyph.color_layers.is_some() == color)
412 {
413 cache
414 .processed
415 .insert((index, source.id.clone(), *advance), glyph.clone());
416 }
417 }
418 cache.parsed.push(parsed);
419 }
420 cache
421 }
422
423 pub(crate) fn update(
427 &mut self,
428 sources: &[LoadedSvgSource],
429 glyph_cache: &GlyphCache,
430 outputs: &FontOutputs,
431 types: &[FontType],
432 ) -> Option<Vec<u8>> {
433 let mut glyphs = Glyphs::default();
434 for source in sources {
437 let hash = source_content_hash(&source.contents);
438 if let Some(glyph) = glyph_cache
440 .entries
441 .get(&source.id)
442 .or_else(|| glyph_cache.by_content_hash.get(&hash))
443 {
444 glyphs.parsed.insert(hash, glyph.clone());
445 }
446 if let Some(glyph) = glyph_cache.processed_entries.get(&source.id) {
447 glyphs.processed.insert(hash, glyph.clone());
448 }
449 }
450 glyphs.processed_signature = glyph_cache.processed_signature;
451 self.replace_glyphs(glyphs);
452 self.finish(outputs, types)
453 }
454
455 pub(crate) fn update_variants<Files: AsRef<[LoadedSvgSource]>>(
458 &mut self,
459 variants: &[Files],
460 glyph_cache: &VariantGlyphCache,
461 outputs: &FontOutputs,
462 types: &[FontType],
463 ) -> Option<Vec<u8>> {
464 let mut glyphs = Glyphs::default();
465 let mut hashes = HashMap::new();
466 for (index, files) in variants.iter().enumerate() {
467 let Some(parsed) = glyph_cache.parsed.get(index) else {
468 continue;
469 };
470 for source in files.as_ref() {
471 let hash = source_content_hash(&source.contents);
472 if parsed.content_hashes.get(&source.id) != Some(&hash) {
473 continue;
474 }
475 if let Some(glyph) = parsed.entries.get(&source.id) {
476 glyphs.parsed.insert(hash, glyph.clone());
477 hashes.insert((index, source.id.as_str()), hash);
478 }
479 }
480 }
481 for ((index, id, advance), glyph) in &glyph_cache.processed {
484 if let Some(hash) = hashes.get(&(*index, id.as_str()))
485 && glyphs.parsed.get(hash).is_some_and(|parsed| {
486 parsed.color_layers.is_some() == glyph.color_layers.is_some()
487 })
488 {
489 let entries = glyphs.variant_processed.entry(*hash).or_default();
490 if entries.iter().all(|(known, _)| known != advance) {
491 entries.push((*advance, glyph.clone()));
492 }
493 }
494 }
495 glyphs.processed_signature = glyph_cache.signature;
496 self.replace_glyphs(glyphs);
497 self.finish(outputs, types)
498 }
499
500 fn finish(&mut self, outputs: &FontOutputs, types: &[FontType]) -> Option<Vec<u8>> {
503 let mut fonts: Vec<FontEntry> = types
504 .iter()
505 .filter_map(|font_type| {
506 Some(FontEntry {
507 key: self.font_key(*font_type),
508 glyph_set: self.glyph_set,
509 font: CachedFont::from_outputs(outputs, *font_type)?,
510 })
511 })
512 .collect();
513 for entry in take(&mut self.fonts) {
514 let same_type = fonts
515 .iter()
516 .filter(|kept| kept.font.font_type() == entry.font.font_type())
517 .collect::<Vec<_>>();
518 if entry.glyph_set == self.glyph_set
521 && same_type.len() < ENTRIES_PER_FORMAT
522 && same_type.iter().all(|kept| kept.key != entry.key)
523 {
524 fonts.push(entry);
525 }
526 }
527 self.fonts = fonts;
528
529 let (encoded, checksums) = self.encode();
530 let identity = Some((checksums, encoded.len()));
531 if self.loaded == identity {
534 return None;
535 }
536 self.loaded = identity;
537 Some(encoded)
538 }
539
540 fn encode(&self) -> (Vec<u8>, [u32; 2]) {
542 let mut out = Writer::default();
543 out.0.extend_from_slice(MAGIC);
544 out.u32(FORMAT_VERSION);
545 out.bytes(ENGINE_VERSION.as_bytes());
546 out.array(&self.fingerprint);
547 let empty = Glyphs::default();
548 let glyphs = self.glyphs().unwrap_or(&empty);
549 let glyph_checksum = section(&mut out, |out| encode_glyphs(out, glyphs));
550 let font_checksum = section(&mut out, |out| {
551 out.len(self.fonts.len());
552 for entry in &self.fonts {
553 out.u8(font_type_tag(entry.font.font_type()));
554 out.array(&entry.key);
555 out.array(&entry.glyph_set);
556 out.bytes(entry.font.bytes());
557 }
558 });
559 (out.0, [glyph_checksum, font_checksum])
560 }
561
562 fn decode(&mut self, bytes: &[u8]) -> Option<([u32; 2], Range<usize>)> {
565 let mut input = Reader(bytes);
566 (input.take(MAGIC.len())? == MAGIC).then_some(())?;
567 (input.u32()? == FORMAT_VERSION).then_some(())?;
568 (input.bytes()? == ENGINE_VERSION.as_bytes()).then_some(())?;
569 (input.array::<16>()? == self.fingerprint).then_some(())?;
570
571 let glyph_checksum = input.u32()?;
572 let glyph_len = input.u32()? as usize;
573 let glyph_start = bytes.len() - input.0.len();
574 input.take(glyph_len)?;
575
576 let font_checksum = input.u32()?;
577 let font_len = input.u32()? as usize;
578 let mut fonts = Reader(input.take(font_len)?);
579 input.0.is_empty().then_some(())?;
580 (checksum(fonts.0) == font_checksum).then_some(())?;
581 for _ in 0..fonts.len(33)? {
582 let font_type = *FONT_TYPES.get(fonts.u8()? as usize)?;
583 let key = fonts.array()?;
584 let glyph_set = fonts.array()?;
585 let font = CachedFont::decode(font_type, fonts.bytes()?.to_vec())?;
586 self.fonts.push(FontEntry {
587 key,
588 glyph_set,
589 font,
590 });
591 }
592 fonts.0.is_empty().then_some(())?;
593 Some((
594 [glyph_checksum, font_checksum],
595 glyph_start..glyph_start + glyph_len,
596 ))
597 }
598}
599
600fn section(out: &mut Writer, write: impl FnOnce(&mut Writer)) -> u32 {
604 let frame = out.0.len();
605 out.u32(0);
606 out.u32(0);
607 let start = out.0.len();
608 write(out);
609 let body_checksum = checksum(&out.0[start..]);
610 let len = u32::try_from(out.0.len() - start).expect("cache sections stay below 4 GiB");
611 out.0[frame..frame + 4].copy_from_slice(&body_checksum.to_le_bytes());
612 out.0[frame + 4..start].copy_from_slice(&len.to_le_bytes());
613 body_checksum
614}
615
616fn encode_glyphs(out: &mut Writer, glyphs: &Glyphs) {
617 out.array(&glyphs.processed_signature.unwrap_or_default());
619
620 let mut parsed: Vec<_> = glyphs.parsed.iter().collect();
622 parsed.sort_unstable_by_key(|(hash, _)| **hash);
623 out.len(parsed.len());
624 for (hash, glyph) in parsed {
625 out.array(hash);
626 out.f64(glyph.height);
627 out.f64(glyph.width);
628 out.len(glyph.paths.len());
629 for path in &glyph.paths {
630 out.tiny_path(path);
631 }
632 out.parsed_color_layers(glyph.color_layers.as_deref());
633 }
634
635 let mut processed: Vec<_> = glyphs.processed.iter().collect();
636 processed.sort_unstable_by_key(|(hash, _)| **hash);
637 out.len(processed.len());
638 for (hash, glyph) in processed {
639 out.array(hash);
640 out.processed_glyph(glyph);
641 }
642
643 let mut variant_processed: Vec<_> = glyphs.variant_processed.iter().collect();
644 variant_processed.sort_unstable_by_key(|(hash, _)| **hash);
645 out.len(variant_processed.len());
646 for (hash, entries) in variant_processed {
647 out.array(hash);
648 let mut entries: Vec<_> = entries.iter().collect();
649 entries.sort_unstable_by_key(|(advance, _)| *advance);
650 out.len(entries.len());
651 for (advance, glyph) in entries {
652 out.u64(*advance);
653 out.processed_glyph(glyph);
654 }
655 }
656}
657
658fn decode_glyphs(section: &[u8]) -> Option<Glyphs> {
659 let mut input = Reader(section);
660 let mut glyphs = Glyphs {
661 processed_signature: Some(input.array()?),
662 ..Default::default()
663 };
664 for _ in 0..input.len(16)? {
665 let hash = input.array()?;
666 let height = input.f64()?;
667 let width = input.f64()?;
668 let mut paths = Vec::new();
669 for _ in 0..input.len(8)? {
670 paths.push(input.tiny_path()?);
671 }
672 let color_layers = input.parsed_color_layers(paths.len())?;
673 glyphs.parsed.insert(
674 hash,
675 Arc::new(CachedGlyph {
676 color_layers,
677 height,
678 paths,
679 width,
680 }),
681 );
682 }
683 for _ in 0..input.len(16)? {
684 let hash = input.array()?;
685 glyphs.processed.insert(hash, input.processed_glyph()?);
686 }
687 for _ in 0..input.len(16)? {
688 let hash = input.array()?;
689 let mut entries = Vec::new();
690 for _ in 0..input.len(8)? {
691 entries.push((input.u64()?, input.processed_glyph()?));
692 }
693 glyphs.variant_processed.insert(hash, entries);
694 }
695 input.0.is_empty().then_some(glyphs)
696}
697
698#[cfg(feature = "fs")]
701fn created_by_cache(path: &Path) -> bool {
702 let name = path.file_name().unwrap_or_default().to_string_lossy();
703 if let Some(stem) = name.strip_suffix(&format!(".{EXTENSION}")) {
704 cache_stem(stem).is_some() && has_cache_header(path)
705 } else {
706 temp_file_stem(&name).is_some_and(|stem| cache_stem(stem).is_some())
707 }
708}
709
710#[cfg(feature = "fs")]
711fn cache_stem(stem: &str) -> Option<&str> {
713 let (prefix, hash) = stem.rsplit_once('.')?;
714 (hash.len() == 16 && hash.bytes().all(|byte| byte.is_ascii_hexdigit())).then_some(prefix)
715}
716
717#[cfg(feature = "fs")]
718fn temp_file_stem(name: &str) -> Option<&str> {
721 let (stem, suffix) = name
722 .strip_suffix(".tmp")?
723 .rsplit_once(&format!(".{EXTENSION}."))?;
724 let (pid, write) = suffix.split_once('-')?;
725 let digits = |part: &str| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_digit());
726 (digits(pid) && digits(write)).then_some(stem)
727}
728
729#[cfg(feature = "fs")]
730fn has_cache_header(path: &Path) -> bool {
732 let mut header = [0; MAGIC.len()];
733 File::open(path)
734 .and_then(|mut file| file.read_exact(&mut header))
735 .is_ok_and(|()| &header == MAGIC)
736}
737
738#[cfg(feature = "fs")]
740pub(crate) struct DiskCache {
741 dir: PathBuf,
742 file_name: String,
743 font_prefix: String,
744 state: CacheState,
745}
746
747#[cfg(feature = "fs")]
748impl Deref for DiskCache {
749 type Target = CacheState;
750
751 fn deref(&self) -> &CacheState {
752 &self.state
753 }
754}
755
756#[cfg(feature = "fs")]
757impl DiskCache {
758 pub(crate) fn open(
759 dir: &str,
760 options: &ResolvedGenerationOptions,
761 sources: &[LoadedSvgSource],
762 ) -> Self {
763 Self::load(dir, &options.font_name, CacheState::new(options, sources))
764 }
765
766 pub(crate) fn open_variant<Files: AsRef<[LoadedSvgSource]>>(
767 dir: &str,
768 options: &ResolvedGenerationOptions,
769 variants: &ResolvedVariants,
770 family: &VariantFamilySources<Files>,
771 ) -> Self {
772 let state = CacheState::new_variant(options, variants, family);
773 Self::load(dir, &options.font_name, state)
774 }
775
776 fn load(dir: &str, font_name: &str, mut state: CacheState) -> Self {
777 let dir = PathBuf::from(dir);
778 let font_prefix = sanitize(font_name);
779 let file_name = format!(
780 "{font_prefix}.{}.{EXTENSION}",
781 &hex(&state.fingerprint)[..16]
782 );
783 if let Ok(bytes) = fs::read(dir.join(&file_name)) {
784 state.load(bytes);
785 }
786 Self {
787 dir,
788 file_name,
789 font_prefix,
790 state,
791 }
792 }
793
794 pub(crate) fn store(
797 mut self,
798 sources: &[LoadedSvgSource],
799 glyph_cache: &GlyphCache,
800 outputs: &FontOutputs,
801 types: &[FontType],
802 ) {
803 let bytes = self.state.update(sources, glyph_cache, outputs, types);
804 self.save(bytes);
805 }
806
807 pub(crate) fn store_variants<Files: AsRef<[LoadedSvgSource]>>(
809 mut self,
810 variants: &[Files],
811 glyph_cache: &VariantGlyphCache,
812 outputs: &FontOutputs,
813 types: &[FontType],
814 ) {
815 let bytes = self
816 .state
817 .update_variants(variants, glyph_cache, outputs, types);
818 self.save(bytes);
819 }
820
821 fn save(self, bytes: Option<Vec<u8>>) {
822 let Self {
823 dir,
824 file_name,
825 font_prefix,
826 ..
827 } = self;
828 if let Some(bytes) = bytes {
829 let path = dir.join(&file_name);
830 match write(&dir, &file_name, &bytes) {
831 Ok(()) => prune_siblings(&dir, &file_name, &font_prefix),
832 Err(_) if path.is_dir() => eprintln!(
835 "warning: webfont-generator: {} is a directory, so the generation cache can't be saved there; remove it to re-enable caching",
836 path.display()
837 ),
838 Err(_) => {}
839 }
840 }
841 }
842}
843
844#[cfg(feature = "fs")]
845fn write(dir: &Path, file_name: &str, bytes: &[u8]) -> Result<()> {
846 fs::create_dir_all(dir)?;
847 static WRITES: AtomicU64 = AtomicU64::new(0);
849 let write = WRITES.fetch_add(1, Relaxed);
850 let tmp = dir.join(format!("{}.{}-{write}.tmp", file_name, process::id()));
851 fs::write(&tmp, bytes)?;
852 fs::rename(&tmp, dir.join(file_name)).inspect_err(|_| {
853 let _ = fs::remove_file(&tmp);
854 })
855}
856
857#[cfg(feature = "fs")]
861fn prune_siblings(dir: &Path, file_name: &str, font_prefix: &str) {
862 for entry in fs::read_dir(dir).into_iter().flatten().flatten() {
863 let name = entry.file_name();
864 let name = name.to_string_lossy();
865 let sibling = name
866 .strip_suffix(&format!(".{EXTENSION}"))
867 .and_then(cache_stem)
868 .is_some_and(|prefix| prefix == font_prefix);
869 if sibling
870 && name != file_name
871 && entry.file_type().is_ok_and(|kind| kind.is_file())
872 && has_cache_header(&entry.path())
873 {
874 let _ = fs::remove_file(entry.path());
875 }
876 }
877}
878
879fn format_settings_digests(settings: &Value) -> [[u8; 16]; 5] {
883 FONT_TYPES.map(|font_type| {
884 let own: &[&str] = match font_type {
885 FontType::Svg => &[],
886 FontType::Ttf | FontType::Eot => &["ttf"],
887 FontType::Woff => &["ttf", "woff"],
888 FontType::Woff2 => &["ttf", "woff2"],
889 };
890 digest(&with_format_options(settings, own))
891 })
892}
893
894fn with_format_options(settings: &Value, own: &[&str]) -> Value {
898 let mut settings = settings.clone();
899 if let Some(object) = settings.as_object_mut()
900 && let Some(formats) = object
901 .get_mut("formatOptions")
902 .and_then(Value::as_object_mut)
903 {
904 formats.retain(|name, options| {
905 (name == "svg" || own.contains(&name.as_str()))
906 && options
907 .as_object()
908 .is_none_or(|options| !options.is_empty())
909 });
910 if formats.is_empty() {
911 object.remove("formatOptions");
912 }
913 }
914 settings
915}
916
917fn font_keys(settings_digests: &[[u8; 16]; 5], glyph_set: &[u8; 16]) -> [[u8; 16]; 5] {
920 from_fn(|index| {
921 digest(&(
922 FORMAT_VERSION,
923 ENGINE_VERSION,
924 FONT_TYPES[index].as_extension(),
925 settings_digests[index],
926 glyph_set,
927 ))
928 })
929}
930
931fn checksum(bytes: &[u8]) -> u32 {
933 let mut crc = Crc::new();
934 crc.update(bytes);
935 crc.sum()
936}
937
938fn digest(value: &impl Serialize) -> [u8; 16] {
940 compute(to_vec(value).expect("cache identity serializes")).0
941}
942
943pub(crate) fn glyph_set(
945 options: &ResolvedGenerationOptions,
946 sources: &[LoadedSvgSource],
947) -> [u8; 16] {
948 let glyphs: Vec<Value> = sources
949 .iter()
950 .map(|source| {
951 json!([
952 source.glyph_name,
953 options.codepoints.get(&source.glyph_name),
954 hex(&source_content_hash(&source.contents)),
955 ])
956 })
957 .collect();
958 digest(&glyphs)
959}
960
961pub(crate) fn variant_glyph_set<Files: AsRef<[LoadedSvgSource]>>(
964 family: &VariantFamilySources<Files>,
965) -> [u8; 16] {
966 let glyphs: Vec<Value> = family
967 .glyphs
968 .iter()
969 .map(|glyph| {
970 let designs: Vec<Option<String>> = glyph
971 .sources
972 .iter()
973 .map(|source| match source {
974 Some(VariantGlyphSource::Source {
975 variant_index,
976 source_index,
977 }) => Some(hex(&source_content_hash(
978 &family.variants[*variant_index].as_ref()[*source_index].contents,
979 ))),
980 _ => None,
981 })
982 .collect();
983 json!([glyph.name, glyph.codepoint, designs])
984 })
985 .collect();
986 digest(&glyphs)
987}
988
989#[cfg(feature = "fs")]
990fn sanitize(font_name: &str) -> String {
991 font_name
992 .chars()
993 .map(|char| {
994 if char.is_ascii_alphanumeric() || char == '-' || char == '_' {
995 char
996 } else {
997 '_'
998 }
999 })
1000 .collect()
1001}
1002
1003fn hex(bytes: &[u8]) -> String {
1004 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
1005}