Support setting font feature values (#11898)

Now (on `macOS` and `Windows`) we can set font feature value:
```rust
  "buffer_font_features": {
    "cv01": true,
    "cv03": 3,
    "cv09": 1,
    "VSAH": 7,
    "VSAJ": 8
  }
```

And one can still use `"cv01": true`.



https://github.com/zed-industries/zed/assets/14981363/3e3fcf4f-abdb-4d9e-a0a6-71dc24a515c2




Release Notes:

- Added font feature values, now you can set font features like `"cv01":
7`.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
张小白 2024-05-17 05:27:55 +08:00 committed by GitHub
parent b6189b05f9
commit 80caa74866
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 153 additions and 265 deletions

View file

@ -30,14 +30,11 @@ pub fn apply_features(font: &mut Font, features: &FontFeatures) {
let native_font = font.native_font(); let native_font = font.native_font();
let mut feature_array = let mut feature_array =
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for (tag, enable) in features.tag_value_list() { for (tag, value) in features.tag_value_list() {
if !enable {
continue;
}
let keys = [kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue]; let keys = [kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue];
let values = [ let values = [
CFString::new(&tag).as_CFTypeRef(), CFString::new(&tag).as_CFTypeRef(),
CFNumber::from(1).as_CFTypeRef(), CFNumber::from(*value as i32).as_CFTypeRef(),
]; ];
let dict = CFDictionaryCreate( let dict = CFDictionaryCreate(
kCFAllocatorDefault, kCFAllocatorDefault,

View file

@ -1201,26 +1201,26 @@ fn apply_font_features(
// All of these features are enabled by default by DirectWrite. // All of these features are enabled by default by DirectWrite.
// If you want to (and can) peek into the source of DirectWrite // If you want to (and can) peek into the source of DirectWrite
let mut feature_liga = make_direct_write_feature("liga", true); let mut feature_liga = make_direct_write_feature("liga", 1);
let mut feature_clig = make_direct_write_feature("clig", true); let mut feature_clig = make_direct_write_feature("clig", 1);
let mut feature_calt = make_direct_write_feature("calt", true); let mut feature_calt = make_direct_write_feature("calt", 1);
for (tag, enable) in tag_values { for (tag, value) in tag_values {
if tag == *"liga" && !enable { if tag.as_str() == "liga" && *value == 0 {
feature_liga.parameter = 0; feature_liga.parameter = 0;
continue; continue;
} }
if tag == *"clig" && !enable { if tag.as_str() == "clig" && *value == 0 {
feature_clig.parameter = 0; feature_clig.parameter = 0;
continue; continue;
} }
if tag == *"calt" && !enable { if tag.as_str() == "calt" && *value == 0 {
feature_calt.parameter = 0; feature_calt.parameter = 0;
continue; continue;
} }
unsafe { unsafe {
direct_write_features.AddFontFeature(make_direct_write_feature(&tag, enable))?; direct_write_features.AddFontFeature(make_direct_write_feature(&tag, *value))?;
} }
} }
unsafe { unsafe {
@ -1233,18 +1233,11 @@ fn apply_font_features(
} }
#[inline] #[inline]
fn make_direct_write_feature(feature_name: &str, enable: bool) -> DWRITE_FONT_FEATURE { fn make_direct_write_feature(feature_name: &str, parameter: u32) -> DWRITE_FONT_FEATURE {
let tag = make_direct_write_tag(feature_name); let tag = make_direct_write_tag(feature_name);
if enable {
DWRITE_FONT_FEATURE { DWRITE_FONT_FEATURE {
nameTag: tag, nameTag: tag,
parameter: 1, parameter,
}
} else {
DWRITE_FONT_FEATURE {
nameTag: tag,
parameter: 0,
}
} }
} }

View file

@ -1,88 +1,37 @@
use crate::SharedString; use std::sync::Arc;
use itertools::Itertools;
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
macro_rules! create_definitions { use schemars::schema::{InstanceType, SchemaObject};
($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
/// The OpenType features that can be configured for a given font. /// The OpenType features that can be configured for a given font.
#[derive(Default, Clone, Eq, PartialEq, Hash)] #[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures { pub struct FontFeatures(pub Arc<Vec<(String, u32)>>);
enabled: u64,
disabled: u64,
other_enabled: SharedString,
other_disabled: SharedString,
}
impl FontFeatures { impl FontFeatures {
$(
/// Get the current value of the corresponding OpenType feature
pub fn $name(&self) -> Option<bool> {
if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
}
}
)*
/// Get the tag name list of the font OpenType features /// Get the tag name list of the font OpenType features
/// only enabled or disabled features are returned /// only enabled or disabled features are returned
pub fn tag_value_list(&self) -> Vec<(String, bool)> { pub fn tag_value_list(&self) -> &[(String, u32)] {
let mut result = Vec::new(); &self.0.as_slice()
$(
{
let value = if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
};
if let Some(enable) = value {
let tag_name = stringify!($name).to_owned();
result.push((tag_name, enable));
}
}
)*
{
for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
result.push((name.collect::<String>(), true));
}
for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
result.push((name.collect::<String>(), false));
}
}
result
} }
} }
impl std::fmt::Debug for FontFeatures { impl std::fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("FontFeatures"); let mut debug = f.debug_struct("FontFeatures");
$( for (tag, value) in self.tag_value_list() {
if let Some(value) = self.$name() { debug.field(tag, value);
debug.field(stringify!($name), &value);
};
)*
#[cfg(target_os = "windows")]
{
for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
debug.field(name.collect::<String>().as_str(), &true);
}
for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
debug.field(name.collect::<String>().as_str(), &false);
}
} }
debug.finish() debug.finish()
} }
} }
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum FeatureValue {
Bool(bool),
Number(serde_json::Number),
}
impl<'de> serde::Deserialize<'de> for FontFeatures { impl<'de> serde::Deserialize<'de> for FontFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -104,47 +53,41 @@ macro_rules! create_definitions {
where where
M: MapAccess<'de>, M: MapAccess<'de>,
{ {
let mut enabled: u64 = 0; let mut feature_list = Vec::new();
let mut disabled: u64 = 0;
let mut other_enabled = "".to_owned();
let mut other_disabled = "".to_owned();
while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? { while let Some((key, value)) =
let idx = match key.as_str() { access.next_entry::<String, Option<FeatureValue>>()?
$(stringify!($name) => Some($idx),)* {
other_feature => { if key.len() != 4 && !key.is_ascii() {
if other_feature.len() != 4 || !other_feature.is_ascii() { log::error!("Incorrect font feature tag: {}", key);
log::error!("Incorrect feature name: {}", other_feature);
continue; continue;
} }
None if let Some(value) = value {
},
};
if let Some(idx) = idx {
match value { match value {
Some(true) => enabled |= 1 << idx, FeatureValue::Bool(enable) => {
Some(false) => disabled |= 1 << idx, if enable {
None => {} feature_list.push((key, 1));
};
} else { } else {
match value { feature_list.push((key, 0));
Some(true) => other_enabled.push_str(key.as_str()),
Some(false) => other_disabled.push_str(key.as_str()),
None => {}
};
} }
} }
let other_enabled = if other_enabled.is_empty() { FeatureValue::Number(value) => {
"".into() if value.is_u64() {
feature_list.push((key, value.as_u64().unwrap() as u32));
} else { } else {
other_enabled.into() log::error!(
}; "Incorrect font feature value {} for feature tag {}",
let other_disabled = if other_disabled.is_empty() { value,
"".into() key
} else { );
other_disabled.into() continue;
}; }
Ok(FontFeatures { enabled, disabled, other_enabled, other_disabled }) }
}
}
}
Ok(FontFeatures(Arc::new(feature_list)))
} }
} }
@ -162,85 +105,40 @@ macro_rules! create_definitions {
let mut map = serializer.serialize_map(None)?; let mut map = serializer.serialize_map(None)?;
$( for (tag, value) in self.tag_value_list() {
{ map.serialize_entry(tag, value)?;
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
}
)*
#[cfg(target_os = "windows")]
{
for name in self.other_enabled.as_ref().chars().chunks(4).into_iter() {
map.serialize_entry(name.collect::<String>().as_str(), &true)?;
}
for name in self.other_disabled.as_ref().chars().chunks(4).into_iter() {
map.serialize_entry(name.collect::<String>().as_str(), &false)?;
}
} }
map.end() map.end()
} }
} }
impl JsonSchema for FontFeatures { impl schemars::JsonSchema for FontFeatures {
fn schema_name() -> String { fn schema_name() -> String {
"FontFeatures".into() "FontFeatures".into()
} }
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema { fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let mut schema = SchemaObject::default(); let mut schema = SchemaObject::default();
let properties = &mut schema.object().properties; schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new(
let feature_schema = Schema::Object(SchemaObject { InstanceType::Object,
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))), )));
..Default::default() {
}); let mut property = SchemaObject::default();
property.instance_type = Some(schemars::schema::SingleOrVec::Vec(vec![
$( InstanceType::Boolean,
properties.insert(stringify!($name).to_owned(), feature_schema.clone()); InstanceType::Integer,
)* ]));
{
let mut number_constraints = property.number();
number_constraints.multiple_of = Some(1.0);
number_constraints.minimum = Some(0.0);
}
schema
.object()
.pattern_properties
.insert("[0-9a-zA-Z]{4}$".into(), property.into());
}
schema.into() schema.into()
} }
} }
};
}
create_definitions!(
(calt, 0),
(case, 1),
(cpsp, 2),
(frac, 3),
(liga, 4),
(onum, 5),
(ordn, 6),
(pnum, 7),
(ss01, 8),
(ss02, 9),
(ss03, 10),
(ss04, 11),
(ss05, 12),
(ss06, 13),
(ss07, 14),
(ss08, 15),
(ss09, 16),
(ss10, 17),
(ss11, 18),
(ss12, 19),
(ss13, 20),
(ss14, 21),
(ss15, 22),
(ss16, 23),
(ss17, 24),
(ss18, 25),
(ss19, 26),
(ss20, 27),
(subs, 28),
(sups, 29),
(swsh, 30),
(titl, 31),
(tnum, 32),
(zero, 33),
);