Merge pull request #160 from forkgull/changable-fallback

Unify the no_std and std impls of FontSystem
This commit is contained in:
Jeremy Soller 2023-08-06 16:03:50 -06:00 committed by GitHub
commit 0476d7cdbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 253 deletions

View file

@ -21,6 +21,8 @@ unicode-linebreak = "0.1.4"
unicode-script = "0.5.5"
unicode-segmentation = "1.10.0"
rangemap = "1.2.0"
hashbrown = { version = "0.14.0", default-features = false }
rustc-hash = { version = "1.1.0", default-features = false }
[dependencies.unicode-bidi]
version = "0.3.8"

207
src/font/system.rs Normal file
View file

@ -0,0 +1,207 @@
use crate::{Attrs, AttrsOwned, Font};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use core::hash::BuildHasherDefault;
use core::ops::{Deref, DerefMut};
type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
// re-export fontdb and rustybuzz
pub use fontdb;
pub use rustybuzz;
/// Access to the system fonts.
pub struct FontSystem {
/// The locale of the system.
locale: String,
/// The underlying font database.
db: fontdb::Database,
/// Cache for loaded fonts from the database.
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
/// Cache for font matches.
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<fontdb::ID>>>,
}
impl fmt::Debug for FontSystem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FontSystem")
.field("locale", &self.locale)
.field("db", &self.db)
.finish()
}
}
impl FontSystem {
/// Create a new [`FontSystem`], that allows access to any installed system fonts
///
/// # Timing
///
/// This function takes some time to run. On the release build, it can take up to a second,
/// while debug builds can take up to ten times longer. For this reason, it should only be
/// called once, and the resulting [`FontSystem`] should be shared.
pub fn new() -> Self {
Self::new_with_fonts(core::iter::empty())
}
/// Create a new [`FontSystem`] with a pre-specified set of fonts.
pub fn new_with_fonts(fonts: impl IntoIterator<Item = fontdb::Source>) -> Self {
let locale = Self::get_locale();
log::debug!("Locale: {}", locale);
let mut db = fontdb::Database::new();
Self::load_fonts(&mut db, fonts.into_iter());
//TODO: configurable default fonts
db.set_monospace_family("Fira Mono");
db.set_sans_serif_family("Fira Sans");
db.set_serif_family("DejaVu Serif");
Self::new_with_locale_and_db(locale, db)
}
/// Create a new [`FontSystem`] with a pre-specified locale and font database.
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
Self {
locale,
db,
font_cache: HashMap::default(),
font_matches_cache: HashMap::default(),
}
}
/// Get the locale.
pub fn locale(&self) -> &str {
&self.locale
}
/// Get the database.
pub fn db(&self) -> &fontdb::Database {
&self.db
}
/// Get a mutable reference to the database.
pub fn db_mut(&mut self) -> &mut fontdb::Database {
self.font_matches_cache.clear();
&mut self.db
}
/// Consume this [`FontSystem`] and return the locale and database.
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
(self.locale, self.db)
}
/// Get a font by its ID.
pub fn get_font(&mut self, id: fontdb::ID) -> Option<Arc<Font>> {
self.font_cache
.entry(id)
.or_insert_with(|| {
#[cfg(feature = "std")]
unsafe {
self.db.make_shared_face_data(id);
}
let face = self.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
}
}
})
.clone()
}
pub fn get_font_matches(&mut self, attrs: Attrs<'_>) -> Arc<Vec<fontdb::ID>> {
self.font_matches_cache
//TODO: do not create AttrsOwned unless entry does not already exist
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let now = std::time::Instant::now();
let ids = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
Arc::new(ids)
})
.clone()
}
#[cfg(feature = "std")]
fn get_locale() -> String {
sys_locale::get_locale().unwrap_or_else(|| {
log::warn!("failed to get system locale, falling back to en-US");
String::from("en-US")
})
}
#[cfg(not(feature = "std"))]
fn get_locale() -> String {
String::from("en-US")
}
#[cfg(feature = "std")]
fn load_fonts(db: &mut fontdb::Database, fonts: impl Iterator<Item = fontdb::Source>) {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
#[cfg(target_os = "redox")]
db.load_fonts_dir("/ui/fonts");
db.load_system_fonts();
for source in fonts {
db.load_font_source(source);
}
#[cfg(not(target_arch = "wasm32"))]
log::info!(
"Parsed {} font faces in {}ms.",
db.len(),
now.elapsed().as_millis()
);
}
#[cfg(not(feature = "std"))]
fn load_fonts(db: &mut fontdb::Database, fonts: impl Iterator<Item = fontdb::Source>) {
for source in fonts {
db.load_font_source(source);
}
}
}
/// A value borrowed together with an [`FontSystem`]
#[derive(Debug)]
pub struct BorrowedWithFontSystem<'a, T> {
pub(crate) inner: &'a mut T,
pub(crate) font_system: &'a mut FontSystem,
}
impl<'a, T> Deref for BorrowedWithFontSystem<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'a, T> DerefMut for BorrowedWithFontSystem<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

View file

@ -1,36 +0,0 @@
use core::ops::{Deref, DerefMut};
#[cfg(not(feature = "std"))]
pub use self::no_std::*;
#[cfg(not(feature = "std"))]
mod no_std;
#[cfg(feature = "std")]
pub use self::std::*;
#[cfg(feature = "std")]
mod std;
// re-export fontdb and rustybuzz
pub use fontdb;
pub use rustybuzz;
/// A value borrowed together with an [`FontSystem`]
#[derive(Debug)]
pub struct BorrowedWithFontSystem<'a, T> {
pub(crate) inner: &'a mut T,
pub(crate) font_system: &'a mut FontSystem,
}
impl<'a, T> Deref for BorrowedWithFontSystem<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'a, T> DerefMut for BorrowedWithFontSystem<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

View file

@ -1,73 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::{
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use crate::{Attrs, Font};
/// Access system fonts
#[derive(Debug)]
pub struct FontSystem {
locale: String,
db: fontdb::Database,
}
impl FontSystem {
pub fn new() -> Self {
let locale = "en-US".to_string();
let mut db = fontdb::Database::new();
{
db.set_monospace_family("Fira Mono");
db.set_sans_serif_family("Fira Sans");
db.set_serif_family("DejaVu Serif");
}
Self { locale, db }
}
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
Self { locale, db }
}
pub fn locale(&self) -> &str {
&self.locale
}
pub fn db(&self) -> &fontdb::Database {
&self.db
}
pub fn db_mut(&mut self) -> &mut fontdb::Database {
&mut self.db
}
pub fn get_font(&self, id: fontdb::ID) -> Option<Arc<Font>> {
get_font(&self.db, id)
}
pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
let ids = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
Arc::new(ids)
}
}
fn get_font(db: &fontdb::Database, id: fontdb::ID) -> Option<Arc<Font>> {
let face = 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
}
}
}

View file

@ -1,144 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::{collections::HashMap, sync::Arc};
use crate::{Attrs, AttrsOwned, Font};
/// Access system fonts
#[derive(Debug)]
pub struct FontSystem {
locale: String,
db: fontdb::Database,
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<fontdb::ID>>>,
}
impl FontSystem {
/// Create a new [`FontSystem`], that allows access to any installed system fonts
///
/// # Timing
///
/// This function takes some time to run. On the release build, it can take up to a second,
/// while debug builds can take up to ten times longer. For this reason, it should only be
/// called once, and the resulting [`FontSystem`] should be shared.
pub fn new() -> Self {
Self::new_with_fonts(std::iter::empty())
}
pub fn new_with_fonts(fonts: impl Iterator<Item = fontdb::Source>) -> Self {
let locale = sys_locale::get_locale().unwrap_or_else(|| {
log::warn!("failed to get system locale, falling back to en-US");
String::from("en-US")
});
log::debug!("Locale: {}", locale);
let mut db = fontdb::Database::new();
{
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
#[cfg(target_os = "redox")]
db.load_fonts_dir("/ui/fonts");
db.load_system_fonts();
for source in fonts {
db.load_font_source(source);
}
//TODO: configurable default fonts
db.set_monospace_family("Fira Mono");
db.set_sans_serif_family("Fira Sans");
db.set_serif_family("DejaVu Serif");
#[cfg(not(target_arch = "wasm32"))]
log::info!(
"Parsed {} font faces in {}ms.",
db.len(),
now.elapsed().as_millis()
);
}
Self::new_with_locale_and_db(locale, db)
}
/// Create a new [`FontSystem`], manually specifying the current locale and font database.
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
Self {
locale,
db,
font_cache: HashMap::new(),
font_matches_cache: HashMap::new(),
}
}
pub fn locale(&self) -> &str {
&self.locale
}
pub fn db(&self) -> &fontdb::Database {
&self.db
}
pub fn db_mut(&mut self) -> &mut fontdb::Database {
self.font_matches_cache.clear();
&mut self.db
}
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
(self.locale, self.db)
}
pub fn get_font(&mut self, id: fontdb::ID) -> Option<Arc<Font>> {
get_font(&mut self.font_cache, &mut self.db, id)
}
pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
self.font_matches_cache
//TODO: do not create AttrsOwned unless entry does not already exist
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
let ids = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
Arc::new(ids)
})
.clone()
}
}
fn get_font(
font_cache: &mut HashMap<fontdb::ID, Option<Arc<Font>>>,
db: &mut fontdb::Database,
id: fontdb::ID,
) -> Option<Arc<Font>> {
font_cache
.entry(id)
.or_insert_with(|| {
unsafe {
db.make_shared_face_data(id);
}
let face = 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
}
}
})
.clone()
}