From db53e94db928946a1df06c88dd68bd80ac443758 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Sun, 23 Jan 2022 20:42:13 -0800 Subject: [PATCH] Use loaded fonts if no configured font works (#1171) --- neovide-derive/src/lib.rs | 2 +- src/bridge/clipboard.rs | 10 +- src/bridge/handler.rs | 2 +- src/bridge/setup.rs | 2 +- src/renderer/cursor_renderer/cursor_vfx.rs | 4 +- src/renderer/cursor_renderer/mod.rs | 2 +- src/renderer/fonts/caching_shaper.rs | 83 ++++++---- src/renderer/fonts/font_loader.rs | 182 ++++++++++----------- src/renderer/fonts/font_options.rs | 9 +- src/settings/from_value.rs | 72 ++++---- src/settings/mod.rs | 2 +- 11 files changed, 186 insertions(+), 184 deletions(-) diff --git a/neovide-derive/src/lib.rs b/neovide-derive/src/lib.rs index 851ce83..d6fc5a5 100644 --- a/neovide-derive/src/lib.rs +++ b/neovide-derive/src/lib.rs @@ -31,7 +31,7 @@ fn struct_stream(name: Ident, prefix: String, data: &DataStruct) -> TokenStream quote! {{ fn update_func(value: rmpv::Value) { let mut s = crate::settings::SETTINGS.get::<#name>(); - s.#ident.from_value(value); + s.#ident.parse_from_value(value); crate::settings::SETTINGS.set(&s); } diff --git a/src/bridge/clipboard.rs b/src/bridge/clipboard.rs index e84f438..ff4fab4 100644 --- a/src/bridge/clipboard.rs +++ b/src/bridge/clipboard.rs @@ -7,17 +7,17 @@ use clipboard::ClipboardProvider; pub fn get_remote_clipboard(format: Option<&str>) -> Result> { let mut ctx: ClipboardContext = ClipboardProvider::new()?; - let clipboard_raw = ctx.get_contents()?.replace("\r", ""); + let clipboard_raw = ctx.get_contents()?.replace('\r', ""); let lines = if let Some("dos") = format { // add \r to lines of current file format is dos - clipboard_raw.replace("\n", "\r\n") + clipboard_raw.replace('\n', "\r\n") } else { // else, \r is stripped, leaving only \n clipboard_raw } - .split("\n") - .map(|line| Value::from(line)) + .split('\n') + .map(Value::from) .collect::>(); let lines = Value::from(lines); @@ -45,7 +45,7 @@ pub fn set_remote_clipboard(arguments: Vec) -> Result<(), Box> .map(|arr| { arr.iter() .filter_map(|x| x.as_str().map(String::from)) - .map(|s| s.replace("\r", "")) // strip \r + .map(|s| s.replace('\r', "")) // strip \r .collect::>() .join(endline) }) diff --git a/src/bridge/handler.rs b/src/bridge/handler.rs index fd9e64e..dd02344 100644 --- a/src/bridge/handler.rs +++ b/src/bridge/handler.rs @@ -49,7 +49,7 @@ impl Handler for NeovimHandler { }); get_remote_clipboard(endline_type.as_deref()) - .or(Err(Value::from("cannot get remote clipboard content"))) + .map_err(|_| Value::from("cannot get remote clipboard content")) } _ => Ok(Value::from("rpcrequest not handled")), } diff --git a/src/bridge/setup.rs b/src/bridge/setup.rs index 4f3d6d9..af42d9e 100644 --- a/src/bridge/setup.rs +++ b/src/bridge/setup.rs @@ -42,7 +42,7 @@ pub async fn setup_neovide_remote_clipboard(nvim: &Neovim, neovide_ch 'cache_enabled': 0 } "# - .replace("\n", "") // make one-liner, because multiline is not accepted (?) + .replace('\n', "") // make one-liner, because multiline is not accepted (?) .replace("neovide_channel", &neovide_channel.to_string()); nvim.command(&custom_clipboard).await.ok(); } diff --git a/src/renderer/cursor_renderer/cursor_vfx.rs b/src/renderer/cursor_renderer/cursor_vfx.rs index 933f822..e7e3908 100644 --- a/src/renderer/cursor_renderer/cursor_vfx.rs +++ b/src/renderer/cursor_renderer/cursor_vfx.rs @@ -48,8 +48,8 @@ pub enum VfxMode { Disabled, } -impl FromValue for VfxMode { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for VfxMode { + fn parse_from_value(&mut self, value: Value) { if value.is_str() { *self = match value.as_str().unwrap() { "sonicboom" => VfxMode::Highlight(HighlightMode::SonicBoom), diff --git a/src/renderer/cursor_renderer/mod.rs b/src/renderer/cursor_renderer/mod.rs index ebb08f7..c4730ce 100644 --- a/src/renderer/cursor_renderer/mod.rs +++ b/src/renderer/cursor_renderer/mod.rs @@ -11,7 +11,7 @@ use crate::{ redraw_scheduler::REDRAW_SCHEDULER, renderer::animation_utils::*, renderer::{GridRenderer, RenderedWindow}, - settings::{FromValue, SETTINGS}, + settings::{ParseFromValue, SETTINGS}, }; use blink::*; diff --git a/src/renderer/fonts/caching_shaper.rs b/src/renderer/fonts/caching_shaper.rs index b46d454..03b2922 100644 --- a/src/renderer/fonts/caching_shaper.rs +++ b/src/renderer/fonts/caching_shaper.rs @@ -3,13 +3,8 @@ use std::sync::Arc; use log::{trace, warn}; use lru::LruCache; use skia_safe::{ - graphics::{ - font_cache_used, - font_cache_limit, - set_font_cache_limit, - }, - TextBlob, - TextBlobBuilder, + graphics::{font_cache_limit, font_cache_used, set_font_cache_limit}, + TextBlob, TextBlobBuilder, }; use swash::{ shape::ShapeContext, @@ -56,16 +51,17 @@ impl CachingShaper { } fn current_font_pair(&mut self) -> Arc { - let default_key = FontKey::default(); - let font_key = FontKey::from(&self.options); - - if let Some(font_pair) = self.font_loader.get_or_load(&font_key) { - return font_pair; - } - self.font_loader - .get_or_load(&default_key) - .expect("Could not load font") + .get_or_load(&FontKey { + bold: false, + italic: false, + family_name: self.options.primary_font(), + }) + .unwrap_or_else(|| { + self.font_loader + .get_or_load(&FontKey::default()) + .expect("Could not load default font") + }) } pub fn current_size(&self) -> f32 { @@ -82,7 +78,11 @@ impl CachingShaper { trace!("Updating font: {}", guifont_setting); let options = FontOptions::parse(guifont_setting); - let font_key = FontKey::from(&options); + let font_key = FontKey { + bold: false, + italic: false, + family_name: options.primary_font(), + }; if self.font_loader.get_or_load(&font_key).is_some() { trace!("Font updated to: {}", guifont_setting); @@ -202,32 +202,20 @@ impl CachingShaper { font_fallback_keys.extend(self.options.font_list.iter().map(|font_name| FontKey { italic: self.options.italic || italic, bold: self.options.bold || bold, - font_selection: font_name.into(), + family_name: Some(font_name.clone()), })); // Add default font font_fallback_keys.push(FontKey { italic: self.options.italic || italic, bold: self.options.bold || bold, - font_selection: FontSelection::Default, + family_name: None, }); - // Add skia fallback - font_fallback_keys.push(FontKey { - italic, - bold, - font_selection: cluster.chars()[0].ch.into(), - }); - - // Add last resort - font_fallback_keys.push(FontKey { - italic: false, - bold: false, - font_selection: FontSelection::LastResort, - }); + // Use the cluster.map function to select a viable font from the fallback list and loaded fonts let mut best = None; - // Use the cluster.map function to select a viable font from the fallback list + // Search through the configured and default fonts for a match for fallback_key in font_fallback_keys.iter() { if let Some(font_pair) = self.font_loader.get_or_load(fallback_key) { let charmap = font_pair.swash_font.as_ref().charmap(); @@ -242,9 +230,36 @@ impl CachingShaper { } } + // Configured font/default didn't work. Search through currently loaded ones + for loaded_font in self.font_loader.loaded_fonts() { + let charmap = loaded_font.swash_font.as_ref().charmap(); + match cluster.map(|ch| charmap.map(ch)) { + Status::Complete => { + results.push((cluster.to_owned(), loaded_font.clone())); + self.font_loader.refresh(loaded_font.as_ref()); + continue 'cluster; + } + Status::Keep => best = Some(loaded_font), + Status::Discard => {} + } + } + if let Some(best) = best { - // Last Resort covers all of the unicode space so we will always have a fallback results.push((cluster.to_owned(), best.clone())); + } else { + let fallback_character = cluster.chars()[0].ch; + if let Some(fallback_font) = + self.font_loader + .load_font_for_character(bold, italic, fallback_character) + { + results.push((cluster.to_owned(), fallback_font)); + } else { + // Last Resort covers all of the unicode space so we will always have a fallback + results.push(( + cluster.to_owned(), + self.font_loader.get_or_load_last_resort(), + )); + } } } diff --git a/src/renderer/fonts/font_loader.rs b/src/renderer/fonts/font_loader.rs index bdeb981..cfd83db 100644 --- a/src/renderer/fonts/font_loader.rs +++ b/src/renderer/fonts/font_loader.rs @@ -1,28 +1,32 @@ use std::sync::Arc; +use log::trace; use lru::LruCache; use skia_safe::{font::Edging, Data, Font, FontHinting, FontMgr, FontStyle, Typeface}; -use crate::renderer::fonts::{font_options::FontOptions, swash_font::SwashFont}; +use crate::renderer::fonts::swash_font::SwashFont; static DEFAULT_FONT: &[u8] = include_bytes!("../../../assets/fonts/FiraCode-Regular.ttf"); static LAST_RESORT_FONT: &[u8] = include_bytes!("../../../assets/fonts/LastResort-Regular.ttf"); pub struct FontPair { + pub key: FontKey, pub skia_font: Font, pub swash_font: SwashFont, } impl FontPair { - fn new(mut skia_font: Font) -> Option { + fn new(key: FontKey, mut skia_font: Font) -> Option { skia_font.set_subpixel(true); skia_font.set_hinting(FontHinting::Full); skia_font.set_edging(Edging::AntiAlias); - let (font_data, index) = skia_font.typeface().unwrap().to_font_data().unwrap(); + let typeface = skia_font.typeface().unwrap(); + let (font_data, index) = typeface.to_font_data().unwrap(); let swash_font = SwashFont::from_data(font_data, index)?; Some(Self { + key, skia_font, swash_font, }) @@ -35,116 +39,43 @@ impl PartialEq for FontPair { } } -pub struct FontLoader { - font_mgr: FontMgr, - cache: LruCache>, - font_size: f32, -} - -#[derive(Debug, Hash, PartialEq, Eq, Clone)] +#[derive(Debug, Default, Hash, PartialEq, Eq, Clone)] pub struct FontKey { // TODO(smolck): Could make these private and add constructor method(s)? // Would theoretically make things safer I guess, but not sure . . . pub bold: bool, pub italic: bool, - pub font_selection: FontSelection, -} - -impl Default for FontKey { - fn default() -> Self { - FontKey { - italic: false, - bold: false, - font_selection: FontSelection::Default, - } - } -} - -impl From<&FontOptions> for FontKey { - fn from(options: &FontOptions) -> FontKey { - FontKey { - italic: options.italic, - bold: options.bold, - font_selection: options.primary_font(), - } - } -} - -#[derive(Debug, Hash, PartialEq, Eq, Clone)] -pub enum FontSelection { - Name(String), - Character(char), - Default, - LastResort, -} - -impl From<&str> for FontSelection { - fn from(string: &str) -> FontSelection { - let string = string.to_string(); - FontSelection::Name(string) - } -} - -impl From<&String> for FontSelection { - fn from(string: &String) -> FontSelection { - let string = string.to_owned(); - FontSelection::Name(string) - } -} - -impl From for FontSelection { - fn from(string: String) -> FontSelection { - FontSelection::Name(string) - } + pub family_name: Option, } -impl From for FontSelection { - fn from(character: char) -> FontSelection { - FontSelection::Character(character) - } +pub struct FontLoader { + font_mgr: FontMgr, + cache: LruCache>, + font_size: f32, + last_resort: Option>, } impl FontLoader { pub fn new(font_size: f32) -> FontLoader { FontLoader { font_mgr: FontMgr::new(), - cache: LruCache::new(10), + cache: LruCache::new(20), font_size, + last_resort: None, } } fn load(&mut self, font_key: FontKey) -> Option { - let font_style = match (font_key.bold, font_key.italic) { - (true, true) => FontStyle::bold_italic(), - (false, true) => FontStyle::italic(), - (true, false) => FontStyle::bold(), - (false, false) => FontStyle::normal(), - }; - - match font_key.font_selection { - FontSelection::Name(name) => { - let typeface = self.font_mgr.match_family_style(name, font_style)?; - FontPair::new(Font::from_typeface(typeface, self.font_size)) - } - FontSelection::Character(character) => { - let typeface = self.font_mgr.match_family_style_character( - "", - font_style, - &[], - character as i32, - )?; - FontPair::new(Font::from_typeface(typeface, self.font_size)) - } - FontSelection::Default => { - let data = Data::new_copy(DEFAULT_FONT); - let typeface = Typeface::from_data(data, 0).unwrap(); - FontPair::new(Font::from_typeface(typeface, self.font_size)) - } - FontSelection::LastResort => { - let data = Data::new_copy(LAST_RESORT_FONT); - let typeface = Typeface::from_data(data, 0).unwrap(); - FontPair::new(Font::from_typeface(typeface, self.font_size)) - } + let font_style = font_style(font_key.bold, font_key.italic); + + trace!("Loading font {:?}", font_key); + if let Some(family_name) = &font_key.family_name { + let typeface = self.font_mgr.match_family_style(family_name, font_style)?; + FontPair::new(font_key, Font::from_typeface(typeface, self.font_size)) + } else { + let data = Data::new_copy(DEFAULT_FONT); + let typeface = Typeface::from_data(data, 0).unwrap(); + FontPair::new(font_key, Font::from_typeface(typeface, self.font_size)) } } @@ -162,7 +93,68 @@ impl FontLoader { Some(font_arc) } + pub fn load_font_for_character( + &mut self, + bold: bool, + italic: bool, + character: char, + ) -> Option> { + let font_style = font_style(bold, italic); + let typeface = + self.font_mgr + .match_family_style_character("", font_style, &[], character as i32)?; + + let font_key = FontKey { + bold, + italic, + family_name: Some(typeface.family_name()), + }; + + let font_pair = Arc::new(FontPair::new( + font_key.clone(), + Font::from_typeface(typeface, self.font_size), + )?); + + self.cache.put(font_key, font_pair.clone()); + + Some(font_pair) + } + + pub fn get_or_load_last_resort(&mut self) -> Arc { + if let Some(last_resort) = self.last_resort.clone() { + last_resort + } else { + let font_key = FontKey::default(); + let data = Data::new_copy(LAST_RESORT_FONT); + let typeface = Typeface::from_data(data, 0).unwrap(); + + let font_pair = + FontPair::new(font_key, Font::from_typeface(typeface, self.font_size)).unwrap(); + let font_pair = Arc::new(font_pair); + + self.last_resort = Some(font_pair.clone()); + font_pair + } + } + + pub fn loaded_fonts(&self) -> Vec> { + self.cache.iter().map(|(_, v)| v.clone()).collect() + } + + pub fn refresh(&mut self, font_pair: &FontPair) { + self.cache.get(&font_pair.key); + } + pub fn font_names(&self) -> Vec { self.font_mgr.family_names().collect() } } + +fn font_style(bold: bool, italic: bool) -> FontStyle { + match (bold, italic) { + (true, true) => FontStyle::bold_italic(), + (false, true) => FontStyle::italic(), + (true, false) => FontStyle::bold(), + (false, false) => FontStyle::normal(), + } +} diff --git a/src/renderer/fonts/font_options.rs b/src/renderer/fonts/font_options.rs index 6e41097..f33c659 100644 --- a/src/renderer/fonts/font_options.rs +++ b/src/renderer/fonts/font_options.rs @@ -1,5 +1,3 @@ -use super::font_loader::FontSelection; - const DEFAULT_FONT_SIZE: f32 = 14.0; #[derive(Clone, Debug)] @@ -51,11 +49,8 @@ impl FontOptions { } } - pub fn primary_font(&self) -> FontSelection { - self.font_list - .first() - .map(FontSelection::from) - .unwrap_or(FontSelection::Default) + pub fn primary_font(&self) -> Option { + self.font_list.first().cloned() } } diff --git a/src/settings/from_value.rs b/src/settings/from_value.rs index e5fb370..63c0865 100644 --- a/src/settings/from_value.rs +++ b/src/settings/from_value.rs @@ -4,13 +4,13 @@ use log::error; // Trait to allow for conversion from rmpv::Value to any other data type. // Note: Feel free to implement this trait for custom types in each subsystem. // The reverse conversion (MyType->Value) can be performed by implementing `From for Value` -pub trait FromValue { - fn from_value(&mut self, value: Value); +pub trait ParseFromValue { + fn parse_from_value(&mut self, value: Value); } // FromValue implementations for most typical types -impl FromValue for f32 { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for f32 { + fn parse_from_value(&mut self, value: Value) { if value.is_f64() { *self = value.as_f64().unwrap() as f32; } else if value.is_i64() { @@ -23,8 +23,8 @@ impl FromValue for f32 { } } -impl FromValue for u64 { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for u64 { + fn parse_from_value(&mut self, value: Value) { if value.is_u64() { *self = value.as_u64().unwrap(); } else { @@ -33,8 +33,8 @@ impl FromValue for u64 { } } -impl FromValue for u32 { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for u32 { + fn parse_from_value(&mut self, value: Value) { if value.is_u64() { *self = value.as_u64().unwrap() as u32; } else { @@ -43,8 +43,8 @@ impl FromValue for u32 { } } -impl FromValue for i32 { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for i32 { + fn parse_from_value(&mut self, value: Value) { if value.is_i64() { *self = value.as_i64().unwrap() as i32; } else { @@ -53,8 +53,8 @@ impl FromValue for i32 { } } -impl FromValue for String { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for String { + fn parse_from_value(&mut self, value: Value) { if value.is_str() { *self = String::from(value.as_str().unwrap()); } else { @@ -63,8 +63,8 @@ impl FromValue for String { } } -impl FromValue for bool { - fn from_value(&mut self, value: Value) { +impl ParseFromValue for bool { + fn parse_from_value(&mut self, value: Value) { if value.is_bool() { *self = value.as_bool().unwrap(); } else if value.is_u64() { @@ -81,7 +81,7 @@ mod tests { use super::*; #[test] - fn test_from_value_f32() { + fn test_parse_from_value_f32() { let mut v0: f32 = 0.0; let v1 = Value::from(1.0); let v2 = Value::from(-1); @@ -90,76 +90,76 @@ mod tests { let v2p = -1.0; let v3p = std::u64::MAX as f32; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); - v0.from_value(v2); + v0.parse_from_value(v2); assert_eq!(v0, v2p, "v0 should equal {} but is actually {}", v2p, v0); - v0.from_value(v3); + v0.parse_from_value(v3); assert_eq!(v0, v3p, "v0 should equal {} but is actually {}", v3p, v0); // This is a noop and prints an error - v0.from_value(Value::from("asd")); + v0.parse_from_value(Value::from("asd")); assert_eq!(v0, v3p, "v0 should equal {} but is actually {}", v3p, v0); } #[test] - fn test_from_value_u64() { + fn test_parse_from_value_u64() { let mut v0: u64 = 0; let v1 = Value::from(std::u64::MAX); let v1p = std::u64::MAX; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); // This is a noop and prints an error - v0.from_value(Value::from(-1)); + v0.parse_from_value(Value::from(-1)); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); } #[test] - fn test_from_value_u32() { + fn test_parse_from_value_u32() { let mut v0: u32 = 0; let v1 = Value::from(std::u64::MAX); let v1p = std::u64::MAX as u32; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); // This is a noop and prints an error - v0.from_value(Value::from(-1)); + v0.parse_from_value(Value::from(-1)); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); } #[test] - fn test_from_value_i32() { + fn test_parse_from_value_i32() { let mut v0: i32 = 0; let v1 = Value::from(std::i64::MAX); let v1p = std::i64::MAX as i32; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); // This is a noop and prints an error - v0.from_value(Value::from(-1)); + v0.parse_from_value(Value::from(-1)); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); } #[test] - fn test_from_value_string() { + fn test_parse_from_value_string() { let mut v0: String = "foo".to_string(); let v1 = Value::from("bar"); let v1p = "bar"; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); // This is a noop and prints an error - v0.from_value(Value::from(-1)); + v0.parse_from_value(Value::from(-1)); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); } #[test] - fn test_from_value_bool() { + fn test_parse_from_value_bool() { let mut v0: bool = false; let v1 = Value::from(true); let v1p = true; @@ -168,15 +168,15 @@ mod tests { let v3 = Value::from(1); let v3p = true; - v0.from_value(v1); + v0.parse_from_value(v1); assert_eq!(v0, v1p, "v0 should equal {} but is actually {}", v1p, v0); - v0.from_value(v2); + v0.parse_from_value(v2); assert_eq!(v0, v2p, "v0 should equal {} but is actually {}", v2p, v0); - v0.from_value(v3); + v0.parse_from_value(v3); assert_eq!(v0, v3p, "v0 should equal {} but is actually {}", v3p, v0); // This is a noop and prints an error - v0.from_value(Value::from(-1)); + v0.parse_from_value(Value::from(-1)); assert_eq!(v0, v3p, "v0 should equal {} but is actually {}", v3p, v0); } } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 3d2c291..1b98390 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -12,7 +12,7 @@ use std::{ }; use crate::{bridge::TxWrapper, error_handling::ResultPanicExplanation}; -pub use from_value::FromValue; +pub use from_value::ParseFromValue; pub use window_geometry::{ load_last_window_settings, parse_window_geometry, save_window_geometry, PersistentWindowSettings, DEFAULT_WINDOW_GEOMETRY,