Skip to content

Instantly share code, notes, and snippets.

@dfdgsdfg
Last active August 18, 2023 09:04
Show Gist options
  • Save dfdgsdfg/9d5cf92e2c390e0ac16e9aa7373e55ac to your computer and use it in GitHub Desktop.
Save dfdgsdfg/9d5cf92e2c390e0ac16e9aa7373e55ac to your computer and use it in GitHub Desktop.
Flutter CJK underline bug workaround

Flutter CJK underline bug workaround

Created with <3 with dartpad.dev.

// 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