diff --git a/src/attrs.rs b/src/attrs.rs index d7acc1c..3debcc4 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -125,6 +125,31 @@ impl From for Metrics { } } +/// Font features for controlling typographic behavior +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct FontFeatures { + /// Kerning adjusts spacing between specific character pairs + pub kerning: bool, + /// Standard ligatures (fi, fl, etc.) + pub standard_ligatures: bool, + /// Contextual ligatures (context-dependent ligatures) + pub contextual_ligatures: bool, + /// Discretionary ligatures (optional stylistic ligatures) + pub discretionary_ligatures: bool, +} + +impl FontFeatures { + /// Create new font features with default settings + pub fn new() -> Self { + Self { + kerning: true, + standard_ligatures: true, + contextual_ligatures: false, + discretionary_ligatures: false, + } + } +} + /// Text attributes #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Attrs<'a> { @@ -137,6 +162,7 @@ pub struct Attrs<'a> { pub metadata: usize, pub cache_key_flags: CacheKeyFlags, pub metrics_opt: Option, + pub font_features: FontFeatures, } impl<'a> Attrs<'a> { @@ -153,6 +179,7 @@ impl<'a> Attrs<'a> { metadata: 0, cache_key_flags: CacheKeyFlags::empty(), metrics_opt: None, + font_features: FontFeatures::new(), } } @@ -204,6 +231,12 @@ impl<'a> Attrs<'a> { self } + /// Set [FontFeatures] + pub fn font_features(mut self, font_features: FontFeatures) -> Self { + self.font_features = font_features; + self + } + /// Check if font matches pub fn matches(&self, face: &fontdb::FaceInfo) -> bool { //TODO: smarter way of including emoji @@ -252,6 +285,7 @@ pub struct AttrsOwned { pub metadata: usize, pub cache_key_flags: CacheKeyFlags, pub metrics_opt: Option, + pub font_features: FontFeatures, } impl AttrsOwned { @@ -265,6 +299,7 @@ impl AttrsOwned { metadata: attrs.metadata, cache_key_flags: attrs.cache_key_flags, metrics_opt: attrs.metrics_opt, + font_features: attrs.font_features, } } @@ -278,6 +313,7 @@ impl AttrsOwned { metadata: self.metadata, cache_key_flags: self.cache_key_flags, metrics_opt: self.metrics_opt, + font_features: self.font_features, } } } diff --git a/src/shape.rs b/src/shape.rs index 04a4d40..31a510c 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -140,12 +140,48 @@ fn shape_fallback( let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft); assert_eq!(rtl, span_rtl); + let attrs = attrs_list.get_span(start_run); + + let mut features = Vec::new(); + + if attrs.font_features.kerning { + features.push(rustybuzz::Feature::new( + rustybuzz::ttf_parser::Tag::from_bytes(b"kern"), + 1, + 0..usize::MAX, + )); + } + + if attrs.font_features.standard_ligatures { + features.push(rustybuzz::Feature::new( + rustybuzz::ttf_parser::Tag::from_bytes(b"liga"), + 1, + 0..usize::MAX, + )); + } + + if attrs.font_features.contextual_ligatures { + features.push(rustybuzz::Feature::new( + rustybuzz::ttf_parser::Tag::from_bytes(b"clig"), + 1, + 0..usize::MAX, + )); + } + + if attrs.font_features.discretionary_ligatures { + features.push(rustybuzz::Feature::new( + rustybuzz::ttf_parser::Tag::from_bytes(b"dlig"), + 1, + 0..usize::MAX, + )); + } + let shape_plan = rustybuzz::ShapePlan::new( font.rustybuzz(), buffer.direction(), Some(buffer.script()), buffer.language().as_ref(), - &[], + &features, ); let glyph_buffer = rustybuzz::shape_with_plan(font.rustybuzz(), &shape_plan, buffer); let glyph_infos = glyph_buffer.glyph_infos();