Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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 (#800)
- Render RGB functions with "modern" syntax when required (#840)
- Add a class diagram to the README (#482)
- Add more tests (#449)

Expand Down
87 changes: 87 additions & 0 deletions src/Value/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ public function render(OutputFormat $outputFormat): string
return $this->renderAsHex();
}

if ($this->shouldRenderInModernSyntax()) {
return $this->renderInModernSyntax($outputFormat);
}

return parent::render($outputFormat);
}

Expand Down Expand Up @@ -282,4 +286,87 @@ private function renderAsHex(): string

return '#' . ($canUseShortVariant ? $result[0] . $result[2] . $result[4] : $result);
}

/**
* The "legacy" syntax does not allow RGB colors to have a mixture of `percentage`s and `number`s.
*
* The "legacy" and "modern" monikers are part of the formal W3C syntax.
* See the following for more information:
* - {@link
* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#formal_syntax
* Description of the formal syntax for `rgb()` on MDN
* };
* - {@link
* https://www.w3.org/TR/css-color-4/#rgb-functions
* The same in the CSS Color Module Level 4 W3C Candidate Recommendation Draft
* } (as of 13 February 2024, at time of writing).
*/
private function shouldRenderInModernSyntax(): bool
{
if (!$this->colorFunctionMayHaveMixedValueTypes($this->getRealName())) {
return false;
}

$hasPercentage = false;
$hasNumber = false;
foreach ($this->aComponents as $key => $value) {
if ($key === 'a') {
// Alpha can have units that don't match those of the RGB components in the "legacy" syntax.
// So it is not necessary to check it. It's also always last, hence `break` rather than `continue`.
break;
}
if (!($value instanceof Size)) {
// Unexpected, unknown, or modified via the API
return false;
}
$unit = $value->getUnit();
// `switch` only does loose comparison
if ($unit === null) {
$hasNumber = true;
} elseif ($unit === '%') {
$hasPercentage = true;
} else {
// Invalid unit
return false;
}
}

return $hasPercentage && $hasNumber;
}

/**
* Some color functions, such as `rgb`,
* may have a mixture of `percentage`, `number`, or possibly other types in their arguments.
*
* Note that this excludes the alpha component, which is treated separately.
*/
private function colorFunctionMayHaveMixedValueTypes(string $function): bool
{
$functionsThatMayHaveMixedValueTypes = ['rgb', 'rgba'];

return \in_array($function, $functionsThatMayHaveMixedValueTypes, true);
}

/**
* @return non-empty-string
*/
private function renderInModernSyntax(OutputFormat $outputFormat): string
{
// Maybe not yet without alpha, but will be...
$componentsWithoutAlpha = $this->aComponents;
\end($componentsWithoutAlpha);
if (\key($componentsWithoutAlpha) === 'a') {
$alpha = $this->aComponents['a'];
unset($componentsWithoutAlpha['a']);
}

$arguments = $outputFormat->implode(' ', $componentsWithoutAlpha);
if (isset($alpha)) {
$separator = $outputFormat->spaceBeforeListArgumentSeparator('/')
. '/' . $outputFormat->spaceAfterListArgumentSeparator('/');
$arguments = $outputFormat->implode($separator, [$arguments, $alpha]);
}

return $this->getName() . '(' . $arguments . ')';
}
}
5 changes: 0 additions & 5 deletions tests/Unit/Value/ColorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ public static function provideValidColorAndExpectedRendering(): array
'rgb(0 119 0)',
'#070',
],
// The "legacy" syntax currently used for rendering does not allow a mixture of percentages and numbers.
/*
'modern rgb with percentage R' => [
'rgb(0% 119 0)',
'rgb(0% 119 0)',
Expand All @@ -112,7 +110,6 @@ public static function provideValidColorAndExpectedRendering(): array
'rgb(0 60% 0%)',
'rgb(0 60% 0%)',
],
//*/
'modern rgb with percentage components' => [
'rgb(0% 60% 0%)',
'rgb(0%,60%,0%)',
Expand All @@ -131,7 +128,6 @@ public static function provideValidColorAndExpectedRendering(): array
'rgb(0 119 0 / 50%)',
'rgba(0,119,0,50%)',
],
/*
'modern rgba with percentage R' => [
'rgb(0% 119 0 / 0.5)',
'rgba(0% 119 0/.5)',
Expand All @@ -144,7 +140,6 @@ public static function provideValidColorAndExpectedRendering(): array
'rgb(0 119 0% / 0.5)',
'rgba(0 119 0%/.5)',
],
//*/
'modern rgba with percentage RGB' => [
'rgb(0% 60% 0% / 0.5)',
'rgba(0%,60%,0%,.5)',
Expand Down