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]
fontdb = "0.9.3"
log = "0.4"
ouroboros = "0.15.5"
rustybuzz = "0.5"
swash = { version = "0.1", optional = true }
sys-locale = "0.2"

View file

@ -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())
}
}

View file

@ -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();

View file

@ -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<BufferLine>,
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 {

View file

@ -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);

View file

@ -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<HashMap<fontdb::ID, Option<Arc<Font<'a>>>>>,
pub font_matches_cache: Mutex<HashMap<AttrsOwned, Arc<FontMatches<'a>>>>,
#[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>>>>,
}
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<Arc<Font<'a>>> {
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<FontMatches<'a>> {
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<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().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<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>(
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 {

View file

@ -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<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) {
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<CacheKey, Option<SwashImage>>,
}
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(),