use std::fmt;
use cssparser::{Parser, Token, match_ignore_ascii_case};
use crate::{
layout::style::{
Animatable, Color, CssSyntaxKind, CssToken, FromCss, LengthDefaultsToZero, MakeComputed,
ParseResult, ToCss, unexpected_token,
},
rendering::Sizing,
};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub struct TextIndent {
pub amount: LengthDefaultsToZero,
pub each_line: bool,
pub hanging: bool,
}
impl TextIndent {
pub const fn new(amount: LengthDefaultsToZero) -> Self {
Self {
amount,
each_line: false,
hanging: false,
}
}
pub const fn with_each_line(mut self, each_line: bool) -> Self {
self.each_line = each_line;
self
}
pub const fn with_hanging(mut self, hanging: bool) -> Self {
self.hanging = hanging;
self
}
pub(crate) fn resolve_px(self, sizing: &Sizing, line_width: f32) -> f32 {
self.amount.to_px(sizing, line_width)
}
}
impl MakeComputed for TextIndent {}
impl Animatable for TextIndent {
fn interpolate(
&mut self,
from: &Self,
to: &Self,
progress: f32,
sizing: &Sizing,
current_color: Color,
) {
self
.amount
.interpolate(&from.amount, &to.amount, progress, sizing, current_color);
self.each_line = if progress >= 0.5 {
to.each_line
} else {
from.each_line
};
self.hanging = if progress >= 0.5 {
to.hanging
} else {
from.hanging
};
}
}
impl<'i> FromCss<'i> for TextIndent {
fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
let mut amount = None;
let mut each_line = false;
let mut hanging = false;
while !input.is_exhausted() {
if amount.is_none()
&& let Ok(length) = input.try_parse(LengthDefaultsToZero::from_css)
{
amount = Some(length);
continue;
}
let location = input.current_source_location();
match input.next()? {
Token::Ident(keyword) => match_ignore_ascii_case! {keyword.as_ref(),
"each-line" if !each_line => each_line = true,
"hanging" if !hanging => hanging = true,
_ => return Err(unexpected_token!(location, &Token::Ident(keyword.clone()))),
},
token => return Err(unexpected_token!(location, token)),
}
}
Ok(Self {
amount: amount.unwrap_or_default(),
each_line,
hanging,
})
}
const VALID_TOKENS: &'static [CssToken] = &[
CssToken::Syntax(CssSyntaxKind::Length),
CssToken::Keyword("each-line"),
CssToken::Keyword("hanging"),
];
}
impl ToCss for TextIndent {
fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
self.amount.to_css(dest)?;
if self.each_line {
dest.write_str(" each-line")?;
}
if self.hanging {
dest.write_str(" hanging")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::layout::style::{FromCss, LengthDefaultsToZero, TextIndent};
#[test]
fn parses_indent_keywords_in_any_order() {
assert_eq!(
TextIndent::from_str("hanging 2em each-line"),
Ok(TextIndent {
amount: LengthDefaultsToZero::Em(2.0),
each_line: true,
hanging: true,
})
);
}
#[test]
fn defaults_to_zero_indent() {
assert_eq!(TextIndent::default().amount, LengthDefaultsToZero::Px(0.0));
}
}