diff --git a/.github/flutter_html_screenshot.png b/.github/flutter_html_screenshot.png index d8ecc1ac28..3d1b893b61 100644 Binary files a/.github/flutter_html_screenshot.png and b/.github/flutter_html_screenshot.png differ diff --git a/.github/flutter_html_screenshot2.png b/.github/flutter_html_screenshot2.png index dae0da9143..eb47b0e146 100644 Binary files a/.github/flutter_html_screenshot2.png and b/.github/flutter_html_screenshot2.png differ diff --git a/example/lib/main.dart b/example/lib/main.dart index b16831ba73..26c7173873 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -46,6 +46,16 @@ const htmlData = """

Support for sub/sup

Solve for xn: log2(x2+n) = 93

One of the most common equations in all of physics is
E=mc2.

+

Inline Styles:

+

The should be BLUE style='color: blue;'

+

The should be RED style='color: red;'

+

The should be BLACK with 10% alpha style='color: rgba(0, 0, 0, 0.10);

+

The should be GREEN style='color: rgb(0, 97, 0);

+

The should be GREEN style='color: rgb(0, 97, 0);

+

blasdafjklasdlkjfkl

+

blasdafjklasdlkjfkl

+

blasdafjklasdlkjfkl

+

blasdafjklasdlkjfkl

Table support (with custom styling!):

Famous quote... diff --git a/lib/html_parser.dart b/lib/html_parser.dart index da6601b887..f4787b14f6 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -5,6 +5,7 @@ import 'package:csslib/parser.dart' as cssparser; import 'package:csslib/visitor.dart' as css; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_html/src/css_parser.dart'; import 'package:flutter_html/src/html_elements.dart'; import 'package:flutter_html/src/layout_element.dart'; import 'package:flutter_html/src/utils.dart'; @@ -152,10 +153,12 @@ class HtmlParser extends StatelessWidget { return tree; } - ///TODO document static StyledElement applyInlineStyles(StyledElement tree) { - //TODO + if (tree.attributes.containsKey("style")) { + tree.style = tree.style.merge(inlineCSSToStyle(tree.attributes['style'])); + } + tree.children?.forEach(applyInlineStyles); return tree; } diff --git a/lib/src/css_parser.dart b/lib/src/css_parser.dart new file mode 100644 index 0000000000..7182ac17d6 --- /dev/null +++ b/lib/src/css_parser.dart @@ -0,0 +1,132 @@ +import 'dart:ui'; + +import 'package:csslib/visitor.dart' as css; +import 'package:csslib/parser.dart' as cssparser; +import 'package:flutter_html/style.dart'; + +Style declarationsToStyle(Map> declarations) { + Style style = new Style(); + declarations.forEach((property, value) { + switch (property) { + case 'background-color': + style.backgroundColor = + ExpressionMapping.expressionToColor(value.first); + break; + case 'color': + style.color = ExpressionMapping.expressionToColor(value.first); + break; + case 'text-align': + style.textAlign = ExpressionMapping.expressionToTextAlign(value.first); + break; + + } + }); + return style; +} + +Style inlineCSSToStyle(String inlineStyle) { + final sheet = cssparser.parse("*{$inlineStyle}"); + final declarations = DeclarationVisitor().getDeclarations(sheet); + return declarationsToStyle(declarations); +} + +class DeclarationVisitor extends css.Visitor { + Map> _result; + String _currentProperty; + + Map> getDeclarations(css.StyleSheet sheet) { + _result = new Map>(); + sheet.visit(this); + return _result; + } + + @override + void visitDeclaration(css.Declaration node) { + _currentProperty = node.property; + _result[_currentProperty] = new List(); + node.expression.visit(this); + } + + @override + void visitExpressions(css.Expressions node) { + node.expressions.forEach((expression) { + _result[_currentProperty].add(expression); + }); + } +} + +//Mapping functions +class ExpressionMapping { + static Color expressionToColor(css.Expression value) { + if (value is css.HexColorTerm) { + return stringToColor(value.text); + } else if (value is css.FunctionTerm) { + if (value.text == 'rgba') { + return rgbOrRgbaToColor(value.span.text); + } else if (value.text == 'rgb') { + return rgbOrRgbaToColor(value.span.text); + } + } + return null; + } + + static Color stringToColor(String _text) { + var text = _text.replaceFirst('#', ''); + if (text.length == 3) + text = text.replaceAllMapped( + RegExp(r"[a-f]|\d"), (match) => '${match.group(0)}${match.group(0)}'); + int color = int.parse(text, radix: 16); + + if (color <= 0xffffff) { + return new Color(color).withAlpha(255); + } else { + return new Color(color); + } + } + + static Color rgbOrRgbaToColor(String text) { + final rgbaText = text.replaceAll(')', '').replaceAll(' ', ''); + try { + final rgbaValues = + rgbaText.split(',').map((value) => double.parse(value)).toList(); + if (rgbaValues.length == 4) { + return Color.fromRGBO( + rgbaValues[0].toInt(), + rgbaValues[1].toInt(), + rgbaValues[2].toInt(), + rgbaValues[3], + ); + } else if (rgbaValues.length == 3) { + return Color.fromRGBO( + rgbaValues[0].toInt(), + rgbaValues[1].toInt(), + rgbaValues[2].toInt(), + 1.0, + ); + } + return null; + } catch (e) { + return null; + } + } + + static TextAlign expressionToTextAlign(css.Expression value) { + if (value is css.LiteralTerm) { + switch(value.text) { + case "center": + return TextAlign.center; + case "left": + return TextAlign.left; + case "right": + return TextAlign.right; + case "justify": + return TextAlign.justify; + case "end": + return TextAlign.end; + case "start": + return TextAlign.start; + } + } + return TextAlign.start; + } +} diff --git a/lib/src/styled_element.dart b/lib/src/styled_element.dart index aaa0dc90e9..508b72fe3d 100644 --- a/lib/src/styled_element.dart +++ b/lib/src/styled_element.dart @@ -25,9 +25,11 @@ class StyledElement { bool matchesSelector(String selector) => _node != null && matches(_node, selector); - Map get attributes => _node.attributes.map((key, value) { + Map get attributes => + _node?.attributes?.map((key, value) { return MapEntry(key, value); - }); + }) ?? + Map(); dom.Element get element => _node; diff --git a/test/goldens/p-with-inline-css-text-align-center.png b/test/goldens/p-with-inline-css-text-align-center.png new file mode 100644 index 0000000000..e1a5195435 Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-center.png differ diff --git a/test/goldens/p-with-inline-css-text-align-end.png b/test/goldens/p-with-inline-css-text-align-end.png new file mode 100644 index 0000000000..f262fac575 Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-end.png differ diff --git a/test/goldens/p-with-inline-css-text-align-justify.png b/test/goldens/p-with-inline-css-text-align-justify.png new file mode 100644 index 0000000000..29d8b14aad Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-justify.png differ diff --git a/test/goldens/p-with-inline-css-text-align-left.png b/test/goldens/p-with-inline-css-text-align-left.png new file mode 100644 index 0000000000..29d8b14aad Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-left.png differ diff --git a/test/goldens/p-with-inline-css-text-align-right.png b/test/goldens/p-with-inline-css-text-align-right.png new file mode 100644 index 0000000000..f262fac575 Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-right.png differ diff --git a/test/goldens/p-with-inline-css-text-align-start.png b/test/goldens/p-with-inline-css-text-align-start.png new file mode 100644 index 0000000000..29d8b14aad Binary files /dev/null and b/test/goldens/p-with-inline-css-text-align-start.png differ diff --git a/test/goldens/span-with-inline-css-backgroundcolor-rgb.png b/test/goldens/span-with-inline-css-backgroundcolor-rgb.png new file mode 100644 index 0000000000..e895a3d1c3 Binary files /dev/null and b/test/goldens/span-with-inline-css-backgroundcolor-rgb.png differ diff --git a/test/goldens/span-with-inline-css-backgroundcolor-rgba.png b/test/goldens/span-with-inline-css-backgroundcolor-rgba.png new file mode 100644 index 0000000000..21451008d1 Binary files /dev/null and b/test/goldens/span-with-inline-css-backgroundcolor-rgba.png differ diff --git a/test/goldens/span-with-inline-css-backgroundcolor.png b/test/goldens/span-with-inline-css-backgroundcolor.png new file mode 100644 index 0000000000..e9d25a0a2b Binary files /dev/null and b/test/goldens/span-with-inline-css-backgroundcolor.png differ diff --git a/test/goldens/span-with-inline-css-color-rgb.png b/test/goldens/span-with-inline-css-color-rgb.png new file mode 100644 index 0000000000..24d5c73cf4 Binary files /dev/null and b/test/goldens/span-with-inline-css-color-rgb.png differ diff --git a/test/goldens/span-with-inline-css-color-rgba.png b/test/goldens/span-with-inline-css-color-rgba.png new file mode 100644 index 0000000000..1e6846e9e4 Binary files /dev/null and b/test/goldens/span-with-inline-css-color-rgba.png differ diff --git a/test/goldens/span-with-inline-css-color.png b/test/goldens/span-with-inline-css-color.png new file mode 100644 index 0000000000..aa8b473034 Binary files /dev/null and b/test/goldens/span-with-inline-css-color.png differ diff --git a/test/test_data.dart b/test/test_data.dart index a23d7c43b5..c2fc16a601 100644 --- a/test/test_data.dart +++ b/test/test_data.dart @@ -49,6 +49,12 @@ const testData = { 'nav': '

', 'noscript': '', 'p': '

Hello, World!

', + 'p-with-inline-css-text-align-center': '

Hello, World!

', + 'p-with-inline-css-text-align-right': '

Hello, World!

', + 'p-with-inline-css-text-align-left': '

Hello, World!

', + 'p-with-inline-css-text-align-justify': '

Hello, World!

', + 'p-with-inline-css-text-align-end': '

Hello, World!

', + 'p-with-inline-css-text-align-start': '

Hello, World!

', 'pre': '
Hello, World!
', 'q': 'Hello, World!', 'rp': ' ㄏㄢˋ ', @@ -59,6 +65,12 @@ const testData = { 'section': '
Hello, World!
', 'small': 'Hello, World!', 'span': 'Hello, World!', + 'span-with-inline-css-color': '

Hello, World!

', + 'span-with-inline-css-color-rgb': '

Hello, World!

', + 'span-with-inline-css-color-rgba': '

Hello, World!

', + 'span-with-inline-css-backgroundcolor': '

Hello, World!

', + 'span-with-inline-css-backgroundcolor-rgb': '

Hello, World!

', + 'span-with-inline-css-backgroundcolor-rgba': '

Hello, World!

', 'strike': 'Hello, World!', 'strong': 'Hello, World!', 'sub': 'Hello, World!',