Merge pull request #89 from geieredgar/font-system
Make `FontSystem` not self-referencing and update `fontdb` and `rustybuzz`
This commit is contained in:
commit
b6398a2d57
11 changed files with 235 additions and 189 deletions
|
|
@ -9,11 +9,10 @@ documentation = "https://docs.rs/cosmic-text/latest/cosmic_text/"
|
||||||
repository = "https://github.com/pop-os/cosmic-text"
|
repository = "https://github.com/pop-os/cosmic-text"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fontdb = { version = "0.10.0", default-features = false }
|
fontdb = { version = "0.13.0", default-features = false }
|
||||||
libm = "0.2.6"
|
libm = "0.2.6"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
ouroboros = "0.15.5"
|
rustybuzz = { version = "0.7.0", default-features = false, features = ["libm"] }
|
||||||
rustybuzz = { version = "0.6.0", default-features = false, features = ["libm"] }
|
|
||||||
swash = { version = "0.1.6", optional = true }
|
swash = { version = "0.1.6", optional = true }
|
||||||
syntect = { version = "5.0.0", optional = true }
|
syntect = { version = "5.0.0", optional = true }
|
||||||
sys-locale = { version = "0.2.3", optional = true }
|
sys-locale = { version = "0.2.3", optional = true }
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ allow = [
|
||||||
"Apache-2.0",
|
"Apache-2.0",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"BSD-2-Clause",
|
"BSD-2-Clause",
|
||||||
|
"Zlib",
|
||||||
#"Apache-2.0 WITH LLVM-exception",
|
#"Apache-2.0 WITH LLVM-exception",
|
||||||
]
|
]
|
||||||
# List of explicitly disallowed licenses
|
# List of explicitly disallowed licenses
|
||||||
|
|
@ -163,8 +164,8 @@ exceptions = [
|
||||||
# and the crate will be checked normally, which may produce warnings or errors
|
# and the crate will be checked normally, which may produce warnings or errors
|
||||||
# depending on the rest of your configuration
|
# depending on the rest of your configuration
|
||||||
#license-files = [
|
#license-files = [
|
||||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||||
#]
|
#]
|
||||||
|
|
||||||
[licenses.private]
|
[licenses.private]
|
||||||
|
|
|
||||||
10
src/cache.rs
10
src/cache.rs
|
|
@ -1,10 +1,12 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use crate::FontKey;
|
||||||
|
|
||||||
/// Key for building a glyph cache
|
/// Key for building a glyph cache
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct CacheKey {
|
pub struct CacheKey {
|
||||||
/// Font ID
|
/// Font key
|
||||||
pub font_id: fontdb::ID,
|
pub font_key: FontKey,
|
||||||
/// Glyph ID
|
/// Glyph ID
|
||||||
pub glyph_id: u16,
|
pub glyph_id: u16,
|
||||||
/// `f32` bits of font size
|
/// `f32` bits of font size
|
||||||
|
|
@ -17,7 +19,7 @@ pub struct CacheKey {
|
||||||
|
|
||||||
impl CacheKey {
|
impl CacheKey {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font_id: fontdb::ID,
|
font_key: FontKey,
|
||||||
glyph_id: u16,
|
glyph_id: u16,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
pos: (f32, f32),
|
pos: (f32, f32),
|
||||||
|
|
@ -26,7 +28,7 @@ impl CacheKey {
|
||||||
let (y, y_bin) = SubpixelBin::new(pos.1);
|
let (y, y_bin) = SubpixelBin::new(pos.1);
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
font_id,
|
font_key,
|
||||||
glyph_id,
|
glyph_id,
|
||||||
font_size_bits: font_size.to_bits(),
|
font_size_bits: font_size.to_bits(),
|
||||||
x_bin,
|
x_bin,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use unicode_script::Script;
|
use unicode_script::Script;
|
||||||
|
|
||||||
use crate::Font;
|
use crate::{Font, FontKey};
|
||||||
|
|
||||||
use self::platform::*;
|
use self::platform::*;
|
||||||
|
|
||||||
|
|
@ -26,7 +25,8 @@ mod platform;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
pub struct FontFallbackIter<'a> {
|
pub struct FontFallbackIter<'a> {
|
||||||
fonts: &'a [Arc<Font<'a>>],
|
db: &'a fontdb::Database,
|
||||||
|
font_keys: &'a [FontKey],
|
||||||
default_families: &'a [&'a str],
|
default_families: &'a [&'a str],
|
||||||
default_i: usize,
|
default_i: usize,
|
||||||
scripts: Vec<Script>,
|
scripts: Vec<Script>,
|
||||||
|
|
@ -39,13 +39,15 @@ pub struct FontFallbackIter<'a> {
|
||||||
|
|
||||||
impl<'a> FontFallbackIter<'a> {
|
impl<'a> FontFallbackIter<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fonts: &'a [Arc<Font<'a>>],
|
db: &'a fontdb::Database,
|
||||||
|
font_keys: &'a [FontKey],
|
||||||
default_families: &'a [&'a str],
|
default_families: &'a [&'a str],
|
||||||
scripts: Vec<Script>,
|
scripts: Vec<Script>,
|
||||||
locale: &'a str,
|
locale: &'a str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fonts,
|
db,
|
||||||
|
font_keys,
|
||||||
default_families,
|
default_families,
|
||||||
default_i: 0,
|
default_i: 0,
|
||||||
scripts,
|
scripts,
|
||||||
|
|
@ -66,12 +68,13 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
word
|
word
|
||||||
);
|
);
|
||||||
} else if self.other_i > 0 {
|
} 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!(
|
log::debug!(
|
||||||
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
||||||
self.scripts,
|
self.scripts,
|
||||||
self.locale,
|
self.locale,
|
||||||
font.info.family,
|
font.name(),
|
||||||
word
|
word
|
||||||
);
|
);
|
||||||
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
||||||
|
|
@ -88,14 +91,15 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for 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> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
while self.default_i < self.default_families.len() {
|
while self.default_i < self.default_families.len() {
|
||||||
let default_family = self.default_families[self.default_i];
|
let default_family = self.default_families[self.default_i];
|
||||||
self.default_i += 1;
|
self.default_i += 1;
|
||||||
|
|
||||||
for font in self.fonts.iter() {
|
for font_key in self.font_keys.iter().copied() {
|
||||||
if font.info.family == default_family {
|
let font = Font::from_key(self.db, font_key).expect("invalid font key");
|
||||||
|
if font.contains_family(default_family) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,8 +112,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
while self.script_i.1 < script_families.len() {
|
while self.script_i.1 < script_families.len() {
|
||||||
let script_family = script_families[self.script_i.1];
|
let script_family = script_families[self.script_i.1];
|
||||||
self.script_i.1 += 1;
|
self.script_i.1 += 1;
|
||||||
for font in self.fonts.iter() {
|
for font_key in self.font_keys.iter().copied() {
|
||||||
if font.info.family == script_family {
|
let font = Font::from_key(self.db, font_key).expect("invalid font key");
|
||||||
|
if font.contains_family(script_family) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +134,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
while self.common_i < common_families.len() {
|
while self.common_i < common_families.len() {
|
||||||
let common_family = common_families[self.common_i];
|
let common_family = common_families[self.common_i];
|
||||||
self.common_i += 1;
|
self.common_i += 1;
|
||||||
for font in self.fonts.iter() {
|
for font_key in self.font_keys.iter().copied() {
|
||||||
if font.info.family == common_family {
|
let font = Font::from_key(self.db, font_key).expect("invalid font key");
|
||||||
|
if font.contains_family(common_family) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,10 +146,14 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
//TODO: do we need to do this?
|
//TODO: do we need to do this?
|
||||||
//TODO: do not evaluate fonts more than once!
|
//TODO: do not evaluate fonts more than once!
|
||||||
let forbidden_families = forbidden_fallback();
|
let forbidden_families = forbidden_fallback();
|
||||||
while self.other_i < self.fonts.len() {
|
while self.other_i < self.font_keys.len() {
|
||||||
let font = &self.fonts[self.other_i];
|
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;
|
self.other_i += 1;
|
||||||
if !forbidden_families.contains(&font.info.family.as_str()) {
|
if forbidden_families
|
||||||
|
.iter()
|
||||||
|
.all(|family| !font.contains_family(family))
|
||||||
|
{
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>>>,
|
|
||||||
}
|
|
||||||
|
|
@ -4,12 +4,18 @@ use core::ops::Deref;
|
||||||
|
|
||||||
pub(crate) mod fallback;
|
pub(crate) mod fallback;
|
||||||
|
|
||||||
pub use self::matches::*;
|
|
||||||
mod matches;
|
|
||||||
|
|
||||||
pub use self::system::*;
|
pub use self::system::*;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
#[cfg_attr(not(feature = "swash"), repr(transparent))]
|
||||||
|
/// Identifies a [`Font`] in a [`FontSystem`]
|
||||||
|
pub struct FontKey {
|
||||||
|
pub id: fontdb::ID,
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
pub swash: (u32, swash::CacheKey),
|
||||||
|
}
|
||||||
|
|
||||||
/// A font
|
/// A font
|
||||||
pub struct Font<'a> {
|
pub struct Font<'a> {
|
||||||
pub info: &'a fontdb::FaceInfo,
|
pub info: &'a fontdb::FaceInfo,
|
||||||
|
|
@ -44,6 +50,48 @@ 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
if let Some((name, _)) = self.info.families.first() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
&self.info.post_script_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_family(&self, family: &str) -> bool {
|
||||||
|
self.info.families.iter().any(|(name, _)| name == family)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn as_swash(&self) -> swash::FontRef {
|
pub fn as_swash(&self) -> swash::FontRef {
|
||||||
swash::FontRef {
|
swash::FontRef {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use alloc::{
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Attrs, Font, FontMatches};
|
use crate::{Attrs, Font, FontKey};
|
||||||
|
|
||||||
/// Access system fonts
|
/// Access system fonts
|
||||||
pub struct FontSystem {
|
pub struct FontSystem {
|
||||||
|
|
@ -45,33 +45,38 @@ impl FontSystem {
|
||||||
|
|
||||||
// Clippy false positive
|
// Clippy false positive
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
|
||||||
let face = self.db.face(id)?;
|
match Font::from_key(&self.db, key) {
|
||||||
match Font::new(face) {
|
Some(font) => Some(font),
|
||||||
Some(font) => Some(Arc::new(font)),
|
|
||||||
None => {
|
None => {
|
||||||
|
let face = self.db.face(key.id)?;
|
||||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
|
||||||
let mut fonts = Vec::new();
|
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() {
|
for face in self.db.faces() {
|
||||||
if !attrs.matches(face) {
|
if !attrs.matches(face) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(font) = self.get_font(face.id) {
|
let font_key = match self.get_font_key(face.id) {
|
||||||
fonts.push(font);
|
Some(font_key) => font_key,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.get_font(font_key).is_some() {
|
||||||
|
font_keys.push(font_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::new(FontMatches {
|
Arc::new(font_keys)
|
||||||
locale: &self.locale,
|
|
||||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
|
||||||
fonts,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use alloc::{
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Attrs, Font, FontMatches};
|
use crate::{Attrs, Font, FontKey};
|
||||||
|
|
||||||
/// Access system fonts
|
/// Access system fonts
|
||||||
pub struct FontSystem {
|
pub struct FontSystem {
|
||||||
|
|
@ -44,8 +44,7 @@ impl FontSystem {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
//TODO only do this on demand!
|
//TODO only do this on demand!
|
||||||
for i in 0..db.faces().len() {
|
for id in db.faces().map(|face| face.id).collect::<Vec<_>>() {
|
||||||
let id = db.faces()[i].id;
|
|
||||||
unsafe {
|
unsafe {
|
||||||
db.make_shared_face_data(id);
|
db.make_shared_face_data(id);
|
||||||
}
|
}
|
||||||
|
|
@ -71,33 +70,38 @@ impl FontSystem {
|
||||||
|
|
||||||
// Clippy false positive
|
// Clippy false positive
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
|
||||||
let face = self.db.face(id)?;
|
match Font::from_key(&self.db, key) {
|
||||||
match Font::new(face) {
|
Some(font) => Some(font),
|
||||||
Some(font) => Some(Arc::new(font)),
|
|
||||||
None => {
|
None => {
|
||||||
|
let face = self.db.face(key.id)?;
|
||||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
pub fn get_font_key(&self, id: fontdb::ID) -> Option<FontKey> {
|
||||||
let mut fonts = Vec::new();
|
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() {
|
for face in self.db.faces() {
|
||||||
if !attrs.matches(face) {
|
if !attrs.matches(face) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(font) = self.get_font(face.id) {
|
let font_key = match self.get_font_key(face.id) {
|
||||||
fonts.push(font);
|
Some(font_key) => font_key,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.get_font(font_key).is_some() {
|
||||||
|
font_keys.push(font_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::new(FontMatches {
|
Arc::new(font_keys)
|
||||||
locale: &self.locale,
|
|
||||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
|
||||||
fonts,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Attrs, AttrsOwned, Font, FontMatches};
|
use crate::{Attrs, AttrsOwned, Font, FontKey};
|
||||||
|
|
||||||
#[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>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access system fonts
|
/// 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 {
|
impl FontSystem {
|
||||||
/// Create a new [`FontSystem`], that allows access to any installed system fonts
|
/// Create a new [`FontSystem`], that allows access to any installed system fonts
|
||||||
|
|
@ -75,8 +71,7 @@ impl FontSystem {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
//TODO only do this on demand!
|
//TODO only do this on demand!
|
||||||
for i in 0..db.faces().len() {
|
for id in db.faces().map(|face| face.id).collect::<Vec<_>>() {
|
||||||
let id = db.faces()[i].id;
|
|
||||||
unsafe {
|
unsafe {
|
||||||
db.make_shared_face_data(id);
|
db.make_shared_face_data(id);
|
||||||
}
|
}
|
||||||
|
|
@ -90,97 +85,92 @@ impl FontSystem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self(
|
Self {
|
||||||
FontSystemInnerBuilder {
|
locale,
|
||||||
locale,
|
db,
|
||||||
db,
|
font_matches_cache: Mutex::new(HashMap::new()),
|
||||||
font_cache_builder: |_| Mutex::new(HashMap::new()),
|
#[cfg(feature = "swash")]
|
||||||
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
|
font_key_cache: Mutex::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locale(&self) -> &str {
|
pub fn locale(&self) -> &str {
|
||||||
self.0.borrow_locale()
|
&self.locale
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn db(&self) -> &fontdb::Database {
|
pub fn db(&self) -> &fontdb::Database {
|
||||||
self.0.borrow_db()
|
&self.db
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
|
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
|
||||||
let heads = self.0.into_heads();
|
(self.locale, self.db)
|
||||||
(heads.locale, heads.db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clippy false positive
|
// Clippy false positive
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
pub fn get_font<'a>(&'a self, key: FontKey) -> Option<Font<'a>> {
|
||||||
self.0.with(|fields| get_font(&fields, id))
|
match Font::from_key(&self.db, key) {
|
||||||
}
|
Some(font) => Some(font),
|
||||||
|
None => {
|
||||||
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
let face = self.db.face(key.id)?;
|
||||||
self.0.with(|fields| {
|
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||||
let mut font_matches_cache = fields
|
None
|
||||||
.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
src/shape.rs
35
src/shape.rs
|
|
@ -9,7 +9,9 @@ use unicode_script::{Script, UnicodeScript};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::fallback::FontFallbackIter;
|
use crate::fallback::FontFallbackIter;
|
||||||
use crate::{Align, AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
use crate::{
|
||||||
|
Align, AttrsList, CacheKey, Color, Font, FontKey, FontSystem, LayoutGlyph, LayoutLine, Wrap,
|
||||||
|
};
|
||||||
|
|
||||||
fn shape_fallback(
|
fn shape_fallback(
|
||||||
font: &Font,
|
font: &Font,
|
||||||
|
|
@ -62,7 +64,7 @@ fn shape_fallback(
|
||||||
y_advance,
|
y_advance,
|
||||||
x_offset,
|
x_offset,
|
||||||
y_offset,
|
y_offset,
|
||||||
font_id: font.info.id,
|
font_key: font.key(),
|
||||||
glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
|
glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
|
||||||
//TODO: color should not be related to shaping
|
//TODO: color should not be related to shaping
|
||||||
color_opt: attrs.color_opt,
|
color_opt: attrs.color_opt,
|
||||||
|
|
@ -125,22 +127,21 @@ fn shape_run(
|
||||||
|
|
||||||
let font_matches = font_system.get_font_matches(attrs);
|
let font_matches = font_system.get_font_matches(attrs);
|
||||||
|
|
||||||
let default_families = [font_matches.default_family.as_str()];
|
let db = font_system.db();
|
||||||
|
|
||||||
|
let default_families = [db.family_name(&attrs.family)];
|
||||||
let mut font_iter = FontFallbackIter::new(
|
let mut font_iter = FontFallbackIter::new(
|
||||||
&font_matches.fonts,
|
db,
|
||||||
|
&font_matches,
|
||||||
&default_families,
|
&default_families,
|
||||||
scripts,
|
scripts,
|
||||||
font_matches.locale,
|
font_system.locale(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mut glyphs, mut missing) = shape_fallback(
|
let font = font_iter.next().expect("no default font found");
|
||||||
font_iter.next().expect("no default font found"),
|
|
||||||
line,
|
let (mut glyphs, mut missing) =
|
||||||
attrs_list,
|
shape_fallback(&font, line, attrs_list, start_run, end_run, span_rtl);
|
||||||
start_run,
|
|
||||||
end_run,
|
|
||||||
span_rtl,
|
|
||||||
);
|
|
||||||
|
|
||||||
//TODO: improve performance!
|
//TODO: improve performance!
|
||||||
while !missing.is_empty() {
|
while !missing.is_empty() {
|
||||||
|
|
@ -149,9 +150,9 @@ fn shape_run(
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("Evaluating fallback with font '{}'", font.info.family);
|
log::trace!("Evaluating fallback with font '{}'", font.name());
|
||||||
let (mut fb_glyphs, fb_missing) =
|
let (mut fb_glyphs, fb_missing) =
|
||||||
shape_fallback(font, line, attrs_list, start_run, end_run, span_rtl);
|
shape_fallback(&font, line, attrs_list, start_run, end_run, span_rtl);
|
||||||
|
|
||||||
// Insert all matching glyphs
|
// Insert all matching glyphs
|
||||||
let mut fb_i = 0;
|
let mut fb_i = 0;
|
||||||
|
|
@ -228,7 +229,7 @@ pub struct ShapeGlyph {
|
||||||
pub y_advance: f32,
|
pub y_advance: f32,
|
||||||
pub x_offset: f32,
|
pub x_offset: f32,
|
||||||
pub y_offset: f32,
|
pub y_offset: f32,
|
||||||
pub font_id: fontdb::ID,
|
pub font_key: FontKey,
|
||||||
pub glyph_id: u16,
|
pub glyph_id: u16,
|
||||||
pub color_opt: Option<Color>,
|
pub color_opt: Option<Color>,
|
||||||
pub metadata: usize,
|
pub metadata: usize,
|
||||||
|
|
@ -247,7 +248,7 @@ impl ShapeGlyph {
|
||||||
let y_offset = font_size * self.y_offset;
|
let y_offset = font_size * self.y_offset;
|
||||||
|
|
||||||
let (cache_key, x_int, y_int) = CacheKey::new(
|
let (cache_key, x_int, y_int) = CacheKey::new(
|
||||||
self.font_id,
|
self.font_key,
|
||||||
self.glyph_id,
|
self.glyph_id,
|
||||||
font_size,
|
font_size,
|
||||||
(x + x_offset, y - y_offset),
|
(x + x_offset, y - y_offset),
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ fn swash_image(
|
||||||
context: &mut ScaleContext,
|
context: &mut ScaleContext,
|
||||||
cache_key: CacheKey,
|
cache_key: CacheKey,
|
||||||
) -> Option<SwashImage> {
|
) -> Option<SwashImage> {
|
||||||
let font = match font_system.get_font(cache_key.font_id) {
|
let font = match font_system.get_font(cache_key.font_key) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
log::warn!("did not find font {:?}", cache_key.font_key.id);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -63,10 +63,10 @@ fn swash_outline_commands(
|
||||||
) -> Option<Vec<swash::zeno::Command>> {
|
) -> Option<Vec<swash::zeno::Command>> {
|
||||||
use swash::zeno::PathData as _;
|
use swash::zeno::PathData as _;
|
||||||
|
|
||||||
let font = match font_system.get_font(cache_key.font_id) {
|
let font = match font_system.get_font(cache_key.font_key) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
log::warn!("did not find font {:?}", cache_key.font_key.id);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue