Sub6Resources / flutter_html

A Flutter widget for rendering static html as Flutter widgets (Will render over 80 different html tags!)
https://pub.dev/packages/flutter_html
MIT License
1.75k stars 805 forks source link

[BUG] Cannot use Certain html : "Feature tag must be exactly 4 characters long" #1380

Open dawarepramod4 opened 7 months ago

dawarepramod4 commented 7 months ago

Describe the bug:

When i am using certain HTML inside the widget i am getting error: image

HTML to reproduce the issue:

use following widget in your code(with the data provided inside it) :

 Html(
                          onLinkTap: (url, _, __) async {
                            if (await canLaunchUrl(Uri.parse(url!))) {
                              await launchUrl(
                                Uri.parse(url),
                              );
                            } else {
                              tfLog.info("Not able to Launch Url: $url");
                            }
                          },
                          style: {
                            "body": Style(
                              fontSize: FontSize(18.0),
                              // fontWeight: FontWeight.bold,
                            ),
                          },
                          data: '''
<p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">Concurrency in Rust is one of the standout features of the language. While concurrency in other languages can often lead to complex and hard-to-debug issues, Rust’s memory safety guarantees make it far easier to write concurrent code.</p><h3 class="wp-block-heading" style="box-sizing: inherit; border: 0px; font-size: 1.33333rem; margin: 0px auto 20px; outline: 0px; padding: 0px; vertical-align: baseline; clear: both; line-height: 1.4; font-family: &quot;Plus Jakarta Sans&quot;, sans-serif; max-width: var(--wp--custom--ast-content-width-size); background-color: rgb(255, 255, 255);"><span class="ez-toc-section" id="Concurrency_with_Threads" ez-toc-data-id="#Concurrency_with_Threads" style="box-sizing: inherit;"></span>Concurrency with Threads<span class="ez-toc-section-end" style="box-sizing: inherit;"></span></h3><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">The basic unit of execution in Rust is a thread. By default, Rust gives you fine-grained control over when and how new threads are created. Each thread is isolated and has its own stack and local state. This isolation is key to Rust’s ability to prevent data races at compile time.</p><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">Consider this example:</p><pre class="wp-block-preformatted" style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 1.6em; vertical-align: baseline; overflow: auto; background-image: ; background-position-x: ; background-position-y: ; background-size: ; background-repeat-x: ; background-repeat-y: ; background-attachment: ; background-origin: ; background-clip: ; font-family: &quot;Courier 10 Pitch&quot;, Courier, monospace; max-width: var(--wp--custom--ast-content-width-size); text-wrap: wrap; color: rgb(103, 118, 142);"><code style="box-sizing: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-stretch: normal; font-size: 15px; line-height: normal; font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace;">use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}</code></pre><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">This code creates a new thread that executes a closure, printing a statement 10 times with a pause in between each one. In the meantime, the main thread also prints a similar statement 5 times. This concurrent execution can lead to various outputs, depending on how the operating system schedules the threads.</p><h3 class="wp-block-heading" style="box-sizing: inherit; border: 0px; font-size: 1.33333rem; margin: 0px auto 20px; outline: 0px; padding: 0px; vertical-align: baseline; clear: both; line-height: 1.4; font-family: &quot;Plus Jakarta Sans&quot;, sans-serif; max-width: var(--wp--custom--ast-content-width-size); background-color: rgb(255, 255, 255);"><span class="ez-toc-section" id="Message_Passing" ez-toc-data-id="#Message_Passing" style="box-sizing: inherit;"></span>Message Passing<span class="ez-toc-section-end" style="box-sizing: inherit;"></span></h3><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">A central idea of concurrent programming is the concept of message passing, where threads communicate by sending each other messages. This strategy avoids shared state and the resulting possibility of data races. Rust provides first-class support for message passing through channels.</p><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">Let’s look at how channels work in Rust:</p><pre class="wp-block-preformatted" style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 1.6em; vertical-align: baseline; overflow: auto; background-image: ; background-position-x: ; background-position-y: ; background-size: ; background-repeat-x: ; background-repeat-y: ; background-attachment: ; background-origin: ; background-clip: ; font-family: &quot;Courier 10 Pitch&quot;, Courier, monospace; max-width: var(--wp--custom--ast-content-width-size); text-wrap: wrap; color: rgb(103, 118, 142);"><code style="box-sizing: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-stretch: normal; font-size: 15px; line-height: normal; font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace;">use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}</code></pre><p style="box-sizing: inherit; border: 0px; font-size: 18px; margin: 0px auto 1.6em; outline: 0px; padding: 0px; vertical-align: baseline; max-width: var(--wp--custom--ast-content-width-size); font-family: Inter, sans-serif; background-color: rgb(255, 255, 255);">Here, we first create a channel using&nbsp;<code style="box-sizing: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-stretch: normal; font-size: 15px; line-height: normal; font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace;">mpsc::channel</code>. This method returns a tuple of a transmitter and a receiver. The transmitter&nbsp;<code style="box-sizing: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-stretch: normal; font-size: 15px; line-height: normal; font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace;">tx</code>&nbsp;is then moved into a new thread where it’s used to send a string message. In the main thread, we call&nbsp;<code style="box-sizing: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-stretch: normal; font-size: 15px; line-height: normal; font-family: Monaco, Consolas, &quot;Andale Mono&quot;, &quot;DejaVu Sans Mono&quot;, monospace;">recv</code>&nbsp;on the receiver rx to wait for a message. When the message arrives, it’s printed out. This example illustrates how you can safely use message-passing concurrency to send data from one thread to another.</p>'''),

Html widget configuration:

provided above code. Expected behavior:

it should show html properly. FOR THE SAME HTML WE GET THE OUTPUT AS: image

Screenshots:

Device details and Flutter/Dart/flutter_html versions:

I am running this on chrome latest version. flutter run web image

flutter doctor: image

Stacktrace/Logcat

INFO: Api Call Initiated for : /getuserprofile (at xxxxxx/api/api.dart 413:13 issueRequest) ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ The following assertion was thrown building Html(state: _HtmlState#9e569): Assertion failed: org-dartlang-sdk:///lib/ui/text.dart:77:16 feature.length == 4 "Feature tag must be exactly four characters long." The relevant error-causing widget was: Html Html:file: xxxxxx/lib/features/knowledge/knowledge_screen.dart:52:23 knowledge_screen.dart:52 When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddcruntime/errors.dart 288:49 throw dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 29:3 assertFailed lib/ui/text.dart 77:34 new packages/flutter_html/src/css_parser.dart 948:42 expressionToFontFeatureSettings packages/flutter_html/src/css_parser.dart 318:33 dart-sdk/lib/_internal/js_dev_runtime/private/linked_hash_map.dart 21:13 forEach packages/flutter_html/src/css_parser.dart 13:15 declarationsToStyle packages/flutter_html/src/css_parser.dart 671:12 inlineCssToStyle packages/flutter_html/src/html_parser.dart 309:11 [_styleTreeRecursive] 3 packages/flutter_html/src/html_parser.dart 325:7 [_styleTreeRecursive] packages/flutter_html/src/html_parser.dart 294:5 styleTree packages/flutter_html/src/html_parser.dart 166:5 prepareTree packages/flutter_html/src/html_parser.dart 156:5 didChangeDependencies packages/flutter/src/widgets/framework.dart 5237:11 [_firstBuild] packages/flutter/src/widgets/framework.dart 5062:5 mount packages/flutter/src/widgets/framework.dart 3971:15 inflateWidget packages/flutter/src/widgets/framework.dart 3708:18 updateChild

Additional info:

A picture of a cute animal (not mandatory but encouraged)

okarasahin commented 5 months ago

I'm having the same issue with no solution so far :(

Manuel36791 commented 2 months ago

This issue may happen cuz your content inside the HTML(data: String) have some special characters you can use a small function to refactor that String to clear out the special characters in it.

  String htmlContent = "";
  String cleanedHtmlContent = "";

  formatDescription() {
    setState(
      (() {
        htmlContent = data.content;
        cleanedHtmlContent = htmlContent.replaceAll(RegExp(r'<[^>]*>'), '');
      }),
    );
  }

  @override
  void initState() {
    super.initState();
    formatDescription();
  }

HTML(data: cleanedHtmlContent)

Just replace the data.content with your desired String and it should work after this. 😁

koreaksh commented 2 weeks ago

"I also encountered the same issue, and as a solution, I used the provided code."

data.replaceAll('font-feature-settings: normal;', '');

"If this method doesn't work, try finding the 'feature' part in the HTML data and remove it using the same approach."