mod query;
pub use query::{Query, QueryFamily, QueryFont, QueryStatus};
use crate::font::FontInfoOverride;
use super::SourceCache;
use super::{
Blob, FontStyle, FontWeight, FontWidth, GenericFamily, Language, Script,
backend::SystemFonts,
fallback::{FallbackKey, FallbackMap},
family::{FamilyId, FamilyInfo},
family_name::{FamilyName, FamilyNameMap},
font::FontInfo,
generic::GenericFamilyMap,
source::{SourceId, SourceInfo, SourceKind},
};
use crate::AtomicCounter;
use alloc::{string::String, sync::Arc, vec::Vec};
use hashbrown::HashMap;
use read_fonts::types::NameId;
#[cfg(feature = "std")]
use std::path::Path;
#[cfg(feature = "std")]
use std::sync::{Mutex, atomic::Ordering};
type FamilyMap = HashMap<FamilyId, Option<FamilyInfo>>;
#[derive(Copy, Clone, Debug)]
pub struct CollectionOptions {
pub shared: bool,
pub system_fonts: bool,
}
impl Default for CollectionOptions {
fn default() -> Self {
Self {
shared: false,
system_fonts: true,
}
}
}
#[derive(Clone)]
pub struct Collection {
inner: Inner,
query_state: query::QueryState,
}
impl Collection {
pub fn new(options: CollectionOptions) -> Self {
Self {
inner: Inner::new(options),
query_state: query::QueryState::default(),
}
}
#[cfg(feature = "std")]
pub fn make_shared(&mut self) {
self.inner.make_shared();
}
pub fn load_system_fonts(&mut self) {
if self.inner.system.is_none() {
self.inner.load_system_fonts();
}
}
#[cfg(feature = "std")]
pub fn load_fonts_from_paths(&mut self, paths: impl IntoIterator<Item = impl AsRef<Path>>) {
self.inner.load_fonts_from_paths(paths);
}
pub fn family_names(&mut self) -> impl Iterator<Item = &str> + '_ + Clone {
self.inner.family_names()
}
pub fn family_id(&mut self, name: &str) -> Option<FamilyId> {
self.inner.family_id(name)
}
pub fn family_name(&mut self, id: FamilyId) -> Option<&str> {
self.inner.family_name(id)
}
pub fn family(&mut self, id: FamilyId) -> Option<FamilyInfo> {
self.inner.family(id)
}
pub fn family_by_name(&mut self, name: &str) -> Option<FamilyInfo> {
self.inner.family_by_name(name)
}
pub fn generic_families(
&mut self,
family: GenericFamily,
) -> impl Iterator<Item = FamilyId> + '_ + Clone {
self.inner.generic_families(family)
}
pub fn set_generic_families(
&mut self,
generic: GenericFamily,
families: impl Iterator<Item = FamilyId>,
) {
self.inner.set_generic_families(generic, families);
}
pub fn append_generic_families(
&mut self,
generic: GenericFamily,
families: impl Iterator<Item = FamilyId>,
) {
self.inner.append_generic_families(generic, families);
}
pub fn fallback_families(
&mut self,
key: impl Into<FallbackKey>,
) -> impl Iterator<Item = FamilyId> + '_ + Clone {
self.inner.fallback_families(key)
}
pub fn set_fallbacks(
&mut self,
key: impl Into<FallbackKey>,
families: impl Iterator<Item = FamilyId>,
) -> bool {
self.inner.set_fallbacks(key, families)
}
pub fn append_fallbacks(
&mut self,
key: impl Into<FallbackKey>,
families: impl Iterator<Item = FamilyId>,
) -> bool {
self.inner.append_fallbacks(key, families)
}
pub fn query<'a>(&'a mut self, source_cache: &'a mut SourceCache) -> Query<'a> {
Query::new(self, source_cache)
}
pub fn register_fonts(
&mut self,
data: Blob<u8>,
info_override: Option<FontInfoOverride<'_>>,
) -> Vec<(FamilyId, Vec<FontInfo>)> {
self.inner.register_fonts(data, info_override)
}
pub fn unregister_font(
&mut self,
family: FamilyId,
width: FontWidth,
style: FontStyle,
weight: FontWeight,
) -> bool {
self.inner.unregister_font(family, width, style, weight)
}
pub fn clear(&mut self) {
self.inner.clear();
}
}
impl Default for Collection {
fn default() -> Self {
Self::new(CollectionOptions::default())
}
}
#[derive(Clone)]
struct Inner {
system: Option<System>,
data: CommonData,
#[allow(unused)]
shared: Option<Arc<Shared>>,
#[allow(unused)]
shared_version: u64,
fallback_cache: FallbackCache,
}
impl Inner {
pub fn new(options: CollectionOptions) -> Self {
let system = options.system_fonts.then(System::new);
let shared = options.shared.then(|| Arc::new(Shared::default()));
Self {
system,
data: CommonData::default(),
shared,
shared_version: 0,
fallback_cache: FallbackCache::default(),
}
}
#[cfg(feature = "std")]
pub fn make_shared(&mut self) {
if self.shared.is_none() {
self.shared = Some(Arc::new(Shared {
data: Mutex::new(self.data.clone()),
version: AtomicCounter::new(self.shared_version),
}));
}
}
pub fn load_system_fonts(&mut self) {
self.system = Some(System::new());
}
pub fn family_names(&mut self) -> impl Iterator<Item = &str> + '_ + Clone {
self.sync_shared();
FamilyNames {
ours: self.data.family_names.iter(),
system: self.system.as_ref().map(|sys| sys.family_names.iter()),
}
.map(|name| name.name())
}
pub fn family_id(&mut self, name: &str) -> Option<FamilyId> {
self.sync_shared();
self.data
.family_names
.get(name)
.or_else(|| {
self.system
.as_ref()
.and_then(|sys| sys.family_names.get(name))
})
.map(|n| n.id())
}
pub fn family_name(&mut self, id: FamilyId) -> Option<&str> {
self.sync_shared();
self.data
.family_names
.get_by_id(id)
.or_else(|| {
self.system
.as_ref()
.and_then(|sys| sys.family_names.get_by_id(id))
})
.map(|name| name.name())
}
pub fn family(&mut self, id: FamilyId) -> Option<FamilyInfo> {
self.sync_shared();
if let Some(family) = self.data.families.get(&id) {
family.as_ref().cloned()
} else {
#[cfg(feature = "system")]
if let Some(system) = &self.system {
let family = system.fonts.lock().unwrap().family(id);
self.data.families.insert(id, family.clone());
family
} else {
None
}
#[cfg(not(feature = "system"))]
{
None
}
}
}
pub fn family_by_name(&mut self, name: &str) -> Option<FamilyInfo> {
if let Some(id) = self.family_id(name) {
self.family(id)
} else {
None
}
}
pub fn generic_families(
&mut self,
family: GenericFamily,
) -> impl Iterator<Item = FamilyId> + '_ + Clone {
self.sync_shared();
GenericFamilies {
ours: self.data.generic_families.get(family).iter().copied(),
system: self
.system
.as_ref()
.map(|sys| sys.generic_families.get(family).iter().copied()),
}
}
pub fn set_generic_families(
&mut self,
generic: GenericFamily,
families: impl Iterator<Item = FamilyId>,
) {
self.sync_shared();
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
shared
.data
.lock()
.unwrap()
.generic_families
.set(generic, families);
shared.bump_version();
} else {
self.data.generic_families.set(generic, families);
}
#[cfg(not(feature = "std"))]
self.data.generic_families.set(generic, families);
}
pub fn append_generic_families(
&mut self,
generic: GenericFamily,
families: impl Iterator<Item = FamilyId>,
) {
self.sync_shared();
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
shared
.data
.lock()
.unwrap()
.generic_families
.append(generic, families);
shared.bump_version();
} else {
self.data.generic_families.append(generic, families);
}
#[cfg(not(feature = "std"))]
self.data.generic_families.append(generic, families);
}
pub fn fallback_families(
&mut self,
key: impl Into<FallbackKey>,
) -> impl Iterator<Item = FamilyId> + '_ + Clone {
let selector = key.into();
let script = selector.script();
let lang_key = selector.locale();
if self.fallback_cache.script != Some(script) || self.fallback_cache.language != lang_key {
self.sync_shared();
self.fallback_cache.reset();
#[cfg(feature = "system")]
if let Some(families) = self.data.fallbacks.get(selector) {
self.fallback_cache.set(script, lang_key, families);
} else if let Some(system) = self.system.as_ref() {
#[allow(unused_mut)]
let mut system = system.fonts.lock().unwrap();
if let Some(family) = system.fallback(selector) {
self.data.fallbacks.set(selector, core::iter::once(family));
self.fallback_cache.set(script, lang_key, &[family]);
}
}
#[cfg(not(feature = "system"))]
if let Some(families) = self.data.fallbacks.get(selector) {
self.fallback_cache.set(script, lang_key, families);
}
}
self.fallback_cache.families.iter().copied()
}
pub fn set_fallbacks(
&mut self,
key: impl Into<FallbackKey>,
families: impl Iterator<Item = FamilyId>,
) -> bool {
self.sync_shared();
self.fallback_cache.reset();
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
let result = shared.data.lock().unwrap().fallbacks.set(key, families);
shared.bump_version();
result
} else {
self.data.fallbacks.set(key, families)
}
#[cfg(not(feature = "std"))]
self.data.fallbacks.set(key, families)
}
pub fn append_fallbacks(
&mut self,
key: impl Into<FallbackKey>,
families: impl Iterator<Item = FamilyId>,
) -> bool {
self.sync_shared();
self.fallback_cache.reset();
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
let result = shared.data.lock().unwrap().fallbacks.append(key, families);
shared.bump_version();
result
} else {
self.data.fallbacks.append(key, families)
}
#[cfg(not(feature = "std"))]
self.data.fallbacks.append(key, families)
}
#[cfg(feature = "std")]
pub fn load_fonts_from_paths(&mut self, paths: impl IntoIterator<Item = impl AsRef<Path>>) {
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
shared.data.lock().unwrap().load_fonts_from_paths(paths);
shared.bump_version();
} else {
self.data.load_fonts_from_paths(paths);
}
#[cfg(not(feature = "std"))]
self.data.register_fonts(paths)
}
pub fn register_fonts(
&mut self,
data: Blob<u8>,
info_override: Option<FontInfoOverride<'_>>,
) -> Vec<(FamilyId, Vec<FontInfo>)> {
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
let result = shared
.data
.lock()
.unwrap()
.register_fonts(data, info_override);
shared.bump_version();
result
} else {
self.data.register_fonts(data, info_override)
}
#[cfg(not(feature = "std"))]
self.data.register_fonts(data, info_override)
}
pub fn unregister_font(
&mut self,
family: FamilyId,
width: FontWidth,
style: FontStyle,
weight: FontWeight,
) -> bool {
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
let result = shared
.data
.lock()
.unwrap()
.unregister_font(family, width, style, weight);
shared.bump_version();
result.is_some()
} else {
self.data
.unregister_font(family, width, style, weight)
.is_some()
}
#[cfg(not(feature = "std"))]
self.data
.unregister_font(family, width, style, weight)
.is_some()
}
pub fn clear(&mut self) {
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
shared.data.lock().unwrap().clear();
shared.bump_version();
} else {
self.data.clear();
}
self.data.clear();
}
fn sync_shared(&mut self) {
#[cfg(feature = "std")]
if let Some(shared) = &self.shared {
let version = shared.version.load(Ordering::Acquire);
if self.shared_version != version {
self.data = shared.data.lock().unwrap().clone();
self.shared_version = version;
self.fallback_cache.reset();
}
}
}
}
#[derive(Clone)]
struct FamilyNames<I> {
ours: I,
system: Option<I>,
}
impl<'a, I> Iterator for FamilyNames<I>
where
I: Iterator<Item = &'a FamilyName> + 'a,
{
type Item = &'a FamilyName;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ours) = self.ours.next() {
return Some(ours);
}
self.system.as_mut()?.next()
}
}
#[derive(Clone)]
struct GenericFamilies<I> {
ours: I,
system: Option<I>,
}
impl<I> Iterator for GenericFamilies<I>
where
I: Iterator<Item = FamilyId>,
{
type Item = FamilyId;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ours) = self.ours.next() {
return Some(ours);
}
self.system.as_mut()?.next()
}
}
#[derive(Clone, Default)]
struct FallbackCache {
script: Option<Script>,
language: Option<Language>,
families: Vec<FamilyId>,
}
impl FallbackCache {
fn reset(&mut self) {
self.script = None;
self.language = None;
self.families.clear();
}
fn set(&mut self, script: Script, language: Option<Language>, families: &[FamilyId]) {
self.script = Some(script);
self.language = language;
self.families.clear();
self.families.extend_from_slice(families);
}
}
#[derive(Clone)]
struct System {
#[cfg(feature = "system")]
fonts: Arc<Mutex<SystemFonts>>,
family_names: Arc<FamilyNameMap>,
generic_families: Arc<GenericFamilyMap>,
}
impl System {
fn new() -> Self {
let fonts = SystemFonts::new();
let family_names = fonts.name_map.clone();
let generic_families = fonts.generic_families.clone();
#[cfg(feature = "system")]
let fonts = Arc::new(Mutex::new(fonts));
Self {
#[cfg(feature = "system")]
fonts,
family_names,
generic_families,
}
}
}
#[derive(Clone, Default)]
struct CommonData {
family_names: FamilyNameMap,
families: FamilyMap,
generic_families: GenericFamilyMap,
fallbacks: FallbackMap,
}
impl CommonData {
#[cfg(feature = "std")]
fn load_fonts_from_paths(&mut self, paths: impl IntoIterator<Item = impl AsRef<Path>>) {
let mut families: HashMap<FamilyId, (FamilyName, Vec<FontInfo>)> = HashMap::default();
let mut scratch_family_name = String::default();
crate::scan::scan_paths(paths, 16, |scanned_font| {
let source = SourceInfo {
id: SourceId::new(),
kind: SourceKind::Path(Arc::from(scanned_font.path.unwrap())),
};
let font_data = scanned_font.font.data().as_bytes();
self.register_font_impl(
font_data,
source,
None,
&mut scratch_family_name,
&mut families,
);
});
}
fn register_fonts(
&mut self,
data: Blob<u8>,
info_override: Option<FontInfoOverride<'_>>,
) -> Vec<(FamilyId, Vec<FontInfo>)> {
let mut families: HashMap<FamilyId, (FamilyName, Vec<FontInfo>)> = HashMap::default();
let mut scratch_family_name = String::default();
let source = SourceInfo {
id: SourceId::new(),
kind: SourceKind::Memory(data.clone()),
};
self.register_font_impl(
data.as_ref(),
source,
info_override,
&mut scratch_family_name,
&mut families,
);
families
.into_iter()
.map(|(id, (_, fonts))| (id, fonts))
.collect()
}
fn register_font_impl(
&mut self,
font_data: &[u8],
source: SourceInfo,
info_override: Option<FontInfoOverride<'_>>,
scratch_family_name: &mut String,
families: &mut HashMap<FamilyId, (FamilyName, Vec<FontInfo>)>,
) {
super::scan::scan_memory(font_data, |scanned_font| {
scratch_family_name.clear();
let family_name =
if let Some(override_family_name) = info_override.and_then(|o| o.family_name) {
override_family_name
} else {
let family_chars = scanned_font
.english_or_first_name(NameId::TYPOGRAPHIC_FAMILY_NAME)
.or_else(|| scanned_font.english_or_first_name(NameId::FAMILY_NAME))
.map(|name| name.chars());
let Some(family_chars) = family_chars else {
return;
};
scratch_family_name.extend(family_chars);
#[allow(clippy::needless_borrow)] &scratch_family_name
};
if family_name.is_empty() {
return;
}
let Some(mut font) =
FontInfo::from_font_ref(&scanned_font.font, source.clone(), scanned_font.index)
else {
return;
};
if let Some(info_override) = info_override.as_ref() {
font.apply_override(info_override);
}
let name = self.family_names.get_or_insert(family_name);
families
.entry(name.id())
.or_insert_with(|| (name, Vec::default()))
.1
.push(font);
});
for (id, (name, fonts)) in families.iter() {
if let Some(Some(family)) = self.families.get_mut(id) {
let new_fonts = family.fonts().iter().chain(fonts).cloned();
*family = FamilyInfo::new(name.clone(), new_fonts);
} else {
let family = FamilyInfo::new(name.clone(), fonts.iter().cloned());
self.families.insert(*id, Some(family));
}
}
}
fn unregister_font(
&mut self,
family: FamilyId,
width: FontWidth,
style: FontStyle,
weight: FontWeight,
) -> Option<()> {
let family_name = self.family_names.get_by_id(family)?;
let family = self.families.get_mut(&family)?.as_mut()?;
let new_fonts = family
.fonts()
.iter()
.filter(|f| f.width() != width || f.style() != style || f.weight() != weight)
.cloned();
*family = FamilyInfo::new(family_name.clone(), new_fonts);
Some(())
}
fn clear(&mut self) {
*self = Self::default();
}
}
#[derive(Default)]
struct Shared {
#[allow(unused)]
version: AtomicCounter,
#[cfg(feature = "std")]
#[allow(unused)]
data: Mutex<CommonData>,
}
impl Shared {
#[cfg(feature = "std")]
fn bump_version(&self) {
self.version.fetch_add(1, Ordering::Release);
}
}
#[test]
#[cfg(all(
feature = "std",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
fn make_shared_matches_local() {
use crate::{Collection, CollectionOptions};
let mut collection = Collection::new(CollectionOptions {
shared: false,
system_fonts: false,
});
let font_dirs: Vec<std::path::PathBuf> = [
#[cfg(target_os = "macos")]
"/Library/Fonts",
#[cfg(target_os = "linux")]
"/usr/share/fonts",
#[cfg(target_os = "windows")]
"C:\\Windows\\Fonts",
]
.iter()
.map(std::path::PathBuf::from)
.filter(|p| p.is_dir())
.collect();
if font_dirs.is_empty() {
return;
}
collection.load_fonts_from_paths(&font_dirs);
let names_before: Vec<String> = collection.family_names().map(String::from).collect();
collection.make_shared();
let names_after: Vec<String> = collection.family_names().map(String::from).collect();
assert_eq!(names_before.len(), names_after.len());
}