mirror of
https://gitlab.com/TheOneWithTheBraid/dart_pkpass.git
synced 2025-07-05 21:08:47 +00:00
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:
parent
92319c20d2
commit
a41784b77e
2 changed files with 362 additions and 25 deletions
|
@ -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,
|
||||||
|
};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue