11import 'package:collection/collection.dart' ;
2+ import 'package:convert/convert.dart' ;
23import 'package:csslib/parser.dart' as css_parser;
34import 'package:csslib/visitor.dart' as css_visitor;
45import 'package:flutter/foundation.dart' ;
@@ -629,6 +630,7 @@ class _KatexParser {
629630 topEm: _takeStyleEm (inlineStyles, 'top' ),
630631 marginLeftEm: _takeStyleEm (inlineStyles, 'margin-left' ),
631632 marginRightEm: _takeStyleEm (inlineStyles, 'margin-right' ),
633+ color: _takeStyleColor (inlineStyles, 'color' ),
632634 // TODO handle more CSS properties
633635 );
634636 if (inlineStyles != null && inlineStyles.isNotEmpty) {
@@ -715,6 +717,54 @@ class _KatexParser {
715717 _hasError = true ;
716718 return null ;
717719 }
720+
721+ /// Remove the given property from the given style map,
722+ /// and parse as a color value.
723+ ///
724+ /// If the property is present but is not a valid CSS Hex color,
725+ /// or is not one of the CSS named color, record an error
726+ /// and return null.
727+ ///
728+ /// If the property is absent, return null with no error.
729+ ///
730+ /// If the map is null, treat it as empty.
731+ ///
732+ /// To produce the map this method expects, see [_parseInlineStyles] .
733+ KatexSpanColor ? _takeStyleColor (Map <String , css_visitor.Expression >? styles, String property) {
734+ final expression = styles? .remove (property);
735+ if (expression == null ) return null ;
736+
737+ // `package:csslib` parser emits a HexColorTerm for the `color`
738+ // attribute. It automatically resolves the named CSS colors to
739+ // their hex values. The `HexColorTerm.value` is the hex
740+ // encoded in an integer in the same sequence as the input hex
741+ // string. But it also allows some non-conformant CSS hex color
742+ // notations, like #f, #ff, #fffff, #fffffff.
743+ // See:
744+ // https://drafts.csswg.org/css-color/#hex-notation.
745+ // https://github.com/dart-lang/tools/blob/2a2a2d611/pkgs/csslib/lib/parser.dart#L2714-L2743
746+ //
747+ // So, we try to parse the value of `color` attribute ourselves
748+ // only allowing conformant CSS hex color notations, mapping
749+ // named CSS colors to their corresponding values, generating a
750+ // typed result (KatexSpanColor(r, g, b, a)) to be used later
751+ // while rendering.
752+ final valueStr = expression.span? .text;
753+ if (valueStr != null ) {
754+ if (valueStr.startsWith ('#' )) {
755+ final color = parseCssHexColor (valueStr);
756+ if (color != null ) return color;
757+ } else {
758+ final color = _cssNamedColorsMap[valueStr];
759+ if (color != null ) return color;
760+ }
761+ }
762+ assert (debugLog ('KaTeX: Unsupported value for CSS property $property ,'
763+ ' expected a color: ${expression .toDebugString ()}' ));
764+ unsupportedInlineCssProperties.add (property);
765+ _hasError = true ;
766+ return null ;
767+ }
718768}
719769
720770enum KatexSpanFontWeight {
@@ -732,6 +782,32 @@ enum KatexSpanTextAlign {
732782 right,
733783}
734784
785+ class KatexSpanColor {
786+ const KatexSpanColor (this .r, this .g, this .b, this .a);
787+
788+ final int r;
789+ final int g;
790+ final int b;
791+ final int a;
792+
793+ @override
794+ bool operator == (Object other) {
795+ return other is KatexSpanColor &&
796+ other.r == r &&
797+ other.g == g &&
798+ other.b == b &&
799+ other.a == a;
800+ }
801+
802+ @override
803+ int get hashCode => Object .hash ('KatexSpanColor' , r, g, b, a);
804+
805+ @override
806+ String toString () {
807+ return '${objectRuntimeType (this , 'KatexSpanColor' )}($r , $g , $b , $a )' ;
808+ }
809+ }
810+
735811@immutable
736812class KatexSpanStyles {
737813 // TODO(#1674) does height actually appear on generic spans?
@@ -755,6 +831,8 @@ class KatexSpanStyles {
755831 final KatexSpanFontStyle ? fontStyle;
756832 final KatexSpanTextAlign ? textAlign;
757833
834+ final KatexSpanColor ? color;
835+
758836 const KatexSpanStyles ({
759837 this .heightEm,
760838 this .topEm,
@@ -765,6 +843,7 @@ class KatexSpanStyles {
765843 this .fontWeight,
766844 this .fontStyle,
767845 this .textAlign,
846+ this .color,
768847 });
769848
770849 @override
@@ -779,6 +858,7 @@ class KatexSpanStyles {
779858 fontWeight,
780859 fontStyle,
781860 textAlign,
861+ color,
782862 );
783863
784864 @override
@@ -792,7 +872,8 @@ class KatexSpanStyles {
792872 other.fontSizeEm == fontSizeEm &&
793873 other.fontWeight == fontWeight &&
794874 other.fontStyle == fontStyle &&
795- other.textAlign == textAlign;
875+ other.textAlign == textAlign &&
876+ other.color == color;
796877 }
797878
798879 @override
@@ -807,6 +888,7 @@ class KatexSpanStyles {
807888 if (fontWeight != null ) args.add ('fontWeight: $fontWeight ' );
808889 if (fontStyle != null ) args.add ('fontStyle: $fontStyle ' );
809890 if (textAlign != null ) args.add ('textAlign: $textAlign ' );
891+ if (color != null ) args.add ('color: $color ' );
810892 return '${objectRuntimeType (this , 'KatexSpanStyles' )}(${args .join (', ' )})' ;
811893 }
812894
@@ -821,6 +903,7 @@ class KatexSpanStyles {
821903 bool fontWeight = true ,
822904 bool fontStyle = true ,
823905 bool textAlign = true ,
906+ bool color = true ,
824907 }) {
825908 return KatexSpanStyles (
826909 heightEm: heightEm ? this .heightEm : null ,
@@ -832,10 +915,201 @@ class KatexSpanStyles {
832915 fontWeight: fontWeight ? this .fontWeight : null ,
833916 fontStyle: fontStyle ? this .fontStyle : null ,
834917 textAlign: textAlign ? this .textAlign : null ,
918+ color: color ? this .color : null ,
835919 );
836920 }
837921}
838922
923+ final _hexColorRegExp =
924+ RegExp (r'^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$' );
925+
926+ /// Parses the CSS hex color notation.
927+ ///
928+ /// See: https://drafts.csswg.org/css-color/#hex-notation
929+ @visibleForTesting
930+ KatexSpanColor ? parseCssHexColor (String hexStr) {
931+ final match = _hexColorRegExp.firstMatch (hexStr);
932+ if (match == null ) return null ;
933+
934+ String hexValue = match.group (1 )! ;
935+ switch (hexValue.length) {
936+ case 3 :
937+ hexValue = '${hexValue [0 ]}${hexValue [0 ]}'
938+ '${hexValue [1 ]}${hexValue [1 ]}'
939+ '${hexValue [2 ]}${hexValue [2 ]}'
940+ 'ff' ;
941+ case 4 :
942+ hexValue = '${hexValue [0 ]}${hexValue [0 ]}'
943+ '${hexValue [1 ]}${hexValue [1 ]}'
944+ '${hexValue [2 ]}${hexValue [2 ]}'
945+ '${hexValue [3 ]}${hexValue [3 ]}' ;
946+ case 6 :
947+ hexValue += 'ff' ;
948+ }
949+
950+ try {
951+ final [r, g, b, a] = hex.decode (hexValue);
952+ return KatexSpanColor (r, g, b, a);
953+ } catch (_) {
954+ return null ; // TODO(log)
955+ }
956+ }
957+
958+ // CSS named colors: https://drafts.csswg.org/css-color/#named-colors
959+ // Map adapted from the following source file:
960+ // https://github.com/w3c/csswg-drafts/blob/1942d0918/css-color-4/Overview.bs#L1562-L1859
961+ const _cssNamedColorsMap = {
962+ 'transparent' : KatexSpanColor (0 , 0 , 0 , 0 ), // https://drafts.csswg.org/css-color/#transparent-color
963+ 'aliceblue' : KatexSpanColor (240 , 248 , 255 , 255 ),
964+ 'antiquewhite' : KatexSpanColor (250 , 235 , 215 , 255 ),
965+ 'aqua' : KatexSpanColor (0 , 255 , 255 , 255 ),
966+ 'aquamarine' : KatexSpanColor (127 , 255 , 212 , 255 ),
967+ 'azure' : KatexSpanColor (240 , 255 , 255 , 255 ),
968+ 'beige' : KatexSpanColor (245 , 245 , 220 , 255 ),
969+ 'bisque' : KatexSpanColor (255 , 228 , 196 , 255 ),
970+ 'black' : KatexSpanColor (0 , 0 , 0 , 255 ),
971+ 'blanchedalmond' : KatexSpanColor (255 , 235 , 205 , 255 ),
972+ 'blue' : KatexSpanColor (0 , 0 , 255 , 255 ),
973+ 'blueviolet' : KatexSpanColor (138 , 43 , 226 , 255 ),
974+ 'brown' : KatexSpanColor (165 , 42 , 42 , 255 ),
975+ 'burlywood' : KatexSpanColor (222 , 184 , 135 , 255 ),
976+ 'cadetblue' : KatexSpanColor (95 , 158 , 160 , 255 ),
977+ 'chartreuse' : KatexSpanColor (127 , 255 , 0 , 255 ),
978+ 'chocolate' : KatexSpanColor (210 , 105 , 30 , 255 ),
979+ 'coral' : KatexSpanColor (255 , 127 , 80 , 255 ),
980+ 'cornflowerblue' : KatexSpanColor (100 , 149 , 237 , 255 ),
981+ 'cornsilk' : KatexSpanColor (255 , 248 , 220 , 255 ),
982+ 'crimson' : KatexSpanColor (220 , 20 , 60 , 255 ),
983+ 'cyan' : KatexSpanColor (0 , 255 , 255 , 255 ),
984+ 'darkblue' : KatexSpanColor (0 , 0 , 139 , 255 ),
985+ 'darkcyan' : KatexSpanColor (0 , 139 , 139 , 255 ),
986+ 'darkgoldenrod' : KatexSpanColor (184 , 134 , 11 , 255 ),
987+ 'darkgray' : KatexSpanColor (169 , 169 , 169 , 255 ),
988+ 'darkgreen' : KatexSpanColor (0 , 100 , 0 , 255 ),
989+ 'darkgrey' : KatexSpanColor (169 , 169 , 169 , 255 ),
990+ 'darkkhaki' : KatexSpanColor (189 , 183 , 107 , 255 ),
991+ 'darkmagenta' : KatexSpanColor (139 , 0 , 139 , 255 ),
992+ 'darkolivegreen' : KatexSpanColor (85 , 107 , 47 , 255 ),
993+ 'darkorange' : KatexSpanColor (255 , 140 , 0 , 255 ),
994+ 'darkorchid' : KatexSpanColor (153 , 50 , 204 , 255 ),
995+ 'darkred' : KatexSpanColor (139 , 0 , 0 , 255 ),
996+ 'darksalmon' : KatexSpanColor (233 , 150 , 122 , 255 ),
997+ 'darkseagreen' : KatexSpanColor (143 , 188 , 143 , 255 ),
998+ 'darkslateblue' : KatexSpanColor (72 , 61 , 139 , 255 ),
999+ 'darkslategray' : KatexSpanColor (47 , 79 , 79 , 255 ),
1000+ 'darkslategrey' : KatexSpanColor (47 , 79 , 79 , 255 ),
1001+ 'darkturquoise' : KatexSpanColor (0 , 206 , 209 , 255 ),
1002+ 'darkviolet' : KatexSpanColor (148 , 0 , 211 , 255 ),
1003+ 'deeppink' : KatexSpanColor (255 , 20 , 147 , 255 ),
1004+ 'deepskyblue' : KatexSpanColor (0 , 191 , 255 , 255 ),
1005+ 'dimgray' : KatexSpanColor (105 , 105 , 105 , 255 ),
1006+ 'dimgrey' : KatexSpanColor (105 , 105 , 105 , 255 ),
1007+ 'dodgerblue' : KatexSpanColor (30 , 144 , 255 , 255 ),
1008+ 'firebrick' : KatexSpanColor (178 , 34 , 34 , 255 ),
1009+ 'floralwhite' : KatexSpanColor (255 , 250 , 240 , 255 ),
1010+ 'forestgreen' : KatexSpanColor (34 , 139 , 34 , 255 ),
1011+ 'fuchsia' : KatexSpanColor (255 , 0 , 255 , 255 ),
1012+ 'gainsboro' : KatexSpanColor (220 , 220 , 220 , 255 ),
1013+ 'ghostwhite' : KatexSpanColor (248 , 248 , 255 , 255 ),
1014+ 'gold' : KatexSpanColor (255 , 215 , 0 , 255 ),
1015+ 'goldenrod' : KatexSpanColor (218 , 165 , 32 , 255 ),
1016+ 'gray' : KatexSpanColor (128 , 128 , 128 , 255 ),
1017+ 'green' : KatexSpanColor (0 , 128 , 0 , 255 ),
1018+ 'greenyellow' : KatexSpanColor (173 , 255 , 47 , 255 ),
1019+ 'grey' : KatexSpanColor (128 , 128 , 128 , 255 ),
1020+ 'honeydew' : KatexSpanColor (240 , 255 , 240 , 255 ),
1021+ 'hotpink' : KatexSpanColor (255 , 105 , 180 , 255 ),
1022+ 'indianred' : KatexSpanColor (205 , 92 , 92 , 255 ),
1023+ 'indigo' : KatexSpanColor (75 , 0 , 130 , 255 ),
1024+ 'ivory' : KatexSpanColor (255 , 255 , 240 , 255 ),
1025+ 'khaki' : KatexSpanColor (240 , 230 , 140 , 255 ),
1026+ 'lavender' : KatexSpanColor (230 , 230 , 250 , 255 ),
1027+ 'lavenderblush' : KatexSpanColor (255 , 240 , 245 , 255 ),
1028+ 'lawngreen' : KatexSpanColor (124 , 252 , 0 , 255 ),
1029+ 'lemonchiffon' : KatexSpanColor (255 , 250 , 205 , 255 ),
1030+ 'lightblue' : KatexSpanColor (173 , 216 , 230 , 255 ),
1031+ 'lightcoral' : KatexSpanColor (240 , 128 , 128 , 255 ),
1032+ 'lightcyan' : KatexSpanColor (224 , 255 , 255 , 255 ),
1033+ 'lightgoldenrodyellow' : KatexSpanColor (250 , 250 , 210 , 255 ),
1034+ 'lightgray' : KatexSpanColor (211 , 211 , 211 , 255 ),
1035+ 'lightgreen' : KatexSpanColor (144 , 238 , 144 , 255 ),
1036+ 'lightgrey' : KatexSpanColor (211 , 211 , 211 , 255 ),
1037+ 'lightpink' : KatexSpanColor (255 , 182 , 193 , 255 ),
1038+ 'lightsalmon' : KatexSpanColor (255 , 160 , 122 , 255 ),
1039+ 'lightseagreen' : KatexSpanColor (32 , 178 , 170 , 255 ),
1040+ 'lightskyblue' : KatexSpanColor (135 , 206 , 250 , 255 ),
1041+ 'lightslategray' : KatexSpanColor (119 , 136 , 153 , 255 ),
1042+ 'lightslategrey' : KatexSpanColor (119 , 136 , 153 , 255 ),
1043+ 'lightsteelblue' : KatexSpanColor (176 , 196 , 222 , 255 ),
1044+ 'lightyellow' : KatexSpanColor (255 , 255 , 224 , 255 ),
1045+ 'lime' : KatexSpanColor (0 , 255 , 0 , 255 ),
1046+ 'limegreen' : KatexSpanColor (50 , 205 , 50 , 255 ),
1047+ 'linen' : KatexSpanColor (250 , 240 , 230 , 255 ),
1048+ 'magenta' : KatexSpanColor (255 , 0 , 255 , 255 ),
1049+ 'maroon' : KatexSpanColor (128 , 0 , 0 , 255 ),
1050+ 'mediumaquamarine' : KatexSpanColor (102 , 205 , 170 , 255 ),
1051+ 'mediumblue' : KatexSpanColor (0 , 0 , 205 , 255 ),
1052+ 'mediumorchid' : KatexSpanColor (186 , 85 , 211 , 255 ),
1053+ 'mediumpurple' : KatexSpanColor (147 , 112 , 219 , 255 ),
1054+ 'mediumseagreen' : KatexSpanColor (60 , 179 , 113 , 255 ),
1055+ 'mediumslateblue' : KatexSpanColor (123 , 104 , 238 , 255 ),
1056+ 'mediumspringgreen' : KatexSpanColor (0 , 250 , 154 , 255 ),
1057+ 'mediumturquoise' : KatexSpanColor (72 , 209 , 204 , 255 ),
1058+ 'mediumvioletred' : KatexSpanColor (199 , 21 , 133 , 255 ),
1059+ 'midnightblue' : KatexSpanColor (25 , 25 , 112 , 255 ),
1060+ 'mintcream' : KatexSpanColor (245 , 255 , 250 , 255 ),
1061+ 'mistyrose' : KatexSpanColor (255 , 228 , 225 , 255 ),
1062+ 'moccasin' : KatexSpanColor (255 , 228 , 181 , 255 ),
1063+ 'navajowhite' : KatexSpanColor (255 , 222 , 173 , 255 ),
1064+ 'navy' : KatexSpanColor (0 , 0 , 128 , 255 ),
1065+ 'oldlace' : KatexSpanColor (253 , 245 , 230 , 255 ),
1066+ 'olive' : KatexSpanColor (128 , 128 , 0 , 255 ),
1067+ 'olivedrab' : KatexSpanColor (107 , 142 , 35 , 255 ),
1068+ 'orange' : KatexSpanColor (255 , 165 , 0 , 255 ),
1069+ 'orangered' : KatexSpanColor (255 , 69 , 0 , 255 ),
1070+ 'orchid' : KatexSpanColor (218 , 112 , 214 , 255 ),
1071+ 'palegoldenrod' : KatexSpanColor (238 , 232 , 170 , 255 ),
1072+ 'palegreen' : KatexSpanColor (152 , 251 , 152 , 255 ),
1073+ 'paleturquoise' : KatexSpanColor (175 , 238 , 238 , 255 ),
1074+ 'palevioletred' : KatexSpanColor (219 , 112 , 147 , 255 ),
1075+ 'papayawhip' : KatexSpanColor (255 , 239 , 213 , 255 ),
1076+ 'peachpuff' : KatexSpanColor (255 , 218 , 185 , 255 ),
1077+ 'peru' : KatexSpanColor (205 , 133 , 63 , 255 ),
1078+ 'pink' : KatexSpanColor (255 , 192 , 203 , 255 ),
1079+ 'plum' : KatexSpanColor (221 , 160 , 221 , 255 ),
1080+ 'powderblue' : KatexSpanColor (176 , 224 , 230 , 255 ),
1081+ 'purple' : KatexSpanColor (128 , 0 , 128 , 255 ),
1082+ 'rebeccapurple' : KatexSpanColor (102 , 51 , 153 , 255 ),
1083+ 'red' : KatexSpanColor (255 , 0 , 0 , 255 ),
1084+ 'rosybrown' : KatexSpanColor (188 , 143 , 143 , 255 ),
1085+ 'royalblue' : KatexSpanColor (65 , 105 , 225 , 255 ),
1086+ 'saddlebrown' : KatexSpanColor (139 , 69 , 19 , 255 ),
1087+ 'salmon' : KatexSpanColor (250 , 128 , 114 , 255 ),
1088+ 'sandybrown' : KatexSpanColor (244 , 164 , 96 , 255 ),
1089+ 'seagreen' : KatexSpanColor (46 , 139 , 87 , 255 ),
1090+ 'seashell' : KatexSpanColor (255 , 245 , 238 , 255 ),
1091+ 'sienna' : KatexSpanColor (160 , 82 , 45 , 255 ),
1092+ 'silver' : KatexSpanColor (192 , 192 , 192 , 255 ),
1093+ 'skyblue' : KatexSpanColor (135 , 206 , 235 , 255 ),
1094+ 'slateblue' : KatexSpanColor (106 , 90 , 205 , 255 ),
1095+ 'slategray' : KatexSpanColor (112 , 128 , 144 , 255 ),
1096+ 'slategrey' : KatexSpanColor (112 , 128 , 144 , 255 ),
1097+ 'snow' : KatexSpanColor (255 , 250 , 250 , 255 ),
1098+ 'springgreen' : KatexSpanColor (0 , 255 , 127 , 255 ),
1099+ 'steelblue' : KatexSpanColor (70 , 130 , 180 , 255 ),
1100+ 'tan' : KatexSpanColor (210 , 180 , 140 , 255 ),
1101+ 'teal' : KatexSpanColor (0 , 128 , 128 , 255 ),
1102+ 'thistle' : KatexSpanColor (216 , 191 , 216 , 255 ),
1103+ 'tomato' : KatexSpanColor (255 , 99 , 71 , 255 ),
1104+ 'turquoise' : KatexSpanColor (64 , 224 , 208 , 255 ),
1105+ 'violet' : KatexSpanColor (238 , 130 , 238 , 255 ),
1106+ 'wheat' : KatexSpanColor (245 , 222 , 179 , 255 ),
1107+ 'white' : KatexSpanColor (255 , 255 , 255 , 255 ),
1108+ 'whitesmoke' : KatexSpanColor (245 , 245 , 245 , 255 ),
1109+ 'yellow' : KatexSpanColor (255 , 255 , 0 , 255 ),
1110+ 'yellowgreen' : KatexSpanColor (154 , 205 , 50 , 255 ),
1111+ };
1112+
8391113class _KatexHtmlParseError extends Error {
8401114 final String ? message;
8411115
0 commit comments