Skip to content

Commit fc4c44a

Browse files
committed
[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.
1 parent 9e79e16 commit fc4c44a

File tree

3 files changed

+197
-4
lines changed

3 files changed

+197
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ Please also have a look at our
1010

1111
### Added
1212

13-
- Partial support for CSS Color Module Level 4 syntax:
13+
- Partial support for CSS Color Module Level 4:
1414
- `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797}
15+
- Parse color functions that use the "modern" syntax
1516
- Add official support for PHP 8.4 (#657)
1617
- Support arithmetic operators in CSS function arguments (#607)
1718
- Add support for inserting an item in a CSS list (#545)

src/Value/Color.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti
116116
}
117117

118118
$bContainsVar = false;
119+
$isLegacySyntax = false;
119120
$iLength = $oParserState->strlen($colorModeForParsing);
120121
for ($i = 0; $i < $iLength; ++$i) {
121122
$oParserState->consumeWhiteSpace();
@@ -138,9 +139,40 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti
138139
break;
139140
}
140141

141-
if ($i < ($iLength - 1)) {
142+
// "Legacy" syntax is comma-delimited.
143+
// "Modern" syntax is space-delimited, with `/` as alpha delimiter.
144+
// They cannot be mixed.
145+
if ($i === 0) {
146+
// An immediate closing parenthesis is not valid.
147+
if ($oParserState->comes(')')) {
148+
throw new UnexpectedTokenException(
149+
'Color function with no arguments',
150+
'',
151+
'custom',
152+
$oParserState->currentLine()
153+
);
154+
}
155+
$isLegacySyntax = $oParserState->comes(',');
156+
}
157+
158+
if ($isLegacySyntax && $i < ($iLength - 1)) {
142159
$oParserState->consume(',');
143160
}
161+
162+
// In the "modern" syntax, the alpha value must be delimited with `/`.
163+
if (!$isLegacySyntax) {
164+
if ($bContainsVar) {
165+
// If the `var` substitution encompasses more than one argument,
166+
// the alpha deliminator may come at any time.
167+
if ($oParserState->comes('/')) {
168+
$oParserState->consume('/');
169+
}
170+
} elseif (($colorModeForParsing[$i + 1] ?? '') === 'a') {
171+
// Alpha value is the next expected argument.
172+
// Since a closing parenthesis was not found, a `/` separator is now required.
173+
$oParserState->consume('/');
174+
}
175+
}
144176
}
145177
$oParserState->consume(')');
146178

tests/Unit/Value/ColorTest.php

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,142 @@ public static function provideValidColorAndExpectedRendering(): array
7070
'rgb(0, 119, 0, 0.5)',
7171
'rgba(0,119,0,.5)',
7272
],
73-
/*
7473
'modern rgb' => [
7574
'rgb(0 119 0)',
76-
'rgb(0,119,0)',
75+
'#070',
7776
],
77+
/*
7878
'modern rgb with none' => [
7979
'rgb(none 119 0)',
8080
'rgb(none 119 0)',
8181
],
82+
//*/
8283
'modern rgba' => [
8384
'rgb(0 119 0 / 0.5)',
8485
'rgba(0,119,0,.5)',
8586
],
87+
/*
88+
'modern rgba with none as alpha' => [
89+
'rgb(0 119 0 / none)',
90+
'rgba(0 119 0 / none)',
91+
],
8692
//*/
93+
'legacy rgb with var for R' => [
94+
'rgb(var(--r), 119, 0)',
95+
'rgb(var(--r),119,0)',
96+
],
97+
'legacy rgb with var for G' => [
98+
'rgb(0, var(--g), 0)',
99+
'rgb(0,var(--g),0)',
100+
],
101+
'legacy rgb with var for B' => [
102+
'rgb(0, 119, var(--b))',
103+
'rgb(0,119,var(--b))',
104+
],
105+
'legacy rgb with var for RG' => [
106+
'rgb(var(--rg), 0)',
107+
'rgb(var(--rg),0)',
108+
],
109+
'legacy rgb with var for GB' => [
110+
'rgb(0, var(--gb))',
111+
'rgb(0,var(--gb))',
112+
],
113+
'legacy rgba with var for R' => [
114+
'rgba(var(--r), 119, 0, 0.5)',
115+
'rgba(var(--r),119,0,.5)',
116+
],
117+
'legacy rgba with var for G' => [
118+
'rgba(0, var(--g), 0, 0.5)',
119+
'rgba(0,var(--g),0,.5)',
120+
],
121+
'legacy rgba with var for B' => [
122+
'rgb(0, 119, var(--b), 0.5)',
123+
'rgb(0,119,var(--b),.5)',
124+
],
125+
'legacy rgba with var for A' => [
126+
'rgba(0, 119, 0, var(--a))',
127+
'rgba(0,119,0,var(--a))',
128+
],
129+
'legacy rgba with var for RG' => [
130+
'rgba(var(--rg), 0, 0.5)',
131+
'rgba(var(--rg),0,.5)',
132+
],
133+
'legacy rgba with var for GB' => [
134+
'rgba(0, var(--gb), 0.5)',
135+
'rgba(0,var(--gb),.5)',
136+
],
137+
'legacy rgba with var for BA' => [
138+
'rgba(0, 119, var(--ba))',
139+
'rgba(0,119,var(--ba))',
140+
],
141+
'legacy rgba with var for RGB' => [
142+
'rgba(var(--rgb), 0.5)',
143+
'rgba(var(--rgb),.5)',
144+
],
145+
'legacy rgba with var for GBA' => [
146+
'rgba(0, var(--gba))',
147+
'rgba(0,var(--gba))',
148+
],
149+
'modern rgb with var for R' => [
150+
'rgb(var(--r) 119 0)',
151+
'rgb(var(--r),119,0)',
152+
],
153+
'modern rgb with var for G' => [
154+
'rgb(0 var(--g) 0)',
155+
'rgb(0,var(--g),0)',
156+
],
157+
'modern rgb with var for B' => [
158+
'rgb(0 119 var(--b))',
159+
'rgb(0,119,var(--b))',
160+
],
161+
'modern rgb with var for RG' => [
162+
'rgb(var(--rg) 0)',
163+
'rgb(var(--rg),0)',
164+
],
165+
'modern rgb with var for GB' => [
166+
'rgb(0 var(--gb))',
167+
'rgb(0,var(--gb))',
168+
],
169+
'modern rgba with var for R' => [
170+
'rgba(var(--r) 119 0 / 0.5)',
171+
'rgba(var(--r),119,0,.5)',
172+
],
173+
'modern rgba with var for G' => [
174+
'rgba(0 var(--g) 0 / 0.5)',
175+
'rgba(0,var(--g),0,.5)',
176+
],
177+
'modern rgba with var for B' => [
178+
'rgba(0 119 var(--b) / 0.5)',
179+
'rgba(0,119,var(--b),.5)',
180+
],
181+
'modern rgba with var for A' => [
182+
'rgba(0 119 0 / var(--a))',
183+
'rgba(0,119,0,var(--a))',
184+
],
185+
'modern rgba with var for RG' => [
186+
'rgba(var(--rg) 0 / 0.5)',
187+
'rgba(var(--rg),0,.5)',
188+
],
189+
'modern rgba with var for GB' => [
190+
'rgba(0 var(--gb) / 0.5)',
191+
'rgba(0,var(--gb),.5)',
192+
],
193+
'modern rgba with var for BA' => [
194+
'rgba(0 119 var(--ba))',
195+
'rgba(0,119,var(--ba))',
196+
],
197+
'modern rgba with var for RGB' => [
198+
'rgba(var(--rgb) / 0.5)',
199+
'rgba(var(--rgb),.5)',
200+
],
201+
'modern rgba with var for GBA' => [
202+
'rgba(0 var(--gba))',
203+
'rgba(0,var(--gba))',
204+
],
205+
'rgba with var for RGBA' => [
206+
'rgba(var(--rgba))',
207+
'rgba(var(--rgba))',
208+
],
87209
'legacy hsl' => [
88210
'hsl(120, 100%, 25%)',
89211
'hsl(120,100%,25%)',
@@ -120,6 +242,26 @@ public static function provideValidColorAndExpectedRendering(): array
120242
'hsl(120, 100%, 25%, 0.5)',
121243
'hsla(120,100%,25%,.5)',
122244
],
245+
'modern hsl' => [
246+
'hsl(120 100% 25%)',
247+
'hsl(120,100%,25%)',
248+
],
249+
/*
250+
'modern hsl with none' => [
251+
'hsl(none 100% 25%)',
252+
'hsl(none 100% 25%)',
253+
],
254+
//*/
255+
'modern hsla' => [
256+
'hsl(120 100% 25% / 0.5)',
257+
'hsla(120,100%,25%,.5)',
258+
],
259+
/*
260+
'modern hsla with none as alpha' => [
261+
'hsl(120 100% 25% none)',
262+
'hsla(120 100% 25% none)',
263+
],
264+
//*/
123265
];
124266
}
125267

@@ -180,6 +322,15 @@ public static function provideInvalidColor(): array
180322
'rgb(255, 0px, 0)',
181323
],
182324
//*/
325+
'modern rgb color without slash separator for alpha' => [
326+
'rgb(255 0 0 0.5)',
327+
],
328+
'rgb color with mixed separators, comma first' => [
329+
'rgb(255, 0 0)',
330+
],
331+
'rgb color with mixed separators, space first' => [
332+
'rgb(255 0, 0)',
333+
],
183334
'hsl color with 0 arguments' => [
184335
'hsl()',
185336
],
@@ -200,6 +351,15 @@ public static function provideInvalidColor(): array
200351
'hsl(0px, 100%, 50%)'
201352
],
202353
//*/
354+
'modern hsl color without slash separator for alpha' => [
355+
'rgb(0 100% 50% 0.5)',
356+
],
357+
'hsl color with mixed separators, comma first' => [
358+
'hsl(0, 100% 50%)',
359+
],
360+
'hsl color with mixed separators, space first' => [
361+
'hsl(0 100%, 50%)',
362+
],
203363
];
204364
}
205365

0 commit comments

Comments
 (0)