Use ouroboros in FontSystem to avoid lifetime bound

Perhaps not quite what ouroboros is expected to be used for, but it's
not too bad, and avoiding the lifetime bound can be a huge help.
This commit is contained in:
Ian Douglas Scott 2022-11-02 19:25:18 -07:00 • committed by Jeremy Soller
parent ac354c3a2a
commit 0d3fb1dd9d
8 changed files with 85 additions and 62 deletions

View file

@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
fontdb = "0.9.3" fontdb = "0.9.3"
log = "0.4" log = "0.4"
ouroboros = "0.15.5"
rustybuzz = "0.5" rustybuzz = "0.5"
swash = { version = "0.1", optional = true } swash = { version = "0.1", optional = true }
sys-locale = "0.2" sys-locale = "0.2"

View file

@ -45,7 +45,7 @@ use self::text_box::text_box;
mod text_box; mod text_box;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref FONT_SYSTEM: FontSystem<'static> = FontSystem::new(); static ref FONT_SYSTEM: FontSystem = FontSystem::new();
} }
static FONT_SIZES: &'static [Metrics] = &[ static FONT_SIZES: &'static [Metrics] = &[
@ -137,9 +137,9 @@ impl Application for Window {
fn title(&self) -> String { fn title(&self) -> String {
if let Some(path) = &self.path_opt { if let Some(path) = &self.path_opt {
format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale, path.display()) format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale(), path.display())
} else { } else {
format!("COSMIC Text - {}", FONT_SYSTEM.locale) format!("COSMIC Text - {}", FONT_SYSTEM.locale())
} }
} }

View file

@ -39,7 +39,7 @@ fn main() {
-1, -1,
1024 * display_scale as u32, 1024 * display_scale as u32,
768 * display_scale as u32, 768 * display_scale as u32,
&format!("COSMIC TEXT - {}", font_system.locale), &format!("COSMIC TEXT - {}", font_system.locale()),
&[WindowFlag::Resizable], &[WindowFlag::Resizable],
) )
.unwrap(); .unwrap();

View file

@ -139,7 +139,7 @@ impl fmt::Display for Metrics {
/// A buffer of text that is shaped and laid out /// A buffer of text that is shaped and laid out
pub struct Buffer<'a> { pub struct Buffer<'a> {
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
/// Lines (or paragraphs) of text in the buffer /// Lines (or paragraphs) of text in the buffer
pub lines: Vec<BufferLine>, pub lines: Vec<BufferLine>,
metrics: Metrics, metrics: Metrics,
@ -153,7 +153,7 @@ pub struct Buffer<'a> {
impl<'a> Buffer<'a> { impl<'a> Buffer<'a> {
/// Create a new [Buffer] with the provided [FontSystem] and [Metrics] /// Create a new [Buffer] with the provided [FontSystem] and [Metrics]
pub fn new( pub fn new(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
metrics: Metrics, metrics: Metrics,
) -> Self { ) -> Self {
let mut buffer = Self { let mut buffer = Self {

View file

@ -132,7 +132,7 @@ impl BufferLine {
} }
/// Shape line, will cache results /// Shape line, will cache results
pub fn shape<'a>(&mut self, font_system: &'a FontSystem<'a>) -> &ShapeLine { pub fn shape(&mut self, font_system: &FontSystem) -> &ShapeLine {
if self.shape_opt.is_none() { if self.shape_opt.is_none() {
self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list)); self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list));
self.layout_opt = None; self.layout_opt = None;
@ -146,7 +146,7 @@ impl BufferLine {
} }
/// Layout line, will cache results /// Layout line, will cache results
pub fn layout<'a>(&mut self, font_system: &'a FontSystem<'a>, font_size: i32, width: i32) -> &[LayoutLine] { pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32) -> &[LayoutLine] {
if self.layout_opt.is_none() { if self.layout_opt.is_none() {
let wrap_simple = self.wrap_simple; let wrap_simple = self.wrap_simple;
let shape = self.shape(font_system); let shape = self.shape(font_system);

View file

@ -7,15 +7,22 @@ use std::{
use crate::{Attrs, AttrsOwned, Font, FontMatches}; use crate::{Attrs, AttrsOwned, Font, FontMatches};
/// Access system fonts #[ouroboros::self_referencing]
pub struct FontSystem<'a> { struct FontSystemInner {
pub locale: String, locale: String,
pub db: fontdb::Database, db: fontdb::Database,
pub font_cache: Mutex<HashMap<fontdb::ID, Option<Arc<Font<'a>>>>>, #[borrows(db)]
pub font_matches_cache: Mutex<HashMap<AttrsOwned, Arc<FontMatches<'a>>>>, #[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>>>>,
} }
impl<'a> FontSystem<'a> { /// Access system fonts
pub struct FontSystem(FontSystemInner);
impl FontSystem {
pub fn new() -> Self { pub fn new() -> Self {
let locale = sys_locale::get_locale().unwrap_or_else(|| { let locale = sys_locale::get_locale().unwrap_or_else(|| {
log::warn!("failed to get system locale, falling back to en-US"); log::warn!("failed to get system locale, falling back to en-US");
@ -57,56 +64,71 @@ impl<'a> FontSystem<'a> {
); );
} }
Self { Self(FontSystemInnerBuilder {
locale, locale,
db, db,
font_cache: Mutex::new(HashMap::new()), font_cache_builder: |_| Mutex::new(HashMap::new()),
font_matches_cache: Mutex::new(HashMap::new()), font_matches_cache_builder: |_, _| Mutex::new(HashMap::new())
} }.build())
} }
pub fn get_font(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> { pub fn locale(&self) -> &str {
let mut font_cache = self.font_cache.lock().unwrap(); self.0.borrow_locale()
font_cache.entry(id).or_insert_with(|| {
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(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> { pub fn db(&self) -> &fontdb::Database {
let mut font_matches_cache = self.font_matches_cache.lock().unwrap(); self.0.borrow_db()
//TODO: do not create AttrsOwned unless entry does not already exist }
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| {
let now = std::time::Instant::now();
let mut fonts = Vec::new(); pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
for face in self.db.faces() { self.0.with(|fields| {
if !attrs.matches(face) { get_font(&fields, id)
continue; })
}
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().unwrap();
//TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| {
let now = std::time::Instant::now();
let mut fonts = Vec::new();
for face in fields.db.faces() {
if !attrs.matches(face) {
continue;
}
match get_font(&fields, face.id) {
Some(font) => fonts.push(font),
None => (),
}
} }
match self.get_font(face.id) { let font_matches = Arc::new(FontMatches {
Some(font) => fonts.push(font), locale: fields.locale,
None => (), default_family: fields.db.family_name(&attrs.family).to_string(),
} fonts
} });
let font_matches = Arc::new(FontMatches { let elapsed = now.elapsed();
locale: &self.locale, log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
default_family: self.db.family_name(&attrs.family).to_string(),
fonts
});
let elapsed = now.elapsed(); font_matches
log::debug!("font matches for {:?} in {:?}", attrs, elapsed); }).clone()
})
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().unwrap().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()
}

View file

@ -102,7 +102,7 @@ fn shape_fallback(
} }
fn shape_run<'a>( fn shape_run<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
start_run: usize, start_run: usize,
@ -281,7 +281,7 @@ pub struct ShapeWord {
impl ShapeWord { impl ShapeWord {
pub fn new<'a>( pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
start_word: usize, start_word: usize,
@ -344,7 +344,7 @@ pub struct ShapeSpan {
impl ShapeSpan { impl ShapeSpan {
pub fn new<'a>( pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
start_span: usize, start_span: usize,
@ -424,7 +424,7 @@ pub struct ShapeLine {
impl ShapeLine { impl ShapeLine {
pub fn new<'a>( pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList attrs_list: &AttrsList
) -> Self { ) -> Self {

View file

@ -9,7 +9,7 @@ use crate::{CacheKey, Color, FontSystem};
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage}; pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> { fn swash_image<'a>(font_system: &'a FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
let font = match font_system.get_font(cache_key.font_id) { let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some, Some(some) => some,
None => { None => {
@ -49,14 +49,14 @@ fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext,
/// Cache for rasterizing with the swash scaler /// Cache for rasterizing with the swash scaler
pub struct SwashCache<'a> { pub struct SwashCache<'a> {
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem,
context: ScaleContext, context: ScaleContext,
pub image_cache: HashMap<CacheKey, Option<SwashImage>>, pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
} }
impl<'a> SwashCache<'a> { impl<'a> SwashCache<'a> {
/// Create a new swash cache /// Create a new swash cache
pub fn new(font_system: &'a FontSystem<'a>) -> Self { pub fn new(font_system: &'a FontSystem) -> Self {
Self { Self {
font_system: font_system, font_system: font_system,
context: ScaleContext::new(), context: ScaleContext::new(),