Skip to content

Commit dbf016e

Browse files
authored
[BUGFIX] Render RGB functions with "modern" syntax if required (#840)
The "legacy" syntax does not allow a mixture of `percentage`s and `number`s for the red, green and blue components. So if `rgb`/`rgba` functions that have such a mixture are rendered in the "legacy" syntax, an invalid property value will result. An `OutputFormat` option to use the "modern" syntax throughout will be added later (see #801). References: - https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#formal_syntax - https://www.w3.org/TR/css-color-4/#rgb-functions
1 parent 0a05b84 commit dbf016e

File tree

3 files changed

+88
-5
lines changed

3 files changed

+88
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Please also have a look at our
1313
- Partial support for CSS Color Module Level 4:
1414
- `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797}
1515
- Parse color functions that use the "modern" syntax (#800)
16+
- Render RGB functions with "modern" syntax when required (#840)
1617
- Add a class diagram to the README (#482)
1718
- Add more tests (#449)
1819

src/Value/Color.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ public function render(OutputFormat $outputFormat): string
233233
return $this->renderAsHex();
234234
}
235235

236+
if ($this->shouldRenderInModernSyntax()) {
237+
return $this->renderInModernSyntax($outputFormat);
238+
}
239+
236240
return parent::render($outputFormat);
237241
}
238242

@@ -282,4 +286,87 @@ private function renderAsHex(): string
282286

283287
return '#' . ($canUseShortVariant ? $result[0] . $result[2] . $result[4] : $result);
284288
}
289+
290+
/**
291+
* The "legacy" syntax does not allow RGB colors to have a mixture of `percentage`s and `number`s.
292+
*
293+
* The "legacy" and "modern" monikers are part of the formal W3C syntax.
294+
* See the following for more information:
295+
* - {@link
296+
* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#formal_syntax
297+
* Description of the formal syntax for `rgb()` on MDN
298+
* };
299+
* - {@link
300+
* https://www.w3.org/TR/css-color-4/#rgb-functions
301+
* The same in the CSS Color Module Level 4 W3C Candidate Recommendation Draft
302+
* } (as of 13 February 2024, at time of writing).
303+
*/
304+
private function shouldRenderInModernSyntax(): bool
305+
{
306+
if (!$this->colorFunctionMayHaveMixedValueTypes($this->getRealName())) {
307+
return false;
308+
}
309+
310+
$hasPercentage = false;
311+
$hasNumber = false;
312+
foreach ($this->aComponents as $key => $value) {
313+
if ($key === 'a') {
314+
// Alpha can have units that don't match those of the RGB components in the "legacy" syntax.
315+
// So it is not necessary to check it. It's also always last, hence `break` rather than `continue`.
316+
break;
317+
}
318+
if (!($value instanceof Size)) {
319+
// Unexpected, unknown, or modified via the API
320+
return false;
321+
}
322+
$unit = $value->getUnit();
323+
// `switch` only does loose comparison
324+
if ($unit === null) {
325+
$hasNumber = true;
326+
} elseif ($unit === '%') {
327+
$hasPercentage = true;
328+
} else {
329+
// Invalid unit
330+
return false;
331+
}
332+
}
333+
334+
return $hasPercentage && $hasNumber;
335+
}
336+
337+
/**
338+
* Some color functions, such as `rgb`,
339+
* may have a mixture of `percentage`, `number`, or possibly other types in their arguments.
340+
*
341+
* Note that this excludes the alpha component, which is treated separately.
342+
*/
343+
private function colorFunctionMayHaveMixedValueTypes(string $function): bool
344+
{
345+
$functionsThatMayHaveMixedValueTypes = ['rgb', 'rgba'];
346+
347+
return \in_array($function, $functionsThatMayHaveMixedValueTypes, true);
348+
}
349+
350+
/**
351+
* @return non-empty-string
352+
*/
353+
private function renderInModernSyntax(OutputFormat $outputFormat): string
354+
{
355+
// Maybe not yet without alpha, but will be...
356+
$componentsWithoutAlpha = $this->aComponents;
357+
\end($componentsWithoutAlpha);
358+
if (\key($componentsWithoutAlpha) === 'a') {
359+
$alpha = $this->aComponents['a'];
360+
unset($componentsWithoutAlpha['a']);
361+
}
362+
363+
$arguments = $outputFormat->implode(' ', $componentsWithoutAlpha);
364+
if (isset($alpha)) {
365+
$separator = $outputFormat->spaceBeforeListArgumentSeparator('/')
366+
. '/' . $outputFormat->spaceAfterListArgumentSeparator('/');
367+
$arguments = $outputFormat->implode($separator, [$arguments, $alpha]);
368+
}
369+
370+
return $this->getName() . '(' . $arguments . ')';
371+
}
285372
}

tests/Unit/Value/ColorTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ public static function provideValidColorAndExpectedRendering(): array
8686
'rgb(0 119 0)',
8787
'#070',
8888
],
89-
// The "legacy" syntax currently used for rendering does not allow a mixture of percentages and numbers.
90-
/*
9189
'modern rgb with percentage R' => [
9290
'rgb(0% 119 0)',
9391
'rgb(0% 119 0)',
@@ -112,7 +110,6 @@ public static function provideValidColorAndExpectedRendering(): array
112110
'rgb(0 60% 0%)',
113111
'rgb(0 60% 0%)',
114112
],
115-
//*/
116113
'modern rgb with percentage components' => [
117114
'rgb(0% 60% 0%)',
118115
'rgb(0%,60%,0%)',
@@ -131,7 +128,6 @@ public static function provideValidColorAndExpectedRendering(): array
131128
'rgb(0 119 0 / 50%)',
132129
'rgba(0,119,0,50%)',
133130
],
134-
/*
135131
'modern rgba with percentage R' => [
136132
'rgb(0% 119 0 / 0.5)',
137133
'rgba(0% 119 0/.5)',
@@ -144,7 +140,6 @@ public static function provideValidColorAndExpectedRendering(): array
144140
'rgb(0 119 0% / 0.5)',
145141
'rgba(0 119 0%/.5)',
146142
],
147-
//*/
148143
'modern rgba with percentage RGB' => [
149144
'rgb(0% 60% 0% / 0.5)',
150145
'rgba(0%,60%,0%,.5)',

0 commit comments

Comments
 (0)