diff --git a/src/renderer/fonts/caching_shaper.rs b/src/renderer/fonts/caching_shaper.rs index 1f2bb18..076c65c 100644 --- a/src/renderer/fonts/caching_shaper.rs +++ b/src/renderer/fonts/caching_shaper.rs @@ -12,8 +12,6 @@ use unicode_segmentation::UnicodeSegmentation; use super::font_loader::*; use super::font_options::*; -const DEFAULT_FONT_SIZE: f32 = 14.0; - #[derive(new, Clone, Hash, PartialEq, Eq, Debug)] struct ShapeKey { pub cells: Vec, @@ -22,7 +20,7 @@ struct ShapeKey { } pub struct CachingShaper { - pub options: Option, + options: FontOptions, font_loader: FontLoader, blob_cache: LruCache>, shape_context: ShapeContext, @@ -30,11 +28,12 @@ pub struct CachingShaper { } impl CachingShaper { - pub fn new(device_scale_factor: f32) -> CachingShaper { - let scale_factor = points_to_pixels(device_scale_factor); + pub fn new(scale_factor: f32) -> CachingShaper { + let options = FontOptions::default(); + let font_size = options.size * scale_factor; CachingShaper { - options: None, - font_loader: FontLoader::new(DEFAULT_FONT_SIZE * scale_factor), + options, + font_loader: FontLoader::new(font_size), blob_cache: LruCache::new(10000), shape_context: ShapeContext::new(), scale_factor, @@ -42,21 +41,8 @@ impl CachingShaper { } fn current_font_pair(&mut self) -> Arc { - let default_key = FontKey { - italic: false, - bold: false, - font_selection: FontSelection::Default, - }; - - let font_key = self - .options - .as_ref() - .map(|options| FontKey { - italic: options.italic, - bold: options.bold, - font_selection: options.fallback_list.first().unwrap().clone().into(), - }) - .unwrap_or_else(|| default_key.clone()); + let default_key = FontKey::default(); + let font_key = FontKey::from(&self.options); if let Some(font_pair) = self.font_loader.get_or_load(&font_key) { return font_pair; @@ -68,26 +54,28 @@ impl CachingShaper { } pub fn current_size(&self) -> f32 { - self.options - .as_ref() - .map(|options| options.size) - .unwrap_or(DEFAULT_FONT_SIZE) - * self.scale_factor + self.options.size * self.scale_factor } - pub fn update_font(&mut self, guifont_setting: &str) -> bool { - let new_options = FontOptions::parse(guifont_setting, DEFAULT_FONT_SIZE); + pub fn update_scale_factor(&mut self, scale_factor: f32) { + trace!("scale_factor changed: {:.2}", scale_factor); + self.scale_factor = scale_factor; + self.reset_font_loader(); + } - if new_options != self.options && new_options.is_some() { - self.font_loader = - FontLoader::new(new_options.as_ref().unwrap().size * self.scale_factor); - self.blob_cache.clear(); - self.options = new_options; + pub fn update_font(&mut self, guifont_setting: &str) { + trace!("Updating font: {}", guifont_setting); - true - } else { - false - } + self.options = FontOptions::parse(guifont_setting); + self.reset_font_loader(); + } + + fn reset_font_loader(&mut self) { + let font_size = self.options.size * self.scale_factor; + trace!("Using font_size: {:.2}px", font_size); + + self.font_loader = FontLoader::new(font_size); + self.blob_cache.clear(); } fn metrics(&mut self) -> Metrics { @@ -156,44 +144,26 @@ impl CachingShaper { // Create font fallback list let mut font_fallback_keys = Vec::new(); - // Add guifont fallback list - if let Some(options) = &self.options { - font_fallback_keys.extend(options.fallback_list.iter().map(|font_name| FontKey { - italic: options.italic || italic, - bold: options.bold || bold, - font_selection: font_name.into(), - })); - - // Add default font - font_fallback_keys.push(FontKey { - italic: options.italic || italic, - bold: options.bold || bold, - font_selection: FontSelection::Default, - }); - - // Add skia fallback - font_fallback_keys.push(FontKey { - italic: options.italic || italic, - bold: options.bold || bold, - font_selection: cluster.chars()[0].ch.into(), - }); - } else { - // No confgured option. Default to not italic and not bold versions - - // Add default font - font_fallback_keys.push(FontKey { - italic, - bold, - font_selection: FontSelection::Default, - }); - - // Add skia fallback - font_fallback_keys.push(FontKey { - italic, - bold, - font_selection: cluster.chars()[0].ch.into(), - }); - } + // Add parsed fonts from guifont + font_fallback_keys.extend(self.options.font_list.iter().map(|font_name| FontKey { + italic: self.options.italic || italic, + bold: self.options.bold || bold, + font_selection: font_name.into(), + })); + + // Add default font + font_fallback_keys.push(FontKey { + italic: self.options.italic || italic, + bold: self.options.bold || bold, + font_selection: FontSelection::Default, + }); + + // Add skia fallback + font_fallback_keys.push(FontKey { + italic, + bold, + font_selection: cluster.chars()[0].ch.into(), + }); // Add last resort font_fallback_keys.push(FontKey { @@ -312,25 +282,3 @@ impl CachingShaper { self.blob_cache.get(&key).unwrap() } } - -fn points_to_pixels(value: f32) -> f32 { - // Fonts in neovim are using points, not pixels. - // - // Skia docs is incorrectly stating it uses points, but uses pixels: - // https://api.skia.org/classSkFont.html#a7e28a156a517d01bc608c14c761346bf - // https://github.com/mono/SkiaSharp/issues/1147#issuecomment-587421201 - // - // So, we need to convert points to pixels. - // - // In reality, this depends on DPI/PPI of monitor, but here we only care about converting - // from points to pixels, so this is standard constant values. - let pixels_per_inch = 96.0; - let points_per_inch = 72.0; - // On macos points == pixels - #[cfg(target_os = "macos")] - let points_per_inch = 96.0; - - let pixels_per_point = pixels_per_inch / points_per_inch; - - value * pixels_per_point -} diff --git a/src/renderer/fonts/font_loader.rs b/src/renderer/fonts/font_loader.rs index 49a03bd..9021e42 100644 --- a/src/renderer/fonts/font_loader.rs +++ b/src/renderer/fonts/font_loader.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use lru::LruCache; use skia_safe::{font::Edging, Data, Font, FontHinting, FontMgr, FontStyle, Typeface}; +use super::font_options::FontOptions; use super::swash_font::SwashFont; #[derive(RustEmbed)] @@ -54,6 +55,26 @@ pub struct FontKey { pub font_selection: FontSelection, } +impl Default for FontKey { + fn default() -> Self { + FontKey { + italic: false, + bold: false, + font_selection: FontSelection::Default, + } + } +} + +impl From<&FontOptions> for FontKey { + fn from(options: &FontOptions) -> FontKey { + FontKey { + italic: options.italic, + bold: options.bold, + font_selection: options.primary_font(), + } + } +} + #[derive(Debug, Hash, PartialEq, Eq, Clone)] pub enum FontSelection { Name(String), diff --git a/src/renderer/fonts/font_options.rs b/src/renderer/fonts/font_options.rs index d73b079..5e3b7b1 100644 --- a/src/renderer/fonts/font_options.rs +++ b/src/renderer/fonts/font_options.rs @@ -1,30 +1,33 @@ +use super::font_loader::FontSelection; + +const DEFAULT_FONT_SIZE: f32 = 14.0; + #[derive(Clone, Debug)] pub struct FontOptions { - guifont_setting: Option, - pub fallback_list: Vec, + pub font_list: Vec, pub size: f32, pub bold: bool, pub italic: bool, } impl FontOptions { - pub fn parse(guifont_setting: &str, default_size: f32) -> Option { - let mut fallback_list = None; - let mut size = default_size; + pub fn parse(guifont_setting: &str) -> FontOptions { + let mut font_list = Vec::new(); + let mut size = DEFAULT_FONT_SIZE; let mut bold = false; let mut italic = false; let mut parts = guifont_setting.split(':').filter(|part| !part.is_empty()); if let Some(parts) = parts.next() { - let parsed_fallback_list: Vec = parts + let parsed_font_list: Vec = parts .split(',') .filter(|fallback| !fallback.is_empty()) .map(|fallback| fallback.to_string()) .collect(); - if !parsed_fallback_list.is_empty() { - fallback_list = Some(parsed_fallback_list); + if !parsed_font_list.is_empty() { + font_list = parsed_font_list; } } @@ -40,25 +43,60 @@ impl FontOptions { } } - fallback_list.map(|fallback_list| FontOptions { - guifont_setting: Some(guifont_setting.to_string()), - fallback_list, + FontOptions { + font_list, bold, italic, - size, - }) + size: points_to_pixels(size), + } + } + + pub fn primary_font(&self) -> FontSelection { + self.font_list + .first() + .map(|f| FontSelection::from(f)) + .unwrap_or(FontSelection::Default) } } -impl PartialEq for FontOptions { - fn eq(&self, other: &Self) -> bool { - if self.guifont_setting.is_some() && self.guifont_setting == other.guifont_setting { - return true; +impl Default for FontOptions { + fn default() -> Self { + FontOptions { + font_list: Vec::new(), + bold: false, + italic: false, + size: points_to_pixels(DEFAULT_FONT_SIZE), } + } +} - self.fallback_list == other.fallback_list +impl PartialEq for FontOptions { + fn eq(&self, other: &Self) -> bool { + self.font_list == other.font_list && (self.size - other.size).abs() < std::f32::EPSILON && self.bold == other.bold && self.italic == other.italic } } + +fn points_to_pixels(value: f32) -> f32 { + // Fonts in neovim are using points, not pixels. + // + // Skia docs is incorrectly stating it uses points, but uses pixels: + // https://api.skia.org/classSkFont.html#a7e28a156a517d01bc608c14c761346bf + // https://github.com/mono/SkiaSharp/issues/1147#issuecomment-587421201 + // + // So, we need to convert points to pixels. + // + // In reality, this depends on DPI/PPI of monitor, but here we only care about converting + // from points to pixels, so this is standard constant values. + let pixels_per_inch = 96.0; + let points_per_inch = 72.0; + // On macos points == pixels + #[cfg(target_os = "macos")] + let points_per_inch = 96.0; + + let pixels_per_point = pixels_per_inch / points_per_inch; + + value * pixels_per_point +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 104f981..f288ac6 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::mpsc::Receiver; use std::sync::Arc; -use log::error; +use log::{error, trace}; use skia_safe::{colors, dash_path_effect, BlendMode, Canvas, Color, Paint, Rect}; pub mod animation_utils; @@ -87,12 +87,25 @@ impl Renderer { } } + pub fn handle_scale_factor_update(&mut self, scale_factor: f64) { + self.shaper.update_scale_factor(scale_factor as f32); + self.update_font_dimensions(); + } + fn update_font(&mut self, guifont_setting: &str) { - if self.shaper.update_font(guifont_setting) { - let (font_width, font_height) = self.shaper.font_base_dimensions(); - self.font_width = font_width; - self.font_height = font_height; - } + self.shaper.update_font(guifont_setting); + self.update_font_dimensions(); + } + + fn update_font_dimensions(&mut self) { + let (font_width, font_height) = self.shaper.font_base_dimensions(); + self.font_width = font_width; + self.font_height = font_height; + trace!( + "Updating font dimensions: {}x{}", + self.font_height, + self.font_width, + ); } fn compute_text_region(&self, grid_pos: (u64, u64), cell_width: u64) -> Rect { diff --git a/src/window/window_wrapper/mod.rs b/src/window/window_wrapper/mod.rs index 82d2a0e..592ca98 100644 --- a/src/window/window_wrapper/mod.rs +++ b/src/window/window_wrapper/mod.rs @@ -19,6 +19,7 @@ use glutin::{ window::{self, Fullscreen, Icon}, ContextBuilder, GlProfile, WindowedContext, }; +use log::trace; #[cfg(target_os = "linux")] use glutin::platform::unix::WindowBuilderExtUnix; @@ -128,6 +129,12 @@ impl GlutinWindowWrapper { } => { self.handle_quit(running); } + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => { + self.handle_scale_factor_update(scale_factor); + } Event::WindowEvent { event: WindowEvent::DroppedFile(path), .. @@ -159,6 +166,7 @@ impl GlutinWindowWrapper { let previous_size = self.saved_inner_size; if previous_size != current_size { + trace!("Updating grid size: {:#?}", current_size); self.saved_inner_size = current_size; handle_new_grid_size(current_size, &self.renderer, &self.ui_command_sender); self.skia_renderer.resize(&self.windowed_context); @@ -181,6 +189,10 @@ impl GlutinWindowWrapper { self.windowed_context.swap_buffers().unwrap(); } } + + fn handle_scale_factor_update(&mut self, scale_factor: f64) { + self.renderer.handle_scale_factor_update(scale_factor); + } } pub fn start_loop(