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", "cocoa",
"collections", "collections",
"core-foundation", "core-foundation",
"core-foundation-sys 0.8.6",
"core-graphics", "core-graphics",
"core-text", "core-text",
"cosmic-text", "cosmic-text",

View file

@ -26,6 +26,9 @@
}, },
// The name of a font to use for rendering text in the editor // The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Plex Mono", "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. // The OpenType features to enable for text in the editor.
"buffer_font_features": { "buffer_font_features": {
// Disable ligatures: // Disable ligatures:
@ -47,8 +50,11 @@
// }, // },
"buffer_line_height": "comfortable", "buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI // 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", "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 // The OpenType features to enable for text in the UI
"ui_font_features": { "ui_font_features": {
// Disable ligatures: // Disable ligatures:
@ -675,6 +681,10 @@
// Set the terminal's font family. If this option is not included, // Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family. // the terminal will default to matching the buffer's font family.
// "font_family": "Zed Plex Mono", // "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. // 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. // 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. // 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)) .timer(Duration::from_millis(200))
.await; .await;
let token_count = cx if let Some(token_count) = cx.update(|cx| {
.update(|cx| { LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx)
LanguageModelCompletionProvider::read_global(cx).count_tokens(request, cx) })? {
})? let token_count = token_count.await?;
.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(()) anyhow::Ok(())
} }
.log_err() .log_err()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
use anyhow::{anyhow, Result}; 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 gpui::{AppContext, Global, Model, ModelContext, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, LanguageModel, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry,
@ -143,11 +143,11 @@ impl LanguageModelCompletionProvider {
&self, &self,
request: LanguageModelRequest, request: LanguageModelRequest,
cx: &AppContext, cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> { ) -> Option<BoxFuture<'static, Result<usize>>> {
if let Some(model) = self.active_model() { if let Some(model) = self.active_model() {
model.count_tokens(request, cx) Some(model.count_tokens(request, cx))
} else { } 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, color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(), font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(), font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(), font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight, font_weight: settings.ui_font.weight,
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),
@ -12439,6 +12440,7 @@ impl Render for Editor {
color: cx.theme().colors().editor_foreground, color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(), font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(), font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(), font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight, font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),

View file

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

View file

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

View file

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

View file

@ -1,10 +1,12 @@
#![allow(unused, non_upper_case_globals)] #![allow(unused, non_upper_case_globals)]
use crate::FontFeatures; use crate::{FontFallbacks, FontFeatures};
use cocoa::appkit::CGFloat; use cocoa::appkit::CGFloat;
use core_foundation::{ use core_foundation::{
array::{ array::{
kCFTypeArrayCallBacks, CFArray, CFArrayAppendValue, CFArrayCreateMutable, CFMutableArrayRef, kCFTypeArrayCallBacks, CFArray, CFArrayAppendArray, CFArrayAppendValue,
CFArrayCreateMutable, CFArrayGetCount, CFArrayGetValueAtIndex, CFArrayRef,
CFMutableArrayRef,
}, },
base::{kCFAllocatorDefault, CFRelease, TCFType}, base::{kCFAllocatorDefault, CFRelease, TCFType},
dictionary::{ dictionary::{
@ -13,21 +15,88 @@ use core_foundation::{
number::CFNumber, number::CFNumber,
string::{CFString, CFStringRef}, string::{CFString, CFStringRef},
}; };
use core_foundation_sys::locale::CFLocaleCopyPreferredLanguages;
use core_graphics::{display::CFDictionary, geometry::CGAffineTransform}; use core_graphics::{display::CFDictionary, geometry::CGAffineTransform};
use core_text::{ use core_text::{
font::{CTFont, CTFontRef}, font::{cascade_list_for_languages, CTFont, CTFontRef},
font_descriptor::{ font_descriptor::{
kCTFontFeatureSettingsAttribute, CTFontDescriptor, CTFontDescriptorCopyAttributes, kCTFontCascadeListAttribute, kCTFontFeatureSettingsAttribute, CTFontDescriptor,
CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorCreateWithAttributes, CTFontDescriptorCopyAttributes, CTFontDescriptorCreateCopyWithFeature,
CTFontDescriptorCreateWithAttributes, CTFontDescriptorCreateWithNameAndSize,
CTFontDescriptorRef, CTFontDescriptorRef,
}, },
}; };
use font_kit::font::Font; use font_kit::font::Font as FontKitFont;
use std::ptr; 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 { unsafe {
let native_font = font.native_font();
let mut feature_array = let mut feature_array =
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for (tag, value) in features.tag_value_list() { 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 _); CFArrayAppendValue(feature_array, dict as _);
CFRelease(dict as _); CFRelease(dict as _);
} }
let attrs = CFDictionaryCreate( feature_array
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);
} }
} }
@ -82,4 +132,8 @@ extern "C" {
matrix: *const CGAffineTransform, matrix: *const CGAffineTransform,
attributes: CTFontDescriptorRef, attributes: CTFontDescriptorRef,
) -> CTFontRef; ) -> CTFontRef;
fn CTFontCopyDefaultCascadeListForLanguages(
font: CTFontRef,
languagePrefList: CFArrayRef,
) -> CFArrayRef;
} }

View file

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

View file

@ -30,6 +30,7 @@ struct FontInfo {
font_family: String, font_family: String,
font_face: IDWriteFontFace3, font_face: IDWriteFontFace3,
features: IDWriteTypography, features: IDWriteTypography,
fallbacks: Option<IDWriteFontFallback>,
is_system_font: bool, is_system_font: bool,
is_emoji: bool, is_emoji: bool,
} }
@ -287,6 +288,63 @@ impl DirectWriteState {
Ok(()) 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( unsafe fn generate_font_features(
&self, &self,
font_features: &FontFeatures, font_features: &FontFeatures,
@ -302,6 +360,7 @@ impl DirectWriteState {
font_weight: FontWeight, font_weight: FontWeight,
font_style: FontStyle, font_style: FontStyle,
font_features: &FontFeatures, font_features: &FontFeatures,
font_fallbacks: Option<&FontFallbacks>,
is_system_font: bool, is_system_font: bool,
) -> Option<FontId> { ) -> Option<FontId> {
let collection = if is_system_font { let collection = if is_system_font {
@ -334,11 +393,16 @@ impl DirectWriteState {
else { else {
continue; continue;
}; };
let fallbacks = self
.generate_font_fallbacks(font_fallbacks)
.log_err()
.unwrap_or_default();
let font_info = FontInfo { let font_info = FontInfo {
font_family: family_name.to_owned(), font_family: family_name.to_owned(),
font_face, font_face,
is_system_font,
features: direct_write_features, features: direct_write_features,
fallbacks,
is_system_font,
is_emoji, is_emoji,
}; };
let font_id = FontId(self.fonts.len()); let font_id = FontId(self.fonts.len());
@ -371,6 +435,7 @@ impl DirectWriteState {
target_font.weight, target_font.weight,
target_font.style, target_font.style,
&target_font.features, &target_font.features,
target_font.fallbacks.as_ref(),
) )
.unwrap() .unwrap()
} else { } else {
@ -379,6 +444,7 @@ impl DirectWriteState {
target_font.weight, target_font.weight,
target_font.style, target_font.style,
&target_font.features, &target_font.features,
target_font.fallbacks.as_ref(),
) )
.unwrap_or_else(|| { .unwrap_or_else(|| {
let family = self.system_ui_font_name.clone(); let family = self.system_ui_font_name.clone();
@ -388,6 +454,7 @@ impl DirectWriteState {
target_font.weight, target_font.weight,
target_font.style, target_font.style,
&target_font.features, &target_font.features,
target_font.fallbacks.as_ref(),
true, true,
) )
.unwrap() .unwrap()
@ -402,16 +469,38 @@ impl DirectWriteState {
weight: FontWeight, weight: FontWeight,
style: FontStyle, style: FontStyle,
features: &FontFeatures, features: &FontFeatures,
fallbacks: Option<&FontFallbacks>,
) -> Option<FontId> { ) -> Option<FontId> {
// try to find target font in custom font collection first // try to find target font in custom font collection first
self.get_font_id_from_font_collection(family_name, weight, style, features, false) self.get_font_id_from_font_collection(
.or_else(|| { family_name,
self.get_font_id_from_font_collection(family_name, weight, style, features, true) weight,
}) style,
.or_else(|| { features,
self.update_system_font_collection(); fallbacks,
self.get_font_id_from_font_collection(family_name, weight, style, features, true) 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( fn layout_line(
@ -440,15 +529,22 @@ impl DirectWriteState {
} else { } else {
&self.custom_font_collection &self.custom_font_collection
}; };
let format = self.components.factory.CreateTextFormat( let format: IDWriteTextFormat1 = self
&HSTRING::from(&font_info.font_family), .components
collection, .factory
font_info.font_face.GetWeight(), .CreateTextFormat(
font_info.font_face.GetStyle(), &HSTRING::from(&font_info.font_family),
DWRITE_FONT_STRETCH_NORMAL, collection,
font_size.0, font_info.font_face.GetWeight(),
&HSTRING::from(&self.components.locale), 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( let layout = self.components.factory.CreateTextLayout(
&text_wide, &text_wide,
@ -1183,6 +1279,7 @@ fn get_font_identifier_and_font_struct(
features: FontFeatures::default(), features: FontFeatures::default(),
weight: weight.into(), weight: weight.into(),
style: style.into(), style: style.into(),
fallbacks: None,
}; };
let is_emoji = unsafe { font_face.IsColorFont().as_bool() }; let is_emoji = unsafe { font_face.IsColorFont().as_bool() };
Some((identifier, font_struct, is_emoji)) Some((identifier, font_struct, is_emoji))

View file

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

View file

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

View file

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

View file

@ -1,8 +1,10 @@
mod font_fallbacks;
mod font_features; mod font_features;
mod line; mod line;
mod line_layout; mod line_layout;
mod line_wrapper; mod line_wrapper;
pub use font_fallbacks::*;
pub use font_features::*; pub use font_features::*;
pub use line::*; pub use line::*;
pub use line_layout::*; pub use line_layout::*;
@ -62,8 +64,7 @@ impl TextSystem {
wrapper_pool: Mutex::default(), wrapper_pool: Mutex::default(),
font_runs_pool: Mutex::default(), font_runs_pool: Mutex::default(),
fallback_font_stack: smallvec![ fallback_font_stack: smallvec![
// TODO: This is currently Zed-specific. // TODO: Remove this when Linux have implemented setting fallbacks.
// We should allow GPUI users to provide their own fallback font stack.
font("Zed Plex Mono"), font("Zed Plex Mono"),
font("Helvetica"), font("Helvetica"),
font("Segoe UI"), // Windows font("Segoe UI"), // Windows
@ -683,6 +684,9 @@ pub struct Font {
/// The font features to use. /// The font features to use.
pub features: FontFeatures, pub features: FontFeatures,
/// The fallbacks fonts to use.
pub fallbacks: Option<FontFallbacks>,
/// The font weight. /// The font weight.
pub weight: FontWeight, pub weight: FontWeight,
@ -697,6 +701,7 @@ pub fn font(family: impl Into<SharedString>) -> Font {
features: FontFeatures::default(), features: FontFeatures::default(),
weight: FontWeight::default(), weight: FontWeight::default(),
style: FontStyle::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, color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(), font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(), font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(), font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight, font_weight: settings.buffer_font.weight,
line_height: relative(1.), line_height: relative(1.),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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