diff --git a/packages/vector_graphics_compiler/CHANGELOG.md b/packages/vector_graphics_compiler/CHANGELOG.md index b43c464030a..c0106e9eb8c 100644 --- a/packages/vector_graphics_compiler/CHANGELOG.md +++ b/packages/vector_graphics_compiler/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 1.1.20 +* Fix rgb and rgba color parsing to handle modern CSS syntax * Updates minimum supported SDK version to Flutter 3.35/Dart 3.9. ## 1.1.19 diff --git a/packages/vector_graphics_compiler/lib/src/svg/parser.dart b/packages/vector_graphics_compiler/lib/src/svg/parser.dart index b8f67adc89c..3825aa37245 100644 --- a/packages/vector_graphics_compiler/lib/src/svg/parser.dart +++ b/packages/vector_graphics_compiler/lib/src/svg/parser.dart @@ -1372,21 +1372,40 @@ class SvgParser { } } - // handle rgba() colors e.g. rgba(255, 255, 255, 1.0) - if (colorString.toLowerCase().startsWith('rgba')) { - final List rawColorElements = colorString + // handle rgba() colors e.g. rgb(255, 255, 255) and rgba(255, 255, 255, 1.0) + // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/rgb + if (colorString.toLowerCase().startsWith('rgba') || + colorString.toLowerCase().startsWith('rgb')) { + final List rgba = colorString .substring(colorString.indexOf('(') + 1, colorString.indexOf(')')) - .split(',') + .split(RegExp(r'[,/\s]')) .map((String rawColor) => rawColor.trim()) + .where((e) => e.isNotEmpty) + .indexed + .map((indexedColor) { + var (index, rawColor) = indexedColor; + if (rawColor.endsWith('%')) { + rawColor = rawColor.substring(0, rawColor.length - 1); + return (parseDouble(rawColor)! * 2.55).round(); + } + if (index == 3) { + // if alpha is not percentage, it means it's a double between 0 and 1 + final double opacity = parseDouble(rawColor)!; + if (opacity < 0 || opacity > 1) { + throw StateError('Invalid "opacity": $opacity'); + } + return (opacity * 255).round(); + } + // If rgb is not percentage, it means it's an integer between 0 and 255 + return int.parse(rawColor); + }) .toList(); - final double opacity = parseDouble(rawColorElements.removeLast())!; - - final List rgb = rawColorElements - .map((String rawColor) => int.parse(rawColor)) - .toList(); + if (rgba.length == 3) { + rgba.add(255); + } - return Color.fromRGBO(rgb[0], rgb[1], rgb[2], opacity); + return Color.fromARGB(rgba[3], rgba[0], rgba[1], rgba[2]); } // Conversion code from: https://github.com/MichaelFenwick/Color, thanks :) @@ -1456,26 +1475,6 @@ class SvgParser { ); } - // handle rgb() colors e.g. rgb(255, 255, 255) - if (colorString.toLowerCase().startsWith('rgb')) { - final List rgb = colorString - .substring(colorString.indexOf('(') + 1, colorString.indexOf(')')) - .split(',') - .map((String rawColor) { - rawColor = rawColor.trim(); - if (rawColor.endsWith('%')) { - rawColor = rawColor.substring(0, rawColor.length - 1); - return (parseDouble(rawColor)! * 2.55).round(); - } - return int.parse(rawColor); - }) - .toList(); - - // rgba() isn't really in the spec, but Firefox supported it at one point so why not. - final int a = rgb.length > 3 ? rgb[3] : 255; - return Color.fromARGB(a, rgb[0], rgb[1], rgb[2]); - } - // handle named colors ('red', 'green', etc.). final Color? namedColor = namedColors[colorString]; if (namedColor != null) { diff --git a/packages/vector_graphics_compiler/pubspec.yaml b/packages/vector_graphics_compiler/pubspec.yaml index 3e6911fc9a3..43d627e60eb 100644 --- a/packages/vector_graphics_compiler/pubspec.yaml +++ b/packages/vector_graphics_compiler/pubspec.yaml @@ -2,7 +2,7 @@ name: vector_graphics_compiler 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.1.19 +version: 1.1.20 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 6062682368a..7f288fe1964 100644 --- a/packages/vector_graphics_compiler/test/parsers_test.dart +++ b/packages/vector_graphics_compiler/test/parsers_test.dart @@ -21,11 +21,71 @@ void main() { parser.parseColor('#ABCDEF', attributeName: 'foo', id: null), const Color.fromARGB(255, 0xAB, 0xCD, 0xEF), ); - // RGBA in svg/css, ARGB in this library. - expect( - parser.parseColor('#ABCDEF88', attributeName: 'foo', id: null), - const Color.fromARGB(0x88, 0xAB, 0xCD, 0xEF), - ); + }); + + group('Colors - svg/css', () { + final parser = SvgParser('', const SvgTheme(), 'test_key', true, null); + + group('with no opacity', () { + const rgbContentVariations = [ + '171, 205, 239', + '171,205,239', + '171 205 239', + '171 205 239', + '171 205 239', + '171 205 239', + '171 205 239', + '67% 80.5% 93.7%', + ]; + + final List rgbaVariations = [ + '#ABCDEF', + ...rgbContentVariations.map((rgb) => 'rgba($rgb)'), + ...rgbContentVariations.map((rgb) => 'rgb($rgb)'), + ]; + + for (final rgba in rgbaVariations) { + test(rgba, () { + expect( + parser.parseColor(rgba, attributeName: 'foo', id: null), + const Color.fromARGB(0xFF, 0xAB, 0xCD, 0xEF), + ); + }); + } + }); + group('with opacity', () { + const rgbContentVariations = [ + '171, 205, 239, 0.53', + '171,205,239,0.53', + '171 205 239 0.53', + '171 205 239 0.53', + '171 205 239 / 53%', + '171 205 239 / 0.53', + '171 205 239 / 53%', + '67% 80.5% 93.7% / 53%', + ]; + final List rgbaVariations = [ + '#ABCDEF87', + ...rgbContentVariations.map((rgb) => 'rgba($rgb)'), + ...rgbContentVariations.map((rgb) => 'rgb($rgb)'), + ]; + for (final rgba in rgbaVariations) { + test(rgba, () { + expect( + parser.parseColor(rgba, attributeName: 'foo', id: null), + const Color.fromARGB(0x87, 0xAB, 0xCD, 0xEF), + ); + }); + } + }); + + test('rgba with no opacity', () { + // RGBA is now an alias for RGB, so opacity can be missing entirely + expect( + parser.parseColor('rgba(171 205 239)', attributeName: 'foo', id: null), + const Color.fromARGB(0xFF, 0xAB, 0xCD, 0xEF), + ); + }); }); test('Colors - mapped', () async {