Use loaded fonts if no configured font works (#1171)

macos-click-through
Keith Simmons 3 years ago committed by GitHub
parent 49224f8008
commit db53e94db9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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);
}

@ -7,17 +7,17 @@ use clipboard::ClipboardProvider;
pub fn get_remote_clipboard(format: Option<&str>) -> Result<Value, Box<dyn Error>> {
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::<Vec<Value>>();
let lines = Value::from(lines);
@ -45,7 +45,7 @@ pub fn set_remote_clipboard(arguments: Vec<Value>) -> Result<(), Box<dyn Error>>
.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::<Vec<String>>()
.join(endline)
})

@ -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")),
}

@ -42,7 +42,7 @@ pub async fn setup_neovide_remote_clipboard(nvim: &Neovim<TxWrapper>, 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();
}

@ -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),

@ -11,7 +11,7 @@ use crate::{
redraw_scheduler::REDRAW_SCHEDULER,
renderer::animation_utils::*,
renderer::{GridRenderer, RenderedWindow},
settings::{FromValue, SETTINGS},
settings::{ParseFromValue, SETTINGS},
};
use blink::*;

@ -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<FontPair> {
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(),
));
}
}
}

@ -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<FontPair> {
fn new(key: FontKey, mut skia_font: Font) -> Option<FontPair> {
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<FontKey, Arc<FontPair>>,
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<String> for FontSelection {
fn from(string: String) -> FontSelection {
FontSelection::Name(string)
}
pub family_name: Option<String>,
}
impl From<char> for FontSelection {
fn from(character: char) -> FontSelection {
FontSelection::Character(character)
}
pub struct FontLoader {
font_mgr: FontMgr,
cache: LruCache<FontKey, Arc<FontPair>>,
font_size: f32,
last_resort: Option<Arc<FontPair>>,
}
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<FontPair> {
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<Arc<FontPair>> {
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<FontPair> {
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<Arc<FontPair>> {
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<String> {
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(),
}
}

@ -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<String> {
self.font_list.first().cloned()
}
}

@ -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<MyType> 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);
}
}

@ -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,

Loading…
Cancel
Save