diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 9455daa..ca7749d 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -64,7 +64,7 @@ pub struct Window { theme: Theme, path_opt: Option, buffer: Mutex>, - cache: Mutex, + cache: Mutex>, bold: bool, italic: bool, monospaced: bool, @@ -117,11 +117,13 @@ impl Application for Window { FONT_SIZES[font_size_i], ); + let cache = SwashCache::new(&FONT_SYSTEM); + let mut window = Window { theme: Theme::Dark, path_opt: None, buffer: Mutex::new(buffer), - cache: Mutex::new(SwashCache::new()), + cache: Mutex::new(cache), bold: false, italic: false, monospaced: true, @@ -178,7 +180,7 @@ impl Application for Window { } else { cosmic_text::Weight::NORMAL }); - buffer.set_attrs(&FONT_SYSTEM, attrs); + buffer.set_attrs(attrs); }, Message::Italic(italic) => { self.italic = italic; @@ -189,7 +191,7 @@ impl Application for Window { } else { cosmic_text::Style::Normal }); - buffer.set_attrs(&FONT_SYSTEM, attrs); + buffer.set_attrs(attrs); }, Message::Monospaced(monospaced) => { self.monospaced = monospaced; @@ -202,7 +204,7 @@ impl Application for Window { cosmic_text::Family::SansSerif }) .monospaced(monospaced); - buffer.set_attrs(&FONT_SYSTEM, attrs); + buffer.set_attrs(attrs); }, Message::MetricsChanged(metrics) => { let mut buffer = self.buffer.lock().unwrap(); diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index d9102c2..1e138a8 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -63,16 +63,16 @@ impl StyleSheet for Theme { pub struct TextBox<'a> { buffer: &'a Mutex>, - cache: &'a Mutex, + cache: &'a Mutex>, } impl<'a> TextBox<'a> { - pub fn new(buffer: &'a Mutex>, cache: &'a Mutex) -> Self { + pub fn new(buffer: &'a Mutex>, cache: &'a Mutex>) -> Self { Self { buffer, cache } } } -pub fn text_box<'a>(buffer: &'a Mutex>, cache: &'a Mutex) -> TextBox<'a> { +pub fn text_box<'a>(buffer: &'a Mutex>, cache: &'a Mutex>) -> TextBox<'a> { TextBox::new(buffer, cache) } diff --git a/examples/editor-orbclient/src/main.rs b/examples/editor-orbclient/src/main.rs index 939471b..c8a5c05 100644 --- a/examples/editor-orbclient/src/main.rs +++ b/examples/editor-orbclient/src/main.rs @@ -53,7 +53,7 @@ fn main() { default_text.to_string() }; - let mut swash_cache = SwashCache::new(); + let mut swash_cache = SwashCache::new(&font_system); let line_x = 8 * display_scale; let attrs = cosmic_text::Attrs::new().monospaced(cfg!(feature = "mono")); diff --git a/examples/editor-test/src/main.rs b/examples/editor-test/src/main.rs index 1919afb..24af48c 100644 --- a/examples/editor-test/src/main.rs +++ b/examples/editor-test/src/main.rs @@ -71,7 +71,7 @@ fn main() { window.height() as i32 ); - let mut swash_cache = SwashCache::new(); + let mut swash_cache = SwashCache::new(&font_system); let text = if let Some(arg) = env::args().nth(1) { fs::read_to_string(&arg).expect("failed to open file") diff --git a/src/attrs.rs b/src/attrs.rs index 5f27255..ce1b591 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -2,7 +2,7 @@ pub use fontdb::{Family, Stretch, Style, Weight}; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Attrs<'a> { pub family: Family<'a>, pub monospaced: bool, diff --git a/src/buffer.rs b/src/buffer.rs index 276c597..06df0c1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -3,6 +3,7 @@ use std::{ cmp, fmt, + sync::Arc, time::Instant, }; use unicode_segmentation::UnicodeSegmentation; @@ -52,32 +53,32 @@ pub enum TextAction { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct TextCursor { /// Text line the cursor is on - pub line: TextLineIndex, + pub line: usize, /// Index of glyph at cursor (will insert behind this glyph) pub index: usize, } impl TextCursor { - pub const fn new(line: TextLineIndex, index: usize) -> Self { + pub const fn new(line: usize, index: usize) -> Self { Self { line, index } } } struct TextLayoutCursor { - line: TextLineIndex, + line: usize, layout: usize, glyph: usize, } impl TextLayoutCursor { - fn new(line: TextLineIndex, layout: usize, glyph: usize) -> Self { + fn new(line: usize, layout: usize, glyph: usize) -> Self { Self { line, layout, glyph } } } pub struct TextLayoutRun<'a> { /// The index of the original text line - pub line_i: TextLineIndex, + pub line_i: usize, /// The original text line pub text: &'a str, /// True if the original paragraph direction is RTL @@ -130,7 +131,7 @@ impl<'a, 'b> Iterator for TextLayoutRunIter<'a, 'b> { } return Some(TextLayoutRun { - line_i: TextLineIndex::new(self.line_i), + line_i: self.line_i, text: line.text.as_str(), rtl: shape.rtl, glyphs: &layout_line.glyphs, @@ -145,20 +146,6 @@ impl<'a, 'b> Iterator for TextLayoutRunIter<'a, 'b> { } } -/// Index of a text line -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] -pub struct TextLineIndex(usize); - -impl TextLineIndex { - pub fn new(index: usize) -> Self { - Self(index) - } - - pub fn get(&self) -> usize { - self.0 - } -} - /// Metrics of text #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct TextMetrics { @@ -237,8 +224,10 @@ impl TextBufferLine { /// A buffer of text that is shaped and laid out pub struct TextBuffer<'a> { - font_matches: FontMatches<'a>, + font_system: &'a FontSystem<'a>, + font_matches: Arc>, attrs: Attrs<'a>, + attr_spans: Vec<(TextCursor, usize, Attrs<'a>)>, lines: Vec, metrics: TextMetrics, width: i32, @@ -256,10 +245,12 @@ impl<'a> TextBuffer<'a> { attrs: Attrs<'a>, metrics: TextMetrics, ) -> Self { - let font_matches = font_system.matches_attrs(&attrs); + let font_matches = font_system.get_font_matches(attrs); let mut buffer = Self { + font_system, font_matches, attrs, + attr_spans: Vec::new(), lines: Vec::new(), metrics, width: 0, @@ -312,7 +303,7 @@ impl<'a> TextBuffer<'a> { let mut reshaped = 0; let mut layout_i = 0; for (line_i, line) in self.lines.iter_mut().enumerate() { - if line_i > self.cursor.line.get() { + if line_i > self.cursor.line { break; } @@ -324,7 +315,7 @@ impl<'a> TextBuffer<'a> { self.metrics.font_size, self.width ); - if line_i == self.cursor.line.get() { + if line_i == self.cursor.line { let layout_cursor = self.layout_cursor(&self.cursor); layout_i += layout_cursor.layout as i32; break; @@ -385,7 +376,7 @@ impl<'a> TextBuffer<'a> { } fn layout_cursor(&self, cursor: &TextCursor) -> TextLayoutCursor { - let line = &self.lines[cursor.line.get()]; + let line = &self.lines[cursor.line]; let layout = line.layout_opt.as_ref().unwrap(); //TODO: ensure layout is done? for (layout_i, layout_line) in layout.iter().enumerate() { @@ -428,7 +419,7 @@ impl<'a> TextBuffer<'a> { } fn set_layout_cursor(&mut self, cursor: TextLayoutCursor) { - let line = &mut self.lines[cursor.line.get()]; + let line = &mut self.lines[cursor.line]; let layout = line.layout( &self.font_matches, self.metrics.font_size, @@ -516,9 +507,9 @@ impl<'a> TextBuffer<'a> { } /// Set attributes - pub fn set_attrs(&mut self, font_system: &'a FontSystem<'a>, attrs: Attrs<'a>) { + pub fn set_attrs(&mut self, attrs: Attrs<'a>) { if attrs != self.attrs { - self.font_matches = font_system.matches_attrs(&attrs); + self.font_matches = self.font_system.get_font_matches(attrs); self.attrs = attrs; for line in self.lines.iter_mut() { @@ -529,6 +520,10 @@ impl<'a> TextBuffer<'a> { } } + pub fn add_attr_span(&mut self, cursor: TextCursor, len: usize, attrs: Attrs<'a>) { + self.attr_spans.push((cursor, len, attrs)); + } + /// Set text of buffer pub fn set_text(&mut self, text: &str) { self.lines.clear(); @@ -558,7 +553,7 @@ impl<'a> TextBuffer<'a> { match action { TextAction::Previous => { - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; if self.cursor.index > 0 { // Find previous character index @@ -573,14 +568,14 @@ impl<'a> TextBuffer<'a> { self.cursor.index = prev_index; self.redraw = true; - } else if self.cursor.line.get() > 0 { - self.cursor.line = TextLineIndex::new(self.cursor.line.get() - 1); - self.cursor.index = self.lines[self.cursor.line.get()].text.len(); + } else if self.cursor.line > 0 { + self.cursor.line -= 1; + self.cursor.index = self.lines[self.cursor.line].text.len(); self.redraw = true; } }, TextAction::Next => { - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; if self.cursor.index < line.text.len() { for (i, c) in line.text.grapheme_indices(true) { @@ -590,14 +585,14 @@ impl<'a> TextBuffer<'a> { break; } } - } else if self.cursor.line.get() + 1 < self.lines.len() { - self.cursor.line = TextLineIndex::new(self.cursor.line.get() + 1); + } else if self.cursor.line + 1 < self.lines.len() { + self.cursor.line += 1; self.cursor.index = 0; self.redraw = true; } }, TextAction::Left => { - let rtl_opt = self.lines[self.cursor.line.get()].shape_opt.as_ref().map(|shape| shape.rtl); + let rtl_opt = self.lines[self.cursor.line].shape_opt.as_ref().map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { self.action(TextAction::Next); @@ -607,7 +602,7 @@ impl<'a> TextBuffer<'a> { } }, TextAction::Right => { - let rtl_opt = self.lines[self.cursor.line.get()].shape_opt.as_ref().map(|shape| shape.rtl); + let rtl_opt = self.lines[self.cursor.line].shape_opt.as_ref().map(|shape| shape.rtl); if let Some(rtl) = rtl_opt { if rtl { self.action(TextAction::Previous); @@ -621,8 +616,8 @@ impl<'a> TextBuffer<'a> { let mut cursor = self.layout_cursor(&self.cursor); if cursor.layout > 0 { cursor.layout -= 1; - } else if cursor.line.get() > 0 { - cursor.line = TextLineIndex::new(cursor.line.get() - 1); + } else if cursor.line > 0 { + cursor.line -= 1; cursor.layout = usize::max_value(); } self.set_layout_cursor(cursor); @@ -631,7 +626,7 @@ impl<'a> TextBuffer<'a> { //TODO: make this preserve X as best as possible! let mut cursor = self.layout_cursor(&self.cursor); let layout_len = { - let line = &mut self.lines[cursor.line.get()]; + let line = &mut self.lines[cursor.line]; let layout = line.layout( &self.font_matches, self.metrics.font_size, @@ -641,8 +636,8 @@ impl<'a> TextBuffer<'a> { }; if cursor.layout + 1 < layout_len { cursor.layout += 1; - } else if cursor.line.get() + 1 < self.lines.len() { - cursor.line = TextLineIndex::new(cursor.line.get() + 1); + } else if cursor.line + 1 < self.lines.len() { + cursor.line += 1; cursor.layout = 0; } self.set_layout_cursor(cursor); @@ -678,7 +673,7 @@ impl<'a> TextBuffer<'a> { // Filter out special chars (except for tab), use TextAction instead log::debug!("Refusing to insert control character {:?}", character); } else { - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; line.reset(); line.text.insert(self.cursor.index, character); @@ -687,20 +682,20 @@ impl<'a> TextBuffer<'a> { }, TextAction::Enter => { let new_line = { - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; line.reset(); line.text.split_off(self.cursor.index) }; - let next_line = self.cursor.line.get() + 1; + let next_line = self.cursor.line + 1; self.lines.insert(next_line, TextBufferLine::new(new_line)); - self.cursor.line = TextLineIndex::new(next_line); + self.cursor.line = next_line; self.cursor.index = 0; }, TextAction::Backspace => { if self.cursor.index > 0 { - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; line.reset(); // Find previous character index @@ -716,23 +711,23 @@ impl<'a> TextBuffer<'a> { self.cursor.index = prev_index; line.text.remove(self.cursor.index); - } else if self.cursor.line.get() > 0 { - let mut line_index = self.cursor.line.get(); + } else if self.cursor.line > 0 { + let mut line_index = self.cursor.line; let old_line = self.lines.remove(line_index); line_index -= 1; let line = &mut self.lines[line_index]; line.reset(); - self.cursor.line = TextLineIndex::new(line_index); + self.cursor.line = line_index; self.cursor.index = line.text.len(); line.text.push_str(&old_line.text); } }, TextAction::Delete => { - if self.cursor.index < self.lines[self.cursor.line.get()].text.len() { - let line = &mut self.lines[self.cursor.line.get()]; + if self.cursor.index < self.lines[self.cursor.line].text.len() { + let line = &mut self.lines[self.cursor.line]; line.reset(); if let Some((i, c)) = line @@ -744,10 +739,10 @@ impl<'a> TextBuffer<'a> { line.text.replace_range(i..(i + c.len()), ""); self.cursor.index = i; } - } else if self.cursor.line.get() + 1 < self.lines.len() { - let old_line = self.lines.remove(self.cursor.line.get() + 1); + } else if self.cursor.line + 1 < self.lines.len() { + let old_line = self.lines.remove(self.cursor.line + 1); - let line = &mut self.lines[self.cursor.line.get()]; + let line = &mut self.lines[self.cursor.line]; line.reset(); line.text.push_str(&old_line.text); @@ -860,7 +855,7 @@ impl<'a> TextBuffer<'a> { let text_glyph = &run.text[glyph.start..glyph.end]; log::debug!( "{}, {}: '{}' ('{}'): '{}' ({:?})", - self.cursor.line.get(), + self.cursor.line, self.cursor.index, font_opt.as_ref().map_or("?", |font| font.info.family.as_str()), font_opt.as_ref().map_or("?", |font| font.info.post_script_name.as_str()), @@ -1045,7 +1040,7 @@ impl<'a> TextBuffer<'a> { for glyph in run.glyphs.iter() { let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); - cache.with_pixels(&self.font_matches, cache_key, color, |x, y, color| { + cache.with_pixels(cache_key, color, |x, y, color| { f(x_int + x, line_y + y_int + y, 1, 1, color) }); } diff --git a/src/font/system.rs b/src/font/system.rs index b8fe0ec..07d1ef5 100644 --- a/src/font/system.rs +++ b/src/font/system.rs @@ -5,13 +5,14 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::{Attrs, Family, Font, FontMatches}; +use crate::{Attrs, 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, Arc>>>, } impl<'a> FontSystem<'a> { @@ -60,6 +61,7 @@ impl<'a> FontSystem<'a> { locale, db, font_cache: Mutex::new(HashMap::new()), + font_matches_cache: Mutex::new(HashMap::new()), } } @@ -77,48 +79,33 @@ impl<'a> FontSystem<'a> { }).clone() } - pub fn matches bool>( - &'a self, - default_family: &Family, - f: F, - ) -> FontMatches<'_> { - let mut fonts = Vec::new(); - for face in self.db.faces() { - if !f(face) { - continue; + pub fn get_font_matches(&'a self, attrs: Attrs<'a>) -> Arc> { + let mut font_matches_cache = self.font_matches_cache.lock().unwrap(); + font_matches_cache.entry(attrs).or_insert_with(|| { + let now = std::time::Instant::now(); + + let mut fonts = Vec::new(); + for face in self.db.faces() { + if !attrs.matches(face) { + continue; + } + + match self.get_font(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: &self.locale, + default_family: self.db.family_name(&attrs.family).to_string(), + fonts + }); - FontMatches { - locale: &self.locale, - default_family: self.db.family_name(default_family).to_string(), - fonts - } - } + let elapsed = now.elapsed(); + log::debug!("font matches for {:?} in {:?}", attrs, elapsed); - pub fn matches_attrs(&'a self, attrs: &Attrs) -> FontMatches<'_> { - self.matches(&attrs.family, |face| { - let matched = attrs.matches(face); - - if matched { - log::debug!( - "{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}", - face.id, - face.family, - face.post_script_name, - face.style, - face.weight, - face.stretch, - face.monospaced - ); - } - - matched - }) + font_matches + }).clone() } } diff --git a/src/swash.rs b/src/swash.rs index 1f26da6..ecb5218 100644 --- a/src/swash.rs +++ b/src/swash.rs @@ -5,12 +5,12 @@ use swash::scale::{ScaleContext, image::Content}; use swash::scale::{Render, Source, StrikeWith}; use swash::zeno::{Format, Vector}; -use crate::{CacheKey, FontMatches}; +use crate::{CacheKey, FontSystem}; pub use swash::scale::image::{Content as SwashContent, Image as SwashImage}; -fn swash_image(context: &mut ScaleContext, matches: &FontMatches, cache_key: CacheKey) -> Option { - let font = match matches.get_font(&cache_key.font_id) { +fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext, cache_key: CacheKey) -> Option { + let font = match font_system.get_font(cache_key.font_id) { Some(some) => some, None => { log::warn!("did not find font {:?}", cache_key.font_id); @@ -47,41 +47,42 @@ fn swash_image(context: &mut ScaleContext, matches: &FontMatches, cache_key: Cac .render(&mut scaler, cache_key.glyph_id) } -pub struct SwashCache { +pub struct SwashCache<'a> { + font_system: &'a FontSystem<'a>, context: ScaleContext, pub image_cache: HashMap>, } -impl SwashCache { +impl<'a> SwashCache<'a> { /// Create a new swash cache - pub fn new() -> Self { + pub fn new(font_system: &'a FontSystem<'a>) -> Self { Self { + font_system: font_system, context: ScaleContext::new(), image_cache: HashMap::new() } } /// Create a swash Image from a cache key, without caching results - pub fn get_image_uncached(&mut self, matches: &FontMatches, cache_key: CacheKey) -> Option { - swash_image(&mut self.context, matches, cache_key) + pub fn get_image_uncached(&mut self, cache_key: CacheKey) -> Option { + swash_image(self.font_system, &mut self.context, cache_key) } /// Create a swash Image from a cache key, caching results - pub fn get_image(&mut self, matches: &FontMatches, cache_key: CacheKey) -> &Option { + pub fn get_image(&mut self, cache_key: CacheKey) -> &Option { self.image_cache.entry(cache_key).or_insert_with(|| { - swash_image(&mut self.context, matches, cache_key) + swash_image(self.font_system, &mut self.context, cache_key) }) } /// Enumerate pixels in an Image, use `with_image` for better performance pub fn with_pixels( &mut self, - matches: &FontMatches<'_>, cache_key: CacheKey, base: u32, mut f: F ) { - if let Some(image) = self.get_image(matches, cache_key) { + if let Some(image) = self.get_image(cache_key) { let x = image.placement.left; let y = -image.placement.top;