Try to load fallback fonts instead of panicking when a font is not found (#3891)

This PR adjusts our font resolution code to attempt to use a fallback
font if the specified font cannot be found.

Right now our fallback font stack is `Zed Mono`, followed by `Helvetica`
(in practice we should always be able to resolve `Zed Mono` since we
bundle it with the app).

In the future we'll want to surface the ability to set the fallback font
stack from GPUI consumers, and potentially even support specifying font
stacks in the user settings (as opposed to a single font family).

Release Notes:

- Fixed a panic when trying to load a font that could not be found.
This commit is contained in:
Marshall Bowers 2024-01-04 14:10:46 -05:00 committed by GitHub
parent 3d1023ef52
commit e4aa7ba4f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 6 deletions

View File

@ -9565,7 +9565,7 @@ impl InputHandler for Editor {
) -> Option<gpui::Bounds<Pixels>> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;
let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx

View File

@ -1775,7 +1775,7 @@ impl EditorElement {
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
@ -3782,7 +3782,7 @@ fn compute_auto_height_layout(
}
let style = editor.style.as_ref().unwrap();
let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx

View File

@ -15,8 +15,9 @@ use crate::{
use anyhow::anyhow;
use collections::FxHashMap;
use core::fmt;
use itertools::Itertools;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use smallvec::{smallvec, SmallVec};
use std::{
cmp,
fmt::{Debug, Display, Formatter},
@ -42,6 +43,7 @@ pub struct TextSystem {
raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
fallback_font_stack: SmallVec<[Font; 2]>,
}
impl TextSystem {
@ -54,6 +56,12 @@ impl TextSystem {
font_ids_by_font: RwLock::default(),
wrapper_pool: Mutex::default(),
font_runs_pool: Mutex::default(),
fallback_font_stack: smallvec![
// TODO: This is currently Zed-specific.
// We should allow GPUI users to provide their own fallback font stack.
font("Zed Mono"),
font("Helvetica")
],
}
}
@ -72,6 +80,33 @@ impl TextSystem {
}
}
/// Resolves the specified font, falling back to the default font stack if
/// the font fails to load.
///
/// # Panics
///
/// Panics if the font and none of the fallbacks can be resolved.
pub fn resolve_font(&self, font: &Font) -> FontId {
if let Ok(font_id) = self.font_id(font) {
return font_id;
}
for fallback in &self.fallback_font_stack {
if let Ok(font_id) = self.font_id(fallback) {
return font_id;
}
}
panic!(
"failed to resolve font '{}' or any of the fallbacks: {}",
font.family,
self.fallback_font_stack
.iter()
.map(|fallback| &fallback.family)
.join(", ")
);
}
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
}
@ -159,7 +194,7 @@ impl TextSystem {
) -> Result<Arc<LineLayout>> {
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
for run in runs.iter() {
let font_id = self.font_id(&run.font)?;
let font_id = self.resolve_font(&run.font);
if let Some(last_run) = font_runs.last_mut() {
if last_run.font_id == font_id {
last_run.len += run.len;

View File

@ -421,7 +421,7 @@ impl TerminalElement {
let rem_size = cx.rem_size();
let font_pixels = text_style.font_size.to_pixels(rem_size);
let line_height = font_pixels * line_height.to_pixels(rem_size);
let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
let font_id = cx.text_system().resolve_font(&text_style.font());
// todo!(do we need to keep this unwrap?)
let cell_width = text_system