Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Support fractional fonts and grid sizes (try 2) #2500

Merged
merged 10 commits into from
May 11, 2024
4 changes: 2 additions & 2 deletions src/bridge/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub enum GuiOption {
GuiFont(String),
GuiFontSet(String),
GuiFontWide(String),
LineSpace(i64),
LineSpace(f64),
Pumblend(u64),
ShowTabLine(u64),
TermGuiColors(bool),
Expand Down Expand Up @@ -454,7 +454,7 @@ fn parse_option_set(option_set_arguments: Vec<Value>) -> Result<RedrawEvent> {
"guifont" => GuiOption::GuiFont(parse_string(value)?),
"guifontset" => GuiOption::GuiFontSet(parse_string(value)?),
"guifontwide" => GuiOption::GuiFontWide(parse_string(value)?),
"linespace" => GuiOption::LineSpace(parse_i64(value)?),
"linespace" => GuiOption::LineSpace(parse_f64(value)?),
"pumblend" => GuiOption::Pumblend(parse_u64(value)?),
"showtabline" => GuiOption::ShowTabLine(parse_u64(value)?),
"termguicolors" => GuiOption::TermGuiColors(parse_bool(value)?),
Expand Down
2 changes: 1 addition & 1 deletion src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ impl Editor {
}
GuiOption::LineSpace(linespace) => {
self.draw_command_batcher
.queue(DrawCommand::LineSpaceChanged(linespace));
.queue(DrawCommand::LineSpaceChanged(linespace as f32));

self.redraw_screen();
}
Expand Down
74 changes: 22 additions & 52 deletions src/renderer/fonts/caching_shaper.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use std::{num::NonZeroUsize, sync::Arc};

use itertools::Itertools;
use log::{debug, error, trace, warn};
use log::{debug, error, info, trace};
use lru::LruCache;
use skia_safe::{
graphics::{font_cache_limit, font_cache_used, set_font_cache_limit},
TextBlob, TextBlobBuilder,
};
use skia_safe::{graphics::set_font_cache_limit, TextBlob, TextBlobBuilder};
use swash::{
shape::ShapeContext,
text::{
Expand All @@ -30,13 +27,14 @@ struct ShapeKey {
pub style: CoarseStyle,
}

const FONT_CACHE_SIZE: usize = 8 * 1024 * 1024;

pub struct CachingShaper {
options: FontOptions,
font_loader: FontLoader,
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
shape_context: ShapeContext,
scale_factor: f32,
fudge_factor: f32,
linespace: f32,
font_info: Option<(Metrics, f32)>,
}
Expand All @@ -51,7 +49,6 @@ impl CachingShaper {
blob_cache: LruCache::new(NonZeroUsize::new(10000).unwrap()),
shape_context: ShapeContext::new(),
scale_factor,
fudge_factor: 1.0,
linespace: 0.0,
font_info: None,
};
Expand All @@ -75,7 +72,7 @@ impl CachingShaper {

pub fn current_size(&self) -> f32 {
let min_font_size = 1.0;
(self.options.size * self.scale_factor * self.fudge_factor).max(min_font_size)
(self.options.size * self.scale_factor).max(min_font_size)
}

pub fn update_scale_factor(&mut self, scale_factor: f32) {
Expand Down Expand Up @@ -154,31 +151,16 @@ impl CachingShaper {
}

fn reset_font_loader(&mut self) {
self.fudge_factor = 1.0;
self.font_info = None;
let mut font_size = self.current_size();
debug!("Original font_size: {:.2}px", font_size);
let font_size = self.current_size();

self.font_loader = FontLoader::new(font_size);
let (metrics, font_width) = self.info();

debug!("Original font_width: {:.2}px", font_width);
let (_, font_width) = self.info();
info!(
"Reset Font Loader: font_size: {:.2}px, font_width: {:.2}px",
font_size, font_width
);

if !self.options.allow_float_size {
// Calculate the new fudge factor required to scale the font width to the nearest exact pixel
debug!(
"Font width: {:.2}px (avg: {:.2}px)",
font_width, metrics.average_width
);
let min_fudged_width = 1.0;
self.fudge_factor = font_width.round().max(min_fudged_width) / font_width;
debug!("Fudge factor: {:.2}", self.fudge_factor);
font_size = self.current_size();
self.font_info = None;
self.font_loader = FontLoader::new(font_size);
debug!("Fudged font size: {:.2}px", font_size);
debug!("Fudged font width: {:.2}px", self.info().1);
}
self.blob_cache.clear();
}

Expand Down Expand Up @@ -219,16 +201,12 @@ impl CachingShaper {
pub fn font_base_dimensions(&mut self) -> PixelSize<f32> {
let (metrics, glyph_advance) = self.info();

let bare_font_height = (metrics.ascent + metrics.descent + metrics.leading).ceil();
let font_height = bare_font_height + self.linespace;
let font_width = (glyph_advance + self.options.width + 0.5).floor();
let bare_font_height = metrics.ascent + metrics.descent + metrics.leading;
// assuming that linespace is checked on receive for validity
let font_height = (bare_font_height + self.linespace).ceil();
let font_width = glyph_advance + self.options.width;

(
font_width,
font_height, // assuming that linespace is checked on receive for
// validity
)
.into()
(font_width, font_height).into()
}

pub fn underline_position(&mut self) -> f32 {
Expand All @@ -237,7 +215,7 @@ impl CachingShaper {

pub fn y_adjustment(&mut self) -> f32 {
let metrics = self.metrics();
(metrics.ascent + metrics.leading + self.linespace / 2.).ceil()
metrics.ascent + metrics.leading + self.linespace / 2.
}

fn build_clusters(
Expand Down Expand Up @@ -373,16 +351,10 @@ impl CachingShaper {
grouped_results
}

pub fn adjust_font_cache_size(&self) {
let current_font_cache_size = font_cache_limit() as f32;
let percent_font_cache_used = font_cache_used() as f32 / current_font_cache_size;
if percent_font_cache_used > 0.9 {
warn!(
"Font cache is {}% full, increasing cache size",
percent_font_cache_used * 100.0
);
set_font_cache_limit((percent_font_cache_used * 1.5) as usize);
}
pub fn cleanup_font_cache(&self) {
tracy_zone!("purge_font_cache");
set_font_cache_limit(FONT_CACHE_SIZE / 2);
set_font_cache_limit(FONT_CACHE_SIZE);
}

pub fn shape(&mut self, text: String, style: CoarseStyle) -> Vec<TextBlob> {
Expand Down Expand Up @@ -420,7 +392,7 @@ impl CachingShaper {

shaper.shape_with(|glyph_cluster| {
for glyph in glyph_cluster.glyphs {
let position = ((glyph.data as f32 * glyph_width), glyph.y);
let position = (glyph.data as f32 * glyph_width, glyph.y);
glyph_data.push((glyph.id, position));
}
});
Expand All @@ -441,8 +413,6 @@ impl CachingShaper {
resulting_blobs.push(blob.expect("Could not create textblob"));
}

self.adjust_font_cache_size();

resulting_blobs
}

Expand Down
1 change: 1 addition & 0 deletions src/renderer/fonts/font_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct FontPair {
impl FontPair {
fn new(key: FontKey, mut skia_font: Font) -> Option<FontPair> {
skia_font.set_subpixel(true);
skia_font.set_baseline_snap(true);
skia_font.set_hinting(font_hinting(&key.hinting));
skia_font.set_edging(font_edging(&key.edging));

Expand Down
11 changes: 0 additions & 11 deletions src/renderer/fonts/font_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const FONT_LIST_SEPARATOR: char = ',';
const FONT_HINTING_PREFIX: &str = "#h-";
const FONT_EDGING_PREFIX: &str = "#e-";
const FONT_HEIGHT_PREFIX: char = 'h';
const ALLOW_FLOAT_SIZE_OPT: char = '.';
const FONT_WIDTH_PREFIX: char = 'w';
const FONT_BOLD_OPT: &str = "b";
const FONT_ITALIC_OPT: &str = "i";
Expand Down Expand Up @@ -109,7 +108,6 @@ pub struct FontOptions {
pub features: HashMap<String /* family */, Vec<FontFeature> /* features */>,
pub size: f32,
pub width: f32,
pub allow_float_size: bool,
pub hinting: FontHinting,
pub edging: FontEdging,
}
Expand Down Expand Up @@ -168,10 +166,8 @@ impl FontOptions {
} else if let Some(edging_string) = part.strip_prefix(FONT_EDGING_PREFIX) {
font_options.edging = FontEdging::parse(edging_string)?;
} else if part.starts_with(FONT_HEIGHT_PREFIX) && part.len() > 1 {
font_options.allow_float_size |= part[1..].contains(ALLOW_FLOAT_SIZE_OPT);
font_options.size = parse_pixels(part).map_err(|_| INVALID_SIZE_ERR)?;
} else if part.starts_with(FONT_WIDTH_PREFIX) && part.len() > 1 {
font_options.allow_float_size |= part[1..].contains(ALLOW_FLOAT_SIZE_OPT);
font_options.width = parse_pixels(part).map_err(|_| INVALID_WIDTH_ERR)?;
} else if part == FONT_BOLD_OPT {
style.push("Bold".to_string());
Expand Down Expand Up @@ -238,7 +234,6 @@ impl Default for FontOptions {
bold: None,
bold_italic: None,
features: HashMap::new(),
allow_float_size: false,
size: points_to_pixels(DEFAULT_FONT_SIZE),
width: 0.0,
hinting: FontHinting::default(),
Expand Down Expand Up @@ -554,12 +549,6 @@ mod tests {
font_size_pixels, font_options.size,
);

assert_eq!(
font_options.allow_float_size, true,
"allow float size should equal {}, but {}",
true, font_options.allow_float_size,
);

for font in font_options.normal.iter() {
assert_eq!(
font.family, "Fira Code Mono",
Expand Down
37 changes: 35 additions & 2 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::{
use itertools::Itertools;
use log::{error, warn};
use skia_safe::Canvas;

use winit::{
event::Event,
event_loop::{EventLoop, EventLoopProxy},
Expand All @@ -37,6 +38,15 @@ use crate::{
WindowSettings,
};

#[cfg(feature = "profiling")]
use crate::profiling::tracy_plot;
#[cfg(feature = "profiling")]
use skia_safe::graphics::{
font_cache_count_limit, font_cache_count_used, font_cache_limit, font_cache_used,
resource_cache_single_allocation_byte_limit, resource_cache_total_bytes_limit,
resource_cache_total_bytes_used,
};

#[cfg(feature = "gpu_profiling")]
use crate::profiling::GpuCtx;

Expand All @@ -52,6 +62,26 @@ pub use vsync::VSync;

use self::fonts::font_options::FontOptions;

#[cfg(feature = "profiling")]
fn plot_skia_cache() {
tracy_plot!("font_cache_limit", font_cache_limit() as f64);
tracy_plot!("font_cache_used", font_cache_used() as f64);
tracy_plot!("font_cache_count_used", font_cache_count_used() as f64);
tracy_plot!("font_cache_count_limit", font_cache_count_limit() as f64);
tracy_plot!(
"resource_cache_total_bytes_used",
resource_cache_total_bytes_used() as f64
);
tracy_plot!(
"resource_cache_total_bytes_limit",
resource_cache_total_bytes_limit() as f64
);
tracy_plot!(
"resource_cache_single_allocation_byte_limit",
resource_cache_single_allocation_byte_limit().unwrap_or_default() as f64
);
}

#[derive(SettingGroup, Clone)]
pub struct RendererSettings {
position_animation_length: f32,
Expand Down Expand Up @@ -98,7 +128,7 @@ impl Default for RendererSettings {
pub enum DrawCommand {
UpdateCursor(Cursor),
FontChanged(String),
LineSpaceChanged(i64),
LineSpaceChanged(f32),
DefaultStyleChanged(Style),
ModeChanged(EditorMode),
UIReady,
Expand Down Expand Up @@ -277,6 +307,9 @@ impl Renderer {
self.profiler.draw(root_canvas, dt);

root_canvas.restore();

#[cfg(feature = "profiling")]
plot_skia_cache();
}

pub fn animate_frame(&mut self, grid_rect: &GridRect<f32>, dt: f32) -> bool {
Expand Down Expand Up @@ -412,7 +445,7 @@ impl Renderer {
result.font_changed = true;
}
DrawCommand::LineSpaceChanged(new_linespace) => {
self.grid_renderer.update_linespace(new_linespace as f32);
self.grid_renderer.update_linespace(new_linespace);
result.font_changed = true;
}
DrawCommand::DefaultStyleChanged(new_style) => {
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/rendered_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,16 +689,15 @@ impl RenderedWindow {
) -> impl Iterator<Item = (Matrix, &Rc<RefCell<Line>>)> {
let scroll_offset_lines = self.scroll_animation.position.floor();
let scroll_offset = scroll_offset_lines - self.scroll_animation.position;
let scroll_offset_pixels = (scroll_offset * grid_scale.height()).round() as isize;
let scroll_offset_pixels = (scroll_offset * grid_scale.0.height).round();

self.iter_scrollable_lines().map(move |(i, line)| {
let mut matrix = Matrix::new_identity();
matrix.set_translate((
pixel_region.min.x,
pixel_region.min.y
+ (scroll_offset_pixels
+ ((i + self.viewport_margins.top as isize) * grid_scale.height() as isize))
as f32,
+ ((i + self.viewport_margins.top as isize) as f32 * grid_scale.0.height)),
));
(matrix, line)
})
Expand All @@ -713,7 +712,7 @@ impl RenderedWindow {
let mut matrix = Matrix::new_identity();
matrix.set_translate((
pixel_region.min.x,
pixel_region.min.y + (i * grid_scale.height() as isize) as f32,
pixel_region.min.y + (i as f32 * grid_scale.height()),
));
(matrix, line)
})
Expand Down
1 change: 0 additions & 1 deletion src/settings/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ impl From<FontSettings> for FontOptions {
.unwrap_or_default(),
size: value.size,
width: value.width.unwrap_or_default(),
allow_float_size: value.allow_float_size.unwrap_or_default(),
hinting: value
.hinting
.map(|hinting| FontHinting::parse(&hinting).unwrap_or_default())
Expand Down
5 changes: 5 additions & 0 deletions src/window/update_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ impl UpdateLoop {
self.pending_render && Instant::now() > (self.animation_start + self.animation_time);
let should_prepare = !self.pending_render || skipped_frame;
if !should_prepare {
window_wrapper
.renderer
.grid_renderer
.shaper
.cleanup_font_cache();
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/window/window_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct WinitWindowWrapper {
// Don't rearrange this, unless you have a good reason to do so
// The destruction order has to be correct
pub skia_renderer: Box<dyn SkiaRenderer>,
renderer: Renderer,
pub renderer: Renderer,
keyboard_manager: KeyboardManager,
mouse_manager: MouseManager,
title: String,
Expand Down
3 changes: 1 addition & 2 deletions website/docs/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ see that doc for details on what those settings do.
- `features`: optional, `{ "<font>" = ["<string>"] }`
- `size`: required,
- `width`: optional,
- `allow_float_size`: optional,
- `hinting`: optional,
- `edging`: optional,

Settings `size`, `width`, `allow_float_size`, `hinting` and `edging` can be found in
Settings `size`, `width`, `hinting` and `edging` can be found in
[Configuration](configuration.md).

- `FontDescription` can be:
Expand Down