Skip to content

LicenseEntryWithLineBreaks leaves carriage returns in paragraphs with CRLF line endings #189313

Closed

Description

@motucraft

Steps to reproduce

  1. Run the code sample below.
  2. Check the console logs printed on startup.
  3. Compare the LF input and CRLF input sections in the app.

Expected results

LicenseEntryWithLineBreaks should treat CRLF line endings the same way as LF line endings inside hard-wrapped paragraphs.

Both LF and CRLF inputs should be reflowed into a single paragraph without leaving \r or \n characters in LicenseParagraph.text.

Actual results

LF input is reflowed as expected, but CRLF input leaves carriage return characters (\r, code unit 13) in LicenseParagraph.text.

Because of this, CRLF license text can render differently from LF license text, with unexpected spacing or line-break-like gaps at the original line endings.

This appears to happen because beforeParagraph already skips \r\n as a paragraph boundary in licenses.dart, but addLine() does not strip the trailing \r when collecting hard-wrapped lines inside a paragraph.

For paragraph boundaries, CRLF is already handled here:

case '\r':
case '\n':
case '\f':
if (lines.isNotEmpty) {
result.add(getParagraph());
}
if (text[currentPosition] == '\r' &&
currentPosition < text.length - 1 &&
text[currentPosition + 1] == '\n') {
currentPosition += 1;
}

Code sample

Code sample
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  final String lfParagraph = const LicenseEntryWithLineBreaks(<String>[
    'lf_license',
  ], _lfLicenseText).paragraphs.single.text;
  final String crlfParagraph = const LicenseEntryWithLineBreaks(<String>[
    'crlf_license',
  ], _crlfLicenseText).paragraphs.single.text;

  debugPrint('LF contains CR: ${lfParagraph.contains('\r')}');
  debugPrint('LF contains LF: ${lfParagraph.contains('\n')}');
  debugPrint('LF code units: ${lfParagraph.codeUnits}');
  debugPrint('CRLF contains CR: ${crlfParagraph.contains('\r')}');
  debugPrint('CRLF contains LF: ${crlfParagraph.contains('\n')}');
  debugPrint('CRLF code units: ${crlfParagraph.codeUnits}');

  LicenseRegistry.addLicense(() async* {
    yield const LicenseEntryWithLineBreaks(<String>[
      'lf_license',
    ], _lfLicenseText);
    yield const LicenseEntryWithLineBreaks(<String>[
      'crlf_license',
    ], _crlfLicenseText);
  });

  runApp(const MyApp());
}

const String _lfLicenseText =
    'This paragraph is hard-wrapped with LF line endings.\n'
    'These lines should be joined with spaces, not preserved as line breaks.\n'
    'The paragraph should appear as one reflowed paragraph.\n';

const String _crlfLicenseText =
    'This paragraph is hard-wrapped with CRLF line endings.\r\n'
    'These lines should be joined with spaces, not preserved as line breaks.\r\n'
    'The paragraph should appear as one reflowed paragraph.\r\n';

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: HomePage());
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final String lfParagraph = const LicenseEntryWithLineBreaks(<String>[
      'lf_license',
    ], _lfLicenseText).paragraphs.single.text;
    final String crlfParagraph = const LicenseEntryWithLineBreaks(<String>[
      'crlf_license',
    ], _crlfLicenseText).paragraphs.single.text;

    return Scaffold(
      appBar: AppBar(title: const Text('License CRLF repro')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _ParsedLicenseCard(title: 'LF input', paragraph: lfParagraph),
          const SizedBox(height: 16),
          _ParsedLicenseCard(title: 'CRLF input', paragraph: crlfParagraph),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: () => showLicensePage(context: context),
            child: const Text('Show licenses'),
          ),
        ],
      ),
    );
  }
}

class _ParsedLicenseCard extends StatelessWidget {
  const _ParsedLicenseCard({required this.title, required this.paragraph});

  final String title;
  final String paragraph;

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;
    final bool containsCr = paragraph.contains('\r');
    final bool containsLf = paragraph.contains('\n');
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(title, style: textTheme.titleMedium),
            const SizedBox(height: 8),
            Text('contains CR: $containsCr'),
            Text('contains LF: $containsLf'),
            const SizedBox(height: 8),
            Text('code units: ${paragraph.codeUnits}'),
            const SizedBox(height: 8),
            Text(paragraph),
          ],
        ),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration
2026-07-11.14.42.57.mov

Logs

Logs
I/flutter (28156): LF contains CR: false
I/flutter (28156): LF contains LF: false
I/flutter (28156): LF code units: [84, 104, 105, 115, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 32, 105, 115, 32, 104, 97, 114, 100, 45, 119, 114, 97, 112, 112, 101, 100, 32, 119, 105, 116, 104, 32, 76, 70, 32, 108, 105, 110, 101, 32, 101, 110, 100, 105, 110, 103, 115, 46, 32, 84, 104, 101, 115, 101, 32, 108, 105, 110, 101, 115, 32, 115, 104, 111, 117, 108, 100, 32, 98, 101, 32, 106, 111, 105, 110, 101, 100, 32, 119, 105, 116, 104, 32, 115, 112, 97, 99, 101, 115, 44, 32, 110, 111, 116, 32, 112, 114, 101, 115, 101, 114, 118, 101, 100, 32, 97, 115, 32, 108, 105, 110, 101, 32, 98, 114, 101, 97, 107, 115, 46, 32, 84, 104, 101, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 32, 115, 104, 111, 117, 108, 100, 32, 97, 112, 112, 101, 97, 114, 32, 97, 115, 32, 111, 110, 101, 32, 114, 101, 102, 108, 111, 119, 101, 100, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 46]
I/flutter (28156): CRLF contains CR: true
I/flutter (28156): CRLF contains LF: false
I/flutter (28156): CRLF code units: [84, 104, 105, 115, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 32, 105, 115, 32, 104, 97, 114, 100, 45, 119, 114, 97, 112, 112, 101, 100, 32, 119, 105, 116, 104, 32, 67, 82, 76, 70, 32, 108, 105, 110, 101, 32, 101, 110, 100, 105, 110, 103, 115, 46, 13, 32, 84, 104, 101, 115, 101, 32, 108, 105, 110, 101, 115, 32, 115, 104, 111, 117, 108, 100, 32, 98, 101, 32, 106, 111, 105, 110, 101, 100, 32, 119, 105, 116, 104, 32, 115, 112, 97, 99, 101, 115, 44, 32, 110, 111, 116, 32, 112, 114, 101, 115, 101, 114, 118, 101, 100, 32, 97, 115, 32, 108, 105, 110, 101, 32, 98, 114, 101, 97, 107, 115, 46, 13, 32, 84, 104, 101, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 32, 115, 104, 111, 117, 108, 100, 32, 97, 112, 112, 101, 97, 114, 32, 97, 115, 32, 111, 110, 101, 32, 114, 101, 102, 108, 111, 119, 101, 100, 32, 112, 97, 114, 97, 103, 114, 97, 112, 104, 46, 13]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.44.6, on macOS 26.5.2 25F84 darwin-arm64, locale ja-JP) [1,026ms]
    • Flutter version 3.44.6 on channel stable at /Users/osaki/fvm/versions/3.44.6
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ee80f08bbf (2 days ago), 2026-07-08 15:02:06 -0700
    • Engine revision 83675ed276
    • Dart version 3.12.2
    • DevTools version 2.57.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
      enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [2.3s]
    • Android SDK at /Users/osaki/Library/Android/sdk
    • Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
    • Platform android-36, build-tools 36.1.0
    • Java binary at: /Users/osaki/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.5) [1,786ms]
    • Xcode at /Applications/Xcode_26.5.app/Contents/Developer
    • Build 17F42
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [5ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (4 available) [6.5s]
    • Pixel 10 (mobile)             • 55161FDCR002T4            • android-arm64  • Android 17 (API 37)
    • motucraft (wireless) (mobile) • 00008150-001C305A3440401C • ios            • iOS 26.5.2 23F84
    • macOS (desktop)               • macos                     • darwin-arm64   • macOS 26.5.2 25F84 darwin-arm64
    • Chrome (web)                  • chrome                    • web-javascript • Google Chrome 150.0.7871.115

[✓] Network resources [672ms]
    • All expected network resources are available.

• No issues found!

Activity

  1. saurabh-mirajkar commented on Jul 13, 2026

    @saurabh-mirajkar
    Contributor

    @motucraft Thanks for reporting this. I was able to reproduced this behaviour on latest Flutter stable 3.44.6. Routing this for further investigation.

  2. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Jul 13, 2026
  3. justinmc commented on Jul 14, 2026

    @justinmc
    Contributor

    @motucraft @saurabh-mirajkar The video looks like Android, is this only reproducible on Android or on all platforms?

    Edit: After reading this it seems like it would repro on any platform where you give it carriage returns.

  4. added
    P2Important issues not at the top of the work list
    fyi-text-inputFor the attention of Text Input team
    on Jul 14, 2026
  5. motucraft commented on Jul 15, 2026

    @motucraft
    ContributorAuthor

    @justinmc
    Yes, I confirmed this on iOS as well. This is not Android-specific; as you noted, it can reproduce on any platform when the license text contains CRLF line endings.

    Thanks for reviewing the PR!

  6. added 2 commits that reference this issue on Jul 22, 2026
    86571d8
    a49c18e
  7. added a commit that references this issue on Aug 14, 2026
    1777461
  8. added a commit that references this issue on Aug 15, 2026
    d26b43a
  9. github-actions commented on Aug 29, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  10. locked as resolved and limited conversation to collaborators on Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listframeworkflutter/packages/flutter repository. See also f: labels.fyi-text-inputFor the attention of Text Input teamteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions