Sonnat Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1172 lines
47 KiB

  1. import 'dart:ui';
  2. import 'package:collection/collection.dart';
  3. import 'package:csslib/parser.dart' as cssparser;
  4. import 'package:csslib/visitor.dart' as css;
  5. import 'package:flutter/material.dart';
  6. import 'package:sonnat/core/html/html_parser.dart';
  7. import 'package:sonnat/core/html/src/style/fontsize.dart';
  8. import 'package:sonnat/core/html/src/style/length.dart';
  9. import 'package:sonnat/core/html/src/style/lineheight.dart';
  10. import 'package:sonnat/core/html/src/style/margin.dart';
  11. import 'package:sonnat/core/html/src/style/size.dart';
  12. import 'package:sonnat/core/html/src/utils.dart';
  13. import 'package:sonnat/core/html/style.dart';
  14. Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
  15. Style style = Style();
  16. declarations.forEach((property, value) {
  17. if (value.isNotEmpty) {
  18. switch (property) {
  19. case 'background-color':
  20. style.backgroundColor = ExpressionMapping.expressionToColor(value.first) ?? style.backgroundColor;
  21. break;
  22. case 'border':
  23. List<css.LiteralTerm?>? borderWidths = value.whereType<css.LiteralTerm>().toList();
  24. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping]
  25. borderWidths.removeWhere((element) =>
  26. element == null ||
  27. (element.text != 'thin' &&
  28. element.text != 'medium' &&
  29. element.text != 'thick' &&
  30. element is! css.LengthTerm &&
  31. element is! css.PercentageTerm &&
  32. element is! css.EmTerm &&
  33. element is! css.RemTerm &&
  34. element is! css.NumberTerm));
  35. List<css.Expression?>? borderColors =
  36. value.where((element) => ExpressionMapping.expressionToColor(element) != null).toList();
  37. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  38. /// Currently doesn't matter, as Flutter only supports "solid" or "none", but may support more in the future.
  39. List<String> possibleBorderValues = [
  40. 'dotted',
  41. 'dashed',
  42. 'solid',
  43. 'double',
  44. 'groove',
  45. 'ridge',
  46. 'inset',
  47. 'outset',
  48. 'none',
  49. 'hidden'
  50. ];
  51. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.style], so make sure to remove those before passing it to [ExpressionMapping]
  52. potentialStyles.removeWhere((element) => element == null || !possibleBorderValues.contains(element.text));
  53. List<css.LiteralTerm?>? borderStyles = potentialStyles;
  54. style.border = ExpressionMapping.expressionToBorder(borderWidths, borderStyles, borderColors);
  55. break;
  56. case 'border-left':
  57. List<css.LiteralTerm?>? borderWidths = value.whereType<css.LiteralTerm>().toList();
  58. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping]
  59. borderWidths.removeWhere((element) =>
  60. element == null ||
  61. (element.text != 'thin' &&
  62. element.text != 'medium' &&
  63. element.text != 'thick' &&
  64. element is! css.LengthTerm &&
  65. element is! css.PercentageTerm &&
  66. element is! css.EmTerm &&
  67. element is! css.RemTerm &&
  68. element is! css.NumberTerm));
  69. css.LiteralTerm? borderWidth = borderWidths.firstWhereOrNull((element) => element != null);
  70. css.Expression? borderColor =
  71. value.firstWhereOrNull((element) => ExpressionMapping.expressionToColor(element) != null);
  72. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  73. /// Currently doesn't matter, as Flutter only supports "solid" or "none", but may support more in the future.
  74. List<String> possibleBorderValues = [
  75. 'dotted',
  76. 'dashed',
  77. 'solid',
  78. 'double',
  79. 'groove',
  80. 'ridge',
  81. 'inset',
  82. 'outset',
  83. 'none',
  84. 'hidden'
  85. ];
  86. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.style], so make sure to remove those before passing it to [ExpressionMapping]
  87. potentialStyles.removeWhere((element) => element == null || !possibleBorderValues.contains(element.text));
  88. css.LiteralTerm? borderStyle = potentialStyles.firstOrNull;
  89. Border newBorder = Border(
  90. left: style.border?.left.copyWith(
  91. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  92. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  93. color: ExpressionMapping.expressionToColor(borderColor),
  94. ) ??
  95. BorderSide(
  96. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  97. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  98. color: ExpressionMapping.expressionToColor(borderColor) ?? Colors.black,
  99. ),
  100. right: style.border?.right ?? BorderSide.none,
  101. top: style.border?.top ?? BorderSide.none,
  102. bottom: style.border?.bottom ?? BorderSide.none,
  103. );
  104. style.border = newBorder;
  105. break;
  106. case 'border-right':
  107. List<css.LiteralTerm?>? borderWidths = value.whereType<css.LiteralTerm>().toList();
  108. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping]
  109. borderWidths.removeWhere((element) =>
  110. element == null ||
  111. (element.text != 'thin' &&
  112. element.text != 'medium' &&
  113. element.text != 'thick' &&
  114. element is! css.LengthTerm &&
  115. element is! css.PercentageTerm &&
  116. element is! css.EmTerm &&
  117. element is! css.RemTerm &&
  118. element is! css.NumberTerm));
  119. css.LiteralTerm? borderWidth = borderWidths.firstWhereOrNull((element) => element != null);
  120. css.Expression? borderColor =
  121. value.firstWhereOrNull((element) => ExpressionMapping.expressionToColor(element) != null);
  122. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  123. /// Currently doesn't matter, as Flutter only supports "solid" or "none", but may support more in the future.
  124. List<String> possibleBorderValues = [
  125. 'dotted',
  126. 'dashed',
  127. 'solid',
  128. 'double',
  129. 'groove',
  130. 'ridge',
  131. 'inset',
  132. 'outset',
  133. 'none',
  134. 'hidden'
  135. ];
  136. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.style], so make sure to remove those before passing it to [ExpressionMapping]
  137. potentialStyles.removeWhere((element) => element == null || !possibleBorderValues.contains(element.text));
  138. css.LiteralTerm? borderStyle = potentialStyles.firstOrNull;
  139. Border newBorder = Border(
  140. left: style.border?.left ?? BorderSide.none,
  141. right: style.border?.right.copyWith(
  142. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  143. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  144. color: ExpressionMapping.expressionToColor(borderColor),
  145. ) ??
  146. BorderSide(
  147. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  148. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  149. color: ExpressionMapping.expressionToColor(borderColor) ?? Colors.black,
  150. ),
  151. top: style.border?.top ?? BorderSide.none,
  152. bottom: style.border?.bottom ?? BorderSide.none,
  153. );
  154. style.border = newBorder;
  155. break;
  156. case 'border-top':
  157. List<css.LiteralTerm?>? borderWidths = value.whereType<css.LiteralTerm>().toList();
  158. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping]
  159. borderWidths.removeWhere((element) =>
  160. element == null ||
  161. (element.text != 'thin' &&
  162. element.text != 'medium' &&
  163. element.text != 'thick' &&
  164. element is! css.LengthTerm &&
  165. element is! css.PercentageTerm &&
  166. element is! css.EmTerm &&
  167. element is! css.RemTerm &&
  168. element is! css.NumberTerm));
  169. css.LiteralTerm? borderWidth = borderWidths.firstWhereOrNull((element) => element != null);
  170. css.Expression? borderColor =
  171. value.firstWhereOrNull((element) => ExpressionMapping.expressionToColor(element) != null);
  172. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  173. /// Currently doesn't matter, as Flutter only supports "solid" or "none", but may support more in the future.
  174. List<String> possibleBorderValues = [
  175. 'dotted',
  176. 'dashed',
  177. 'solid',
  178. 'double',
  179. 'groove',
  180. 'ridge',
  181. 'inset',
  182. 'outset',
  183. 'none',
  184. 'hidden'
  185. ];
  186. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.style], so make sure to remove those before passing it to [ExpressionMapping]
  187. potentialStyles.removeWhere((element) => element == null || !possibleBorderValues.contains(element.text));
  188. css.LiteralTerm? borderStyle = potentialStyles.firstOrNull;
  189. Border newBorder = Border(
  190. left: style.border?.left ?? BorderSide.none,
  191. right: style.border?.right ?? BorderSide.none,
  192. top: style.border?.top.copyWith(
  193. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  194. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  195. color: ExpressionMapping.expressionToColor(borderColor),
  196. ) ??
  197. BorderSide(
  198. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  199. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  200. color: ExpressionMapping.expressionToColor(borderColor) ?? Colors.black,
  201. ),
  202. bottom: style.border?.bottom ?? BorderSide.none,
  203. );
  204. style.border = newBorder;
  205. break;
  206. case 'border-bottom':
  207. List<css.LiteralTerm?>? borderWidths = value.whereType<css.LiteralTerm>().toList();
  208. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping]
  209. borderWidths.removeWhere((element) =>
  210. element == null ||
  211. (element.text != 'thin' &&
  212. element.text != 'medium' &&
  213. element.text != 'thick' &&
  214. element is! css.LengthTerm &&
  215. element is! css.PercentageTerm &&
  216. element is! css.EmTerm &&
  217. element is! css.RemTerm &&
  218. element is! css.NumberTerm));
  219. css.LiteralTerm? borderWidth = borderWidths.firstWhereOrNull((element) => element != null);
  220. css.Expression? borderColor =
  221. value.firstWhereOrNull((element) => ExpressionMapping.expressionToColor(element) != null);
  222. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  223. /// Currently doesn't matter, as Flutter only supports "solid" or "none", but may support more in the future.
  224. List<String> possibleBorderValues = [
  225. 'dotted',
  226. 'dashed',
  227. 'solid',
  228. 'double',
  229. 'groove',
  230. 'ridge',
  231. 'inset',
  232. 'outset',
  233. 'none',
  234. 'hidden'
  235. ];
  236. /// List<css.LiteralTerm> might include other values than the ones we want for [BorderSide.style], so make sure to remove those before passing it to [ExpressionMapping]
  237. potentialStyles.removeWhere((element) => element == null || !possibleBorderValues.contains(element.text));
  238. css.LiteralTerm? borderStyle = potentialStyles.firstOrNull;
  239. Border newBorder = Border(
  240. left: style.border?.left ?? BorderSide.none,
  241. right: style.border?.right ?? BorderSide.none,
  242. top: style.border?.top ?? BorderSide.none,
  243. bottom: style.border?.bottom.copyWith(
  244. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  245. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  246. color: ExpressionMapping.expressionToColor(borderColor),
  247. ) ??
  248. BorderSide(
  249. width: ExpressionMapping.expressionToBorderWidth(borderWidth),
  250. style: ExpressionMapping.expressionToBorderStyle(borderStyle),
  251. color: ExpressionMapping.expressionToColor(borderColor) ?? Colors.black,
  252. ),
  253. );
  254. style.border = newBorder;
  255. break;
  256. case 'color':
  257. style.color = ExpressionMapping.expressionToColor(value.first) ?? style.color;
  258. break;
  259. case 'direction':
  260. style.direction = ExpressionMapping.expressionToDirection(value.first);
  261. break;
  262. case 'display':
  263. style.display = ExpressionMapping.expressionToDisplay(value.first);
  264. break;
  265. case 'line-height':
  266. style.lineHeight = ExpressionMapping.expressionToLineHeight(value.first);
  267. break;
  268. case 'font-family':
  269. style.fontFamily = ExpressionMapping.expressionToFontFamily(value.first) ?? style.fontFamily;
  270. break;
  271. case 'font-feature-settings':
  272. style.fontFeatureSettings = ExpressionMapping.expressionToFontFeatureSettings(value);
  273. break;
  274. case 'font-size':
  275. style.fontSize = ExpressionMapping.expressionToFontSize(value.first) ?? style.fontSize;
  276. break;
  277. case 'font-style':
  278. style.fontStyle = ExpressionMapping.expressionToFontStyle(value.first);
  279. break;
  280. case 'font-weight':
  281. style.fontWeight = ExpressionMapping.expressionToFontWeight(value.first);
  282. break;
  283. case 'list-style':
  284. css.LiteralTerm? position =
  285. value.firstWhereOrNull((e) => e is css.LiteralTerm && (e.text == 'outside' || e.text == 'inside'))
  286. as css.LiteralTerm?;
  287. css.UriTerm? image = value.firstWhereOrNull((e) => e is css.UriTerm) as css.UriTerm?;
  288. css.LiteralTerm? type =
  289. value.firstWhereOrNull((e) => e is css.LiteralTerm && e.text != 'outside' && e.text != 'inside')
  290. as css.LiteralTerm?;
  291. if (position != null) {
  292. switch (position.text) {
  293. case 'outside':
  294. style.listStylePosition = ListStylePosition.outside;
  295. break;
  296. case 'inside':
  297. style.listStylePosition = ListStylePosition.inside;
  298. break;
  299. }
  300. }
  301. if (image != null) {
  302. style.listStyleImage = ExpressionMapping.expressionToListStyleImage(image) ?? style.listStyleImage;
  303. } else if (type != null) {
  304. style.listStyleType = ExpressionMapping.expressionToListStyleType(type) ?? style.listStyleType;
  305. }
  306. break;
  307. case 'list-style-image':
  308. if (value.first is css.UriTerm) {
  309. style.listStyleImage =
  310. ExpressionMapping.expressionToListStyleImage(value.first as css.UriTerm) ?? style.listStyleImage;
  311. }
  312. break;
  313. case 'list-style-position':
  314. if (value.first is css.LiteralTerm) {
  315. switch ((value.first as css.LiteralTerm).text) {
  316. case 'outside':
  317. style.listStylePosition = ListStylePosition.outside;
  318. break;
  319. case 'inside':
  320. style.listStylePosition = ListStylePosition.inside;
  321. break;
  322. }
  323. }
  324. break;
  325. case 'height':
  326. style.height = ExpressionMapping.expressionToHeight(value.first) ?? style.height;
  327. break;
  328. case 'list-style-type':
  329. if (value.first is css.LiteralTerm) {
  330. style.listStyleType =
  331. ExpressionMapping.expressionToListStyleType(value.first as css.LiteralTerm) ?? style.listStyleType;
  332. }
  333. break;
  334. case 'margin':
  335. List<css.LiteralTerm>? marginLengths = value.whereType<css.LiteralTerm>().toList();
  336. /// List<css.LiteralTerm> might include other values than the ones we want for margin length, so make sure to remove those before passing it to [ExpressionMapping]
  337. marginLengths.removeWhere((element) =>
  338. element is! css.LengthTerm &&
  339. element is! css.EmTerm &&
  340. element is! css.RemTerm &&
  341. element is! css.NumberTerm &&
  342. !(element.text == 'auto'));
  343. Margins margin = ExpressionMapping.expressionToMargins(marginLengths);
  344. style.margin = (style.margin ?? Margins.all(0)).copyWith(
  345. left: margin.left,
  346. right: margin.right,
  347. top: margin.top,
  348. bottom: margin.bottom,
  349. );
  350. break;
  351. case 'margin-left':
  352. style.margin =
  353. (style.margin ?? Margins.zero).copyWith(left: ExpressionMapping.expressionToMargin(value.first));
  354. break;
  355. case 'margin-right':
  356. style.margin =
  357. (style.margin ?? Margins.zero).copyWith(right: ExpressionMapping.expressionToMargin(value.first));
  358. break;
  359. case 'margin-top':
  360. style.margin =
  361. (style.margin ?? Margins.zero).copyWith(top: ExpressionMapping.expressionToMargin(value.first));
  362. break;
  363. case 'margin-bottom':
  364. style.margin =
  365. (style.margin ?? Margins.zero).copyWith(bottom: ExpressionMapping.expressionToMargin(value.first));
  366. break;
  367. case 'padding':
  368. List<css.LiteralTerm>? paddingLengths = value.whereType<css.LiteralTerm>().toList();
  369. /// List<css.LiteralTerm> might include other values than the ones we want for padding length, so make sure to remove those before passing it to [ExpressionMapping]
  370. paddingLengths.removeWhere((element) =>
  371. element is! css.LengthTerm &&
  372. element is! css.EmTerm &&
  373. element is! css.RemTerm &&
  374. element is! css.NumberTerm);
  375. List<double?> padding = ExpressionMapping.expressionToPadding(paddingLengths);
  376. style.padding = (style.padding ?? EdgeInsets.zero).copyWith(
  377. left: padding[0],
  378. right: padding[1],
  379. top: padding[2],
  380. bottom: padding[3],
  381. );
  382. break;
  383. case 'padding-left':
  384. style.padding = (style.padding ?? EdgeInsets.zero)
  385. .copyWith(left: ExpressionMapping.expressionToPaddingLength(value.first));
  386. break;
  387. case 'padding-right':
  388. style.padding = (style.padding ?? EdgeInsets.zero)
  389. .copyWith(right: ExpressionMapping.expressionToPaddingLength(value.first));
  390. break;
  391. case 'padding-top':
  392. style.padding = (style.padding ?? EdgeInsets.zero)
  393. .copyWith(top: ExpressionMapping.expressionToPaddingLength(value.first));
  394. break;
  395. case 'padding-bottom':
  396. style.padding = (style.padding ?? EdgeInsets.zero)
  397. .copyWith(bottom: ExpressionMapping.expressionToPaddingLength(value.first));
  398. break;
  399. case 'text-align':
  400. style.textAlign = ExpressionMapping.expressionToTextAlign(value.first);
  401. break;
  402. case 'text-decoration':
  403. List<css.LiteralTerm?>? textDecorationList = value.whereType<css.LiteralTerm>().toList();
  404. /// List<css.LiteralTerm> might include other values than the ones we want for [textDecorationList], so make sure to remove those before passing it to [ExpressionMapping]
  405. textDecorationList.removeWhere((element) =>
  406. element == null ||
  407. (element.text != 'none' &&
  408. element.text != 'overline' &&
  409. element.text != 'underline' &&
  410. element.text != 'line-through'));
  411. List<css.Expression?>? nullableList = value;
  412. css.Expression? textDecorationColor;
  413. textDecorationColor =
  414. nullableList.firstWhereOrNull((element) => element is css.HexColorTerm || element is css.FunctionTerm);
  415. List<css.LiteralTerm?>? potentialStyles = value.whereType<css.LiteralTerm>().toList();
  416. /// List<css.LiteralTerm> might include other values than the ones we want for [textDecorationStyle], so make sure to remove those before passing it to [ExpressionMapping]
  417. potentialStyles.removeWhere((element) =>
  418. element == null ||
  419. (element.text != 'solid' &&
  420. element.text != 'double' &&
  421. element.text != 'dashed' &&
  422. element.text != 'dotted' &&
  423. element.text != 'wavy'));
  424. css.LiteralTerm? textDecorationStyle = potentialStyles.isNotEmpty ? potentialStyles.last : null;
  425. style.textDecoration = ExpressionMapping.expressionToTextDecorationLine(textDecorationList);
  426. if (textDecorationColor != null) {
  427. style.textDecorationColor =
  428. ExpressionMapping.expressionToColor(textDecorationColor) ?? style.textDecorationColor;
  429. }
  430. if (textDecorationStyle != null) {
  431. style.textDecorationStyle = ExpressionMapping.expressionToTextDecorationStyle(textDecorationStyle);
  432. }
  433. break;
  434. case 'text-decoration-color':
  435. style.textDecorationColor = ExpressionMapping.expressionToColor(value.first) ?? style.textDecorationColor;
  436. break;
  437. case 'text-decoration-line':
  438. List<css.LiteralTerm?>? textDecorationList = value.whereType<css.LiteralTerm>().toList();
  439. style.textDecoration = ExpressionMapping.expressionToTextDecorationLine(textDecorationList);
  440. break;
  441. case 'text-decoration-style':
  442. style.textDecorationStyle = ExpressionMapping.expressionToTextDecorationStyle(value.first as css.LiteralTerm);
  443. break;
  444. case 'text-shadow':
  445. style.textShadow = ExpressionMapping.expressionToTextShadow(value);
  446. break;
  447. case 'text-transform':
  448. final val = (value.first as css.LiteralTerm).text;
  449. if (val == 'uppercase') {
  450. style.textTransform = TextTransform.uppercase;
  451. } else if (val == 'lowercase') {
  452. style.textTransform = TextTransform.lowercase;
  453. } else if (val == 'capitalize') {
  454. style.textTransform = TextTransform.capitalize;
  455. } else {
  456. style.textTransform = TextTransform.none;
  457. }
  458. break;
  459. case 'width':
  460. style.width = ExpressionMapping.expressionToWidth(value.first) ?? style.width;
  461. break;
  462. }
  463. }
  464. });
  465. return style;
  466. }
  467. Style? inlineCssToStyle(String? inlineStyle, OnCssParseError? errorHandler) {
  468. var errors = <cssparser.Message>[];
  469. final sheet = cssparser.parse('*{$inlineStyle}', errors: errors);
  470. if (errors.isEmpty) {
  471. final declarations = DeclarationVisitor().getDeclarations(sheet);
  472. return declarationsToStyle(declarations['*']!);
  473. } else if (errorHandler != null) {
  474. String? newCss = errorHandler.call(inlineStyle ?? '', errors);
  475. if (newCss != null) {
  476. return inlineCssToStyle(newCss, errorHandler);
  477. }
  478. }
  479. return null;
  480. }
  481. Map<String, Map<String, List<css.Expression>>> parseExternalCss(String css, OnCssParseError? errorHandler) {
  482. var errors = <cssparser.Message>[];
  483. final sheet = cssparser.parse(css, errors: errors);
  484. if (errors.isEmpty) {
  485. return DeclarationVisitor().getDeclarations(sheet);
  486. } else if (errorHandler != null) {
  487. String? newCss = errorHandler.call(css, errors);
  488. if (newCss != null) {
  489. return parseExternalCss(newCss, errorHandler);
  490. }
  491. }
  492. return {};
  493. }
  494. class DeclarationVisitor extends css.Visitor {
  495. final Map<String, Map<String, List<css.Expression>>> _result = {};
  496. final Map<String, List<css.Expression>> _properties = {};
  497. late String _selector;
  498. late String _currentProperty;
  499. Map<String, Map<String, List<css.Expression>>> getDeclarations(css.StyleSheet sheet) {
  500. for (var element in sheet.topLevels) {
  501. if (element.span != null) {
  502. _selector = element.span!.text;
  503. element.visit(this);
  504. if (_result[_selector] != null) {
  505. _properties.forEach((key, value) {
  506. if (_result[_selector]![key] != null) {
  507. _result[_selector]![key]!.addAll(List<css.Expression>.from(value));
  508. } else {
  509. _result[_selector]![key] = List<css.Expression>.from(value);
  510. }
  511. });
  512. } else {
  513. _result[_selector] = Map<String, List<css.Expression>>.from(_properties);
  514. }
  515. _properties.clear();
  516. }
  517. }
  518. return _result;
  519. }
  520. @override
  521. void visitDeclaration(css.Declaration node) {
  522. _currentProperty = node.property;
  523. _properties[_currentProperty] = <css.Expression>[];
  524. node.expression!.visit(this);
  525. }
  526. @override
  527. void visitExpressions(css.Expressions node) {
  528. if (_properties[_currentProperty] != null) {
  529. _properties[_currentProperty]!.addAll(node.expressions);
  530. } else {
  531. _properties[_currentProperty] = node.expressions;
  532. }
  533. }
  534. }
  535. //Mapping functions
  536. class ExpressionMapping {
  537. static Border expressionToBorder(
  538. List<css.Expression?>? borderWidths, List<css.LiteralTerm?>? borderStyles, List<css.Expression?>? borderColors) {
  539. CustomBorderSide left = CustomBorderSide();
  540. CustomBorderSide top = CustomBorderSide();
  541. CustomBorderSide right = CustomBorderSide();
  542. CustomBorderSide bottom = CustomBorderSide();
  543. if (borderWidths != null && borderWidths.isNotEmpty) {
  544. top.width = expressionToBorderWidth(borderWidths.first);
  545. if (borderWidths.length == 4) {
  546. right.width = expressionToBorderWidth(borderWidths[1]);
  547. bottom.width = expressionToBorderWidth(borderWidths[2]);
  548. left.width = expressionToBorderWidth(borderWidths.last);
  549. }
  550. if (borderWidths.length == 3) {
  551. left.width = expressionToBorderWidth(borderWidths[1]);
  552. right.width = expressionToBorderWidth(borderWidths[1]);
  553. bottom.width = expressionToBorderWidth(borderWidths.last);
  554. }
  555. if (borderWidths.length == 2) {
  556. bottom.width = expressionToBorderWidth(borderWidths.first);
  557. left.width = expressionToBorderWidth(borderWidths.last);
  558. right.width = expressionToBorderWidth(borderWidths.last);
  559. }
  560. if (borderWidths.length == 1) {
  561. bottom.width = expressionToBorderWidth(borderWidths.first);
  562. left.width = expressionToBorderWidth(borderWidths.first);
  563. right.width = expressionToBorderWidth(borderWidths.first);
  564. }
  565. }
  566. if (borderStyles != null && borderStyles.isNotEmpty) {
  567. top.style = expressionToBorderStyle(borderStyles.first);
  568. if (borderStyles.length == 4) {
  569. right.style = expressionToBorderStyle(borderStyles[1]);
  570. bottom.style = expressionToBorderStyle(borderStyles[2]);
  571. left.style = expressionToBorderStyle(borderStyles.last);
  572. }
  573. if (borderStyles.length == 3) {
  574. left.style = expressionToBorderStyle(borderStyles[1]);
  575. right.style = expressionToBorderStyle(borderStyles[1]);
  576. bottom.style = expressionToBorderStyle(borderStyles.last);
  577. }
  578. if (borderStyles.length == 2) {
  579. bottom.style = expressionToBorderStyle(borderStyles.first);
  580. left.style = expressionToBorderStyle(borderStyles.last);
  581. right.style = expressionToBorderStyle(borderStyles.last);
  582. }
  583. if (borderStyles.length == 1) {
  584. bottom.style = expressionToBorderStyle(borderStyles.first);
  585. left.style = expressionToBorderStyle(borderStyles.first);
  586. right.style = expressionToBorderStyle(borderStyles.first);
  587. }
  588. }
  589. if (borderColors != null && borderColors.isNotEmpty) {
  590. top.color = expressionToColor(borderColors.first);
  591. if (borderColors.length == 4) {
  592. right.color = expressionToColor(borderColors[1]);
  593. bottom.color = expressionToColor(borderColors[2]);
  594. left.color = expressionToColor(borderColors.last);
  595. }
  596. if (borderColors.length == 3) {
  597. left.color = expressionToColor(borderColors[1]);
  598. right.color = expressionToColor(borderColors[1]);
  599. bottom.color = expressionToColor(borderColors.last);
  600. }
  601. if (borderColors.length == 2) {
  602. bottom.color = expressionToColor(borderColors.first);
  603. left.color = expressionToColor(borderColors.last);
  604. right.color = expressionToColor(borderColors.last);
  605. }
  606. if (borderColors.length == 1) {
  607. bottom.color = expressionToColor(borderColors.first);
  608. left.color = expressionToColor(borderColors.first);
  609. right.color = expressionToColor(borderColors.first);
  610. }
  611. }
  612. return Border(
  613. top: BorderSide(width: top.width, color: top.color ?? Colors.black, style: top.style),
  614. right: BorderSide(width: right.width, color: right.color ?? Colors.black, style: right.style),
  615. bottom: BorderSide(width: bottom.width, color: bottom.color ?? Colors.black, style: bottom.style),
  616. left: BorderSide(width: left.width, color: left.color ?? Colors.black, style: left.style));
  617. }
  618. static double expressionToBorderWidth(css.Expression? value) {
  619. if (value is css.NumberTerm) {
  620. return double.tryParse(value.text) ?? 1.0;
  621. } else if (value is css.PercentageTerm) {
  622. return (double.tryParse(value.text) ?? 400) / 100;
  623. } else if (value is css.EmTerm) {
  624. return double.tryParse(value.text) ?? 1.0;
  625. } else if (value is css.RemTerm) {
  626. return double.tryParse(value.text) ?? 1.0;
  627. } else if (value is css.LengthTerm) {
  628. return double.tryParse(value.text.replaceAll(RegExp(r'\s+(\d+\.\d+)\s+'), '')) ?? 1.0;
  629. } else if (value is css.LiteralTerm) {
  630. switch (value.text) {
  631. case 'thin':
  632. return 2.0;
  633. case 'medium':
  634. return 4.0;
  635. case 'thick':
  636. return 6.0;
  637. }
  638. }
  639. return 4.0;
  640. }
  641. static BorderStyle expressionToBorderStyle(css.LiteralTerm? value) {
  642. if (value != null && value.text != 'none' && value.text != 'hidden') {
  643. return BorderStyle.solid;
  644. }
  645. return BorderStyle.none;
  646. }
  647. static Color? expressionToColor(css.Expression? value) {
  648. if (value != null) {
  649. if (value is css.HexColorTerm) {
  650. return stringToColor(value.text);
  651. } else if (value is css.FunctionTerm) {
  652. if (value.text == 'rgba' || value.text == 'rgb') {
  653. return rgbOrRgbaToColor(value.span!.text);
  654. } else if (value.text == 'hsla' || value.text == 'hsl') {
  655. return hslToRgbToColor(value.span!.text);
  656. }
  657. } else if (value is css.LiteralTerm) {
  658. return namedColorToColor(value.text);
  659. }
  660. }
  661. return null;
  662. }
  663. static TextDirection expressionToDirection(css.Expression value) {
  664. if (value is css.LiteralTerm) {
  665. switch (value.text) {
  666. case 'ltr':
  667. return TextDirection.ltr;
  668. case 'rtl':
  669. return TextDirection.rtl;
  670. }
  671. }
  672. return TextDirection.ltr;
  673. }
  674. static Display expressionToDisplay(css.Expression value) {
  675. if (value is css.LiteralTerm) {
  676. switch (value.text) {
  677. case 'block':
  678. return Display.block;
  679. case 'inline-block':
  680. return Display.inlineBlock;
  681. case 'inline':
  682. return Display.inline;
  683. case 'list-item':
  684. return Display.listItem;
  685. case 'none':
  686. return Display.none;
  687. }
  688. }
  689. return Display.inline;
  690. }
  691. static List<FontFeature> expressionToFontFeatureSettings(List<css.Expression> value) {
  692. List<FontFeature> fontFeatures = [];
  693. for (int i = 0; i < value.length; i++) {
  694. css.Expression exp = value[i];
  695. if (exp is css.LiteralTerm) {
  696. if (exp.text != 'on' && exp.text != 'off' && exp.text != '1' && exp.text != '0') {
  697. if (i < value.length - 1) {
  698. css.Expression nextExp = value[i + 1];
  699. if (nextExp is css.LiteralTerm &&
  700. (nextExp.text == 'on' || nextExp.text == 'off' || nextExp.text == '1' || nextExp.text == '0')) {
  701. fontFeatures.add(FontFeature(exp.text, nextExp.text == 'on' || nextExp.text == '1' ? 1 : 0));
  702. } else {
  703. fontFeatures.add(FontFeature.enable(exp.text));
  704. }
  705. } else {
  706. fontFeatures.add(FontFeature.enable(exp.text));
  707. }
  708. }
  709. }
  710. }
  711. List<FontFeature> finalFontFeatures = fontFeatures.toSet().toList();
  712. return finalFontFeatures;
  713. }
  714. static FontSize? expressionToFontSize(css.Expression value) {
  715. if (value is css.NumberTerm) {
  716. return FontSize(double.tryParse(value.text) ?? 16, Unit.px);
  717. } else if (value is css.PercentageTerm) {
  718. return FontSize(double.tryParse(value.text) ?? 100, Unit.percent);
  719. } else if (value is css.EmTerm) {
  720. return FontSize(double.tryParse(value.text) ?? 1, Unit.em);
  721. } else if (value is css.LengthTerm) {
  722. return FontSize(double.tryParse(value.text.replaceAll(RegExp(r'\s+(\d+\.\d+)\s+'), '')) ?? 16);
  723. } else if (value is css.LiteralTerm) {
  724. switch (value.text) {
  725. case 'xx-small':
  726. return FontSize.xxSmall;
  727. case 'x-small':
  728. return FontSize.xSmall;
  729. case 'small':
  730. return FontSize.small;
  731. case 'medium':
  732. return FontSize.medium;
  733. case 'large':
  734. return FontSize.large;
  735. case 'x-large':
  736. return FontSize.xLarge;
  737. case 'xx-large':
  738. return FontSize.xxLarge;
  739. }
  740. }
  741. return null;
  742. }
  743. static FontStyle expressionToFontStyle(css.Expression value) {
  744. if (value is css.LiteralTerm) {
  745. switch (value.text) {
  746. case 'italic':
  747. case 'oblique':
  748. return FontStyle.italic;
  749. }
  750. return FontStyle.normal;
  751. }
  752. return FontStyle.normal;
  753. }
  754. static FontWeight expressionToFontWeight(css.Expression value) {
  755. if (value is css.NumberTerm) {
  756. switch (value.text) {
  757. case '100':
  758. return FontWeight.w100;
  759. case '200':
  760. return FontWeight.w200;
  761. case '300':
  762. return FontWeight.w300;
  763. case '400':
  764. return FontWeight.w400;
  765. case '500':
  766. return FontWeight.w500;
  767. case '600':
  768. return FontWeight.w600;
  769. case '700':
  770. return FontWeight.w700;
  771. case '800':
  772. return FontWeight.w800;
  773. case '900':
  774. return FontWeight.w900;
  775. }
  776. } else if (value is css.LiteralTerm) {
  777. switch (value.text) {
  778. case 'bold':
  779. return FontWeight.bold;
  780. case 'bolder':
  781. return FontWeight.w900;
  782. case 'lighter':
  783. return FontWeight.w200;
  784. }
  785. return FontWeight.normal;
  786. }
  787. return FontWeight.normal;
  788. }
  789. static String? expressionToFontFamily(css.Expression value) {
  790. if (value is css.LiteralTerm) return value.text;
  791. return null;
  792. }
  793. static LineHeight expressionToLineHeight(css.Expression value) {
  794. if (value is css.NumberTerm) {
  795. return LineHeight.number(double.tryParse(value.text)!);
  796. } else if (value is css.PercentageTerm) {
  797. return LineHeight.percent(double.tryParse(value.text)!);
  798. } else if (value is css.EmTerm) {
  799. return LineHeight.em(double.tryParse(value.text)!);
  800. } else if (value is css.RemTerm) {
  801. return LineHeight.rem(double.tryParse(value.text)!);
  802. } else if (value is css.LengthTerm) {
  803. return LineHeight(double.tryParse(value.text.replaceAll(RegExp(r'\s+(\d+\.\d+)\s+'), '')), units: 'length');
  804. }
  805. return LineHeight.normal;
  806. }
  807. static ListStyleImage? expressionToListStyleImage(css.UriTerm value) {
  808. return ListStyleImage(value.text);
  809. }
  810. static ListStyleType? expressionToListStyleType(css.LiteralTerm value) {
  811. return ListStyleType.fromName(value.text);
  812. }
  813. static Width? expressionToWidth(css.Expression value) {
  814. if ((value is css.LiteralTerm) && value.text == 'auto') {
  815. return Width.auto();
  816. } else {
  817. final computedValue = expressionToLengthOrPercent(value);
  818. return Width(computedValue.value, computedValue.unit);
  819. }
  820. }
  821. static Height? expressionToHeight(css.Expression value) {
  822. if ((value is css.LiteralTerm) && value.text == 'auto') {
  823. return Height.auto();
  824. } else {
  825. final computedValue = expressionToLengthOrPercent(value);
  826. return Height(computedValue.value, computedValue.unit);
  827. }
  828. }
  829. static Margin? expressionToMargin(css.Expression value) {
  830. if ((value is css.LiteralTerm) && value.text == 'auto') {
  831. return Margin.auto();
  832. } else {
  833. final computedValue = expressionToLengthOrPercent(value);
  834. return Margin(computedValue.value, computedValue.unit);
  835. }
  836. }
  837. static Margins expressionToMargins(List<css.Expression>? lengths) {
  838. Margin? left;
  839. Margin? right;
  840. Margin? top;
  841. Margin? bottom;
  842. if (lengths != null && lengths.isNotEmpty) {
  843. top = expressionToMargin(lengths.first);
  844. if (lengths.length == 4) {
  845. right = expressionToMargin(lengths[1]);
  846. bottom = expressionToMargin(lengths[2]);
  847. left = expressionToMargin(lengths.last);
  848. }
  849. if (lengths.length == 3) {
  850. left = expressionToMargin(lengths[1]);
  851. right = expressionToMargin(lengths[1]);
  852. bottom = expressionToMargin(lengths.last);
  853. }
  854. if (lengths.length == 2) {
  855. bottom = expressionToMargin(lengths.first);
  856. left = expressionToMargin(lengths.last);
  857. right = expressionToMargin(lengths.last);
  858. }
  859. if (lengths.length == 1) {
  860. bottom = expressionToMargin(lengths.first);
  861. left = expressionToMargin(lengths.first);
  862. right = expressionToMargin(lengths.first);
  863. }
  864. }
  865. return Margins(left: left, right: right, top: top, bottom: bottom);
  866. }
  867. static List<double?> expressionToPadding(List<css.Expression>? lengths) {
  868. double? left;
  869. double? right;
  870. double? top;
  871. double? bottom;
  872. if (lengths != null && lengths.isNotEmpty) {
  873. top = expressionToPaddingLength(lengths.first);
  874. if (lengths.length == 4) {
  875. right = expressionToPaddingLength(lengths[1]);
  876. bottom = expressionToPaddingLength(lengths[2]);
  877. left = expressionToPaddingLength(lengths.last);
  878. }
  879. if (lengths.length == 3) {
  880. left = expressionToPaddingLength(lengths[1]);
  881. right = expressionToPaddingLength(lengths[1]);
  882. bottom = expressionToPaddingLength(lengths.last);
  883. }
  884. if (lengths.length == 2) {
  885. bottom = expressionToPaddingLength(lengths.first);
  886. left = expressionToPaddingLength(lengths.last);
  887. right = expressionToPaddingLength(lengths.last);
  888. }
  889. if (lengths.length == 1) {
  890. bottom = expressionToPaddingLength(lengths.first);
  891. left = expressionToPaddingLength(lengths.first);
  892. right = expressionToPaddingLength(lengths.first);
  893. }
  894. }
  895. return [left, right, top, bottom];
  896. }
  897. static double? expressionToPaddingLength(css.Expression value) {
  898. if (value is css.NumberTerm) {
  899. return double.tryParse(value.text);
  900. } else if (value is css.EmTerm) {
  901. return double.tryParse(value.text);
  902. } else if (value is css.RemTerm) {
  903. return double.tryParse(value.text);
  904. } else if (value is css.LengthTerm) {
  905. return double.tryParse(value.text.replaceAll(RegExp(r'\s+(\d+\.\d+)\s+'), ''));
  906. }
  907. return null;
  908. }
  909. static LengthOrPercent expressionToLengthOrPercent(css.Expression value) {
  910. if (value is css.NumberTerm) {
  911. return LengthOrPercent(double.parse(value.text));
  912. } else if (value is css.EmTerm) {
  913. return LengthOrPercent(double.parse(value.text), Unit.em);
  914. } else if (value is css.LengthTerm) {
  915. double number = double.parse(value.text.replaceAll(RegExp(r'\s+(\d+\.\d+)\s+'), ''));
  916. Unit unit = _unitMap(value.unit);
  917. return LengthOrPercent(number, unit);
  918. }
  919. //Ignore unparsable input
  920. return LengthOrPercent(0);
  921. }
  922. static Unit _unitMap(int cssParserUnitToken) {
  923. switch (cssParserUnitToken) {
  924. default:
  925. return Unit.px;
  926. }
  927. }
  928. static TextAlign expressionToTextAlign(css.Expression value) {
  929. if (value is css.LiteralTerm) {
  930. switch (value.text) {
  931. case 'center':
  932. return TextAlign.center;
  933. case 'left':
  934. return TextAlign.left;
  935. case 'right':
  936. return TextAlign.right;
  937. case 'justify':
  938. return TextAlign.justify;
  939. case 'end':
  940. return TextAlign.end;
  941. case 'start':
  942. return TextAlign.start;
  943. }
  944. }
  945. return TextAlign.start;
  946. }
  947. static TextDecoration expressionToTextDecorationLine(List<css.LiteralTerm?> value) {
  948. List<TextDecoration> decorationList = [];
  949. for (css.LiteralTerm? term in value) {
  950. if (term != null) {
  951. switch (term.text) {
  952. case 'overline':
  953. decorationList.add(TextDecoration.overline);
  954. break;
  955. case 'underline':
  956. decorationList.add(TextDecoration.underline);
  957. break;
  958. case 'line-through':
  959. decorationList.add(TextDecoration.lineThrough);
  960. break;
  961. default:
  962. decorationList.add(TextDecoration.none);
  963. break;
  964. }
  965. }
  966. }
  967. if (decorationList.contains(TextDecoration.none)) {
  968. decorationList = [TextDecoration.none];
  969. }
  970. return TextDecoration.combine(decorationList);
  971. }
  972. static TextDecorationStyle expressionToTextDecorationStyle(css.LiteralTerm value) {
  973. switch (value.text) {
  974. case 'wavy':
  975. return TextDecorationStyle.wavy;
  976. case 'dotted':
  977. return TextDecorationStyle.dotted;
  978. case 'dashed':
  979. return TextDecorationStyle.dashed;
  980. case 'double':
  981. return TextDecorationStyle.double;
  982. default:
  983. return TextDecorationStyle.solid;
  984. }
  985. }
  986. static List<Shadow> expressionToTextShadow(List<css.Expression> value) {
  987. List<Shadow> shadow = [];
  988. List<int> indices = [];
  989. List<List<css.Expression>> valueList = [];
  990. for (css.Expression e in value) {
  991. if (e is css.OperatorComma) {
  992. indices.add(value.indexOf(e));
  993. }
  994. }
  995. indices.add(value.length);
  996. int previousIndex = 0;
  997. for (int i in indices) {
  998. valueList.add(value.sublist(previousIndex, i));
  999. previousIndex = i + 1;
  1000. }
  1001. for (List<css.Expression> list in valueList) {
  1002. css.Expression? offsetX;
  1003. css.Expression? offsetY;
  1004. css.Expression? blurRadius;
  1005. css.Expression? color;
  1006. int expressionIndex = 0;
  1007. for (var element in list) {
  1008. if (element is css.HexColorTerm || element is css.FunctionTerm) {
  1009. color = element;
  1010. } else if (expressionIndex == 0) {
  1011. offsetX = element;
  1012. expressionIndex++;
  1013. } else if (expressionIndex++ == 1) {
  1014. offsetY = element;
  1015. expressionIndex++;
  1016. } else {
  1017. blurRadius = element;
  1018. }
  1019. }
  1020. RegExp nonNumberRegex = RegExp(r'\s+(\d+\.\d+)\s+');
  1021. if (offsetX is css.LiteralTerm && offsetY is css.LiteralTerm) {
  1022. if (color != null && ExpressionMapping.expressionToColor(color) != null) {
  1023. shadow.add(Shadow(
  1024. color: expressionToColor(color)!,
  1025. offset: Offset(double.tryParse((offsetX).text.replaceAll(nonNumberRegex, ''))!,
  1026. double.tryParse((offsetY).text.replaceAll(nonNumberRegex, ''))!),
  1027. blurRadius: (blurRadius is css.LiteralTerm)
  1028. ? double.tryParse((blurRadius).text.replaceAll(nonNumberRegex, ''))!
  1029. : 0.0,
  1030. ));
  1031. } else {
  1032. shadow.add(Shadow(
  1033. offset: Offset(double.tryParse((offsetX).text.replaceAll(nonNumberRegex, ''))!,
  1034. double.tryParse((offsetY).text.replaceAll(nonNumberRegex, ''))!),
  1035. blurRadius: (blurRadius is css.LiteralTerm)
  1036. ? double.tryParse((blurRadius).text.replaceAll(nonNumberRegex, ''))!
  1037. : 0.0,
  1038. ));
  1039. }
  1040. }
  1041. }
  1042. List<Shadow> finalShadows = shadow.toSet().toList();
  1043. return finalShadows;
  1044. }
  1045. static Color stringToColor(String rawText) {
  1046. var text = rawText.replaceFirst('#', '');
  1047. if (text.length == 3) {
  1048. text = text.replaceAllMapped(
  1049. RegExp(r'[a-f]|\d', caseSensitive: false), (match) => '${match.group(0)}${match.group(0)}');
  1050. }
  1051. if (text.length > 6) {
  1052. text = '0x$text';
  1053. } else {
  1054. text = '0xFF$text';
  1055. }
  1056. return Color(int.parse(text));
  1057. }
  1058. static Color? rgbOrRgbaToColor(String text) {
  1059. final rgbaText = text.replaceAll(')', '').replaceAll(' ', '');
  1060. try {
  1061. final rgbaValues = rgbaText.split(',').map((value) => double.parse(value)).toList();
  1062. if (rgbaValues.length == 4) {
  1063. return Color.fromRGBO(
  1064. rgbaValues[0].toInt(),
  1065. rgbaValues[1].toInt(),
  1066. rgbaValues[2].toInt(),
  1067. rgbaValues[3],
  1068. );
  1069. } else if (rgbaValues.length == 3) {
  1070. return Color.fromRGBO(
  1071. rgbaValues[0].toInt(),
  1072. rgbaValues[1].toInt(),
  1073. rgbaValues[2].toInt(),
  1074. 1.0,
  1075. );
  1076. }
  1077. return null;
  1078. } catch (e) {
  1079. return null;
  1080. }
  1081. }
  1082. static Color hslToRgbToColor(String text) {
  1083. final hslText = text.replaceAll(')', '').replaceAll(' ', '');
  1084. final hslValues = hslText.split(',').toList();
  1085. List<double?> parsedHsl = [];
  1086. for (var element in hslValues) {
  1087. if (element.contains('%') && double.tryParse(element.replaceAll('%', '')) != null) {
  1088. parsedHsl.add(double.tryParse(element.replaceAll('%', ''))! * 0.01);
  1089. } else {
  1090. if (element != hslValues.first && (double.tryParse(element) == null || double.tryParse(element)! > 1)) {
  1091. parsedHsl.add(null);
  1092. } else {
  1093. parsedHsl.add(double.tryParse(element));
  1094. }
  1095. }
  1096. }
  1097. if (parsedHsl.length == 4 && !parsedHsl.contains(null)) {
  1098. return HSLColor.fromAHSL(parsedHsl.last!, parsedHsl.first!, parsedHsl[1]!, parsedHsl[2]!).toColor();
  1099. } else if (parsedHsl.length == 3 && !parsedHsl.contains(null)) {
  1100. return HSLColor.fromAHSL(1.0, parsedHsl.first!, parsedHsl[1]!, parsedHsl.last!).toColor();
  1101. } else {
  1102. return Colors.black;
  1103. }
  1104. }
  1105. static Color? namedColorToColor(String text) {
  1106. String namedColor =
  1107. namedColors.keys.firstWhere((element) => element.toLowerCase() == text.toLowerCase(), orElse: () => '');
  1108. if (namedColor != '') {
  1109. return stringToColor(namedColors[namedColor]!);
  1110. } else {
  1111. return null;
  1112. }
  1113. }
  1114. }