diff --git a/CHANGELOG.md b/CHANGELOG.md index d75a0e76..1ea166fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,12 @@ Please also have a look at our ### Documentation +## 9.2.0: + +### Added + +- Add use modern CSS color syntax option to `OutputFormat` (#1442) + ## 9.1.0: Add support for PHP 8.5 ### Added diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 5c493865..52abc9e8 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -20,6 +20,13 @@ final class OutputFormat */ private $usesRgbHashNotation = true; + /** + * Output RGB colors in CSS 4 notation if possible + * + * @var bool + */ + private $usesModernColorSyntax = false; + /** * Declaration format * @@ -220,6 +227,24 @@ public function setRGBHashNotation(bool $usesRgbHashNotation): self return $this; } + /** + * @internal + */ + public function usesModernColorSyntax(): bool + { + return $this->usesModernColorSyntax; + } + + /** + * @return $this fluent interface + */ + public function setUseModernColorSyntax(bool $usesModernColorSyntax): self + { + $this->usesModernColorSyntax = $usesModernColorSyntax; + + return $this; + } + /** * @internal */ diff --git a/src/Value/Color.php b/src/Value/Color.php index 63bccab8..27d78bdb 100644 --- a/src/Value/Color.php +++ b/src/Value/Color.php @@ -232,7 +232,7 @@ public function render(OutputFormat $outputFormat): string return $this->renderAsHex(); } - if ($this->shouldRenderInModernSyntax()) { + if ($this->shouldRenderInModernSyntax($outputFormat)) { return $this->renderInModernSyntax($outputFormat); } @@ -309,9 +309,9 @@ private function renderAsHex(): string * 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 + private function shouldRenderInModernSyntax(OutputFormat $outputFormat): bool { - if ($this->hasNoneAsComponentValue()) { + if ($this->hasNoneAsComponentValue() || $outputFormat->usesModernColorSyntax()) { return true; } @@ -385,6 +385,12 @@ private function renderInModernSyntax(OutputFormat $outputFormat): string $arguments = $formatter->implode($separator, [$arguments, $alpha]); } - return $this->getName() . '(' . $arguments . ')'; + $name = $this->getName(); + + if ($outputFormat->usesModernColorSyntax()) { + $name = str_replace('a', '', $name); + } + + return $name . '(' . $arguments . ')'; } } diff --git a/tests/Unit/Value/ColorTest.php b/tests/Unit/Value/ColorTest.php index aaa25755..b455c890 100644 --- a/tests/Unit/Value/ColorTest.php +++ b/tests/Unit/Value/ColorTest.php @@ -30,310 +30,387 @@ public static function provideValidColorAndExpectedRendering(): array '3-digit hex color' => [ '#070', '#070', + '#070', ], '6-digit hex color that can be represented as 3-digit' => [ '#007700', '#070', + '#070', ], '6-digit hex color that cannot be represented as 3-digit' => [ '#007600', '#007600', + '#007600', ], '4-digit hex color (with alpha)' => [ '#0707', 'rgba(0,119,0,.47)', + 'rgb(0 119 0/.47)', ], '8-digit hex color (with alpha)' => [ '#0077007F', 'rgba(0,119,0,.5)', + 'rgb(0 119 0/.5)', ], 'legacy rgb that can be represented as 3-digit hex' => [ 'rgb(0, 119, 0)', '#070', + '#070', ], 'legacy rgb that cannot be represented as 3-digit hex' => [ 'rgb(0, 118, 0)', '#007600', + '#007600', ], 'legacy rgb with percentage components' => [ 'rgb(0%, 60%, 0%)', 'rgb(0%,60%,0%)', + 'rgb(0% 60% 0%)', ], 'legacy rgba with fractional alpha' => [ 'rgba(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', + 'rgb(0 119 0/.5)', ], 'legacy rgba with percentage alpha' => [ 'rgba(0, 119, 0, 50%)', 'rgba(0,119,0,50%)', + 'rgb(0 119 0/50%)', ], 'legacy rgba with percentage components and fractional alpha' => [ 'rgba(0%, 60%, 0%, 0.5)', 'rgba(0%,60%,0%,.5)', + 'rgb(0% 60% 0%/.5)', ], 'legacy rgba with percentage components and percentage alpha' => [ 'rgba(0%, 60%, 0%, 50%)', 'rgba(0%,60%,0%,50%)', + 'rgb(0% 60% 0%/50%)', ], 'legacy rgb as rgba' => [ 'rgba(0, 119, 0)', '#070', + '#070', ], 'legacy rgba as rgb' => [ 'rgb(0, 119, 0, 0.5)', 'rgba(0,119,0,.5)', + 'rgb(0 119 0/.5)', ], 'modern rgb' => [ 'rgb(0 119 0)', '#070', + '#070', ], 'modern rgb with percentage R' => [ 'rgb(0% 119 0)', 'rgb(0% 119 0)', + 'rgb(0% 119 0)', ], 'modern rgb with percentage G' => [ 'rgb(0 60% 0)', 'rgb(0 60% 0)', + 'rgb(0 60% 0)', ], 'modern rgb with percentage B' => [ 'rgb(0 119 0%)', 'rgb(0 119 0%)', + 'rgb(0 119 0%)', ], 'modern rgb with percentage R&G' => [ 'rgb(0% 60% 0)', 'rgb(0% 60% 0)', + 'rgb(0% 60% 0)', ], 'modern rgb with percentage R&B' => [ 'rgb(0% 119 0%)', 'rgb(0% 119 0%)', + 'rgb(0% 119 0%)', ], 'modern rgb with percentage G&B' => [ 'rgb(0 60% 0%)', 'rgb(0 60% 0%)', + 'rgb(0 60% 0%)', ], 'modern rgb with percentage components' => [ 'rgb(0% 60% 0%)', 'rgb(0%,60%,0%)', + 'rgb(0% 60% 0%)', ], 'modern rgb with none as red' => [ 'rgb(none 119 0)', 'rgb(none 119 0)', + 'rgb(none 119 0)', ], 'modern rgb with none as green' => [ 'rgb(0 none 0)', 'rgb(0 none 0)', + 'rgb(0 none 0)', ], 'modern rgb with none as blue' => [ 'rgb(0 119 none)', 'rgb(0 119 none)', + 'rgb(0 119 none)', ], 'modern rgba with fractional alpha' => [ 'rgb(0 119 0 / 0.5)', 'rgba(0,119,0,.5)', + 'rgb(0 119 0/.5)', ], 'modern rgba with percentage alpha' => [ - 'rgb(0 119 0 / 50%)', + 'rgb(0 119 0/50%)', 'rgba(0,119,0,50%)', + 'rgb(0 119 0/50%)', ], 'modern rgba with percentage R' => [ 'rgb(0% 119 0 / 0.5)', 'rgba(0% 119 0/.5)', + 'rgb(0% 119 0/.5)', ], 'modern rgba with percentage G' => [ 'rgb(0 60% 0 / 0.5)', 'rgba(0 60% 0/.5)', + 'rgb(0 60% 0/.5)', ], 'modern rgba with percentage B' => [ 'rgb(0 119 0% / 0.5)', 'rgba(0 119 0%/.5)', + 'rgb(0 119 0%/.5)', ], 'modern rgba with percentage RGB' => [ 'rgb(0% 60% 0% / 0.5)', 'rgba(0%,60%,0%,.5)', + 'rgb(0% 60% 0%/.5)', ], 'modern rgba with percentage components' => [ 'rgb(0% 60% 0% / 50%)', 'rgba(0%,60%,0%,50%)', + 'rgb(0% 60% 0%/50%)', ], 'modern rgba with none as alpha' => [ 'rgb(0 119 0 / none)', 'rgba(0 119 0/none)', + 'rgb(0 119 0/none)', ], 'legacy rgb with var for R' => [ 'rgb(var(--r), 119, 0)', '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)', + 'rgb(0,var(--g),0)', ], 'legacy rgb with var for B' => [ 'rgb(0, 119, var(--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)', + 'rgb(var(--rg),0)', ], 'legacy rgb with var for GB' => [ 'rgb(0, var(--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)', + '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)', + '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)', + 'rgb(0,119,var(--b),.5)', ], 'legacy rgba with var for A' => [ 'rgba(0, 119, 0, var(--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)', + 'rgba(var(--rg),0,.5)', ], 'legacy rgba with var for GB' => [ 'rgba(0, var(--gb), 0.5)', 'rgba(0,var(--gb),.5)', + 'rgba(0,var(--gb),.5)', ], 'legacy rgba with var for BA' => [ 'rgba(0, 119, var(--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)', + 'rgba(var(--rgb),.5)', ], 'legacy rgba with var for GBA' => [ 'rgba(0, var(--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)', + 'rgb(var(--r) 119 0)', ], 'modern rgb with var for G' => [ 'rgb(0 var(--g) 0)', '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))', + 'rgb(0 119 var(--b))', ], 'modern rgb with var for RG' => [ 'rgb(var(--rg) 0)', 'rgb(var(--rg),0)', + 'rgb(var(--rg) 0)', ], 'modern rgb with var for GB' => [ 'rgb(0 var(--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)', + 'rgb(var(--r) 119 0/.5)', ], 'modern rgba with var for G' => [ 'rgba(0 var(--g) 0 / 0.5)', 'rgba(0,var(--g),0,.5)', + 'rgb(0 var(--g) 0/.5)', ], 'modern rgba with var for B' => [ 'rgba(0 119 var(--b) / 0.5)', 'rgba(0,119,var(--b),.5)', + 'rgb(0 119 var(--b)/.5)', ], 'modern rgba with var for A' => [ 'rgba(0 119 0 / var(--a))', 'rgba(0,119,0,var(--a))', + 'rgb(0 119 0/var(--a))', ], 'modern rgba with var for RG' => [ 'rgba(var(--rg) 0 / 0.5)', 'rgba(var(--rg),0,.5)', + 'rgb(var(--rg) 0/.5)', ], 'modern rgba with var for GB' => [ 'rgba(0 var(--gb) / 0.5)', 'rgba(0,var(--gb),.5)', + 'rgb(0 var(--gb)/.5)', ], 'modern rgba with var for BA' => [ 'rgba(0 119 var(--ba))', 'rgba(0,119,var(--ba))', + 'rgb(0 119 var(--ba))', ], 'modern rgba with var for RGB' => [ 'rgba(var(--rgb) / 0.5)', 'rgba(var(--rgb),.5)', + 'rgb(var(--rgb) /.5)', ], 'modern rgba with var for GBA' => [ 'rgba(0 var(--gba))', 'rgba(0,var(--gba))', + 'rgb(0 var(--gba))', ], 'rgba with var for RGBA' => [ 'rgba(var(--rgba))', 'rgba(var(--rgba))', + 'rgb(var(--rgba))', ], 'legacy hsl' => [ 'hsl(120, 100%, 25%)', 'hsl(120,100%,25%)', + 'hsl(120 100% 25%)', ], 'legacy hsl with deg' => [ 'hsl(120deg, 100%, 25%)', 'hsl(120deg,100%,25%)', + 'hsl(120deg 100% 25%)', ], 'legacy hsl with grad' => [ 'hsl(133grad, 100%, 25%)', 'hsl(133grad,100%,25%)', + 'hsl(133grad 100% 25%)', ], 'legacy hsl with rad' => [ 'hsl(2.094rad, 100%, 25%)', 'hsl(2.094rad,100%,25%)', + 'hsl(2.094rad 100% 25%)', ], 'legacy hsl with turn' => [ 'hsl(0.333turn, 100%, 25%)', 'hsl(.333turn,100%,25%)', + 'hsl(.333turn 100% 25%)', ], 'legacy hsla with fractional alpha' => [ 'hsla(120, 100%, 25%, 0.5)', 'hsla(120,100%,25%,.5)', + 'hsl(120 100% 25%/.5)', ], 'legacy hsla with percentage alpha' => [ 'hsla(120, 100%, 25%, 50%)', 'hsla(120,100%,25%,50%)', + 'hsl(120 100% 25%/50%)', ], 'legacy hsl as hsla' => [ 'hsla(120, 100%, 25%)', 'hsl(120,100%,25%)', + 'hsl(120 100% 25%)', ], 'legacy hsla as hsl' => [ 'hsl(120, 100%, 25%, 0.5)', 'hsla(120,100%,25%,.5)', + 'hsl(120 100% 25%/.5)', ], 'modern hsl' => [ 'hsl(120 100% 25%)', 'hsl(120,100%,25%)', + 'hsl(120 100% 25%)', ], 'modern hsl with none as hue' => [ 'hsl(none 100% 25%)', 'hsl(none 100% 25%)', + 'hsl(none 100% 25%)', ], 'modern hsl with none as saturation' => [ 'hsl(120 none 25%)', 'hsl(120 none 25%)', + 'hsl(120 none 25%)', ], 'modern hsl with none as lightness' => [ 'hsl(120 100% none)', 'hsl(120 100% none)', + 'hsl(120 100% none)', ], 'modern hsla' => [ 'hsl(120 100% 25% / 0.5)', 'hsla(120,100%,25%,.5)', + 'hsl(120 100% 25%/.5)', ], 'modern hsla with none as alpha' => [ 'hsl(120 100% 25% / none)', 'hsla(120 100% 25%/none)', + 'hsl(120 100% 25%/none)', ], ]; } @@ -343,13 +420,16 @@ public static function provideValidColorAndExpectedRendering(): array * * @dataProvider provideValidColorAndExpectedRendering */ - public function parsesAndRendersValidColor(string $color, string $expectedRendering): void + public function parsesAndRendersValidColor(string $color, string $expectedRendering, string $expectedModernRendering): void { $subject = Color::parse(new ParserState($color, Settings::create())); + $outputFormat = OutputFormat::create(); + + self::assertSame($expectedRendering, $subject->render($outputFormat)); - $renderedResult = $subject->render(OutputFormat::create()); + $outputFormat->setUseModernColorSyntax(true); - self::assertSame($expectedRendering, $renderedResult); + self::assertSame($expectedModernRendering, $subject->render($outputFormat)); } /**