From fc4c44a0bff3fa398af89451a7339264a09a1949 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 23 Jan 2025 19:25:07 +0000 Subject: [PATCH 1/2] [FEATURE] Parse color functions with modern syntax Also include tests for color functions with CSS variable substitutions where they use the "legacy" syntax. Resolves #755. --- CHANGELOG.md | 3 +- src/Value/Color.php | 34 ++++++- tests/Unit/Value/ColorTest.php | 164 ++++++++++++++++++++++++++++++++- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3704586..1c1b0da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ Please also have a look at our ### Added -- Partial support for CSS Color Module Level 4 syntax: +- Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} + - Parse color functions that use the "modern" syntax - Add official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545) diff --git a/src/Value/Color.php b/src/Value/Color.php index d08822b7..5c716678 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -116,6 +116,7 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti } $bContainsVar = false; + $isLegacySyntax = false; $iLength = $oParserState->strlen($colorModeForParsing); for ($i = 0; $i < $iLength; ++$i) { $oParserState->consumeWhiteSpace(); @@ -138,9 +139,40 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti break; } - if ($i < ($iLength - 1)) { + // "Legacy" syntax is comma-delimited. + // "Modern" syntax is space-delimited, with `/` as alpha delimiter. + // They cannot be mixed. + if ($i === 0) { + // An immediate closing parenthesis is not valid. + if ($oParserState->comes(')')) { + throw new UnexpectedTokenException( + 'Color function with no arguments', + '', + 'custom', + $oParserState->currentLine() + ); + } + $isLegacySyntax = $oParserState->comes(','); + } + + if ($isLegacySyntax && $i < ($iLength - 1)) { $oParserState->consume(','); } + + // In the "modern" syntax, the alpha value must be delimited with `/`. + if (!$isLegacySyntax) { + if ($bContainsVar) { + // If the `var` substitution encompasses more than one argument, + // the alpha deliminator may come at any time. + if ($oParserState->comes('/')) { + $oParserState->consume('/'); + } + } elseif (($colorModeForParsing[$i + 1] ?? '') === 'a') { + // Alpha value is the next expected argument. + // Since a closing parenthesis was not found, a `/` separator is now required. + $oParserState->consume('/'); + } + } } $oParserState->consume(')'); diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index b4799aa8..822ba5be 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -70,20 +70,142 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', ], - /* 'modern rgb' => [ 'rgb(0 119 0)', - 'rgb(0,119,0)', + '#070', ], + /* 'modern rgb with none' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', ], + //*/ 'modern rgba' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', ], + /* + 'modern rgba with none as alpha' => [ + 'rgb(0 119 0 / none)', + 'rgba(0 119 0 / none)', + ], //*/ + 'legacy rgb with var for R' => [ + 'rgb(var(--r), 119, 0)', + 'rgb(var(--r),119,0)', + ], + 'legacy rgb with var for G' => [ + 'rgb(0, var(--g), 0)', + 'rgb(0,var(--g),0)', + ], + 'legacy rgb with var for B' => [ + 'rgb(0, 119, var(--b))', + 'rgb(0,119,var(--b))', + ], + 'legacy rgb with var for RG' => [ + 'rgb(var(--rg), 0)', + 'rgb(var(--rg),0)', + ], + 'legacy rgb with var for GB' => [ + 'rgb(0, var(--gb))', + 'rgb(0,var(--gb))', + ], + 'legacy rgba with var for R' => [ + 'rgba(var(--r), 119, 0, 0.5)', + 'rgba(var(--r),119,0,.5)', + ], + 'legacy rgba with var for G' => [ + 'rgba(0, var(--g), 0, 0.5)', + 'rgba(0,var(--g),0,.5)', + ], + 'legacy rgba with var for B' => [ + 'rgb(0, 119, var(--b), 0.5)', + 'rgb(0,119,var(--b),.5)', + ], + 'legacy rgba with var for A' => [ + 'rgba(0, 119, 0, var(--a))', + 'rgba(0,119,0,var(--a))', + ], + 'legacy rgba with var for RG' => [ + 'rgba(var(--rg), 0, 0.5)', + 'rgba(var(--rg),0,.5)', + ], + 'legacy rgba with var for GB' => [ + 'rgba(0, var(--gb), 0.5)', + 'rgba(0,var(--gb),.5)', + ], + 'legacy rgba with var for BA' => [ + 'rgba(0, 119, var(--ba))', + 'rgba(0,119,var(--ba))', + ], + 'legacy rgba with var for RGB' => [ + 'rgba(var(--rgb), 0.5)', + 'rgba(var(--rgb),.5)', + ], + 'legacy rgba with var for GBA' => [ + 'rgba(0, var(--gba))', + 'rgba(0,var(--gba))', + ], + 'modern rgb with var for R' => [ + 'rgb(var(--r) 119 0)', + 'rgb(var(--r),119,0)', + ], + 'modern rgb with var for G' => [ + 'rgb(0 var(--g) 0)', + 'rgb(0,var(--g),0)', + ], + 'modern rgb with var for B' => [ + 'rgb(0 119 var(--b))', + 'rgb(0,119,var(--b))', + ], + 'modern rgb with var for RG' => [ + 'rgb(var(--rg) 0)', + 'rgb(var(--rg),0)', + ], + 'modern rgb with var for GB' => [ + 'rgb(0 var(--gb))', + 'rgb(0,var(--gb))', + ], + 'modern rgba with var for R' => [ + 'rgba(var(--r) 119 0 / 0.5)', + 'rgba(var(--r),119,0,.5)', + ], + 'modern rgba with var for G' => [ + 'rgba(0 var(--g) 0 / 0.5)', + 'rgba(0,var(--g),0,.5)', + ], + 'modern rgba with var for B' => [ + 'rgba(0 119 var(--b) / 0.5)', + 'rgba(0,119,var(--b),.5)', + ], + 'modern rgba with var for A' => [ + 'rgba(0 119 0 / var(--a))', + 'rgba(0,119,0,var(--a))', + ], + 'modern rgba with var for RG' => [ + 'rgba(var(--rg) 0 / 0.5)', + 'rgba(var(--rg),0,.5)', + ], + 'modern rgba with var for GB' => [ + 'rgba(0 var(--gb) / 0.5)', + 'rgba(0,var(--gb),.5)', + ], + 'modern rgba with var for BA' => [ + 'rgba(0 119 var(--ba))', + 'rgba(0,119,var(--ba))', + ], + 'modern rgba with var for RGB' => [ + 'rgba(var(--rgb) / 0.5)', + 'rgba(var(--rgb),.5)', + ], + 'modern rgba with var for GBA' => [ + 'rgba(0 var(--gba))', + 'rgba(0,var(--gba))', + ], + 'rgba with var for RGBA' => [ + 'rgba(var(--rgba))', + 'rgba(var(--rgba))', + ], 'legacy hsl' => [ 'hsl(120, 100%, 25%)', 'hsl(120,100%,25%)', @@ -120,6 +242,26 @@ public static function provideValidColorAndExpectedRendering(): array 'hsl(120, 100%, 25%, 0.5)', 'hsla(120,100%,25%,.5)', ], + 'modern hsl' => [ + 'hsl(120 100% 25%)', + 'hsl(120,100%,25%)', + ], + /* + 'modern hsl with none' => [ + 'hsl(none 100% 25%)', + 'hsl(none 100% 25%)', + ], + //*/ + 'modern hsla' => [ + 'hsl(120 100% 25% / 0.5)', + 'hsla(120,100%,25%,.5)', + ], + /* + 'modern hsla with none as alpha' => [ + 'hsl(120 100% 25% none)', + 'hsla(120 100% 25% none)', + ], + //*/ ]; } @@ -180,6 +322,15 @@ public static function provideInvalidColor(): array 'rgb(255, 0px, 0)', ], //*/ + 'modern rgb color without slash separator for alpha' => [ + 'rgb(255 0 0 0.5)', + ], + 'rgb color with mixed separators, comma first' => [ + 'rgb(255, 0 0)', + ], + 'rgb color with mixed separators, space first' => [ + 'rgb(255 0, 0)', + ], 'hsl color with 0 arguments' => [ 'hsl()', ], @@ -200,6 +351,15 @@ public static function provideInvalidColor(): array 'hsl(0px, 100%, 50%)' ], //*/ + 'modern hsl color without slash separator for alpha' => [ + 'rgb(0 100% 50% 0.5)', + ], + 'hsl color with mixed separators, comma first' => [ + 'hsl(0, 100% 50%)', + ], + 'hsl color with mixed separators, space first' => [ + 'hsl(0 100%, 50%)', + ], ]; } From ed97ed2507ed9a2ff3de8a1f4cb58b459e1f3740 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Thu, 23 Jan 2025 21:59:29 +0000 Subject: [PATCH 2/2] Add PR number to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1b0da4..a92ad218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ Please also have a look at our - Partial support for CSS Color Module Level 4: - `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797} - - Parse color functions that use the "modern" syntax + - Parse color functions that use the "modern" syntax (#800) - Add official support for PHP 8.4 (#657) - Support arithmetic operators in CSS function arguments (#607) - Add support for inserting an item in a CSS list (#545)