From 5a880d06aa6cc3e10d16298aec838689e5ab2333 Mon Sep 17 00:00:00 2001 From: keith Date: Thu, 23 Jan 2020 13:51:37 -0800 Subject: [PATCH] remove fonts cache and make first attempt at font fallback --- Cargo.toml | 2 - src/renderer/caching_shaper.rs | 191 ++++++++++++++++++++++++-------- src/renderer/cursor_renderer.rs | 14 +-- src/renderer/fonts.rs | 83 -------------- src/renderer/mod.rs | 53 ++++----- 5 files changed, 169 insertions(+), 174 deletions(-) delete mode 100644 src/renderer/fonts.rs diff --git a/Cargo.toml b/Cargo.toml index 57bd8b8..563937c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,10 @@ lru = "0.4.3" skulpin = "0.5" derive-new = "0.5" env_logger = "0.7.1" -#neovim-lib = { git = "https://github.com/daa84/neovim-lib", version = "0.6" } rmpv = "0.4.2" msgbox = "0.4.0" rust-embed = { version = "5.2.0", features = ["debug-embed"] } image = "0.22.3" -#nvim-rs = "0.1.0" nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "futures", features = [ "use_tokio" ] } tokio = { version = "0.2.9", features = [ "blocking", "process", "time" ] } async-trait = "0.1.18" diff --git a/src/renderer/caching_shaper.rs b/src/renderer/caching_shaper.rs index 34f300a..025bfa3 100644 --- a/src/renderer/caching_shaper.rs +++ b/src/renderer/caching_shaper.rs @@ -1,109 +1,194 @@ +use std::sync::Arc; use std::collections::HashMap; 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 skribo::{layout_run, FontRef, TextStyle}; - -use super::fonts::FontLookup; +use skribo::{layout, layout_run, FontRef as SkriboFont, FontFamily, FontCollection, TextStyle}; 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 { - pub name: String, - pub base_size: String, // hack because comparison of floats doesn't work pub scale: u16, pub bold: bool, pub italic: bool } -#[derive(new, Clone, Hash, PartialEq, Eq)] +#[derive(new, Clone, Hash, PartialEq, Eq, Debug)] struct ShapeKey { pub text: String, pub font_key: FontKey } +struct FontPair { + normal: (SkiaFont, SkriboFont), + emoji: (SkiaFont, SkriboFont) +} + +#[derive(Debug)] pub struct CachingShaper { - font_cache: LruCache, - blob_cache: LruCache + pub font_name: String, + pub base_size: f32, + font_cache: LruCache, + blob_cache: LruCache> +} + +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 { pub fn new() -> CachingShaper { CachingShaper { + font_name: DEFAULT_FONT.to_string(), + base_size: DEFAULT_FONT_SIZE, 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) { - let source = SystemSource::new(); - let font_name = font_key.name.clone(); - let font = source - .select_family_by_name(&font_name) - .expect("Failed to load by postscript name") - .fonts()[0] - .load() - .unwrap(); - self.font_cache.put(font_key.clone(), FontRef::new(font)); + let font_pair = FontPair { + normal: build_fonts(font_key, &self.font_name, self.base_size), + 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() } - pub fn shape(&mut self, text: &str, font_name: &str, base_size: f32, scale: u16, bold: bool, italic: bool, font: &Font) -> TextBlob { - let font_key = FontKey::new(font_name.to_string(), base_size.to_string(), scale, bold, italic); - let font_ref = self.get_font(&font_key); + 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 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 metrics = font_ref.font.metrics(); - let ascent = metrics.ascent * base_size / metrics.units_per_em as f32; - let (glyphs, positions) = blob_builder.alloc_run_pos_h(font, count, ascent, None); + let mut groups = Vec::new(); + let mut group = Vec::new(); + let mut previous_font: Option = None; - for (i, glyph_id) in layout.glyphs.iter().map(|glyph| glyph.glyph_id as u16).enumerate() { - glyphs[i] = glyph_id; + for glyph in layout.glyphs { + 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() { - positions[i] = offset; + if !group.is_empty() { + 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 { - let font_key = FontKey::new(font_name.to_string(), base_size.to_string(), scale, bold, italic); + 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); if !self.blob_cache.contains(&key) { - let blob = self.shape(text, font_name, base_size, scale, bold, italic, &font); - self.blob_cache.put(key.clone(), blob); + let blobs = self.shape(text, scale, bold, italic); + self.blob_cache.put(key.clone(), blobs); } self.blob_cache.get(&key).unwrap() } - pub fn clear(&mut self) { + pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option) { 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, font_lookup: &mut FontLookup) -> (f32, f32) { - let base_fonts = font_lookup.size(1); - let normal_font = &base_fonts.normal; - let (_, metrics) = normal_font.metrics(); + 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) = skia_font.metrics(); 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 font_ref = self.get_font(&font_key); - let style = TextStyle { size: font_lookup.base_size }; - let layout = layout_run(&style, font_ref, STANDARD_CHARACTER_STRING); + 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 glyph_advances: Vec = glyph_offsets.windows(2).map(|pair| pair[1] - pair[0]).collect(); @@ -118,4 +203,12 @@ 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() + } } diff --git a/src/renderer/cursor_renderer.rs b/src/renderer/cursor_renderer.rs index 04fbf7a..25a0982 100644 --- a/src/renderer/cursor_renderer.rs +++ b/src/renderer/cursor_renderer.rs @@ -1,10 +1,9 @@ -use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use skulpin::skia_safe::{Canvas, Paint, Path, Point}; -use crate::renderer::{CachingShaper, FontLookup}; -use crate::editor::{EDITOR, Colors, Cursor, CursorShape, Editor}; +use crate::renderer::CachingShaper; +use crate::editor::{EDITOR, Colors, Cursor, CursorShape}; const AVERAGE_MOTION_PERCENTAGE: f32 = 0.7; const MOTION_PERCENTAGE_SPREAD: f32 = 0.5; @@ -172,7 +171,7 @@ impl CursorRenderer { cursor: Cursor, default_colors: &Colors, font_width: f32, font_height: f32, paint: &mut Paint, shaper: &mut CachingShaper, - fonts_lookup: &mut FontLookup, canvas: &mut Canvas) -> (bool, Option) { + canvas: &mut Canvas) -> (bool, Option) { let (render, scheduled_update) = self.blink_status.update_status(&cursor); self.previous_position = { @@ -233,9 +232,10 @@ impl CursorRenderer { canvas.save(); canvas.clip_path(&path, None, Some(false)); - canvas.draw_text_blob( - shaper.shape_cached(&character.to_string(), &fonts_lookup.name.clone(), fonts_lookup.base_size, 1, false, false, &fonts_lookup.size(1).normal), - destination, &paint); + let blobs = &shaper.shape_cached(&character.to_string(), 1, false, false); + for blob in blobs.iter() { + canvas.draw_text_blob(&blob, destination, &paint); + } canvas.restore(); } diff --git a/src/renderer/fonts.rs b/src/renderer/fonts.rs deleted file mode 100644 index 6ff959d..0000000 --- a/src/renderer/fonts.rs +++ /dev/null @@ -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 -} - -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) - }) - } -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ab7ebaa..90ebe95 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,4 +1,3 @@ -use std::sync::{Arc, Mutex}; use std::time::Instant; use skulpin::CoordinateSystemHelper; @@ -7,16 +6,11 @@ use skulpin::skia_safe::gpu::SurfaceOrigin; mod caching_shaper; mod cursor_renderer; -mod fonts; pub use caching_shaper::CachingShaper; use cursor_renderer::CursorRenderer; -use fonts::FontLookup; -use crate::editor::{EDITOR, Editor, Style, Colors}; - -const DEFAULT_FONT_NAME: &str = "Delugia Nerd Font"; -const DEFAULT_FONT_SIZE: f32 = 14.0; +use crate::editor::{EDITOR, Style, Colors}; #[derive(new)] pub struct DrawResult { @@ -28,7 +22,6 @@ pub struct DrawResult { pub struct Renderer { surface: Option, paint: Paint, - fonts_lookup: FontLookup, shaper: CachingShaper, pub font_width: f32, @@ -44,17 +37,15 @@ impl Renderer { 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(&mut fonts_lookup); + let (font_width, font_height) = shaper.font_base_dimensions(); 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) { - self.fonts_lookup = FontLookup::new(name, size); - self.shaper.clear(); - let (font_width, font_height) = self.shaper.font_base_dimensions(&mut self.fonts_lookup); + fn set_font(&mut self, name: Option<&str>, size: Option) { + self.shaper.change_font(name, size); + let (font_width, font_height) = self.shaper.font_base_dimensions(); self.font_width = font_width; 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