flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
162.19k stars 26.64k forks source link

Flutter Web HTML render : TextAlign.justify = Lines that end with hard line breaks are not aligned towards the [start] edge #147515

Closed pagetronic closed 1 week ago

pagetronic commented 2 weeks ago

When I try to TextAlign justify a Text.rich with Web export, on a web-renderer canvaskit there is no problem, but with a html renderer the last line is not aligned to "start".

In CSS it would have been necessary to put "text-align-last: left;"

Steps to reproduce

    SelectableText.rich(
        TextSpan(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."),  
        textAlign: TextAlign.justify,
    )

Expected results

Capture d’écran du 2024-04-29 13-46-53

Actual results

Capture d’écran du 2024-04-29 13-47-37

Code sample

Code sample ```dart @override Widget build(BuildContext context) { if (text == null) { return const SizedBox.shrink(); } List riches = []; TextStyle lineBreak = const TextStyle(fontSize: 5); RegExp pattern = RegExp("([^\n\r]+)?([\n\r ]+)?"); RegExp headPattern = RegExp('^([= ]+)([^=]+)([= ]+)\$'); RegExp hrefPattern = RegExp('(.*)?]+href="(.*?)"[^>]*>(.*)?(.*)?'); Iterator matches = pattern.allMatches(text!).iterator; while (matches.moveNext()) { String subText = matches.current.group(1) ?? ""; String separator = matches.current.group(2) ?? ""; separator = separator.substring(0, min(separator.length, 2)); RegExpMatch? head = headPattern.firstMatch(subText); if (head != null) { riches.add(TextSpan( text: head.group(2)!.trim(), style: head.group(1)!.length == 1 ? Theme.of(context).textTheme.headlineLarge : head.group(1)!.length == 2 ? Theme.of(context).textTheme.headlineMedium : head.group(1)!.length == 3 ? Theme.of(context).textTheme.headlineSmall : head.group(1)!.length == 4 ? Theme.of(context).textTheme.titleLarge : head.group(1)!.length == 5 ? Theme.of(context).textTheme.titleMedium : head.group(1)!.length == 6 ? Theme.of(context).textTheme.titleSmall : null, )); riches.add(TextSpan(text: separator.substring(0, min(separator.length, 1)), style: lineBreak)); } else if (subText.startsWith("*")) { riches.add(TextSpan( text: " ${String.fromCharCode(0x2022)} ${subText.substring(1)}", style: Theme.of(context).textTheme.bodyMedium)); riches.add(TextSpan(text: separator, style: lineBreak)); } else { if (hrefPattern.hasMatch(subText)) { Iterator hrefMatches = hrefPattern.allMatches(subText).iterator; while (hrefMatches.moveNext()) { String before = hrefMatches.current.group(1) ?? ''; String anchor = hrefMatches.current.group(3) ?? ''; String url = hrefMatches.current.group(2) ?? ''; String after = hrefMatches.current.group(4) ?? ''; riches.add( TextSpan(text: before, style: Theme.of(context).textTheme.bodyMedium), ); riches.add( TextSpan( text: anchor, mouseCursor: SystemMouseCursors.click, style: TextStyle( fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, decoration: TextDecoration.underline, ), recognizer: TapGestureRecognizer() ..onTap = () => url.startsWith("http") ? launchUrl(Uri.parse(url)) : Navigator.pushNamed(context, url), ), ); riches.add( TextSpan(text: after, style: Theme.of(context).textTheme.bodyMedium), ); } } else { riches.add(TextSpan(text: subText, style: Theme.of(context).textTheme.bodyMedium)); } riches.add(TextSpan(text: separator, style: lineBreak)); } } return SelectableText.rich( TextSpan(children: riches), //TODO bug? textAlign: !kIsWeb ? TextAlign.justify : TextAlign.left, ); } ```
darshankawar commented 2 weeks ago

Thanks for the report @pagetronic Can you provide flutter version on which you are seeing this behavior ? TextAlign.justify for html renderer was added as part of https://github.com/flutter/flutter/issues/88270

pagetronic commented 2 weeks ago

thanks for your consideration

Flutter version 3.19.6

darshankawar commented 1 week ago

Steps to reproduce

    SelectableText.rich(
        TextSpan(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."),  
        textAlign: TextAlign.justify,
    )

Thanks for the update. With this code snippet, below are the current results:

  1. With html renderer:
Screenshot 2024-05-02 at 11 59 28 AM
  1. With canvaskit:
Screenshot 2024-05-02 at 12 01 01 PM

I see difference of a word that starts from next line using html, but another word using canvaskit. Is this the difference you are pointing to as not expected ?

pagetronic commented 1 week ago

Lines that end with hard line breaks are aligned towards the [start] edge.

Ok, sorry I understand now.

Only the last line is "start" aligned in Text.rich. If we place successions of TextSpan there, which have last lines, they do not consider an alignment on their own last line.

You must use Collum() and nest Text.rich to each section of text that you want to align TextAlign.justify.

On the other hand this poses a big problem for SelectableText.rich you have to use SelectableRegion instead.

Do not do :

SelectableText.rich(
      TextSpan(children: [
        for (int i = 0; i < 5; i++)
          TextSpan(children: [
            for (int i = 0; i < 5; i++)
              TextSpan(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " * 5),
            TextSpan(text: "\n\n"),
          ]),
      ]),
      textAlign: TextAlign.justify,
    )

Do :

SelectableRegion(
      focusNode: FocusNode(),
      selectionControls: materialTextSelectionControls,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (int i = 0; i < 5; i++) ...[
            Text.rich(
              TextSpan(
                children: [
                  for (int i = 0; i < 5; i++)
                    TextSpan(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " * 5),
                ],
              ),
              textAlign: TextAlign.justify,
            ),
            Text("\n")
          ],
        ],
      ),
    )

It even seems much better in terms of performance

darshankawar commented 1 week ago

Seems like you have resolved the issue. Closing based on it. If you disagree, write in comments and I'll reopen it.