Feature/fallback fonts (#15306)

Supersedes https://github.com/zed-industries/zed/pull/12090

fixes #5180
fixes #5055

See original PR for an example of the feature at work.

This PR changes the settings interface to be backwards compatible, and
adds the `ui_font_fallbacks`, `buffer_font_fallbacks`, and
`terminal.font_fallbacks` settings.

Release Notes:

- Added support for font fallbacks via three new settings:
`ui_font_fallbacks`, `buffer_font_fallbacks`, and
`terminal.font_fallbacks`.(#5180, #5055).

---------

Co-authored-by: Junkui Zhang <364772080@qq.com>
This commit is contained in:
Mikayla Maki 2024-07-26 16:42:21 -07:00 committed by GitHub
parent 3e31955b7f
commit a1bd7a1297
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 444 additions and 136 deletions

1
Cargo.lock generated
View File

@ -4866,6 +4866,7 @@ dependencies = [
"cocoa",
"collections",
"core-foundation",
"core-foundation-sys 0.8.6",
"core-graphics",
"core-text",
"cosmic-text",

View File

@ -26,6 +26,9 @@
},
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Plex Mono",
// Set the buffer text's font fallbacks, this will be merged with
// the platform's default fallbacks.
"buffer_font_fallbacks": [],
// The OpenType features to enable for text in the editor.
"buffer_font_features": {
// Disable ligatures:
@ -47,8 +50,11 @@
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
// (On macOS) You can set this to ".SystemUIFont" to use the system font
// You can set this to ".SystemUIFont" to use the system font
"ui_font_family": "Zed Plex Sans",
// Set the UI's font fallbacks, this will be merged with the platform's
// default font fallbacks.
"ui_font_fallbacks": [],
// The OpenType features to enable for text in the UI
"ui_font_features": {
// Disable ligatures:
@ -675,6 +681,10 @@
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
// "font_family": "Zed Plex Mono",
// Set the terminal's font fallbacks. If this option is not included,
// the terminal will default to matching the buffer's font fallbacks.
// This will be merged with the platform's default font fallbacks
// "font_fallbacks": ["FiraCode Nerd Fonts"],
// Sets the maximum number of lines in the terminal's scrollback buffer.
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
// Existing terminals will not pick up this change until they are recreated.

View File

@ -1123,16 +1123,17 @@ impl Context {
.timer(Duration::from_millis(200))
.await;
let token_count = cx
.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})?
.await?;
if let Some(token_count) = cx.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})? {
let token_count = token_count.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify()
})?;
}
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify()
})?;
anyhow::Ok(())
}
.log_err()

View File

@ -1635,15 +1635,18 @@ impl PromptEditor {
})?
.await?;
let token_count = cx
.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})?
.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify();
})
if let Some(token_count) = cx.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})? {
let token_count = token_count.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify();
})
} else {
Ok(())
}
})
}
@ -1832,6 +1835,7 @@ impl PromptEditor {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),

View File

@ -734,26 +734,29 @@ impl PromptLibrary {
const DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
let token_count = cx
.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(
LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: Role::System,
content: body.to_string(),
}],
stop: Vec::new(),
temperature: 1.,
},
cx,
)
})?
.await?;
this.update(&mut cx, |this, cx| {
let prompt_editor = this.prompt_editors.get_mut(&prompt_id).unwrap();
prompt_editor.token_count = Some(token_count);
cx.notify();
})
if let Some(token_count) = cx.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(
LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: Role::System,
content: body.to_string(),
}],
stop: Vec::new(),
temperature: 1.,
},
cx,
)
})? {
let token_count = token_count.await?;
this.update(&mut cx, |this, cx| {
let prompt_editor = this.prompt_editors.get_mut(&prompt_id).unwrap();
prompt_editor.token_count = Some(token_count);
cx.notify();
})
} else {
Ok(())
}
}
.log_err()
});

View File

@ -707,15 +707,18 @@ impl PromptEditor {
inline_assistant.request_for_inline_assist(assist_id, cx)
})??;
let token_count = cx
.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})?
.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify();
})
if let Some(token_count) = cx.update(|cx| {
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
})? {
let token_count = token_count.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify();
})
} else {
Ok(())
}
})
}
@ -906,6 +909,7 @@ impl PromptEditor {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),

View File

@ -533,6 +533,7 @@ impl Render for MessageEditor {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: TextSize::Small.rems(cx).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,

View File

@ -2190,6 +2190,7 @@ impl CollabPanel {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,

View File

@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use futures::{future::BoxFuture, stream::BoxStream, StreamExt};
use gpui::{AppContext, Global, Model, ModelContext, Task};
use language_model::{
LanguageModel, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry,
@ -143,11 +143,11 @@ impl LanguageModelCompletionProvider {
&self,
request: LanguageModelRequest,
cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
) -> Option<BoxFuture<'static, Result<usize>>> {
if let Some(model) = self.active_model() {
model.count_tokens(request, cx)
Some(model.count_tokens(request, cx))
} else {
std::future::ready(Err(anyhow!("No active model set"))).boxed()
None
}
}

View File

@ -12430,6 +12430,7 @@ impl Render for Editor {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(settings.buffer_line_height.value()),
@ -12439,6 +12440,7 @@ impl Render for Editor {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),

View File

@ -27,6 +27,7 @@ pub fn marked_display_snapshot(
let font = Font {
family: "Zed Plex Mono".into(),
features: FontFeatures::default(),
fallbacks: None,
weight: FontWeight::default(),
style: FontStyle::default(),
};

View File

@ -816,6 +816,7 @@ impl ExtensionsPage {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),

View File

@ -93,6 +93,7 @@ cbindgen = { version = "0.26.0", default-features = false }
block = "0.1"
cocoa.workspace = true
core-foundation.workspace = true
core-foundation-sys = "0.8"
core-graphics = "0.23"
core-text = "20.1"
foreign-types = "0.5"

View File

@ -1,10 +1,12 @@
#![allow(unused, non_upper_case_globals)]
use crate::FontFeatures;
use crate::{FontFallbacks, FontFeatures};
use cocoa::appkit::CGFloat;
use core_foundation::{
array::{
kCFTypeArrayCallBacks, CFArray, CFArrayAppendValue, CFArrayCreateMutable, CFMutableArrayRef,
kCFTypeArrayCallBacks, CFArray, CFArrayAppendArray, CFArrayAppendValue,
CFArrayCreateMutable, CFArrayGetCount, CFArrayGetValueAtIndex, CFArrayRef,
CFMutableArrayRef,
},
base::{kCFAllocatorDefault, CFRelease, TCFType},
dictionary::{
@ -13,21 +15,88 @@ use core_foundation::{
number::CFNumber,
string::{CFString, CFStringRef},
};
use core_foundation_sys::locale::CFLocaleCopyPreferredLanguages;
use core_graphics::{display::CFDictionary, geometry::CGAffineTransform};
use core_text::{
font::{CTFont, CTFontRef},
font::{cascade_list_for_languages, CTFont, CTFontRef},
font_descriptor::{
kCTFontFeatureSettingsAttribute, CTFontDescriptor, CTFontDescriptorCopyAttributes,
CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorCreateWithAttributes,
kCTFontCascadeListAttribute, kCTFontFeatureSettingsAttribute, CTFontDescriptor,
CTFontDescriptorCopyAttributes, CTFontDescriptorCreateCopyWithFeature,
CTFontDescriptorCreateWithAttributes, CTFontDescriptorCreateWithNameAndSize,
CTFontDescriptorRef,
},
};
use font_kit::font::Font;
use font_kit::font::Font as FontKitFont;
use std::ptr;
pub fn apply_features(font: &mut Font, features: &FontFeatures) {
pub fn apply_features_and_fallbacks(
font: &mut FontKitFont,
features: &FontFeatures,
fallbacks: Option<&FontFallbacks>,
) -> anyhow::Result<()> {
unsafe {
let fallback_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if let Some(fallbacks) = fallbacks {
for user_fallback in fallbacks.fallback_list() {
let name = CFString::from(user_fallback.as_str());
let fallback_desc =
CTFontDescriptorCreateWithNameAndSize(name.as_concrete_TypeRef(), 0.0);
CFArrayAppendValue(fallback_array, fallback_desc as _);
CFRelease(fallback_desc as _);
}
}
{
let preferred_languages: CFArray<CFString> =
CFArray::wrap_under_create_rule(CFLocaleCopyPreferredLanguages());
let default_fallbacks = CTFontCopyDefaultCascadeListForLanguages(
font.native_font().as_concrete_TypeRef(),
preferred_languages.as_concrete_TypeRef(),
);
let default_fallbacks: CFArray<CTFontDescriptor> =
CFArray::wrap_under_create_rule(default_fallbacks);
default_fallbacks
.iter()
.filter(|desc| desc.font_path().is_some())
.map(|desc| {
CFArrayAppendValue(fallback_array, desc.as_concrete_TypeRef() as _);
});
}
let feature_array = generate_feature_array(features);
let keys = [kCTFontFeatureSettingsAttribute, kCTFontCascadeListAttribute];
let values = [feature_array, fallback_array];
let attrs = CFDictionaryCreate(
kCFAllocatorDefault,
keys.as_ptr() as _,
values.as_ptr() as _,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
);
CFRelease(feature_array as *const _ as _);
CFRelease(fallback_array as *const _ as _);
let new_descriptor = CTFontDescriptorCreateWithAttributes(attrs);
CFRelease(attrs as _);
let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
let new_font = CTFontCreateCopyWithAttributes(
font.native_font().as_concrete_TypeRef(),
0.0,
std::ptr::null(),
new_descriptor.as_concrete_TypeRef(),
);
let new_font = CTFont::wrap_under_create_rule(new_font);
*font = font_kit::font::Font::from_native_font(&new_font);
Ok(())
}
}
fn generate_feature_array(features: &FontFeatures) -> CFMutableArrayRef {
unsafe {
let native_font = font.native_font();
let mut feature_array =
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for (tag, value) in features.tag_value_list() {
@ -48,26 +117,7 @@ pub fn apply_features(font: &mut Font, features: &FontFeatures) {
CFArrayAppendValue(feature_array, dict as _);
CFRelease(dict as _);
}
let attrs = CFDictionaryCreate(
kCFAllocatorDefault,
&kCTFontFeatureSettingsAttribute as *const _ as _,
&feature_array as *const _ as _,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
);
CFRelease(feature_array as *const _ as _);
let new_descriptor = CTFontDescriptorCreateWithAttributes(attrs);
CFRelease(attrs as _);
let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
let new_font = CTFontCreateCopyWithAttributes(
font.native_font().as_concrete_TypeRef(),
0.0,
ptr::null(),
new_descriptor.as_concrete_TypeRef(),
);
let new_font = CTFont::wrap_under_create_rule(new_font);
*font = Font::from_native_font(&new_font);
feature_array
}
}
@ -82,4 +132,8 @@ extern "C" {
matrix: *const CGAffineTransform,
attributes: CTFontDescriptorRef,
) -> CTFontRef;
fn CTFontCopyDefaultCascadeListForLanguages(
font: CTFontRef,
languagePrefList: CFArrayRef,
) -> CFArrayRef;
}

View File

@ -1,6 +1,6 @@
use crate::{
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
point, px, size, Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics,
FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
};
use anyhow::anyhow;
@ -43,7 +43,7 @@ use pathfinder_geometry::{
use smallvec::SmallVec;
use std::{borrow::Cow, char, cmp, convert::TryFrom, sync::Arc};
use super::open_type;
use super::open_type::apply_features_and_fallbacks;
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
@ -54,6 +54,7 @@ pub(crate) struct MacTextSystem(RwLock<MacTextSystemState>);
struct FontKey {
font_family: SharedString,
font_features: FontFeatures,
font_fallbacks: Option<FontFallbacks>,
}
struct MacTextSystemState {
@ -123,11 +124,13 @@ impl PlatformTextSystem for MacTextSystem {
let font_key = FontKey {
font_family: font.family.clone(),
font_features: font.features.clone(),
font_fallbacks: font.fallbacks.clone(),
};
let candidates = if let Some(font_ids) = lock.font_ids_by_font_key.get(&font_key) {
font_ids.as_slice()
} else {
let font_ids = lock.load_family(&font.family, &font.features)?;
let font_ids =
lock.load_family(&font.family, &font.features, font.fallbacks.as_ref())?;
lock.font_ids_by_font_key.insert(font_key.clone(), font_ids);
lock.font_ids_by_font_key[&font_key].as_ref()
};
@ -212,6 +215,7 @@ impl MacTextSystemState {
&mut self,
name: &str,
features: &FontFeatures,
fallbacks: Option<&FontFallbacks>,
) -> Result<SmallVec<[FontId; 4]>> {
let name = if name == ".SystemUIFont" {
".AppleSystemUIFont"
@ -227,8 +231,7 @@ impl MacTextSystemState {
for font in family.fonts() {
let mut font = font.load()?;
open_type::apply_features(&mut font, features);
apply_features_and_fallbacks(&mut font, features, fallbacks)?;
// This block contains a precautionary fix to guard against loading fonts
// that might cause panics due to `.unwrap()`s up the chain.
{
@ -457,6 +460,7 @@ impl MacTextSystemState {
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let font: &FontKitFont = &self.fonts[run.font_id.0];
unsafe {
string.set_attribute(
cf_range,
@ -634,7 +638,7 @@ impl From<FontStyle> for FontkitStyle {
}
}
// Some fonts may have no attributest despite `core_text` requiring them (and panicking).
// Some fonts may have no attributes despite `core_text` requiring them (and panicking).
// This is the same version as `core_text` has without `expect` calls.
mod lenient_font_attributes {
use core_foundation::{

View File

@ -30,6 +30,7 @@ struct FontInfo {
font_family: String,
font_face: IDWriteFontFace3,
features: IDWriteTypography,
fallbacks: Option<IDWriteFontFallback>,
is_system_font: bool,
is_emoji: bool,
}
@ -287,6 +288,63 @@ impl DirectWriteState {
Ok(())
}
fn generate_font_fallbacks(
&self,
fallbacks: Option<&FontFallbacks>,
) -> Result<Option<IDWriteFontFallback>> {
if fallbacks.is_some_and(|fallbacks| fallbacks.fallback_list().is_empty()) {
return Ok(None);
}
unsafe {
let builder = self.components.factory.CreateFontFallbackBuilder()?;
let font_set = &self.system_font_collection.GetFontSet()?;
if let Some(fallbacks) = fallbacks {
for family_name in fallbacks.fallback_list() {
let Some(fonts) = font_set
.GetMatchingFonts(
&HSTRING::from(family_name),
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
)
.log_err()
else {
continue;
};
if fonts.GetFontCount() == 0 {
log::error!("No mathcing font find for {}", family_name);
continue;
}
let font = fonts.GetFontFaceReference(0)?.CreateFontFace()?;
let mut count = 0;
font.GetUnicodeRanges(None, &mut count).ok();
if count == 0 {
continue;
}
let mut unicode_ranges = vec![DWRITE_UNICODE_RANGE::default(); count as usize];
let Some(_) = font
.GetUnicodeRanges(Some(&mut unicode_ranges), &mut count)
.log_err()
else {
continue;
};
let target_family_name = HSTRING::from(family_name);
builder.AddMapping(
&unicode_ranges,
&[target_family_name.as_ptr()],
None,
None,
None,
1.0,
)?;
}
}
let system_fallbacks = self.components.factory.GetSystemFontFallback()?;
builder.AddMappings(&system_fallbacks)?;
Ok(Some(builder.CreateFontFallback()?))
}
}
unsafe fn generate_font_features(
&self,
font_features: &FontFeatures,
@ -302,6 +360,7 @@ impl DirectWriteState {
font_weight: FontWeight,
font_style: FontStyle,
font_features: &FontFeatures,
font_fallbacks: Option<&FontFallbacks>,
is_system_font: bool,
) -> Option<FontId> {
let collection = if is_system_font {
@ -334,11 +393,16 @@ impl DirectWriteState {
else {
continue;
};
let fallbacks = self
.generate_font_fallbacks(font_fallbacks)
.log_err()
.unwrap_or_default();
let font_info = FontInfo {
font_family: family_name.to_owned(),
font_face,
is_system_font,
features: direct_write_features,
fallbacks,
is_system_font,
is_emoji,
};
let font_id = FontId(self.fonts.len());
@ -371,6 +435,7 @@ impl DirectWriteState {
target_font.weight,
target_font.style,
&target_font.features,
target_font.fallbacks.as_ref(),
)
.unwrap()
} else {
@ -379,6 +444,7 @@ impl DirectWriteState {
target_font.weight,
target_font.style,
&target_font.features,
target_font.fallbacks.as_ref(),
)
.unwrap_or_else(|| {
let family = self.system_ui_font_name.clone();
@ -388,6 +454,7 @@ impl DirectWriteState {
target_font.weight,
target_font.style,
&target_font.features,
target_font.fallbacks.as_ref(),
true,
)
.unwrap()
@ -402,16 +469,38 @@ impl DirectWriteState {
weight: FontWeight,
style: FontStyle,
features: &FontFeatures,
fallbacks: Option<&FontFallbacks>,
) -> Option<FontId> {
// try to find target font in custom font collection first
self.get_font_id_from_font_collection(family_name, weight, style, features, false)
.or_else(|| {
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
})
.or_else(|| {
self.update_system_font_collection();
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
})
self.get_font_id_from_font_collection(
family_name,
weight,
style,
features,
fallbacks,
false,
)
.or_else(|| {
self.get_font_id_from_font_collection(
family_name,
weight,
style,
features,
fallbacks,
true,
)
})
.or_else(|| {
self.update_system_font_collection();
self.get_font_id_from_font_collection(
family_name,
weight,
style,
features,
fallbacks,
true,
)
})
}
fn layout_line(
@ -440,15 +529,22 @@ impl DirectWriteState {
} else {
&self.custom_font_collection
};
let format = self.components.factory.CreateTextFormat(
&HSTRING::from(&font_info.font_family),
collection,
font_info.font_face.GetWeight(),
font_info.font_face.GetStyle(),
DWRITE_FONT_STRETCH_NORMAL,
font_size.0,
&HSTRING::from(&self.components.locale),
)?;
let format: IDWriteTextFormat1 = self
.components
.factory
.CreateTextFormat(
&HSTRING::from(&font_info.font_family),
collection,
font_info.font_face.GetWeight(),
font_info.font_face.GetStyle(),
DWRITE_FONT_STRETCH_NORMAL,
font_size.0,
&HSTRING::from(&self.components.locale),
)?
.cast()?;
if let Some(ref fallbacks) = font_info.fallbacks {
format.SetFontFallback(fallbacks)?;
}
let layout = self.components.factory.CreateTextLayout(
&text_wide,
@ -1183,6 +1279,7 @@ fn get_font_identifier_and_font_struct(
features: FontFeatures::default(),
weight: weight.into(),
style: style.into(),
fallbacks: None,
};
let is_emoji = unsafe { font_face.IsColorFont().as_bool() };
Some((identifier, font_struct, is_emoji))

View File

@ -1,4 +1,5 @@
use derive_more::{Deref, DerefMut};
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, sync::Arc};
use util::arc_cow::ArcCow;

View File

@ -6,9 +6,9 @@ use std::{
use crate::{
black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement,
CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight,
Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled,
TextRun, WindowContext,
CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFallbacks, FontFeatures,
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size,
SizeRefinement, Styled, TextRun, WindowContext,
};
use collections::HashSet;
use refineable::Refineable;
@ -180,6 +180,9 @@ pub struct TextStyle {
/// The font features to use
pub font_features: FontFeatures,
/// The fallback fonts to use
pub font_fallbacks: Option<FontFallbacks>,
/// The font size to use, in pixels or rems.
pub font_size: AbsoluteLength,
@ -218,6 +221,7 @@ impl Default for TextStyle {
"Helvetica".into()
},
font_features: FontFeatures::default(),
font_fallbacks: None,
font_size: rems(1.).into(),
line_height: phi(),
font_weight: FontWeight::default(),
@ -269,6 +273,7 @@ impl TextStyle {
Font {
family: self.font_family.clone(),
features: self.font_features.clone(),
fallbacks: self.font_fallbacks.clone(),
weight: self.font_weight,
style: self.font_style,
}
@ -286,6 +291,7 @@ impl TextStyle {
font: Font {
family: self.font_family.clone(),
features: Default::default(),
fallbacks: self.font_fallbacks.clone(),
weight: self.font_weight,
style: self.font_style,
},

View File

@ -506,6 +506,7 @@ pub trait Styled: Sized {
let Font {
family,
features,
fallbacks,
weight,
style,
} = font;
@ -515,6 +516,7 @@ pub trait Styled: Sized {
text_style.font_features = Some(features);
text_style.font_weight = Some(weight);
text_style.font_style = Some(style);
text_style.font_fallbacks = fallbacks;
self
}

View File

@ -1,8 +1,10 @@
mod font_fallbacks;
mod font_features;
mod line;
mod line_layout;
mod line_wrapper;
pub use font_fallbacks::*;
pub use font_features::*;
pub use line::*;
pub use line_layout::*;
@ -62,8 +64,7 @@ impl TextSystem {
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.
// TODO: Remove this when Linux have implemented setting fallbacks.
font("Zed Plex Mono"),
font("Helvetica"),
font("Segoe UI"), // Windows
@ -683,6 +684,9 @@ pub struct Font {
/// The font features to use.
pub features: FontFeatures,
/// The fallbacks fonts to use.
pub fallbacks: Option<FontFallbacks>,
/// The font weight.
pub weight: FontWeight,
@ -697,6 +701,7 @@ pub fn font(family: impl Into<SharedString>) -> Font {
features: FontFeatures::default(),
weight: FontWeight::default(),
style: FontStyle::default(),
fallbacks: None,
}
}

View File

@ -0,0 +1,21 @@
use std::sync::Arc;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
/// The fallback fonts that can be configured for a given font.
/// Fallback fonts family names are stored here.
#[derive(Default, Clone, Eq, PartialEq, Hash, Debug, Deserialize, Serialize, JsonSchema)]
pub struct FontFallbacks(pub Arc<Vec<String>>);
impl FontFallbacks {
/// Get the fallback fonts family names
pub fn fallback_list(&self) -> &[String] {
&self.0.as_slice()
}
/// Create a font fallback from a list of strings
pub fn from_fonts(fonts: Vec<String>) -> Self {
FontFallbacks(Arc::new(fonts))
}
}

View File

@ -162,6 +162,7 @@ pub fn render_item<T>(
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.),

View File

@ -406,6 +406,7 @@ impl AuthenticationPrompt {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,

View File

@ -341,6 +341,7 @@ impl AuthenticationPrompt {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,

View File

@ -114,6 +114,7 @@ impl BufferSearchBar {
},
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),

View File

@ -1338,6 +1338,7 @@ impl ProjectSearchBar {
},
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),

View File

@ -18,9 +18,11 @@ pub fn test_settings() -> String {
"ui_font_family": "Courier",
"ui_font_features": {},
"ui_font_size": 14,
"ui_font_fallback": [],
"buffer_font_family": "Courier",
"buffer_font_features": {},
"buffer_font_size": 14,
"buffer_font_fallback": [],
"theme": EMPTY_THEME_NAME,
}),
&mut value,

View File

@ -1,8 +1,10 @@
use collections::HashMap;
use gpui::{px, AbsoluteLength, AppContext, FontFeatures, FontWeight, Pixels};
use gpui::{
px, AbsoluteLength, AppContext, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString,
};
use schemars::{
gen::SchemaGenerator,
schema::{InstanceType, RootSchema, Schema, SchemaObject},
schema::{ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject},
JsonSchema,
};
use serde_derive::{Deserialize, Serialize};
@ -24,15 +26,16 @@ pub struct Toolbar {
pub title: bool,
}
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
pub struct TerminalSettings {
pub shell: Shell,
pub working_directory: WorkingDirectory,
pub font_size: Option<Pixels>,
pub font_family: Option<String>,
pub line_height: TerminalLineHeight,
pub font_family: Option<SharedString>,
pub font_fallbacks: Option<FontFallbacks>,
pub font_features: Option<FontFeatures>,
pub font_weight: Option<FontWeight>,
pub line_height: TerminalLineHeight,
pub env: HashMap<String, String>,
pub blinking: TerminalBlink,
pub alternate_scroll: AlternateScroll,
@ -111,6 +114,13 @@ pub struct TerminalSettingsContent {
/// If this option is not included,
/// the terminal will default to matching the buffer's font family.
pub font_family: Option<String>,
/// Sets the terminal's font fallbacks.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font fallbacks.
pub font_fallbacks: Option<Vec<String>>,
/// Sets the terminal's line height.
///
/// Default: comfortable
@ -192,30 +202,51 @@ impl settings::Settings for TerminalSettings {
_: &AppContext,
) -> RootSchema {
let mut root_schema = generator.root_schema_for::<Self::FileContent>();
let available_fonts = params
let available_fonts: Vec<_> = params
.font_names
.iter()
.cloned()
.map(Value::String)
.collect();
let fonts_schema = SchemaObject {
let font_family_schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(available_fonts),
..Default::default()
};
root_schema
.definitions
.extend([("FontFamilies".into(), fonts_schema.into())]);
let font_fallback_schema = SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(Box::new(ArrayValidation {
items: Some(schemars::schema::SingleOrVec::Single(Box::new(
font_family_schema.clone().into(),
))),
unique_items: Some(true),
..Default::default()
})),
..Default::default()
};
root_schema.definitions.extend([
("FontFamilies".into(), font_family_schema.into()),
("FontFallbacks".into(), font_fallback_schema.into()),
]);
root_schema
.schema
.object
.as_mut()
.unwrap()
.properties
.extend([(
"font_family".to_owned(),
Schema::new_ref("#/definitions/FontFamilies".into()),
)]);
.extend([
(
"font_family".to_owned(),
Schema::new_ref("#/definitions/FontFamilies".into()),
),
(
"font_fallbacks".to_owned(),
Schema::new_ref("#/definitions/FontFallbacks".into()),
),
]);
root_schema
}

View File

@ -614,16 +614,24 @@ impl Element for TerminalElement {
let buffer_font_size = settings.buffer_font_size(cx);
let terminal_settings = TerminalSettings::get_global(cx);
let font_family = terminal_settings
.font_family
.as_ref()
.map(|string| string.clone().into())
.unwrap_or(settings.buffer_font.family);
.unwrap_or(&settings.buffer_font.family)
.clone();
let font_fallbacks = terminal_settings
.font_fallbacks
.as_ref()
.or(settings.buffer_font.fallbacks.as_ref())
.map(|fallbacks| fallbacks.clone());
let font_features = terminal_settings
.font_features
.clone()
.unwrap_or(settings.buffer_font.features.clone());
.as_ref()
.unwrap_or(&settings.buffer_font.features)
.clone();
let font_weight = terminal_settings.font_weight.unwrap_or_default();
@ -653,6 +661,7 @@ impl Element for TerminalElement {
font_family,
font_features,
font_weight,
font_fallbacks,
font_size: font_size.into(),
font_style: FontStyle::Normal,
line_height: line_height.into(),

View File

@ -3,10 +3,11 @@ use crate::{Appearance, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
use anyhow::Result;
use derive_more::{Deref, DerefMut};
use gpui::{
px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription,
ViewContext, WindowContext,
px, AppContext, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
Subscription, ViewContext, WindowContext,
};
use refineable::Refineable;
use schemars::schema::ArrayValidation;
use schemars::{
gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
@ -244,6 +245,9 @@ pub struct ThemeSettingsContent {
/// The name of a font to use for rendering in the UI.
#[serde(default)]
pub ui_font_family: Option<String>,
/// The font fallbacks to use for rendering in the UI.
#[serde(default)]
pub ui_font_fallbacks: Option<Vec<String>>,
/// The OpenType features to enable for text in the UI.
#[serde(default)]
pub ui_font_features: Option<FontFeatures>,
@ -253,6 +257,9 @@ pub struct ThemeSettingsContent {
/// The name of a font to use for rendering in text buffers.
#[serde(default)]
pub buffer_font_family: Option<String>,
/// The font fallbacks to use for rendering in text buffers.
#[serde(default)]
pub buffer_font_fallbacks: Option<Vec<String>>,
/// The default font size for rendering in text buffers.
#[serde(default)]
pub buffer_font_size: Option<f32>,
@ -510,14 +517,22 @@ impl settings::Settings for ThemeSettings {
let mut this = Self {
ui_font_size: defaults.ui_font_size.unwrap().into(),
ui_font: Font {
family: defaults.ui_font_family.clone().unwrap().into(),
family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
features: defaults.ui_font_features.clone().unwrap(),
fallbacks: defaults
.ui_font_fallbacks
.as_ref()
.map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
style: Default::default(),
},
buffer_font: Font {
family: defaults.buffer_font_family.clone().unwrap().into(),
family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
features: defaults.buffer_font_features.clone().unwrap(),
fallbacks: defaults
.buffer_font_fallbacks
.as_ref()
.map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
style: FontStyle::default(),
},
@ -543,7 +558,9 @@ impl settings::Settings for ThemeSettings {
if let Some(value) = value.buffer_font_features.clone() {
this.buffer_font.features = value;
}
if let Some(value) = value.buffer_font_fallbacks.clone() {
this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
}
if let Some(value) = value.buffer_font_weight {
this.buffer_font.weight = FontWeight(value);
}
@ -554,6 +571,9 @@ impl settings::Settings for ThemeSettings {
if let Some(value) = value.ui_font_features.clone() {
this.ui_font.features = value;
}
if let Some(value) = value.ui_font_fallbacks.clone() {
this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
}
if let Some(value) = value.ui_font_weight {
this.ui_font.weight = FontWeight(value);
}
@ -605,15 +625,28 @@ impl settings::Settings for ThemeSettings {
.iter()
.cloned()
.map(Value::String)
.collect();
let fonts_schema = SchemaObject {
.collect::<Vec<_>>();
let font_family_schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(available_fonts),
..Default::default()
};
let font_fallback_schema = SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(Box::new(ArrayValidation {
items: Some(schemars::schema::SingleOrVec::Single(Box::new(
font_family_schema.clone().into(),
))),
unique_items: Some(true),
..Default::default()
})),
..Default::default()
};
root_schema.definitions.extend([
("ThemeName".into(), theme_name_schema.into()),
("FontFamilies".into(), fonts_schema.into()),
("FontFamilies".into(), font_family_schema.into()),
("FontFallbacks".into(), font_fallback_schema.into()),
]);
root_schema
@ -627,10 +660,18 @@ impl settings::Settings for ThemeSettings {
"buffer_font_family".to_owned(),
Schema::new_ref("#/definitions/FontFamilies".into()),
),
(
"buffer_font_fallbacks".to_owned(),
Schema::new_ref("#/definitions/FontFallbacks".into()),
),
(
"ui_font_family".to_owned(),
Schema::new_ref("#/definitions/FontFamilies".into()),
),
(
"ui_font_fallbacks".to_owned(),
Schema::new_ref("#/definitions/FontFallbacks".into()),
),
]);
root_schema