use std::fmt;
use crate::layout::style::{ToCss, unexpected_token};
use cssparser::{Parser, Token, match_ignore_ascii_case};
use crate::{
layout::style::{
Animatable, Color, CssSyntaxKind, CssToken, FromCss, Length, MakeComputed, ParseResult,
},
rendering::Sizing,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum FontSizeKeyword {
XXSmall,
XSmall,
Small,
#[default]
Medium,
Large,
XLarge,
XXLarge,
XXXLarge,
}
impl FontSizeKeyword {
pub const fn to_length(self) -> Length {
match self {
Self::XXSmall => Length::Rem(0.6),
Self::XSmall => Length::Rem(0.75),
Self::Small => Length::Rem(8.0 / 9.0),
Self::Medium => Length::Rem(1.0),
Self::Large => Length::Rem(1.2),
Self::XLarge => Length::Rem(1.5),
Self::XXLarge => Length::Rem(2.0),
Self::XXXLarge => Length::Rem(3.0),
}
}
}
impl<'i> FromCss<'i> for FontSizeKeyword {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let location = input.current_source_location();
let token = input.next()?;
match token {
Token::Ident(ident) => match_ignore_ascii_case! { ident,
"xx-small" => Ok(Self::XXSmall),
"x-small" => Ok(Self::XSmall),
"small" => Ok(Self::Small),
"medium" => Ok(Self::Medium),
"large" => Ok(Self::Large),
"x-large" => Ok(Self::XLarge),
"xx-large" => Ok(Self::XXLarge),
"xxx-large" => Ok(Self::XXXLarge),
_ => Err(unexpected_token!(location, token)),
},
_ => Err(unexpected_token!(location, token)),
}
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Keyword("xx-small"),
CssToken::Keyword("x-small"),
CssToken::Keyword("small"),
CssToken::Keyword("medium"),
CssToken::Keyword("large"),
CssToken::Keyword("x-large"),
CssToken::Keyword("xx-large"),
CssToken::Keyword("xxx-large"),
];
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum FontSize {
Keyword(FontSizeKeyword),
Length(Length),
}
impl FontSize {
pub(crate) fn to_px(self, sizing: &Sizing, inherited_font_size: f32) -> f32 {
match self {
Self::Keyword(keyword) => keyword.to_length().to_px(sizing, inherited_font_size),
Self::Length(length) => length.to_px(sizing, inherited_font_size),
}
}
}
impl Default for FontSize {
fn default() -> Self {
Self::Keyword(FontSizeKeyword::Medium)
}
}
impl From<Length> for FontSize {
fn from(value: Length) -> Self {
Self::Length(value)
}
}
impl From<FontSizeKeyword> for FontSize {
fn from(value: FontSizeKeyword) -> Self {
Self::Keyword(value)
}
}
impl<'i> FromCss<'i> for FontSize {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
input
.try_parse(FontSizeKeyword::from_css)
.map(Self::Keyword)
.or_else(|_| Length::from_css(input).map(Self::Length))
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Keyword("xx-small"),
CssToken::Keyword("x-small"),
CssToken::Keyword("small"),
CssToken::Keyword("medium"),
CssToken::Keyword("large"),
CssToken::Keyword("x-large"),
CssToken::Keyword("xx-large"),
CssToken::Keyword("xxx-large"),
CssToken::Syntax(CssSyntaxKind::Length),
];
}
impl MakeComputed for FontSize {
fn make_computed(&mut self, sizing: &Sizing) {
if let Self::Length(length) = self {
length.make_computed(sizing);
}
}
}
impl Animatable for FontSize {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
let from_length = match *from {
Self::Keyword(keyword) => keyword.to_length(),
Self::Length(length) => length,
};
let to_length = match *to {
Self::Keyword(keyword) => keyword.to_length(),
Self::Length(length) => length,
};
let mut value = from_length;
value.interpolate(&from_length, &to_length, progress, sizing, current_color);
*self = Self::Length(value);
}
}
impl ToCss for FontSizeKeyword {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::XXSmall => dest.write_str("xx-small"),
Self::XSmall => dest.write_str("x-small"),
Self::Small => dest.write_str("small"),
Self::Medium => dest.write_str("medium"),
Self::Large => dest.write_str("large"),
Self::XLarge => dest.write_str("x-large"),
Self::XXLarge => dest.write_str("xx-large"),
Self::XXXLarge => dest.write_str("xxx-large"),
}
}
}
impl ToCss for FontSize {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
match self {
Self::Keyword(k) => k.to_css(dest),
Self::Length(l) => l.to_css(dest),
}
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use taffy::Size;
use super::*;
use crate::{
layout::{style::CalcArena, viewport::Viewport},
rendering::Sizing,
};
#[test]
fn defaults_to_medium_keyword() {
assert_eq!(
FontSize::default(),
FontSize::Keyword(FontSizeKeyword::Medium)
);
}
#[test]
fn resolves_medium_keyword_to_default_font_size() {
let sizing = Sizing {
viewport: Viewport::new((1200, 630)),
container_size: Size::NONE,
font_size: 16.0,
root_font_size: None,
line_height: 0.0,
root_line_height: None,
calc_arena: Rc::new(CalcArena::default()),
};
assert_eq!(FontSize::default().to_px(&sizing, sizing.font_size), 16.0);
}
#[test]
fn rem_font_size_in_descendant_does_not_double_apply_dpr() {
use crate::layout::viewport::DEFAULT_FONT_SIZE;
let viewport = Viewport::new((1200, 630)).with_device_pixel_ratio(2.0);
let root_font_size_device_px = DEFAULT_FONT_SIZE * viewport.device_pixel_ratio;
let sizing = Sizing {
viewport,
container_size: Size::NONE,
font_size: root_font_size_device_px,
root_font_size: Some(root_font_size_device_px),
line_height: 0.0,
root_line_height: None,
calc_arena: Rc::new(CalcArena::default()),
};
assert_eq!(
FontSize::Length(Length::Rem(0.5)).to_px(&sizing, sizing.font_size),
16.0
);
assert_eq!(
FontSize::Length(Length::Rem(1.0)).to_px(&sizing, sizing.font_size),
32.0
);
}
}