use std::{
collections::{HashMap, HashSet, hash_map},
fmt,
marker::PhantomData,
num::NonZeroU32,
ops,
};
use num_enum::FromPrimitive;
#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FontFeatureName(pub [u8; 4]);
impl FontFeatureName {
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or_default()
}
}
impl From<&'static [u8; 4]> for FontFeatureName {
fn from(name: &'static [u8; 4]) -> Self {
FontFeatureName(*name)
}
}
impl From<FontFeatureName> for skrifa::Tag {
fn from(value: FontFeatureName) -> Self {
skrifa::Tag::new(&value.0)
}
}
impl ops::Deref for FontFeatureName {
type Target = [u8; 4];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Debug for FontFeatureName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.as_str().is_empty() {
write!(f, "{:?}", self.0)
} else {
write!(f, "{}", self.as_str())
}
}
}
impl fmt::Display for FontFeatureName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
pub const FEATURE_ENABLED: u32 = 1;
pub const FEATURE_DISABLED: u32 = 0;
type FontFeaturesMap = HashMap<FontFeatureName, u32>;
#[derive(Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FontFeatures(FontFeaturesMap);
impl FontFeatures {
pub fn new() -> FontFeatures {
FontFeatures::default()
}
pub fn builder() -> FontFeaturesBuilder {
FontFeaturesBuilder::default()
}
pub fn set_all(&mut self, other: &FontFeatures) -> Vec<(FontFeatureName, Option<u32>)> {
let mut prev = Vec::with_capacity(other.0.len());
for (&name, &state) in other.0.iter() {
prev.push((name, self.0.insert(name, state)));
}
prev
}
pub fn restore(&mut self, prev: Vec<(FontFeatureName, Option<u32>)>) {
for (name, state) in prev {
match state {
Some(state) => {
self.0.insert(name, state);
}
None => {
self.0.remove(&name);
}
}
}
}
pub fn feature(&mut self, name: FontFeatureName) -> FontFeature<'_> {
FontFeature(self.0.entry(name))
}
pub fn feature_set(&mut self, names: &'static [FontFeatureName]) -> FontFeatureSet<'_> {
assert!(names.len() >= 2);
FontFeatureSet {
features: &mut self.0,
names,
}
}
pub fn feature_exclusive_set<S: FontFeatureExclusiveSetState>(&mut self) -> FontFeatureExclusiveSet<'_, S> {
assert!(S::names().len() >= 2);
FontFeatureExclusiveSet {
features: &mut self.0,
_t: PhantomData,
}
}
pub fn feature_exclusive_sets<S: FontFeatureExclusiveSetsState>(&mut self) -> FontFeatureExclusiveSets<'_, S> {
assert!(S::names().len() >= 2);
FontFeatureExclusiveSets {
features: &mut self.0,
_t: PhantomData,
}
}
pub fn finalize(&self) -> RFontFeatures {
self.0
.iter()
.map(|(&n, &s)| harfrust::Feature::new(skrifa::Tag::from(n), s, 0..usize::MAX))
.collect()
}
}
impl fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut map = f.debug_map();
for (name, state) in self.0.iter() {
map.entry(&name.as_str(), state);
}
map.finish()
}
}
pub type RFontFeatures = Vec<harfrust::Feature>;
#[derive(Default)]
pub struct FontFeaturesBuilder(FontFeatures);
impl FontFeaturesBuilder {
pub fn build(self) -> FontFeatures {
self.0
}
pub fn feature(mut self, name: FontFeatureName, state: impl Into<FontFeatureState>) -> Self {
self.0.feature(name).set(state);
self
}
pub fn feature_set(mut self, names: &'static [FontFeatureName], state: impl Into<FontFeatureState>) -> Self {
self.0.feature_set(names).set(state);
self
}
pub fn feature_exclusive_set<S: FontFeatureExclusiveSetState>(mut self, state: impl Into<S>) -> Self {
self.0.feature_exclusive_set::<S>().set(state);
self
}
pub fn feature_exclusive_sets<S: FontFeatureExclusiveSetsState>(mut self, state: impl Into<S>) -> Self {
self.0.feature_exclusive_sets::<S>().set(state);
self
}
}
macro_rules! font_features {
($(
$(#[$docs:meta])*
fn $name:ident($feat0_or_Enum:tt $(, $feat1:tt)?) $(-> $Helper:ident)?;
)+) => {
impl FontFeatures {$(
font_features!{feature $(#[$docs])* fn $name($feat0_or_Enum $(, $feat1)?) $(-> $Helper)?; }
)+}
impl FontFeaturesBuilder {$(
font_features!{builder $(#[$docs])* fn $name($($feat0_or_Enum -> $Helper)?); }
)+}
};
(feature $(#[$docs:meta])* fn $name:ident($feat0:tt, $feat1:tt); ) => {
$(#[$docs])*
pub fn $name(&mut self) -> FontFeatureSet<'_> {
static FEATS: [FontFeatureName; 2] = [FontFeatureName(*$feat0), FontFeatureName(*$feat1)];
self.feature_set(&FEATS)
}
};
(feature $(#[$docs:meta])* fn $name:ident($feat0:tt);) => {
$(#[$docs])*
pub fn $name(&mut self) -> FontFeature<'_> {
self.feature(FontFeatureName(*$feat0))
}
};
(feature $(#[$docs:meta])* fn $name:ident($Enum:ident) -> $Helper:ident;) => {
$(#[$docs])*
pub fn $name(&mut self) -> $Helper<'_, $Enum> {
$Helper { features: &mut self.0, _t: PhantomData }
}
};
(builder $(#[$docs:meta])* fn $name:ident();) => {
$(#[$docs])*
pub fn $name(mut self, state: impl Into<FontFeatureState>) -> Self {
self.0.$name().set(state);
self
}
};
(builder $(#[$docs:meta])* fn $name:ident($Enum:ident -> $Helper:ident);) => {
$(#[$docs])*
pub fn $name(mut self, state: impl Into<$Enum>) -> Self {
self.0.$name().set(state);
self
}
};
}
#[rustfmt::skip]font_features! {
fn caps(CapsVariant) -> FontFeatureExclusiveSets;
fn kerning(b"kern");
fn common_lig(b"liga", b"clig");
fn discretionary_lig(b"dlig");
fn historical_lig(b"hlig");
fn contextual_alt(b"calt");
fn ordinal(b"ordn");
fn slashed_zero(b"zero");
fn swash(b"swsh", b"cswh");
fn stylistic(b"salt");
fn historical_forms(b"hist");
fn ornaments(b"ornm");
fn annotation(b"nalt");
fn numeric(NumVariant) -> FontFeatureExclusiveSet;
fn num_spacing(NumSpacing) -> FontFeatureExclusiveSet;
fn num_fraction(NumFraction) -> FontFeatureExclusiveSet;
fn style_set(FontStyleSet) -> FontFeatureExclusiveSet;
fn char_variant(CharVariant) -> FontFeatureExclusiveSet;
fn position(FontPosition) -> FontFeatureExclusiveSet;
fn ruby(b"ruby");
fn jp_variant(JpVariant) -> FontFeatureExclusiveSet;
fn horizontal_kana(b"hkna");
fn cn_variant(CnVariant) -> FontFeatureExclusiveSet;
fn ea_width(EastAsianWidth) -> FontFeatureExclusiveSet;
}
pub struct FontFeature<'a>(hash_map::Entry<'a, FontFeatureName, u32>);
impl FontFeature<'_> {
pub fn name(&self) -> FontFeatureName {
*self.0.key()
}
pub fn state(&self) -> FontFeatureState {
match &self.0 {
hash_map::Entry::Occupied(e) => FontFeatureState(Some(*e.get())),
hash_map::Entry::Vacant(_) => FontFeatureState::auto(),
}
}
pub fn is_enabled(&self) -> bool {
self.state().is_enabled()
}
pub fn is_disabled(&self) -> bool {
self.state().is_disabled()
}
pub fn is_auto(&self) -> bool {
self.state().is_auto()
}
pub fn set(self, state: impl Into<FontFeatureState>) -> FontFeatureState {
let prev = self.state();
match state.into().0 {
Some(n) => self.set_explicit(n),
None => self.auto(),
}
prev
}
fn set_explicit(self, state: u32) {
match self.0 {
hash_map::Entry::Occupied(mut e) => {
e.insert(state);
}
hash_map::Entry::Vacant(e) => {
e.insert(state);
}
}
}
pub fn enable(self) {
self.set_explicit(FEATURE_ENABLED);
}
pub fn enable_alt(self, alt: NonZeroU32) {
self.set_explicit(alt.get())
}
pub fn disable(self) {
self.set_explicit(FEATURE_DISABLED);
}
pub fn auto(self) {
if let hash_map::Entry::Occupied(e) = self.0 {
e.remove();
}
}
}
impl fmt::Debug for FontFeature<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "b\"{}\": {:?}", self.name(), self.state())
}
}
pub struct FontFeatureSet<'a> {
features: &'a mut FontFeaturesMap,
names: &'static [FontFeatureName],
}
impl FontFeatureSet<'_> {
pub fn names(&self) -> &'static [FontFeatureName] {
self.names
}
pub fn state(&self) -> FontFeatureState {
if let Some(&a) = self.features.get(&self.names[0]) {
for name in &self.names[1..] {
if self.features.get(name) != Some(&a) {
return FontFeatureState::auto();
}
}
FontFeatureState(Some(a))
} else {
FontFeatureState::auto()
}
}
pub fn is_enabled(&self) -> bool {
self.state().is_enabled()
}
pub fn is_disabled(&self) -> bool {
self.state().is_disabled()
}
pub fn is_auto(&self) -> bool {
self.state().is_auto()
}
pub fn set(self, state: impl Into<FontFeatureState>) -> FontFeatureState {
let prev = self.state();
match state.into().0 {
Some(n) => self.set_explicit(n),
None => self.auto(),
}
prev
}
fn set_explicit(self, state: u32) {
for name in self.names {
self.features.insert(*name, state);
}
}
pub fn enable(self) {
self.set_explicit(FEATURE_ENABLED);
}
pub fn disable(self) {
self.set_explicit(FEATURE_DISABLED);
}
pub fn auto(self) {
for name in self.names {
self.features.remove(name);
}
}
}
impl fmt::Debug for FontFeatureSet<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}: {:?}", self.names, self.state())
}
}
pub struct FontFeatureExclusiveSet<'a, S: FontFeatureExclusiveSetState> {
features: &'a mut FontFeaturesMap,
_t: PhantomData<S>,
}
impl<S: FontFeatureExclusiveSetState> FontFeatureExclusiveSet<'_, S> {
pub fn names(&self) -> &'static [FontFeatureName] {
S::names()
}
pub fn state(&self) -> S {
let mut state = 0;
for (i, name) in S::names().iter().enumerate() {
if let Some(&s) = self.features.get(name)
&& s == FEATURE_ENABLED
&& state == 0
{
state = i + 1; continue;
}
return S::auto();
}
S::from_variant(state as u32)
}
fn take_state(&mut self) -> S {
let mut state = 0;
let mut skip = false;
for (i, name) in S::names().iter().enumerate() {
if let Some(s) = self.features.remove(name) {
if skip {
continue;
}
if s == FEATURE_ENABLED && state == 0 {
state = i + 1; continue;
}
}
skip = true;
}
S::from_variant(state as u32)
}
pub fn is_auto(&self) -> bool {
self.state() == S::auto()
}
pub fn set(&mut self, state: impl Into<S>) -> S {
let prev = self.take_state();
if let Some(state) = state.into().variant() {
self.features.insert(self.names()[state as usize - 1], FEATURE_ENABLED);
}
prev
}
}
impl<S: FontFeatureExclusiveSetState + fmt::Debug> fmt::Debug for FontFeatureExclusiveSet<'_, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.state(), f)
}
}
pub struct FontFeatureExclusiveSets<'a, S: FontFeatureExclusiveSetsState> {
features: &'a mut FontFeaturesMap,
_t: PhantomData<S>,
}
impl<S: FontFeatureExclusiveSetsState> FontFeatureExclusiveSets<'_, S> {
pub fn names(&self) -> &'static [&'static [FontFeatureName]] {
S::names()
}
pub fn state(&self) -> S {
let mut active = HashSet::new();
for &names in self.names() {
for name in names {
if let Some(&s) = self.features.get(name) {
if s != FEATURE_ENABLED {
return S::auto();
} else {
active.insert(*name);
}
}
}
}
if !active.is_empty() {
'names: for (i, &names) in self.names().iter().enumerate() {
if names.len() == active.len() {
for name in names {
if !active.contains(name) {
continue 'names;
}
}
return S::from_variant(i as u32 + 1);
}
}
}
S::auto()
}
fn take_state(&mut self) -> S {
let mut active = HashSet::new();
let mut force_auto = false;
for &names in self.names() {
for name in names {
if let Some(s) = self.features.remove(name) {
if force_auto {
continue;
}
if s != FEATURE_ENABLED {
force_auto = true;
} else {
active.insert(name);
}
}
}
}
if !force_auto && !active.is_empty() {
'names: for (i, &names) in self.names().iter().enumerate() {
if names.len() == active.len() {
for name in names {
if !active.contains(name) {
continue 'names;
}
}
return S::from_variant(i as u32 + 1);
}
}
}
S::auto()
}
pub fn is_auto(&self) -> bool {
self.state() == S::auto()
}
pub fn set(&mut self, state: impl Into<S>) -> S {
let prev = self.take_state();
if let Some(state) = state.into().variant() {
for name in self.names()[state as usize - 1] {
self.features.insert(*name, FEATURE_ENABLED);
}
}
prev
}
}
impl<S: FontFeatureExclusiveSetsState + fmt::Debug> fmt::Debug for FontFeatureExclusiveSets<'_, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.state(), f)
}
}
pub trait FontFeatureExclusiveSetState: Copy + PartialEq + 'static {
fn names() -> &'static [FontFeatureName];
fn variant(self) -> Option<u32>;
fn from_variant(v: u32) -> Self;
fn auto() -> Self;
}
pub trait FontFeatureExclusiveSetsState: Copy + PartialEq + 'static {
fn names() -> &'static [&'static [FontFeatureName]];
fn variant(self) -> Option<u32>;
fn from_variant(v: u32) -> Self;
fn auto() -> Self;
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct FontFeatureState(Option<u32>);
impl FontFeatureState {
pub const fn auto() -> Self {
FontFeatureState(None)
}
pub const fn enabled() -> Self {
FontFeatureState(Some(1))
}
pub const fn enabled_alt(alt: NonZeroU32) -> Self {
FontFeatureState(Some(alt.get()))
}
pub const fn disabled() -> Self {
FontFeatureState(Some(0))
}
pub fn is_auto(self) -> bool {
self.0.is_none()
}
pub fn is_enabled(self) -> bool {
if let Some(n) = self.0
&& n >= 1
{
return true;
}
false
}
pub fn is_disabled(self) -> bool {
self == Self::disabled()
}
pub fn alt(self) -> Option<u32> {
if let Some(n) = self.0
&& n >= 1
{
return Some(n);
}
None
}
}
impl fmt::Debug for FontFeatureState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(n) => {
if n == FEATURE_DISABLED {
write!(f, "FontFeatureState::disabled()")
} else if n == FEATURE_ENABLED {
write!(f, "FontFeatureState::enabled()")
} else {
write!(f, "FontFeatureState::enabled_alt({n})")
}
}
None => write!(f, "FontFeatureState::auto()"),
}
}
}
impl_from_and_into_var! {
fn from(enabled: bool) -> FontFeatureState {
if enabled {
FontFeatureState::enabled()
} else {
FontFeatureState::disabled()
}
}
fn from(alt: u32) -> FontFeatureState {
FontFeatureState(Some(alt))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum CapsVariant {
#[default]
Auto,
SmallCaps,
AllSmallCaps,
Petite,
AllPetite,
Unicase,
TitlingCaps,
}
impl fmt::Debug for CapsVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "CapsVariant::")?;
}
match self {
CapsVariant::Auto => write!(f, "Auto"),
CapsVariant::SmallCaps => write!(f, "SmallCaps"),
CapsVariant::AllSmallCaps => write!(f, "AllSmallCaps"),
CapsVariant::Petite => write!(f, "Petite"),
CapsVariant::AllPetite => write!(f, "AllPetite"),
CapsVariant::Unicase => write!(f, "Unicase"),
CapsVariant::TitlingCaps => write!(f, "TitlingCaps"),
}
}
}
impl Default for CapsVariant {
fn default() -> Self {
CapsVariant::Auto
}
}
impl FontFeatureExclusiveSetsState for CapsVariant {
fn names() -> &'static [&'static [FontFeatureName]] {
static N0: [FontFeatureName; 1] = [FontFeatureName(*b"smcp")];
static N1: [FontFeatureName; 2] = [FontFeatureName(*b"c2sc"), FontFeatureName(*b"smcp")];
static N2: [FontFeatureName; 1] = [FontFeatureName(*b"pcap")];
static N3: [FontFeatureName; 2] = [FontFeatureName(*b"c2pc"), FontFeatureName(*b"pcap")];
static N4: [FontFeatureName; 1] = [FontFeatureName(*b"unic")];
static N5: [FontFeatureName; 1] = [FontFeatureName(*b"titl")];
static NAMES: [&[FontFeatureName]; 6] = [&N0, &N1, &N2, &N3, &N4, &N5];
&NAMES
}
fn variant(self) -> Option<u32> {
if self == CapsVariant::Auto { None } else { Some(self as u32) }
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
CapsVariant::Auto
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum NumVariant {
#[default]
Auto,
Lining,
OldStyle,
}
impl fmt::Debug for NumVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "NumVariant::")?;
}
match self {
NumVariant::Auto => write!(f, "Auto"),
NumVariant::Lining => write!(f, "Lining"),
NumVariant::OldStyle => write!(f, "OldStyle"),
}
}
}
impl Default for NumVariant {
fn default() -> Self {
NumVariant::Auto
}
}
impl FontFeatureExclusiveSetState for NumVariant {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 2] = [FontFeatureName(*b"lnum"), FontFeatureName(*b"onum")];
&NAMES
}
fn variant(self) -> Option<u32> {
match self {
NumVariant::Auto => None,
NumVariant::Lining => Some(1),
NumVariant::OldStyle => Some(2),
}
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
NumVariant::Auto
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum NumSpacing {
#[default]
Auto,
Proportional,
Tabular,
}
impl fmt::Debug for NumSpacing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "NumSpacing::")?;
}
match self {
NumSpacing::Auto => write!(f, "Auto"),
NumSpacing::Proportional => write!(f, "Proportional"),
NumSpacing::Tabular => write!(f, "Tabular"),
}
}
}
impl Default for NumSpacing {
fn default() -> Self {
NumSpacing::Auto
}
}
impl FontFeatureExclusiveSetState for NumSpacing {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 2] = [FontFeatureName(*b"pnum"), FontFeatureName(*b"tnum")];
&NAMES
}
fn variant(self) -> Option<u32> {
match self {
NumSpacing::Auto => None,
NumSpacing::Proportional => Some(1),
NumSpacing::Tabular => Some(2),
}
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
NumSpacing::Auto
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum NumFraction {
#[default]
Auto,
Diagonal,
Stacked,
}
impl fmt::Debug for NumFraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "NumFraction::")?;
}
match self {
NumFraction::Auto => write!(f, "Auto"),
NumFraction::Diagonal => write!(f, "Diagonal"),
NumFraction::Stacked => write!(f, "Stacked"),
}
}
}
impl Default for NumFraction {
fn default() -> Self {
NumFraction::Auto
}
}
impl FontFeatureExclusiveSetState for NumFraction {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 2] = [FontFeatureName(*b"frac"), FontFeatureName(*b"afrc")];
&NAMES
}
fn variant(self) -> Option<u32> {
match self {
NumFraction::Auto => None,
NumFraction::Diagonal => Some(1),
NumFraction::Stacked => Some(2),
}
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
NumFraction::Auto
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontStyleSet {
#[default]
Auto = 0,
S01,
S02,
S03,
S04,
S05,
S06,
S07,
S08,
S09,
S10,
S11,
S12,
S13,
S14,
S15,
S16,
S17,
S18,
S19,
S20,
}
impl Default for FontStyleSet {
fn default() -> Self {
FontStyleSet::Auto
}
}
impl fmt::Debug for FontStyleSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "FontStyleSet::")?;
}
let n = *self as u8;
if n == 0 { write!(f, "Auto") } else { write!(f, "S{n:0<2}") }
}
}
impl_from_and_into_var! {
fn from(set: u8) -> FontStyleSet;
}
impl FontFeatureExclusiveSetState for FontStyleSet {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 20] = [
FontFeatureName(*b"ss01"),
FontFeatureName(*b"ss02"),
FontFeatureName(*b"ss03"),
FontFeatureName(*b"ss04"),
FontFeatureName(*b"ss05"),
FontFeatureName(*b"ss06"),
FontFeatureName(*b"ss07"),
FontFeatureName(*b"ss08"),
FontFeatureName(*b"ss09"),
FontFeatureName(*b"ss10"),
FontFeatureName(*b"ss11"),
FontFeatureName(*b"ss12"),
FontFeatureName(*b"ss13"),
FontFeatureName(*b"ss14"),
FontFeatureName(*b"ss15"),
FontFeatureName(*b"ss16"),
FontFeatureName(*b"ss17"),
FontFeatureName(*b"ss18"),
FontFeatureName(*b"ss19"),
FontFeatureName(*b"ss20"),
];
&NAMES
}
fn variant(self) -> Option<u32> {
if self == FontStyleSet::Auto { None } else { Some(self as u32) }
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
FontStyleSet::Auto
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct CharVariant(u8);
impl CharVariant {
pub const fn new(v: u8) -> Self {
if v > 99 { CharVariant(0) } else { CharVariant(v) }
}
pub const fn auto() -> Self {
CharVariant(0)
}
pub const fn is_auto(self) -> bool {
self.0 == 0
}
}
impl_from_and_into_var! {
fn from(v: u8) -> CharVariant {
CharVariant::new(v)
}
}
impl FontFeatureExclusiveSetState for CharVariant {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 100] = [
FontFeatureName(*b"cv01"),
FontFeatureName(*b"cv02"),
FontFeatureName(*b"cv03"),
FontFeatureName(*b"cv04"),
FontFeatureName(*b"cv05"),
FontFeatureName(*b"cv06"),
FontFeatureName(*b"cv07"),
FontFeatureName(*b"cv08"),
FontFeatureName(*b"cv09"),
FontFeatureName(*b"cv20"),
FontFeatureName(*b"cv21"),
FontFeatureName(*b"cv22"),
FontFeatureName(*b"cv23"),
FontFeatureName(*b"cv24"),
FontFeatureName(*b"cv25"),
FontFeatureName(*b"cv26"),
FontFeatureName(*b"cv27"),
FontFeatureName(*b"cv28"),
FontFeatureName(*b"cv29"),
FontFeatureName(*b"cv30"),
FontFeatureName(*b"cv31"),
FontFeatureName(*b"cv32"),
FontFeatureName(*b"cv33"),
FontFeatureName(*b"cv34"),
FontFeatureName(*b"cv35"),
FontFeatureName(*b"cv36"),
FontFeatureName(*b"cv37"),
FontFeatureName(*b"cv38"),
FontFeatureName(*b"cv39"),
FontFeatureName(*b"cv40"),
FontFeatureName(*b"cv41"),
FontFeatureName(*b"cv42"),
FontFeatureName(*b"cv43"),
FontFeatureName(*b"cv44"),
FontFeatureName(*b"cv45"),
FontFeatureName(*b"cv46"),
FontFeatureName(*b"cv47"),
FontFeatureName(*b"cv48"),
FontFeatureName(*b"cv49"),
FontFeatureName(*b"cv50"),
FontFeatureName(*b"cv51"),
FontFeatureName(*b"cv52"),
FontFeatureName(*b"cv53"),
FontFeatureName(*b"cv54"),
FontFeatureName(*b"cv55"),
FontFeatureName(*b"cv56"),
FontFeatureName(*b"cv57"),
FontFeatureName(*b"cv58"),
FontFeatureName(*b"cv59"),
FontFeatureName(*b"cv60"),
FontFeatureName(*b"cv61"),
FontFeatureName(*b"cv62"),
FontFeatureName(*b"cv63"),
FontFeatureName(*b"cv64"),
FontFeatureName(*b"cv65"),
FontFeatureName(*b"cv66"),
FontFeatureName(*b"cv67"),
FontFeatureName(*b"cv68"),
FontFeatureName(*b"cv69"),
FontFeatureName(*b"cv70"),
FontFeatureName(*b"cv71"),
FontFeatureName(*b"cv72"),
FontFeatureName(*b"cv73"),
FontFeatureName(*b"cv74"),
FontFeatureName(*b"cv75"),
FontFeatureName(*b"cv76"),
FontFeatureName(*b"cv77"),
FontFeatureName(*b"cv78"),
FontFeatureName(*b"cv79"),
FontFeatureName(*b"cv70"),
FontFeatureName(*b"cv71"),
FontFeatureName(*b"cv72"),
FontFeatureName(*b"cv73"),
FontFeatureName(*b"cv74"),
FontFeatureName(*b"cv75"),
FontFeatureName(*b"cv76"),
FontFeatureName(*b"cv77"),
FontFeatureName(*b"cv78"),
FontFeatureName(*b"cv79"),
FontFeatureName(*b"cv80"),
FontFeatureName(*b"cv81"),
FontFeatureName(*b"cv82"),
FontFeatureName(*b"cv83"),
FontFeatureName(*b"cv84"),
FontFeatureName(*b"cv85"),
FontFeatureName(*b"cv86"),
FontFeatureName(*b"cv87"),
FontFeatureName(*b"cv88"),
FontFeatureName(*b"cv89"),
FontFeatureName(*b"cv90"),
FontFeatureName(*b"cv91"),
FontFeatureName(*b"cv92"),
FontFeatureName(*b"cv93"),
FontFeatureName(*b"cv94"),
FontFeatureName(*b"cv95"),
FontFeatureName(*b"cv96"),
FontFeatureName(*b"cv97"),
FontFeatureName(*b"cv98"),
FontFeatureName(*b"cv99"),
FontFeatureName(*b"cv99"),
];
&NAMES
}
fn variant(self) -> Option<u32> {
if self.is_auto() { None } else { Some(self.0 as u32) }
}
fn from_variant(v: u32) -> Self {
if v > 99 { CharVariant::auto() } else { CharVariant(v as u8) }
}
fn auto() -> Self {
CharVariant::auto()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum FontPosition {
#[default]
Auto,
Sub,
Super,
}
impl fmt::Debug for FontPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "FontPosition::")?;
}
match self {
FontPosition::Auto => write!(f, "Auto"),
FontPosition::Sub => write!(f, "Sub"),
FontPosition::Super => write!(f, "Super"),
}
}
}
impl Default for FontPosition {
fn default() -> Self {
FontPosition::Auto
}
}
impl FontFeatureExclusiveSetState for FontPosition {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 2] = [FontFeatureName(*b"subs"), FontFeatureName(*b"sups")];
&NAMES
}
fn variant(self) -> Option<u32> {
match self {
FontPosition::Auto => None,
FontPosition::Sub => Some(1),
FontPosition::Super => Some(2),
}
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
FontPosition::Auto
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum JpVariant {
#[default]
Auto,
Jis78,
Jis83,
Jis90,
Jis04,
NlcKanji,
}
impl fmt::Debug for JpVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "JpVariant::")?;
}
match self {
JpVariant::Auto => write!(f, "Auto"),
JpVariant::Jis78 => write!(f, "Jis78"),
JpVariant::Jis83 => write!(f, "Jis83"),
JpVariant::Jis90 => write!(f, "Jis90"),
JpVariant::Jis04 => write!(f, "Jis04"),
JpVariant::NlcKanji => write!(f, "NlcKanji"),
}
}
}
impl Default for JpVariant {
fn default() -> Self {
JpVariant::Auto
}
}
impl FontFeatureExclusiveSetState for JpVariant {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 5] = [
FontFeatureName(*b"jp78"),
FontFeatureName(*b"jp83"),
FontFeatureName(*b"jp90"),
FontFeatureName(*b"jp04"),
FontFeatureName(*b"nlck"),
];
&NAMES
}
fn variant(self) -> Option<u32> {
if self == JpVariant::Auto { None } else { Some(self as u32) }
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
JpVariant::Auto
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum CnVariant {
#[default]
Auto,
Simplified,
Traditional,
}
impl fmt::Debug for CnVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "CnVariant")?;
}
match self {
CnVariant::Auto => write!(f, "Auto"),
CnVariant::Simplified => write!(f, "Simplified"),
CnVariant::Traditional => write!(f, "Traditional"),
}
}
}
impl Default for CnVariant {
fn default() -> Self {
CnVariant::Auto
}
}
impl FontFeatureExclusiveSetState for CnVariant {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 2] = [FontFeatureName(*b"smpl"), FontFeatureName(*b"trad")];
&NAMES
}
fn variant(self) -> Option<u32> {
match self {
CnVariant::Auto => None,
CnVariant::Simplified => Some(1),
CnVariant::Traditional => Some(2),
}
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
CnVariant::Auto
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)]
#[repr(u8)]
pub enum EastAsianWidth {
#[default]
Auto,
Proportional,
ProportionalAlt,
ProportionalKana,
Full,
Half,
HalfAlt,
Third,
Quarter,
}
impl fmt::Debug for EastAsianWidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "EastAsianWidth::")?;
}
match self {
EastAsianWidth::Auto => write!(f, "Auto"),
EastAsianWidth::Proportional => write!(f, "Proportional"),
EastAsianWidth::ProportionalAlt => write!(f, "ProportionalAlt"),
EastAsianWidth::ProportionalKana => write!(f, "ProportionalKana"),
EastAsianWidth::Full => write!(f, "Full"),
EastAsianWidth::Half => write!(f, "Half"),
EastAsianWidth::HalfAlt => write!(f, "HalfAlt"),
EastAsianWidth::Third => write!(f, "Third"),
EastAsianWidth::Quarter => write!(f, "Quarter"),
}
}
}
impl Default for EastAsianWidth {
fn default() -> Self {
EastAsianWidth::Auto
}
}
impl FontFeatureExclusiveSetState for EastAsianWidth {
fn names() -> &'static [FontFeatureName] {
static NAMES: [FontFeatureName; 8] = [
FontFeatureName(*b"pwid"),
FontFeatureName(*b"palt"),
FontFeatureName(*b"pkna"),
FontFeatureName(*b"fwid"),
FontFeatureName(*b"hwid"),
FontFeatureName(*b"halt"),
FontFeatureName(*b"twid"),
FontFeatureName(*b"qwid"),
];
&NAMES
}
fn variant(self) -> Option<u32> {
if self == EastAsianWidth::Auto { None } else { Some(self as u32) }
}
fn from_variant(v: u32) -> Self {
Self::from(v as u8)
}
fn auto() -> Self {
EastAsianWidth::Auto
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FontVariationName(pub [u8; 4]);
impl FontVariationName {
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or_default()
}
}
impl From<&'static [u8; 4]> for FontVariationName {
fn from(name: &'static [u8; 4]) -> Self {
FontVariationName(*name)
}
}
impl From<FontVariationName> for skrifa::Tag {
fn from(value: FontVariationName) -> Self {
skrifa::Tag::new(&value.0)
}
}
impl ops::Deref for FontVariationName {
type Target = [u8; 4];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Debug for FontVariationName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.as_str().is_empty() {
write!(f, "{:?}", self.0)
} else {
write!(f, "{}", self.as_str())
}
}
}
impl fmt::Display for FontVariationName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Default, Clone, PartialEq)]
pub struct FontVariations(Vec<(FontVariationName, f32)>);
impl FontVariations {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self(Vec::with_capacity(capacity))
}
pub fn from_pairs(pairs: &[(FontVariationName, f32)]) -> Self {
let mut r = Self::with_capacity(pairs.len());
for (name, value) in pairs {
r.insert(*name, *value);
}
r
}
pub fn insert(&mut self, name: FontVariationName, value: f32) -> Option<f32> {
if let Some(entry) = self.0.iter_mut().find(|v| v.0 == name) {
let prev = Some(entry.1);
entry.1 = value;
prev
} else {
self.0.push((name, value));
None
}
}
pub fn remove(&mut self, name: FontVariationName) -> Option<f32> {
if let Some(i) = self.0.iter().position(|v| v.0 == name) {
Some(self.0.swap_remove(i).1)
} else {
None
}
}
pub fn contains(&self, name: FontVariationName) -> bool {
self.0.iter().any(|v| v.0 == name)
}
pub fn get(&self, name: FontVariationName) -> Option<f32> {
self.0.iter().find(|v| v.0 == name).map(|v| v.1)
}
pub fn get_mut(&mut self, name: FontVariationName) -> Option<&mut f32> {
self.0.iter_mut().find(|v| v.0 == name).map(|v| &mut v.1)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn finalize(&self) -> RFontVariations {
self.0
.iter()
.map(|(name, value)| harfrust::Variation {
tag: (*name).into(),
value: *value,
})
.collect()
}
}
impl fmt::Debug for FontVariations {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_tuple("FontVariations").field(&self.0).finish()
} else {
write!(f, "[")?;
let mut first = false;
for entry in &self.0 {
if first {
first = false;
} else {
write!(f, ", ")?;
}
write!(f, r#", b"{}": {}"#, entry.0, entry.1)?;
}
write!(f, "]")
}
}
}
#[macro_export]
macro_rules! font_variations {
[$(
$name:tt => $value: expr
),* $(,)?] => {
$crate::font_features::FontVariations::from_pairs(&[
$(
($name.into(), $value),
)*
])
}
}
#[doc(inline)]
pub use font_variations;
use zng_var::impl_from_and_into_var;
pub type RFontVariations = Vec<harfrust::Variation>;