diff --git a/.circleci/config.yml b/.circleci/config.yml index 010547222b..718328b1f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,4 +37,4 @@ jobs: command: melos run analyze - run: name: Check That Flutter Code is Formatted Correctly - command: flutter format -o none --set-exit-if-changed . + command: dart format -o none --set-exit-if-changed . diff --git a/.github/flutter_html_screenshot.png b/.github/flutter_html_screenshot.png deleted file mode 100644 index 3d1b893b61..0000000000 Binary files a/.github/flutter_html_screenshot.png and /dev/null differ diff --git a/.github/flutter_html_screenshot2.png b/.github/flutter_html_screenshot2.png deleted file mode 100644 index eb47b0e146..0000000000 Binary files a/.github/flutter_html_screenshot2.png and /dev/null differ diff --git a/.github/flutter_html_screenshot3.png b/.github/flutter_html_screenshot3.png deleted file mode 100644 index 75a065879d..0000000000 Binary files a/.github/flutter_html_screenshot3.png and /dev/null differ diff --git a/.gitignore b/.gitignore index caf0d56dbf..188d2e1fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -154,4 +154,6 @@ modules.xml packages/**/pubspec_overrides.yaml ./pubspec_overrides.yaml -/example/pubspec_overrides.yaml \ No newline at end of file +/example/pubspec_overrides.yaml + +coverage/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 122bb26fbb..80c8b6f22a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# Change Log + + +#### 3.0.0-beta.1 - *May 2023* + + - Several Breaking Changes. See the [migration guide](https://github.com/Sub6Resources/flutter_html/wiki/Migration-Guides#300) + + - **FIX**: Aspect ratio exception when height is 0 ([#1222](https://github.com/sub6resources/flutter_html/issues/1222)). ([ed75f8fe](https://github.com/sub6resources/flutter_html/commit/ed75f8fef779e920ecc1f27719a4150a29e3ebee)) + - **FIX**: Fix issue with font scaling introduced in 3.0.0-alpha.6 ([#1173](https://github.com/sub6resources/flutter_html/issues/1173)). ([c75e0dfb](https://github.com/sub6resources/flutter_html/commit/c75e0dfb1be6cb79748f719487043d12bc330c60)) + - **FIX**: Fix various issues with list rendering. ([520ff3c3](https://github.com/sub6resources/flutter_html/commit/520ff3c326d5dc8f5a601022c2a32d58e2e83cbb)) + - **FIX**: Apply margins to properly. ([7581ea79](https://github.com/sub6resources/flutter_html/commit/7581ea798744b2830affaaf75bbdff016b03f7af)) + - **FIX**: Use enum instead of const int internally in length.dart. ([9dc7f08c](https://github.com/sub6resources/flutter_html/commit/9dc7f08ca238ff6a93314be5de716ad4e3baebb8)) + - **FIX**: Change CSSBoxWidget to CssBoxWidget. ([a62449a7](https://github.com/sub6resources/flutter_html/commit/a62449a77c18701a0faf8ffd650f9c535b2d006c)) + - **FEAT**: Support mmultiscripts. ([#1175](https://github.com/sub6resources/flutter_html/issues/1175)). ([a999a300](https://github.com/sub6resources/flutter_html/commit/a999a30027eff0aabb2825ffdbe383f9affab7f6)) + - **FEAT**: Support mfenced. ([#1174](https://github.com/sub6resources/flutter_html/issues/1174)). ([9ca23084](https://github.com/sub6resources/flutter_html/commit/9ca230848beb15332f96294083ed4989831130d7)) + - **FEAT**: Upgrade list-style-type to CSS3. ([deb726ae](https://github.com/sub6resources/flutter_html/commit/deb726ae2776f45305026c0aa081d4a5b5a1c71d)) + - **FEAT**: Support mtable, mtd, mtr for draw matrix. ([#1164](https://github.com/sub6resources/flutter_html/issues/1164)). ([e99e2cc1](https://github.com/sub6resources/flutter_html/commit/e99e2cc1553ab17b4ceff08f784e99283b28dff4)) + ## 3.0.0-alpha.6 - *September 2022* - **FIX** #731 Resolve newline `
` issue diff --git a/README.md b/README.md index 9210a56651..7cb73ac0fe 100644 --- a/README.md +++ b/README.md @@ -6,63 +6,57 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. -
- - - - - - - - - - -
Screenshot 1Screenshot 2Screenshot 3
A Screenshot of flutter_htmlAnother Screenshot of flutter_htmlYet another Screenshot of flutter_html
+```dart +Widget build(context) { + return Html( + data: """ +

Hello, World!

+

flutter_html supports a variety of HTML and CSS tags and attributes.

+

Over a hundred static tags are supported out of the box.

+

Or you can even define your own using an Extension:

+

Its easy to add custom styles to your Html as well using the Style class:

+

Here's a fancy <p> element!

+ """, + extensions: [ + TagExtension( + tagsToExtend: {"flutter"}, + child: const FlutterLogo(), + ), + ], + style: { + "p.fancy": Style( + textAlign: TextAlign.center, + padding: const EdgeInsets.all(16), + backgroundColor: Colors.grey, + margin: Margins(left: Margin(50, Unit.px), right: Margin.auto()), + width: Width(300, Unit.px), + fontWeight: FontWeight.bold, + ), + }, + ); +} +``` -## Table of Contents: +becomes... -- [Installing](#installing) +A screenshot showing the above code snippet rendered using flutter_html -- [Currently Supported HTML Tags](#currently-supported-html-tags) +## Table of Contents: -- [Currently Supported CSS Attributes](#currently-supported-css-attributes) +- [Supported HTML Tags](https://github.com/Sub6Resources/flutter_html/wiki/Supported-HTML-Elements) -- [Currently Supported Inline CSS Attributes](#currently-supported-inline-css-attributes) +- [Supported CSS Attributes](https://github.com/Sub6Resources/flutter_html/wiki/Supported-CSS-Attributes) - [Why flutter_html?](#why-this-package) +- [Migration Guide](#migration-guides) + - [API Reference](#api-reference) - [Constructors](#constructors) - - [Selectable Text](#selectable-text) - - [Parameters Table](#parameters) - - [Methods](#methods) - - - [Getters](#getters) - - - [Data](#data) - - - [Document](#document) - - - [onLinkTap](#onlinktap) - - - [customRender](#customrender) - - - [onImageError](#onimageerror) - - - [onImageTap](#onimagetap) - - - [tagsList](#tagslist) - - - [style](#style) - -- [Rendering Reference](#rendering-reference) - - - [Image](#image) - - [External Packages](#external-packages) - [`flutter_html_all`](#flutter_html_all) @@ -79,56 +73,21 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. - [`flutter_html_video`](#flutter_html_video) -- [Notes](#notes) - -- [Migration Guide](#migration-guides) - -- [Contribution Guide](#contribution-guide) - -## Installing: - -Add the following to your `pubspec.yaml` file: +- [Frequently Asked Questions](#faq) - dependencies: - flutter_html: ^3.0.0-alpha.5 - // Or flutter_html_all: ^3.0.0-alpha.5 to include table, video, audio, iframe... +- [Example](#example) -## Currently Supported HTML Tags: -| | | | | | | | | | | | -|------------|-----------|-------|-------------|---------|---------|-------|------|--------|--------|--------| -|`a` | `abbr` | `acronym`| `address` | `article`| `aside` | `audio`| `b` | `bdi` | `bdo` | `big` | -|`blockquote`| `body` | `br` | `caption` | `cite` | `code` | `data`| `dd` | `del` | `details` | `dfn` | -| `div` | `dl` | `dt` | `em` | `figcaption`| `figure`| `footer`| `font` | `h1` | `h2` | `h3` | -| `h4` | `h5` |`h6` | `header` | `hr` | `i` | `iframe`| `img` | `ins` | `kbd`| `li` | -| `main` | `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` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` | -| `mfrac` | `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | | - - -## Currently Supported CSS Attributes: -| | | | | | | | -|------------------|--------|------------|----------|--------------|------------------------|------------| -|`background-color`| `color`| `direction`| `display`| `font-family`| `font-feature-settings`| `font-size`| -|`font-style` | `font-weight`| `height` | `letter-spacing`| `line-height`| `list-style-type` | `list-style-position`| -|`padding` | `margin`| `text-align`| `text-decoration`| `text-decoration-color`| `text-decoration-style`| `text-decoration-thickness`| -|`text-shadow` | `vertical-align`| `white-space`| `width` | `word-spacing`| | | - -## Currently Supported Inline CSS Attributes: -| | | | | | | | -|------------------|--------|------------|----------|--------------|------------------------|------------| -|`background-color`| `border` (including specific directions) | `color`| `direction`| `display`| `font-family`| `font-feature-settings` | -| `font-size`|`font-style` | `font-weight`| `line-height` | `list-style-type` | `list-style-position`|`padding` (including specific directions) | -| `margin` (including specific directions) | `text-align`| `text-decoration`| `text-decoration-color`| `text-decoration-style`| `text-shadow` | | - -Don't see a tag or attribute you need? File a feature request or contribute to the project! ## Why this package? This package is designed with simplicity in mind. Originally created to allow basic rendering of HTML content into the Flutter widget tree, this project has expanded to include support for basic styling as well! -If you need something more robust and customizable, the package also provides a number of optional custom APIs for extremely granular control over widget rendering! + +If you need something more robust and customizable, the package also provides a number of extension APIs for extremely granular control over widget rendering! + +## Migration Guides + +[3.0.0 Migration Guide](https://github.com/Sub6Resources/flutter_html/wiki/Migration-Guides#300) ## API Reference: @@ -146,418 +105,30 @@ The `Html()` constructor is for those who would like to directly pass HTML from If you would like to modify or sanitize the HTML before rendering it, then `Html.fromDom()` is for you - you can convert the HTML string to a `Document` and use its methods to modify the HTML as you wish. Then, you can directly pass the modified `Document` to the package. This eliminates the need to parse the modified `Document` back to a string, pass to `Html()`, and convert back to a `Document`, thus cutting down on load times. -#### Selectable Text - -The package also has two constructors for selectable text support - `SelectableHtml()` and `SelectableHtml.fromDom()`. - -The difference between the two is the same as noted above. - -Please note: Due to Flutter [#38474](https://github.com/flutter/flutter/issues/38474), selectable text support is significantly watered down compared to the standard non-selectable version of the widget. The changes are as follows: - -1. The list of tags that can be rendered is significantly reduced. Key omissions include no support for images/video/audio, table, and ul/ol. - -2. No support for `customRender`, `customImageRender`, `onImageError`, `onImageTap`, `onMathError`, and `navigationDelegateForIframe`. (Support for `customRender` may be added in the future). - -3. Styling support is significantly reduced. Only text-related styling works (e.g. bold or italic), while container related styling (e.g. borders or padding/margin) do not work. - -Once the above issue is resolved, the aforementioned compromises will go away. Currently the `SelectableText.rich()` constructor does not support `WidgetSpan`s, resulting in the feature losses above. - ### Parameters: -| Parameters | Description | -|--------------|-----------------| -| `data` | The HTML data passed to the `Html` widget. This is required and cannot be null when using `Html()`. | -| `document` | The DOM document passed to the `Html` widget. This is required and cannot be null when using `Html.fromDom()`. | -| `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. | -| `customRenders` | 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. | -| `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. | -| `tagsList` | A list of elements the `Html` widget should render. The list should contain the tags of the HTML elements you wish to include. | -| `style` | A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag. | -| `selectionControls` | A custom text selection controls that allow you to override default toolbar and build toolbar with custom text selection options. See an [example](https://github.com/justinmc/flutter-text-selection-menu-examples/blob/master/lib/custom_menu_page.dart). | - -### Methods: - -| Methods | Description | -|--------------|-----------------| -| `disposeAll()` | Disposes all `ChewieController`s, `ChewieAudioController`s, and `VideoPlayerController`s being used by every `Html` widget. (Note: `Html` widgets automatically dispose their controllers, this method is only provided in case you need other behavior) | - -### Getters: - -1. `Html.tags`. This provides a list of all the tags the package renders. The main use case is to assist in excluding elements using `tagsList`. See an [example](#example-usage---tagslist---excluding-tags) below. +| Parameters | Description | +|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `data` | The HTML data passed to the `Html` widget. This is required and cannot be null when using `Html()`. | +| `document` | The DOM document passed to the `Html` widget. This is required and cannot be null when using `Html.fromDom()`. | +| `onLinkTap` | Optional. 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. | +| `extensions` | Optional. A powerful API that allows you to customize everything when rendering a specific HTML tag. | +| `shrinkWrap` | Optional. A `bool` used while rendering different widgets to specify whether they should be shrink-wrapped or not, like `ContainerSpan` | +| `onlyRenderTheseTags` | Optional. An exclusive set of elements the `Html` widget should render. Note that your html will be wrapped in `` and `` if it isn't already, so you should include those in this list. | +| `doNotRenderTheseTags` | Optional. A set of tags that should not be rendered by the `Html` widget. | +| `style` | Optional. A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag. | -2. `SelectableHtml.tags`. This provides a list of all the tags that can be rendered in selectable mode. -3. `Html.chewieAudioControllers`. This provides a list of all `ChewieAudioController`s being used by `Html` widgets. +More examples and in-depth details are available: -4. `Html.chewieControllers`. This provides a list of all `ChewieController`s being used by `Html` widgets. - -5. `Html.videoPlayerControllers`. This provides a list of all `VideoPlayerController`s being used for video widgets by `Html` widgets. - -6. `Html.audioPlayerControllers`. This provides a list of all `VideoPlayerController`s being used for audio widgets by `Html` widgets. - -### Data: - -The HTML data passed to the `Html` widget as a `String`. This is required and cannot be null when using `Html`. -Any HTML tags in the `String` that are not supported by the package will not be rendered. - -#### Example Usage - Data: - -```dart -Widget html = Html( - data: """
-

Demo Page

-

This is a fantastic product that you should buy!

-

Features

- - -
""", -); -``` - -### Document: - -The DOM document passed to the `Html` widget as a `Document`. This is required and cannot be null when using `Html.fromDom()`. -Any HTML tags in the document that are not supported by the package will not be rendered. -Using the `Html.fromDom()` constructor can be useful when you would like to sanitize the HTML string yourself before passing it to the package. - -#### Example Usage - Document: - -```dart -import 'package:html/parser.dart' as htmlparser; -import 'package:html/dom.dart' as dom; -... -String htmlData = """
-

Demo Page

-

This is a fantastic product that you should buy!

-

Features

- - -
"""; -dom.Document document = htmlparser.parse(htmlData); -/// sanitize or query document here -Widget html = Html( - document: document, -); -``` - -### onLinkTap: - -A function that defines what the widget should do when a link is tapped. - -#### Example Usage - onLinkTap: - -```dart -Widget html = Html( - data: """

- Linking to websites has never been easier. -

""", - onLinkTap: (String? url, RenderContext context, Map attributes, dom.Element? element) { - //open URL in webview, or launch URL in browser, or any other logic here - } -); -``` - -Inner links (such as `Back to the top` will work out of the box by scrolling the viewport, as long as your `Html` widget is wrapped in a scroll container such as a `SingleChildScrollView`. - -### customRenders: - -A powerful API that allows you to customize everything when rendering a specific HTML tag. This means you can change the default behaviour or add support for HTML elements that aren't supported natively. You can also make up your own custom tags in your HTML! - -`customRender` accepts a `Map`. - -`CustomRenderMatcher` is a function that requires a `bool` to be returned. It exposes the `RenderContext` which provides `BuildContext` and access to the HTML tree. - -The `CustomRender` class has two constructors: `CustomRender.widget()` and `CustomRender.inlineSpan()`. Both require a ` Function(RenderContext, Function())`. The `Function()` argument is a function that will provide you with the element's children when needed. - -To use this API, create a matching function and an instance of `CustomRender`. - -#### Example Usages - customRenders: -Note: If you add any custom tags, you must add these tags to the [`tagsList`](#tagslist) parameter, otherwise they will not be rendered. See below for an example. - -1. Simple example - rendering custom HTML tags - -```dart -Widget html = Html( - data: """ -

Display bird element and flutter element

- - - """, - customRenders: { - birdMatcher(): CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan(text: "🐦")), - flutterMatcher(): CustomRender.widget(widget: (context, buildChildren) => FlutterLogo( - style: (context.tree.element!.attributes['horizontal'] != null) - ? FlutterLogoStyle.horizontal - : FlutterLogoStyle.markOnly, - textColor: context.style.color!, - size: context.style.fontSize!.size! * 5, - )), - }, - tagsList: Html.tags..addAll(["bird", "flutter"]), -); - -CustomRenderMatcher birdMatcher() => (context) => context.tree.element?.localName == 'bird'; - -CustomRenderMatcher flutterMatcher() => (context) => context.tree.element?.localName == 'flutter'; -``` - -2. Complex example - wrapping the default widget with your own, in this case placing a horizontal scroll around a (potentially too wide) table. - -Note: Requires the [`flutter_html_table`](#flutter_html_table) package. - -
View code - -```dart -Widget html = Html( - data: """ - - - - - -
Monthly savings
January February March April May June July August September October November December
\$100 \$50 \$80 \$60 \$90 \$140 \$110 \$80 \$90 \$60 \$40 \$70
\90 \$60 \$80 \$80 \$100 \$160 \$150 \$110 \$100 \$60 \$30 \$80
- """, - customRenders: { - tableMatcher(): CustomRender.widget(widget: (context, child) { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - // this calls the table CustomRender to render a table as normal (it uses a widget so we know widget is not null) - child: tableRender.call().widget!.call(context, buildChildren), - ); - }), - }, -); - -CustomRenderMatcher tableMatcher() => (context) => context.tree.element?.localName == "table"; -``` - -
- -3. Complex example - rendering an `iframe` differently based on whether it is an embedded youtube video or some other embedded content. - -
View code - -```dart -Widget html = Html( - data: """ -

Google iframe:

- -

YouTube iframe:

- - """, - customRenders: { - iframeYT(): CustomRender.widget(widget: (context, buildChildren) { - double? width = double.tryParse(context.tree.attributes['width'] ?? ""); - double? height = double.tryParse(context.tree.attributes['height'] ?? ""); - return Container( - width: width ?? (height ?? 150) * 2, - height: height ?? (width ?? 300) / 2, - child: WebView( - initialUrl: context.tree.attributes['src']!, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) async { - //no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading - if (!request.url.contains("youtube.com/embed")) { - return NavigationDecision.prevent; - } else { - return NavigationDecision.navigate; - } - }, - ), - ); - }), - iframeOther(): CustomRender.widget(widget: (context, buildChildren) { - double? width = double.tryParse(context.tree.attributes['width'] ?? ""); - double? height = double.tryParse(context.tree.attributes['height'] ?? ""); - return Container( - width: width ?? (height ?? 150) * 2, - height: height ?? (width ?? 300) / 2, - child: WebView( - initialUrl: context.tree.attributes['src'], - javascriptMode: JavascriptMode.unrestricted, - //on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer - gestureRecognizers: [ - Factory(() => VerticalDragGestureRecognizer()) - ].toSet(), - ), - ); - }), - iframeNull(): CustomRender.widget(widget: (context, buildChildren) => Container(height: 0, width: 0)), - } - ); - -CustomRenderMatcher iframeYT() => (context) => context.tree.element?.attributes['src']?.contains("youtube.com/embed") ?? false; - -CustomRenderMatcher iframeOther() => (context) => !(context.tree.element?.attributes['src']?.contains("youtube.com/embed") - ?? context.tree.element?.attributes['src'] == null); - -CustomRenderMatcher iframeNull() => (context) => context.tree.element?.attributes['src'] == null; -``` -
- -More example usages and in-depth details available [here](https://github.com/Sub6Resources/flutter_html/wiki/All-About-customRender). - -### 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. - -#### Example Usage - onImageError: - -```dart -Widget html = Html( - data: """Alt Text of an intentionally broken image""", - onImageError: (Exception exception, StackTrace stackTrace) { - FirebaseCrashlytics.instance.recordError(exception, stackTrace); - }, -); -``` - -### onImageTap: - -A function that defines what the widget should do when an image is tapped. - -#### Example Usage - onImageTap: - -```dart -Widget html = Html( - data: """Google""", - onImageTap: (String? url, RenderContext context, Map attributes, dom.Element? element) { - //open image in webview, or launch image in browser, or any other logic here - } -); -``` - -### tagsList: - -A list of elements the `Html` widget should render. The list should contain the tags of the HTML elements you wish to whitelist. - -#### Example Usage - tagsList - Excluding Tags: -You may have instances where you can choose between two different types of HTML tags to display the same content. In the example below, the `