diff --git a/Cargo.toml b/Cargo.toml index 627ed1b..6b1d847 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0" [dependencies] fontdb = "0.9.3" log = "0.4" +ouroboros = "0.15.5" rustybuzz = "0.5" swash = { version = "0.1", optional = true } sys-locale = "0.2" diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 525f2dc..982a357 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -45,7 +45,7 @@ use self::text_box::text_box; mod text_box; lazy_static::lazy_static! { - static ref FONT_SYSTEM: FontSystem<'static> = FontSystem::new(); + static ref FONT_SYSTEM: FontSystem = FontSystem::new(); } static FONT_SIZES: &'static [Metrics] = &[ @@ -137,9 +137,9 @@ impl Application for Window { fn title(&self) -> String { if let Some(path) = &self.path_opt { - format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale, path.display()) + format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale(), path.display()) } else { - format!("COSMIC Text - {}", FONT_SYSTEM.locale) + format!("COSMIC Text - {}", FONT_SYSTEM.locale()) } } diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index a54400d..483190c 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -39,7 +39,7 @@ fn main() { -1, 1024 * display_scale as u32, 768 * display_scale as u32, - &format!("COSMIC TEXT - {}", font_system.locale), + &format!("COSMIC TEXT - {}", font_system.locale()), &[WindowFlag::Resizable], ) .unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index 0ecd3c0..1344f3e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -139,7 +139,7 @@ impl fmt::Display for Metrics { /// A buffer of text that is shaped and laid out pub struct Buffer<'a> { - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, /// Lines (or paragraphs) of text in the buffer pub lines: Vec, metrics: Metrics, @@ -153,7 +153,7 @@ pub struct Buffer<'a> { impl<'a> Buffer<'a> { /// Create a new [Buffer] with the provided [FontSystem] and [Metrics] pub fn new( - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, metrics: Metrics, ) -> Self { let mut buffer = Self { diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 611114d..a82c86c 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -132,7 +132,7 @@ impl BufferLine { } /// 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() { self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list)); self.layout_opt = None; @@ -146,7 +146,7 @@ impl BufferLine { } /// 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() { let wrap_simple = self.wrap_simple; let shape = self.shape(font_system); diff --git a/src/font/system.rs b/src/font/system.rs index b8902ee..b44b2cd 100644 --- a/src/font/system.rs +++ b/src/font/system.rs @@ -7,15 +7,22 @@ use std::{ use crate::{Attrs, AttrsOwned, Font, FontMatches}; -/// Access system fonts -pub struct FontSystem<'a> { - pub locale: String, - pub db: fontdb::Database, - pub font_cache: Mutex>>>>, - pub font_matches_cache: Mutex>>>, +#[ouroboros::self_referencing] +struct FontSystemInner { + locale: String, + db: fontdb::Database, + #[borrows(db)] + #[not_covariant] + font_cache: Mutex>>>>, + #[borrows(locale, db)] + #[not_covariant] + font_matches_cache: Mutex>>>, } -impl<'a> FontSystem<'a> { +/// Access system fonts +pub struct FontSystem(FontSystemInner); + +impl FontSystem { pub fn new() -> Self { let locale = sys_locale::get_locale().unwrap_or_else(|| { log::warn!("failed to get system locale, falling back to en-US"); @@ -57,56 +64,71 @@ impl<'a> FontSystem<'a> { ); } - Self { + Self(FontSystemInnerBuilder { locale, db, - font_cache: Mutex::new(HashMap::new()), - font_matches_cache: Mutex::new(HashMap::new()), - } + font_cache_builder: |_| Mutex::new(HashMap::new()), + font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()) + }.build()) } - pub fn get_font(&'a self, id: fontdb::ID) -> Option>> { - let mut font_cache = self.font_cache.lock().unwrap(); - 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 locale(&self) -> &str { + self.0.borrow_locale() } - pub fn get_font_matches(&'a self, attrs: Attrs) -> Arc> { - let mut font_matches_cache = self.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(); + pub fn db(&self) -> &fontdb::Database { + self.0.borrow_db() + } - let mut fonts = Vec::new(); - for face in self.db.faces() { - if !attrs.matches(face) { - continue; + pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option>> { + self.0.with(|fields| { + get_font(&fields, id) + }) + } + + pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc> { + 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) { - Some(font) => fonts.push(font), - None => (), - } - } + let font_matches = Arc::new(FontMatches { + locale: fields.locale, + default_family: fields.db.family_name(&attrs.family).to_string(), + fonts + }); - let font_matches = Arc::new(FontMatches { - locale: &self.locale, - default_family: self.db.family_name(&attrs.family).to_string(), - fonts - }); + let elapsed = now.elapsed(); + log::debug!("font matches for {:?} in {:?}", attrs, elapsed); - let elapsed = now.elapsed(); - log::debug!("font matches for {:?} in {:?}", attrs, elapsed); - - font_matches - }).clone() + font_matches + }).clone() + }) } } + +fn get_font<'b>(fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, id: fontdb::ID) -> Option>> { + 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() +} diff --git a/src/shape.rs b/src/shape.rs index d88309b..9f3757f 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -102,7 +102,7 @@ fn shape_fallback( } fn shape_run<'a>( - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList, start_run: usize, @@ -281,7 +281,7 @@ pub struct ShapeWord { impl ShapeWord { pub fn new<'a>( - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList, start_word: usize, @@ -344,7 +344,7 @@ pub struct ShapeSpan { impl ShapeSpan { pub fn new<'a>( - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList, start_span: usize, @@ -424,7 +424,7 @@ pub struct ShapeLine { impl ShapeLine { pub fn new<'a>( - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList ) -> Self { diff --git a/src/swash.rs b/src/swash.rs index fc5c813..70a68db 100644 --- a/src/swash.rs +++ b/src/swash.rs @@ -9,7 +9,7 @@ use crate::{CacheKey, Color, FontSystem}; 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 { +fn swash_image<'a>(font_system: &'a FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option { let font = match font_system.get_font(cache_key.font_id) { Some(some) => some, None => { @@ -49,14 +49,14 @@ fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext, /// Cache for rasterizing with the swash scaler pub struct SwashCache<'a> { - font_system: &'a FontSystem<'a>, + font_system: &'a FontSystem, context: ScaleContext, pub image_cache: HashMap>, } impl<'a> SwashCache<'a> { /// Create a new swash cache - pub fn new(font_system: &'a FontSystem<'a>) -> Self { + pub fn new(font_system: &'a FontSystem) -> Self { Self { font_system: font_system, context: ScaleContext::new(),