From 540b315935c334f0f4d1e6698fcec2173d4073ba Mon Sep 17 00:00:00 2001 From: JakeQZ Date: Sat, 25 Jan 2025 08:50:44 +0000 Subject: [PATCH] [BUGFIX] Don't render `rgb` colors with `%` values as hex (#803) The only percentage values that could be reliably converted to hex notation are 0%, 20%, 40%, etc. It's beyond the scope of this library to do that. Also add additional tests to confirm parsing of percentage values. Some of these are commented out, because the input data would result in rendering in an invalid format. (The "legacy" syntax does not allow a mixture of `percentage`s and `number`s, so it would be necessary to implement rendering in the "modern" syntax to resolve those cases, which is beyond the scope of this PR.) Co-authored-by: Jake Hotson --- CHANGELOG.md | 1 + src/Value/Color.php | 21 +++++++++- tests/Unit/Value/ColorTest.php | 71 +++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92ad218..3cae9f51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Please also have a look at our ### Fixed +- Don't render `rgb` colors with percentage values using hex notation (#803) - Parse `@font-face` `src` property as comma-delimited list (#790) - Fix type errors in PHP strict mode (#664) - Fix undefined local variable in `CalcFunction::parse()` (#593) diff --git a/src/Value/Color.php b/src/Value/Color.php index 85890393..07f11c34 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -225,7 +225,11 @@ public function __toString(): string public function render(OutputFormat $outputFormat): string { // Shorthand RGB color values - if ($outputFormat->getRGBHashNotation() && \implode('', \array_keys($this->aComponents)) === 'rgb') { + if ( + $outputFormat->getRGBHashNotation() + && \implode('', \array_keys($this->aComponents)) === 'rgb' + && $this->allComponentsAreNumbers() + ) { $result = \sprintf( '%02x%02x%02x', $this->aComponents['r']->getSize(), @@ -237,4 +241,19 @@ public function render(OutputFormat $outputFormat): string } return parent::render($outputFormat); } + + /** + * Test whether all color components are absolute numbers (CSS type `number`), not percentages or anything else. + * If any component is not an instance of `Size`, the method will also return `false`. + */ + private function allComponentsAreNumbers(): bool + { + foreach ($this->aComponents as $component) { + if (!$component instanceof Size || $component->getUnit() !== null) { + return false; + } + } + + return true; + } } diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index 822ba5be..2b533a7f 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -54,6 +54,10 @@ public static function provideValidColorAndExpectedRendering(): array 'rgb(0, 118, 0)', '#007600', ], + 'legacy rgb with percentage components' => [ + 'rgb(0%, 60%, 0%)', + 'rgb(0%,60%,0%)', + ], 'legacy rgba with fractional alpha' => [ 'rgba(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', @@ -62,6 +66,14 @@ public static function provideValidColorAndExpectedRendering(): array 'rgba(0, 119, 0, 50%)', 'rgba(0,119,0,50%)', ], + 'legacy rgba with percentage components and fractional alpha' => [ + 'rgba(0%, 60%, 0%, 0.5)', + 'rgba(0%,60%,0%,.5)', + ], + 'legacy rgba with percentage components and percentage alpha' => [ + 'rgba(0%, 60%, 0%, 50%)', + 'rgba(0%,60%,0%,50%)', + ], 'legacy rgb as rgba' => [ 'rgba(0, 119, 0)', '#070', @@ -74,16 +86,73 @@ 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)', + ], + 'modern rgb with percentage G' => [ + 'rgb(0 60% 0)', + 'rgb(0 60% 0)', + ], + 'modern rgb with percentage B' => [ + 'rgb(0 119 0%)', + 'rgb(0 119 0%)', + ], + 'modern rgb with percentage R&G' => [ + 'rgb(0% 60% 0)', + 'rgb(0% 60% 0)', + ], + 'modern rgb with percentage R&B' => [ + 'rgb(0% 119 0%)', + 'rgb(0% 119 0%)', + ], + 'modern rgb with percentage G&B' => [ + 'rgb(0 60% 0%)', + 'rgb(0 60% 0%)', + ], + //*/ + 'modern rgb with percentage components' => [ + 'rgb(0% 60% 0%)', + 'rgb(0%,60%,0%)', + ], /* 'modern rgb with none' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', ], //*/ - 'modern rgba' => [ + 'modern rgba with fractional alpha' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', ], + 'modern rgba with percentage alpha' => [ + '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)', + ], + 'modern rgba with percentage G' => [ + 'rgb(0 60% 0 / 0.5)', + 'rgba(0 60% 0/.5)', + ], + 'modern rgba with percentage B' => [ + '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)', + ], + 'modern rgba with percentage components' => [ + 'rgb(0% 60% 0% / 50%)', + 'rgba(0%,60%,0%,50%)', + ], /* 'modern rgba with none as alpha' => [ 'rgb(0 119 0 / none)',