use std::fmt;
use crate::style::{ToCss, unexpected_token};
use cssparser::{Parser, Token, match_ignore_ascii_case};
use parley::style::FontWeight as ParleyFontWeight;
use crate::style::{
Animatable, Color, CssSyntaxKind, CssToken, FromCss, MakeComputed, ParseResult, SizingContext,
lerp, tw::TailwindPropertyParser,
};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FontWeight {
Absolute(ParleyFontWeight),
Bolder,
Lighter,
}
impl Default for FontWeight {
fn default() -> Self {
FontWeight::from(400.0)
}
}
fn bolder(weight: f32) -> f32 {
if weight < 350.0 {
400.0
} else if weight < 550.0 {
700.0
} else if weight < 900.0 {
900.0
} else {
weight
}
}
fn lighter(weight: f32) -> f32 {
if weight < 100.0 {
weight
} else if weight < 550.0 {
100.0
} else if weight < 750.0 {
400.0
} else {
700.0
}
}
impl MakeComputed for FontWeight {}
impl Animatable for FontWeight {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
_sizing: &SizingContext,
_current_color: Color,
) {
*self = FontWeight::from(lerp(from.value(), to.value(), progress));
}
}
impl<'i> FromCss<'i> for FontWeight {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let location = input.current_source_location();
let token = input.next()?;
match token {
Token::Number { value, .. } => Ok((*value).into()),
Token::Ident(ident) => match_ignore_ascii_case! { ident,
"normal" => Ok(400.0.into()),
"bold" => Ok(700.0.into()),
"bolder" => Ok(FontWeight::Bolder),
"lighter" => Ok(FontWeight::Lighter),
_ => Err(unexpected_token!(location, token)),
},
_ => Err(unexpected_token!(location, token)),
}
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Syntax(CssSyntaxKind::Number),
CssToken::Keyword("normal"),
CssToken::Keyword("bold"),
CssToken::Keyword("bolder"),
CssToken::Keyword("lighter"),
];
}
impl TailwindPropertyParser for FontWeight {
fn parse_tw(token: &str) -> Option<Self> {
if let Ok(value) = token.parse::<f32>() {
return Some(value.into());
}
match_ignore_ascii_case! {&token,
"thin" => Some(100.0.into()),
"extralight" => Some(200.0.into()),
"light" => Some(300.0.into()),
"normal" => Some(400.0.into()),
"medium" => Some(500.0.into()),
"semibold" => Some(600.0.into()),
"bold" => Some(700.0.into()),
"extrabold" => Some(800.0.into()),
"black" => Some(900.0.into()),
_ => None,
}
}
}
impl FontWeight {
pub fn value(self) -> f32 {
match self {
Self::Absolute(weight) => weight.value(),
Self::Bolder => bolder(400.0),
Self::Lighter => lighter(400.0),
}
}
pub fn resolve_against(self, parent: f32) -> Self {
match self {
Self::Bolder => FontWeight::from(bolder(parent)),
Self::Lighter => FontWeight::from(lighter(parent)),
absolute => absolute,
}
}
}
impl From<FontWeight> for ParleyFontWeight {
fn from(value: FontWeight) -> Self {
ParleyFontWeight::new(value.value())
}
}
impl From<f32> for FontWeight {
fn from(value: f32) -> Self {
FontWeight::Absolute(ParleyFontWeight::new(value))
}
}
impl ToCss for FontWeight {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::Bolder => dest.write_str("bolder"),
Self::Lighter => dest.write_str("lighter"),
Self::Absolute(weight) => write!(dest, "{}", weight.value()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_numeric_font_weight() {
assert_eq!(FontWeight::from_str("700"), Ok(700.0.into()));
}
#[test]
fn resolves_relative_keywords_against_parent() {
assert_eq!(FontWeight::from_str("bolder"), Ok(FontWeight::Bolder));
assert_eq!(FontWeight::from_str("lighter"), Ok(FontWeight::Lighter));
assert_eq!(FontWeight::Bolder.resolve_against(400.0).value(), 700.0);
assert_eq!(FontWeight::Bolder.resolve_against(700.0).value(), 900.0);
assert_eq!(FontWeight::Bolder.resolve_against(900.0).value(), 900.0);
assert_eq!(FontWeight::Lighter.resolve_against(400.0).value(), 100.0);
assert_eq!(FontWeight::Lighter.resolve_against(600.0).value(), 400.0);
assert_eq!(FontWeight::Lighter.resolve_against(900.0).value(), 700.0);
}
}