diff --git a/docs/docs/releases.md b/docs/docs/releases.md index b3fc0d3be9..de189d908e 100644 --- a/docs/docs/releases.md +++ b/docs/docs/releases.md @@ -10,6 +10,11 @@ language: 'en' - **breaking**: `CollapsedTab` has been renamed to `Bookmark`. +- Memory usage reduced by 75% (avg ~201mb to 48mb on first screen render). +- Implemented font data deallocator. +- Reduced font atlas buffer size to `1024`. +- Added lifetimes to application level (allowing to deallocate window structs once is removed). +- Migrated font context from `RwLock` to `Arc`. - MacOS does not clear with background operation anymore, instead it relies on window background. - Background color has changed to `#0F0D0E`. - Fix font emoji width. diff --git a/frontends/rioterm/src/application.rs b/frontends/rioterm/src/application.rs index 6ecc7eef27..4b1eeab3f8 100644 --- a/frontends/rioterm/src/application.rs +++ b/frontends/rioterm/src/application.rs @@ -23,19 +23,19 @@ use rio_window::window::{CursorIcon, Fullscreen}; use std::error::Error; use std::time::{Duration, Instant}; -pub struct Application { +pub struct Application<'a> { config: rio_backend::config::Config, event_proxy: EventProxy, - router: Router, + router: Router<'a>, scheduler: Scheduler, } -impl Application { - pub fn new( +impl Application<'_> { + pub fn new<'app>( config: rio_backend::config::Config, config_error: Option, event_loop: &EventLoop, - ) -> Application { + ) -> Application<'app> { // SAFETY: Since this takes a pointer to the winit event loop, it MUST be dropped first, // which is done in `loop_exiting`. let clipboard = @@ -94,7 +94,7 @@ impl Application { } } -impl ApplicationHandler for Application { +impl ApplicationHandler for Application<'_> { fn resumed(&mut self, _active_event_loop: &ActiveEventLoop) { #[cfg(not(any(target_os = "macos", windows)))] { diff --git a/frontends/rioterm/src/renderer/mod.rs b/frontends/rioterm/src/renderer/mod.rs index 7b7ddfa106..84033f1649 100644 --- a/frontends/rioterm/src/renderer/mod.rs +++ b/frontends/rioterm/src/renderer/mod.rs @@ -288,7 +288,7 @@ impl Renderer { ) { let columns: usize = row.len(); let mut content = String::default(); - let mut last_char_was_empty = false; + let mut last_char_was_space = false; let mut last_style = FragmentStyle::default(); for column in 0..columns { @@ -405,14 +405,23 @@ impl Renderer { ); }; - // TODO: Write tests for it - if square_content != ' ' && last_char_was_empty { - if !content.is_empty() { + if square_content == ' ' { + if !last_char_was_space { + if !content.is_empty() { + content_builder.add_text(&content, last_style); + content.clear(); + } + + last_char_was_space = true; + last_style = style; + } + } else { + if last_char_was_space && !content.is_empty() { content_builder.add_text(&content, last_style); content.clear(); } - last_char_was_empty = false; + last_char_was_space = false; } if last_style != style { @@ -424,26 +433,12 @@ impl Renderer { last_style = style; } - if square_content == ' ' && !last_char_was_empty { - if !content.is_empty() { - content_builder.add_text(&content, last_style); - content.clear(); - } - - content.push(square_content); - last_char_was_empty = true; - continue; - } - content.push(square_content); // Render last column and break row if column == (columns - 1) { if !content.is_empty() { - // let start = std::time::Instant::now(); content_builder.add_text(&content, last_style); - // let duration = start.elapsed(); - // println!("Total add_text: {:?}", duration); } break; diff --git a/frontends/rioterm/src/router/mod.rs b/frontends/rioterm/src/router/mod.rs index 2a567cd46a..9c77163bd9 100644 --- a/frontends/rioterm/src/router/mod.rs +++ b/frontends/rioterm/src/router/mod.rs @@ -27,13 +27,13 @@ use std::rc::Rc; // #[cfg(not(any(target_os = "macos", target_os = "windows")))] const RIO_TITLE: &str = "▲"; -pub struct Route { +pub struct Route<'a> { pub assistant: assistant::Assistant, pub path: RoutePath, - pub window: RouteWindow, + pub window: RouteWindow<'a>, } -impl Route { +impl Route<'_> { /// Create a performer. #[inline] pub fn new( @@ -49,7 +49,7 @@ impl Route { } } -impl Route { +impl Route<'_> { #[inline] pub fn request_redraw(&mut self) { self.window.winit_window.request_redraw(); @@ -141,19 +141,19 @@ impl Route { } } -pub struct Router { - pub routes: HashMap, +pub struct Router<'a> { + pub routes: HashMap>, propagated_report: Option, pub font_library: Box, pub config_route: Option, pub clipboard: Rc>, } -impl Router { - pub fn new( +impl Router<'_> { + pub fn new<'b>( fonts: rio_backend::sugarloaf::font::SugarloafFonts, clipboard: Clipboard, - ) -> Router { + ) -> Router<'b> { let (font_library, fonts_not_found) = rio_backend::sugarloaf::font::FontLibrary::new(fonts); @@ -238,11 +238,11 @@ impl Router { } #[inline] - pub fn create_window( - &mut self, - event_loop: &ActiveEventLoop, + pub fn create_window<'a>( + &'a mut self, + event_loop: &'a ActiveEventLoop, event_proxy: EventProxy, - config: &rio_backend::config::Config, + config: &'a rio_backend::config::Config, open_url: Option, ) { let tab_id = if config.navigation.is_native() { @@ -279,11 +279,11 @@ impl Router { #[cfg(target_os = "macos")] #[inline] - pub fn create_native_tab( - &mut self, - event_loop: &ActiveEventLoop, + pub fn create_native_tab<'a>( + &'a mut self, + event_loop: &'a ActiveEventLoop, event_proxy: EventProxy, - config: &rio_backend::config::Config, + config: &'a rio_backend::config::Config, tab_id: Option<&str>, open_url: Option, ) { @@ -308,33 +308,33 @@ impl Router { } } -pub struct RouteWindow { +pub struct RouteWindow<'a> { pub is_focused: bool, pub is_occluded: bool, pub has_frame: bool, pub has_updates: bool, pub winit_window: Window, - pub screen: Screen<'static>, + pub screen: Screen<'a>, #[cfg(target_os = "macos")] pub is_macos_deadzone: bool, } -impl RouteWindow { +impl<'a> RouteWindow<'a> { pub fn configure_window(&mut self, config: &rio_backend::config::Config) { configure_window(&self.winit_window, config); } #[allow(clippy::too_many_arguments)] - pub fn from_target( - event_loop: &ActiveEventLoop, + pub fn from_target<'b>( + event_loop: &'b ActiveEventLoop, event_proxy: EventProxy, - config: &RioConfig, + config: &'b RioConfig, font_library: &rio_backend::sugarloaf::font::FontLibrary, window_name: &str, tab_id: Option<&str>, open_url: Option, clipboard: Rc>, - ) -> RouteWindow { + ) -> RouteWindow<'a> { #[allow(unused_mut)] let mut window_builder = create_window_builder(window_name, config, tab_id); diff --git a/rio-backend/src/crosswords/mod.rs b/rio-backend/src/crosswords/mod.rs index 784d8858b2..3770945418 100644 --- a/rio-backend/src/crosswords/mod.rs +++ b/rio-backend/src/crosswords/mod.rs @@ -76,30 +76,30 @@ bitflags! { #[derive(Debug, Copy, Clone)] pub struct Mode: u32 { const NONE = 0; - const SHOW_CURSOR = 0b0000_0000_0000_0000_0000_0001; - const APP_CURSOR = 0b0000_0000_0000_0000_0000_0010; - const APP_KEYPAD = 0b0000_0000_0000_0000_0000_0100; - const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_0000_1000; - const BRACKETED_PASTE = 0b0000_0000_0000_0000_0001_0000; - const SGR_MOUSE = 0b0000_0000_0000_0000_0010_0000; - const MOUSE_MOTION = 0b0000_0000_0000_0000_0100_0000; - const LINE_WRAP = 0b0000_0000_0000_0000_1000_0000; - const LINE_FEED_NEW_LINE = 0b0000_0000_0000_0001_0000_0000; - const ORIGIN = 0b0000_0000_0000_0010_0000_0000; - const INSERT = 0b0000_0000_0000_0100_0000_0000; - const FOCUS_IN_OUT = 0b0000_0000_0000_1000_0000_0000; - const ALT_SCREEN = 0b0000_0000_0001_0000_0000_0000; - const MOUSE_DRAG = 0b0000_0000_0010_0000_0000_0000; - const MOUSE_MODE = 0b0000_0000_0010_0000_0100_1000; - const UTF8_MOUSE = 0b0000_0000_0100_0000_0000_0000; - const ALTERNATE_SCROLL = 0b0000_0000_1000_0000_0000_0000; - const VI = 0b0000_0001_0000_0000_0000_0000; - const URGENCY_HINTS = 0b0000_0010_0000_0000_0000_0000; - const DISAMBIGUATE_ESC_CODES = 0b0000_0100_0000_0000_0000_0000; - const REPORT_EVENT_TYPES = 0b0000_1000_0000_0000_0000_0000; - const REPORT_ALTERNATE_KEYS = 0b0001_0000_0000_0000_0000_0000; - const REPORT_ALL_KEYS_AS_ESC = 0b0010_0000_0000_0000_0000_0000; - const REPORT_ASSOCIATED_TEXT = 0b0100_0000_0000_0000_0000_0000; + const SHOW_CURSOR = 1; + const APP_CURSOR = 1 << 1; + const APP_KEYPAD = 1 << 2; + const MOUSE_REPORT_CLICK = 1 << 3; + const BRACKETED_PASTE = 1 << 4; + const SGR_MOUSE = 1 << 5; + const MOUSE_MOTION = 1 << 6; + const LINE_WRAP = 1 << 7; + const LINE_FEED_NEW_LINE = 1 << 8; + const ORIGIN = 1 << 9; + const INSERT = 1 << 10; + const FOCUS_IN_OUT = 1 << 11; + const ALT_SCREEN = 1 << 12; + const MOUSE_DRAG = 1 << 13; + const UTF8_MOUSE = 1 << 14; + const ALTERNATE_SCROLL = 1 << 15; + const VI = 1 << 16; + const URGENCY_HINTS = 1 << 17; + const DISAMBIGUATE_ESC_CODES = 1 << 18; + const REPORT_EVENT_TYPES = 1 << 19; + const REPORT_ALTERNATE_KEYS = 1 << 20; + const REPORT_ALL_KEYS_AS_ESC = 1 << 21; + const REPORT_ASSOCIATED_TEXT = 1 << 22; + const MOUSE_MODE = Self::MOUSE_REPORT_CLICK.bits() | Self::MOUSE_MOTION.bits() | Self::MOUSE_DRAG.bits(); const KITTY_KEYBOARD_PROTOCOL = Self::DISAMBIGUATE_ESC_CODES.bits() | Self::REPORT_EVENT_TYPES.bits() | Self::REPORT_ALTERNATE_KEYS.bits() diff --git a/sugarloaf/src/components/rich_text/image_cache/cache.rs b/sugarloaf/src/components/rich_text/image_cache/cache.rs index f686db78bd..e56b9a6be9 100644 --- a/sugarloaf/src/components/rich_text/image_cache/cache.rs +++ b/sugarloaf/src/components/rich_text/image_cache/cache.rs @@ -38,7 +38,7 @@ pub fn buffer_size(width: u32, height: u32) -> Option { .checked_add(4) } -pub const SIZE: u16 = 3072; +pub const SIZE: u16 = 2048; impl ImageCache { /// Creates a new image cache. diff --git a/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs index dc572ed140..03d9598e3b 100644 --- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs +++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs @@ -124,80 +124,83 @@ impl<'a> GlyphCacheSession<'a> { } self.scaled_image.data.clear(); - let font_library_data = self.font_library.inner.lock(); - let mut scaler = self - .scale_context - .builder(font_library_data[self.font].as_ref()) - // With the advent of high-DPI displays (displays with >300 pixels per inch), - // font hinting has become less relevant, as aliasing effects become - // un-noticeable to the human eye. - // As a result Apple's Quartz text renderer, which is targeted for Retina displays, - // now ignores font hint information completely. - // .hint(!IS_MACOS) - .hint(true) - .size(self.quant_size.into()) - // .normalized_coords(coords) - .build(); + let mut font_library_data = self.font_library.inner.lock(); - // let embolden = if IS_MACOS { 0.25 } else { 0. }; - if Render::new(SOURCES) - .format(Format::CustomSubpixel([0.3, 0., -0.3])) - // .format(Format::Alpha) - // .offset(Vector::new(subpx[0].to_f32(), subpx[1].to_f32())) - // .embolden(embolden) - // .transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) { - // Some(Transform::skew( - // Angle::from_degrees(14.0), - // Angle::from_degrees(0.0), - // )) - // } else { - // None - // }) - .render_into(&mut scaler, id, self.scaled_image) - { - let p = self.scaled_image.placement; - let w = p.width as u16; - let h = p.height as u16; - let req = AddImage { - width: w, - height: h, - has_alpha: true, - data: ImageData::Borrowed(&self.scaled_image.data), - }; - let image = self.images.allocate(req)?; + if let Some(data) = font_library_data.get_data(&self.font) { + let mut scaler = self + .scale_context + .builder(data) + // With the advent of high-DPI displays (displays with >300 pixels per inch), + // font hinting has become less relevant, as aliasing effects become + // un-noticeable to the human eye. + // As a result Apple's Quartz text renderer, which is targeted for Retina displays, + // now ignores font hint information completely. + // .hint(!IS_MACOS) + .hint(true) + .size(self.quant_size.into()) + // .normalized_coords(coords) + .build(); - // let mut top = p.top; - // let mut height = h; + // let embolden = if IS_MACOS { 0.25 } else { 0. }; + if Render::new(SOURCES) + .format(Format::CustomSubpixel([0.3, 0., -0.3])) + // .format(Format::Alpha) + // .offset(Vector::new(subpx[0].to_f32(), subpx[1].to_f32())) + // .embolden(embolden) + // .transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) { + // Some(Transform::skew( + // Angle::from_degrees(14.0), + // Angle::from_degrees(0.0), + // )) + // } else { + // None + // }) + .render_into(&mut scaler, id, self.scaled_image) + { + let p = self.scaled_image.placement; + let w = p.width as u16; + let h = p.height as u16; + let req = AddImage { + width: w, + height: h, + has_alpha: true, + data: ImageData::Borrowed(&self.scaled_image.data), + }; + let image = self.images.allocate(req)?; - // If dimension is None it means that we are running - // for the first time and in this case, we will obtain - // what the next glyph entries should respect in terms of - // top and height values - // - // e.g: Placement { left: 11, top: 42, width: 8, height: 50 } - // - // The calculation is made based on max_height - // If the rect max height is 50 and the glyph height is 68 - // and 48 top, then (68 - 50 = 18) height as difference and - // apply it to the top (bigger the top == up ^). - // if self.max_height > &0 && &h > self.max_height { - // let difference = h - self.max_height; + // let mut top = p.top; + // let mut height = h; - // top -= difference as i32; - // height = *self.max_height; - // } + // If dimension is None it means that we are running + // for the first time and in this case, we will obtain + // what the next glyph entries should respect in terms of + // top and height values + // + // e.g: Placement { left: 11, top: 42, width: 8, height: 50 } + // + // The calculation is made based on max_height + // If the rect max height is 50 and the glyph height is 68 + // and 48 top, then (68 - 50 = 18) height as difference and + // apply it to the top (bigger the top == up ^). + // if self.max_height > &0 && &h > self.max_height { + // let difference = h - self.max_height; - let entry = GlyphEntry { - left: p.left, - top: p.top, - width: w, - height: h, - image, - is_bitmap: self.scaled_image.content == Content::Color, - }; + // top -= difference as i32; + // height = *self.max_height; + // } - self.entry.glyphs.insert(key, entry); - return Some(entry); + let entry = GlyphEntry { + left: p.left, + top: p.top, + width: w, + height: h, + image, + is_bitmap: self.scaled_image.content == Content::Color, + }; + + self.entry.glyphs.insert(key, entry); + return Some(entry); + } } None diff --git a/sugarloaf/src/font/fallbacks/mod.rs b/sugarloaf/src/font/fallbacks/mod.rs index ea87a7c06e..80980474e7 100644 --- a/sugarloaf/src/font/fallbacks/mod.rs +++ b/sugarloaf/src/font/fallbacks/mod.rs @@ -2,8 +2,8 @@ pub fn external_fallbacks() -> Vec { vec![ String::from(".SF NS"), - // String::from("Menlo"), - // String::from("Geneva"), + String::from("Menlo"), + String::from("Geneva"), String::from("Arial Unicode MS"), // String::from("Noto Emoji"), // String::from("Noto Color Emoji"), diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index 2609df37fd..3703034399 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -12,13 +12,14 @@ use crate::font_introspector::text::cluster::Token; use crate::font_introspector::text::cluster::{CharCluster, Status}; use crate::font_introspector::text::Codepoint; use crate::font_introspector::text::Script; -use crate::font_introspector::{Attributes, CacheKey, FontRef, Synthesis}; +use crate::font_introspector::{CacheKey, FontRef, Synthesis}; use crate::layout::FragmentStyle; use crate::SugarloafErrors; use ab_glyph::FontArc; +use lru::LruCache; use parking_lot::FairMutex; use rustc_hash::FxHashMap; -use std::ops::{Index, IndexMut}; +use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::Arc; @@ -27,47 +28,55 @@ pub use crate::font_introspector::{Style, Weight}; pub fn lookup_for_font_match( cluster: &mut CharCluster, synth: &mut Synthesis, - library: &FontLibraryData, + library: &mut FontLibraryData, spec_font_attr_opt: Option<&(crate::font_introspector::Style, bool)>, ) -> Option<(usize, bool)> { - let mut font_id = None; - for (current_font_id, font) in library.inner.iter().enumerate() { - let (font, font_ref) = match font { - FontSource::Data(font_data) => (font_data, font_data.as_ref()), - FontSource::Standard => (&library.standard, library.standard.as_ref()), - }; - - // In this case, the font does match however - // we need to check if is indeed a match - if let Some(spec_font_attr) = spec_font_attr_opt { - if font.style != spec_font_attr.0 { - continue; - } + let mut search_result = None; + let mut font_synth = Synthesis::default(); + let fonts_len: usize = library.inner.len(); + + for font_id in 0..fonts_len { + let mut is_emoji = false; + + if let Some(font) = library.inner.get(&font_id) { + is_emoji = font.is_emoji; + font_synth = font.synth; + + // In this case, the font does match however + // we need to check if is indeed a match + if let Some(spec_font_attr) = spec_font_attr_opt { + if font.style != spec_font_attr.0 { + continue; + } - // In case bold is required - // It follows spec on Bold (>=700) - // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight - if spec_font_attr.1 && font.weight < crate::font_introspector::Weight(700) { - continue; + // In case bold is required + // It follows spec on Bold (>=700) + // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight + if spec_font_attr.1 && font.weight < crate::font_introspector::Weight(700) + { + continue; + } } } - let charmap = font_ref.charmap(); - let status = cluster.map(|ch| charmap.map(ch)); - if status != Status::Discard { - *synth = library[current_font_id].synth; - font_id = Some((current_font_id, library[current_font_id].is_emoji)); - break; + if let Some(data) = library.get_data(&font_id) { + let charmap = data.charmap(); + let status = cluster.map(|ch| charmap.map(ch)); + if status != Status::Discard { + *synth = font_synth; + search_result = Some((font_id, is_emoji)); + break; + } } } // In case no font_id is found and exists a font spec requirement // then drop requirement and try to find something that can match. - if font_id.is_none() && spec_font_attr_opt.is_some() { + if search_result.is_none() && spec_font_attr_opt.is_some() { return lookup_for_font_match(cluster, synth, library, None); } - font_id + search_result } #[derive(Clone)] @@ -106,26 +115,19 @@ impl Default for FontLibrary { } } -pub enum FontSource { - Standard, - Data(FontData), -} - pub struct FontLibraryData { pub ui: FontArc, // Standard is fallback for everything, it is also the inner number 0 - pub standard: FontData, - pub inner: Vec, - pub cache: FxHashMap, + pub inner: FxHashMap, + pub stash: LruCache, } impl Default for FontLibraryData { fn default() -> Self { Self { ui: FontArc::try_from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), - standard: FontData::from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), - inner: vec![], - cache: FxHashMap::default(), + inner: FxHashMap::default(), + stash: LruCache::new(NonZeroUsize::new(3).unwrap()), } } } @@ -180,7 +182,50 @@ impl FontLibraryData { #[inline] pub fn insert(&mut self, font_data: FontData) { - self.inner.push(FontSource::Data(font_data)); + self.inner.insert(self.inner.len(), font_data); + } + + #[inline] + pub fn get(&mut self, font_id: &usize) -> &FontData { + &self.inner[font_id] + } + + pub fn get_data<'a>(&'a mut self, font_id: &usize) -> Option> { + if let Some(font) = self.inner.get(font_id) { + match &font.data { + Some(data) => { + return Some(FontRef { + data: data.as_ref(), + offset: font.offset, + key: font.key, + }) + } + None => { + if !self.stash.contains(font_id) { + if let Some(path) = &font.path { + if let Some(raw_data) = load_from_font_source(path) { + self.stash.put(*font_id, SharedData::new(raw_data)); + } + } + } + } + } + + if let Some(data) = self.stash.get(font_id) { + return Some(FontRef { + data: data.as_ref(), + offset: font.offset, + key: font.key, + }); + } + }; + + None + } + + #[inline] + pub fn get_mut(&mut self, font_id: &usize) -> Option<&mut FontData> { + self.inner.get_mut(font_id) } #[inline] @@ -193,15 +238,6 @@ impl FontLibraryData { self.inner.is_empty() } - #[inline] - pub fn upsert(&mut self, font_id: usize, path: PathBuf) { - if let Some(font_data) = self.inner.get_mut(font_id) { - if let Some(loaded_font_data) = load_from_font_source(&path) { - *font_data = FontSource::Data(loaded_font_data); - }; - } - } - #[cfg(not(target_arch = "wasm32"))] pub fn load(&mut self, mut spec: SugarloafFonts) -> Vec { let mut fonts_not_fount: Vec = vec![]; @@ -217,53 +253,48 @@ impl FontLibraryData { let mut db = loader::Database::new(); db.load_system_fonts(); - match find_font(&db, spec.regular) { + match find_font(&db, spec.regular, false, false) { FindResult::Found(data) => { - self.standard = data; - self.inner = vec![FontSource::Standard]; + self.insert(data); } FindResult::NotFound(spec) => { - self.standard = load_fallback_from_memory(&spec); - self.inner = vec![FontSource::Standard]; + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } - match find_font(&db, spec.italic) { + match find_font(&db, spec.italic, true, false) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } - match find_font(&db, spec.bold) { + match find_font(&db, spec.bold, true, false) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } - match find_font(&db, spec.bold_italic) { + match find_font(&db, spec.bold_italic, true, false) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -277,9 +308,11 @@ impl FontLibraryData { family: fallback, ..SugarloafFont::default() }, + true, + false, ) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { // Fallback should not add errors @@ -289,27 +322,19 @@ impl FontLibraryData { } if let Some(emoji_font) = spec.emoji { - match find_font(&db, emoji_font) { + match find_font(&db, emoji_font, true, true) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner.push(FontSource::Data( - FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), - )); + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap()); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } } else { - self.inner.push(FontSource::Data( - FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), - )); - } - // Set last font as emoji - if let Some(FontSource::Data(ref mut font_ref)) = self.inner.last_mut() { - font_ref.is_emoji = true; + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap()); } for extra_font in spec.extras { @@ -320,9 +345,11 @@ impl FontLibraryData { style: extra_font.style, weight: extra_font.weight, }, + true, + true, ) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.insert(data); } FindResult::NotFound(spec) => { fonts_not_fount.push(spec); @@ -330,14 +357,12 @@ impl FontLibraryData { } } - self.inner.push(FontSource::Data( - FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap(), - )); + self.insert(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO, false).unwrap()); if let Some(ui_spec) = spec.ui { - match find_font(&db, ui_spec) { + match find_font(&db, ui_spec, false, false) { FindResult::Found(data) => { - self.ui = FontArc::try_from_vec(data.data.to_vec()).unwrap(); + self.ui = FontArc::try_from_vec(data.data.unwrap().to_vec()).unwrap(); } FindResult::NotFound(spec) => { fonts_not_fount.push(spec); @@ -351,32 +376,12 @@ impl FontLibraryData { #[cfg(target_arch = "wasm32")] pub fn load(&mut self, _font_spec: SugarloafFonts) -> Vec { self.inner - .insert(FontData::from_slice(FONT_CASCADIAMONO_REGULAR).unwrap()); + .insert(FontData::from_slice(FONT_CASCADIAMONO_REGULAR, false).unwrap()); vec![] } } -impl Index for FontLibraryData { - type Output = FontData; - - fn index(&self, index: usize) -> &Self::Output { - match &self.inner[index] { - FontSource::Data(font_ref) => font_ref, - FontSource::Standard => &self.standard, - } - } -} - -impl IndexMut for FontLibraryData { - fn index_mut(&mut self, index: usize) -> &mut FontData { - match &mut self.inner[index] { - FontSource::Data(font_ref) => font_ref, - FontSource::Standard => &mut self.standard, - } - } -} - /// Atomically reference counted, heap allocated or memory mapped buffer. #[derive(Clone)] pub struct SharedData { @@ -409,7 +414,8 @@ impl AsRef<[u8]> for SharedData { #[derive(Clone)] pub struct FontData { // Full content of the font file - data: SharedData, + data: Option, + path: Option, // Offset to the table directory offset: u32, // Cache key @@ -429,15 +435,14 @@ impl PartialEq for FontData { } } -impl<'a> From<&'a FontData> for FontRef<'a> { - fn from(f: &'a FontData) -> FontRef<'a> { - f.as_ref() - } -} - impl FontData { #[inline] - pub fn from_data(data: Vec) -> Result> { + pub fn from_data( + data: Vec, + path: PathBuf, + evictable: bool, + is_emoji: bool, + ) -> Result> { let font = FontRef::from_index(&data, 0).unwrap(); let (offset, key) = (font.offset, font.key); @@ -449,20 +454,30 @@ impl FontData { let stretch = attributes.stretch(); let synth = attributes.synthesize(attributes); + let data = if evictable { + None + } else { + Some(SharedData::new(data)) + }; + Ok(Self { - data: SharedData::new(data), + data, offset, key, synth, style, weight, stretch, - is_emoji: false, + path: Some(path), + is_emoji, }) } #[inline] - pub fn from_slice(data: &[u8]) -> Result> { + pub fn from_slice( + data: &[u8], + is_emoji: bool, + ) -> Result> { let font = FontRef::from_index(data, 0).unwrap(); let (offset, key) = (font.offset, font.key); // Return our struct with the original file data and copies of the @@ -474,37 +489,17 @@ impl FontData { let synth = attributes.synthesize(attributes); Ok(Self { - data: SharedData::new(data.to_vec()), + data: Some(SharedData::new(data.to_vec())), offset, key, synth, style, weight, stretch, - is_emoji: false, + path: None, + is_emoji, }) } - - // As a convenience, you may want to forward some methods. - #[inline] - pub fn attributes(&self) -> Attributes { - self.as_ref().attributes() - } - - // Create the transient font reference for accessing this crate's - // functionality. - #[inline] - pub fn as_ref(&self) -> FontRef { - // Note that you'll want to initialize the struct directly here as - // using any of the FontRef constructors will generate a new key which, - // while completely safe, will nullify the performance optimizations of - // the caching mechanisms used in this crate. - FontRef { - data: &self.data, - offset: self.offset, - key: self.key, - } - } } pub type SugarloafFont = fonts::SugarloafFont; @@ -528,7 +523,12 @@ enum FindResult { #[cfg(not(target_arch = "wasm32"))] #[inline] -fn find_font(db: &crate::font::loader::Database, font_spec: SugarloafFont) -> FindResult { +fn find_font( + db: &crate::font::loader::Database, + font_spec: SugarloafFont, + evictable: bool, + is_emoji: bool, +) -> FindResult { use std::io::Read; if !font_spec.is_default_family() { @@ -561,7 +561,12 @@ fn find_font(db: &crate::font::loader::Database, font_spec: SugarloafFont) -> Fi if let Ok(mut file) = std::fs::File::open(path) { let mut font_data = vec![]; if file.read_to_end(&mut font_data).is_ok() { - match FontData::from_data(font_data) { + match FontData::from_data( + font_data, + path.to_path_buf(), + evictable, + is_emoji, + ) { Ok(d) => { tracing::info!( "Font '{}' found in {}", @@ -617,7 +622,7 @@ fn load_fallback_from_memory(font_spec: &SugarloafFont) -> FontData { (_, _) => constants::FONT_CASCADIAMONO_REGULAR, }; - FontData::from_slice(font_to_load).unwrap() + FontData::from_slice(font_to_load, false).unwrap() } #[allow(dead_code)] @@ -644,21 +649,13 @@ fn find_font_path( } #[cfg(not(target_arch = "wasm32"))] -fn load_from_font_source(path: &PathBuf) -> Option { +fn load_from_font_source(path: &PathBuf) -> Option> { use std::io::Read; if let Ok(mut file) = std::fs::File::open(path) { let mut font_data = vec![]; if file.read_to_end(&mut font_data).is_ok() { - match FontData::from_data(font_data) { - Ok(d) => { - return Some(d); - } - Err(err_message) => { - tracing::info!("Failed to load font from source {err_message}"); - return None; - } - } + return Some(font_data); } } diff --git a/sugarloaf/src/layout/builder.rs b/sugarloaf/src/layout/content.rs similarity index 76% rename from sugarloaf/src/layout/builder.rs rename to sugarloaf/src/layout/content.rs index 59c392332d..d1abc70031 100644 --- a/sugarloaf/src/layout/builder.rs +++ b/sugarloaf/src/layout/content.rs @@ -287,7 +287,8 @@ impl Content { // println!("{:?} -> {:?}", item.style.font_id, shaper_key); - if let Some(shaper) = self.word_cache.inner.get(shaper_key) { + if let Some(shaper) = self.word_cache.get(&item.style.font_id, shaper_key) + { if let Some(metrics) = self.metrics_cache.inner.get(&item.style.font_id) { @@ -303,50 +304,66 @@ impl Content { } } - self.word_cache.key = shaper_key.to_owned(); - let font_library = { &self.fonts.inner.lock() }; - - let mut shaper = self - .scx - .builder(font_library[item.style.font_id].as_ref()) - .script(script) - .size(self.state.font_size) - .features(self.font_features.iter().copied()) - .variations(vars.iter().copied()) - .build(); - - shaper.add_str(&self.word_cache.key); - - self.metrics_cache - .inner - .entry(item.style.font_id) - .or_insert_with(|| shaper.metrics()); - - render_data.push_run( - item.style, - self.state.font_size, - line_number as u32, - shaper, - &mut self.word_cache, - ); + self.word_cache.font_id = item.style.font_id; + self.word_cache.content = item.content.clone(); + let font_library = { &mut self.fonts.inner.lock() }; + if let Some(data) = font_library.get_data(&item.style.font_id) { + let mut shaper = self + .scx + .builder(data) + .script(script) + .size(self.state.font_size) + .features(self.font_features.iter().copied()) + .variations(vars.iter().copied()) + .build(); + + shaper.add_str(&self.word_cache.content); + + self.metrics_cache + .inner + .entry(item.style.font_id) + .or_insert_with(|| shaper.metrics()); + + render_data.push_run( + item.style, + self.state.font_size, + line_number as u32, + shaper, + &mut self.word_cache, + ); + } } } } } pub struct WordCache { - pub inner: LruCache>, + pub inner: FxHashMap>>, stash: Vec, - pub key: String, + font_id: usize, + content: String, } impl WordCache { pub fn new() -> Self { WordCache { - inner: LruCache::new(NonZeroUsize::new(768).unwrap()), + inner: FxHashMap::default(), stash: vec![], - key: String::new(), + font_id: 0, + content: String::new(), + } + } + + #[inline] + pub fn get( + &mut self, + font_id: &usize, + content: &String, + ) -> Option<&Vec> { + if let Some(cache) = self.inner.get_mut(font_id) { + return cache.get(content); } + None } #[inline] @@ -356,19 +373,30 @@ impl WordCache { #[inline] pub fn finish(&mut self) { - // println!("{:?} {:?}", self.key, self.inner.len()); - if !self.key.is_empty() - && !self.stash.is_empty() - && self.inner.get(&self.key).is_none() - { - self.inner.put( - std::mem::take(&mut self.key), - std::mem::take(&mut self.stash), - ); + if !self.content.is_empty() && !self.stash.is_empty() { + if let Some(cache) = self.inner.get_mut(&self.font_id) { + // println!("{:?} {:?}", self.content, cache.len()); + cache.put( + std::mem::take(&mut self.content), + std::mem::take(&mut self.stash), + ); + } else { + // If font id is main + let size = if self.font_id == 0 { 384 } else { 128 }; + let mut cache = LruCache::new(NonZeroUsize::new(size).unwrap()); + cache.put( + std::mem::take(&mut self.content), + std::mem::take(&mut self.stash), + ); + self.inner.insert(self.font_id, cache); + } + + self.font_id = 0; return; } self.stash.clear(); - self.key.clear(); + self.font_id = 0; + self.content.clear(); } } diff --git a/sugarloaf/src/layout/mod.rs b/sugarloaf/src/layout/mod.rs index 765e83de8a..beea5a1c07 100644 --- a/sugarloaf/src/layout/mod.rs +++ b/sugarloaf/src/layout/mod.rs @@ -7,7 +7,7 @@ // nav and span_style were originally retired from dfrg/swash_demo licensed under MIT // https://github.com/dfrg/swash_demo/blob/master/LICENSE -mod builder; +mod content; mod layout_data; mod render_data; @@ -18,12 +18,12 @@ pub mod iter { pub use super::render_data::{Clusters, Glyphs, Lines, Runs}; } -pub use builder::{ +pub use content::{ Content, FragmentStyle, FragmentStyleDecoration, UnderlineInfo, UnderlineShape, }; pub use render_data::{Cluster, Glyph, Line, Run}; -/// Index of a span in sequential order of submission to a paragraph builder. +/// Index of a span in sequential order of submission to a paragraph content. #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Default, Debug)] pub struct SpanId(pub usize); diff --git a/sugarloaf/src/layout/render_data.rs b/sugarloaf/src/layout/render_data.rs index 58f16370c3..2d0c564e46 100644 --- a/sugarloaf/src/layout/render_data.rs +++ b/sugarloaf/src/layout/render_data.rs @@ -16,7 +16,7 @@ use crate::font_introspector::shape::{cluster::Glyph as ShapedGlyph, Shaper}; use crate::font_introspector::text::cluster::ClusterInfo; use crate::font_introspector::GlyphId; use crate::font_introspector::Metrics; -use crate::layout::builder::{FragmentStyleDecoration, WordCache}; +use crate::layout::content::{FragmentStyleDecoration, WordCache}; use crate::layout::FragmentStyle; use crate::sugarloaf::primitives::SugarCursor; use crate::{Graphic, GraphicId};