Make FontSystem not self-referencing

This commit is contained in:
Edgar Geier 2023-03-01 02:50:10 +01:00
parent 0548d7ae59
commit 506a4194be
No known key found for this signature in database
GPG key ID: 7A65B51FD6B75EF5
10 changed files with 206 additions and 175 deletions

View file

@ -1,11 +1,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use unicode_script::Script;
use crate::Font;
use crate::{Font, FontKey};
use self::platform::*;
@ -26,7 +25,8 @@ mod platform;
mod platform;
pub struct FontFallbackIter<'a> {
fonts: &'a [Arc<Font<'a>>],
db: &'a fontdb::Database,
font_keys: &'a [FontKey],
default_families: &'a [&'a str],
default_i: usize,
scripts: Vec<Script>,
@ -39,13 +39,15 @@ pub struct FontFallbackIter<'a> {
impl<'a> FontFallbackIter<'a> {
pub fn new(
fonts: &'a [Arc<Font<'a>>],
db: &'a fontdb::Database,
font_keys: &'a [FontKey],
default_families: &'a [&'a str],
scripts: Vec<Script>,
locale: &'a str,
) -> Self {
Self {
fonts,
db,
font_keys,
default_families,
default_i: 0,
scripts,
@ -66,7 +68,8 @@ impl<'a> FontFallbackIter<'a> {
word
);
} else if self.other_i > 0 {
let font = &self.fonts[self.other_i - 1];
let font_key = self.font_keys[self.other_i - 1];
let font = Font::from_key(self.db, font_key).expect("invalid font key");
log::debug!(
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
@ -88,13 +91,14 @@ impl<'a> FontFallbackIter<'a> {
}
impl<'a> Iterator for FontFallbackIter<'a> {
type Item = &'a Arc<Font<'a>>;
type Item = Font<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.default_i < self.default_families.len() {
let default_family = self.default_families[self.default_i];
self.default_i += 1;
for font in self.fonts.iter() {
for font_key in self.font_keys.iter().copied() {
let font = Font::from_key(self.db, font_key).expect("invalid font key");
if font.info.family == default_family {
return Some(font);
}
@ -108,7 +112,8 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.script_i.1 < script_families.len() {
let script_family = script_families[self.script_i.1];
self.script_i.1 += 1;
for font in self.fonts.iter() {
for font_key in self.font_keys.iter().copied() {
let font = Font::from_key(self.db, font_key).expect("invalid font key");
if font.info.family == script_family {
return Some(font);
}
@ -129,7 +134,8 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.common_i < common_families.len() {
let common_family = common_families[self.common_i];
self.common_i += 1;
for font in self.fonts.iter() {
for font_key in self.font_keys.iter().copied() {
let font = Font::from_key(self.db, font_key).expect("invalid font key");
if font.info.family == common_family {
return Some(font);
}
@ -140,8 +146,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
//TODO: do we need to do this?
//TODO: do not evaluate fonts more than once!
let forbidden_families = forbidden_fallback();
while self.other_i < self.fonts.len() {
let font = &self.fonts[self.other_i];
while self.other_i < self.font_keys.len() {
let font_key = self.font_keys[self.other_i];
let font = Font::from_key(self.db, font_key).expect("invalid font key");
self.other_i += 1;
if !forbidden_families.contains(&font.info.family.as_str()) {
return Some(font);

View file

@ -1,14 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::Font;
/// Fonts that match a pattern
pub struct FontMatches<'a> {
pub locale: &'a str,
pub default_family: String,
pub fonts: Vec<Arc<Font<'a>>>,
}

View file

@ -4,12 +4,17 @@ use core::ops::Deref;
pub(crate) mod fallback;
pub use self::matches::*;
mod matches;
pub use self::system::*;
mod system;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
/// Identifies a [`Font`] in a [`FontSystem`]
pub struct FontKey {
pub id: fontdb::ID,
#[cfg(feature = "swash")]
pub swash: (u32, swash::CacheKey),
}
/// A font
pub struct Font<'a> {
pub info: &'a fontdb::FaceInfo,
@ -44,6 +49,36 @@ impl<'a> Font<'a> {
})
}
pub fn from_key(db: &'a fontdb::Database, key: FontKey) -> Option<Self> {
let info = db.face(key.id)?;
let data = match &info.source {
fontdb::Source::Binary(data) => data.deref().as_ref(),
#[cfg(feature = "std")]
fontdb::Source::File(path) => {
log::warn!("Unsupported fontdb Source::File('{}')", path.display());
return None;
}
#[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => data.deref().as_ref(),
};
Some(Self {
info,
data,
rustybuzz: rustybuzz::Face::from_slice(data, info.index)?,
#[cfg(feature = "swash")]
swash: key.swash,
})
}
pub fn key(&self) -> FontKey {
FontKey {
id: self.info.id,
#[cfg(feature = "swash")]
swash: self.swash,
}
}
#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef {
swash::FontRef {

View file

@ -6,7 +6,7 @@ use alloc::{
vec::Vec,
};
use crate::{Attrs, Font, FontMatches};
use crate::{Attrs, Font, FontKey};
/// Access system fonts
pub struct FontSystem {
@ -45,33 +45,38 @@ impl FontSystem {
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
let face = self.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
match Font::from_key(&self.db, key) {
Some(font) => Some(font),
None => {
let face = self.db.face(key.id)?;
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
}
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
let mut fonts = Vec::new();
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
Some(Font::new(self.db.face(id)?)?.key())
}
pub fn get_font_matches(&self, attrs: Attrs) -> Arc<Vec<FontKey>> {
let mut font_keys = Vec::new();
for face in self.db.faces() {
if !attrs.matches(face) {
continue;
}
if let Some(font) = self.get_font(face.id) {
fonts.push(font);
let font_key = match self.get_font_key(face.id) {
Some(font_key) => font_key,
None => continue,
};
if self.get_font(font_key).is_some() {
font_keys.push(font_key);
}
}
Arc::new(FontMatches {
locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(),
fonts,
})
Arc::new(font_keys)
}
}

View file

@ -6,7 +6,7 @@ use alloc::{
vec::Vec,
};
use crate::{Attrs, Font, FontMatches};
use crate::{Attrs, Font, FontKey};
/// Access system fonts
pub struct FontSystem {
@ -71,33 +71,38 @@ impl FontSystem {
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
let face = self.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
match Font::from_key(&self.db, key) {
Some(font) => Some(font),
None => {
let face = self.db.face(key.id)?;
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
}
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
let mut fonts = Vec::new();
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
Some(Font::new(self.db.face(id)?)?.key())
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<Vec<FontKey>> {
let mut font_keys = Vec::new();
for face in self.db.faces() {
if !attrs.matches(face) {
continue;
}
if let Some(font) = self.get_font(face.id) {
fonts.push(font);
let font_key = match self.get_font_key(face.id) {
Some(font_key) => font_key,
None => continue,
};
if self.get_font(font_key).is_some() {
font_keys.push(font_key);
}
}
Arc::new(FontMatches {
locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(),
fonts,
})
Arc::new(font_keys)
}
}

View file

@ -1,26 +1,22 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(feature = "swash")]
use std::collections::hash_map::Entry;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use crate::{Attrs, AttrsOwned, Font, FontMatches};
#[ouroboros::self_referencing]
struct FontSystemInner {
locale: String,
db: fontdb::Database,
#[borrows(db)]
#[not_covariant]
font_cache: Mutex<HashMap<fontdb::ID, Option<Arc<Font<'this>>>>>,
#[borrows(locale, db)]
#[not_covariant]
font_matches_cache: Mutex<HashMap<AttrsOwned, Arc<FontMatches<'this>>>>,
}
use crate::{Attrs, AttrsOwned, Font, FontKey};
/// Access system fonts
pub struct FontSystem(FontSystemInner);
pub struct FontSystem {
locale: String,
db: fontdb::Database,
font_matches_cache: Mutex<HashMap<AttrsOwned, Arc<Vec<FontKey>>>>,
#[cfg(feature = "swash")]
font_key_cache: Mutex<HashMap<fontdb::ID, Option<FontKey>>>,
}
impl FontSystem {
/// Create a new [`FontSystem`], that allows access to any installed system fonts
@ -90,97 +86,92 @@ impl FontSystem {
);
}
Self(
FontSystemInnerBuilder {
locale,
db,
font_cache_builder: |_| Mutex::new(HashMap::new()),
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
}
.build(),
)
Self {
locale,
db,
font_matches_cache: Mutex::new(HashMap::new()),
#[cfg(feature = "swash")]
font_key_cache: Mutex::new(HashMap::new()),
}
}
pub fn locale(&self) -> &str {
self.0.borrow_locale()
&self.locale
}
pub fn db(&self) -> &fontdb::Database {
self.0.borrow_db()
&self.db
}
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
let heads = self.0.into_heads();
(heads.locale, heads.db)
(self.locale, self.db)
}
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
self.0.with(|fields| get_font(&fields, id))
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
self.0.with(|fields| {
let mut font_matches_cache = fields
.font_matches_cache
.lock()
.expect("failed to lock font matches cache");
//TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
let mut fonts = Vec::new();
for face in fields.db.faces() {
if !attrs.matches(face) {
continue;
}
if let Some(font) = get_font(&fields, face.id) {
fonts.push(font);
}
}
let font_matches = Arc::new(FontMatches {
locale: fields.locale,
default_family: fields.db.family_name(&attrs.family).to_string(),
fonts,
});
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
font_matches
})
.clone()
})
}
}
fn get_font<'b>(
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
id: fontdb::ID,
) -> Option<Arc<Font<'b>>> {
fields
.font_cache
.lock()
.expect("failed to lock font cache")
.entry(id)
.or_insert_with(|| {
let face = fields.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
match Font::from_key(&self.db, key) {
Some(font) => Some(font),
None => {
let face = self.db.face(key.id)?;
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
})
.clone()
}
}
#[cfg(feature = "swash")]
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
let mut font_key_cache = self
.font_key_cache
.lock()
.expect("failed to lock font matches cache");
match font_key_cache.entry(id) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let key = self.db.face(id).and_then(Font::new).as_ref().map(Font::key);
entry.insert(key);
key
}
}
}
#[cfg(not(feature = "swash"))]
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
Some(Font::new(self.db.face(id)?)?.key())
}
pub fn get_font_matches(&self, attrs: Attrs) -> Arc<Vec<FontKey>> {
let mut font_matches_cache = self
.font_matches_cache
.lock()
.expect("failed to lock font matches cache");
//TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
let mut font_keys = Vec::new();
for face in self.db.faces() {
if !attrs.matches(face) {
continue;
}
if let Some(key) = self.get_font_key(face.id) {
font_keys.push(key);
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
Arc::new(font_keys)
})
.clone()
}
}