Skip to content

Commit b4eb198

Browse files
#19: Added inline color support
1 parent 6a8e58b commit b4eb198

9 files changed

+126
-5
lines changed

example/lib/main.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const htmlData = """
4646
<h3>Support for <code>sub</code>/<code>sup</code></h3>
4747
Solve for <var>x<sub>n</sub></var>: log<sub>2</sub>(<var>x</var><sup>2</sup>+<var>n</var>) = 9<sup>3</sup>
4848
<p>One of the most <span>common</span> equations in all of physics is <br /><var>E</var>=<var>m</var><var>c</var><sup>2</sup>.</p>
49+
<h3>Inline Styles:</h3>
50+
<p>The should be <span style='color: blue;'>BLUE style='color: blue;'</span></p>
51+
<p>The should be <span style='color: red;'>RED style='color: red;'</span></p>
52+
<p>The should be <span style='color: rgba(0, 0, 0, 0.10);'>BLACK with 10% alpha style='color: rgba(0, 0, 0, 0.10);</span></p>
53+
<p>The should be <span style='color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
4954
<h3>Table support (with custom styling!):</h3>
5055
<p>
5156
<q>Famous quote...</q>

lib/html_parser.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:csslib/parser.dart' as cssparser;
55
import 'package:csslib/visitor.dart' as css;
66
import 'package:flutter/material.dart';
77
import 'package:flutter_html/flutter_html.dart';
8+
import 'package:flutter_html/src/css_parser.dart';
89
import 'package:flutter_html/src/html_elements.dart';
910
import 'package:flutter_html/src/layout_element.dart';
1011
import 'package:flutter_html/src/utils.dart';
@@ -152,10 +153,12 @@ class HtmlParser extends StatelessWidget {
152153
return tree;
153154
}
154155

155-
///TODO document
156156
static StyledElement applyInlineStyles(StyledElement tree) {
157-
//TODO
157+
if (tree.attributes.containsKey("style")) {
158+
tree.style = tree.style.merge(inlineCSSToStyle(tree.attributes['style']));
159+
}
158160

161+
tree.children?.forEach(applyInlineStyles);
159162
return tree;
160163
}
161164

lib/src/css_parser.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import 'dart:ui';
2+
3+
import 'package:csslib/visitor.dart' as css;
4+
import 'package:csslib/parser.dart' as cssparser;
5+
import 'package:flutter_html/style.dart';
6+
7+
Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
8+
Style style = new Style();
9+
declarations.forEach((property, value) {
10+
switch (property) {
11+
case 'background-color':
12+
style.backgroundColor =
13+
ExpressionMapping.expressionToColor(value.first);
14+
break;
15+
case 'color':
16+
style.color = ExpressionMapping.expressionToColor(value.first);
17+
break;
18+
}
19+
});
20+
return style;
21+
}
22+
23+
Style inlineCSSToStyle(String inlineStyle) {
24+
final sheet = cssparser.parse("*{$inlineStyle}");
25+
final declarations = DeclarationVisitor().getDeclarations(sheet);
26+
return declarationsToStyle(declarations);
27+
}
28+
29+
class DeclarationVisitor extends css.Visitor {
30+
Map<String, List<css.Expression>> _result;
31+
String _currentProperty;
32+
33+
Map<String, List<css.Expression>> getDeclarations(css.StyleSheet sheet) {
34+
_result = new Map<String, List<css.Expression>>();
35+
sheet.visit(this);
36+
return _result;
37+
}
38+
39+
@override
40+
void visitDeclaration(css.Declaration node) {
41+
_currentProperty = node.property;
42+
_result[_currentProperty] = new List<css.Expression>();
43+
node.expression.visit(this);
44+
}
45+
46+
@override
47+
void visitExpressions(css.Expressions node) {
48+
node.expressions.forEach((expression) {
49+
_result[_currentProperty].add(expression);
50+
});
51+
}
52+
}
53+
54+
//Mapping functions
55+
class ExpressionMapping {
56+
static Color expressionToColor(css.Expression value) {
57+
if (value is css.HexColorTerm) {
58+
return stringToColor(value.text);
59+
} else if (value is css.FunctionTerm) {
60+
if (value.text == 'rgba') {
61+
return rgbOrRgbaToColor(value.span.text);
62+
} else if (value.text == 'rgb') {
63+
return rgbOrRgbaToColor(value.span.text);
64+
}
65+
}
66+
return null;
67+
}
68+
69+
static Color stringToColor(String _text) {
70+
var text = _text.replaceFirst('#', '');
71+
if (text.length == 3)
72+
text = text.replaceAllMapped(
73+
RegExp(r"[a-f]|\d"), (match) => '${match.group(0)}${match.group(0)}');
74+
int color = int.parse(text, radix: 16);
75+
76+
if (color <= 0xffffff) {
77+
return new Color(color).withAlpha(255);
78+
} else {
79+
return new Color(color);
80+
}
81+
}
82+
83+
static Color rgbOrRgbaToColor(String text) {
84+
final rgbaText = text.replaceAll(')', '').replaceAll(' ', '');
85+
try {
86+
final rgbaValues =
87+
rgbaText.split(',').map((value) => double.parse(value)).toList();
88+
if (rgbaValues.length == 4) {
89+
return Color.fromRGBO(
90+
rgbaValues[0].toInt(),
91+
rgbaValues[1].toInt(),
92+
rgbaValues[2].toInt(),
93+
rgbaValues[3],
94+
);
95+
} else if (rgbaValues.length == 3) {
96+
return Color.fromRGBO(
97+
rgbaValues[0].toInt(),
98+
rgbaValues[1].toInt(),
99+
rgbaValues[2].toInt(),
100+
1.0,
101+
);
102+
}
103+
return null;
104+
} catch (e) {
105+
return null;
106+
}
107+
}
108+
}

lib/src/styled_element.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ class StyledElement {
2525
bool matchesSelector(String selector) =>
2626
_node != null && matches(_node, selector);
2727

28-
Map<String, String> get attributes => _node.attributes.map((key, value) {
28+
Map<String, String> get attributes =>
29+
_node?.attributes?.map((key, value) {
2930
return MapEntry(key, value);
30-
});
31+
}) ??
32+
Map<String, String>();
3133

3234
dom.Element get element => _node;
3335

test/golden_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void testHtml(String name, String htmlData) {
3232
),
3333
);
3434
expect(find.byType(Html), findsOneWidget);
35-
// await expectLater(find.byType(Html), matchesGoldenFile('./goldens/$name.png'));
35+
await expectLater(find.byType(Html), matchesGoldenFile('./goldens/$name.png'));
3636
});
3737
}
3838

3.5 KB
Loading
3.49 KB
Loading
3.49 KB
Loading

test/test_data.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ const testData = <String, String>{
5959
'section': '<section>Hello, World!</section>',
6060
'small': '<small>Hello, World!</small>',
6161
'span': '<span>Hello, World!</span>',
62+
'span-with-inline-styles': '<p>Hello, <span style="color: red">World!</span></p>',
63+
'span-with-inline-styles-rgb': '<p>Hello, <span style="color: rgb(252, 186, 3)">World!</span></p>',
64+
'span-with-inline-styles-rgba': '<p>Hello, <span style="color: rgba(252, 186, 3,0.5)">World!</span></p>',
6265
'strike': '<strike>Hello, World!</strike>',
6366
'strong': '<strong>Hello, World!</strong>',
6467
'sub': '<sub>Hello, World!</sub>',

0 commit comments

Comments
 (0)