Skip to content

Commit 31359b7

Browse files
Merge pull request Sub6Resources#197 from Sub6Resources/sub-sup
Add support for both sub and sup as well as add the css vertical-align property
2 parents f52af25 + 02084fe commit 31359b7

File tree

7 files changed

+152
-54
lines changed

7 files changed

+152
-54
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
[![pub package](https://img.shields.io/pub/v/flutter_html.svg)](https://pub.dev/packages/flutter_html)
33
[![codecov](https://codecov.io/gh/Sub6Resources/flutter_html/branch/master/graph/badge.svg)](https://codecov.io/gh/Sub6Resources/flutter_html)
44
[![CircleCI](https://circleci.com/gh/Sub6Resources/flutter_html.svg?style=svg)](https://circleci.com/gh/Sub6Resources/flutter_html)
5+
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Sub6Resources/flutter_html/blob/master/LICENSE)
56

67
A Flutter widget for rendering html and css as Flutter widgets.
78

example/lib/main.dart

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,21 @@ class MyHomePage extends StatefulWidget {
2929
}
3030

3131
const htmlData = """
32-
<p id='whitespace'>
33-
These two lines should have an identical length:<br /><br />
34-
35-
The quick <b> brown </b><u><i> fox </i></u> jumped over the
36-
lazy
37-
38-
39-
40-
41-
dog.<br />
42-
The quick brown fox jumped over the lazy dog.
43-
</p>
32+
<h1>Header 1</h1>
33+
<h2>Header 2</h2>
34+
<h3>Header 3</h3>
35+
<h4>Header 4</h4>
36+
<h5>Header 5</h5>
37+
<h6>Header 6</h6>
4438
<p>
4539
<ruby>
4640
漢<rt>かん</rt>
4741
字<rt>じ</rt>
4842
</ruby>
4943
&nbsp;is Japanese Kanji
5044
</p>
45+
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>
46+
<p>One of the most common equations in all of physics is <var>E</var>=<var>m</var><var>c</var><sup>2</sup>.<p>
5147
<table>
5248
<colgroup>
5349
<col width="50%" />
@@ -124,35 +120,34 @@ class _MyHomePageState extends State<MyHomePage> {
124120
// backgroundColor: Colors.red,
125121
// fontSize: 20,
126122
// margin: const EdgeInsets.only(top: 32),
127-
),
123+
),
128124
"h1, h3, h5": Style(
129125
// backgroundColor: Colors.deepPurple,
130126
// alignment: Alignment.center,
131-
),
127+
),
132128
"#whitespace": Style(
133-
backgroundColor: Colors.purple,
134-
),
135-
"table": Style(
136-
backgroundColor: Color.fromARGB(0x50, 0xee, 0xee, 0xee)
137-
),
138-
"tr": Style(
139-
border: Border(bottom: BorderSide(color: Colors.grey))
129+
backgroundColor: Colors.deepPurple,
140130
),
131+
"table": Style(backgroundColor: Color.fromARGB(0x50, 0xee, 0xee, 0xee)),
132+
"tr": Style(border: Border(bottom: BorderSide(color: Colors.grey))),
141133
"th": Style(
142134
padding: EdgeInsets.all(6),
143-
backgroundColor: Colors.grey
135+
backgroundColor: Colors.grey,
144136
),
145137
"td": Style(
146138
padding: EdgeInsets.all(6),
147-
backgroundColor: Colors.transparent
148-
)
139+
backgroundColor: Colors.transparent,
140+
),
141+
"var": Style(fontFamily: 'serif'),
149142
},
150143
customRender: {
151144
"flutter": (RenderContext context, Widget child, attributes) {
152145
return FlutterLogo(
153-
style: (attributes['horizontal'] != null)? FlutterLogoStyle.horizontal: FlutterLogoStyle.markOnly,
146+
style: (attributes['horizontal'] != null)
147+
? FlutterLogoStyle.horizontal
148+
: FlutterLogoStyle.markOnly,
154149
textColor: context.style.color,
155-
size: context.style.fontSize * 5,
150+
size: context.style.fontSize.size * 5,
156151
);
157152
}
158153
},
@@ -166,12 +161,14 @@ class _MyHomePageState extends State<MyHomePage> {
166161
),
167162
),
168163
Expanded(
169-
child: SingleChildScrollView(
170-
child: Text(
171-
HtmlParser.cleanTree(HtmlParser.applyCSS(HtmlParser.lexDomTree(HtmlParser.parseHTML(htmlData), [], []), null)).toString(),
172-
style: TextStyle(fontFamily: 'monospace'),
173-
),
164+
child: SingleChildScrollView(
165+
child: Text(
166+
HtmlParser.cleanTree(HtmlParser.applyCSS(
167+
HtmlParser.lexDomTree(HtmlParser.parseHTML(htmlData), [], []), null))
168+
.toString(),
169+
style: TextStyle(fontFamily: 'monospace'),
174170
),
171+
),
175172
)
176173
],
177174
),

lib/html_parser.dart

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class HtmlParser extends StatelessWidget {
177177
tree = _processListCharacters(tree);
178178
tree = _processBeforesAndAfters(tree);
179179
tree = _collapseMargins(tree);
180+
tree = _processFontSize(tree);
180181
return tree;
181182
}
182183

@@ -186,7 +187,7 @@ class HtmlParser extends StatelessWidget {
186187
// inherit the correct style
187188
RenderContext newContext = RenderContext(
188189
parser: this,
189-
style: context.style.merge(tree.style),
190+
style: context.style.copyOnlyInherited(tree.style),
190191
);
191192

192193
if (customRender?.containsKey(tree.name) ?? false) {
@@ -254,7 +255,7 @@ class HtmlParser extends StatelessWidget {
254255
} else if (tree is InteractableElement) {
255256
return WidgetSpan(
256257
child: GestureDetector(
257-
onTap: () => onLinkTap(tree.href),
258+
onTap: () => onLinkTap?.call(tree.href),
258259
child: RichText(
259260
text: TextSpan(
260261
style: newContext.style.generateTextStyle(),
@@ -267,6 +268,30 @@ class HtmlParser extends StatelessWidget {
267268
return WidgetSpan(
268269
child: tree.toWidget(context),
269270
);
271+
} else if (tree.style.verticalAlign != null && tree.style.verticalAlign != VerticalAlign.BASELINE) {
272+
double verticalOffset;
273+
switch(tree.style.verticalAlign) {
274+
case VerticalAlign.SUB:
275+
verticalOffset = tree.style.fontSize.size / 2.5;
276+
break;
277+
case VerticalAlign.SUPER:
278+
verticalOffset = tree.style.fontSize.size / -2.5;
279+
break;
280+
default:
281+
break;
282+
}
283+
//Requires special layout features not available in the TextStyle API.
284+
return WidgetSpan(
285+
child: Transform.translate(
286+
offset: Offset(0, verticalOffset),
287+
child: RichText(
288+
text: TextSpan(
289+
style: newContext.style.generateTextStyle(),
290+
children: tree.children.map((tree) => parseTree(newContext, tree)).toList() ?? [],
291+
),
292+
),
293+
),
294+
);
270295
} else {
271296
///[tree] is an inline element.
272297
return TextSpan(
@@ -514,6 +539,19 @@ class HtmlParser extends StatelessWidget {
514539

515540
return tree;
516541
}
542+
543+
static StyledElement _processFontSize(StyledElement tree) {
544+
double parentFontSize = tree.style?.fontSize?.size ?? FontSize.medium.size;
545+
546+
tree.children?.forEach((child) {
547+
if ((child.style.fontSize?.size ?? parentFontSize) < 0) {
548+
child.style.fontSize = FontSize(parentFontSize * -child.style.fontSize.size);
549+
}
550+
551+
_processFontSize(child);
552+
});
553+
return tree;
554+
}
517555
}
518556

519557
///TODO document better

lib/src/replaced_element.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ class RubyElement extends ReplacedElement {
250250
Widget toWidget(RenderContext context) {
251251
dom.Node textNode = null;
252252
List<Widget> widgets = List<Widget>();
253-
final rubySize = max(9.0, context.style.fontSize / 2);
253+
//TODO calculate based off of parent font size.
254+
final rubySize = max(9.0, context.style.fontSize.size / 2);
254255
final rubyYPos = rubySize + 2;
255256
element.nodes.forEach((c) {
256257
if (c.nodeType == dom.Node.TEXT_NODE) {

lib/src/styled_element.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ StyledElement parseStyledElement(
8888
break;
8989
case "big":
9090
styledElement.style = Style(
91-
fontSize: 20.0,
91+
fontSize: FontSize.larger,
9292
);
9393
break;
9494
case "blockquote":
@@ -176,47 +176,47 @@ StyledElement parseStyledElement(
176176
break;
177177
case "h1":
178178
styledElement.style = Style(
179-
fontSize: 28.0,
179+
fontSize: FontSize.xxLarge,
180180
fontWeight: FontWeight.bold,
181181
margin: EdgeInsets.symmetric(vertical: 18.67),
182182
display: Display.BLOCK,
183183
);
184184
break;
185185
case "h2":
186186
styledElement.style = Style(
187-
fontSize: 21.0,
187+
fontSize: FontSize.xLarge,
188188
fontWeight: FontWeight.bold,
189189
margin: EdgeInsets.symmetric(vertical: 17.5),
190190
display: Display.BLOCK,
191191
);
192192
break;
193193
case "h3":
194194
styledElement.style = Style(
195-
fontSize: 16.5,
195+
fontSize: FontSize(16.38),
196196
fontWeight: FontWeight.bold,
197197
margin: EdgeInsets.symmetric(vertical: 16.5),
198198
display: Display.BLOCK,
199199
);
200200
break;
201201
case "h4":
202202
styledElement.style = Style(
203-
fontSize: 14.0,
203+
fontSize: FontSize.medium,
204204
fontWeight: FontWeight.bold,
205205
margin: EdgeInsets.symmetric(vertical: 18.5),
206206
display: Display.BLOCK,
207207
);
208208
break;
209209
case "h5":
210210
styledElement.style = Style(
211-
fontSize: 11.5,
211+
fontSize: FontSize(11.62),
212212
fontWeight: FontWeight.bold,
213213
margin: EdgeInsets.symmetric(vertical: 19.25),
214214
display: Display.BLOCK,
215215
);
216216
break;
217217
case "h6":
218218
styledElement.style = Style(
219-
fontSize: 9.5,
219+
fontSize: FontSize(9.38),
220220
fontWeight: FontWeight.bold,
221221
margin: EdgeInsets.symmetric(vertical: 22),
222222
display: Display.BLOCK,
@@ -324,7 +324,7 @@ StyledElement parseStyledElement(
324324
break;
325325
case "small":
326326
styledElement.style = Style(
327-
fontSize: 10.0,
327+
fontSize: FontSize.smaller,
328328
);
329329
break;
330330
case "strike":
@@ -333,14 +333,14 @@ StyledElement parseStyledElement(
333333
continue bold;
334334
case "sub":
335335
styledElement.style = Style(
336-
fontSize: 10.0,
337-
baselineOffset: -1,
336+
fontSize: FontSize.smaller,
337+
verticalAlign: VerticalAlign.SUB,
338338
);
339339
break;
340340
case "sup":
341341
styledElement.style = Style(
342-
fontSize: 10.0,
343-
baselineOffset: 1,
342+
fontSize: FontSize.smaller,
343+
verticalAlign: VerticalAlign.SUPER,
344344
);
345345
break;
346346
case "tt":

0 commit comments

Comments
 (0)