From 7b31b2d532d763c7faebb9873947fa3fa4b3026a Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Sat, 5 Aug 2023 20:20:40 +0100 Subject: [PATCH] implement white-space and friends --- crates/hdx_ast/src/css/properties.rs | 6 +- crates/hdx_ast/src/css/values/text.rs | 80 +++++++++++- crates/hdx_parser/src/css/values/mod.rs | 3 + crates/hdx_parser/src/css/values/text.rs | 151 +++++++++++++++++++++++ crates/hdx_writer/src/css/values/mod.rs | 3 + crates/hdx_writer/src/css/values/text.rs | 59 +++++++++ 6 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 crates/hdx_parser/src/css/values/text.rs create mode 100644 crates/hdx_writer/src/css/values/text.rs diff --git a/crates/hdx_ast/src/css/properties.rs b/crates/hdx_ast/src/css/properties.rs index f425d3a3..74f3ecc6 100644 --- a/crates/hdx_ast/src/css/properties.rs +++ b/crates/hdx_ast/src/css/properties.rs @@ -914,9 +914,9 @@ properties! { atom!("text-spacing-trim") => TextSpacingTrim> inherits=true, atom!("text-transform") => TextTransform> inherits=true, atom!("text-wrap") => TextWrap> inherits=true, - atom!("white-space") => WhiteSpace> inherits=true, - atom!("white-space-collapse") => WhiteSpaceCollapse> inherits=true, - atom!("white-space-trim") => WhiteSpaceTrim>, + atom!("white-space") => WhiteSpace> inherits=true, + atom!("white-space-collapse") => WhiteSpaceCollapse> inherits=true, + atom!("white-space-trim") => WhiteSpaceTrim>, atom!("word-boundary-detection") => WordBoundaryDetection> inherits=true, atom!("word-boundary-expansion") => WordBoundaryExpansion> inherits=true, atom!("word-break") => WordBreak> inherits=true, diff --git a/crates/hdx_ast/src/css/values/text.rs b/crates/hdx_ast/src/css/values/text.rs index 22e0128f..8501ba69 100644 --- a/crates/hdx_ast/src/css/values/text.rs +++ b/crates/hdx_ast/src/css/values/text.rs @@ -1,9 +1,10 @@ #[cfg(feature = "serde")] use serde::Serialize; +use super::{Expr, Shorthand}; use crate::{atom, Atom, Atomizable}; -// https://drafts.csswg.org/css-text/#text-align-property +// https://drafts.csswg.org/css-text-4/#propdef-text-align #[derive(Atomizable, Default, Debug, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum TextAlignValue { @@ -16,9 +17,10 @@ pub enum TextAlignValue { Justify, // atom!("justify") MatchParent, // atom!("match-parent") JustifyAll, // atom!("justify-all") + // TODO: Custom? } -// https://drafts.csswg.org/css-text/#text-align-all-property +// https://drafts.csswg.org/css-text-4/#propdef-text-align-all #[derive(Atomizable, Default, Debug, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum TextAlignAllValue { @@ -32,7 +34,7 @@ pub enum TextAlignAllValue { MatchParent, // atom!("match-parent") } -// https://drafts.csswg.org/css-text/#text-align-all-property +// https://drafts.csswg.org/css-text-4/#propdef-text-align-last #[derive(Atomizable, Default, Debug, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde())] pub enum TextAlignLastValue { @@ -46,3 +48,75 @@ pub enum TextAlignLastValue { Justify, // atom!("justify") MatchParent, // atom!("match-parent") } + +// https://drafts.csswg.org/css-text-4/#propdef-text-wrap +#[derive(Atomizable, Default, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum TextWrapValue { + #[default] + Wrap, // atom!("wrap") + Nowrap, // atom!("nowrap") + Balance, // atom!("balance") + Stable, // atom!("stable") + Pretty, // atom!("pretty") +} + +// https://drafts.csswg.org/css-text-4/#propdef-white-space-collapse +#[derive(Atomizable, Default, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum WhiteSpaceCollapseValue { + #[default] + Collapse, // atom!("collapse") + Discard, // atom!("discard") + Preserve, // atom!("preserve") + PreserveBreaks, // atom!("preserve-breaks") + PreserveSpaces, // atom!("preserve-spaces") + BreakSpaces, // atom!("break-spaces") +} + +// https://drafts.csswg.org/css-text-4/#propdef-white-space-trim +#[derive(Default, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum WhiteSpaceTrimValue { + #[default] + None, + Discard { + before: bool, + after: bool, + inner: bool, + }, +} + +// https://drafts.csswg.org/css-text-4/#propdef-white-space +#[derive(Default, Debug, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde())] +pub enum WhiteSpaceShorthand<'a> { + #[default] + Normal, + Pre, + Nowrap, + PreWrap, + PreLine, + Expanded { + collapse: Shorthand<'a, Expr<'a, WhiteSpaceCollapseValue>>, + wrap: Shorthand<'a, Expr<'a, TextWrapValue>>, + trim: Shorthand<'a, Expr<'a, WhiteSpaceTrimValue>>, + }, +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn size_test() { + use std::mem::size_of; + assert_eq!(size_of::(), 1); + assert_eq!(size_of::(), 1); + assert_eq!(size_of::(), 1); + assert_eq!(size_of::(), 3); + assert_eq!(size_of::(), 1); + assert_eq!(size_of::(), 32); + } +} diff --git a/crates/hdx_parser/src/css/values/mod.rs b/crates/hdx_parser/src/css/values/mod.rs index fb4d1d78..44a04244 100644 --- a/crates/hdx_parser/src/css/values/mod.rs +++ b/crates/hdx_parser/src/css/values/mod.rs @@ -15,6 +15,7 @@ pub mod page_floats; pub mod shorthand; pub mod size_adjust; pub mod sizing; +pub mod text; pub mod text_decor; pub mod ui; @@ -63,7 +64,9 @@ parse_for_enums! { TextAlignValue, TextDecorationSkipInkValue, TextDecorationStyleValue, + TextWrapValue, VisibilityValue, + WhiteSpaceCollapseValue, } // TODO: diff --git a/crates/hdx_parser/src/css/values/text.rs b/crates/hdx_parser/src/css/values/text.rs new file mode 100644 index 00000000..e61aeb1b --- /dev/null +++ b/crates/hdx_parser/src/css/values/text.rs @@ -0,0 +1,151 @@ +use hdx_ast::css::values::{ + Expr, Shorthand, TextWrapValue, WhiteSpaceCollapseValue, WhiteSpaceShorthand, + WhiteSpaceTrimValue, +}; +use hdx_lexer::Kind; + +use crate::{atom, diagnostics, Atomizable, Parse, Parser, Result, Spanned}; + +impl<'a> Parse<'a> for WhiteSpaceTrimValue { + fn parse(parser: &mut Parser<'a>) -> Result> { + let span = parser.cur().span; + let mut inner = false; + let mut after = false; + let mut before = false; + loop { + match parser.cur().kind { + Kind::Ident => match parser.cur_atom_lower().unwrap() { + atom!("none") => { + parser.advance(); + return Ok(Self::None.spanned(span)); + } + atom!("discard-inner") => { + parser.advance(); + inner = true; + } + atom!("discard-after") => { + parser.advance(); + after = true; + } + atom!("discard-before") => { + parser.advance(); + before = true; + } + _ => break, + }, + _ => break, + } + if inner && after && before { + break; + } + } + Ok(Self::Discard { inner, after, before }.spanned(span.up_to(&parser.cur().span))) + } +} + +impl<'a> Parse<'a> for WhiteSpaceShorthand<'a> { + fn parse(parser: &mut Parser<'a>) -> Result> { + let span = parser.cur().span; + if parser.at(Kind::Ident) { + match parser.cur_atom_lower().unwrap() { + //normal | pre | nowrap | pre-wrap | pre-line + atom!("normal") => { + parser.advance(); + return Ok(Self::Normal.spanned(span)); + } + atom!("pre") => { + parser.advance(); + return Ok(Self::Pre.spanned(span)); + } + atom!("nowrap") => { + parser.advance(); + return Ok(Self::Nowrap.spanned(span)); + } + atom!("pre-wrap") => { + parser.advance(); + return Ok(Self::PreWrap.spanned(span)); + } + atom!("pre-line") => { + parser.advance(); + return Ok(Self::PreLine.spanned(span)); + } + _ => {} + } + } + let mut collapse = Shorthand::Implicit; + let mut wrap = Shorthand::Implicit; + let mut trim = Shorthand::Implicit; + loop { + match parser.cur().kind { + Kind::Semicolon | Kind::Comma | Kind::Eof => { + break; + } + Kind::Ident => { + let ident = parser.cur_atom_lower().unwrap(); + if collapse.is_implicit() + && WhiteSpaceCollapseValue::from_atom(ident.clone()).is_some() + { + let node = Expr::::parse(parser)?; + collapse = Shorthand::Explicit(parser.boxup(node)); + } else if wrap.is_implicit() + && TextWrapValue::from_atom(ident.clone()).is_some() + { + let node = Expr::::parse(parser)?; + wrap = Shorthand::Explicit(parser.boxup(node)); + } else if trim.is_implicit() + && matches!( + ident, + atom!("none") + | atom!("discard-inner") | atom!("discard-after") + | atom!("discard-before") + ) { + let node = Expr::::parse(parser)?; + trim = Shorthand::Explicit(parser.boxup(node)); + } else { + Err(diagnostics::UnexpectedIdent(ident.clone(), parser.cur().span))? + } + } + k => { + let checkpoint = parser.checkpoint(); + if collapse.is_implicit() { + let node = Expr::::parse(parser); + match node { + Ok(node) => { + collapse = Shorthand::Explicit(parser.boxup(node)); + continue; + } + Err(_) => parser.rewind(checkpoint), + } + } + let checkpoint = parser.checkpoint(); + if wrap.is_implicit() { + let node = Expr::::parse(parser); + match node { + Ok(node) => { + wrap = Shorthand::Explicit(parser.boxup(node)); + continue; + } + Err(_) => parser.rewind(checkpoint), + } + } + let checkpoint = parser.checkpoint(); + if trim.is_implicit() { + let node = Expr::::parse(parser); + match node { + Ok(node) => { + trim = Shorthand::Explicit(parser.boxup(node)); + continue; + } + Err(_) => parser.rewind(checkpoint), + } + } + Err(diagnostics::Unexpected(k, parser.cur().span))? + } + } + if collapse.is_explicit() && wrap.is_explicit() && trim.is_explicit() { + break; + } + } + Ok(Self::Expanded { collapse, wrap, trim }.spanned(span.up_to(&parser.cur().span))) + } +} diff --git a/crates/hdx_writer/src/css/values/mod.rs b/crates/hdx_writer/src/css/values/mod.rs index 0716dcea..0fefdfe5 100644 --- a/crates/hdx_writer/src/css/values/mod.rs +++ b/crates/hdx_writer/src/css/values/mod.rs @@ -15,6 +15,7 @@ mod page_floats; mod shorthand; mod size_adjust; mod sizing; +mod text; mod text_decor; mod ui; @@ -60,7 +61,9 @@ write_atomizable_values! { TextAlignValue, TextDecorationSkipInkValue, TextDecorationStyleValue, + TextWrapValue, VisibilityValue, + WhiteSpaceCollapseValue, } impl<'a> WriteCss<'a> for TimeOrAuto { diff --git a/crates/hdx_writer/src/css/values/text.rs b/crates/hdx_writer/src/css/values/text.rs new file mode 100644 index 00000000..5a19f3f2 --- /dev/null +++ b/crates/hdx_writer/src/css/values/text.rs @@ -0,0 +1,59 @@ +use hdx_ast::css::values::{text::*, Shorthand}; + +use crate::{CssWriter, Result, WriteCss}; + +impl<'a> WriteCss<'a> for WhiteSpaceTrimValue { + fn write_css(&self, sink: &mut W) -> Result { + match self { + Self::None => sink.write_str("none"), + Self::Discard { inner, after, before } => { + if *inner { + sink.write_str("discard-inner")?; + if *before { + sink.write_char(' ')?; + } + } + if *before { + sink.write_str("discard-before")?; + if *after { + sink.write_char(' ')?; + } + } + if *after { + sink.write_str("discard-after")?; + } + Ok(()) + } + } + } +} + +impl<'a> WriteCss<'a> for WhiteSpaceShorthand<'a> { + fn write_css(&self, sink: &mut W) -> Result { + match self { + Self::Normal => sink.write_str("normal")?, + Self::Pre => sink.write_str("pre")?, + Self::Nowrap => sink.write_str("nowrap")?, + Self::PreWrap => sink.write_str("pre-wrap")?, + Self::PreLine => sink.write_str("pre-line")?, + Self::Expanded { collapse, trim, wrap } => { + if let Shorthand::Explicit(value) = collapse { + value.write_css(sink)?; + if trim.is_explicit() { + sink.write_char(' ')?; + } + } + if let Shorthand::Explicit(value) = trim { + value.write_css(sink)?; + if wrap.is_explicit() { + sink.write_char(' ')?; + } + } + if let Shorthand::Explicit(value) = wrap { + value.write_css(sink)?; + } + } + } + Ok(()) + } +}