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 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<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>) {
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<UiCommand>) {
.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<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;
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::<UiCommand>();
runtime.spawn(async move {
runtime.block_on(async move {
start_process(receiver).await;
});
println!("Bridge created.");
Bridge { _runtime: runtime, sender }
}

@ -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<String>,
pub base_size: f32,
font_cache: LruCache<FontKey, FontPair>,
collection: FontCollection,
font_cache: LruCache<String, SkiaFont>,
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 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<TextBlob> {
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<TextBlob> {
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<TextBlob> {
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<TextBlob> {
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<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.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<f32> = layout.glyphs.iter().map(|glyph| glyph.offset.x).collect();
let layout_run = session.iter_all().next().unwrap();
let glyph_offsets: Vec<f32> = layout_run.glyphs().map(|glyph| glyph.offset.x).collect();
let glyph_advances: Vec<f32> = 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
}
}

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

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

Loading…
Cancel
Save