Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/css/properties/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate as css;
use crate::PrintErr;
use crate::Printer;
use crate::css_values::color::CssColor;
use crate::css_values::length::LengthValue as Length;
use crate::css_values::length::Length;
Comment thread
robobun marked this conversation as resolved.
Resolved
use crate::css_values::number::NumericRange;

/// A value for the [text-shadow](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-shadow-property) property.
#[derive(Clone, PartialEq)]
Expand All @@ -29,11 +30,25 @@ impl TextShadow {
loop {
if lengths.is_none() {
let value = input.try_parse(|i: &mut css::Parser| -> css::Result<Lengths> {
let horizontal = Length::parse(i)?;
let vertical = Length::parse(i)?;
let blur = i.try_parse(Length::parse).ok().unwrap_or_else(Length::zero);
let spread = i.try_parse(Length::parse).ok().unwrap_or_else(Length::zero);
Ok((horizontal, vertical, blur, spread))
let horizontal = Length::parse_with_range(i, NumericRange::All)?;
Comment thread
robobun marked this conversation as resolved.
Resolved
let vertical = Length::parse_with_range(i, NumericRange::All)?;
let non_negative = |i: &mut css::Parser| {
i.try_parse(|i| Length::parse_with_range(i, NumericRange::NonNegative))
Comment thread
robobun marked this conversation as resolved.
Resolved
.ok()
};
let blur = non_negative(i);
// A length that is not a blur radius is not a spread either.
let spread = if blur.is_some() {
non_negative(i)
} else {
None
};
Ok((
horizontal,
vertical,
blur.unwrap_or_else(Length::zero),
spread.unwrap_or_else(Length::zero),
))
});

if let Ok(v) = value {
Expand Down
30 changes: 29 additions & 1 deletion src/css/values/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::css_parser::{CssResult, Maybe, Parser, PrintErr, Printer, Token};
use crate::targets::Browsers;
use crate::values::angle::Angle;
use crate::values::calc::{Calc, MathFunction};
use crate::values::number::CSSNumber;
use crate::values::number::{CSSNumber, NumericRange};
use crate::values::percentage::DimensionPercentage;
use crate::values::protocol;

Expand Down Expand Up @@ -426,6 +426,34 @@ impl Length {
Ok(Self::Value(len))
}

/// Parses a length for a slot with `range` (lightningcss `Length::parse_with_range`).
pub(crate) fn parse_with_range(input: &mut Parser, range: NumericRange) -> CssResult<Length> {
// A literal and a math function start with different tokens, so the cheap one goes first.
if let Ok(value) = input.try_parse(LengthValue::parse) {
if range == NumericRange::NonNegative && value.value() < 0.0 {
return Err(input.new_custom_error(css::ParserError::invalid_value));
}
return Ok(Self::Value(value));
}
match Calc::<Length>::parse(input)? {
Comment thread
robobun marked this conversation as resolved.
Resolved
Calc::Value(length) => Ok(match *length {
// NaN is 0 too: it does not escape a top-level calculation.
Self::Value(value)
if range == NumericRange::NonNegative
&& (value.value() < 0.0 || value.value().is_nan()) =>
{
Self::zero()
}
length => length,
}),
Comment thread
robobun marked this conversation as resolved.
Resolved
calc @ Calc::Function(_) => Ok(Self::Calc(Box::new(calc))),
// Not a length: a number, or the bare sum or product of a one-argument min(), max() or hypot().
Calc::Number(_) | Calc::Sum { .. } | Calc::Product { .. } => {
Err(input.new_custom_error(css::ParserError::invalid_value))
}
}
}

// to_css — provided by #[derive(css::ToCss)] (Box<Calc<Length>> auto-derefs
// to Calc::<V: CalcValue>::to_css inherent). parse KEPT (custom Calc::Value unwrap).

Expand Down
8 changes: 8 additions & 0 deletions src/css/values/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ use crate::values::calc::Calc;

pub type CSSNumber = f32;

/// The values a numeric slot takes (lightningcss `NumericRange`).
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum NumericRange {
All,
/// `[0,∞]`: a negative literal is invalid, a negative math result is 0.
NonNegative,
}

pub struct CSSNumberFns;

impl CSSNumberFns {
Expand Down
155 changes: 155 additions & 0 deletions test/js/bun/css/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,161 @@ describe("css tests", () => {
);
});

describe("text-shadow", () => {
minify_test(".foo { text-shadow: 1px 1px 2px yellow }", ".foo{text-shadow:1px 1px 2px #ff0}");
minify_test(".foo { text-shadow: 1px 1px 2px 3px yellow }", ".foo{text-shadow:1px 1px 2px 3px #ff0}");
minify_test(".foo { text-shadow: 1px 1px 0 yellow }", ".foo{text-shadow:1px 1px #ff0}");
minify_test(".foo { text-shadow: 1px 1px yellow, 2px 3px red }", ".foo{text-shadow:1px 1px #ff0,2px 3px red}");
minify_test(".foo { text-shadow: -1px -2px }", ".foo{text-shadow:-1px -2px}");

// A length can be a math function. A constant result is folded.
minify_test(".foo { text-shadow: calc(1px + 2px) 0 0 red }", ".foo{text-shadow:3px 0 red}");
minify_test(".foo { text-shadow: round(14.8px, 5px) 0 0 red }", ".foo{text-shadow:15px 0 red}");
minify_test(".foo { text-shadow: 1px 1px calc(2px * 2) yellow }", ".foo{text-shadow:1px 1px 4px #ff0}");
minify_test(".foo { text-shadow: 1px 1px 2px max(1px, 3px) yellow }", ".foo{text-shadow:1px 1px 2px 3px #ff0}");
minify_test(
".foo { text-shadow: 1px 1px yellow, calc(1px + 2px) 3px red }",
".foo{text-shadow:1px 1px #ff0,3px 3px red}",
);
minify_test(".foo { text-shadow: calc(1px - 3px) calc(2px - 4px) red }", ".foo{text-shadow:-2px -2px red}");
minify_test(".foo { text-shadow: calc(1px + 1em) 0 red }", ".foo{text-shadow:calc(1px + 1em) 0 red}");
minify_test(".foo { text-shadow: round(1em, 5px) 0 red }", ".foo{text-shadow:round(1em,5px) 0 red}");

// The value is read the same way in every place a declaration can be.
minify_test(
"@keyframes x { from { text-shadow: calc(1px + 2px) 0 red } to { text-shadow: 0 0 calc(1px - 6px) red } }",
"@keyframes x{0%{text-shadow:3px 0 red}to{text-shadow:0 0 red}}",
);
minify_test(
".a { .b { text-shadow: calc(1px + 2px) 0 red !important } }",
".a{& .b{text-shadow:3px 0 red!important}}",
);

// The blur radius and the spread cannot be negative. The browser clamps a calculation
// after it resolves it, so a constant below zero is 0.
minify_test(".foo { text-shadow: 0 0 calc(1px - 6px) red }", ".foo{text-shadow:0 0 red}");
minify_test(".foo { text-shadow: 1px 2px calc(-3px) }", ".foo{text-shadow:1px 2px}");
minify_test(".foo { text-shadow: 0 0 calc(1px - 6px) 2px red }", ".foo{text-shadow:0 0 0 2px red}");
minify_test(".foo { text-shadow: 0 0 1px calc(1px - 6px) red }", ".foo{text-shadow:0 0 1px red}");
minify_test(".foo { text-shadow: 0 0 calc(-infinity * 1px) red }", ".foo{text-shadow:0 0 red}");
minify_test(".foo { text-shadow: 0 0 calc(NaN * 1px) red }", ".foo{text-shadow:0 0 red}");
minify_test(".foo { text-shadow: 0 0 calc(1px - 1em) red }", ".foo{text-shadow:0 0 calc(1px - 1em) red}");
minify_test(".foo { text-shadow: 0 0 max(-5px, -1em) red }", ".foo{text-shadow:0 0 max(-5px,-1em) red}");

// The declaration prints as written when a value is not a length for its slot: a negative
// blur or spread, math that resolves to a number, a one-argument min(), max() or hypot()
// of a sum or product.
for (const value of [
"0 0 -5px red",
"1px 2px 0 -4px red",
"calc(1px + 2px) 0 -5px red",
"calc(1px + 2px) 0 1px -5px red",
"calc(1 + 2) 0 red",
"0 sign(-2px) red",
"min(1em + 2px) 0 red",
"0 0 hypot(1em + 2px) red",
"max(2 * min(1em, 2px)) 0 red",
]) {
cssTest(`.foo { text-shadow: ${value} }`, `.foo { text-shadow: ${value}; }`);
}

// The earlier declaration is the fallback for a target that does not support the math
// function of the later one.
prefix_test(
`.foo {
text-shadow: 15px 0 red;
text-shadow: round(1em, 5px) 0 red;
}
`,
`.foo {
text-shadow: 15px 0 red;
text-shadow: round(1em, 5px) 0 red;
}
`,
{ chrome: Some(87 << 16), firefox: Some(78 << 16), safari: Some(14 << 16) },
);
prefix_test(
`.foo {
text-shadow: 15px 0 red;
text-shadow: round(1em, 5px) 0 red;
}
`,
`.foo {
text-shadow: round(1em, 5px) 0 red;
}
`,
{ chrome: Some(125 << 16) },
);
prefix_test(
`.foo {
text-shadow: 0px 0px 22px red;
text-shadow: 0px 0px max(2cqw, 22px) red;
}
`,
`.foo {
text-shadow: 0 0 22px red;
text-shadow: 0 0 max(2cqw, 22px) red;
}
`,
{ safari: Some(14 << 16) },
);
prefix_test(
`.foo {
text-shadow: 0px 0px 22px red;
text-shadow: 0px 0px max(2cqw, 22px) red;
}
`,
`.foo {
text-shadow: 0 0 max(2cqw, 22px) red;
}
`,
{ safari: Some(16 << 16) },
);

// A color fallback of a shadow with math is a declaration in the same rule.
prefix_test(
".foo { text-shadow: calc(6px * 2) 12px lab(40% 56.6 39) }",
`.foo {
text-shadow: 12px 12px #b32323;
text-shadow: 12px 12px lab(40% 56.6 39);
}
`,
{ chrome: Some(90 << 16) },
);
prefix_test(
".foo { text-shadow: calc(1px + 1em) 12px lab(40% 56.6 39) }",
`.foo {
text-shadow: calc(1px + 1em) 12px #b32323;
text-shadow: calc(1px + 1em) 12px color(display-p3 .643308 .192455 .167712);
text-shadow: calc(1px + 1em) 12px lab(40% 56.6 39);
}
`,
{ chrome: Some(90 << 16), safari: Some(14 << 16) },
);
prefix_test(
".foo { text-shadow: calc(1px + 1em) 12px lab(40% 56.6 39), 12px 12px yellow }",
`.foo {
text-shadow: calc(1px + 1em) 12px #b32323, 12px 12px #ff0;
text-shadow: calc(1px + 1em) 12px lab(40% 56.6 39), 12px 12px #ff0;
}
`,
{ chrome: Some(4 << 16) },
);
prefix_test(
`.foo {
text-shadow: 0 0 22px red;
text-shadow: 0 0 calc(1px + 1em) lab(40% 56.6 39);
}
`,
`.foo {
text-shadow: 0 0 22px red;
text-shadow: 0 0 calc(1px + 1em) lab(40% 56.6 39);
}
`,
{ safari: Some(14 << 16) },
);
});

describe("margin", () => {
cssTest(
`
Expand Down
Loading