From 2a7a3ce552c2b793e99041b0b2be66c23fc4ff69 Mon Sep 17 00:00:00 2001 From: Samuel Guerra Date: Fri, 9 Oct 2020 16:58:34 -0300 Subject: [PATCH] More text features added. --- .vscode/spellright.dict | 5 + src/core/text.rs | 276 +++++++++++++++++++++++++++++++---- src/properties/text_theme.rs | 36 +++++ 3 files changed, 290 insertions(+), 27 deletions(-) diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 1e98670c2..ec9faecfe 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -36,3 +36,8 @@ pnum tnum frac afrc +swsh +cswh +ornm +nalt +hist diff --git a/src/core/text.rs b/src/core/text.rs index 4b07772ad..6203e6a63 100644 --- a/src/core/text.rs +++ b/src/core/text.rs @@ -7,7 +7,7 @@ use crate::core::types::{FontInstanceKey, FontName, FontProperties, FontStyle}; use crate::core::var::ContextVar; use crate::properties::text_theme::FontFamilyVar; use fnv::FnvHashMap; -use std::{borrow::Cow, fmt, rc::Rc}; +use std::{borrow::Cow, fmt, mem, rc::Rc}; use std::{collections::hash_map::Entry as HEntry, num::NonZeroU32}; use std::{collections::HashMap, sync::Arc}; use webrender::api::units::Au; @@ -700,15 +700,6 @@ impl FontFeatures { } } - /// The most common ligatures, like for `fi`, `ffi`, `th` or similar. - /// - /// This corresponds to OpenType `liga` and `clig` features. - /// - /// `Auto` always activates these ligatures. - pub fn common_lig(&mut self) -> FontFeatureSet { - self.feature_set([b"liga", b"clig"]) - } - /// Font capital glyph variants. /// /// See [`CapsVariant`] for more details. @@ -736,9 +727,18 @@ impl FontFeatures { /// Font numeric spacing variants. /// /// See [`NumSpacing`] for more details. + #[inline] pub fn num_fraction(&mut self) -> NumFractionFeatures { NumFractionFeatures { features: &mut self.0 } } + + /// Enables stylistic alternatives for sets of character + /// + /// See [`StyleSet`] for more details. + #[inline] + pub fn style_set(&mut self) -> StyleSetFeatures { + StyleSetFeatures { features: &mut self.0 } + } } impl fmt::Debug for FontFeatures { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -778,16 +778,6 @@ impl FontFeaturesBuilder { self } - /// The most common ligatures, like for `fi`, `ffi`, `th` or similar. - /// - /// This corresponds to OpenType `liga` and `clig` features. - /// - /// `Auto` always activates these ligatures. - pub fn common_lig(mut self, state: impl Into) -> Self { - self.0.common_lig().set(state); - self - } - /// Font capital glyph variants. /// /// See [`CapsVariant`] for more details. @@ -823,6 +813,15 @@ impl FontFeaturesBuilder { self.0.num_fraction().set(state); self } + + /// Enables stylistic alternatives for sets of character + /// + /// See [`StyleSet`] for more details. + #[inline] + pub fn style_set(mut self, state: impl Into) -> Self { + self.0.style_set().set(state); + self + } } /// Generate `FontFeature` methods in `FontFeatures` and builder methods in `FontFeaturesBuilder` @@ -830,25 +829,44 @@ impl FontFeaturesBuilder { macro_rules! font_features { ($( $(#[$docs:meta])* - fn $name:ident($o_name:tt); + fn $name:ident($feat0:tt $(, $feat1:tt)?); )+) => { - impl FontFeatures {$( + $( + font_features!{feature $(#[$docs])* fn $name($feat0 $(, $feat1)?); } + font_features!{builder $(#[$docs])* fn $name(); } + )+ + }; + + (feature $(#[$docs:meta])* fn $name:ident($feat0:tt, $feat1:tt); ) => { + impl FontFeatures { + $(#[$docs])* + #[inline] + pub fn $name(&mut self) -> FontFeatureSet { + self.feature_set([$feat0, $feat1]) + } + } + }; + + (feature $(#[$docs:meta])* fn $name:ident($feat0:tt);) => { + impl FontFeatures { $(#[$docs])* #[inline] pub fn $name(&mut self) -> FontFeature { - self.feature($o_name) + self.feature($feat0) } - )+} + } + }; - impl FontFeaturesBuilder {$( + (builder $(#[$docs:meta])* fn $name:ident();) => { + impl FontFeaturesBuilder { $(#[$docs])* #[inline] pub fn $name(mut self, state: impl Into) -> Self { self.0.$name().set(state); self } - )+} - } + } + }; } font_features! { @@ -859,6 +877,13 @@ font_features! { /// `Auto` always activates these kerning. fn kerning(b"kern"); + /// The most common ligatures, like for `fi`, `ffi`, `th` or similar. + /// + /// This corresponds to OpenType `liga` and `clig` features. + /// + /// `Auto` always activates these ligatures. + fn common_lig(b"liga", b"clig"); + /// Ligatures specific to the font, usually decorative. /// /// This corresponds to OpenType `dlig` feature. @@ -893,6 +918,49 @@ font_features! { /// /// `Auto` deactivates this feature. fn slashed_zero(b"zero"); + + /// Use swashes flourish style. + /// + /// Fonts can have alternative swash styles, you can select then by enabling a number. + /// + /// This corresponds to OpenType `swsh` and `cswh` feature. + /// + /// `Auto` does not use swashes. + fn swash(b"swsh", b"cswh"); + + /// Use stylistic alternatives. + /// + /// Fonts can have multiple alternative styles, you can select then by enabling a number. + /// + /// This corresponds to OpenType `salt` feature. + /// + /// `Auto` does not use alternative styles. + fn stylistic(b"salt"); + + /// Use glyphs that were common in the past but not today. + /// + /// This corresponds to OpenType `hist` feature. + /// + /// `Auto` does not use alternative styles. + fn historical_forms(b"hist"); + + /// Replace letter with fleurons, dingbats and border elements. + /// + /// Fonts can have multiple alternative styles, you can select then by enabling a number. + /// + /// This corresponds to OpenType `ornm` feature. + /// + /// `Auto` does not enable this by default, but some fonts are purely dingbats glyphs. + fn ornaments(b"ornm"); + + /// Enables annotation alternatives, like circled digits or inverted characters. + /// + /// Fonts can have multiple alternative styles, you can select then by enabling a number. + /// + /// This corresponds to OpenType `nalt` feature. + /// + /// `Auto` does not use alternative styles. + fn annotation(b"nalt"); } // TODO @@ -1393,6 +1461,55 @@ impl<'a> fmt::Debug for NumFractionFeatures<'a> { fmt::Debug::fmt(&self.state(), f) } } +/// Represents the [style_set](FontFeatures::style_set) features. At any time only one of +/// these features are be enabled. +pub struct StyleSetFeatures<'a> { + features: &'a mut FnvHashMap, +} +impl<'a> StyleSetFeatures<'a> { + /// Gets the OpenType names of all the features affected. + #[inline] + pub fn names(&self) -> [FontFeatureName; 20] { + StyleSet::NAMES + } + + /// Gets the current state of the features. + #[inline] + pub fn state(&self) -> StyleSet { + for (i, name) in self.names().iter().enumerate() { + if self.features.get(name) == Some(&FEATURE_ENABLED) { + return (i as u8 + 1).into(); + } + } + StyleSet::Auto + } + fn take_state(&mut self) -> StyleSet { + let mut state = StyleSet::Auto; + for (i, name) in self.names().iter().enumerate() { + if self.features.get(name) == Some(&FEATURE_ENABLED) { + state = (i as u8 + 1).into() + } + } + state + } + + /// Sets the features. + /// + /// Returns the previous state. + #[inline] + pub fn set(&mut self, state: impl Into) -> StyleSet { + let prev = self.take_state(); + if let Some(name) = state.into().name() { + self.features.insert(name, FEATURE_ENABLED); + } + prev + } +} +impl<'a> fmt::Debug for StyleSetFeatures<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.state(), f) + } +} /// State of a [font feature](FontFeatures). #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -1571,3 +1688,108 @@ pub enum NumFraction { /// This corresponds to OpenType `afrc` feature. Stacked, } + +/// All possible [style_set](FontFeatures::style_set) features. +/// +/// The styles depend on the font, it is recommended you create an `enum` with named sets that +/// converts into this one for each font you wish to use. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[repr(u8)] +pub enum StyleSet { + /// Don't use alternative style set. + Auto = 0, + + S01, + S02, + S03, + S04, + S05, + S06, + S07, + S08, + S09, + S10, + + S11, + S12, + S13, + S14, + S15, + S16, + S17, + S18, + S19, + S20, +} +impl_from_and_into_var! { + /// `set == 0 || set > 20` is Auto, `set >= 1 && set <= 20` maps to their variant. + fn from(set: u8) -> StyleSet { + if set > 20 { + StyleSet::Auto + } else { + // SAFETY: We eliminated the bad values in the `if`. + unsafe { mem::transmute(set) } + } + } +} +impl StyleSet { + pub fn name(self) -> Option { + if self == StyleSet::Auto { + None + } else { + Some(Self::NAMES[self as usize - 1]) + } + } + + const NAMES: [FontFeatureName; 20] = [ + b"ss01", b"ss02", b"ss03", b"ss04", b"ss05", b"ss06", b"ss07", b"ss08", b"ss09", b"ss10", b"ss11", b"ss12", b"ss13", b"ss14", + b"ss15", b"ss16", b"ss17", b"ss18", b"ss19", b"ss20", + ]; +} + +/// All possible [character_variant](FontFeatures::character_variant) features (`cv00..=cv99`). +/// +/// The styles depend on the font, it is recommended you create `const`s with named variants to use with a specific font. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct CharacterVariant(u8); +impl CharacterVariant { + /// New variant. + /// + /// Returns auto if `v == 0 || v > 99`. + #[inline] + pub const fn new(v: u8) -> Self { + if v > 99 { + CharacterVariant(0) + } else { + CharacterVariant(v) + } + } + + /// New auto. + #[inline] + pub const fn auto() -> Self { + CharacterVariant(0) + } + + /// Is auto. + #[inline] + pub const fn is_auto(self) -> bool { + self.0 == 0 + } + + /// Gets the feature name if it is not auto. + #[inline] + pub fn name(self) -> Option { + todo!() + } + + /// Gets the variant number, if it is not auto. + #[inline] + pub const fn variant(self) -> Option { + if self.0 == 0 { + None + } else { + Some(self.0) + } + } +} diff --git a/src/properties/text_theme.rs b/src/properties/text_theme.rs index 54254ba7b..3d730c512 100644 --- a/src/properties/text_theme.rs +++ b/src/properties/text_theme.rs @@ -264,3 +264,39 @@ pub fn font_num_spacing(child: impl UiNode, state: impl IntoVar) -> pub fn font_num_fraction(child: impl UiNode, state: impl IntoVar) -> impl UiNode { with_font_feature(child, state, |f, s| f.num_fraction().set(s)) } + +/// Sets the font swash features. +#[property(context)] +pub fn font_swash(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.swash().set(s)) +} + +/// Sets the font stylistic alternative feature. +#[property(context)] +pub fn font_stylistic(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.stylistic().set(s)) +} + +/// Sets the font historical forms alternative feature. +#[property(context)] +pub fn font_historical_forms(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.historical_forms().set(s)) +} + +/// Sets the font ornaments alternative feature. +#[property(context)] +pub fn font_ornaments(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.ornaments().set(s)) +} + +/// Sets the font annotation alternative feature. +#[property(context)] +pub fn font_annotation(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.annotation().set(s)) +} + +/// Sets the font stylistic set alternative feature. +#[property(context)] +pub fn font_style_set(child: impl UiNode, state: impl IntoVar) -> impl UiNode { + with_font_feature(child, state, |f, s| f.style_set().set(s)) +}