chore: support complete CSS color codes

Signed-off-by: The one with the braid <the-one@with-the-braid.cf>
This commit is contained in:
The one with the braid 2023-09-04 12:23:44 +02:00
parent 92319c20d2
commit a41784b77e
2 changed files with 362 additions and 25 deletions

View file

@ -1,34 +1,127 @@
/// Creates in [int] with a parsed [Color] value from RGB(A) color value. /// Creates in [int] with a parsed [Color] value from a CSS color String.
/// ///
/// Credits : https://pub.dev/packages/from_css_color - ported since dependency /// Credits : https://pub.dev/packages/from_css_color - ported since dependency
/// on `dart:ui`, here reimplemented without but with raw int. /// on `dart:ui`, here reimplemented without but with raw int.
int parseRgbToInt(String color) { int fromCssColor(String color) {
List<String> channels = _parseChannels(color)!; color = color.trim();
int result = 0xFF000000;
int shift = 16;
if (channels.length == 4) { switch (_recognizeCssColorFormat(color)) {
result = (_opacityChannelToHex(channels.removeLast()) << 24) & result; case ColorFormat.hex:
} else if (channels.length != 3) { return _hexToColor(color);
case ColorFormat.rgb:
case ColorFormat.rgba:
return _rgbToColor(color);
case ColorFormat.keyword:
return colorKeywords[color]!;
default:
return _hslToColor(color);
}
}
/// Translation options from [Color] to a string format recognizable according to https://drafts.csswg.org/css-color-3 and forthcoming drafts.
enum CssColorString {
/// Hex format that truncates to a short form (3-4 digits) if possible and contains alpha digits if color is not fully opaque.
hex,
/// RGB/RGBA format that contains alpha value if color is not fully opaque.
rgb,
}
/// Color formats available to construct [Color] instance from.
enum ColorFormat {
hex,
rgb,
rgba,
hsl,
hsla,
keyword,
}
ColorFormat _recognizeCssColorFormat(String color) {
if (color.startsWith('#')) {
return ColorFormat.hex;
} else if (color.startsWith('rgba')) {
return ColorFormat.rgba;
} else if (color.startsWith('rgb')) {
return ColorFormat.rgb;
} else if (color.startsWith('hsla')) {
return ColorFormat.hsla;
} else if (color.startsWith('hsl')) {
return ColorFormat.hsl;
} else if (colorKeywords.containsKey(color)) {
return ColorFormat.keyword;
}
throw FormatException('Unable to recognize this CSS color format.', color);
}
/// Check correctness of color string according to https://drafts.csswg.org/css-color-3
bool isCssColor(String color) {
color = color.trim();
final chNumExpr = '-?[0-9]{1,3}(\\.[0-9]+)?';
final opNumExpr = '-?([01]+(\\.[0-9]+)?|\\.[0-9]+)';
if (RegExp(
r'^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$',
).hasMatch(color)) {
return true;
} else if (RegExp(
'^rgba?\\($chNumExpr%,\\s?$chNumExpr%,\\s?$chNumExpr%(,\\s?$opNumExpr)?\\)\$',
).hasMatch(color)) {
return true;
} else if (RegExp(
'^rgba?\\($chNumExpr,\\s?$chNumExpr,\\s?$chNumExpr(,\\s?$opNumExpr)?\\)\$',
).hasMatch(color)) {
return true;
} else if (RegExp(
'^hsla?\\($chNumExpr,\\s?$chNumExpr%,\\s?$chNumExpr%(,\\s?$opNumExpr)?\\)\$',
).hasMatch(color)) {
return true;
} else if (colorKeywords.containsKey(color)) {
return true;
} else {
return false;
}
}
@Deprecated('Call isCssColor() instead.'
'This feature was deprecated since v1.2.0 to meet the https://dart.dev/guides/language/effective-dart/style#do-capitalize-acronyms-and-abbreviations-longer-than-two-letters-like-words')
/// Check correctness of color string according to https://drafts.csswg.org/css-color-3
bool isCSSColor(String color) {
return isCssColor(color);
}
/// Creates [Color] instance from hexadecimal color value.
int _hexToColor(String color) {
color = color.substring(1);
String alpha = 'FF';
if (color.length == 4) {
alpha = color[3] * 2;
color = color.substring(0, 3);
} else if (color.length == 8) {
alpha = color.substring(6);
color = color.substring(0, 6);
}
if (color.length == 3) {
color = color.splitMapJoin('', onNonMatch: (m) => m * 2);
} else if (color.length != 6) {
throw FormatException( throw FormatException(
'Incorrect number of values in RGB color string, there must be 3 or 4 of them.', 'Hex color string has incorrect length, only strings of 3 or 6 characters are allowed.',
color, '#$color',
); );
} }
return 0x1000000 *
if (_isPercentFormat(channels)) { int.parse(
for (var ch in channels) { alpha,
result = (_rgbChannelPercentToHex(ch) << shift) | result; radix: 16,
shift -= 8; ) +
} int.parse(
} else { color,
for (var ch in channels) { radix: 16,
result = (_rgbChannelNumToHex(ch) << shift) | result; );
shift -= 8;
}
}
return result;
} }
/// Parses channels from RGBA/HSLA string representation. /// Parses channels from RGBA/HSLA string representation.
@ -69,3 +162,247 @@ int _rgbChannelPercentToHex(String value) {
num _parsePercent(String percent) { num _parsePercent(String percent) {
return (double.parse(percent.substring(0, percent.length - 1)).clamp(0, 100)); return (double.parse(percent.substring(0, percent.length - 1)).clamp(0, 100));
} }
/// Creates [Color] instance from RGB(A) color value.
int _rgbToColor(String color) {
var channels = _parseChannels(color)!;
var result = 0xFF000000;
var shift = 16;
if (channels.length == 4) {
result = (_opacityChannelToHex(channels.removeLast()) << 24) & result;
} else if (channels.length != 3) {
throw FormatException(
'Incorrect number of values in RGB color string, there must be 3 or 4 of them.',
color,
);
}
if (_isPercentFormat(channels)) {
for (var ch in channels) {
result = (_rgbChannelPercentToHex(ch) << shift) | result;
shift -= 8;
}
} else {
for (var ch in channels) {
result = (_rgbChannelNumToHex(ch) << shift) | result;
shift -= 8;
}
}
return result;
}
/// Creates [Color] instance from HSL(A) color value.
int _hslToColor(String color) {
var channels = _parseChannels(color)!;
var result = 0xFF000000;
var shift = 16;
if (channels.length == 4) {
result = (_opacityChannelToHex(channels.removeLast()) << 24) & result;
} else if (channels.length != 3) {
throw FormatException(
'Incorrect number of values in HSL color string, there must be 3 or 4 of them.',
color,
);
}
try {
// Translate HSL to RGB according to CSS3 draft
final h = double.parse(channels[0]) % 360 / 360;
final s = _parsePercent(channels[1]) / 100;
final l = _parsePercent(channels[2]) / 100;
final m2 = l < 0.5 ? l * (s + 1) : l + s - l * s;
final m1 = l * 2 - m2;
final hexChannels = [
_hueToRGB(m1, m2, h + 1 / 3),
_hueToRGB(m1, m2, h),
_hueToRGB(m1, m2, h - 1 / 3),
];
for (var ch in hexChannels) {
result = (ch << shift) | result;
shift -= 8;
}
return result;
} on FormatException catch (e) {
throw FormatException(
'Incorrect format of HSL color string.',
'${e.message} ${e.source}',
);
}
}
/// Converts hue parameters of HSL to RGB channel hexadecimal integer form.
int _hueToRGB(num m1, num m2, num h) {
int result;
if (h < 0) {
h = h + 1;
} else if (h > 1) {
h = h - 1;
}
if (h * 6 < 1) {
result = ((m1 + (m2 - m1) * h * 6) * 255).floor();
} else if (h * 2 < 1) {
result = (m2 * 255).floor();
} else if (h * 3 < 2) {
result = ((m1 + (m2 - m1) * (2 / 3 - h) * 6) * 255).floor();
} else {
result = (m1 * 255).floor();
}
return result;
}
// A map of X11 color keywords and their 8-digit hexadecimal forms.
Map<String, int> colorKeywords = {
"transparent": 0x00000000,
"aliceblue": 0xFFF0F8FF,
"antiquewhite": 0xFFFAEBD7,
"aqua": 0xFF00FFFF,
"aquamarine": 0xFF7FFFD4,
"azure": 0xFFF0FFFF,
"beige": 0xFFF5F5DC,
"bisque": 0xFFFFE4C4,
"black": 0xFF000000,
"blanchedalmond": 0xFFFFEBCD,
"blue": 0xFF0000FF,
"blueviolet": 0xFF8A2BE2,
"brown": 0xFFA52A2A,
"burlywood": 0xFFDEB887,
"cadetblue": 0xFF5F9EA0,
"chartreuse": 0xFF7FFF00,
"chocolate": 0xFFD2691E,
"coral": 0xFFFF7F50,
"cornflowerblue": 0xFF6495ED,
"cornsilk": 0xFFFFF8DC,
"crimson": 0xFFDC143C,
"cyan": 0xFF00FFFF,
"darkblue": 0xFF00008B,
"darkcyan": 0xFF008B8B,
"darkgoldenrod": 0xFFB8860B,
"darkgray": 0xFFA9A9A9,
"darkgreen": 0xFF006400,
"darkgrey": 0xFFA9A9A9,
"darkkhaki": 0xFFBDB76B,
"darkmagenta": 0xFF8B008B,
"darkolivegreen": 0xFF556B2F,
"darkorange": 0xFFFF8C00,
"darkorchid": 0xFF9932CC,
"darkred": 0xFF8B0000,
"darksalmon": 0xFFE9967A,
"darkseagreen": 0xFF8FBC8F,
"darkslateblue": 0xFF483D8B,
"darkslategray": 0xFF2F4F4F,
"darkslategrey": 0xFF2F4F4F,
"darkturquoise": 0xFF00CED1,
"darkviolet": 0xFF9400D3,
"deeppink": 0xFFFF1493,
"deepskyblue": 0xFF00BFFF,
"dimgray": 0xFF696969,
"dimgrey": 0xFF696969,
"dodgerblue": 0xFF1E90FF,
"firebrick": 0xFFB22222,
"floralwhite": 0xFFFFFAF0,
"forestgreen": 0xFF228B22,
"fuchsia": 0xFFFF00FF,
"gainsboro": 0xFFDCDCDC,
"ghostwhite": 0xFFF8F8FF,
"gold": 0xFFFFD700,
"goldenrod": 0xFFDAA520,
"gray": 0xFF808080,
"green": 0xFF008000,
"greenyellow": 0xFFADFF2F,
"grey": 0xFF808080,
"honeydew": 0xFFF0FFF0,
"hotpink": 0xFFFF69B4,
"indianred": 0xFFCD5C5C,
"indigo": 0xFF4B0082,
"ivory": 0xFFFFFFF0,
"khaki": 0xFFF0E68C,
"lavender": 0xFFE6E6FA,
"lavenderblush": 0xFFFFF0F5,
"lawngreen": 0xFF7CFC00,
"lemonchiffon": 0xFFFFFACD,
"lightblue": 0xFFADD8E6,
"lightcoral": 0xFFF08080,
"lightcyan": 0xFFE0FFFF,
"lightgoldenrodyellow": 0xFFFAFAD2,
"lightgray": 0xFFD3D3D3,
"lightgreen": 0xFF90EE90,
"lightgrey": 0xFFD3D3D3,
"lightpink": 0xFFFFB6C1,
"lightsalmon": 0xFFFFA07A,
"lightseagreen": 0xFF20B2AA,
"lightskyblue": 0xFF87CEFA,
"lightslategray": 0xFF778899,
"lightslategrey": 0xFF778899,
"lightsteelblue": 0xFFB0C4DE,
"lightyellow": 0xFFFFFFE0,
"lime": 0xFF00FF00,
"limegreen": 0xFF32CD32,
"linen": 0xFFFAF0E6,
"magenta": 0xFFFF00FF,
"maroon": 0xFF800000,
"mediumaquamarine": 0xFF66CDAA,
"mediumblue": 0xFF0000CD,
"mediumorchid": 0xFFBA55D3,
"mediumpurple": 0xFF9370DB,
"mediumseagreen": 0xFF3CB371,
"mediumslateblue": 0xFF7B68EE,
"mediumspringgreen": 0xFF00FA9A,
"mediumturquoise": 0xFF48D1CC,
"mediumvioletred": 0xFFC71585,
"midnightblue": 0xFF191970,
"mintcream": 0xFFF5FFFA,
"mistyrose": 0xFFFFE4E1,
"moccasin": 0xFFFFE4B5,
"navajowhite": 0xFFFFDEAD,
"navy": 0xFF000080,
"oldlace": 0xFFFDF5E6,
"olive": 0xFF808000,
"olivedrab": 0xFF6B8E23,
"orange": 0xFFFFA500,
"orangered": 0xFFFF4500,
"orchid": 0xFFDA70D6,
"palegoldenrod": 0xFFEEE8AA,
"palegreen": 0xFF98FB98,
"paleturquoise": 0xFFAFEEEE,
"palevioletred": 0xFFDB7093,
"papayawhip": 0xFFFFEFD5,
"peachpuff": 0xFFFFDAB9,
"peru": 0xFFCD853F,
"pink": 0xFFFFC0CB,
"plum": 0xFFDDA0DD,
"powderblue": 0xFFB0E0E6,
"purple": 0xFF800080,
"red": 0xFFFF0000,
"rosybrown": 0xFFBC8F8F,
"royalblue": 0xFF4169E1,
"saddlebrown": 0xFF8B4513,
"salmon": 0xFFFA8072,
"sandybrown": 0xFFF4A460,
"seagreen": 0xFF2E8B57,
"seashell": 0xFFFFF5EE,
"sienna": 0xFFA0522D,
"silver": 0xFFC0C0C0,
"skyblue": 0xFF87CEEB,
"slateblue": 0xFF6A5ACD,
"slategray": 0xFF708090,
"slategrey": 0xFF708090,
"snow": 0xFFFFFAFA,
"springgreen": 0xFF00FF7F,
"steelblue": 0xFF4682B4,
"tan": 0xFFD2B48C,
"teal": 0xFF008080,
"thistle": 0xFFD8BFD8,
"tomato": 0xFFFF6347,
"turquoise": 0xFF40E0D0,
"violet": 0xFFEE82EE,
"wheat": 0xFFF5DEB3,
"white": 0xFFFFFFFF,
"whitesmoke": 0xFFF5F5F5,
"yellow": 0xFFFFFF00,
"yellowgreen": 0xFF9ACD32,
};

View file

@ -6,7 +6,7 @@ abstract class MaybeDecode {
static int? maybeColor(String? colorCode) { static int? maybeColor(String? colorCode) {
if (colorCode == null) return null; if (colorCode == null) return null;
return parseRgbToInt(colorCode); return fromCssColor(colorCode);
} }
static DateTime? maybeDateTime(String? timeStamp) { static DateTime? maybeDateTime(String? timeStamp) {