Skip to content

Feature/#19 inline styles #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/flutter_html_screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .github/flutter_html_screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ const htmlData = """
<h3>Support for <code>sub</code>/<code>sup</code></h3>
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>
<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>
<h3>Inline Styles:</h3>
<p>The should be <span style='color: blue;'>BLUE style='color: blue;'</span></p>
<p>The should be <span style='color: red;'>RED style='color: red;'</span></p>
<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>
<p>The should be <span style='color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
<p>The should be <span style='background-color: red; color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
<p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: right;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: justify;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<h3>Table support (with custom styling!):</h3>
<p>
<q>Famous quote...</q>
Expand Down
7 changes: 5 additions & 2 deletions lib/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

Expand Down
132 changes: 132 additions & 0 deletions lib/src/css_parser.dart
Original file line number Diff line number Diff line change
@@ -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<String, List<css.Expression>> 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<String, List<css.Expression>> _result;
String _currentProperty;

Map<String, List<css.Expression>> getDeclarations(css.StyleSheet sheet) {
_result = new Map<String, List<css.Expression>>();
sheet.visit(this);
return _result;
}

@override
void visitDeclaration(css.Declaration node) {
_currentProperty = node.property;
_result[_currentProperty] = new List<css.Expression>();
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;
}
}
6 changes: 4 additions & 2 deletions lib/src/styled_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ class StyledElement {
bool matchesSelector(String selector) =>
_node != null && matches(_node, selector);

Map<String, String> get attributes => _node.attributes.map((key, value) {
Map<String, String> get attributes =>
_node?.attributes?.map((key, value) {
return MapEntry(key, value);
});
}) ??
Map<String, String>();

dom.Element get element => _node;

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/goldens/p-with-inline-css-text-align-end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/goldens/span-with-inline-css-color-rgb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/goldens/span-with-inline-css-color-rgba.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/goldens/span-with-inline-css-color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions test/test_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ const testData = <String, String>{
'nav': '<nav>Hello, World!</nav>',
'noscript': '<noscript>Hello, World!</noscript>',
'p': '<p>Hello, World!</p>',
'p-with-inline-css-text-align-center': '<p style="text-align: center;">Hello, World!</p>',
'p-with-inline-css-text-align-right': '<p style="text-align: right;">Hello, World!</p>',
'p-with-inline-css-text-align-left': '<p style="text-align: left;">Hello, World!</p>',
'p-with-inline-css-text-align-justify': '<p style="text-align: justify;">Hello, World!</p>',
'p-with-inline-css-text-align-end': '<p style="text-align: end;">Hello, World!</p>',
'p-with-inline-css-text-align-start': '<p style="text-align: start;">Hello, World!</p>',
'pre': '<pre>Hello, World!</pre>',
'q': '<q>Hello, World!</q>',
'rp': '<ruby>漢 <rp> ㄏㄢˋ </rp></ruby>',
Expand All @@ -59,6 +65,12 @@ const testData = <String, String>{
'section': '<section>Hello, World!</section>',
'small': '<small>Hello, World!</small>',
'span': '<span>Hello, World!</span>',
'span-with-inline-css-color': '<p>Hello, <span style="color: red;">World!</span></p>',
'span-with-inline-css-color-rgb': '<p>Hello, <span style="color: rgb(252, 186, 3);">World!</span></p>',
'span-with-inline-css-color-rgba': '<p>Hello, <span style="color: rgba(252, 186, 3,0.5);">World!</span></p>',
'span-with-inline-css-backgroundcolor': '<p>Hello, <span style="background-color: red; color: rgba(0, 0, 0,0.5);">World!</span></p>',
'span-with-inline-css-backgroundcolor-rgb': '<p>Hello, <span style="background-color: rgb(252, 186, 3); color: rgba(0, 0, 0,0.5);">World!</span></p>',
'span-with-inline-css-backgroundcolor-rgba': '<p>Hello, <span style="background-color: rgba(252, 186, 3,0.5); color: rgba(0, 0, 0,0.5);">World!</span></p>',
'strike': '<strike>Hello, World!</strike>',
'strong': '<strong>Hello, World!</strong>',
'sub': '<sub>Hello, World!</sub>',
Expand Down