[vector_graphics_compiler] Fix HSL color parsing for decimal percentage components (#11619)
## Description
Fixes a bug in the HSL color parser where decimal percentage components
(e.g. `76.2745098039%`) were incorrectly multiplied by `2.55` — the
conversion factor used for **rgb** percentages (percent → 0–255). For
**hsl**, saturation and lightness must stay in the 0–100 range (later
divided by 100) so the multiplication produced wildly wrong values
(e.g. `76.27 → 194`, then `194 / 100 = 1.94`).
The same branch also mis-handled the `hsla` alpha component: a unitless
decimal like `0.5` should be converted to 0–255 via `× 255`, not `× 2.55`.
### Root cause
`parser.dart` checked `rawColor.contains('.')` and unconditionally
applied `* 2.55`, regardless of whether the current function was
`rgb()`/`rgba()` (correct) or `hsl()`/`hsla()` (incorrect).
### Fix
Track whether each token had a `%` suffix before stripping it.
For HSL percentage components, return the raw `double` (0–100 range).
For unitless alpha decimals (0–1 range), multiply by 255.
### Reproduction (from issue #185833)
```
hsl(270, 100%, 76.2745098039%)
```
| Before fix | After fix |
|---|---|
| `#efdeff` ❌ | `#c286ff` ✓ |
## Tests
Four new tests added to `parsers_test.dart`:
- `hsl` with integer percentages (regression)
- `hsl` with decimal lightness percentage (the reported bug)
- `hsla` with integer percentages + decimal alpha (regression)
- `hsla` with decimal lightness + decimal alpha (combined case)
## Related Issues
Fixes https://github.com/flutter/flutter/issues/185833diff --git a/packages/vector_graphics_compiler/CHANGELOG.md b/packages/vector_graphics_compiler/CHANGELOG.md
index 1bc29a6..13682cb 100644
--- a/packages/vector_graphics_compiler/CHANGELOG.md
+++ b/packages/vector_graphics_compiler/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.2.1
+
+* Fixes HSL/HSLA color parsing for decimal percentage components (e.g. `hsl(270, 100%, 76.27%)`).
+
## 1.2.0
* Adds support for percentage units in SVG shape attributes (rect, circle, ellipse, line).
diff --git a/packages/vector_graphics_compiler/lib/src/svg/parser.dart b/packages/vector_graphics_compiler/lib/src/svg/parser.dart
index 6209ae2..52eb386 100644
--- a/packages/vector_graphics_compiler/lib/src/svg/parser.dart
+++ b/packages/vector_graphics_compiler/lib/src/svg/parser.dart
@@ -1434,27 +1434,29 @@
// Conversion code from: https://github.com/MichaelFenwick/Color, thanks :)
if (colorString.toLowerCase().startsWith('hsl')) {
- final List<int> values = colorString
+ final List<String> values = colorString
.substring(colorString.indexOf('(') + 1, colorString.indexOf(')'))
.split(',')
- .map((String rawColor) {
- rawColor = rawColor.trim();
-
- if (rawColor.endsWith('%')) {
- rawColor = rawColor.substring(0, rawColor.length - 1);
- }
-
- if (rawColor.contains('.')) {
- return (parseDouble(rawColor)! * 2.55).round();
- }
-
- return int.parse(rawColor);
- })
+ .map((String rawColor) => rawColor.trim())
.toList();
- final double hue = values[0] / 360 % 1;
- final double saturation = values[1] / 100;
- final double luminance = values[2] / 100;
- final int alpha = values.length > 3 ? values[3] : 255;
+ double parseHslValue(String rawColor) {
+ if (rawColor.endsWith('%')) {
+ rawColor = rawColor.substring(0, rawColor.length - 1);
+ }
+ return parseDouble(rawColor)!;
+ }
+
+ int parseHslAlpha(String rawAlpha) {
+ if (rawAlpha.endsWith('%')) {
+ return (parseHslValue(rawAlpha).clamp(0, 100) * 2.55).round();
+ }
+ return (parseDouble(rawAlpha)!.clamp(0, 1) * 255).round();
+ }
+
+ final double hue = parseHslValue(values[0]) / 360 % 1;
+ final double saturation = parseHslValue(values[1]) / 100;
+ final double luminance = parseHslValue(values[2]) / 100;
+ final int alpha = values.length > 3 ? parseHslAlpha(values[3]) : 255;
var rgb = <double>[0, 0, 0];
if (hue < 1 / 6) {
diff --git a/packages/vector_graphics_compiler/pubspec.yaml b/packages/vector_graphics_compiler/pubspec.yaml
index f231c93..47e7b8c 100644
--- a/packages/vector_graphics_compiler/pubspec.yaml
+++ b/packages/vector_graphics_compiler/pubspec.yaml
@@ -2,7 +2,7 @@
description: A compiler to convert SVGs to the binary format used by `package:vector_graphics`.
repository: https://github.com/flutter/packages/tree/main/packages/vector_graphics_compiler
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+vector_graphics%22
-version: 1.2.0
+version: 1.2.1
executables:
vector_graphics_compiler:
diff --git a/packages/vector_graphics_compiler/test/parsers_test.dart b/packages/vector_graphics_compiler/test/parsers_test.dart
index 1750ae4..84c4082 100644
--- a/packages/vector_graphics_compiler/test/parsers_test.dart
+++ b/packages/vector_graphics_compiler/test/parsers_test.dart
@@ -219,6 +219,65 @@
});
});
+ group('Colors - hsl/hsla', () {
+ final parser = SvgParser('', const SvgTheme(), 'test_key', true, null);
+
+ test('hsl with integer percentages', () {
+ expect(
+ parser.parseColor(
+ 'hsl(270, 100%, 76%)',
+ attributeName: 'fill',
+ id: null,
+ ),
+ const Color.fromARGB(255, 194, 133, 255),
+ );
+ });
+
+ test('hsl with decimal lightness percentage (issue #185833)', () {
+ expect(
+ parser.parseColor(
+ 'hsl(270, 100%, 76.2745098039%)',
+ attributeName: 'fill',
+ id: null,
+ ),
+ const Color.fromARGB(255, 194, 134, 255),
+ );
+ });
+
+ test('hsla with integer percentages and decimal alpha', () {
+ expect(
+ parser.parseColor(
+ 'hsla(270, 100%, 76%, 0.5)',
+ attributeName: 'fill',
+ id: null,
+ ),
+ const Color.fromARGB(128, 194, 133, 255),
+ );
+ });
+
+ test('hsla with integer percentages and alpha 1', () {
+ expect(
+ parser.parseColor(
+ 'hsla(270, 100%, 76%, 1)',
+ attributeName: 'fill',
+ id: null,
+ ),
+ const Color.fromARGB(255, 194, 133, 255),
+ );
+ });
+
+ test('hsla with decimal lightness percentage and decimal alpha', () {
+ expect(
+ parser.parseColor(
+ 'hsla(270, 100%, 76.2745098039%, 0.5)',
+ attributeName: 'fill',
+ id: null,
+ ),
+ const Color.fromARGB(128, 194, 134, 255),
+ );
+ });
+ });
+
test('Colors - mapped', () async {
final mapper = TestColorMapper();
final parser =