-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[vector_graphics_compiler] Fix rgb/rgba color parsing to support modern CSS syntax #10538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request refactors the color parsing logic for rgb() and rgba() to support modern CSS syntax, including space-separated values, percentage-based values, and the slash separator for the alpha channel. The implementation is consolidated, and the old rgb parsing logic is removed. Comprehensive tests have been added to cover the new parsing capabilities.
The changes are well-structured and the added tests are thorough. I have one suggestion to improve the strictness of the parsing to better align with CSS standards.
| .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); | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of the custom parseDouble from numbers.dart is not ideal for parsing color components. This function is designed to strip units like px or em, but according to the CSS specification, color components in rgb() or rgba() functions should not have units. This could lead to incorrect parsing of invalid color strings (e.g., rgb(50px, 0, 0) would be parsed as rgb(50, 0, 0)).
To ensure stricter adherence to the CSS standard, you should use double.parse from dart:core instead, which will correctly throw an error for values with units.
| .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); | |
| }) | |
| .map((indexedColor) { | |
| var (index, rawColor) = indexedColor; | |
| if (rawColor.endsWith('%')) { | |
| rawColor = rawColor.substring(0, rawColor.length - 1); | |
| return (double.parse(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 = double.parse(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); | |
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parseDouble usage was retained from the existing rbg parsing implementation.
| final List<int> rgba = colorString | ||
| .substring(colorString.indexOf('(') + 1, colorString.indexOf(')')) | ||
| .split(',') | ||
| .split(RegExp(r'[,/\s]')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
valid delimiters are: ,, (empty space) and / (used to delimit the opacity)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a correct reframing of the spec you linked to in the comment introducing this code. From what I see there, the only valid delimiters are either:
- all spaces, except for alpha which uses /, or
- all commas (legacy)
For example, rgb(255 / 255 255, 1.0) is not valid, but will be accepted by this code.
Thanks for the contribution! Once you've completed the checklist, please mark the PR as ready for review.
Please make sure to follow the linked style guide. |
…rn CSS syntax - Consolidate rgb() and rgba() parsing into single implementation - Add support for space-separated color values - Add support for percentage-based RGB values - Add support for slash (/) separator before alpha channel - Handle both comma and space delimiters - Add comprehensive test coverage for various color formats
057153b to
16bf81a
Compare
|
@stuartmorgan-g Completed the checklist. This is my first PR to any flutter/dart repo. If anything is not exactly correct, please let me know and I'll address. |
|
Ping @stuartmorgan-g . Any chance we can get this fix reviewed/merged? |
|
I can do an initial review, but this will need a review from the engine team. Review capacity tends to be low in December, which is probably why this hasn't been picked up yet. |
| // 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')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rgba starts with rgb, so the first check here is duplicative.
| final List<int> rgba = colorString | ||
| .substring(colorString.indexOf('(') + 1, colorString.indexOf(')')) | ||
| .split(',') | ||
| .split(RegExp(r'[,/\s]')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a correct reframing of the spec you linked to in the comment introducing this code. From what I see there, the only valid delimiters are either:
- all spaces, except for alpha which uses /, or
- all commas (legacy)
For example, rgb(255 / 255 255, 1.0) is not valid, but will be accepted by this code.
| .split(',') | ||
| .split(RegExp(r'[,/\s]')) | ||
| .map((String rawColor) => rawColor.trim()) | ||
| .where((e) => e.isNotEmpty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be trying to correct for overly aggressive splitting (e.g., ", " as a delimiter), and will silently accept bad input in surprising ways.
For instance, this typo where someone using legacy syntax and forgot the green value will be accepted as an unexpected color instead of erroring out: rgba(255, , 255, 1.0)
| const Color.fromARGB(0xFF, 0xAB, 0xCD, 0xEF), | ||
| ); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test suite should include various invalid variants, and ensure that they are handled as expected (I'm not familiar with the context of this code, but I'm assuming since the return type is nullable invalid values should likely return null).
| ## NEXT | ||
| ## 1.1.20 | ||
|
|
||
| * Fix rgb and rgba color parsing to handle modern CSS syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to follow the linked style guide; please see the notes about both verb form and punctuation.
This PR solves flutter/flutter#179261
Pre-Review Checklist
[shared_preferences]pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under1.CHANGELOG.mdto add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under1.///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2 ↩3