daohoangson / flutter_widget_from_html

Flutter package to render html as widgets that supports hyperlink, image, audio, video, iframe and many other tags.
https://pub.dev/packages/flutter_widget_from_html
MIT License
635 stars 235 forks source link

Whitespace is removed in <pre> tag #1230

Closed TijnvandenEijnde closed 5 months ago

TijnvandenEijnde commented 5 months ago

First of all, thank you for creating this package, it does almost everything I need!

I have a question about the pre tag, because it seems for now that it is swallowing the whitespace. I have been looking through the documentation and I cannot find a way to disable it without changing the package's code.

Steps to Reproduce

See the HTML down below:

<pre class="shiki material-theme-ocean" style="background-color: #0F111A" tabindex="0"><code>
  <span class="line"><span style="color: #F78C6C">import</span><span style="color: #BABED8"> </span><span style="color: #C3E88D">'package:flutter/material.dart'</span><span style="color: #89DDFF">;</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #F78C6C">import</span><span style="color: #BABED8"> </span><span style="color: #C3E88D">'package:flutter_authentication/authentication_screen.dart'</span><span style="color: #89DDFF">;</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #C792EA">void</span><span style="color: #BABED8"> </span><span style="color: #82AAFF">main</span><span style="color: #BABED8">() </span><span style="color: #89DDFF">=&gt;</span><span style="color: #BABED8"> </span><span style="color: #82AAFF">runApp</span><span style="color: #BABED8">(</span><span style="color: #C792EA">const</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">MyApp</span><span style="color: #BABED8">())</span><span style="color: #89DDFF">;</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #89DDFF">class</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">MyApp</span><span style="color: #BABED8"> </span><span style="color: #89DDFF">extends</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">StatelessWidget</span><span style="color: #BABED8"> {</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">  </span><span style="color: #C792EA">const</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">MyApp</span><span style="color: #BABED8">({super</span><span style="color: #89DDFF">.</span><span style="color: #BABED8">key})</span><span style="color: #89DDFF">;</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">  </span><span style="color: #C792EA">@override</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">  </span><span style="color: #FFCB6B">Widget</span><span style="color: #BABED8"> </span><span style="color: #82AAFF">build</span><span style="color: #BABED8">(</span><span style="color: #FFCB6B">BuildContext</span><span style="color: #BABED8"> context) {</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">    </span><span style="color: #89DDFF; font-style: italic">return</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">MaterialApp</span><span style="color: #BABED8">(</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">      theme</span><span style="color: #89DDFF">:</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">ThemeData</span><span style="color: #BABED8">(</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">        colorScheme</span><span style="color: #89DDFF">:</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">ColorScheme</span><span style="color: #89DDFF">.</span><span style="color: #82AAFF">fromSeed</span><span style="color: #BABED8">(seedColor</span><span style="color: #89DDFF">:</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">Colors</span><span style="color: #89DDFF">.</span><span style="color: #BABED8">blueAccent)</span><span style="color: #89DDFF">,</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">        useMaterial3</span><span style="color: #89DDFF">:</span><span style="color: #BABED8"> </span><span style="color: #89DDFF">true,</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">      )</span><span style="color: #89DDFF">,</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">      home</span><span style="color: #89DDFF">:</span><span style="color: #BABED8"> </span><span style="color: #C792EA">const</span><span style="color: #BABED8"> </span><span style="color: #FFCB6B">AuthenticationScreen</span><span style="color: #BABED8">()</span><span style="color: #89DDFF">,</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">    )</span><span style="color: #89DDFF">;</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">  }</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
  <span class="line"><span style="color: #BABED8">}</span><span aria-hidden="true" class="cbp-line-highlighter"></span></span>
</code></pre>

With the following code, you should be able to get the same results as shown in the screenshot in the "Actual results" section.

import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:http/http.dart' as http;
import 'package:html/parser.dart' as parser;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<String> _html;

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

    _html = _getHtml();
  }

  Future<String> _getHtml() async {
    final response = await http.get(Uri.parse(
        'https://onlyflutter.com/how-to-validate-login-credentials-in-flutter/'));

    final document = parser.parse(response.body);
    final articleElement = document.querySelector('article');

    return articleElement!.outerHtml;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueAccent),
        useMaterial3: true,
      ),
      home: Scaffold(
        body: FutureBuilder(
            future: _html,
            initialData: '',
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) =>
            switch (snapshot.connectionState) {
              ConnectionState.done => SingleChildScrollView(
                child: Padding(
                  padding: const EdgeInsets.all(20),
                  child: HtmlWidget(snapshot.data.toString()),
                ),
              ),
              _ => const Center(child: CircularProgressIndicator()),
            }),
      ),
    );
  }
}
dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.1
  html: ^0.15.4
  flutter_widget_from_html_core: ^0.14.11

Expected results

I expected to see the following:

image

And I can achieve this by disabling the following inside the _loop function of the package's flattener class:

  void _loop(BuildBit bit) {
    _bit = bit;
    final thisInheritanceResolvers =
        bit.effectiveInheritanceResolvers ?? _inheritanceResolvers;
    if (_childrenBuilder == null) {
      _resetLoop(thisInheritanceResolvers);
    }
    if (!thisInheritanceResolvers.isIdenticalWith(_inheritanceResolvers)) {
      _saveSpan();
    }
    _inheritanceResolvers = thisInheritanceResolvers;

    bit.flatten(this);

    // _swallowWhitespace = bit.swallowWhitespace ?? _swallowWhitespace;
    _swallowWhitespace = false;
  }

Actual results

This is what I am actually getting

whitespace

I think this is happening because of the spans inside the pre tag. However, I am working with dynamic RSS feeds so there is not much I can do about that. So it would be very nice if there was a way to set _shallowWhiteSpace to false.

I have already tried to override the BuildOp for the pre tag, but I could not find a way to set the _swallowWhitespace to false.

class _CustomWidgetFactory extends WidgetFactory {
  final codeOp = BuildOp(
    debugLabel: 'pre',
    defaultStyles: (_) => {
      'display': 'block',
      'white-space': 'pre',
    },
    onRenderBlock: (tree, placeholder) => placeholder.wrapWith(
      (_, child) => SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Container(
          margin: const EdgeInsets.all(10),
          child: child,
        ),
      ),
    ),
  );

  @override
  void parse(BuildTree tree) {
    final element = tree.element;

    if (element.localName == 'pre') {
      tree.register(codeOp);
      return;
    }

    super.parse(tree);
  }
}
daohoangson commented 5 months ago

The latest version flutter_widget_from_html_core@0.14.12 has a fix for this, please try upgrading and let me know whether it works for you.

TijnvandenEijnde commented 5 months ago

@daohoangson Just tested it and it is working as intended! Thank you so much for picking it up so quickly!