remove fonts cache and make first attempt at font fallback

macos-click-through
keith 5 years ago
parent b4c392997e
commit 5a880d06aa

@ -13,12 +13,10 @@ lru = "0.4.3"
skulpin = "0.5" skulpin = "0.5"
derive-new = "0.5" derive-new = "0.5"
env_logger = "0.7.1" env_logger = "0.7.1"
#neovim-lib = { git = "https://github.com/daa84/neovim-lib", version = "0.6" }
rmpv = "0.4.2" rmpv = "0.4.2"
msgbox = "0.4.0" msgbox = "0.4.0"
rust-embed = { version = "5.2.0", features = ["debug-embed"] } rust-embed = { version = "5.2.0", features = ["debug-embed"] }
image = "0.22.3" image = "0.22.3"
#nvim-rs = "0.1.0"
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "futures", features = [ "use_tokio" ] } nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "futures", features = [ "use_tokio" ] }
tokio = { version = "0.2.9", features = [ "blocking", "process", "time" ] } tokio = { version = "0.2.9", features = [ "blocking", "process", "time" ] }
async-trait = "0.1.18" async-trait = "0.1.18"

@ -1,109 +1,194 @@
use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use lru::LruCache; use lru::LruCache;
use skulpin::skia_safe::{TextBlob, Font, TextBlobBuilder}; use skulpin::skia_safe::{TextBlob, Font as SkiaFont, FontStyle, Typeface, TextBlobBuilder};
use font_kit::source::SystemSource; use font_kit::source::SystemSource;
use skribo::{layout_run, FontRef, TextStyle}; use skribo::{layout, layout_run, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle};
use super::fonts::FontLookup;
const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; const STANDARD_CHARACTER_STRING: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
#[derive(new, Clone, Hash, PartialEq, Eq)] #[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 { struct FontKey {
pub name: String,
pub base_size: String, // hack because comparison of floats doesn't work
pub scale: u16, pub scale: u16,
pub bold: bool, pub bold: bool,
pub italic: bool pub italic: bool
} }
#[derive(new, Clone, Hash, PartialEq, Eq)] #[derive(new, Clone, Hash, PartialEq, Eq, Debug)]
struct ShapeKey { struct ShapeKey {
pub text: String, pub text: String,
pub font_key: FontKey pub font_key: FontKey
} }
struct FontPair {
normal: (SkiaFont, SkriboFont),
emoji: (SkiaFont, SkriboFont)
}
#[derive(Debug)]
pub struct CachingShaper { pub struct CachingShaper {
font_cache: LruCache<FontKey, FontRef>, pub font_name: String,
blob_cache: LruCache<ShapeKey, TextBlob> pub base_size: f32,
font_cache: LruCache<FontKey, FontPair>,
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>
}
fn build_fonts(font_key: &FontKey, font_name: &str, base_size: f32) -> (SkiaFont, SkriboFont) {
let source = SystemSource::new();
let skribo_font = SkriboFont::new(
source.select_family_by_name(font_name)
.expect("Failed to load by postscript name")
.fonts()[0]
.load()
.unwrap());
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).expect("Could not load skia font file"),
base_size * font_key.scale as f32);
(skia_font, skribo_font)
} }
impl CachingShaper { impl CachingShaper {
pub fn new() -> CachingShaper { pub fn new() -> CachingShaper {
CachingShaper { CachingShaper {
font_name: DEFAULT_FONT.to_string(),
base_size: DEFAULT_FONT_SIZE,
font_cache: LruCache::new(100), font_cache: LruCache::new(100),
blob_cache: LruCache::new(10000) blob_cache: LruCache::new(10000),
} }
} }
fn get_font(&mut self, font_key: &FontKey) -> &FontRef { fn get_font_pair(&mut self, font_key: &FontKey) -> &FontPair {
if !self.font_cache.contains(font_key) { if !self.font_cache.contains(font_key) {
let source = SystemSource::new(); let font_pair = FontPair {
let font_name = font_key.name.clone(); normal: build_fonts(font_key, &self.font_name, self.base_size),
let font = source emoji: build_fonts(font_key, EMOJI_FONT, self.base_size)
.select_family_by_name(&font_name) };
.expect("Failed to load by postscript name") self.font_cache.put(font_key.clone(), font_pair);
.fonts()[0]
.load()
.unwrap();
self.font_cache.put(font_key.clone(), FontRef::new(font));
} }
self.font_cache.get(font_key).unwrap() self.font_cache.get(font_key).unwrap()
} }
pub fn shape(&mut self, text: &str, font_name: &str, base_size: f32, scale: u16, bold: bool, italic: bool, font: &Font) -> TextBlob { pub fn shape(&mut self, text: &str, scale: u16, bold: bool, italic: bool) -> Vec<TextBlob> {
let font_key = FontKey::new(font_name.to_string(), base_size.to_string(), scale, bold, italic); let base_size = self.base_size;
let font_ref = self.get_font(&font_key); 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 style = TextStyle { size: base_size * scale as f32 };
let layout = layout_run(&style, &font_ref, text);
let mut blob_builder = TextBlobBuilder::new(); let mut family = FontFamily::new();
family.add_font(font_pair.normal.1.clone());
family.add_font(font_pair.emoji.1.clone());
let mut collection = FontCollection::new();
collection.add_family(family);
let layout = layout(&style, &collection, text);
let count = layout.glyphs.len(); let mut groups = Vec::new();
let metrics = font_ref.font.metrics(); let mut group = Vec::new();
let ascent = metrics.ascent * base_size / metrics.units_per_em as f32; let mut previous_font: Option<SkriboFont> = None;
let (glyphs, positions) = blob_builder.alloc_run_pos_h(font, count, ascent, None);
for (i, glyph_id) in layout.glyphs.iter().map(|glyph| glyph.glyph_id as u16).enumerate() { for glyph in layout.glyphs {
glyphs[i] = glyph_id; if previous_font.clone().map(|previous_font| Arc::ptr_eq(&previous_font.font, &glyph.font.font)).unwrap_or(true) {
group.push(glyph);
} else {
groups.push(group);
previous_font = Some(glyph.font.clone());
group = vec![glyph];
}
} }
for (i, offset) in layout.glyphs.iter().map(|glyph| glyph.offset.x as f32).enumerate() { if !group.is_empty() {
positions[i] = offset; groups.push(group);
}
let mut blobs = Vec::new();
for group in groups {
if group.is_empty() {
continue;
}
let skribo_font = group[0].font.clone();
let skia_font = if Arc::ptr_eq(&skribo_font.font, &font_pair.normal.1.font) {
&font_pair.normal.0
} else {
&font_pair.emoji.0
};
let mut blob_builder = TextBlobBuilder::new();
let count = group.len();
let metrics = skribo_font.font.metrics();
let ascent = metrics.ascent * base_size / metrics.units_per_em as f32;
let (glyphs, positions) = blob_builder.alloc_run_pos_h(&skia_font, count, ascent, None);
for (i, glyph_id) in group.iter().map(|glyph| glyph.glyph_id as u16).enumerate() {
glyphs[i] = glyph_id;
}
for (i, offset) in group.iter().map(|glyph| glyph.offset.x as f32).enumerate() {
positions[i] = offset;
}
blobs.push(blob_builder.make().unwrap());
} }
blob_builder.make().unwrap() blobs
} }
pub fn shape_cached(&mut self, text: &str, font_name: &str, base_size: f32, scale: u16, bold: bool, italic: bool, font: &Font) -> &TextBlob { pub fn shape_cached(&mut self, text: &str, scale: u16, bold: bool, italic: bool) -> &Vec<TextBlob> {
let font_key = FontKey::new(font_name.to_string(), base_size.to_string(), scale, bold, italic); let font_key = FontKey::new(scale, bold, italic);
let key = ShapeKey::new(text.to_string(), font_key); let key = ShapeKey::new(text.to_string(), font_key);
if !self.blob_cache.contains(&key) { if !self.blob_cache.contains(&key) {
let blob = self.shape(text, font_name, base_size, scale, bold, italic, &font); let blobs = self.shape(text, scale, bold, italic);
self.blob_cache.put(key.clone(), blob); self.blob_cache.put(key.clone(), blobs);
} }
self.blob_cache.get(&key).unwrap() self.blob_cache.get(&key).unwrap()
} }
pub fn clear(&mut self) { pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option<f32>) {
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, font_lookup: &mut FontLookup) -> (f32, f32) { pub fn font_base_dimensions(&mut self) -> (f32, f32) {
let base_fonts = font_lookup.size(1); let base_size = self.base_size;
let normal_font = &base_fonts.normal; let font_key = FontKey::new(1, false, false);
let (_, metrics) = normal_font.metrics(); let (skia_font, skribo_font) = &self.get_font_pair(&font_key).normal;
let (_, metrics) = skia_font.metrics();
let font_height = metrics.descent - metrics.ascent; let font_height = metrics.descent - metrics.ascent;
let font_key = FontKey::new(font_lookup.name.to_string(), font_lookup.base_size.to_string(), 1, false, false); let style = TextStyle { size: base_size };
let font_ref = self.get_font(&font_key); let layout = layout_run(&style, &skribo_font, STANDARD_CHARACTER_STRING);
let style = TextStyle { size: font_lookup.base_size };
let layout = layout_run(&style, font_ref, STANDARD_CHARACTER_STRING);
let glyph_offsets: Vec<f32> = layout.glyphs.iter().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();
@ -118,4 +203,12 @@ impl CachingShaper {
(font_width, font_height) (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()
}
} }

@ -1,10 +1,9 @@
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use skulpin::skia_safe::{Canvas, Paint, Path, Point}; use skulpin::skia_safe::{Canvas, Paint, Path, Point};
use crate::renderer::{CachingShaper, FontLookup}; use crate::renderer::CachingShaper;
use crate::editor::{EDITOR, Colors, Cursor, CursorShape, Editor}; use crate::editor::{EDITOR, Colors, Cursor, CursorShape};
const AVERAGE_MOTION_PERCENTAGE: f32 = 0.7; const AVERAGE_MOTION_PERCENTAGE: f32 = 0.7;
const MOTION_PERCENTAGE_SPREAD: f32 = 0.5; const MOTION_PERCENTAGE_SPREAD: f32 = 0.5;
@ -172,7 +171,7 @@ impl CursorRenderer {
cursor: Cursor, default_colors: &Colors, cursor: Cursor, default_colors: &Colors,
font_width: f32, font_height: f32, font_width: f32, font_height: f32,
paint: &mut Paint, shaper: &mut CachingShaper, paint: &mut Paint, shaper: &mut CachingShaper,
fonts_lookup: &mut FontLookup, canvas: &mut Canvas) -> (bool, Option<Instant>) { canvas: &mut Canvas) -> (bool, Option<Instant>) {
let (render, scheduled_update) = self.blink_status.update_status(&cursor); let (render, scheduled_update) = self.blink_status.update_status(&cursor);
self.previous_position = { self.previous_position = {
@ -233,9 +232,10 @@ impl CursorRenderer {
canvas.save(); canvas.save();
canvas.clip_path(&path, None, Some(false)); canvas.clip_path(&path, None, Some(false));
canvas.draw_text_blob( let blobs = &shaper.shape_cached(&character.to_string(), 1, false, false);
shaper.shape_cached(&character.to_string(), &fonts_lookup.name.clone(), fonts_lookup.base_size, 1, false, false, &fonts_lookup.size(1).normal), for blob in blobs.iter() {
destination, &paint); canvas.draw_text_blob(&blob, destination, &paint);
}
canvas.restore(); canvas.restore();
} }

@ -1,83 +0,0 @@
use std::collections::HashMap;
use skulpin::skia_safe::{Typeface, Font, FontStyle};
use crate::editor::Style;
pub struct Fonts {
pub name: String,
pub size: f32,
pub normal: Font,
pub bold: Font,
pub italic: Font,
pub bold_italic: Font
}
impl Fonts {
fn new(name: &str, size: f32) -> Fonts {
let normal = Font::from_typeface(
Typeface::new(name, FontStyle::normal()).expect("Could not load normal font file"),
size);
let mut bold = Font::from_typeface(
Typeface::new(name, FontStyle::bold()).expect("Could not load bold font file"),
size);
if bold.is_embolden() {
dbg!("Disabled embolden for normal bold");
bold.set_embolden(false);
}
let italic = Font::from_typeface(
Typeface::new(name, FontStyle::italic()).expect("Could not load italic font file"),
size);
let mut bold_italic = Font::from_typeface(
Typeface::new(name, FontStyle::bold_italic()).expect("Could not load bold italic font file"),
size);
if bold_italic.is_embolden() {
dbg!("Disabled embolden for italic bold");
bold_italic.set_embolden(false);
}
Fonts {
name: name.to_string(), size,
normal, bold, italic, bold_italic
}
}
pub fn get(&self, style: &Style) -> &Font {
match (style.bold, style.italic) {
(false, false) => &self.normal,
(true, false) => &self.bold,
(false, true) => &self.italic,
(true, true) => &self.bold_italic
}
}
}
pub struct FontLookup {
pub name: String,
pub base_size: f32,
pub loaded_fonts: HashMap<u16, Fonts>
}
impl FontLookup {
pub fn new(name: &str, base_size: f32) -> FontLookup {
let mut lookup = FontLookup {
name: name.to_string(),
base_size,
loaded_fonts: HashMap::new()
};
lookup.size(1);
lookup.size(2);
lookup.size(3);
lookup
}
pub fn size(&mut self, size_multiplier: u16) -> &Fonts {
let name = self.name.clone();
let base_size = self.base_size;
self.loaded_fonts.entry(size_multiplier).or_insert_with(|| {
Fonts::new(&name, base_size * size_multiplier as f32)
})
}
}

@ -1,4 +1,3 @@
use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use skulpin::CoordinateSystemHelper; use skulpin::CoordinateSystemHelper;
@ -7,16 +6,11 @@ use skulpin::skia_safe::gpu::SurfaceOrigin;
mod caching_shaper; mod caching_shaper;
mod cursor_renderer; mod cursor_renderer;
mod fonts;
pub use caching_shaper::CachingShaper; pub use caching_shaper::CachingShaper;
use cursor_renderer::CursorRenderer; use cursor_renderer::CursorRenderer;
use fonts::FontLookup; use crate::editor::{EDITOR, Style, Colors};
use crate::editor::{EDITOR, Editor, Style, Colors};
const DEFAULT_FONT_NAME: &str = "Delugia Nerd Font";
const DEFAULT_FONT_SIZE: f32 = 14.0;
#[derive(new)] #[derive(new)]
pub struct DrawResult { pub struct DrawResult {
@ -28,7 +22,6 @@ pub struct DrawResult {
pub struct Renderer { pub struct Renderer {
surface: Option<Surface>, surface: Option<Surface>,
paint: Paint, paint: Paint,
fonts_lookup: FontLookup,
shaper: CachingShaper, shaper: CachingShaper,
pub font_width: f32, pub font_width: f32,
@ -44,17 +37,15 @@ impl Renderer {
let mut shaper = CachingShaper::new(); let mut shaper = CachingShaper::new();
let mut fonts_lookup = FontLookup::new(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE); let (font_width, font_height) = shaper.font_base_dimensions();
let (font_width, font_height) = shaper.font_base_dimensions(&mut fonts_lookup);
let cursor_renderer = CursorRenderer::new(); let cursor_renderer = CursorRenderer::new();
Renderer { surface, paint, fonts_lookup, shaper, font_width, font_height, cursor_renderer } Renderer { surface, paint, shaper, font_width, font_height, cursor_renderer }
} }
fn set_font(&mut self, name: &str, size: f32) { fn set_font(&mut self, name: Option<&str>, size: Option<f32>) {
self.fonts_lookup = FontLookup::new(name, size); self.shaper.change_font(name, size);
self.shaper.clear(); let (font_width, font_height) = self.shaper.font_base_dimensions();
let (font_width, font_height) = self.shaper.font_base_dimensions(&mut self.fonts_lookup);
self.font_width = font_width; self.font_width = font_width;
self.font_height = font_height; self.font_height = font_height;
} }
@ -70,7 +61,7 @@ impl Renderer {
fn draw_background(&mut self, canvas: &mut Canvas, text: &str, grid_pos: (u64, u64), size: u16, style: &Option<Style>, default_colors: &Colors) { fn draw_background(&mut self, canvas: &mut Canvas, text: &str, grid_pos: (u64, u64), size: u16, style: &Option<Style>, default_colors: &Colors) {
let region = self.compute_text_region(text, grid_pos, size); let region = self.compute_text_region(text, grid_pos, size);
let style = style.clone().unwrap_or(Style::new(default_colors.clone())); let style = style.clone().unwrap_or_else(|| Style::new(default_colors.clone()));
self.paint.set_color(style.background(default_colors).to_color()); self.paint.set_color(style.background(default_colors).to_color());
canvas.draw_rect(region, &self.paint); canvas.draw_rect(region, &self.paint);
@ -82,7 +73,7 @@ impl Renderer {
let y = grid_y as f32 * self.font_height; let y = grid_y as f32 * self.font_height;
let width = text.chars().count() as f32 * self.font_width; let width = text.chars().count() as f32 * self.font_width;
let style = style.clone().unwrap_or(Style::new(default_colors.clone())); let style = style.clone().unwrap_or_else(|| Style::new(default_colors.clone()));
canvas.save(); canvas.save();
@ -91,21 +82,17 @@ 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 (_, metrics) = self.fonts_lookup.size(size).get(&style).metrics(); let line_position = self.shaper.underline_position(size);
let line_position = metrics.underline_position().unwrap();
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);
} }
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.len() > 0 { if !text.is_empty() {
let font_name = self.fonts_lookup.name.clone(); for blob in self.shaper.shape_cached(text, size, style.bold, style.italic).iter() {
let font_size = self.fonts_lookup.base_size; canvas.draw_text_blob(blob, (x, y), &self.paint);
let font = self.fonts_lookup.size(size).get(&style); }
let blob = self.shaper.shape_cached(text, &font_name, font_size, size, style.bold, style.italic, font);
canvas.draw_text_blob(blob, (x, y), &self.paint);
} }
if style.strikethrough { if style.strikethrough {
@ -125,15 +112,15 @@ impl Renderer {
editor.default_colors.clone(), editor.default_colors.clone(),
editor.cursor.clone(), editor.cursor.clone(),
editor.font_name.clone(), editor.font_name.clone(),
editor.font_size.clone() editor.font_size
) )
}; };
let font_changed = let font_changed =
font_name.clone().map(|new_name| new_name != self.fonts_lookup.name).unwrap_or(false) || font_name.clone().map(|new_name| new_name != self.shaper.font_name).unwrap_or(false) ||
font_size.map(|new_size| new_size != self.fonts_lookup.base_size).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.unwrap_or(DEFAULT_FONT_NAME.to_string()), font_size.unwrap_or(DEFAULT_FONT_SIZE)); self.set_font(font_name.as_deref(), font_size);
} }
if should_clear { if should_clear {
@ -172,8 +159,8 @@ impl Renderer {
cursor, &default_colors, cursor, &default_colors,
self.font_width, self.font_height, self.font_width, self.font_height,
&mut self.paint, &mut self.shaper, &mut self.paint, &mut self.shaper,
&mut self.fonts_lookup, gpu_canvas); gpu_canvas);
DrawResult::new(draw_commands.len() > 0 || cursor_animating, font_changed, scheduled_cursor_update) DrawResult::new(!draw_commands.is_empty() || cursor_animating, font_changed, scheduled_cursor_update)
} }
} }

Loading…
Cancel
Save