rework font loading to be WAY more robust

macos-click-through
keith 5 years ago
parent d83fbee805
commit 24a5e5dc51

@ -7,9 +7,10 @@ use std::sync::Arc;
use std::process::Stdio; use std::process::Stdio;
use rmpv::Value; use rmpv::Value;
use nvim_rs::{create::tokio as create, UiAttachOptions}; use nvim_rs::{create::tokio as create, UiAttachOptions, Neovim};
use nvim_rs::compat::tokio::Compat;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::process::Command; use tokio::process::{Command, ChildStdin};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
pub use events::*; pub use events::*;
@ -53,6 +54,24 @@ async fn drain(receiver: &mut UnboundedReceiver<UiCommand>) -> Option<Vec<UiComm
} }
} }
async fn handle_current_commands(receiver: &mut UnboundedReceiver<UiCommand>, nvim: &Neovim<Compat<ChildStdin>>) -> bool {
if let Some(commands) = drain(receiver).await {
let (resize_list, other_commands): (Vec<UiCommand>, Vec<UiCommand>) = commands
.into_iter()
.partition(|command| command.is_resize());
if let Some(resize_command) = resize_list.into_iter().last() {
resize_command.execute(&nvim).await;
}
for ui_command in other_commands.into_iter() {
ui_command.execute(&nvim).await;
}
true
} else {
false
}
}
async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) { async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
let (width, height) = INITIAL_DIMENSIONS; let (width, height) = INITIAL_DIMENSIONS;
let (mut nvim, io_handler, _) = create::new_child_cmd(&mut create_nvim_command(), NeovimHandler::new()).await let (mut nvim, io_handler, _) = create::new_child_cmd(&mut create_nvim_command(), NeovimHandler::new()).await
@ -76,22 +95,13 @@ async fn start_process(mut receiver: UnboundedReceiver<UiCommand>) {
.unwrap_or_explained_panic("Could not attach.", "Could not attach ui to neovim process"); .unwrap_or_explained_panic("Could not attach.", "Could not attach ui to neovim process");
let nvim = Arc::new(nvim); let nvim = Arc::new(nvim);
loop { tokio::spawn(async move {
if let Some(commands) = drain(&mut receiver).await { loop {
let (resize_list, other_commands): (Vec<UiCommand>, Vec<UiCommand>) = commands if !handle_current_commands(&mut receiver, &nvim).await {
.into_iter() break;
.partition(|command| command.is_resize());
if let Some(resize_command) = resize_list.into_iter().last() {
resize_command.execute(&nvim).await;
}
for ui_command in other_commands.into_iter() {
ui_command.execute(&nvim).await;
} }
} else {
break;
} }
} });
} }
pub struct Bridge { pub struct Bridge {
@ -101,15 +111,13 @@ pub struct Bridge {
impl Bridge { impl Bridge {
pub fn new() -> Bridge { pub fn new() -> Bridge {
let runtime = Runtime::new().unwrap(); let mut runtime = Runtime::new().unwrap();
let (sender, receiver) = unbounded_channel::<UiCommand>(); let (sender, receiver) = unbounded_channel::<UiCommand>();
runtime.spawn(async move { runtime.block_on(async move {
start_process(receiver).await; start_process(receiver).await;
}); });
println!("Bridge created.");
Bridge { _runtime: runtime, sender } Bridge { _runtime: runtime, sender }
} }

@ -2,138 +2,112 @@ use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use lru::LruCache; use lru::LruCache;
use skulpin::skia_safe::{TextBlob, Font as SkiaFont, FontStyle, Typeface, TextBlobBuilder}; use skulpin::skia_safe::{TextBlob, Font as SkiaFont, FontStyle, Typeface, TextBlobBuilder, Data};
use font_kit::source::SystemSource; use font_kit::{source::SystemSource, metrics::Metrics, properties::Properties, family_name::FamilyName};
use skribo::{layout_run, LayoutSession, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle}; use skribo::{layout_run, LayoutSession, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle};
use crate::error_handling::OptionPanicExplanation; use crate::error_handling::OptionPanicExplanation;
const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
#[cfg(target_os = "windows")]
const DEFAULT_FONT: &str = "Consolas";
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
const EMOJI_FONT: &str = "Segoe UI Emoji"; const EMOJI_FONT: &str = "Segoe UI Emoji";
#[cfg(target_os = "macos")]
const DEFAULT_FONT: &str = "Menlo";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const EMOJI_FONT: &str = "Apple COlor Emoji"; const EMOJI_FONT: &str = "Apple COlor Emoji";
#[cfg(target_os = "linux")]
const DEFAULT_FONT: &str = "Monospace";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const EMOJI_FONT: &str = "Noto Color Emoji"; const EMOJI_FONT: &str = "Noto Color Emoji";
const DEFAULT_FONT_SIZE: f32 = 14.0; const DEFAULT_FONT_SIZE: f32 = 14.0;
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
struct FontKey {
pub scale: u16,
pub bold: bool,
pub italic: bool
}
#[derive(new, Clone, Hash, PartialEq, Eq, Debug)] #[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
struct ShapeKey { struct ShapeKey {
pub text: String, pub text: String,
pub font_key: FontKey pub bold: bool,
} pub italic: bool
struct FontPair {
normal: (SkiaFont, SkriboFont),
emoji: Option<(SkiaFont, SkriboFont)>
} }
#[derive(Debug)]
pub struct CachingShaper { pub struct CachingShaper {
pub font_name: String, pub font_name: Option<String>,
pub base_size: f32, pub base_size: f32,
font_cache: LruCache<FontKey, FontPair>, collection: FontCollection,
font_cache: LruCache<String, SkiaFont>,
blob_cache: LruCache<ShapeKey, Vec<TextBlob>> blob_cache: LruCache<ShapeKey, Vec<TextBlob>>
} }
fn build_fonts(font_key: &FontKey, font_name: &str, base_size: f32) -> Option<(SkiaFont, SkriboFont)> { fn build_collection_by_font_name(font_name: Option<&str>) -> FontCollection {
let source = SystemSource::new(); let source = SystemSource::new();
let skribo_font = SkriboFont::new(
source.select_family_by_name(font_name) let mut collection = FontCollection::new();
.ok()?
.fonts()[0] if let Some(font_name) = font_name {
.load() if let Ok(custom) = source.select_family_by_name(font_name) {
.ok()?); let font = custom.fonts()[0].load().unwrap();
collection.add_family(FontFamily::new_from_font(font));
let font_style = match (font_key.bold, font_key.italic) { }
(false, false) => FontStyle::normal(), }
(true, false) => FontStyle::bold(),
(false, true) => FontStyle::italic(), if let Ok(monospace) = source.select_best_match(&[FamilyName::Monospace], &Properties::new()) {
(true, true) => FontStyle::bold_italic() let font = monospace.load().unwrap();
}; collection.add_family(FontFamily::new_from_font(font));
let skia_font = SkiaFont::from_typeface( }
Typeface::new(font_name.clone(), font_style)?,
base_size * font_key.scale as f32); if let Ok(emoji) = source.select_family_by_name(EMOJI_FONT) {
let font = emoji.fonts()[0].load().unwrap();
Some((skia_font, skribo_font)) collection.add_family(FontFamily::new_from_font(font));
}
collection
}
fn build_skia_font_from_skribo_font(skribo_font: &SkriboFont, base_size: f32) -> SkiaFont {
let font_data = skribo_font.font.copy_font_data().unwrap();
let skia_data = Data::new_copy(&font_data[..]);
let typeface = Typeface::from_data(skia_data, None).unwrap();
SkiaFont::from_typeface(typeface, base_size)
} }
impl CachingShaper { impl CachingShaper {
pub fn new() -> CachingShaper { pub fn new() -> CachingShaper {
CachingShaper { CachingShaper {
font_name: DEFAULT_FONT.to_string(), font_name: None,
base_size: DEFAULT_FONT_SIZE, base_size: DEFAULT_FONT_SIZE,
collection: build_collection_by_font_name(None),
font_cache: LruCache::new(100), font_cache: LruCache::new(100),
blob_cache: LruCache::new(10000), blob_cache: LruCache::new(10000),
} }
} }
fn get_font_pair(&mut self, font_key: &FontKey) -> &FontPair {
if !self.font_cache.contains(font_key) { fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> &SkiaFont {
let font_pair = FontPair { let font_name = skribo_font.font.postscript_name().unwrap();
normal: build_fonts(font_key, &self.font_name, self.base_size) if !self.font_cache.contains(&font_name) {
.unwrap_or_explained_panic( let font = build_skia_font_from_skribo_font(skribo_font, self.base_size);
"Could not load configured font", self.font_cache.put(font_name.clone(), font);
&format!("Could not load {}. This font was either the font configured in init scripts via guifont, or the default font is not available on your machine. Defaults are defined here: https://github.com/Kethku/neovide/blob/master/src/renderer/caching_shaper.rs", &self.font_name)),
emoji: build_fonts(font_key, EMOJI_FONT, self.base_size)
};
self.font_cache.put(font_key.clone(), font_pair);
} }
self.font_cache.get(font_key).unwrap() self.font_cache.get(&font_name).unwrap()
} }
pub fn shape(&mut self, text: &str, scale: u16, bold: bool, italic: bool) -> Vec<TextBlob> { fn metrics(&self) -> Metrics {
let base_size = self.base_size; self.collection.itemize("a").next().unwrap().1.font.metrics()
let font_key = FontKey::new(scale, bold, italic); }
let font_pair = self.get_font_pair(&font_key);
let style = TextStyle { size: base_size * scale as f32 };
let mut collection = FontCollection::new();
let mut normal_family = FontFamily::new();
normal_family.add_font(font_pair.normal.1.clone());
collection.add_family(normal_family);
if let Some(emoji_font_pair) = &font_pair.emoji { pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> {
let mut emoji_family = FontFamily::new(); let style = TextStyle { size: self.base_size };
emoji_family.add_font(emoji_font_pair.1.clone());
collection.add_family(emoji_family);
}
let session = LayoutSession::create(text, &style, &collection); let session = LayoutSession::create(text, &style, &self.collection);
let metrics = font_pair.normal.1.font.metrics(); let metrics = self.metrics();
let ascent = metrics.ascent * base_size / metrics.units_per_em as f32; let ascent = metrics.ascent * self.base_size / metrics.units_per_em as f32;
let mut blobs = Vec::new(); let mut blobs = Vec::new();
for layout_run in session.iter_all() { for layout_run in session.iter_all() {
let skribo_font = layout_run.font(); let skribo_font = layout_run.font();
let skia_font = if Arc::ptr_eq(&skribo_font.font, &font_pair.normal.1.font) { let skia_font = self.get_skia_font(&skribo_font);
&font_pair.normal.0
} else {
&font_pair.emoji.as_ref().unwrap().0
};
let mut blob_builder = TextBlobBuilder::new(); let mut blob_builder = TextBlobBuilder::new();
@ -150,11 +124,10 @@ impl CachingShaper {
blobs blobs
} }
pub fn shape_cached(&mut self, text: &str, scale: u16, bold: bool, italic: bool) -> &Vec<TextBlob> { pub fn shape_cached(&mut self, text: &str, bold: bool, italic: bool) -> &Vec<TextBlob> {
let font_key = FontKey::new(scale, bold, italic); let key = ShapeKey::new(text.to_string(), bold, italic);
let key = ShapeKey::new(text.to_string(), font_key);
if !self.blob_cache.contains(&key) { if !self.blob_cache.contains(&key) {
let blobs = self.shape(text, scale, bold, italic); let blobs = self.shape(text, bold, italic);
self.blob_cache.put(key.clone(), blobs); self.blob_cache.put(key.clone(), blobs);
} }
@ -162,23 +135,22 @@ impl CachingShaper {
} }
pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option<f32>) { pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option<f32>) {
self.font_name = font_name.map(|name| name.to_string());
self.base_size = base_size.unwrap_or(DEFAULT_FONT_SIZE);
self.collection = build_collection_by_font_name(font_name);
self.font_cache.clear(); self.font_cache.clear();
self.blob_cache.clear(); self.blob_cache.clear();
self.font_name = font_name.unwrap_or(DEFAULT_FONT).to_string();
self.base_size = base_size.unwrap_or(DEFAULT_FONT_SIZE);
} }
pub fn font_base_dimensions(&mut self) -> (f32, f32) { pub fn font_base_dimensions(&mut self) -> (f32, f32) {
let base_size = self.base_size; let metrics = self.metrics();
let font_key = FontKey::new(1, false, false); let font_height = (metrics.ascent - metrics.descent) * self.base_size / metrics.units_per_em as f32;
let (skia_font, skribo_font) = &self.get_font_pair(&font_key).normal;
let (_, metrics) = skia_font.metrics(); let style = TextStyle { size: self.base_size };
let font_height = metrics.descent - metrics.ascent; let session = LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.collection);
let style = TextStyle { size: base_size }; let layout_run = session.iter_all().next().unwrap();
let layout = layout_run(&style, &skribo_font, STANDARD_CHARACTER_STRING); let glyph_offsets: Vec<f32> = layout_run.glyphs().map(|glyph| glyph.offset.x).collect();
let glyph_offsets: Vec<f32> = layout.glyphs.iter().map(|glyph| glyph.offset.x).collect();
let glyph_advances: Vec<f32> = glyph_offsets.windows(2).map(|pair| pair[1] - pair[0]).collect(); let glyph_advances: Vec<f32> = glyph_offsets.windows(2).map(|pair| pair[1] - pair[0]).collect();
let mut amounts = HashMap::new(); let mut amounts = HashMap::new();
@ -193,11 +165,8 @@ impl CachingShaper {
(font_width, font_height) (font_width, font_height)
} }
pub fn underline_position(&mut self, scale: u16) -> f32 { pub fn underline_position(&mut self) -> f32 {
let font_key = FontKey::new(scale, false, false); let metrics = self.metrics();
let (skia_font, _) = &self.get_font_pair(&font_key).normal; metrics.underline_position
let (_, metrics) = skia_font.metrics();
metrics.underline_position().unwrap()
} }
} }

@ -251,7 +251,7 @@ impl CursorRenderer {
canvas.save(); canvas.save();
canvas.clip_path(&path, None, Some(false)); canvas.clip_path(&path, None, Some(false));
let blobs = &shaper.shape_cached(&character.to_string(), 1, false, false); let blobs = &shaper.shape_cached(&character.to_string(), false, false);
for blob in blobs.iter() { for blob in blobs.iter() {
canvas.draw_text_blob(&blob, destination, &paint); canvas.draw_text_blob(&blob, destination, &paint);
} }

@ -74,7 +74,7 @@ impl Renderer {
canvas.clip_rect(region, None, Some(false)); canvas.clip_rect(region, None, Some(false));
if style.underline || style.undercurl { if style.underline || style.undercurl {
let line_position = self.shaper.underline_position(size); let line_position = self.shaper.underline_position();
self.paint.set_color(style.special(&default_colors).to_color()); self.paint.set_color(style.special(&default_colors).to_color());
canvas.draw_line((x, y - line_position + self.font_height), (x + width, y - line_position + self.font_height), &self.paint); canvas.draw_line((x, y - line_position + self.font_height), (x + width, y - line_position + self.font_height), &self.paint);
} }
@ -82,7 +82,7 @@ impl Renderer {
self.paint.set_color(style.foreground(&default_colors).to_color()); self.paint.set_color(style.foreground(&default_colors).to_color());
let text = text.trim_end(); let text = text.trim_end();
if !text.is_empty() { if !text.is_empty() {
for blob in self.shaper.shape_cached(text, size, style.bold, style.italic).iter() { for blob in self.shaper.shape_cached(text, style.bold, style.italic).iter() {
canvas.draw_text_blob(blob, (x, y), &self.paint); canvas.draw_text_blob(blob, (x, y), &self.paint);
} }
} }
@ -109,7 +109,7 @@ impl Renderer {
}; };
let font_changed = let font_changed =
font_name.clone().map(|new_name| new_name != self.shaper.font_name).unwrap_or(false) || font_name != self.shaper.font_name ||
font_size.map(|new_size| (new_size - self.shaper.base_size).abs() > std::f32::EPSILON).unwrap_or(false); font_size.map(|new_size| (new_size - self.shaper.base_size).abs() > std::f32::EPSILON).unwrap_or(false);
if font_changed { if font_changed {
self.set_font(font_name.as_deref(), font_size); self.set_font(font_name.as_deref(), font_size);

Loading…
Cancel
Save