@@ -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
@@ -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
@@ -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;
@@ -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