From 9f8d3918343d083e5bc1943214f372b7c7d810cb Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Sun, 15 Sep 2024 15:43:09 +0200 Subject: [PATCH 01/12] implement get for font_library --- frontends/rioterm/src/screen/mod.rs | 8 +- .../components/rich_text/image_cache/glyph.rs | 4 +- sugarloaf/src/font/mod.rs | 109 ++++++++---------- sugarloaf/src/layout/builder.rs | 4 +- 4 files changed, 57 insertions(+), 68 deletions(-) diff --git a/frontends/rioterm/src/screen/mod.rs b/frontends/rioterm/src/screen/mod.rs index 1aeeb1ceb7..5bac5f4e9f 100644 --- a/frontends/rioterm/src/screen/mod.rs +++ b/frontends/rioterm/src/screen/mod.rs @@ -1901,8 +1901,8 @@ impl Screen<'_> { #[inline] pub fn render(&mut self) { - // let start_total = std::time::Instant::now(); - // println!("_____________________________\nrender time elapsed"); + let start_total = std::time::Instant::now(); + println!("_____________________________\nrender time elapsed"); let is_search_active = self.search_active(); if is_search_active { if let Some(history_index) = self.search_state.history_index { @@ -1967,7 +1967,7 @@ impl Screen<'_> { self.context_manager.schedule_render_on_route(800); } - // let duration = start_total.elapsed(); - // println!("Total whole render function is: {:?}\n", duration); + let duration = start_total.elapsed(); + println!("Total whole render function is: {:?}\n", duration); } } diff --git a/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs index dc572ed140..56ac2d0466 100644 --- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs +++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs @@ -124,10 +124,10 @@ impl<'a> GlyphCacheSession<'a> { } self.scaled_image.data.clear(); - let font_library_data = self.font_library.inner.lock(); + let mut font_library_data = self.font_library.inner.lock(); let mut scaler = self .scale_context - .builder(font_library_data[self.font].as_ref()) + .builder(font_library_data.get(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. diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index 2609df37fd..1146183ff7 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -18,7 +18,7 @@ use crate::SugarloafErrors; use ab_glyph::FontArc; use parking_lot::FairMutex; use rustc_hash::FxHashMap; -use std::ops::{Index, IndexMut}; +// use std::ops::{Index, IndexMut}; use std::path::PathBuf; use std::sync::Arc; @@ -27,16 +27,11 @@ 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 { @@ -52,11 +47,11 @@ pub fn lookup_for_font_match( } } - let charmap = font_ref.charmap(); + let charmap = font.as_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)); + *synth = library.get(current_font_id).synth; + font_id = Some((current_font_id, library.get(current_font_id).is_emoji)); break; } } @@ -106,16 +101,10 @@ 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 inner: Vec, pub cache: FxHashMap, } @@ -123,7 +112,6 @@ 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(), } @@ -180,7 +168,12 @@ impl FontLibraryData { #[inline] pub fn insert(&mut self, font_data: FontData) { - self.inner.push(FontSource::Data(font_data)); + self.inner.push(font_data); + } + + pub fn get(&mut self, font_id: usize) -> &FontData { + println!("font_id required {}", font_id); + &self.inner[font_id] } #[inline] @@ -197,7 +190,7 @@ impl FontLibraryData { 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); + *font_data = loaded_font_data; }; } } @@ -219,12 +212,10 @@ impl FontLibraryData { match find_font(&db, spec.regular) { FindResult::Found(data) => { - self.standard = data; - self.inner = vec![FontSource::Standard]; + self.inner.push(data); } FindResult::NotFound(spec) => { - self.standard = load_fallback_from_memory(&spec); - self.inner = vec![FontSource::Standard]; + self.inner.push(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -233,11 +224,11 @@ impl FontLibraryData { match find_font(&db, spec.italic) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + .push(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -246,11 +237,11 @@ impl FontLibraryData { match find_font(&db, spec.bold) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + .push(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -259,11 +250,11 @@ impl FontLibraryData { match find_font(&db, spec.bold_italic) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { self.inner - .push(FontSource::Data(load_fallback_from_memory(&spec))); + .push(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -279,7 +270,7 @@ impl FontLibraryData { }, ) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { // Fallback should not add errors @@ -291,24 +282,24 @@ impl FontLibraryData { if let Some(emoji_font) = spec.emoji { match find_font(&db, emoji_font) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { - self.inner.push(FontSource::Data( + self.inner.push( FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), - )); + ); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } } else { - self.inner.push(FontSource::Data( + self.inner.push( 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() { + if let Some(ref mut font_ref) = self.inner.last_mut() { font_ref.is_emoji = true; } @@ -322,7 +313,7 @@ impl FontLibraryData { }, ) { FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); + self.inner.push(data); } FindResult::NotFound(spec) => { fonts_not_fount.push(spec); @@ -330,9 +321,7 @@ impl FontLibraryData { } } - self.inner.push(FontSource::Data( - FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap(), - )); + self.inner.push(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap()); if let Some(ui_spec) = spec.ui { match find_font(&db, ui_spec) { @@ -357,25 +346,25 @@ impl FontLibraryData { } } -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, - } - } -} +// 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)] diff --git a/sugarloaf/src/layout/builder.rs b/sugarloaf/src/layout/builder.rs index 59c392332d..be3ddf08af 100644 --- a/sugarloaf/src/layout/builder.rs +++ b/sugarloaf/src/layout/builder.rs @@ -304,11 +304,11 @@ impl Content { } self.word_cache.key = shaper_key.to_owned(); - let font_library = { &self.fonts.inner.lock() }; + let font_library = { &mut self.fonts.inner.lock() }; let mut shaper = self .scx - .builder(font_library[item.style.font_id].as_ref()) + .builder(font_library.get(item.style.font_id).as_ref()) .script(script) .size(self.state.font_size) .features(self.font_features.iter().copied()) From 4baf8a56f5e0637a508eef91e71bbd7d8d493f87 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Sun, 15 Sep 2024 21:45:18 +0200 Subject: [PATCH 02/12] apply corrections on renderer and use hashmap for font --- frontends/rioterm/src/renderer/mod.rs | 33 +- .../components/rich_text/image_cache/glyph.rs | 2 +- sugarloaf/src/font/fallbacks/mod.rs | 4 +- sugarloaf/src/font/mod.rs | 76 ++-- sugarloaf/src/layout/builder.rs | 378 ------------------ sugarloaf/src/layout/mod.rs | 6 +- sugarloaf/src/layout/render_data.rs | 2 +- 7 files changed, 61 insertions(+), 440 deletions(-) delete mode 100644 sugarloaf/src/layout/builder.rs 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/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs index 56ac2d0466..9d97938e6e 100644 --- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs +++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs @@ -127,7 +127,7 @@ impl<'a> GlyphCacheSession<'a> { let mut font_library_data = self.font_library.inner.lock(); let mut scaler = self .scale_context - .builder(font_library_data.get(self.font).as_ref()) + .builder(font_library_data.get(&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. 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 1146183ff7..8bd100e18e 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -31,7 +31,7 @@ pub fn lookup_for_font_match( 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() { + for (current_font_id, font) in library.inner.iter() { // 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 { @@ -50,8 +50,9 @@ pub fn lookup_for_font_match( let charmap = font.as_ref().charmap(); let status = cluster.map(|ch| charmap.map(ch)); if status != Status::Discard { - *synth = library.get(current_font_id).synth; - font_id = Some((current_font_id, library.get(current_font_id).is_emoji)); + let current_font_id = *current_font_id; + *synth = library.get(¤t_font_id).synth; + font_id = Some((current_font_id, library.get(¤t_font_id).is_emoji)); break; } } @@ -104,16 +105,14 @@ impl Default for FontLibrary { pub struct FontLibraryData { pub ui: FontArc, // Standard is fallback for everything, it is also the inner number 0 - pub inner: Vec, - pub cache: FxHashMap, + pub inner: FxHashMap, } impl Default for FontLibraryData { fn default() -> Self { Self { ui: FontArc::try_from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), - inner: vec![], - cache: FxHashMap::default(), + inner: FxHashMap::default(), } } } @@ -167,15 +166,21 @@ impl FontLibraryData { } #[inline] - pub fn insert(&mut self, font_data: FontData) { - self.inner.push(font_data); + pub fn insert(&mut self, font_data: FontData) -> usize { + let id = self.inner.len(); + self.inner.insert(id, font_data); + id } - pub fn get(&mut self, font_id: usize) -> &FontData { + pub fn get(&mut self, font_id: &usize) -> &FontData { println!("font_id required {}", font_id); &self.inner[font_id] } + pub fn get_mut(&mut self, font_id: &usize) -> Option<&mut FontData> { + self.inner.get_mut(font_id) + } + #[inline] pub fn len(&self) -> usize { self.inner.len() @@ -188,7 +193,7 @@ impl FontLibraryData { #[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(font_data) = self.inner.get_mut(&font_id) { if let Some(loaded_font_data) = load_from_font_source(&path) { *font_data = loaded_font_data; }; @@ -212,10 +217,10 @@ impl FontLibraryData { match find_font(&db, spec.regular) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner.push(load_fallback_from_memory(&spec)); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -224,11 +229,10 @@ impl FontLibraryData { match find_font(&db, spec.italic) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(load_fallback_from_memory(&spec)); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -237,11 +241,10 @@ impl FontLibraryData { match find_font(&db, spec.bold) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(load_fallback_from_memory(&spec)); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -250,11 +253,10 @@ impl FontLibraryData { match find_font(&db, spec.bold_italic) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { - self.inner - .push(load_fallback_from_memory(&spec)); + self.insert(load_fallback_from_memory(&spec)); if !spec.is_default_family() { fonts_not_fount.push(spec); } @@ -270,7 +272,7 @@ impl FontLibraryData { }, ) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { // Fallback should not add errors @@ -282,25 +284,27 @@ impl FontLibraryData { if let Some(emoji_font) = spec.emoji { match find_font(&db, emoji_font) { FindResult::Found(data) => { - self.inner.push(data); + let id = self.insert(data); + if let Some(ref mut font_ref) = self.inner.get_mut(&id) { + font_ref.is_emoji = true; + } } FindResult::NotFound(spec) => { - self.inner.push( - FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), - ); + let id = + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); + if let Some(ref mut font_ref) = self.inner.get_mut(&id) { + font_ref.is_emoji = true; + } if !spec.is_default_family() { fonts_not_fount.push(spec); } } } } else { - self.inner.push( - FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), - ); - } - // Set last font as emoji - if let Some(ref mut font_ref) = self.inner.last_mut() { - font_ref.is_emoji = true; + let id = self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); + if let Some(ref mut font_ref) = self.inner.get_mut(&id) { + font_ref.is_emoji = true; + } } for extra_font in spec.extras { @@ -313,7 +317,7 @@ impl FontLibraryData { }, ) { FindResult::Found(data) => { - self.inner.push(data); + self.insert(data); } FindResult::NotFound(spec) => { fonts_not_fount.push(spec); @@ -321,7 +325,7 @@ impl FontLibraryData { } } - self.inner.push(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap()); + self.insert(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap()); if let Some(ui_spec) = spec.ui { match find_font(&db, ui_spec) { diff --git a/sugarloaf/src/layout/builder.rs b/sugarloaf/src/layout/builder.rs deleted file mode 100644 index be3ddf08af..0000000000 --- a/sugarloaf/src/layout/builder.rs +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright (c) 2023-present, Raphael Amorim. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use crate::font::FontLibrary; -use crate::font_introspector::shape::cluster::GlyphCluster; -use crate::font_introspector::shape::cluster::OwnedGlyphCluster; -use crate::font_introspector::shape::ShapeContext; -use crate::font_introspector::text::Script; -use crate::font_introspector::Metrics; -use crate::layout::render_data::RenderData; -use lru::LruCache; -use rustc_hash::FxHashMap; -use std::num::NonZeroUsize; - -use crate::font_introspector::Attributes; -use crate::font_introspector::Setting; -use crate::{sugarloaf::primitives::SugarCursor, Graphic}; - -/// Data that describes a fragment. -#[derive(Debug, Clone)] -pub struct FragmentData { - pub content: String, - /// Style - pub style: FragmentStyle, -} - -#[derive(Default)] -pub struct BuilderLine { - /// Collection of fragments. - pub fragments: Vec, -} - -/// Builder state. -#[derive(Default)] -pub struct BuilderState { - /// Lines State - pub lines: Vec, - /// Font variation setting cache. - pub vars: FontSettingCache, - /// User specified scale. - pub scale: f32, - // Font size in ppem. - pub font_size: f32, -} - -impl BuilderState { - /// Creates a new layout state. - pub fn new() -> Self { - let lines = vec![BuilderLine::default()]; - Self { - lines, - ..BuilderState::default() - } - } - #[inline] - pub fn new_line(&mut self) { - self.lines.push(BuilderLine::default()); - } - #[inline] - pub fn current_line(&self) -> usize { - let size = self.lines.len(); - if size == 0 { - 0 - } else { - size - 1 - } - } - #[inline] - pub fn clear(&mut self) { - self.lines.clear(); - self.vars.clear(); - } - - #[inline] - pub fn begin(&mut self) { - self.lines.push(BuilderLine::default()); - } -} - -/// Index into a font setting cache. -pub type FontSettingKey = u32; - -/// Cache of tag/value pairs for font settings. -#[derive(Default)] -pub struct FontSettingCache { - settings: Vec>, - lists: Vec, - tmp: Vec>, -} - -impl FontSettingCache { - pub fn get(&self, key: u32) -> &[Setting] { - if key == !0 { - &[] - } else { - self.lists - .get(key as usize) - .map(|list| list.get(&self.settings)) - .unwrap_or(&[]) - } - } - - pub fn clear(&mut self) { - self.settings.clear(); - self.lists.clear(); - self.tmp.clear(); - } -} - -/// Sentinel for an empty set of font settings. -pub const EMPTY_FONT_SETTINGS: FontSettingKey = !0; - -/// Range within a font setting cache. -#[derive(Copy, Clone)] -struct FontSettingList { - pub start: u32, - pub end: u32, -} - -impl FontSettingList { - pub fn get(self, elements: &[T]) -> &[T] { - elements - .get(self.start as usize..self.end as usize) - .unwrap_or(&[]) - } -} - -#[repr(u8)] -#[derive(Copy, Clone, PartialEq, Debug, Default)] -pub enum UnderlineShape { - #[default] - Regular = 0, - Dotted = 1, - Dashed = 2, - Curly = 3, -} - -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct UnderlineInfo { - pub offset: f32, - pub size: f32, - pub is_doubled: bool, - pub shape: UnderlineShape, -} - -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum FragmentStyleDecoration { - // offset, size - Underline(UnderlineInfo), - Strikethrough, -} - -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct FragmentStyle { - pub font_id: usize, - // Unicode width - pub width: f32, - /// Font attributes. - pub font_attrs: Attributes, - /// Font color. - pub color: [f32; 4], - /// Background color. - pub background_color: Option<[f32; 4]>, - /// Font variations. - pub font_vars: FontSettingKey, - /// Additional spacing between letters (clusters) of text. - // pub letter_spacing: f32, - /// Additional spacing between words of text. - // pub word_spacing: f32, - /// Multiplicative line spacing factor. - // pub line_spacing: f32, - /// Enable underline decoration. - pub decoration: Option, - /// Decoration color. - pub decoration_color: Option<[f32; 4]>, - /// Cursor style. - pub cursor: Option, - /// Media - pub media: Option, -} - -impl Default for FragmentStyle { - fn default() -> Self { - Self { - font_id: 0, - width: 1.0, - font_attrs: Attributes::default(), - font_vars: EMPTY_FONT_SETTINGS, - // letter_spacing: 0., - // word_spacing: 0., - // line_spacing: 1., - color: [1.0, 1.0, 1.0, 1.0], - background_color: None, - cursor: None, - decoration: None, - decoration_color: None, - media: None, - } - } -} - -/// Context for paragraph layout. -pub struct Content { - fonts: FontLibrary, - font_features: Vec>, - scx: ShapeContext, - state: BuilderState, - word_cache: WordCache, - metrics_cache: MetricsCache, -} - -impl Content { - /// Creates a new layout context with the specified font library. - pub fn new(font_library: &FontLibrary) -> Self { - Self { - fonts: font_library.clone(), - scx: ShapeContext::new(), - state: BuilderState::new(), - word_cache: WordCache::new(), - font_features: vec![], - metrics_cache: MetricsCache::default(), - } - } - - #[inline] - pub fn font_library(&self) -> &FontLibrary { - &self.fonts - } - - #[inline] - pub fn set_font_features( - &mut self, - font_features: Vec>, - ) { - self.font_features = font_features; - } - - #[inline] - pub fn build(&mut self, scale: f32, font_size: f32) { - self.state.clear(); - self.state.begin(); - let prev_font_size = self.state.font_size; - self.state.scale = scale; - self.state.font_size = font_size * scale; - - if prev_font_size != self.state.font_size { - self.metrics_cache.inner.clear(); - } - // ContentBuilder { - // font_features: &self.font_features, - // fonts: &self.fonts, - // scx: &mut self.scx, - // s: &mut self.state, - // word_cache: &mut self.word_cache, - // metrics_cache: &mut self.metrics_cache, - // } - } - - #[inline] - pub fn new_line(&mut self) { - self.state.new_line(); - } - - /// Adds a text fragment to the paragraph. - pub fn add_text(&mut self, text: &str, style: FragmentStyle) -> Option<()> { - let current_line = self.state.current_line(); - let line = &mut self.state.lines[current_line]; - - line.fragments.push(FragmentData { - content: text.to_string(), - style, - }); - - Some(()) - } - - #[inline] - pub fn resolve(&mut self, render_data: &mut RenderData) { - let script = Script::Latin; - for line_number in 0..self.state.lines.len() { - let line = &self.state.lines[line_number]; - for item in &line.fragments { - let vars = self.state.vars.get(item.style.font_vars); - let shaper_key = &item.content; - - // println!("{:?} -> {:?}", item.style.font_id, shaper_key); - - if let Some(shaper) = self.word_cache.inner.get(shaper_key) { - if let Some(metrics) = - self.metrics_cache.inner.get(&item.style.font_id) - { - if render_data.push_run_without_shaper( - item.style, - self.state.font_size, - line_number as u32, - shaper, - metrics, - ) { - continue; - } - } - } - - self.word_cache.key = shaper_key.to_owned(); - let font_library = { &mut self.fonts.inner.lock() }; - - let mut shaper = self - .scx - .builder(font_library.get(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, - ); - } - } - } -} - -pub struct WordCache { - pub inner: LruCache>, - stash: Vec, - pub key: String, -} - -impl WordCache { - pub fn new() -> Self { - WordCache { - inner: LruCache::new(NonZeroUsize::new(768).unwrap()), - stash: vec![], - key: String::new(), - } - } - - #[inline] - pub fn add_glyph_cluster(&mut self, glyph_cluster: &GlyphCluster) { - self.stash.push(glyph_cluster.into()); - } - - #[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), - ); - return; - } - self.stash.clear(); - self.key.clear(); - } -} - -#[derive(Default)] -struct MetricsCache { - pub inner: FxHashMap, -} 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}; From 9132af2ed210bb3f9eff8b28272ad1f0f2164e65 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Sun, 15 Sep 2024 21:45:58 +0200 Subject: [PATCH 03/12] add content.rs --- sugarloaf/src/layout/content.rs | 378 ++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 sugarloaf/src/layout/content.rs diff --git a/sugarloaf/src/layout/content.rs b/sugarloaf/src/layout/content.rs new file mode 100644 index 0000000000..2e8d62711f --- /dev/null +++ b/sugarloaf/src/layout/content.rs @@ -0,0 +1,378 @@ +// Copyright (c) 2023-present, Raphael Amorim. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use crate::font::FontLibrary; +use crate::font_introspector::shape::cluster::GlyphCluster; +use crate::font_introspector::shape::cluster::OwnedGlyphCluster; +use crate::font_introspector::shape::ShapeContext; +use crate::font_introspector::text::Script; +use crate::font_introspector::Metrics; +use crate::layout::render_data::RenderData; +use lru::LruCache; +use rustc_hash::FxHashMap; +use std::num::NonZeroUsize; + +use crate::font_introspector::Attributes; +use crate::font_introspector::Setting; +use crate::{sugarloaf::primitives::SugarCursor, Graphic}; + +/// Data that describes a fragment. +#[derive(Debug, Clone)] +pub struct FragmentData { + pub content: String, + /// Style + pub style: FragmentStyle, +} + +#[derive(Default)] +pub struct BuilderLine { + /// Collection of fragments. + pub fragments: Vec, +} + +/// Builder state. +#[derive(Default)] +pub struct BuilderState { + /// Lines State + pub lines: Vec, + /// Font variation setting cache. + pub vars: FontSettingCache, + /// User specified scale. + pub scale: f32, + // Font size in ppem. + pub font_size: f32, +} + +impl BuilderState { + /// Creates a new layout state. + pub fn new() -> Self { + let lines = vec![BuilderLine::default()]; + Self { + lines, + ..BuilderState::default() + } + } + #[inline] + pub fn new_line(&mut self) { + self.lines.push(BuilderLine::default()); + } + #[inline] + pub fn current_line(&self) -> usize { + let size = self.lines.len(); + if size == 0 { + 0 + } else { + size - 1 + } + } + #[inline] + pub fn clear(&mut self) { + self.lines.clear(); + self.vars.clear(); + } + + #[inline] + pub fn begin(&mut self) { + self.lines.push(BuilderLine::default()); + } +} + +/// Index into a font setting cache. +pub type FontSettingKey = u32; + +/// Cache of tag/value pairs for font settings. +#[derive(Default)] +pub struct FontSettingCache { + settings: Vec>, + lists: Vec, + tmp: Vec>, +} + +impl FontSettingCache { + pub fn get(&self, key: u32) -> &[Setting] { + if key == !0 { + &[] + } else { + self.lists + .get(key as usize) + .map(|list| list.get(&self.settings)) + .unwrap_or(&[]) + } + } + + pub fn clear(&mut self) { + self.settings.clear(); + self.lists.clear(); + self.tmp.clear(); + } +} + +/// Sentinel for an empty set of font settings. +pub const EMPTY_FONT_SETTINGS: FontSettingKey = !0; + +/// Range within a font setting cache. +#[derive(Copy, Clone)] +struct FontSettingList { + pub start: u32, + pub end: u32, +} + +impl FontSettingList { + pub fn get(self, elements: &[T]) -> &[T] { + elements + .get(self.start as usize..self.end as usize) + .unwrap_or(&[]) + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Debug, Default)] +pub enum UnderlineShape { + #[default] + Regular = 0, + Dotted = 1, + Dashed = 2, + Curly = 3, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct UnderlineInfo { + pub offset: f32, + pub size: f32, + pub is_doubled: bool, + pub shape: UnderlineShape, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FragmentStyleDecoration { + // offset, size + Underline(UnderlineInfo), + Strikethrough, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct FragmentStyle { + pub font_id: usize, + // Unicode width + pub width: f32, + /// Font attributes. + pub font_attrs: Attributes, + /// Font color. + pub color: [f32; 4], + /// Background color. + pub background_color: Option<[f32; 4]>, + /// Font variations. + pub font_vars: FontSettingKey, + /// Additional spacing between letters (clusters) of text. + // pub letter_spacing: f32, + /// Additional spacing between words of text. + // pub word_spacing: f32, + /// Multiplicative line spacing factor. + // pub line_spacing: f32, + /// Enable underline decoration. + pub decoration: Option, + /// Decoration color. + pub decoration_color: Option<[f32; 4]>, + /// Cursor style. + pub cursor: Option, + /// Media + pub media: Option, +} + +impl Default for FragmentStyle { + fn default() -> Self { + Self { + font_id: 0, + width: 1.0, + font_attrs: Attributes::default(), + font_vars: EMPTY_FONT_SETTINGS, + // letter_spacing: 0., + // word_spacing: 0., + // line_spacing: 1., + color: [1.0, 1.0, 1.0, 1.0], + background_color: None, + cursor: None, + decoration: None, + decoration_color: None, + media: None, + } + } +} + +/// Context for paragraph layout. +pub struct Content { + fonts: FontLibrary, + font_features: Vec>, + scx: ShapeContext, + state: BuilderState, + word_cache: WordCache, + metrics_cache: MetricsCache, +} + +impl Content { + /// Creates a new layout context with the specified font library. + pub fn new(font_library: &FontLibrary) -> Self { + Self { + fonts: font_library.clone(), + scx: ShapeContext::new(), + state: BuilderState::new(), + word_cache: WordCache::new(), + font_features: vec![], + metrics_cache: MetricsCache::default(), + } + } + + #[inline] + pub fn font_library(&self) -> &FontLibrary { + &self.fonts + } + + #[inline] + pub fn set_font_features( + &mut self, + font_features: Vec>, + ) { + self.font_features = font_features; + } + + #[inline] + pub fn build(&mut self, scale: f32, font_size: f32) { + self.state.clear(); + self.state.begin(); + let prev_font_size = self.state.font_size; + self.state.scale = scale; + self.state.font_size = font_size * scale; + + if prev_font_size != self.state.font_size { + self.metrics_cache.inner.clear(); + } + // ContentBuilder { + // font_features: &self.font_features, + // fonts: &self.fonts, + // scx: &mut self.scx, + // s: &mut self.state, + // word_cache: &mut self.word_cache, + // metrics_cache: &mut self.metrics_cache, + // } + } + + #[inline] + pub fn new_line(&mut self) { + self.state.new_line(); + } + + /// Adds a text fragment to the paragraph. + pub fn add_text(&mut self, text: &str, style: FragmentStyle) -> Option<()> { + let current_line = self.state.current_line(); + let line = &mut self.state.lines[current_line]; + + line.fragments.push(FragmentData { + content: text.to_string(), + style, + }); + + Some(()) + } + + #[inline] + pub fn resolve(&mut self, render_data: &mut RenderData) { + let script = Script::Latin; + for line_number in 0..self.state.lines.len() { + let line = &self.state.lines[line_number]; + for item in &line.fragments { + let vars = self.state.vars.get(item.style.font_vars); + let shaper_key = &item.content; + + // println!("{:?} -> {:?}", item.style.font_id, shaper_key); + + if let Some(shaper) = self.word_cache.inner.get(shaper_key) { + if let Some(metrics) = + self.metrics_cache.inner.get(&item.style.font_id) + { + if render_data.push_run_without_shaper( + item.style, + self.state.font_size, + line_number as u32, + shaper, + metrics, + ) { + continue; + } + } + } + + self.word_cache.key = shaper_key.to_owned(); + let font_library = { &mut self.fonts.inner.lock() }; + + let mut shaper = self + .scx + .builder(font_library.get(&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, + ); + } + } + } +} + +pub struct WordCache { + pub inner: LruCache>, + stash: Vec, + pub key: String, +} + +impl WordCache { + pub fn new() -> Self { + WordCache { + inner: LruCache::new(NonZeroUsize::new(768).unwrap()), + stash: vec![], + key: String::new(), + } + } + + #[inline] + pub fn add_glyph_cluster(&mut self, glyph_cluster: &GlyphCluster) { + self.stash.push(glyph_cluster.into()); + } + + #[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), + ); + return; + } + self.stash.clear(); + self.key.clear(); + } +} + +#[derive(Default)] +struct MetricsCache { + pub inner: FxHashMap, +} From d0f7af9cb061a96d34a342599d271c928a6e7b27 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 12:17:36 +0200 Subject: [PATCH 04/12] reduce SIZE of atlas to 1024 (it was consuming a lot of memory) --- sugarloaf/src/components/rich_text/image_cache/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugarloaf/src/components/rich_text/image_cache/cache.rs b/sugarloaf/src/components/rich_text/image_cache/cache.rs index f686db78bd..672fc3da48 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 = 1024; impl ImageCache { /// Creates a new image cache. From 0bf2bd93b4ec001bdde3b634e13a4ad8b634cbca Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 16:13:11 +0200 Subject: [PATCH 05/12] font deallocation --- .../components/rich_text/image_cache/glyph.rs | 137 +++++----- sugarloaf/src/font/mod.rs | 247 +++++++++--------- sugarloaf/src/layout/content.rs | 49 ++-- 3 files changed, 220 insertions(+), 213 deletions(-) diff --git a/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs index 9d97938e6e..03d9598e3b 100644 --- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs +++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs @@ -125,79 +125,82 @@ impl<'a> GlyphCacheSession<'a> { self.scaled_image.data.clear(); let mut font_library_data = self.font_library.inner.lock(); - let mut scaler = self - .scale_context - .builder(font_library_data.get(&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 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/mod.rs b/sugarloaf/src/font/mod.rs index 8bd100e18e..3ed9a1ccf1 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; @@ -30,40 +31,54 @@ pub fn lookup_for_font_match( 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() { - // 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 keys: Vec = library.inner.clone().into_keys().collect(); + + for font_id in keys { + let mut is_emoji = false; + + if let Some(font) = library.inner.get(&font_id) { + is_emoji = font.is_emoji; + font_synth = font.synth; + + println!("{:?} {:?}", font_id, font.data.is_none()); - // 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 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; + } } } - let charmap = font.as_ref().charmap(); - let status = cluster.map(|ch| charmap.map(ch)); - if status != Status::Discard { - let current_font_id = *current_font_id; - *synth = library.get(¤t_font_id).synth; - font_id = Some((current_font_id, library.get(¤t_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,6 +121,7 @@ pub struct FontLibraryData { pub ui: FontArc, // Standard is fallback for everything, it is also the inner number 0 pub inner: FxHashMap, + pub stash: LruCache, } impl Default for FontLibraryData { @@ -113,6 +129,7 @@ impl Default for FontLibraryData { Self { ui: FontArc::try_from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), inner: FxHashMap::default(), + stash: LruCache::new(NonZeroUsize::new(2).unwrap()), } } } @@ -166,17 +183,49 @@ impl FontLibraryData { } #[inline] - pub fn insert(&mut self, font_data: FontData) -> usize { - let id = self.inner.len(); - self.inner.insert(id, font_data); - id + pub fn insert(&mut self, font_data: FontData) { + self.inner.insert(self.inner.len(), font_data); } + #[inline] pub fn get(&mut self, font_id: &usize) -> &FontData { - println!("font_id required {}", font_id); &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) } @@ -191,15 +240,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 = loaded_font_data; - }; - } - } - #[cfg(not(target_arch = "wasm32"))] pub fn load(&mut self, mut spec: SugarloafFonts) -> Vec { let mut fonts_not_fount: Vec = vec![]; @@ -215,7 +255,7 @@ 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.insert(data); } @@ -227,7 +267,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.italic) { + match find_font(&db, spec.italic, false, false) { FindResult::Found(data) => { self.insert(data); } @@ -239,7 +279,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.bold) { + match find_font(&db, spec.bold, false, false) { FindResult::Found(data) => { self.insert(data); } @@ -251,7 +291,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.bold_italic) { + match find_font(&db, spec.bold_italic, false, false) { FindResult::Found(data) => { self.insert(data); } @@ -270,6 +310,8 @@ impl FontLibraryData { family: fallback, ..SugarloafFont::default() }, + true, + false, ) { FindResult::Found(data) => { self.insert(data); @@ -282,29 +324,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) => { - let id = self.insert(data); - if let Some(ref mut font_ref) = self.inner.get_mut(&id) { - font_ref.is_emoji = true; - } + self.insert(data); } FindResult::NotFound(spec) => { - let id = - self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); - if let Some(ref mut font_ref) = self.inner.get_mut(&id) { - font_ref.is_emoji = true; - } + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); if !spec.is_default_family() { fonts_not_fount.push(spec); } } } } else { - let id = self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); - if let Some(ref mut font_ref) = self.inner.get_mut(&id) { - font_ref.is_emoji = true; - } + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); } for extra_font in spec.extras { @@ -315,6 +347,8 @@ impl FontLibraryData { style: extra_font.style, weight: extra_font.weight, }, + true, + true, ) { FindResult::Found(data) => { self.insert(data); @@ -328,9 +362,9 @@ impl FontLibraryData { self.insert(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).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); @@ -350,26 +384,6 @@ impl FontLibraryData { } } -// 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 { @@ -402,7 +416,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 @@ -422,15 +437,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); @@ -442,15 +456,22 @@ 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, }) } @@ -467,37 +488,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, + path: None, is_emoji: false, }) } - - // 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; @@ -521,7 +522,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() { @@ -554,7 +560,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 {}", @@ -637,21 +648,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/content.rs b/sugarloaf/src/layout/content.rs index 2e8d62711f..67611b28f8 100644 --- a/sugarloaf/src/layout/content.rs +++ b/sugarloaf/src/layout/content.rs @@ -305,30 +305,31 @@ impl Content { self.word_cache.key = shaper_key.to_owned(); let font_library = { &mut self.fonts.inner.lock() }; - - let mut shaper = self - .scx - .builder(font_library.get(&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, - ); + 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.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, + ); + } } } } From 4156ea9020d8027d20911576cc38a9352d92407c Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 17:31:49 +0200 Subject: [PATCH 06/12] add is_emoji to from_slice --- frontends/rioterm/src/screen/mod.rs | 8 ++++---- sugarloaf/src/font/mod.rs | 23 ++++++++++++----------- sugarloaf/src/layout/content.rs | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/frontends/rioterm/src/screen/mod.rs b/frontends/rioterm/src/screen/mod.rs index 5bac5f4e9f..1aeeb1ceb7 100644 --- a/frontends/rioterm/src/screen/mod.rs +++ b/frontends/rioterm/src/screen/mod.rs @@ -1901,8 +1901,8 @@ impl Screen<'_> { #[inline] pub fn render(&mut self) { - let start_total = std::time::Instant::now(); - println!("_____________________________\nrender time elapsed"); + // let start_total = std::time::Instant::now(); + // println!("_____________________________\nrender time elapsed"); let is_search_active = self.search_active(); if is_search_active { if let Some(history_index) = self.search_state.history_index { @@ -1967,7 +1967,7 @@ impl Screen<'_> { self.context_manager.schedule_render_on_route(800); } - let duration = start_total.elapsed(); - println!("Total whole render function is: {:?}\n", duration); + // let duration = start_total.elapsed(); + // println!("Total whole render function is: {:?}\n", duration); } } diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index 3ed9a1ccf1..ff5cb193fd 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -33,17 +33,15 @@ pub fn lookup_for_font_match( ) -> Option<(usize, bool)> { let mut search_result = None; let mut font_synth = Synthesis::default(); - let keys: Vec = library.inner.clone().into_keys().collect(); + let fonts_len: usize = library.inner.len(); - for font_id in keys { + 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; - println!("{:?} {:?}", font_id, font.data.is_none()); - // 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 { @@ -329,14 +327,14 @@ impl FontLibraryData { self.insert(data); } FindResult::NotFound(spec) => { - self.insert(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.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap()); + self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap()); } for extra_font in spec.extras { @@ -359,7 +357,7 @@ impl FontLibraryData { } } - self.insert(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, false, false) { @@ -378,7 +376,7 @@ 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![] } @@ -476,7 +474,10 @@ impl FontData { } #[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 @@ -496,7 +497,7 @@ impl FontData { weight, stretch, path: None, - is_emoji: false, + is_emoji, }) } } @@ -621,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)] diff --git a/sugarloaf/src/layout/content.rs b/sugarloaf/src/layout/content.rs index 67611b28f8..5f746a0f98 100644 --- a/sugarloaf/src/layout/content.rs +++ b/sugarloaf/src/layout/content.rs @@ -357,7 +357,7 @@ impl WordCache { #[inline] pub fn finish(&mut self) { - println!("{:?} {:?}", self.key, self.inner.len()); + // println!("{:?} {:?}", self.key, self.inner.len()); if !self.key.is_empty() && !self.stash.is_empty() && self.inner.get(&self.key).is_none() From 66d94c8d39eecd500c6e3e47ce8fa3ddfdf1202b Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 19:40:56 +0200 Subject: [PATCH 07/12] add lifetimes to application --- frontends/rioterm/src/application.rs | 12 +++---- frontends/rioterm/src/router/mod.rs | 48 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 30 deletions(-) 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/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); From aba1fe95b0e26749bfcd99773437cf0da546a27a Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 19:44:50 +0200 Subject: [PATCH 08/12] update releases.md --- docs/docs/releases.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/releases.md b/docs/docs/releases.md index b3fc0d3be9..9803ee96bb 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). +- 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. From 8a3c495afa5db635b366730847cbc32c7a69175e Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 21:05:30 +0200 Subject: [PATCH 09/12] add MOUSE_MODE to Mode initialisation --- docs/docs/releases.md | 2 +- rio-backend/src/crosswords/mod.rs | 48 +++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/docs/releases.md b/docs/docs/releases.md index 9803ee96bb..de189d908e 100644 --- a/docs/docs/releases.md +++ b/docs/docs/releases.md @@ -10,7 +10,7 @@ language: 'en' - **breaking**: `CollapsedTab` has been renamed to `Bookmark`. -- Memory usage reduced by 75% (avg ~201mb to 48mb). +- 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). 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() From 6375a78bbb0572b856b3a55a56bccb58c9ec3676 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 22:47:41 +0200 Subject: [PATCH 10/12] Word cache for multiple font sources --- sugarloaf/src/layout/content.rs | 65 ++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/sugarloaf/src/layout/content.rs b/sugarloaf/src/layout/content.rs index 5f746a0f98..e1409f80df 100644 --- a/sugarloaf/src/layout/content.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,7 +304,8 @@ impl Content { } } - self.word_cache.key = shaper_key.to_owned(); + 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 @@ -315,7 +317,7 @@ impl Content { .variations(vars.iter().copied()) .build(); - shaper.add_str(&self.word_cache.key); + shaper.add_str(&self.word_cache.content); self.metrics_cache .inner @@ -336,18 +338,32 @@ impl Content { } 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] @@ -357,19 +373,34 @@ 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(); } } From dcbec29c5698adbb405c608e43db26328aaf346a Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 22:53:11 +0200 Subject: [PATCH 11/12] remove println and apply format on size' --- sugarloaf/src/layout/content.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sugarloaf/src/layout/content.rs b/sugarloaf/src/layout/content.rs index e1409f80df..d1abc70031 100644 --- a/sugarloaf/src/layout/content.rs +++ b/sugarloaf/src/layout/content.rs @@ -375,18 +375,14 @@ impl WordCache { pub fn finish(&mut self) { 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()); + // 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 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), From adf15790d1f22e6433b51edbf9b0ea4f3f678ccf Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Mon, 16 Sep 2024 23:39:16 +0200 Subject: [PATCH 12/12] make bold, bold_italic and italic evictable as well --- sugarloaf/src/components/rich_text/image_cache/cache.rs | 2 +- sugarloaf/src/font/mod.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sugarloaf/src/components/rich_text/image_cache/cache.rs b/sugarloaf/src/components/rich_text/image_cache/cache.rs index 672fc3da48..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 = 1024; +pub const SIZE: u16 = 2048; impl ImageCache { /// Creates a new image cache. diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index ff5cb193fd..3703034399 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -127,7 +127,7 @@ impl Default for FontLibraryData { Self { ui: FontArc::try_from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), inner: FxHashMap::default(), - stash: LruCache::new(NonZeroUsize::new(2).unwrap()), + stash: LruCache::new(NonZeroUsize::new(3).unwrap()), } } } @@ -265,7 +265,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.italic, false, false) { + match find_font(&db, spec.italic, true, false) { FindResult::Found(data) => { self.insert(data); } @@ -277,7 +277,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.bold, false, false) { + match find_font(&db, spec.bold, true, false) { FindResult::Found(data) => { self.insert(data); } @@ -289,7 +289,7 @@ impl FontLibraryData { } } - match find_font(&db, spec.bold_italic, false, false) { + match find_font(&db, spec.bold_italic, true, false) { FindResult::Found(data) => { self.insert(data); }