Created with <3 with dartpad.dev.
Last active
August 18, 2023 09:04
-
-
Save dfdgsdfg/9d5cf92e2c390e0ac16e9aa7373e55ac to your computer and use it in GitHub Desktop.
Flutter CJK underline bug workaround
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code is distributed under the MIT License. | |
// Copyright (c) 2019 Remi Rousselet. | |
// You can find the original at https://github.com/rrousselGit/provider. | |
import 'package:flutter/material.dart'; | |
import 'package:google_fonts/google_fonts.dart'; | |
// This is a reimplementation of the default Flutter application | |
// using provider + [ChangeNotifier]. | |
void main() { | |
runApp( | |
// Providers are above [MyApp] instead of inside it, so that | |
// tests can use [MyApp] while mocking the providers | |
const MyApp(), | |
); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData( | |
useMaterial3: true, | |
), | |
home: const MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatelessWidget { | |
const MyHomePage({Key? key}) : super(key: key); | |
TextPainter _textPainter(String text, TextStyle style, | |
{double maxWidth = double.infinity}) { | |
final tp = TextPainter( | |
text: TextSpan(text: text, style: style), | |
textDirection: TextDirection.ltr, | |
maxLines: 999, | |
)..layout(minWidth: 0, maxWidth: maxWidth); | |
return tp; | |
} | |
@override | |
Widget build(BuildContext context) { | |
final theme = Theme.of(context); | |
final textTheme = theme.textTheme; | |
final style = textTheme.displayMedium; | |
final googleFontStyle = GoogleFonts.nanumGothic().copyWith(fontSize: style!.fontSize); | |
final underlineStyle = style!.apply( | |
decoration: TextDecoration.underline, | |
); | |
final bgStyle = style.apply( | |
backgroundColor: Colors.yellow, | |
); | |
const ko = '한글 사이에 공 백 이나english있으면 그리고1숫2자도 마찮가지 플 러 터 this lo사lo v랑ev해e'; | |
const jp = '日本語の間に空白や英語や数2字3が4混 ざ っ て いても、私はあloなloたをve愛しveています。'; | |
const zn = | |
'好的,以下是你的句子,其中有空格、英文和数字,用2中23文4翻222译:「漢 字 之 間 有 空格、英文和數字,我lo愛ve你!」'; | |
const en = | |
"asdjoaiystdfoaystdfoaystdfoasdt asodiyftaosidytf asodiyftaosid fasd asidyftaosidy aosdyt faoisdytfao"; | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text(''), | |
), | |
body: LayoutBuilder( | |
builder: (context, constraints) { | |
final tp = _textPainter(ko, underlineStyle); | |
tp.layout(minWidth: 0, maxWidth: constraints.maxWidth); | |
final lines = tp.computeLineMetrics(); | |
lines.map((x) => print(x)); | |
return SingleChildScrollView( | |
child: Column( | |
children: <Widget>[ | |
Text('Flutter underline, background color bug with CJK'), | |
Text( | |
ko, | |
style: underlineStyle, | |
), | |
Text( | |
ko, | |
style: bgStyle, | |
), | |
const SizedBox(height: 32), | |
Text('With CJK 1 line'), | |
AppLinedText( | |
text: 'CJK 한 글 en영1gl어ish', | |
style: style, | |
), | |
const SizedBox(height: 32), | |
Text('With CJK 1 line with google_font custom font'), | |
AppLinedText( | |
text: 'CJK 한 글 en영1gl어ish', | |
style: googleFontStyle, | |
), | |
const SizedBox(height: 32), | |
Text('With CJK multiline'), | |
AppLinedText( | |
text: 'CJK 한 글 en영gla어1ish as1dfas;df 한1글 플l러o터v 사e랑l해o', | |
style: style, | |
), | |
Text('With CJK multiline google_font custom font'), | |
AppLinedText( | |
text: 'CJK 한 글 en영gla어1ish as1dfas;df 한1글 플l러o터v 사e랑l해o', | |
style: style, | |
), | |
const SizedBox(height: 32), | |
Text('Only Western Alphabet multiline'), | |
AppLinedText( | |
text: en, | |
style: style, | |
), | |
const SizedBox(height: 32), | |
], | |
), | |
); | |
}, | |
), | |
); | |
} | |
} | |
class AppLinedText extends StatelessWidget { | |
final double underlineSpacing; | |
final String text; | |
final TextStyle style; | |
final int maxLines = 999; | |
const AppLinedText({ | |
super.key, | |
this.underlineSpacing = 0, | |
required this.text, | |
required this.style, | |
}); | |
TextPainter _textPainter({double maxWidth = double.infinity}) { | |
final tp = TextPainter( | |
text: TextSpan(text: text, style: style), | |
textDirection: TextDirection.ltr, | |
maxLines: maxLines, | |
)..layout(minWidth: 0, maxWidth: maxWidth); | |
return tp; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return LayoutBuilder(builder: (context, constraints) { | |
final tp = _textPainter(maxWidth: constraints.maxWidth); | |
final canvasSize = tp.size; | |
final lines = tp.computeLineMetrics() | |
..sort((a, b) => a.lineNumber.compareTo(b.lineNumber)); | |
return SizedBox( | |
width: canvasSize.width * 1.05, | |
height: canvasSize.height, | |
child: Stack( | |
children: [ | |
...lines.map( | |
(x) { | |
final index = x.lineNumber + 1; | |
final mWidth = | |
canvasSize.width < x.width ? canvasSize.width : x.width; | |
return Positioned( | |
child: CustomPaint( | |
painter: _LinePainter(), | |
size: Size( | |
mWidth, | |
(x.height + | |
(underlineSpacing < 0 ? 0 : underlineSpacing)) * | |
index, | |
), | |
), | |
); | |
}, | |
), | |
Text( | |
text, | |
style: style, | |
maxLines: maxLines, | |
), | |
], | |
), | |
); | |
}); | |
} | |
} | |
class _LinePainter extends CustomPainter { | |
final Color? color; | |
_LinePainter({this.color}); | |
@override | |
void paint(Canvas canvas, Size size) { | |
final startPoint = Offset(0, size.height); | |
final endPoint = Offset(size.width, size.height); | |
final mColor = color ?? Colors.red; | |
Paint paint = Paint() | |
..color = mColor | |
..strokeWidth = 8 | |
..strokeCap = StrokeCap.square; | |
canvas.drawLine(startPoint, endPoint, paint); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment