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:
parent
3e31955b7f
commit
a1bd7a1297
30 changed files with 444 additions and 136 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
});
|
});
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::{
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
crates/gpui/src/text_system/font_fallbacks.rs
Normal file
21
crates/gpui/src/text_system/font_fallbacks.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue