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:
parent
ac354c3a2a
commit
0d3fb1dd9d
8 changed files with 85 additions and 62 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue