Honor the `calt` font feature

Antonio Scandurra created

Change summary

crates/gpui/src/platform/mac/fonts.rs           | 57 ++++++++++++
crates/gpui/src/platform/mac/fonts/open_type.rs | 83 +++++++++++++++++++
2 files changed, 136 insertions(+), 4 deletions(-)

Detailed changes

crates/gpui/src/platform/mac/fonts.rs 🔗

@@ -1,3 +1,5 @@
+mod open_type;
+
 use crate::{
     fonts::{Features, FontId, GlyphId, Metrics, Properties},
     geometry::{
@@ -14,19 +16,29 @@ use core_foundation::{
     array::CFIndex,
     attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
     base::{CFRange, TCFType},
+    number::CFNumber,
     string::CFString,
 };
 use core_graphics::{
     base::{kCGImageAlphaPremultipliedLast, CGGlyph},
     color_space::CGColorSpace,
     context::CGContext,
+    geometry::CGAffineTransform,
+};
+use core_text::{
+    font::{CTFont, CTFontRef},
+    font_descriptor::{
+        CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
+    },
+    line::CTLine,
+    string_attributes::kCTFontAttributeName,
 };
-use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
 use font_kit::{
-    handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
+    font::Font, handle::Handle, hinting::HintingOptions, source::SystemSource,
+    sources::mem::MemSource,
 };
 use parking_lot::RwLock;
-use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
+use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, ptr, sync::Arc};
 
 #[allow(non_upper_case_globals)]
 const kCGImageAlphaOnly: u32 = 7;
@@ -134,7 +146,17 @@ impl FontSystemState {
             .select_family_by_name(name)
             .or_else(|_| self.system_source.select_family_by_name(name))?;
         for font in family.fonts() {
-            let font = font.load()?;
+            let mut font = font.load()?;
+
+            if let Some(calt) = features.calt {
+                let value = if calt {
+                    open_type::kContextualAlternatesOnSelector
+                } else {
+                    open_type::kContextualAlternatesOffSelector
+                };
+                font = toggle_open_type_feature(&font, open_type::kContextualAlternatesType, value);
+            }
+
             let font_id = FontId(self.fonts.len());
             font_ids.push(font_id);
             let postscript_name = font.postscript_name().unwrap();
@@ -490,6 +512,33 @@ extern "C" {
         start_index: CFIndex,
         width: f64,
     ) -> CFIndex;
+
+    fn CTFontCreateCopyWithAttributes(
+        font: CTFontRef,
+        size: CGFloat,
+        matrix: *const CGAffineTransform,
+        attributes: CTFontDescriptorRef,
+    ) -> CTFontRef;
+}
+
+fn toggle_open_type_feature(font: &Font, type_identifier: i32, selector_identifier: i32) -> Font {
+    let native_font = font.native_font();
+    unsafe {
+        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 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::from_native_font(new_font)
+    }
 }
 
 #[cfg(test)]

crates/gpui/src/platform/mac/fonts/open_type.rs 🔗

@@ -0,0 +1,83 @@
+#![allow(unused, non_upper_case_globals)]
+
+pub const kAltHalfWidthTextSelector: i32 = 6;
+pub const kAltProportionalTextSelector: i32 = 5;
+pub const kAlternateHorizKanaOffSelector: i32 = 1;
+pub const kAlternateHorizKanaOnSelector: i32 = 0;
+pub const kAlternateKanaType: i32 = 34;
+pub const kAlternateVertKanaOffSelector: i32 = 3;
+pub const kAlternateVertKanaOnSelector: i32 = 2;
+pub const kCaseSensitiveLayoutOffSelector: i32 = 1;
+pub const kCaseSensitiveLayoutOnSelector: i32 = 0;
+pub const kCaseSensitiveLayoutType: i32 = 33;
+pub const kCaseSensitiveSpacingOffSelector: i32 = 3;
+pub const kCaseSensitiveSpacingOnSelector: i32 = 2;
+pub const kContextualAlternatesOffSelector: i32 = 1;
+pub const kContextualAlternatesOnSelector: i32 = 0;
+pub const kContextualAlternatesType: i32 = 36;
+pub const kContextualLigaturesOffSelector: i32 = 19;
+pub const kContextualLigaturesOnSelector: i32 = 18;
+pub const kContextualSwashAlternatesOffSelector: i32 = 5;
+pub const kContextualSwashAlternatesOnSelector: i32 = 4;
+pub const kDefaultLowerCaseSelector: i32 = 0;
+pub const kDefaultUpperCaseSelector: i32 = 0;
+pub const kHistoricalLigaturesOffSelector: i32 = 21;
+pub const kHistoricalLigaturesOnSelector: i32 = 20;
+pub const kHojoCharactersSelector: i32 = 12;
+pub const kJIS2004CharactersSelector: i32 = 11;
+pub const kLowerCasePetiteCapsSelector: i32 = 2;
+pub const kLowerCaseSmallCapsSelector: i32 = 1;
+pub const kLowerCaseType: i32 = 37;
+pub const kMathematicalGreekOffSelector: i32 = 11;
+pub const kMathematicalGreekOnSelector: i32 = 10;
+pub const kNLCCharactersSelector: i32 = 13;
+pub const kQuarterWidthTextSelector: i32 = 4;
+pub const kScientificInferiorsSelector: i32 = 4;
+pub const kStylisticAltEightOffSelector: i32 = 17;
+pub const kStylisticAltEightOnSelector: i32 = 16;
+pub const kStylisticAltEighteenOffSelector: i32 = 37;
+pub const kStylisticAltEighteenOnSelector: i32 = 36;
+pub const kStylisticAltElevenOffSelector: i32 = 23;
+pub const kStylisticAltElevenOnSelector: i32 = 22;
+pub const kStylisticAltFifteenOffSelector: i32 = 31;
+pub const kStylisticAltFifteenOnSelector: i32 = 30;
+pub const kStylisticAltFiveOffSelector: i32 = 11;
+pub const kStylisticAltFiveOnSelector: i32 = 10;
+pub const kStylisticAltFourOffSelector: i32 = 9;
+pub const kStylisticAltFourOnSelector: i32 = 8;
+pub const kStylisticAltFourteenOffSelector: i32 = 29;
+pub const kStylisticAltFourteenOnSelector: i32 = 28;
+pub const kStylisticAltNineOffSelector: i32 = 19;
+pub const kStylisticAltNineOnSelector: i32 = 18;
+pub const kStylisticAltNineteenOffSelector: i32 = 39;
+pub const kStylisticAltNineteenOnSelector: i32 = 38;
+pub const kStylisticAltOneOffSelector: i32 = 3;
+pub const kStylisticAltOneOnSelector: i32 = 2;
+pub const kStylisticAltSevenOffSelector: i32 = 15;
+pub const kStylisticAltSevenOnSelector: i32 = 14;
+pub const kStylisticAltSeventeenOffSelector: i32 = 35;
+pub const kStylisticAltSeventeenOnSelector: i32 = 34;
+pub const kStylisticAltSixOffSelector: i32 = 13;
+pub const kStylisticAltSixOnSelector: i32 = 12;
+pub const kStylisticAltSixteenOffSelector: i32 = 33;
+pub const kStylisticAltSixteenOnSelector: i32 = 32;
+pub const kStylisticAltTenOffSelector: i32 = 21;
+pub const kStylisticAltTenOnSelector: i32 = 20;
+pub const kStylisticAltThirteenOffSelector: i32 = 27;
+pub const kStylisticAltThirteenOnSelector: i32 = 26;
+pub const kStylisticAltThreeOffSelector: i32 = 7;
+pub const kStylisticAltThreeOnSelector: i32 = 6;
+pub const kStylisticAltTwelveOffSelector: i32 = 25;
+pub const kStylisticAltTwelveOnSelector: i32 = 24;
+pub const kStylisticAltTwentyOffSelector: i32 = 41;
+pub const kStylisticAltTwentyOnSelector: i32 = 40;
+pub const kStylisticAltTwoOffSelector: i32 = 5;
+pub const kStylisticAltTwoOnSelector: i32 = 4;
+pub const kStylisticAlternativesType: i32 = 35;
+pub const kSwashAlternatesOffSelector: i32 = 3;
+pub const kSwashAlternatesOnSelector: i32 = 2;
+pub const kThirdWidthTextSelector: i32 = 3;
+pub const kTraditionalNamesCharactersSelector: i32 = 14;
+pub const kUpperCasePetiteCapsSelector: i32 = 2;
+pub const kUpperCaseSmallCapsSelector: i32 = 1;
+pub const kUpperCaseType: i32 = 38;