Skip to content

Commit d1a6b49

Browse files
committed
Refactor flutter_html_math
1 parent 6c9b870 commit d1a6b49

File tree

6 files changed

+177
-104
lines changed

6 files changed

+177
-104
lines changed

example/lib/main.dart

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_html/flutter_html.dart';
3-
import 'package:flutter_html_all/flutter_html_all.dart';
3+
import 'package:flutter_html_math/flutter_html_math.dart';
44

55
void main() => runApp(const MyApp());
66

@@ -203,7 +203,7 @@ const htmlData = r"""
203203
<mn>2</mn>
204204
</msup>
205205
<mo>&sdot;</mo>
206-
<mi>&dd;</mi><mi>x</mi>
206+
<mi>d</mi><mi>x</mi>
207207
<mo>=</mo>
208208
<mo>[</mo>
209209
<mfrac>
@@ -232,6 +232,7 @@ const htmlData = r"""
232232
<mi>3</mi>
233233
</mfrac>
234234
</math>
235+
<br />
235236
<math>
236237
<msup>
237238
<mo>sin</mo>
@@ -292,73 +293,83 @@ class MyHomePageState extends State<MyHomePage> {
292293
alignment: Alignment.topLeft,
293294
),
294295
'h5': Style(maxLines: 2, textOverflow: TextOverflow.ellipsis),
296+
'flutter': Style(
297+
display: Display.block,
298+
fontSize: FontSize(5, Unit.em),
299+
),
295300
},
296301
tagsList: Html.tags..addAll(['tex', 'bird', 'flutter']),
297-
customRenders: {
298-
tagMatcher("tex"): CustomRender.widget(
299-
widget: (context, buildChildren) => Math.tex(
300-
context.tree.element?.innerHtml ?? '',
301-
mathStyle: MathStyle.display,
302-
textStyle: context.style.generateTextStyle(),
303-
onErrorFallback: (FlutterMathException e) {
304-
return Text(e.message);
305-
},
306-
)),
307-
tagMatcher("bird"): CustomRender.inlineSpan(
308-
inlineSpan: (context, buildChildren) =>
309-
const TextSpan(text: "🐦")),
310-
tagMatcher("flutter"): CustomRender.widget(
311-
widget: (context, buildChildren) => FlutterLogo(
312-
style: (context.tree.element!.attributes['horizontal'] !=
313-
null)
314-
? FlutterLogoStyle.horizontal
315-
: FlutterLogoStyle.markOnly,
316-
textColor: context.style.color!,
317-
size: context.style.fontSize!.value * 5,
318-
)),
319-
tagMatcher("table"): CustomRender.widget(
320-
widget: (context, buildChildren) => SingleChildScrollView(
321-
scrollDirection: Axis.horizontal,
322-
child: tableRender
323-
.call()
324-
.widget!
325-
.call(context, buildChildren),
326-
)),
327-
audioMatcher(): audioRender(),
328-
iframeMatcher(): iframeRender(),
329-
mathMatcher():
330-
mathRender(onMathError: (error, exception, exceptionWithType) {
331-
debugPrint(exception);
332-
return Text(exception);
333-
}),
334-
svgTagMatcher(): svgTagRender(),
335-
svgDataUriMatcher(): svgDataImageRender(),
336-
svgAssetUriMatcher(): svgAssetImageRender(),
337-
svgNetworkSourceMatcher(): svgNetworkImageRender(),
338-
networkSourceMatcher(domains: ["flutter.dev"]):
339-
CustomRender.widget(widget: (context, buildChildren) {
340-
return const FlutterLogo(size: 36);
341-
}),
342-
networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
343-
headers: {"Custom-Header": "some-value"},
344-
altWidget: (alt) => Text(alt ?? ""),
345-
loadingWidget: () => const Text("Loading..."),
302+
extensions: [
303+
TagExtension(
304+
tagsToExtend: {"h4"},
305+
child: FlutterLogo(),
346306
),
347-
// On relative paths starting with /wiki, prefix with a base url
348-
(context) =>
349-
context.tree.element?.attributes["src"] != null &&
350-
context.tree.element!.attributes["src"]!
351-
.startsWith("/wiki"): networkImageRender(
352-
mapUrl: (url) => "https://upload.wikimedia.org${url!}"),
353-
// Custom placeholder image for broken links
354-
networkSourceMatcher():
355-
networkImageRender(altWidget: (_) => const FlutterLogo()),
356-
videoMatcher(): videoRender(),
357-
},
358-
onLinkTap: (url, _, __, ___) {
307+
TagExtension(
308+
tagsToExtend: {"tex"},
309+
builder: (context) => Math.tex(
310+
context.innerHtml,
311+
mathStyle: MathStyle.display,
312+
textStyle: context.styledElement?.style.generateTextStyle(),
313+
onErrorFallback: (FlutterMathException e) {
314+
return Text(e.message);
315+
},
316+
),
317+
),
318+
TagExtension.inline(
319+
tagsToExtend: {"bird"},
320+
child: const TextSpan(text: "🐦"),
321+
),
322+
TagExtension(
323+
tagsToExtend: {"flutter"},
324+
builder: (context) => FlutterLogo(
325+
style: context.attributes['horizontal'] != null
326+
? FlutterLogoStyle.horizontal
327+
: FlutterLogoStyle.markOnly,
328+
textColor: context.styledElement!.style.color!,
329+
size: context.styledElement!.style.fontSize!.value,
330+
),
331+
),
332+
MathHtmlExtension(),
333+
],
334+
// customRenders: {
335+
// tagMatcher("table"): CustomRender.widget(
336+
// widget: (context, buildChildren) => SingleChildScrollView(
337+
// scrollDirection: Axis.horizontal,
338+
// child: tableRender
339+
// .call()
340+
// .widget!
341+
// .call(context, buildChildren),
342+
// )),
343+
// audioMatcher(): audioRender(),
344+
// iframeMatcher(): iframeRender(),
345+
// svgTagMatcher(): svgTagRender(),
346+
// svgDataUriMatcher(): svgDataImageRender(),
347+
// svgAssetUriMatcher(): svgAssetImageRender(),
348+
// svgNetworkSourceMatcher(): svgNetworkImageRender(),
349+
// networkSourceMatcher(domains: ["flutter.dev"]):
350+
// CustomRender.widget(widget: (context, buildChildren) {
351+
// return const FlutterLogo(size: 36);
352+
// }),
353+
// networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
354+
// headers: {"Custom-Header": "some-value"},
355+
// altWidget: (alt) => Text(alt ?? ""),
356+
// loadingWidget: () => const Text("Loading..."),
357+
// ),
358+
// // On relative paths starting with /wiki, prefix with a base url
359+
// (context) =>
360+
// context.tree.element?.attributes["src"] != null &&
361+
// context.tree.element!.attributes["src"]!
362+
// .startsWith("/wiki"): networkImageRender(
363+
// mapUrl: (url) => "https://upload.wikimedia.org${url!}"),
364+
// // Custom placeholder image for broken links
365+
// networkSourceMatcher():
366+
// networkImageRender(altWidget: (_) => const FlutterLogo()),
367+
// videoMatcher(): videoRender(),
368+
// },
369+
onLinkTap: (url, _, __) {
359370
debugPrint("Opening $url...");
360371
},
361-
onImageTap: (src, _, __, ___) {
372+
onImageTap: (src, _, __) {
362373
debugPrint(src);
363374
},
364375
onImageError: (exception, stackTrace) {
@@ -378,11 +389,11 @@ class MyHomePageState extends State<MyHomePage> {
378389
}
379390
}
380391

381-
CustomRenderMatcher texMatcher() =>
382-
(context) => context.tree.element?.localName == 'tex';
392+
// CustomRenderMatcher texMatcher() =>
393+
// (context) => context.tree.element?.localName == 'tex';
383394

384-
CustomRenderMatcher birdMatcher() =>
385-
(context) => context.tree.element?.localName == 'bird';
395+
// CustomRenderMatcher birdMatcher() =>
396+
// (context) => context.tree.element?.localName == 'bird';
386397

387-
CustomRenderMatcher flutterMatcher() =>
388-
(context) => context.tree.element?.localName == 'flutter';
398+
// CustomRenderMatcher flutterMatcher() =>
399+
// (context) => context.tree.element?.localName == 'flutter';

lib/src/extension/extension.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import 'package:flutter/painting.dart';
2-
import 'package:flutter_html/flutter_html.dart';
32
import 'package:flutter_html/src/extension/extension_context.dart';
3+
import 'package:flutter_html/src/tree/styled_element.dart';
44

55
export 'package:flutter_html/src/extension/extension_context.dart';
6+
export 'package:flutter_html/src/extension/helpers/tag_extension.dart';
67

78
/// The [Extension] class allows you to customize the behavior of flutter_html
89
/// or add additional functionality.

lib/src/extension/extension_context.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ class ExtensionContext {
2323

2424
/// Returns the HTML within this element, or an empty string if there is none.
2525
String get innerHtml {
26-
return node.sourceSpan?.text ?? '';
26+
if(node is html.Element) {
27+
return (node as html.Element).innerHtml;
28+
}
29+
30+
return node.text ?? "";
2731
}
2832

2933
/// Returns the list of child Elements on this html Node, or an empty list if
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/widgets.dart';
2+
import 'package:flutter_html/src/extension/extension.dart';
3+
import 'package:flutter_html/src/style.dart';
4+
import 'package:flutter_html/src/tree/styled_element.dart';
5+
6+
/// [TagExtension] allows you to extend the functionality of flutter_html
7+
/// by defining the behavior of custom tags.
8+
class TagExtension extends Extension {
9+
final Set<String> tagsToExtend;
10+
late final InlineSpan Function(ExtensionContext) builder;
11+
12+
/// [TagExtension] allows you to extend the functionality of flutter_html
13+
/// by defining the behavior of custom tags to return a child widget.
14+
TagExtension({
15+
required this.tagsToExtend,
16+
Widget? child,
17+
Widget Function(ExtensionContext)? builder,
18+
}): assert((child == null) ^ (builder == null), "Either child or builder needs to be provided to TagExtension") {
19+
if(child != null) {
20+
this.builder = (_) => WidgetSpan(child: child);
21+
} else {
22+
this.builder = (context) => WidgetSpan(child: builder!.call(context));
23+
}
24+
}
25+
26+
/// [TagExtension.inline] allows you to extend the functionality of
27+
/// flutter_html by defining the behavior of custom tags to return
28+
/// a child InlineSpan.
29+
TagExtension.inline({
30+
required this.tagsToExtend,
31+
InlineSpan? child,
32+
InlineSpan Function(ExtensionContext)? builder,
33+
}): assert((child == null) ^ (builder == null), "Either child or builder needs to be provided to TagExtension.inline") {
34+
if(child != null) {
35+
this.builder = (_) => child;
36+
} else {
37+
this.builder = builder!;
38+
}
39+
}
40+
41+
@override
42+
Set<String> get supportedTags => tagsToExtend;
43+
44+
@override
45+
StyledElement lex(ExtensionContext context, List<StyledElement> children) {
46+
return StyledElement(
47+
node: context.node,
48+
children: children,
49+
style: Style(),
50+
elementId: context.id,
51+
elementClasses: context.classes.toList(),
52+
name: context.elementName,
53+
);
54+
}
55+
56+
@override
57+
InlineSpan parse(ExtensionContext context, parseChildren) {
58+
return builder(context);
59+
}
60+
61+
}

packages/flutter_html_math/lib/flutter_html_math.dart

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,33 @@ import 'package:flutter_math_fork/flutter_math.dart';
77

88
export 'package:flutter_math_fork/flutter_math.dart';
99

10-
/// The CustomRender function for the <math> tag.
11-
CustomRender mathRender({OnMathError? onMathError}) =>
12-
CustomRender.widget(widget: (context, buildChildren) {
13-
String texStr = context.tree.element == null
14-
? ''
15-
: _parseMathRecursive(context.tree.element!, r'');
16-
return SizedBox(
17-
width: context.parser.shrinkWrap
18-
? null
19-
: MediaQuery.of(context.buildContext).size.width,
20-
child: Math.tex(
21-
texStr,
22-
mathStyle: MathStyle.display,
23-
textStyle: context.style.generateTextStyle(),
24-
onErrorFallback: (FlutterMathException e) {
25-
if (onMathError != null) {
26-
return onMathError.call(texStr, e.message, e.messageWithType);
27-
} else {
28-
return Text(e.message);
29-
}
30-
},
31-
));
32-
});
10+
class MathHtmlExtension extends TagExtension {
3311

34-
/// The CustomRenderMatcher for the <math> element.
35-
CustomRenderMatcher mathMatcher() => (context) {
36-
return context.tree.element?.localName == "math";
37-
};
12+
MathHtmlExtension({OnMathErrorBuilder? onMathErrorBuilder}): super(
13+
tagsToExtend: {"math"},
14+
builder: (context) => _renderMath(context, onMathErrorBuilder),
15+
);
16+
17+
static Widget _renderMath(ExtensionContext context, OnMathErrorBuilder? onMathError) {
18+
String texStr = _parseMathRecursive(context.styledElement!.element!, '');
19+
return CssBoxWidget(
20+
style: context.styledElement!.style,
21+
childIsReplaced: true,
22+
child: Math.tex(
23+
texStr,
24+
mathStyle: MathStyle.display,
25+
textStyle: context.styledElement!.style.generateTextStyle(),
26+
onErrorFallback: (FlutterMathException e) {
27+
if (onMathError != null) {
28+
return onMathError.call(texStr, e.message, e.messageWithType);
29+
} else {
30+
return Text(e.message);
31+
}
32+
},
33+
),
34+
);
35+
}
36+
}
3837

3938
String _parseMathRecursive(dom.Node node, String parsed) {
4039
if (node is dom.Element) {
@@ -164,7 +163,7 @@ String _parseMathRecursive(dom.Node node, String parsed) {
164163
return parsed;
165164
}
166165

167-
Map<String, String> _mathML2Tex = {
166+
const Map<String, String> _mathML2Tex = {
168167
"sin": r"\sin",
169168
"sinh": r"\sinh",
170169
"csc": r"\csc",
@@ -183,7 +182,7 @@ Map<String, String> _mathML2Tex = {
183182
"}": r"\}",
184183
};
185184

186-
typedef OnMathError = Widget Function(
185+
typedef OnMathErrorBuilder = Widget Function(
187186
String parsedTex,
188187
String exception,
189188
String exceptionWithType,

packages/flutter_html_math/pubspec.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ dependencies:
1212
sdk: flutter
1313
html: '>=0.15.0 <1.0.0'
1414
flutter_html: ^3.0.0-alpha.6
15-
# flutter_html:
16-
# path: ../..
17-
1815
flutter_math_fork: '>=0.6.0 <1.0.0'
1916

2017
dev_dependencies:

0 commit comments

Comments
 (0)