font fallback guifont extension

macos-click-through
keith 5 years ago
parent 2c196f84f1
commit e8263f1f19

@ -33,8 +33,7 @@ pub struct Editor {
pub grid: CharacterGrid, pub grid: CharacterGrid,
pub title: String, pub title: String,
pub mouse_enabled: bool, pub mouse_enabled: bool,
pub font_name: Option<String>, pub guifont: Option<String>,
pub font_size: Option<f32>,
pub cursor: Cursor, pub cursor: Cursor,
pub default_style: Arc<Style>, pub default_style: Arc<Style>,
pub defined_styles: HashMap<u64, Arc<Style>>, pub defined_styles: HashMap<u64, Arc<Style>>,
@ -49,8 +48,7 @@ impl Editor {
grid: CharacterGrid::new(window_geometry_or_default()), grid: CharacterGrid::new(window_geometry_or_default()),
title: "Neovide".to_string(), title: "Neovide".to_string(),
mouse_enabled: true, mouse_enabled: true,
font_name: None, guifont: None,
font_size: None,
cursor: Cursor::new(), cursor: Cursor::new(),
default_style: Arc::new(Style::new(Colors::new( default_style: Arc::new(Style::new(Colors::new(
Some(colors::WHITE), Some(colors::WHITE),
@ -307,14 +305,8 @@ impl Editor {
fn set_option(&mut self, gui_option: GuiOption) { fn set_option(&mut self, gui_option: GuiOption) {
trace!("Option set {:?}", &gui_option); trace!("Option set {:?}", &gui_option);
match gui_option { match gui_option {
GuiOption::GuiFont(font_description) => { GuiOption::GuiFont(guifont) => {
let parts: Vec<&str> = font_description.split(':').collect(); self.guifont = Some(guifont);
self.font_name = Some(parts[0].to_string());
for part in parts.iter().skip(1) {
if part.starts_with('h') && part.len() > 1 {
self.font_size = part[1..].parse::<f32>().ok();
}
}
} }
_ => {} _ => {}
} }

@ -10,7 +10,11 @@ use log::{trace, warn};
use lru::LruCache; use lru::LruCache;
use skribo::{FontCollection, FontFamily, FontRef as SkriboFont, LayoutSession, TextStyle}; use skribo::{FontCollection, FontFamily, FontRef as SkriboFont, LayoutSession, TextStyle};
use skulpin::skia_safe::{Data, Font as SkiaFont, TextBlob, TextBlobBuilder, Typeface}; use skulpin::skia_safe::{Data, Font as SkiaFont, TextBlob, TextBlobBuilder, Typeface};
use std::collections::HashMap; use std::collections::HashMap;
use std::iter;
use super::font_options::FontOptions;
const STANDARD_CHARACTER_STRING: &str = const STANDARD_CHARACTER_STRING: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
@ -158,13 +162,12 @@ struct ShapeKey {
pub fn build_collection_by_font_name( pub fn build_collection_by_font_name(
loader: &mut FontLoader, loader: &mut FontLoader,
font_name: Option<&str>, fallback_list: &Vec<String>,
bold: bool, bold: bool,
italic: bool, italic: bool,
) -> FontCollection { ) -> FontCollection {
let mut collection = FontCollection::new(); let mut collection = FontCollection::new();
if let Some(font_name) = font_name {
let weight = if bold { Weight::BOLD } else { Weight::NORMAL }; let weight = if bold { Weight::BOLD } else { Weight::NORMAL };
let style = if italic { Style::Italic } else { Style::Normal }; let style = if italic { Style::Italic } else { Style::Normal };
let properties = Properties { let properties = Properties {
@ -173,7 +176,7 @@ pub fn build_collection_by_font_name(
stretch: Stretch::NORMAL, stretch: Stretch::NORMAL,
}; };
let gui_fonts = &[font_name, SYSTEM_DEFAULT_FONT]; let gui_fonts = fallback_list.iter().map(|fallback_item| fallback_item.as_ref()).chain(iter::once(SYSTEM_DEFAULT_FONT));
for font_name in gui_fonts { for font_name in gui_fonts {
if let Some(family) = loader.get_or_load(font_name, false) { if let Some(family) = loader.get_or_load(font_name, false) {
@ -183,7 +186,6 @@ pub fn build_collection_by_font_name(
} }
} }
} }
}
for font in &[SYSTEM_SYMBOL_FONT, SYSTEM_EMOJI_FONT] { for font in &[SYSTEM_SYMBOL_FONT, SYSTEM_EMOJI_FONT] {
if let Some(family) = loader.get_or_load(font, false) { if let Some(family) = loader.get_or_load(font, false) {
@ -207,11 +209,11 @@ struct FontSet {
} }
impl FontSet { impl FontSet {
fn new(font_name: Option<&str>, mut loader: &mut FontLoader) -> FontSet { fn new(fallback_list: &Vec<String>, mut loader: &mut FontLoader) -> FontSet {
FontSet { FontSet {
normal: build_collection_by_font_name(&mut loader, font_name, false, false), normal: build_collection_by_font_name(&mut loader, fallback_list, false, false),
bold: build_collection_by_font_name(&mut loader, font_name, true, false), bold: build_collection_by_font_name(&mut loader, fallback_list, true, false),
italic: build_collection_by_font_name(&mut loader, font_name, false, true), italic: build_collection_by_font_name(&mut loader, fallback_list, false, true),
} }
} }
@ -225,8 +227,7 @@ impl FontSet {
} }
pub struct CachingShaper { pub struct CachingShaper {
pub font_name: Option<String>, pub options: FontOptions,
pub base_size: f32,
font_set: FontSet, font_set: FontSet,
font_loader: FontLoader, font_loader: FontLoader,
font_cache: LruCache<String, SkiaFont>, font_cache: LruCache<String, SkiaFont>,
@ -243,12 +244,14 @@ fn build_skia_font_from_skribo_font(skribo_font: &SkriboFont, base_size: f32) ->
impl CachingShaper { impl CachingShaper {
pub fn new() -> CachingShaper { pub fn new() -> CachingShaper {
let options = FontOptions::new(String::from(SYSTEM_DEFAULT_FONT), DEFAULT_FONT_SIZE);
let mut loader = FontLoader::new(); let mut loader = FontLoader::new();
let font_set = FontSet::new(&options.fallback_list, &mut loader);
CachingShaper { CachingShaper {
font_name: Some(String::from(SYSTEM_DEFAULT_FONT)), options,
base_size: DEFAULT_FONT_SIZE, font_set,
font_set: FontSet::new(Some(SYSTEM_DEFAULT_FONT), &mut loader),
font_loader: loader, font_loader: loader,
font_cache: LruCache::new(10), font_cache: LruCache::new(10),
blob_cache: LruCache::new(10000), blob_cache: LruCache::new(10000),
@ -259,7 +262,7 @@ impl CachingShaper {
let font_name = skribo_font.font.postscript_name()?; let font_name = skribo_font.font.postscript_name()?;
if !self.font_cache.contains(&font_name) { if !self.font_cache.contains(&font_name) {
let font = build_skia_font_from_skribo_font(skribo_font, self.base_size)?; let font = build_skia_font_from_skribo_font(skribo_font, self.options.size)?;
self.font_cache.put(font_name.clone(), font); self.font_cache.put(font_name.clone(), font);
} }
@ -279,12 +282,12 @@ impl CachingShaper {
pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> { pub fn shape(&mut self, text: &str, bold: bool, italic: bool) -> Vec<TextBlob> {
let style = TextStyle { let style = TextStyle {
size: self.base_size, size: self.options.size,
}; };
let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic)); let session = LayoutSession::create(text, &style, &self.font_set.get(bold, italic));
let metrics = self.metrics(); let metrics = self.metrics();
let ascent = metrics.ascent * self.base_size / metrics.units_per_em as f32; let ascent = metrics.ascent * self.options.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() {
@ -321,21 +324,23 @@ impl CachingShaper {
self.blob_cache.get(&key).unwrap() self.blob_cache.get(&key).unwrap()
} }
pub fn change_font(&mut self, font_name: Option<&str>, base_size: Option<f32>) { pub fn update_font(&mut self, guifont_setting: &str) -> bool {
trace!("Font changed {:?} {:?}", &font_name, &base_size); let updated = self.options.update(guifont_setting);
self.font_name = font_name.map(|name| name.to_string()); if updated {
self.base_size = base_size.unwrap_or(DEFAULT_FONT_SIZE); trace!("Font changed: {:?}", self.options);
self.font_set = FontSet::new(font_name, &mut self.font_loader); self.font_set = FontSet::new(&self.options.fallback_list, &mut self.font_loader);
self.font_cache.clear(); self.font_cache.clear();
self.blob_cache.clear(); self.blob_cache.clear();
} }
updated
}
pub fn font_base_dimensions(&mut self) -> (f32, f32) { pub fn font_base_dimensions(&mut self) -> (f32, f32) {
let metrics = self.metrics(); let metrics = self.metrics();
let font_height = let font_height =
(metrics.ascent - metrics.descent) * self.base_size / metrics.units_per_em as f32; (metrics.ascent - metrics.descent) * self.options.size / metrics.units_per_em as f32;
let style = TextStyle { let style = TextStyle {
size: self.base_size, size: self.options.size,
}; };
let session = let session =
LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal); LayoutSession::create(STANDARD_CHARACTER_STRING, &style, &self.font_set.normal);
@ -363,6 +368,6 @@ impl CachingShaper {
pub fn underline_position(&mut self) -> f32 { pub fn underline_position(&mut self) -> f32 {
let metrics = self.metrics(); let metrics = self.metrics();
-metrics.underline_position * self.base_size / metrics.units_per_em as f32 -metrics.underline_position * self.options.size / metrics.units_per_em as f32
} }
} }

@ -0,0 +1,55 @@
#[derive(Clone, PartialEq, Debug)]
pub struct FontOptions {
previous_guifont_setting: Option<String>,
pub fallback_list: Vec<String>,
pub size: f32
}
impl FontOptions {
pub fn new(name: String, size: f32) -> FontOptions {
FontOptions {
previous_guifont_setting: None,
fallback_list: vec!(name),
size
}
}
pub fn update(self: &mut FontOptions, guifont_setting: &str) -> bool {
if self.previous_guifont_setting.is_some() &&
guifont_setting == self.previous_guifont_setting.as_ref().unwrap() {
return false;
}
self.previous_guifont_setting = Some(guifont_setting.to_string());
let mut parts = guifont_setting
.split(':')
.filter(|part| !part.is_empty());
let mut updated = false;
if let Some(parts) = parts.next() {
let parsed_fallback_list: Vec<String> = parts
.split(',')
.filter(|fallback| !fallback.is_empty())
.map(|fallback| fallback.to_string())
.collect();
if parsed_fallback_list.len() > 0 && self.fallback_list != parsed_fallback_list {
self.fallback_list = parsed_fallback_list;
updated = true;
}
}
for part in parts {
if part.starts_with('h') && part.len() > 1 {
if let Some(size) = part[1..].parse::<f32>().ok() {
if (self.size - size).abs() > std::f32::EPSILON {
self.size = size;
updated = true;
}
}
}
}
updated
}
}

@ -6,8 +6,10 @@ use skulpin::skia_safe::{colors, dash_path_effect, Budgeted, Canvas, Paint, Rect
use skulpin::CoordinateSystemHelper; use skulpin::CoordinateSystemHelper;
mod caching_shaper; mod caching_shaper;
pub mod font_options;
pub mod cursor_renderer; pub mod cursor_renderer;
pub use font_options::*;
pub use caching_shaper::CachingShaper; pub use caching_shaper::CachingShaper;
use crate::editor::{Style, EDITOR}; use crate::editor::{Style, EDITOR};
@ -44,12 +46,15 @@ impl Renderer {
} }
} }
fn set_font(&mut self, name: Option<&str>, size: Option<f32>) { fn update_font(&mut self, guifont_setting: &str) -> bool {
self.shaper.change_font(name, size); let updated = self.shaper.update_font(guifont_setting);
if updated {
let (font_width, font_height) = self.shaper.font_base_dimensions(); let (font_width, font_height) = self.shaper.font_base_dimensions();
self.font_width = font_width; self.font_width = font_width;
self.font_height = font_height; self.font_height = font_height;
} }
updated
}
fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect { fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect {
let (grid_x, grid_y) = grid_pos; let (grid_x, grid_y) = grid_pos;
@ -100,7 +105,7 @@ impl Renderer {
if style.underline || style.undercurl { if style.underline || style.undercurl {
let line_position = self.shaper.underline_position(); let line_position = self.shaper.underline_position();
let stroke_width = self.shaper.base_size / 10.0; let stroke_width = self.shaper.options.size / 10.0;
self.paint self.paint
.set_color(style.special(&default_style.colors).to_color()); .set_color(style.special(&default_style.colors).to_color());
self.paint.set_stroke_width(stroke_width); self.paint.set_stroke_width(stroke_width);
@ -152,33 +157,20 @@ impl Renderer {
) -> bool { ) -> bool {
trace!("Rendering"); trace!("Rendering");
let ((draw_commands, should_clear), default_style, cursor, font_name, font_size) = { let ((draw_commands, should_clear), default_style, cursor, guifont_setting) = {
let mut editor = EDITOR.lock(); let mut editor = EDITOR.lock();
( (
editor.build_draw_commands(), editor.build_draw_commands(),
editor.default_style.clone(), editor.default_style.clone(),
editor.cursor.clone(), editor.cursor.clone(),
editor.font_name.clone(), editor.guifont.clone()
editor.font_size,
) )
}; };
let current_font = Some(self.shaper.font_name.clone().unwrap_or(String::from(""))); let font_changed = guifont_setting
let editor_font = if font_name.clone().unwrap_or_default().is_empty() { .map(|guifont| self.update_font(&guifont))
&current_font
} else {
&font_name
};
let font_changed = current_font != *editor_font
|| font_size
.map(|new_size| (new_size - self.shaper.base_size).abs() > std::f32::EPSILON)
.unwrap_or(false); .unwrap_or(false);
if font_changed {
self.set_font(font_name.as_deref(), font_size);
}
if should_clear { if should_clear {
self.surface = None; self.surface = None;
} }

Loading…
Cancel
Save