Honor more OpenType features

Antonio Scandurra created

Change summary

crates/gpui/src/fonts.rs                        |  33 +
crates/gpui/src/platform/mac/fonts.rs           |  54 --
crates/gpui/src/platform/mac/fonts/open_type.rs | 474 +++++++++++++++---
styles/src/styleTree/components.ts              |  73 ++
4 files changed, 500 insertions(+), 134 deletions(-)

Detailed changes

crates/gpui/src/fonts.rs 🔗

@@ -24,6 +24,39 @@ pub type GlyphId = u32;
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
 pub struct Features {
     pub calt: Option<bool>,
+    pub case: Option<bool>,
+    pub cpsp: Option<bool>,
+    pub frac: Option<bool>,
+    pub liga: Option<bool>,
+    pub onum: Option<bool>,
+    pub ordn: Option<bool>,
+    pub pnum: Option<bool>,
+    pub ss01: Option<bool>,
+    pub ss02: Option<bool>,
+    pub ss03: Option<bool>,
+    pub ss04: Option<bool>,
+    pub ss05: Option<bool>,
+    pub ss06: Option<bool>,
+    pub ss07: Option<bool>,
+    pub ss08: Option<bool>,
+    pub ss09: Option<bool>,
+    pub ss10: Option<bool>,
+    pub ss11: Option<bool>,
+    pub ss12: Option<bool>,
+    pub ss13: Option<bool>,
+    pub ss14: Option<bool>,
+    pub ss15: Option<bool>,
+    pub ss16: Option<bool>,
+    pub ss17: Option<bool>,
+    pub ss18: Option<bool>,
+    pub ss19: Option<bool>,
+    pub ss20: Option<bool>,
+    pub subs: Option<bool>,
+    pub sups: Option<bool>,
+    pub swsh: Option<bool>,
+    pub titl: Option<bool>,
+    pub tnum: Option<bool>,
+    pub zero: Option<bool>,
 }
 
 #[derive(Clone, Debug)]

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

@@ -16,29 +16,19 @@ 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::{
-    font::Font, handle::Handle, hinting::HintingOptions, source::SystemSource,
-    sources::mem::MemSource,
+    handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
 };
 use parking_lot::RwLock;
-use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, ptr, sync::Arc};
+use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
 
 #[allow(non_upper_case_globals)]
 const kCGImageAlphaOnly: u32 = 7;
@@ -147,16 +137,7 @@ impl FontSystemState {
             .or_else(|_| self.system_source.select_family_by_name(name))?;
         for font in family.fonts() {
             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);
-            }
-
+            open_type::apply_features(&mut font, features);
             let font_id = FontId(self.fonts.len());
             font_ids.push(font_id);
             let postscript_name = font.postscript_name().unwrap();
@@ -512,33 +493,6 @@ 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 🔗

@@ -1,83 +1,395 @@
 #![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;
+use std::ptr;
+
+use crate::fonts::Features;
+use cocoa::appkit::CGFloat;
+use core_foundation::{base::TCFType, number::CFNumber};
+use core_graphics::geometry::CGAffineTransform;
+use core_text::{
+    font::{CTFont, CTFontRef},
+    font_descriptor::{
+        CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
+    },
+};
+use font_kit::font::Font;
+
+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: &Features) {
+    // 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<bool>,
+    type_identifier: i32,
+    on_selector_identifier: i32,
+    off_selector_identifier: i32,
+) {
+    if let Some(enabled) = enabled {
+        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 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" {
+    fn CTFontCreateCopyWithAttributes(
+        font: CTFontRef,
+        size: CGFloat,
+        matrix: *const CGAffineTransform,
+        attributes: CTFontDescriptorRef,
+    ) -> CTFontRef;
+}

styles/src/styleTree/components.ts 🔗

@@ -98,11 +98,78 @@ export interface TextProperties {
     weight?: FontWeight
     underline?: boolean
     color?: string,
-    features?: TextFeatures,
+    features?: FontFeatures,
 }
 
-interface TextFeatures {
-    calt?: boolean
+interface FontFeatures {
+    /** Contextual Alternates: Applies a second substitution feature based on a match of a character pattern within a context of surrounding patterns */
+    calt?: boolean;
+    /** Case-Sensitive Forms: Shifts various punctuation marks up to a position that works better with all-capital sequences */
+    case?: boolean;
+    /** Capital Spacing: Adjusts inter-glyph spacing for all-capital text */
+    cpsp?: boolean;
+    /** Fractions: Replaces figures separated by a slash with diagonal fractions */
+    frac?: boolean;
+    /** Standard Ligatures: Replaces a sequence of glyphs with a single glyph which is preferred for typographic purposes */
+    liga?: boolean;
+    /** Oldstyle Figures: Changes selected figures from the default or lining style to oldstyle form. */
+    onum?: boolean;
+    /** Ordinals: Replaces default alphabetic glyphs with the corresponding ordinal forms for use after figures */
+    ordn?: boolean;
+    /** Proportional Figures: Replaces figure glyphs set on uniform (tabular) widths with corresponding glyphs set on proportional widths */
+    pnum?: boolean;
+    /** Stylistic set 01 */
+    ss01?: boolean;
+    /** Stylistic set 02 */
+    ss02?: boolean;
+    /** Stylistic set 03 */
+    ss03?: boolean;
+    /** Stylistic set 04 */
+    ss04?: boolean;
+    /** Stylistic set 05 */
+    ss05?: boolean;
+    /** Stylistic set 06 */
+    ss06?: boolean;
+    /** Stylistic set 07 */
+    ss07?: boolean;
+    /** Stylistic set 08 */
+    ss08?: boolean;
+    /** Stylistic set 09 */
+    ss09?: boolean;
+    /** Stylistic set 10 */
+    ss10?: boolean;
+    /** Stylistic set 11 */
+    ss11?: boolean;
+    /** Stylistic set 12 */
+    ss12?: boolean;
+    /** Stylistic set 13 */
+    ss13?: boolean;
+    /** Stylistic set 14 */
+    ss14?: boolean;
+    /** Stylistic set 15 */
+    ss15?: boolean;
+    /** Stylistic set 16 */
+    ss16?: boolean;
+    /** Stylistic set 17 */
+    ss17?: boolean;
+    /** Stylistic set 18 */
+    ss18?: boolean;
+    /** Stylistic set 19 */
+    ss19?: boolean;
+    /** Stylistic set 20 */
+    ss20?: boolean;
+    /** Subscript: Replaces default glyphs with subscript glyphs */
+    subs?: boolean;
+    /** Superscript: Replaces default glyphs with superscript glyphs */
+    sups?: boolean;
+    /** Swash: Replaces default glyphs with swash glyphs for stylistic purposes */
+    swsh?: boolean;
+    /** Titling: Replaces default glyphs with titling glyphs for use in large-size settings */
+    titl?: boolean;
+    /** Tabular Figures: Replaces figure glyphs set on proportional widths with corresponding glyphs set on uniform (tabular) widths */
+    tnum?: boolean;
+    /** Slashed Zero: Replaces default zero with a slashed zero for better distinction between "0" and "O" */
+    zero?: boolean;
 }
 
 export function text(