use core::fmt;
use easy_cast::Cast;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct FontWidth(u16);
impl FontWidth {
pub const ULTRA_CONDENSED: Self = Self(128);
pub const EXTRA_CONDENSED: Self = Self(160);
pub const CONDENSED: Self = Self(192);
pub const SEMI_CONDENSED: Self = Self(224);
pub const NORMAL: Self = Self(256);
pub const SEMI_EXPANDED: Self = Self(288);
pub const EXPANDED: Self = Self(320);
pub const EXTRA_EXPANDED: Self = Self(384);
pub const ULTRA_EXPANDED: Self = Self(512);
}
impl FontWidth {
pub fn from_ratio(ratio: f32) -> Self {
let value = (ratio * 256.0).round();
assert!(0.0 <= value && value <= (u16::MAX as f32));
Self(value as u16)
}
pub fn from_percentage(percentage: f32) -> Self {
Self::from_ratio(percentage / 100.0)
}
pub fn ratio(self) -> f32 {
(self.0 as f32) / 256.0
}
pub fn percentage(self) -> f32 {
self.ratio() * 100.0
}
pub fn is_normal(self) -> bool {
self == Self::NORMAL
}
pub fn is_condensed(self) -> bool {
self < Self::NORMAL
}
pub fn is_expanded(self) -> bool {
self > Self::NORMAL
}
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim();
Some(match s {
"ultra-condensed" => Self::ULTRA_CONDENSED,
"extra-condensed" => Self::EXTRA_CONDENSED,
"condensed" => Self::CONDENSED,
"semi-condensed" => Self::SEMI_CONDENSED,
"normal" => Self::NORMAL,
"semi-expanded" => Self::SEMI_EXPANDED,
"expanded" => Self::EXPANDED,
"extra-expanded" => Self::EXTRA_EXPANDED,
"ultra-expanded" => Self::ULTRA_EXPANDED,
_ => {
if s.ends_with('%') {
let p = s.get(..s.len() - 1)?.parse::<f32>().ok()?;
return Some(Self::from_percentage(p));
}
return None;
}
})
}
}
impl fmt::Display for FontWidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let keyword = match *self {
v if v == Self::ULTRA_CONDENSED => "ultra-condensed",
v if v == Self::EXTRA_CONDENSED => "extra-condensed",
v if v == Self::CONDENSED => "condensed",
v if v == Self::SEMI_CONDENSED => "semi-condensed",
v if v == Self::NORMAL => "normal",
v if v == Self::SEMI_EXPANDED => "semi-expanded",
v if v == Self::EXPANDED => "expanded",
v if v == Self::EXTRA_EXPANDED => "extra-expanded",
v if v == Self::ULTRA_EXPANDED => "ultra-expanded",
_ => {
return write!(f, "{}%", self.percentage());
}
};
write!(f, "{keyword}")
}
}
impl Default for FontWidth {
fn default() -> Self {
Self::NORMAL
}
}
impl From<FontWidth> for fontique::FontWidth {
#[inline]
fn from(width: FontWidth) -> Self {
fontique::FontWidth::from_ratio(width.ratio())
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct FontWeight(u16);
impl FontWeight {
pub const THIN: Self = Self(100);
pub const EXTRA_LIGHT: Self = Self(200);
pub const LIGHT: Self = Self(300);
pub const SEMI_LIGHT: Self = Self(350);
pub const NORMAL: Self = Self(400);
pub const MEDIUM: Self = Self(500);
pub const SEMI_BOLD: Self = Self(600);
pub const BOLD: Self = Self(700);
pub const EXTRA_BOLD: Self = Self(800);
pub const BLACK: Self = Self(900);
pub const EXTRA_BLACK: Self = Self(950);
}
impl FontWeight {
pub fn new(weight: u16) -> Self {
Self(weight)
}
pub fn value(self) -> u16 {
self.0
}
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim();
Some(match s {
"normal" => Self::NORMAL,
"bold" => Self::BOLD,
_ => Self(s.parse::<u16>().ok()?),
})
}
}
impl Default for FontWeight {
fn default() -> Self {
Self::NORMAL
}
}
impl fmt::Display for FontWeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let keyword = match self.0 {
400 => "normal",
700 => "bold",
_ => return write!(f, "{}", self.0),
};
write!(f, "{keyword}")
}
}
impl From<FontWeight> for fontique::FontWeight {
#[inline]
fn from(weight: FontWeight) -> Self {
fontique::FontWeight::new(weight.value().cast())
}
}
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Hash)]
pub enum FontStyle {
#[default]
Normal,
Italic,
Oblique(Option<i16>),
}
impl FontStyle {
pub fn parse(mut s: &str) -> Option<Self> {
s = s.trim();
Some(match s {
"normal" => Self::Normal,
"italic" => Self::Italic,
"oblique" => Self::Oblique(None),
_ => {
if s.starts_with("oblique ") {
s = s.get(8..)?;
if s.ends_with("deg") {
s = s.get(..s.len() - 3)?;
if let Ok(degrees) = s.trim().parse::<f32>() {
return Some(Self::from_degrees(degrees));
}
} else if s.ends_with("grad") {
s = s.get(..s.len() - 4)?;
if let Ok(gradians) = s.trim().parse::<f32>() {
return Some(Self::from_degrees(gradians / 400.0 * 360.0));
}
} else if s.ends_with("rad") {
s = s.get(..s.len() - 3)?;
if let Ok(radians) = s.trim().parse::<f32>() {
return Some(Self::from_degrees(radians.to_degrees()));
}
} else if s.ends_with("turn") {
s = s.get(..s.len() - 4)?;
if let Ok(turns) = s.trim().parse::<f32>() {
return Some(Self::from_degrees(turns * 360.0));
}
}
return Some(Self::Oblique(None));
}
return None;
}
})
}
}
impl FontStyle {
pub const fn oblique_degrees(angle: Option<i16>) -> f32 {
if let Some(a) = angle {
(a as f32) / 256.0
} else {
14.0
}
}
pub fn from_degrees(degrees: f32) -> Self {
assert!(-90.0 <= degrees && degrees <= 90.0);
let a = (degrees * 256.0).round();
Self::Oblique(Some(a as i16))
}
}
impl fmt::Display for FontStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match *self {
Self::Normal => "normal",
Self::Italic => "italic",
Self::Oblique(None) => "oblique",
Self::Oblique(Some(angle)) => {
let degrees = (angle as f32) / 256.0;
return write!(f, "oblique {degrees}deg");
}
};
write!(f, "{value}")
}
}
impl From<FontStyle> for fontique::FontStyle {
#[inline]
fn from(style: FontStyle) -> Self {
match style {
FontStyle::Normal => fontique::FontStyle::Normal,
FontStyle::Italic => fontique::FontStyle::Italic,
FontStyle::Oblique(None) => fontique::FontStyle::Oblique(None),
FontStyle::Oblique(slant) => {
fontique::FontStyle::Oblique(Some(FontStyle::oblique_degrees(slant)))
}
}
}
}
#[cfg(feature = "serde")]
mod serde_impls {
use super::*;
use serde::{de, ser};
impl ser::Serialize for FontWidth {
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&format!("{}", self))
}
}
impl<'de> de::Deserialize<'de> for FontWidth {
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontWidth, D::Error> {
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = FontWidth;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "a keyword or integer percentage")
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<FontWidth, E> {
FontWidth::parse(s)
.ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
}
}
de.deserialize_str(Visitor)
}
}
impl ser::Serialize for FontWeight {
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&format!("{}", self))
}
}
impl<'de> de::Deserialize<'de> for FontWeight {
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontWeight, D::Error> {
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = FontWeight;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "a keyword or integer")
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<FontWeight, E> {
FontWeight::parse(s)
.ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
}
}
de.deserialize_str(Visitor)
}
}
impl ser::Serialize for FontStyle {
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&format!("{}", self))
}
}
impl<'de> de::Deserialize<'de> for FontStyle {
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontStyle, D::Error> {
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = FontStyle;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "a keyword or 'oblique' value")
}
fn visit_str<E: de::Error>(self, s: &str) -> Result<FontStyle, E> {
FontStyle::parse(s)
.ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
}
}
de.deserialize_str(Visitor)
}
}
}