diff --git a/.gitignore b/.gitignore index e82b6873..3f780399 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist/ .parcel-cache node/*.flow artifacts -npm \ No newline at end of file +npm +.idea \ No newline at end of file diff --git a/selectors/parser.rs b/selectors/parser.rs index 6f64a31f..4d6d2e2c 100644 --- a/selectors/parser.rs +++ b/selectors/parser.rs @@ -1107,7 +1107,7 @@ pub enum Component<'i, Impl: SelectorImpl<'i>> { Scope, NthChild(i32, i32), NthLastChild(i32, i32), - NthCol(i32, i32), // https://www.w3.org/TR/selectors-4/#the-nth-col-pseudo + NthCol(i32, i32), // https://www.w3.org/TR/selectors-4/#the-nth-col-pseudo NthLastCol(i32, i32), // https://www.w3.org/TR/selectors-4/#the-nth-last-col-pseudo NthOfType(i32, i32), NthLastOfType(i32, i32), @@ -1606,7 +1606,12 @@ impl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> { FirstOfType => dest.write_str(":first-of-type"), LastOfType => dest.write_str(":last-of-type"), OnlyOfType => dest.write_str(":only-of-type"), - NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) | NthCol(a, b) | NthLastCol(a, b) => { + NthChild(a, b) + | NthLastChild(a, b) + | NthOfType(a, b) + | NthLastOfType(a, b) + | NthCol(a, b) + | NthLastCol(a, b) => { match *self { NthChild(_, _) => dest.write_str(":nth-child(")?, NthLastChild(_, _) => dest.write_str(":nth-last-child(")?, diff --git a/src/declaration.rs b/src/declaration.rs index 4f66a12e..bff35177 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -14,6 +14,7 @@ use crate::properties::{ animation::AnimationHandler, background::BackgroundHandler, border::BorderHandler, + columns::ColumnsHandler, contain::ContainerHandler, display::DisplayHandler, flex::FlexHandler, @@ -463,6 +464,7 @@ pub(crate) struct DeclarationHandler<'i> { box_shadow: BoxShadowHandler, mask: MaskHandler<'i>, container: ContainerHandler<'i>, + columns: ColumnsHandler, fallback: FallbackHandler, prefix: PrefixHandler, decls: DeclarationList<'i>, @@ -495,6 +497,7 @@ impl<'i> DeclarationHandler<'i> { box_shadow: BoxShadowHandler::new(targets), mask: MaskHandler::default(), container: ContainerHandler::default(), + columns: ColumnsHandler::default(), fallback: FallbackHandler::new(targets), prefix: PrefixHandler::new(targets), decls: DeclarationList::new(), @@ -536,6 +539,7 @@ impl<'i> DeclarationHandler<'i> { || self.box_shadow.handle_property(property, &mut self.decls, context) || self.mask.handle_property(property, &mut self.decls, context) || self.container.handle_property(property, &mut self.decls, context) + || self.columns.handle_property(property, &mut self.decls, context) || self.fallback.handle_property(property, &mut self.decls, context) || self.prefix.handle_property(property, &mut self.decls, context) } @@ -565,6 +569,7 @@ impl<'i> DeclarationHandler<'i> { self.box_shadow.finalize(&mut self.decls, context); self.mask.finalize(&mut self.decls, context); self.container.finalize(&mut self.decls, context); + self.columns.finalize(&mut self.decls, context); self.fallback.finalize(&mut self.decls, context); self.prefix.finalize(&mut self.decls, context); } diff --git a/src/lib.rs b/src/lib.rs index 53e7f7e6..ff57f88d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5982,6 +5982,71 @@ mod tests { ); } + #[test] + fn test_columns() { + // columns shorthand + minify_test( + ".foo { column-width: 200px; column-count: 2; }", + ".foo{columns:200px 2}", + ); + minify_test(".foo { column-width: auto; column-count: 3; }", ".foo{columns:3}"); + minify_test(".foo { column-width: 10em; column-count: auto; }", ".foo{columns:10em}"); + minify_test( + ".foo { column-width: calc(80px * 2); column-count: auto; }", + ".foo{columns:160px}", + ); + minify_test( + ".foo { column-count: auto; column-width: 20vw; }", + ".foo{columns:20vw}", + ); + minify_test(".foo { columns: 200px; column-count: 2; }", ".foo{columns:200px 2}"); + minify_test( + ".foo { columns: 200px; column-count: 99999988888; }", + ".foo{columns:200px 2147483647}", + ); + minify_test(".foo { columns: auto; column-count: 3; }", ".foo{columns:3}"); + minify_test(".foo { column-count: auto; columns: 200px; }", ".foo{columns:200px}"); + minify_test(".foo { column-count: auto; columns: 3; }", ".foo{columns:3}"); + minify_test( + ".foo { column-count: auto; columns: 100px 3; }", + ".foo{columns:100px 3}", + ); + minify_test(".foo { column-width: 300px; columns: 2 auto; }", ".foo{columns:2}"); + minify_test(".foo { column-width: 300px; columns: 2; }", ".foo{columns:2}"); + + minify_test(".foo { column-width: auto; }", ".foo{column-width:auto}"); + minify_test(".foo { column-width: 0px; }", ".foo{column-width:0}"); + minify_test(".foo { column-width: calc(80px * 2); }", ".foo{column-width:160px}"); + minify_test( + ".foo { column-width: calc(100% - 30px); }", + ".foo{column-width:calc(100% - 30px)}", + ); + minify_test( + ".foo { column-width: clamp(1em, 2px, 4vh); }", + ".foo{column-width:clamp(1em,2px,4vh)}", + ); + + minify_test(".foo { column-count: auto; }", ".foo{column-count:auto}"); + minify_test(".foo { column-count: 1; }", ".foo{column-count:1}"); + + // Test minimum and maximum values: Chrome/Safari is 65535, Firefox is 1000. + minify_test(".foo { column-count: 123456789000; }", ".foo{column-count:2147483647}"); + minify_test( + ".foo { column-count: -123456789000; }", + ".foo{column-count:-2147483648}", + ); + + // TODO: Supprot calc + // minify_test(".foo { column-count: min(10, 3); }", ".foo{column-count:3}"); + // minify_test(".foo { column-count: calc(10 / 5); }", ".foo{column-count:2}"); + // minify_test(".foo { column-count: calc(2 * 3); }", ".foo{column-count:6}"); + // minify_test(".foo { column-count: calc(infinity * 1); }", ".foo{column-count:2147483647}"); + + // Invalid values + // minify_test(".foo { column-count: 1.4; }", ".foo{column-count:1.4}"); + // minify_test(".foo { column-count: 1.5; }", ".foo{column-count:1.5}"); + } + #[test] fn test_calc() { minify_test(".foo { width: calc(20px * 2) }", ".foo{width:40px}"); @@ -21926,6 +21991,9 @@ mod tests { minify_test(".foo { z-index: 999999 }", ".foo{z-index:999999}"); minify_test(".foo { z-index: 9999999 }", ".foo{z-index:9999999}"); minify_test(".foo { z-index: -9999999 }", ".foo{z-index:-9999999}"); + minify_test(".foo { z-index: calc(10 * 2) }", ".foo{z-index:calc(10*2)}"); + minify_test(".foo { z-index: 2147483647 }", ".foo{z-index:2147483647}"); + minify_test(".foo { z-index: auto }", ".foo{z-index:auto}"); } #[test] diff --git a/src/properties/columns.rs b/src/properties/columns.rs new file mode 100644 index 00000000..ad0245de --- /dev/null +++ b/src/properties/columns.rs @@ -0,0 +1,109 @@ +//! CSS properties related to Multi-column Layout. +//! https://www.w3.org/TR/css-multicol-1/ + +use super::{Property, PropertyId}; +use crate::context::PropertyHandlerContext; +use crate::declaration::{DeclarationBlock, DeclarationList}; +use crate::error::{ParserError, PrinterError}; +use crate::macros::{define_shorthand, shorthand_handler}; +use crate::printer::Printer; +use crate::targets::Browsers; +use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss}; +use crate::values::length::{IntegerOrAuto, Length, LengthOrAuto, LengthValue}; +use cssparser::*; +use std::ops::Not; + +define_shorthand! { + /// A value for the [columns](https://www.w3.org/TR/css-multicol-1/#columns) shorthand property. + /// columns = <'column-width'> || <'column-count'> + pub struct Columns { + /// The column-width property. + width: ColumnWidth(LengthOrAuto), + /// The column-count property. + count: ColumnCount(IntegerOrAuto), + } +} + +impl<'i> Parse<'i> for Columns { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + let state = input.state(); + let with_or_count = LengthOrAuto::parse(input)?; + + if let LengthOrAuto::Length(Length::Value(LengthValue::Unitless(..))) = with_or_count { + // first is unitless, so it must be column-count + // reparse with column-count + input.reset(&state); + let count = IntegerOrAuto::parse(input)?; + let width = input.try_parse(|input| LengthOrAuto::parse(input)).unwrap_or_default(); + + return Ok(Columns { width, count }); + } else if matches!(with_or_count, LengthOrAuto::Auto).not() + && matches!( + with_or_count, + LengthOrAuto::Length(Length::Value(LengthValue::Unitless(..))) + ) + .not() + { + // first is neither unitless nor auto, so it must be column-width + let count = input.try_parse(|input| IntegerOrAuto::parse(input)).unwrap_or_default(); + + return Ok(Columns { + width: with_or_count, + count, + }); + } + + // first is auto, check second + let state = input.state(); + let count = input.try_parse(|input| LengthOrAuto::parse(input)).unwrap_or_default(); + + if let LengthOrAuto::Auto = count { + // second is auto, so first must be column-width + Ok(Columns { + width: LengthOrAuto::Auto, + count: IntegerOrAuto::Auto, + }) + } else if matches!(count, LengthOrAuto::Length(Length::Value(LengthValue::Unitless(..)))).not() { + // second is not unitless, so first must be column-count + // reparse with column-with + Ok(Columns { + width: count, + count: IntegerOrAuto::Auto, + }) + } else { + // second is unitless, so first must be column-width + input.reset(&state); + let count = IntegerOrAuto::parse(input)?; + + Ok(Columns { + width: with_or_count, + count, + }) + } + } +} + +impl<'i> ToCss for Columns { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + if self.width != LengthOrAuto::default() { + self.width.to_css(dest)?; + } + + if self.width != LengthOrAuto::default() && self.count != IntegerOrAuto::default() { + dest.write_str(" ")?; + } + + if self.count != IntegerOrAuto::default() { + self.count.to_css(dest)?; + } + Ok(()) + } +} + +shorthand_handler!(ColumnsHandler -> Columns { + width: ColumnWidth(LengthOrAuto), + count: ColumnCount(IntegerOrAuto), +}); diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 2a0216d5..a1242d91 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -97,6 +97,7 @@ pub mod border; pub mod border_image; pub mod border_radius; pub mod box_shadow; +pub mod columns; pub mod contain; pub mod css_modules; pub mod custom; @@ -143,6 +144,7 @@ use border::*; use border_image::*; use border_radius::*; use box_shadow::*; +use columns::*; use contain::*; use css_modules::*; use cssparser::*; @@ -789,6 +791,11 @@ define_properties! { "overflow": Overflow(Overflow) shorthand: true, "overflow-x": OverflowX(OverflowKeyword), "overflow-y": OverflowY(OverflowKeyword), + + "columns": Columns(Columns) shorthand: true, // columns = <'column-width'> || <'column-count'> + "column-width": ColumnWidth(LengthOrAuto), // auto | + "column-count": ColumnCount(IntegerOrAuto), // auto | + "text-overflow": TextOverflow(TextOverflow, VendorPrefix) / O, // https://www.w3.org/TR/2020/WD-css-position-3-20200519 diff --git a/src/properties/text.rs b/src/properties/text.rs index 0710d6c6..4735b1db 100644 --- a/src/properties/text.rs +++ b/src/properties/text.rs @@ -1300,11 +1300,11 @@ impl ToCss for TextShadow { dest.write_char(' ')?; self.y_offset.to_css(dest)?; - if self.blur != Length::zero() || self.spread != Length::zero() { + if !self.blur.is_zero() || !self.spread.is_zero() { dest.write_char(' ')?; self.blur.to_css(dest)?; - if self.spread != Length::zero() { + if !self.spread.is_zero() { dest.write_char(' ')?; self.spread.to_css(dest)?; } diff --git a/src/values/length.rs b/src/values/length.rs index 0f6ebfac..09ab83c6 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -2,7 +2,7 @@ use super::angle::impl_try_from_angle; use super::calc::{Calc, MathFunction}; -use super::number::CSSNumber; +use super::number::{CSSInteger, CSSNumber}; use super::percentage::DimensionPercentage; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; @@ -35,6 +35,100 @@ impl LengthPercentage { } } +/// Either a [``](https://www.w3.org/TR/css-values-4/#integers), or the `auto` keyword. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", content = "value", rename_all = "kebab-case") +)] +pub enum IntegerOrAuto { + /// The `auto` keyword. + Auto, + /// A `` value. + Integer(CSSInteger), +} + +impl Default for IntegerOrAuto { + fn default() -> Self { + IntegerOrAuto::Auto + } +} + +impl<'i> Parse<'i> for IntegerOrAuto { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(IntegerOrAuto::Auto); + } + + if let Ok(integer) = input.try_parse(CSSInteger::parse) { + return Ok(IntegerOrAuto::Integer(integer)); + } + + Err(input.new_error_for_next_token()) + } +} + +impl ToCss for IntegerOrAuto { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + use IntegerOrAuto::*; + match self { + Auto => dest.write_str("auto"), + Integer(integer) => integer.to_css(dest), + } + } +} + +/// Either a [``](https://www.w3.org/TR/css-values-4/#lengths), or the `auto` keyword. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", content = "value", rename_all = "kebab-case") +)] +pub enum LengthOrAuto { + /// The `auto` keyword. + Auto, + /// A [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage) value. + Length(Length), +} + +impl Default for LengthOrAuto { + fn default() -> Self { + LengthOrAuto::Auto + } +} + +impl<'i> Parse<'i> for LengthOrAuto { + fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(LengthOrAuto::Auto); + } + + if let Ok(percent) = input.try_parse(|input| Length::parse(input)) { + return Ok(LengthOrAuto::Length(percent)); + } + + Err(input.new_error_for_next_token()) + } +} + +impl ToCss for LengthOrAuto { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + use LengthOrAuto::*; + match self { + Auto => dest.write_str("auto"), + Length(l) => l.to_css(dest), + } + } +} + /// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword. #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -99,6 +193,10 @@ macro_rules! define_length_units { $(#[$meta])* $name(CSSNumber), )+ + + /// A [``](https://www.w3.org/TR/css-values-4/#lengths) value + /// without a unit specified. + Unitless(CSSNumber), } impl<'i> Parse<'i> for LengthValue { @@ -116,7 +214,7 @@ macro_rules! define_length_units { }, Token::Number { value, .. } => { // TODO: quirks mode only? - Ok(LengthValue::Px(value)) + Ok(LengthValue::Unitless(value)) } ref token => return Err(location.new_unexpected_token_error(token.clone())), } @@ -130,6 +228,9 @@ macro_rules! define_length_units { $( LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))), )+ + + // TODO: only works in quirks mode? + LengthValue::Unitless(value) => (*value, "px"), } } } @@ -161,6 +262,8 @@ macro_rules! define_length_units { $( $name(value) => $name(value * other), )+ + + Unitless(value) => Unitless(value * other), } } } @@ -224,6 +327,8 @@ macro_rules! define_length_units { $( $name(value) => $name(op(*value)), )+ + + Unitless(value) => Unitless(op(*value)), } } } @@ -235,6 +340,8 @@ macro_rules! define_length_units { $( $name(value) => value.sign(), )+ + + Unitless(value) => value.sign(), } } } @@ -250,6 +357,8 @@ macro_rules! define_length_units { $( $name(value) => value.is_zero(), )+ + + Unitless(value) => value.is_zero(), } } } @@ -397,12 +506,15 @@ impl ToCss for LengthValue { } impl LengthValue { + // TODO(CGQAQ): This should be removed eventually. + // cause we already have IntegerValue pub(crate) fn to_css_unitless(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { LengthValue::Px(value) => value.to_css(dest), + LengthValue::Unitless(value) => value.to_css(dest), _ => self.to_css(dest), } } diff --git a/src/values/number.rs b/src/values/number.rs index bac4d23d..8c2a8843 100644 --- a/src/values/number.rs +++ b/src/values/number.rs @@ -115,7 +115,7 @@ pub type CSSInteger = i32; impl<'i> Parse<'i> for CSSInteger { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { - // TODO: calc?? + // TODO: Support calc?? let integer = input.expect_integer()?; Ok(integer) }