use std::fmt::{self, Debug, Formatter};
use ecow::EcoString;
use serde::{Deserialize, Serialize};
use crate::foundations::{cast, Cast, IntoValue, Repr};
use crate::layout::Ratio;
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
pub struct FontVariant {
pub style: FontStyle,
pub weight: FontWeight,
pub stretch: FontStretch,
}
impl FontVariant {
pub fn new(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Self {
Self { style, weight, stretch }
}
}
impl Debug for FontVariant {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}-{:?}-{:?}", self.style, self.weight, self.stretch)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize, Cast)]
#[serde(rename_all = "kebab-case")]
pub enum FontStyle {
Normal,
Italic,
Oblique,
}
impl FontStyle {
pub fn distance(self, other: Self) -> u16 {
if self == other {
0
} else if self != Self::Normal && other != Self::Normal {
1
} else {
2
}
}
}
impl Default for FontStyle {
fn default() -> Self {
Self::Normal
}
}
impl From<usvg::FontStyle> for FontStyle {
fn from(style: usvg::FontStyle) -> Self {
match style {
usvg::FontStyle::Normal => Self::Normal,
usvg::FontStyle::Italic => Self::Italic,
usvg::FontStyle::Oblique => Self::Oblique,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct FontWeight(pub(super) u16);
impl FontWeight {
pub const THIN: Self = Self(100);
pub const EXTRALIGHT: Self = Self(200);
pub const LIGHT: Self = Self(300);
pub const REGULAR: Self = Self(400);
pub const MEDIUM: Self = Self(500);
pub const SEMIBOLD: Self = Self(600);
pub const BOLD: Self = Self(700);
pub const EXTRABOLD: Self = Self(800);
pub const BLACK: Self = Self(900);
pub fn from_number(weight: u16) -> Self {
Self(weight.clamp(100, 900))
}
pub fn to_number(self) -> u16 {
self.0
}
pub fn thicken(self, delta: i16) -> Self {
Self((self.0 as i16).saturating_add(delta).clamp(100, 900) as u16)
}
pub fn distance(self, other: Self) -> u16 {
(self.0 as i16 - other.0 as i16).unsigned_abs()
}
}
impl Default for FontWeight {
fn default() -> Self {
Self::REGULAR
}
}
impl Debug for FontWeight {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<fontdb::Weight> for FontWeight {
fn from(weight: fontdb::Weight) -> Self {
Self::from_number(weight.0)
}
}
cast! {
FontWeight,
self => IntoValue::into_value(match self {
FontWeight::THIN => "thin",
FontWeight::EXTRALIGHT => "extralight",
FontWeight::LIGHT => "light",
FontWeight::REGULAR => "regular",
FontWeight::MEDIUM => "medium",
FontWeight::SEMIBOLD => "semibold",
FontWeight::BOLD => "bold",
FontWeight::EXTRABOLD => "extrabold",
FontWeight::BLACK => "black",
_ => return self.to_number().into_value(),
}),
v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
"thin" => Self::THIN,
"extralight" => Self::EXTRALIGHT,
"light" => Self::LIGHT,
"regular" => Self::REGULAR,
"medium" => Self::MEDIUM,
"semibold" => Self::SEMIBOLD,
"bold" => Self::BOLD,
"extrabold" => Self::EXTRABOLD,
"black" => Self::BLACK,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct FontStretch(pub(super) u16);
impl FontStretch {
pub const ULTRA_CONDENSED: Self = Self(500);
pub const EXTRA_CONDENSED: Self = Self(625);
pub const CONDENSED: Self = Self(750);
pub const SEMI_CONDENSED: Self = Self(875);
pub const NORMAL: Self = Self(1000);
pub const SEMI_EXPANDED: Self = Self(1125);
pub const EXPANDED: Self = Self(1250);
pub const EXTRA_EXPANDED: Self = Self(1500);
pub const ULTRA_EXPANDED: Self = Self(2000);
pub fn from_ratio(ratio: Ratio) -> Self {
Self((ratio.get().clamp(0.5, 2.0) * 1000.0) as u16)
}
pub fn from_number(stretch: u16) -> Self {
match stretch {
0 | 1 => Self::ULTRA_CONDENSED,
2 => Self::EXTRA_CONDENSED,
3 => Self::CONDENSED,
4 => Self::SEMI_CONDENSED,
5 => Self::NORMAL,
6 => Self::SEMI_EXPANDED,
7 => Self::EXPANDED,
8 => Self::EXTRA_EXPANDED,
_ => Self::ULTRA_EXPANDED,
}
}
pub fn to_ratio(self) -> Ratio {
Ratio::new(self.0 as f64 / 1000.0)
}
pub fn round(self) -> Self {
match self.0 {
..=562 => Self::ULTRA_CONDENSED,
563..=687 => Self::EXTRA_CONDENSED,
688..=812 => Self::CONDENSED,
813..=937 => Self::SEMI_CONDENSED,
938..=1062 => Self::NORMAL,
1063..=1187 => Self::SEMI_EXPANDED,
1188..=1374 => Self::EXPANDED,
1375..=1749 => Self::EXTRA_EXPANDED,
1750.. => Self::ULTRA_EXPANDED,
}
}
pub fn distance(self, other: Self) -> Ratio {
(self.to_ratio() - other.to_ratio()).abs()
}
}
impl Default for FontStretch {
fn default() -> Self {
Self::NORMAL
}
}
impl Repr for FontStretch {
fn repr(&self) -> EcoString {
self.to_ratio().repr()
}
}
impl From<usvg::FontStretch> for FontStretch {
fn from(stretch: usvg::FontStretch) -> Self {
match stretch {
usvg::FontStretch::UltraCondensed => Self::ULTRA_CONDENSED,
usvg::FontStretch::ExtraCondensed => Self::EXTRA_CONDENSED,
usvg::FontStretch::Condensed => Self::CONDENSED,
usvg::FontStretch::SemiCondensed => Self::SEMI_CONDENSED,
usvg::FontStretch::Normal => Self::NORMAL,
usvg::FontStretch::SemiExpanded => Self::SEMI_EXPANDED,
usvg::FontStretch::Expanded => Self::EXPANDED,
usvg::FontStretch::ExtraExpanded => Self::EXTRA_EXPANDED,
usvg::FontStretch::UltraExpanded => Self::ULTRA_EXPANDED,
}
}
}
cast! {
FontStretch,
self => self.to_ratio().into_value(),
v: Ratio => Self::from_ratio(v),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_weight_distance() {
let d = |a, b| FontWeight(a).distance(FontWeight(b));
assert_eq!(d(500, 200), 300);
assert_eq!(d(500, 500), 0);
assert_eq!(d(500, 900), 400);
assert_eq!(d(10, 100), 90);
}
#[test]
fn test_font_stretch_debug() {
assert_eq!(FontStretch::EXPANDED.repr(), "125%")
}
}