From 0d666a2a52fc3c88e13dc30e9e37979deaa470ad Mon Sep 17 00:00:00 2001 From: tanay Date: Mon, 8 Mar 2021 11:22:56 -0500 Subject: [PATCH 1/8] Add preliminary support for MathML --- README.md | 39 ++++++++++++- example/lib/main.dart | 106 +++++++++++++++++++++++++++++++--- lib/flutter_html.dart | 3 + lib/html_parser.dart | 7 +++ lib/src/html_elements.dart | 1 + lib/src/replaced_element.dart | 85 +++++++++++++++++++++++++++ lib/src/utils.dart | 17 ++++++ pubspec.yaml | 5 +- 8 files changed, 253 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 19608681d4..e79e3ca333 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. - [customRender](#customrender) - [onImageError](#onimageerror) + + - [onMathError](#onmatherror) - [onImageTap](#onimagetap) @@ -71,6 +73,8 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. - [SVG](#svg) + - [MathML](#mathml) + - [Table](#table) - [Notes](#notes) @@ -96,8 +100,8 @@ Add the following to your `pubspec.yaml` file: | `mark` | `nav` | `noscript`|`ol` | `p` | `pre` | `q` | `rp` | `rt` | `ruby` | `s` | | `samp` | `section` | `small` | `span`| `strike` | `strong`| `sub` | `sup` | `summary` | `svg`| `table`| | `tbody` | `td` | `template` | `tfoot` | `th` | `thead` |`time` | `tr` | `tt` | `u` | `ul` | -| `var` | `video` | | | | | | | | | | - +| `var` | `video` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` | `mfrac` | +| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | | | ## Currently Supported CSS Attributes: @@ -139,6 +143,7 @@ Below, you will find brief descriptions of the parameters the`Html` widget accep | `onLinkTap` | A function that defines what the widget should do when a link is tapped. The function exposes the `src` of the link as a `String` to use in your implementation. | | `customRender` | A powerful API that allows you to customize everything when rendering a specific HTML tag. | | `onImageError` | A function that defines what the widget should do when an image fails to load. The function exposes the exception `Object` and `StackTrace` to use in your implementation. | +| `omMathError` | A function that defines what the widget should do when a math fails to render. The function exposes the parsed Tex `String`, as well as the error and error with type from `flutter_math` as a `String`. | | `shrinkWrap` | A `bool` used while rendering different widgets to specify whether they should be shrink-wrapped or not, like `ContainerSpan` | | `onImageTap` | A function that defines what the widget should do when an image is tapped. The function exposes the `src` of the image as a `String` to use in your implementation. | | `blacklistedElements` | A list of elements the `Html` widget should not render. The list should contain the tags of the HTML elements you wish to blacklist. | @@ -292,6 +297,24 @@ Widget html = Html( ); ``` +### onMathError: + +A function that defines what the widget should do when a math fails to render. The function exposes the parsed Tex `String`, as well as the error and error with type from `flutter_math` as a `String`. + +#### Example Usage - onMathError: + +```dart +Widget html = Html( + data: """""", + onMathError: (String parsedTex, String error, String errorWithType) { + //your logic here. A Widget must be returned from this function: + return Text(error); + //you can also try and fix the parsing yourself: + return Math.tex(correctedParsedTex); + }, +); +``` + ### onImageTap: A function that defines what the widget should do when an image is tapped. @@ -636,6 +659,18 @@ This package renders svg elements using the [`flutter_svg`](https://pub.dev/pack When rendering SVGs, the package takes the SVG data within the `` tag and passes it to `flutter_svg`. The `width` and `height` attributes are considered while rendering, if given. +### MathML + +This package renders MathML elements using the [`flutter_math`](https://pub.dev/packages/flutter_math) plugin. + +When rendering MathML, the package takes the MathML data within the `` tag and tries to parse it to Tex. Then, it will pass the parsed string to `flutter_math`. + +Because this package is parsing MathML to Tex, it may not support some functionalities. The current list of supported tags can be found [above](#currently-supported-html-tags), but some of these only have partial support at the moment. + +If the parsing errors, you can use the [onMathError](#onmatherror) API to catch the error and potentially fix it on your end - you can analyze the error and the parsed string, and finally return a new instance of `Math.tex()` with the corrected Tex string. + +If you'd like to see more MathML features, feel free to create a PR or file a feature request! + ### Table This package renders table elements using the [`flutter_layout_grid`](https://pub.dev/packages/flutter_layout_grid) plugin. diff --git a/example/lib/main.dart b/example/lib/main.dart index 66bb38a3e7..a03879c2b9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -28,13 +28,13 @@ class MyHomePage extends StatefulWidget { } const htmlData = """ -

Header 1

-

Header 2

-

Header 3

-

Header 4

-
Header 5
-
Header 6
-

Ruby Support:

+

Header 1

+

Header 2

+

Header 3

+

Header 4

+
Header 5
+
Header 6
+

Ruby Support:

かん @@ -136,6 +136,98 @@ const htmlData = """ Empty source

Broken network image

Broken image +

MathML Support:

+ + + x + = + + + + - + b + + ± + + + + b + 2 + + - + + 4 + + a + + c + + + + + + 2 + + a + + + + + + + + 0 + 5 + + + x + 2 + + + x + = + [ + + 1 + 3 + + + x + 3 + + + ] + 0 + 5 + + = + + 125 + 3 + + - + 0 + = + + 125 + 3 + + + + + sin + 2 + + θ + + + + cos + 2 + + θ + = + 1 + """; class _MyHomePageState extends State { diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 51bd6712c6..1958b2f918 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -39,6 +39,7 @@ class Html extends StatelessWidget { this.customRender = const {}, this.customImageRenders = const {}, this.onImageError, + this.onMathError, this.shrinkWrap = false, this.onImageTap, this.blacklistedElements = const [], @@ -50,6 +51,7 @@ class Html extends StatelessWidget { final OnTap? onLinkTap; final Map customImageRenders; final ImageErrorListener? onImageError; + final OnMathError? onMathError; final bool shrinkWrap; /// Properties for the Image widget that gets rendered by the rich text parser @@ -80,6 +82,7 @@ class Html extends StatelessWidget { onLinkTap: onLinkTap, onImageTap: onImageTap, onImageError: onImageError, + onMathError: onMathError, shrinkWrap: shrinkWrap, style: style, customRender: customRender, diff --git a/lib/html_parser.dart b/lib/html_parser.dart index f12c67674f..e8c9c175a0 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -22,6 +22,11 @@ typedef OnTap = void Function( Map attributes, dom.Element? element, ); +typedef OnMathError = Widget Function( + String parsedTex, + String exception, + String exceptionWithType, +); typedef CustomRender = dynamic Function( RenderContext context, Widget parsedChild, @@ -34,6 +39,7 @@ class HtmlParser extends StatelessWidget { final OnTap? onLinkTap; final OnTap? onImageTap; final ImageErrorListener? onImageError; + final OnMathError? onMathError; final bool shrinkWrap; final Map style; @@ -47,6 +53,7 @@ class HtmlParser extends StatelessWidget { required this.onLinkTap, required this.onImageTap, required this.onImageError, + required this.onMathError, required this.shrinkWrap, required this.style, required this.customRender, diff --git a/lib/src/html_elements.dart b/lib/src/html_elements.dart index d17c9ae6c7..020f0ae345 100644 --- a/lib/src/html_elements.dart +++ b/lib/src/html_elements.dart @@ -86,6 +86,7 @@ const REPLACED_ELEMENTS = [ "rp", "rt", "ruby", + "math", ]; const LAYOUT_ELEMENTS = [ diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index edb8420264..f61f5c27c5 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -11,6 +11,7 @@ import 'package:flutter_html/src/widgets/iframe_unsupported.dart' if (dart.library.io) 'package:flutter_html/src/widgets/iframe_mobile.dart' if (dart.library.html) 'package:flutter_html/src/widgets/iframe_web.dart'; import 'package:flutter_html/style.dart'; +import 'package:flutter_math/flutter_math.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:html/dom.dart' as dom; import 'package:video_player/video_player.dart'; @@ -264,6 +265,85 @@ class RubyElement extends ReplacedElement { } } +class MathElement extends ReplacedElement { + dom.Element element; + String texStr; + + MathElement({ + required this.element, + required this.texStr, + String name = "math", + }) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style(display: Display.INLINE)); + + @override + Widget toWidget(RenderContext context) { + texStr = parseMathRecursive(element, r''); + return Container( + width: MediaQuery.of(context.buildContext).size.width, + child: Math.tex( + texStr, + onErrorFallback: (FlutterMathException e) { + if (context.parser.onMathError != null) { + return context.parser.onMathError!.call(texStr, e.message, e.messageWithType); + } else { + return Text(e.message); + } + }, + ) + ); + } + + String parseMathRecursive(dom.Node node, String parsed) { + if (node is dom.Element) { + List nodeList = node.nodes.whereType().toList(); + if (node.localName == "math" || node.localName == "mrow") { + nodeList.forEach((element) { + parsed = parseMathRecursive(element, parsed); + }); + } + // note: munder, mover, and munderover do not support placing braces and other + // markings above/below elements, instead they are treated as super/subscripts for now. + if ((node.localName == "msup" || node.localName == "msub" + || node.localName == "munder" || node.localName == "mover") && nodeList.length == 2) { + parsed = parseMathRecursive(nodeList[0], parsed); + parsed = parseMathRecursive(nodeList[1], + parsed + "${node.localName == "msup" || node.localName == "mover" ? "^" : "_"}{") + "}"; + } + if ((node.localName == "msubsup" || node.localName == "munderover") && nodeList.length == 3) { + parsed = parseMathRecursive(nodeList[0], parsed); + parsed = parseMathRecursive(nodeList[1], parsed + "_{") + "}"; + parsed = parseMathRecursive(nodeList[2], parsed + "^{") + "}"; + } + if (node.localName == "mfrac" && nodeList.length == 2) { + parsed = parseMathRecursive(nodeList[0], parsed + r"\frac{") + "}"; + parsed = parseMathRecursive(nodeList[1], parsed + "{") + "}"; + } + // note: doesn't support answer & intermediate steps + if (node.localName == "mlongdiv" && nodeList.length == 4) { + parsed = parseMathRecursive(nodeList[0], parsed); + parsed = parseMathRecursive(nodeList[2], parsed + r"\overline{)") + "}"; + } + if (node.localName == "msqrt" && nodeList.length == 1) { + parsed = parseMathRecursive(nodeList[0], parsed + r"\sqrt{") + "}"; + } + if (node.localName == "mroot" && nodeList.length == 2) { + parsed = parseMathRecursive(nodeList[1], parsed + r"\sqrt[") + "]"; + parsed = parseMathRecursive(nodeList[0], parsed + "{") + "}"; + } + if (node.localName == "mi" || node.localName == "mn" || node.localName == "mo") { + if (mathML2Tex.keys.contains(node.text.trim())) { + parsed = parsed + mathML2Tex[mathML2Tex.keys.firstWhere((e) => e == node.text.trim())]!; + } else if (node.text.startsWith("&") && node.text.endsWith(";")) { + parsed = parsed + node.text.trim().replaceFirst("&", r"\").substring(0, node.text.trim().length - 1); + } else { + parsed = parsed + node.text.trim(); + } + } + } + return parsed; + } +} + ReplacedElement parseReplacedElement( dom.Element element, NavigationDelegate? navigationDelegateForIframe, @@ -339,6 +419,11 @@ ReplacedElement parseReplacedElement( return RubyElement( element: element, ); + case "math": + return MathElement( + element: element, + texStr: r'', + ); default: return EmptyContentElement(name: element.localName == null ? "[[No Name]]" : element.localName!); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 155e313c9c..cdd2c363b9 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -23,6 +23,23 @@ Map namedColors = { "Purple": "#800080", }; +Map mathML2Tex = { + "sin": r"\sin", + "sinh": r"\sinh", + "csc": r"\csc", + "csch": r"csch", + "cos": r"\cos", + "cosh": r"\cosh", + "sec": r"\sec", + "sech": r"\sech", + "tan": r"\tan", + "tanh": r"\tanh", + "cot": r"\cot", + "coth": r"\coth", + "log": r"\log", + "ln": r"\ln", +}; + class Context { T data; diff --git a/pubspec.yaml b/pubspec.yaml index 93f9779472..74bf5b562e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,10 @@ dependencies: chewie_audio: ^1.2.0 # Plugins for rendering the tag. - flutter_svg: ^0.21.0-nullsafety.0 + flutter_svg: ^0.20.0-nullsafety.3 + + # Plugin for rendering MathML + flutter_math: ^0.3.0-nullsafety.1 flutter: sdk: flutter From bf980a72622fe8dfb79381c9b6683395a6fc3bcf Mon Sep 17 00:00:00 2001 From: tanay Date: Mon, 8 Mar 2021 11:34:29 -0500 Subject: [PATCH 2/8] Add custom tag? --- README.md | 10 +++++++++- example/lib/main.dart | 4 +++- lib/src/html_elements.dart | 1 + lib/src/replaced_element.dart | 11 +++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e79e3ca333..20809c9737 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Add the following to your `pubspec.yaml` file: | `samp` | `section` | `small` | `span`| `strike` | `strong`| `sub` | `sup` | `summary` | `svg`| `table`| | `tbody` | `td` | `template` | `tfoot` | `th` | `thead` |`time` | `tr` | `tt` | `u` | `ul` | | `var` | `video` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` | `mfrac` | -| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | | | +| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | `tex` (custom tag for this package) | | | | | ## Currently Supported CSS Attributes: @@ -671,6 +671,14 @@ If the parsing errors, you can use the [onMathError](#onmatherror) API to catch If you'd like to see more MathML features, feel free to create a PR or file a feature request! +### Tex + +If you have a Tex string you'd like to render inside your HTML you can do that! + +Use the custom `` tag inside your HTML, and place your **raw** Tex string inside. The package uses the same logic as MathML rendering above, but doesn't need to parse the string, of course. + + The rendering error will also be called if your Tex string fails to render. + ### Table This package renders table elements using the [`flutter_layout_grid`](https://pub.dev/packages/flutter_layout_grid) plugin. diff --git a/example/lib/main.dart b/example/lib/main.dart index a03879c2b9..90b4bc079f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -27,7 +27,7 @@ class MyHomePage extends StatefulWidget { _MyHomePageState createState() => new _MyHomePageState(); } -const htmlData = """ +const htmlData = r"""

Header 1

Header 2

Header 3

@@ -228,6 +228,8 @@ const htmlData = """ = 1 +

Tex Support with the custom tex tag:

+ i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t) """; class _MyHomePageState extends State { diff --git a/lib/src/html_elements.dart b/lib/src/html_elements.dart index 020f0ae345..80cdb90fa5 100644 --- a/lib/src/html_elements.dart +++ b/lib/src/html_elements.dart @@ -87,6 +87,7 @@ const REPLACED_ELEMENTS = [ "rt", "ruby", "math", + "tex", ]; const LAYOUT_ELEMENTS = [ diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index f61f5c27c5..9963e481e9 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -272,12 +272,14 @@ class MathElement extends ReplacedElement { MathElement({ required this.element, required this.texStr, - String name = "math", + String name = "[[MathElement]]", }) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style(display: Display.INLINE)); @override Widget toWidget(RenderContext context) { - texStr = parseMathRecursive(element, r''); + if (element.localName == "math") { + texStr = parseMathRecursive(element, r''); + } return Container( width: MediaQuery.of(context.buildContext).size.width, child: Math.tex( @@ -424,6 +426,11 @@ ReplacedElement parseReplacedElement( element: element, texStr: r'', ); + case "tex": + return MathElement( + element: element, + texStr: element.text, + ); default: return EmptyContentElement(name: element.localName == null ? "[[No Name]]" : element.localName!); } From c0f5f93cd7617d82c4de4e7b1e586d63029fcc1d Mon Sep 17 00:00:00 2001 From: tanay Date: Thu, 11 Mar 2021 19:32:38 -0500 Subject: [PATCH 3/8] Test for baseline alignment --- lib/src/replaced_element.dart | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index 9963e481e9..4aa4dccc75 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -280,18 +280,22 @@ class MathElement extends ReplacedElement { if (element.localName == "math") { texStr = parseMathRecursive(element, r''); } - return Container( - width: MediaQuery.of(context.buildContext).size.width, - child: Math.tex( - texStr, - onErrorFallback: (FlutterMathException e) { - if (context.parser.onMathError != null) { - return context.parser.onMathError!.call(texStr, e.message, e.messageWithType); - } else { - return Text(e.message); - } - }, - ) + return Padding( + padding: const EdgeInsets.only(bottom: 3.5), + child: Container( + width: element.localName == "math" ? + MediaQuery.of(context.buildContext).size.width : null, + child: Math.tex( + texStr, + onErrorFallback: (FlutterMathException e) { + if (context.parser.onMathError != null) { + return context.parser.onMathError!.call(texStr, e.message, e.messageWithType); + } else { + return Text(e.message); + } + }, + ) + ), ); } From 5d2293e02b671c9df2b016351503fbd98e14de6e Mon Sep 17 00:00:00 2001 From: tanay Date: Sat, 13 Mar 2021 17:24:52 -0500 Subject: [PATCH 4/8] Final changes to MathML implementation --- example/lib/main.dart | 237 +++++++++++++++++++++++++++++++++- lib/src/replaced_element.dart | 17 +-- pubspec.yaml | 4 +- 3 files changed, 247 insertions(+), 11 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 90b4bc079f..3b7b6c56ea 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -242,7 +242,242 @@ class _MyHomePageState extends State { ), body: SingleChildScrollView( child: Html( - data: htmlData, + data: """ + +
Lorem Ipsum
+Lorem Ipsum
+Lorem Ipsum

+Lorem Ipsum
+ +
+   + +
            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum
+             Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum,
+            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
+            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
+            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
+            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum.
+
+Lorem Ipsum
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lorem Ipsum
+ NO
Lorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum
+
Lorem Ipsum
Lorem IpsumLorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum14.01.2021260Lorem Ipsum
3Lorem IpsumLorem IpsumLorem Ipsum11.01.2021200,00Lorem Ipsum
4Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021648,00Lorem Ipsum
5Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum09.09.2020300,00Lorem Ipsum
6Lorem Ipsum Lorem Ipsum Lorem IpsumLorem Lorem Ipsum-Lorem Ipsum15.11.201916.262,33Lorem IpsumLorem Ipsum
7Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021100,00Lorem Ipsum
8Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021760,00Lorem Ipsum
9Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021380,00Lorem Ipsum
10Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021320,00Lorem Ipsum
11Lorem Ipsum Lorem Ipsum Lorem Ipsum.Lorem IpsumLorem Ipsum17.02.202010.029.107,40Lorem IpsumLorem Ipsum.
112Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021220,00Lorem Ipsum
+ +
 
+Lorem Ipsum
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lorem Ipsum Lorem IpsumLorem Ipsum Lorem Ipsum / Lorem Ipsum Lorem Ipsum Lorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum Lorem Ipsum
+ (TL)
Lorem Ipsum
Lorem Ipsum/Lorem Ipsum - Lorem IpsumLorem Ipsum/Lorem Ipsum/
+ Lorem Ipsum
1Lorem Ipsum Lorem Ipsum Lorem Ipsum
+ Lorem Ipsum Lorem Ipsum. Lorem Ipsum. Lorem Ipsum. Lorem Ipsum.
Lorem IpsumLorem Ipsum02.10.2020318Lorem Ipsum Lorem Ipsum Lorem Ipsum
2Lorem Ipsum Lorem Ipsum Lorem Ipsum_Lorem Ipsum
+ Lorem Ipsum
17.09.2020 25.09.2020                      520Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
3Lorem Ipsum Lorem Ipsum_Lorem Ipsum17.01.2019136.402,64Lorem Ipsum Lorem Ipsum Lorem Ipsum
4Lorem Ipsum Lorem Ipsum Lorem Ipsum_Lorem Ipsum17.01.201955.745,56Lorem Ipsum Lorem Ipsum Lorem Ipsum
5Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum09.01.2020584.589,34Lorem Ipsum/Lorem Ipsum Lorem Ipsum
+ +
 

+  + +
+ """, //Optional parameters: customImageRenders: { networkSourceMatcher(domains: ["flutter.dev"]): diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index 4aa4dccc75..2ae546cc36 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -11,7 +11,7 @@ import 'package:flutter_html/src/widgets/iframe_unsupported.dart' if (dart.library.io) 'package:flutter_html/src/widgets/iframe_mobile.dart' if (dart.library.html) 'package:flutter_html/src/widgets/iframe_web.dart'; import 'package:flutter_html/style.dart'; -import 'package:flutter_math/flutter_math.dart'; +import 'package:flutter_math_fork/flutter_math.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:html/dom.dart' as dom; import 'package:video_player/video_player.dart'; @@ -267,13 +267,13 @@ class RubyElement extends ReplacedElement { class MathElement extends ReplacedElement { dom.Element element; - String texStr; + String? texStr; MathElement({ required this.element, - required this.texStr, + this.texStr, String name = "[[MathElement]]", - }) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style(display: Display.INLINE)); + }) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style()); @override Widget toWidget(RenderContext context) { @@ -283,13 +283,15 @@ class MathElement extends ReplacedElement { return Padding( padding: const EdgeInsets.only(bottom: 3.5), child: Container( - width: element.localName == "math" ? + width: element.localName == "math" || element.parent!.localName == "body" ? MediaQuery.of(context.buildContext).size.width : null, child: Math.tex( - texStr, + texStr ?? '', + mathStyle: element.parent!.localName != "body" ? MathStyle.text : MathStyle.display, + textStyle: context.style.generateTextStyle(), onErrorFallback: (FlutterMathException e) { if (context.parser.onMathError != null) { - return context.parser.onMathError!.call(texStr, e.message, e.messageWithType); + return context.parser.onMathError!.call(texStr ?? '', e.message, e.messageWithType); } else { return Text(e.message); } @@ -428,7 +430,6 @@ ReplacedElement parseReplacedElement( case "math": return MathElement( element: element, - texStr: r'', ); case "tex": return MathElement( diff --git a/pubspec.yaml b/pubspec.yaml index 74bf5b562e..b0daf689e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,10 +29,10 @@ dependencies: chewie_audio: ^1.2.0 # Plugins for rendering the tag. - flutter_svg: ^0.20.0-nullsafety.3 + flutter_svg: ^0.21.0-nullsafety.0 # Plugin for rendering MathML - flutter_math: ^0.3.0-nullsafety.1 + flutter_math_fork: ^0.3.0+1 flutter: sdk: flutter From 2eedb3a7cf62ff8e6f7c9a7b7c37f998002a4059 Mon Sep 17 00:00:00 2001 From: tanay Date: Mon, 15 Mar 2021 19:59:37 -0400 Subject: [PATCH 5/8] Remove 3.5 bottom padding and remove accidental change to main.dart --- README.md | 4 +- example/lib/main.dart | 237 +--------------------------------- lib/src/replaced_element.dart | 33 +++-- 3 files changed, 19 insertions(+), 255 deletions(-) diff --git a/README.md b/README.md index 20809c9737..4d69159432 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. - [MathML](#mathml) + - [Tex](#tex) + - [Table](#table) - [Notes](#notes) @@ -677,7 +679,7 @@ If you have a Tex string you'd like to render inside your HTML you can do that! Use the custom `` tag inside your HTML, and place your **raw** Tex string inside. The package uses the same logic as MathML rendering above, but doesn't need to parse the string, of course. - The rendering error will also be called if your Tex string fails to render. +The rendering error will also be called if your Tex string fails to render. ### Table diff --git a/example/lib/main.dart b/example/lib/main.dart index 3b7b6c56ea..90b4bc079f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -242,242 +242,7 @@ class _MyHomePageState extends State { ), body: SingleChildScrollView( child: Html( - data: """ - -
Lorem Ipsum
-Lorem Ipsum
-Lorem Ipsum

-Lorem Ipsum
- -
-   - -
            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum
-             Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum,
-            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
-            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
-            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum;
-            Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum.
-
-Lorem Ipsum
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Lorem Ipsum
- NO
Lorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum
-
Lorem Ipsum
Lorem IpsumLorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem IpsumLorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum14.01.2021260Lorem Ipsum
3Lorem IpsumLorem IpsumLorem Ipsum11.01.2021200,00Lorem Ipsum
4Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021648,00Lorem Ipsum
5Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum09.09.2020300,00Lorem Ipsum
6Lorem Ipsum Lorem Ipsum Lorem IpsumLorem Lorem Ipsum-Lorem Ipsum15.11.201916.262,33Lorem IpsumLorem Ipsum
7Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021100,00Lorem Ipsum
8Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021760,00Lorem Ipsum
9Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021380,00Lorem Ipsum
10Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021320,00Lorem Ipsum
11Lorem Ipsum Lorem Ipsum Lorem Ipsum.Lorem IpsumLorem Ipsum17.02.202010.029.107,40Lorem IpsumLorem Ipsum.
112Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum11.01.2021220,00Lorem Ipsum
- -
 
-Lorem Ipsum
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Lorem Ipsum Lorem IpsumLorem Ipsum Lorem Ipsum / Lorem Ipsum Lorem Ipsum Lorem Ipsum
Lorem IpsumLorem IpsumLorem IpsumLorem Ipsum Lorem Ipsum
- (TL)
Lorem Ipsum
Lorem Ipsum/Lorem Ipsum - Lorem IpsumLorem Ipsum/Lorem Ipsum/
- Lorem Ipsum
1Lorem Ipsum Lorem Ipsum Lorem Ipsum
- Lorem Ipsum Lorem Ipsum. Lorem Ipsum. Lorem Ipsum. Lorem Ipsum.
Lorem IpsumLorem Ipsum02.10.2020318Lorem Ipsum Lorem Ipsum Lorem Ipsum
2Lorem Ipsum Lorem Ipsum Lorem Ipsum_Lorem Ipsum
- Lorem Ipsum
17.09.2020 25.09.2020                      520Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
3Lorem Ipsum Lorem Ipsum_Lorem Ipsum17.01.2019136.402,64Lorem Ipsum Lorem Ipsum Lorem Ipsum
4Lorem Ipsum Lorem Ipsum Lorem Ipsum_Lorem Ipsum17.01.201955.745,56Lorem Ipsum Lorem Ipsum Lorem Ipsum
5Lorem Ipsum Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum09.01.2020584.589,34Lorem Ipsum/Lorem Ipsum Lorem Ipsum
- -
 

-  - -
- """, + data: htmlData, //Optional parameters: customImageRenders: { networkSourceMatcher(domains: ["flutter.dev"]): diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index 2ae546cc36..b6d5a5f7c4 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -280,24 +280,21 @@ class MathElement extends ReplacedElement { if (element.localName == "math") { texStr = parseMathRecursive(element, r''); } - return Padding( - padding: const EdgeInsets.only(bottom: 3.5), - child: Container( - width: element.localName == "math" || element.parent!.localName == "body" ? - MediaQuery.of(context.buildContext).size.width : null, - child: Math.tex( - texStr ?? '', - mathStyle: element.parent!.localName != "body" ? MathStyle.text : MathStyle.display, - textStyle: context.style.generateTextStyle(), - onErrorFallback: (FlutterMathException e) { - if (context.parser.onMathError != null) { - return context.parser.onMathError!.call(texStr ?? '', e.message, e.messageWithType); - } else { - return Text(e.message); - } - }, - ) - ), + return Container( + width: element.localName == "math" || element.parent!.localName == "body" ? + MediaQuery.of(context.buildContext).size.width : null, + child: Math.tex( + texStr ?? '', + mathStyle: element.parent!.localName != "body" ? MathStyle.text : MathStyle.display, + textStyle: context.style.generateTextStyle(), + onErrorFallback: (FlutterMathException e) { + if (context.parser.onMathError != null) { + return context.parser.onMathError!.call(texStr ?? '', e.message, e.messageWithType); + } else { + return Text(e.message); + } + }, + ) ); } From 4d67c4176ce4cb50d97777c9189ed04ab059de23 Mon Sep 17 00:00:00 2001 From: tanay Date: Tue, 16 Mar 2021 21:34:31 -0400 Subject: [PATCH 6/8] Remove "display: block" behavior from whose parent is --- lib/src/replaced_element.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index b6d5a5f7c4..95c94da372 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -281,7 +281,7 @@ class MathElement extends ReplacedElement { texStr = parseMathRecursive(element, r''); } return Container( - width: element.localName == "math" || element.parent!.localName == "body" ? + width: element.localName == "math" ? MediaQuery.of(context.buildContext).size.width : null, child: Math.tex( texStr ?? '', From d7550d74363a4cce64f14443999fbb6d6cfd27e6 Mon Sep 17 00:00:00 2001 From: tanay Date: Thu, 18 Mar 2021 12:41:31 -0400 Subject: [PATCH 7/8] Remove official tex support and add note in README for customRender instructions --- README.md | 23 +++++++++++++++++++---- lib/src/html_elements.dart | 1 - lib/src/replaced_element.dart | 16 ++++------------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0ad773eb69..2c6e58130f 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Add the following to your `pubspec.yaml` file: | `samp` | `section` | `small` | `span`| `strike` | `strong`| `sub` | `sup` | `summary` | `svg`| `table`| | `tbody` | `td` | `template` | `tfoot` | `th` | `thead` |`time` | `tr` | `tt` | `u` | `ul` | | `var` | `video` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` | `mfrac` | -| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | `tex` (custom tag for this package) | | | | | +| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | | | ## Currently Supported CSS Attributes: @@ -718,11 +718,26 @@ If you'd like to see more MathML features, feel free to create a PR or file a fe ### Tex -If you have a Tex string you'd like to render inside your HTML you can do that! +If you have a Tex string you'd like to render inside your HTML you can do that using the same [`flutter_math`](https://pub.dev/packages/flutter_math) plugin. -Use the custom `` tag inside your HTML, and place your **raw** Tex string inside. The package uses the same logic as MathML rendering above, but doesn't need to parse the string, of course. +Use a custom tag inside your HTML (an example could be ``), and place your **raw** Tex string inside. -The rendering error will also be called if your Tex string fails to render. +Then, use the `customRender` parameter to add the widget to render Tex. It could look like this: + +```dart +Widget htmlWidget = Html( + data: r"""i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)""", + customRender: { + "tex": (_, __, ___, element) => Math.tex( + element.text, + onErrorFallback: (FlutterMathException e) { + //return your error widget here e.g. + return Text(e.message); + }, + ), + } +); +``` ### Table diff --git a/lib/src/html_elements.dart b/lib/src/html_elements.dart index 80cdb90fa5..020f0ae345 100644 --- a/lib/src/html_elements.dart +++ b/lib/src/html_elements.dart @@ -87,7 +87,6 @@ const REPLACED_ELEMENTS = [ "rt", "ruby", "math", - "tex", ]; const LAYOUT_ELEMENTS = [ diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index 7d845c02f9..53e8f2c6eb 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -275,20 +275,17 @@ class MathElement extends ReplacedElement { MathElement({ required this.element, this.texStr, - String name = "[[MathElement]]", + String name = "math", }) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style()); @override Widget toWidget(RenderContext context) { - if (element.localName == "math") { - texStr = parseMathRecursive(element, r''); - } + texStr = parseMathRecursive(element, r''); return Container( - width: element.localName == "math" ? - MediaQuery.of(context.buildContext).size.width : null, + width: MediaQuery.of(context.buildContext).size.width, child: Math.tex( texStr ?? '', - mathStyle: element.parent!.localName != "body" ? MathStyle.text : MathStyle.display, + mathStyle: MathStyle.display, textStyle: context.style.generateTextStyle(), onErrorFallback: (FlutterMathException e) { if (context.parser.onMathError != null) { @@ -433,11 +430,6 @@ ReplacedElement parseReplacedElement( return MathElement( element: element, ); - case "tex": - return MathElement( - element: element, - texStr: element.text, - ); default: return EmptyContentElement(name: element.localName == null ? "[[No Name]]" : element.localName!); } From ee0c6419d9a82ea7070229e8809c985aad500d0c Mon Sep 17 00:00:00 2001 From: tanay Date: Thu, 18 Mar 2021 12:42:15 -0400 Subject: [PATCH 8/8] Fix minor merge conflict --- lib/flutter_html.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index baf67d54f0..a678ea8911 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -57,6 +57,7 @@ class Html extends StatelessWidget { this.customRender = const {}, this.customImageRenders = const {}, this.onImageError, + this.onMathError, this.shrinkWrap = false, this.onImageTap, this.blacklistedElements = const [],