From f7c5d70740cda7db8d85c5f9057891501211a042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 16 May 2024 00:26:50 +0800 Subject: [PATCH] macOS: Support all `OpenType` font features (#11611) This PR brings support for all `OpenType` font features to `macOS(v10.10+)`. Now, both `Windows`(with #10756 ) and `macOS` support all font features. Due to my limited familiarity with the APIs on macOS, I believe I have made sure to call `CFRelease` on all variables where it should be called. Close #11486 , and I think the official website's [documentation](https://zed.dev/docs/configuring-zed) can be updated after merging this PR. > Zed supports a subset of OpenType features that can be enabled or disabled for a given buffer or terminal font. The following OpenType features can be enabled or disabled too: calt, case, cpsp, frac, liga, onum, ordn, pnum, ss01, ss02, ss03, ss04, ss05, ss06, ss07, ss08, ss09, ss10, ss11, ss12, ss13, ss14, ss15, ss16, ss17, ss18, ss19, ss20, subs, sups, swsh, titl, tnum, zero. https://github.com/zed-industries/zed/assets/14981363/44e503f9-1496-4746-bc7d-20878c6f8a93 Release Notes: - Added support for **all** `OpenType` font features to macOS. --- crates/gpui/src/platform/mac/open_type.rs | 426 +++---------------- crates/gpui/src/text_system/font_features.rs | 28 -- 2 files changed, 60 insertions(+), 394 deletions(-) diff --git a/crates/gpui/src/platform/mac/open_type.rs b/crates/gpui/src/platform/mac/open_type.rs index d465e8f745..9948162c86 100644 --- a/crates/gpui/src/platform/mac/open_type.rs +++ b/crates/gpui/src/platform/mac/open_type.rs @@ -2,389 +2,83 @@ use crate::FontFeatures; use cocoa::appkit::CGFloat; -use core_foundation::{base::TCFType, number::CFNumber}; -use core_graphics::geometry::CGAffineTransform; +use core_foundation::{ + array::{ + kCFTypeArrayCallBacks, CFArray, CFArrayAppendValue, CFArrayCreateMutable, CFMutableArrayRef, + }, + base::{kCFAllocatorDefault, CFRelease, TCFType}, + dictionary::{ + kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionaryCreate, + }, + number::CFNumber, + string::{CFString, CFStringRef}, +}; +use core_graphics::{display::CFDictionary, geometry::CGAffineTransform}; use core_text::{ font::{CTFont, CTFontRef}, font_descriptor::{ - CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef, + kCTFontFeatureSettingsAttribute, CTFontDescriptor, CTFontDescriptorCopyAttributes, + CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorCreateWithAttributes, + CTFontDescriptorRef, }, }; use font_kit::font::Font; use std::ptr; -const kCaseSensitiveLayoutOffSelector: i32 = 1; -const kCaseSensitiveLayoutOnSelector: i32 = 0; -const kCaseSensitiveLayoutType: i32 = 33; -const kCaseSensitiveSpacingOffSelector: i32 = 3; -const kCaseSensitiveSpacingOnSelector: i32 = 2; -const kCharacterAlternativesType: i32 = 17; -const kCommonLigaturesOffSelector: i32 = 3; -const kCommonLigaturesOnSelector: i32 = 2; -const kContextualAlternatesOffSelector: i32 = 1; -const kContextualAlternatesOnSelector: i32 = 0; -const kContextualAlternatesType: i32 = 36; -const kContextualLigaturesOffSelector: i32 = 19; -const kContextualLigaturesOnSelector: i32 = 18; -const kContextualSwashAlternatesOffSelector: i32 = 5; -const kContextualSwashAlternatesOnSelector: i32 = 4; -const kDefaultLowerCaseSelector: i32 = 0; -const kDefaultUpperCaseSelector: i32 = 0; -const kDiagonalFractionsSelector: i32 = 2; -const kFractionsType: i32 = 11; -const kHistoricalLigaturesOffSelector: i32 = 21; -const kHistoricalLigaturesOnSelector: i32 = 20; -const kHojoCharactersSelector: i32 = 12; -const kInferiorsSelector: i32 = 2; -const kJIS2004CharactersSelector: i32 = 11; -const kLigaturesType: i32 = 1; -const kLowerCasePetiteCapsSelector: i32 = 2; -const kLowerCaseSmallCapsSelector: i32 = 1; -const kLowerCaseType: i32 = 37; -const kLowerCaseNumbersSelector: i32 = 0; -const kMathematicalGreekOffSelector: i32 = 11; -const kMathematicalGreekOnSelector: i32 = 10; -const kMonospacedNumbersSelector: i32 = 0; -const kNLCCharactersSelector: i32 = 13; -const kNoFractionsSelector: i32 = 0; -const kNormalPositionSelector: i32 = 0; -const kNoStyleOptionsSelector: i32 = 0; -const kNumberCaseType: i32 = 21; -const kNumberSpacingType: i32 = 6; -const kOrdinalsSelector: i32 = 3; -const kProportionalNumbersSelector: i32 = 1; -const kQuarterWidthTextSelector: i32 = 4; -const kScientificInferiorsSelector: i32 = 4; -const kSlashedZeroOffSelector: i32 = 5; -const kSlashedZeroOnSelector: i32 = 4; -const kStyleOptionsType: i32 = 19; -const kStylisticAltEighteenOffSelector: i32 = 37; -const kStylisticAltEighteenOnSelector: i32 = 36; -const kStylisticAltEightOffSelector: i32 = 17; -const kStylisticAltEightOnSelector: i32 = 16; -const kStylisticAltElevenOffSelector: i32 = 23; -const kStylisticAltElevenOnSelector: i32 = 22; -const kStylisticAlternativesType: i32 = 35; -const kStylisticAltFifteenOffSelector: i32 = 31; -const kStylisticAltFifteenOnSelector: i32 = 30; -const kStylisticAltFiveOffSelector: i32 = 11; -const kStylisticAltFiveOnSelector: i32 = 10; -const kStylisticAltFourOffSelector: i32 = 9; -const kStylisticAltFourOnSelector: i32 = 8; -const kStylisticAltFourteenOffSelector: i32 = 29; -const kStylisticAltFourteenOnSelector: i32 = 28; -const kStylisticAltNineOffSelector: i32 = 19; -const kStylisticAltNineOnSelector: i32 = 18; -const kStylisticAltNineteenOffSelector: i32 = 39; -const kStylisticAltNineteenOnSelector: i32 = 38; -const kStylisticAltOneOffSelector: i32 = 3; -const kStylisticAltOneOnSelector: i32 = 2; -const kStylisticAltSevenOffSelector: i32 = 15; -const kStylisticAltSevenOnSelector: i32 = 14; -const kStylisticAltSeventeenOffSelector: i32 = 35; -const kStylisticAltSeventeenOnSelector: i32 = 34; -const kStylisticAltSixOffSelector: i32 = 13; -const kStylisticAltSixOnSelector: i32 = 12; -const kStylisticAltSixteenOffSelector: i32 = 33; -const kStylisticAltSixteenOnSelector: i32 = 32; -const kStylisticAltTenOffSelector: i32 = 21; -const kStylisticAltTenOnSelector: i32 = 20; -const kStylisticAltThirteenOffSelector: i32 = 27; -const kStylisticAltThirteenOnSelector: i32 = 26; -const kStylisticAltThreeOffSelector: i32 = 7; -const kStylisticAltThreeOnSelector: i32 = 6; -const kStylisticAltTwelveOffSelector: i32 = 25; -const kStylisticAltTwelveOnSelector: i32 = 24; -const kStylisticAltTwentyOffSelector: i32 = 41; -const kStylisticAltTwentyOnSelector: i32 = 40; -const kStylisticAltTwoOffSelector: i32 = 5; -const kStylisticAltTwoOnSelector: i32 = 4; -const kSuperiorsSelector: i32 = 1; -const kSwashAlternatesOffSelector: i32 = 3; -const kSwashAlternatesOnSelector: i32 = 2; -const kTitlingCapsSelector: i32 = 4; -const kTypographicExtrasType: i32 = 14; -const kVerticalFractionsSelector: i32 = 1; -const kVerticalPositionType: i32 = 10; - pub fn apply_features(font: &mut Font, features: &FontFeatures) { - // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc - // for a reference implementation. - toggle_open_type_feature( - font, - features.calt(), - kContextualAlternatesType, - kContextualAlternatesOnSelector, - kContextualAlternatesOffSelector, - ); - toggle_open_type_feature( - font, - features.case(), - kCaseSensitiveLayoutType, - kCaseSensitiveLayoutOnSelector, - kCaseSensitiveLayoutOffSelector, - ); - toggle_open_type_feature( - font, - features.cpsp(), - kCaseSensitiveLayoutType, - kCaseSensitiveSpacingOnSelector, - kCaseSensitiveSpacingOffSelector, - ); - toggle_open_type_feature( - font, - features.frac(), - kFractionsType, - kDiagonalFractionsSelector, - kNoFractionsSelector, - ); - toggle_open_type_feature( - font, - features.liga(), - kLigaturesType, - kCommonLigaturesOnSelector, - kCommonLigaturesOffSelector, - ); - toggle_open_type_feature( - font, - features.onum(), - kNumberCaseType, - kLowerCaseNumbersSelector, - 2, - ); - toggle_open_type_feature( - font, - features.ordn(), - kVerticalPositionType, - kOrdinalsSelector, - kNormalPositionSelector, - ); - toggle_open_type_feature( - font, - features.pnum(), - kNumberSpacingType, - kProportionalNumbersSelector, - 4, - ); - toggle_open_type_feature( - font, - features.ss01(), - kStylisticAlternativesType, - kStylisticAltOneOnSelector, - kStylisticAltOneOffSelector, - ); - toggle_open_type_feature( - font, - features.ss02(), - kStylisticAlternativesType, - kStylisticAltTwoOnSelector, - kStylisticAltTwoOffSelector, - ); - toggle_open_type_feature( - font, - features.ss03(), - kStylisticAlternativesType, - kStylisticAltThreeOnSelector, - kStylisticAltThreeOffSelector, - ); - toggle_open_type_feature( - font, - features.ss04(), - kStylisticAlternativesType, - kStylisticAltFourOnSelector, - kStylisticAltFourOffSelector, - ); - toggle_open_type_feature( - font, - features.ss05(), - kStylisticAlternativesType, - kStylisticAltFiveOnSelector, - kStylisticAltFiveOffSelector, - ); - toggle_open_type_feature( - font, - features.ss06(), - kStylisticAlternativesType, - kStylisticAltSixOnSelector, - kStylisticAltSixOffSelector, - ); - toggle_open_type_feature( - font, - features.ss07(), - kStylisticAlternativesType, - kStylisticAltSevenOnSelector, - kStylisticAltSevenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss08(), - kStylisticAlternativesType, - kStylisticAltEightOnSelector, - kStylisticAltEightOffSelector, - ); - toggle_open_type_feature( - font, - features.ss09(), - kStylisticAlternativesType, - kStylisticAltNineOnSelector, - kStylisticAltNineOffSelector, - ); - toggle_open_type_feature( - font, - features.ss10(), - kStylisticAlternativesType, - kStylisticAltTenOnSelector, - kStylisticAltTenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss11(), - kStylisticAlternativesType, - kStylisticAltElevenOnSelector, - kStylisticAltElevenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss12(), - kStylisticAlternativesType, - kStylisticAltTwelveOnSelector, - kStylisticAltTwelveOffSelector, - ); - toggle_open_type_feature( - font, - features.ss13(), - kStylisticAlternativesType, - kStylisticAltThirteenOnSelector, - kStylisticAltThirteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss14(), - kStylisticAlternativesType, - kStylisticAltFourteenOnSelector, - kStylisticAltFourteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss15(), - kStylisticAlternativesType, - kStylisticAltFifteenOnSelector, - kStylisticAltFifteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss16(), - kStylisticAlternativesType, - kStylisticAltSixteenOnSelector, - kStylisticAltSixteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss17(), - kStylisticAlternativesType, - kStylisticAltSeventeenOnSelector, - kStylisticAltSeventeenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss18(), - kStylisticAlternativesType, - kStylisticAltEighteenOnSelector, - kStylisticAltEighteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss19(), - kStylisticAlternativesType, - kStylisticAltNineteenOnSelector, - kStylisticAltNineteenOffSelector, - ); - toggle_open_type_feature( - font, - features.ss20(), - kStylisticAlternativesType, - kStylisticAltTwentyOnSelector, - kStylisticAltTwentyOffSelector, - ); - toggle_open_type_feature( - font, - features.subs(), - kVerticalPositionType, - kInferiorsSelector, - kNormalPositionSelector, - ); - toggle_open_type_feature( - font, - features.sups(), - kVerticalPositionType, - kSuperiorsSelector, - kNormalPositionSelector, - ); - toggle_open_type_feature( - font, - features.swsh(), - kContextualAlternatesType, - kSwashAlternatesOnSelector, - kSwashAlternatesOffSelector, - ); - toggle_open_type_feature( - font, - features.titl(), - kStyleOptionsType, - kTitlingCapsSelector, - kNoStyleOptionsSelector, - ); - toggle_open_type_feature( - font, - features.tnum(), - kNumberSpacingType, - kMonospacedNumbersSelector, - 4, - ); - toggle_open_type_feature( - font, - features.zero(), - kTypographicExtrasType, - kSlashedZeroOnSelector, - kSlashedZeroOffSelector, - ); -} - -fn toggle_open_type_feature( - font: &mut Font, - enabled: Option, - type_identifier: i32, - on_selector_identifier: i32, - off_selector_identifier: i32, -) { - if let Some(enabled) = enabled { + unsafe { let native_font = font.native_font(); - unsafe { - let selector_identifier = if enabled { - on_selector_identifier - } else { - off_selector_identifier - }; - let new_descriptor = CTFontDescriptorCreateCopyWithFeature( - native_font.copy_descriptor().as_concrete_TypeRef(), - CFNumber::from(type_identifier).as_concrete_TypeRef(), - CFNumber::from(selector_identifier).as_concrete_TypeRef(), + let mut feature_array = + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + for (tag, enable) in features.tag_value_list() { + if !enable { + continue; + } + let keys = [kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue]; + let values = [ + CFString::new(&tag).as_CFTypeRef(), + CFNumber::from(1).as_CFTypeRef(), + ]; + let dict = CFDictionaryCreate( + kCFAllocatorDefault, + &keys as *const _ as _, + &values as *const _ as _, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks, ); - 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); + values.into_iter().for_each(|value| CFRelease(value)); + CFArrayAppendValue(feature_array, dict as _); + CFRelease(dict as _); } + let attrs = CFDictionaryCreate( + kCFAllocatorDefault, + &kCTFontFeatureSettingsAttribute as *const _ as _, + &feature_array as *const _ as _, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks, + ); + CFRelease(feature_array as *const _ as _); + let new_descriptor = CTFontDescriptorCreateWithAttributes(attrs); + CFRelease(attrs as _); + let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor); + let new_font = CTFontCreateCopyWithAttributes( + font.native_font().as_concrete_TypeRef(), + 0.0, + ptr::null(), + new_descriptor.as_concrete_TypeRef(), + ); + let new_font = CTFont::wrap_under_create_rule(new_font); + *font = Font::from_native_font(&new_font); } } #[link(name = "CoreText", kind = "framework")] extern "C" { + static kCTFontOpenTypeFeatureTag: CFStringRef; + static kCTFontOpenTypeFeatureValue: CFStringRef; + fn CTFontCreateCopyWithAttributes( font: CTFontRef, size: CGFloat, diff --git a/crates/gpui/src/text_system/font_features.rs b/crates/gpui/src/text_system/font_features.rs index 39ec18f74b..3202b6f60f 100644 --- a/crates/gpui/src/text_system/font_features.rs +++ b/crates/gpui/src/text_system/font_features.rs @@ -1,6 +1,4 @@ -#[cfg(target_os = "windows")] use crate::SharedString; -#[cfg(target_os = "windows")] use itertools::Itertools; use schemars::{ schema::{InstanceType, Schema, SchemaObject, SingleOrVec}, @@ -15,9 +13,7 @@ macro_rules! create_definitions { pub struct FontFeatures { enabled: u64, disabled: u64, - #[cfg(target_os = "windows")] other_enabled: SharedString, - #[cfg(target_os = "windows")] other_disabled: SharedString, } @@ -37,7 +33,6 @@ macro_rules! create_definitions { /// Get the tag name list of the font OpenType features /// only enabled or disabled features are returned - #[cfg(target_os = "windows")] pub fn tag_value_list(&self) -> Vec<(String, bool)> { let mut result = Vec::new(); $( @@ -105,29 +100,6 @@ macro_rules! create_definitions { formatter.write_str("a map of font features") } - #[cfg(not(target_os = "windows"))] - fn visit_map(self, mut access: M) -> Result - where - M: MapAccess<'de>, - { - let mut enabled: u64 = 0; - let mut disabled: u64 = 0; - - while let Some((key, value)) = access.next_entry::>()? { - let idx = match key.as_str() { - $(stringify!($name) => $idx,)* - _ => continue, - }; - match value { - Some(true) => enabled |= 1 << idx, - Some(false) => disabled |= 1 << idx, - None => {} - }; - } - Ok(FontFeatures { enabled, disabled }) - } - - #[cfg(target_os = "windows")] fn visit_map(self, mut access: M) -> Result where M: MapAccess<'de>,