diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 34e8252..6608347 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -7,9 +7,10 @@ use std::sync::Arc; use std::process::Stdio; 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::process::Command; +use tokio::process::{Command, ChildStdin}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; pub use events::*; @@ -53,6 +54,24 @@ async fn drain(receiver: &mut UnboundedReceiver) -> Option, nvim: &Neovim>) -> bool { + if let Some(commands) = drain(receiver).await { + let (resize_list, other_commands): (Vec, Vec) = 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) { let (width, height) = INITIAL_DIMENSIONS; 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) { .unwrap_or_explained_panic("Could not attach.", "Could not attach ui to neovim process"); let nvim = Arc::new(nvim); - loop { - if let Some(commands) = drain(&mut receiver).await { - let (resize_list, other_commands): (Vec, Vec) = 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; + tokio::spawn(async move { + loop { + if !handle_current_commands(&mut receiver, &nvim).await { + break; } - } else { - break; } - } + }); } pub struct Bridge { @@ -101,15 +111,13 @@ pub struct Bridge { impl Bridge { pub fn new() -> Bridge { - let runtime = Runtime::new().unwrap(); + let mut runtime = Runtime::new().unwrap(); let (sender, receiver) = unbounded_channel::(); - runtime.spawn(async move { + runtime.block_on(async move { start_process(receiver).await; }); - println!("Bridge created."); - Bridge { _runtime: runtime, sender } } diff --git a/src/renderer/caching_shaper.rs b/src/renderer/caching_shaper.rs index 8707379..261b06d 100644 --- a/src/renderer/caching_shaper.rs +++ b/src/renderer/caching_shaper.rs @@ -2,138 +2,112 @@ use std::sync::Arc; use std::collections::HashMap; use lru::LruCache; -use skulpin::skia_safe::{TextBlob, Font as SkiaFont, FontStyle, Typeface, TextBlobBuilder}; -use font_kit::source::SystemSource; +use skulpin::skia_safe::{TextBlob, Font as SkiaFont, FontStyle, Typeface, TextBlobBuilder, Data}; +use font_kit::{source::SystemSource, metrics::Metrics, properties::Properties, family_name::FamilyName}; use skribo::{layout_run, LayoutSession, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle}; use crate::error_handling::OptionPanicExplanation; const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; -#[cfg(target_os = "windows")] -const DEFAULT_FONT: &str = "Consolas"; #[cfg(target_os = "windows")] const EMOJI_FONT: &str = "Segoe UI Emoji"; -#[cfg(target_os = "macos")] -const DEFAULT_FONT: &str = "Menlo"; #[cfg(target_os = "macos")] const EMOJI_FONT: &str = "Apple COlor Emoji"; -#[cfg(target_os = "linux")] -const DEFAULT_FONT: &str = "Monospace"; #[cfg(target_os = "linux")] const EMOJI_FONT: &str = "Noto Color Emoji"; 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)] struct ShapeKey { pub text: String, - pub font_key: FontKey -} - -struct FontPair { - normal: (SkiaFont, SkriboFont), - emoji: Option<(SkiaFont, SkriboFont)> + pub bold: bool, + pub italic: bool } -#[derive(Debug)] pub struct CachingShaper { - pub font_name: String, + pub font_name: Option, pub base_size: f32, - font_cache: LruCache, + collection: FontCollection, + font_cache: LruCache, blob_cache: LruCache> } -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 skribo_font = SkriboFont::new( - source.select_family_by_name(font_name) - .ok()? - .fonts()[0] - .load() - .ok()?); - - let font_style = match (font_key.bold, font_key.italic) { - (false, false) => FontStyle::normal(), - (true, false) => FontStyle::bold(), - (false, true) => FontStyle::italic(), - (true, true) => FontStyle::bold_italic() - }; - let skia_font = SkiaFont::from_typeface( - Typeface::new(font_name.clone(), font_style)?, - base_size * font_key.scale as f32); - - Some((skia_font, skribo_font)) + + let mut collection = FontCollection::new(); + + if let Some(font_name) = font_name { + if let Ok(custom) = source.select_family_by_name(font_name) { + let font = custom.fonts()[0].load().unwrap(); + collection.add_family(FontFamily::new_from_font(font)); + } + } + + if let Ok(monospace) = source.select_best_match(&[FamilyName::Monospace], &Properties::new()) { + let font = monospace.load().unwrap(); + collection.add_family(FontFamily::new_from_font(font)); + } + + if let Ok(emoji) = source.select_family_by_name(EMOJI_FONT) { + let font = emoji.fonts()[0].load().unwrap(); + 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 { pub fn new() -> CachingShaper { CachingShaper { - font_name: DEFAULT_FONT.to_string(), + font_name: None, base_size: DEFAULT_FONT_SIZE, + collection: build_collection_by_font_name(None), font_cache: LruCache::new(100), blob_cache: LruCache::new(10000), } } - fn get_font_pair(&mut self, font_key: &FontKey) -> &FontPair { - if !self.font_cache.contains(font_key) { - let font_pair = FontPair { - normal: build_fonts(font_key, &self.font_name, self.base_size) - .unwrap_or_explained_panic( - "Could not load configured 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); + + fn get_skia_font(&mut self, skribo_font: &SkriboFont) -> &SkiaFont { + let font_name = skribo_font.font.postscript_name().unwrap(); + if !self.font_cache.contains(&font_name) { + let font = build_skia_font_from_skribo_font(skribo_font, self.base_size); + self.font_cache.put(font_name.clone(), font); } - 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 { - let base_size = self.base_size; - 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); + fn metrics(&self) -> Metrics { + self.collection.itemize("a").next().unwrap().1.font.metrics() + } - if let Some(emoji_font_pair) = &font_pair.emoji { - let mut emoji_family = FontFamily::new(); - emoji_family.add_font(emoji_font_pair.1.clone()); - collection.add_family(emoji_family); - } + pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec { + let style = TextStyle { size: self.base_size }; - let session = LayoutSession::create(text, &style, &collection); + let session = LayoutSession::create(text, &style, &self.collection); - let metrics = font_pair.normal.1.font.metrics(); - let ascent = metrics.ascent * base_size / metrics.units_per_em as f32; + let metrics = self.metrics(); + let ascent = metrics.ascent * self.base_size / metrics.units_per_em as f32; let mut blobs = Vec::new(); for layout_run in session.iter_all() { let skribo_font = layout_run.font(); - let skia_font = if Arc::ptr_eq(&skribo_font.font, &font_pair.normal.1.font) { - &font_pair.normal.0 - } else { - &font_pair.emoji.as_ref().unwrap().0 - }; + let skia_font = self.get_skia_font(&skribo_font); let mut blob_builder = TextBlobBuilder::new(); @@ -150,11 +124,10 @@ impl CachingShaper { blobs } - pub fn shape_cached(&mut self, text: &str, scale: u16, bold: bool, italic: bool) -> &Vec { - let font_key = FontKey::new(scale, bold, italic); - let key = ShapeKey::new(text.to_string(), font_key); + pub fn shape_cached(&mut self, text: &str, bold: bool, italic: bool) -> &Vec { + let key = ShapeKey::new(text.to_string(), bold, italic); 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); } @@ -162,23 +135,22 @@ impl CachingShaper { } pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option) { + 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.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) { - let base_size = self.base_size; - let font_key = FontKey::new(1, false, false); - let (skia_font, skribo_font) = &self.get_font_pair(&font_key).normal; + let metrics = self.metrics(); + let font_height = (metrics.ascent - metrics.descent) * self.base_size / metrics.units_per_em as f32; - let (_, metrics) = skia_font.metrics(); - let font_height = metrics.descent - metrics.ascent; + let style = TextStyle { size: self.base_size }; + let session = LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.collection); - let style = TextStyle { size: base_size }; - let layout = layout_run(&style, &skribo_font, STANDARD_CHARACTER_STRING); - let glyph_offsets: Vec = layout.glyphs.iter().map(|glyph| glyph.offset.x).collect(); + let layout_run = session.iter_all().next().unwrap(); + let glyph_offsets: Vec = layout_run.glyphs().map(|glyph| glyph.offset.x).collect(); let glyph_advances: Vec = glyph_offsets.windows(2).map(|pair| pair[1] - pair[0]).collect(); let mut amounts = HashMap::new(); @@ -193,11 +165,8 @@ impl CachingShaper { (font_width, font_height) } - pub fn underline_position(&mut self, scale: u16) -> f32 { - let font_key = FontKey::new(scale, false, false); - let (skia_font, _) = &self.get_font_pair(&font_key).normal; - - let (_, metrics) = skia_font.metrics(); - metrics.underline_position().unwrap() + pub fn underline_position(&mut self) -> f32 { + let metrics = self.metrics(); + metrics.underline_position } } diff --git a/src/renderer/cursor_renderer.rs b/src/renderer/cursor_renderer.rs index e065af6..fb10497 100644 --- a/src/renderer/cursor_renderer.rs +++ b/src/renderer/cursor_renderer.rs @@ -251,7 +251,7 @@ impl CursorRenderer { canvas.save(); 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() { canvas.draw_text_blob(&blob, destination, &paint); } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 10b4a5b..35fc156 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -74,7 +74,7 @@ impl Renderer { canvas.clip_rect(region, None, Some(false)); 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()); 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()); let text = text.trim_end(); 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); } } @@ -109,7 +109,7 @@ impl Renderer { }; 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); if font_changed { self.set_font(font_name.as_deref(), font_size);