Skip to content

Commit 3150524

Browse files
authored
Merge pull request #11 from EXXETA/bugfix/FTD-5-underlined-text-multiple-lines
[FTD #5] support underline for multiple lines of text
2 parents 350717e + 778d2fc commit 3150524

File tree

13 files changed

+180
-42
lines changed

13 files changed

+180
-42
lines changed

.github/workflows/basic_dart_check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ jobs:
2727
run: flutter analyze
2828

2929
- name: 🧪 Run tests
30-
run: flutter test
30+
run: flutter test

CONTRIBUTING.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ Please make sure that your commit messages follow the follwing templates.
6161

6262
The commit message starts with the ISSUE-ID followed by a COMMITTAG:
6363

64-
{ISSUE-ID} [{COMMITTAG}]: {COMMIT MSG}
64+
[FTD #{ISSUE-ID}] [{COMMITTAG}]: {COMMIT MSG}
6565

66-
[ADD] -> New feature/files/code/folders added <br>
67-
[UPDATE] -> Updated a file e.g. from screenshot test <br>
68-
[FIX] -> Fixed a bug/error <br>
69-
[DELETE] -> Delete a files/code/folders <br>
70-
[REFAC] -> Modified or refactored the files/code/folders / changes from PRs <br>
66+
Commit Tags:
67+
[ADD] -> New feature/files/code/folders added
68+
[UPDATE] -> Updated a file e.g. from screenshot test
69+
[FIX] -> Fixed a bug/error
70+
[DELETE] -> Delete a files/code/folders
71+
[REFAC] -> Modified or refactored the files/code/folders / changes from PRs
7172

72-
**Example** <br>
73-
123 [ADD]: new box style
73+
**Example**
74+
[FTD #123][ADD]: new box style

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ linter:
44
rules:
55
lines_longer_than_80_chars: false
66
public_member_api_docs: false
7+
omit_local_variable_types: false
78

example/lib/screens/examples/underline_example_screen.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ class UnderlineExampleScreen extends StatelessWidget {
3939
const SizedBox(height: 32),
4040
Center(
4141
child: TextDecorator.underlined(
42-
style: UnderlineStyle.curved,
42+
style: UnderlineStyle.horizontal,
4343
text: const Text(
44-
'Franz jagt im komplett verwahrlosten Taxi quer durch Berlin',
44+
'Franz jagt im komplett verwahrlosten Taxiiii quer Franz jagt im kompletr Franz jagt im kompletr Franz jagt im jagt im kompletr Franz jagt im',
4545
style: TextStyle(fontSize: 16),
4646
),
4747
color: Colors.red,

lib/src/modules/box/painter/bubble_box_painter.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class BubbleBoxPainter extends CustomPainter {
5959
final bubbleHeight = availableHeight + padding.vertical;
6060

6161
// Calculate tail size
62-
// !TODO: extract
62+
// TODO(everyone): extract
6363
final tailHeight = bubbleHeight * 0.25;
6464

6565
final path = Path()

lib/src/modules/underline/enums/underline_style.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ enum UnderlineStyle {
2222
switch (this) {
2323
case UnderlineStyle.horizontal:
2424
return HorizontalUnderlinePainter(
25+
text: text.data ?? '',
26+
textStyle: text.style ?? const TextStyle(),
2527
color: color,
2628
strokeWidth: strokeWidth,
2729
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'dart:ui' as ui;
2+
3+
mixin LineGap {
4+
double calculateGapBetweenLines({
5+
required int lineIndex,
6+
required ui.LineMetrics line,
7+
required double strokeWidth,
8+
}) {
9+
return line.height + (line.descent / 2) + strokeWidth;
10+
}
11+
}

lib/src/modules/underline/painter/curved_underline_painter.dart

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import 'dart:ui' as ui;
12
import 'package:flutter/material.dart';
23
import 'package:flutter_text_decorator/src/modules/underline/base/underline_painter.dart';
4+
import 'package:flutter_text_decorator/src/modules/underline/mixins/line_gap_mixin.dart';
35

46
/// A [CustomPainter] that draws a curved or wavy underline beneath text.
57
///
@@ -28,39 +30,58 @@ import 'package:flutter_text_decorator/src/modules/underline/base/underline_pain
2830
/// ),
2931
/// )
3032
/// ```
31-
class CurvedUnderlinePainter extends UnderlinePainter {
32-
CurvedUnderlinePainter({required this.text, required super.color, required super.strokeWidth, super.textStyle});
33+
class CurvedUnderlinePainter extends UnderlinePainter with LineGap {
34+
CurvedUnderlinePainter({
35+
required this.text,
36+
required super.color,
37+
required super.strokeWidth,
38+
this.curvyFactor = 3,
39+
super.textStyle,
40+
});
3341

3442
final String text;
43+
final double curvyFactor;
3544

3645
@override
3746
void paint(Canvas canvas, Size size) {
38-
final textSpan = TextSpan(text: text, style: textStyle);
3947
final textPainter = TextPainter(
40-
text: textSpan,
41-
textDirection: TextDirection.ltr,
42-
)..layout();
48+
text: TextSpan(text: text, style: textStyle ?? const TextStyle()),
49+
textDirection: ui.TextDirection.ltr,
50+
)..layout(maxWidth: size.width);
4351

4452
final paint = Paint()
4553
..color = color
4654
..style = PaintingStyle.stroke
4755
..strokeWidth = strokeWidth;
48-
final path = Path()
49-
..moveTo(5, size.height * 1.1)
50-
..cubicTo(
51-
size.width - textPainter.width / 2,
52-
size.height + 1,
53-
size.width - textPainter.width / 4,
54-
size.height - 7,
55-
size.width,
56-
size.height,
57-
);
5856

59-
canvas.drawPath(path, paint);
57+
final lines = textPainter.computeLineMetrics();
58+
59+
double yOffset = 0;
60+
61+
for (final line in lines) {
62+
final double startX = line.left;
63+
final double xEnd = startX + line.width;
64+
65+
if (line.width <= 0) continue;
66+
67+
yOffset += calculateGapBetweenLines(line: line, lineIndex: line.lineNumber, strokeWidth: strokeWidth);
68+
69+
final double y1 = yOffset - strokeWidth * curvyFactor;
70+
final double y2 = yOffset + strokeWidth * curvyFactor;
71+
final double yEnd = yOffset;
72+
final double x1 = startX + xEnd * (1 / 3);
73+
final double x2 = startX + xEnd * (2 / 3);
74+
final double x3 = xEnd;
75+
final double y3 = yEnd;
76+
77+
final Path path = Path()
78+
..moveTo(startX, yOffset)
79+
..cubicTo(x1, y1, x2, y2, x3, y3);
80+
81+
canvas.drawPath(path, paint);
82+
}
6083
}
6184

6285
@override
63-
bool shouldRepaint(covariant CustomPainter oldDelegate) {
64-
return false;
65-
}
86+
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
6687
}

lib/src/modules/underline/painter/horizontal_underline_painter.dart

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import 'dart:ui' as ui;
2+
13
import 'package:flutter/material.dart';
24
import 'package:flutter_text_decorator/src/modules/underline/base/underline_painter.dart';
35
import 'package:flutter_text_decorator/src/modules/underline/classes/horizontal_offset.dart';
6+
import 'package:flutter_text_decorator/src/modules/underline/mixins/line_gap_mixin.dart';
47

58
/// A [CustomPainter] that draws a straight horizontal underline beneath text.
69
///
@@ -26,24 +29,46 @@ import 'package:flutter_text_decorator/src/modules/underline/classes/horizontal_
2629
/// child: Text("Underlined Text"),
2730
/// )
2831
/// ```
29-
class HorizontalUnderlinePainter extends UnderlinePainter {
30-
HorizontalUnderlinePainter({required super.color, required super.strokeWidth, super.horizontalOffset});
32+
class HorizontalUnderlinePainter extends UnderlinePainter with LineGap {
33+
HorizontalUnderlinePainter({
34+
required this.text,
35+
required super.color,
36+
required super.strokeWidth,
37+
super.textStyle,
38+
super.horizontalOffset,
39+
});
40+
41+
final String text;
3142

43+
@override
3244
@override
3345
void paint(Canvas canvas, Size size) {
3446
final paint = Paint()
3547
..color = color
3648
..style = PaintingStyle.stroke
37-
..strokeWidth = strokeWidth;
38-
final path = Path()
39-
..moveTo(horizontalOffset.left, size.height)
40-
..lineTo(size.width - horizontalOffset.right, size.height);
49+
..strokeWidth = strokeWidth
50+
..strokeCap = StrokeCap.square;
51+
52+
final textPainter = TextPainter(
53+
text: TextSpan(text: text, style: textStyle ?? const TextStyle()),
54+
textDirection: ui.TextDirection.ltr,
55+
)..layout(maxWidth: size.width);
4156

42-
canvas.drawPath(path, paint);
57+
final lines = textPainter.computeLineMetrics();
58+
double yOffset = 0;
59+
60+
for (final line in lines) {
61+
final double startX = line.left + horizontalOffset.left;
62+
final double endX = line.left + line.width - horizontalOffset.right;
63+
final double underlineY = yOffset + line.ascent + line.descent + strokeWidth;
64+
65+
if (startX < endX) {
66+
canvas.drawLine(Offset(startX, underlineY), Offset(endX, underlineY), paint);
67+
}
68+
yOffset += calculateGapBetweenLines(line: line, lineIndex: line.lineNumber, strokeWidth: strokeWidth);
69+
}
4370
}
4471

4572
@override
46-
bool shouldRepaint(covariant CustomPainter oldDelegate) {
47-
return false;
48-
}
73+
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
4974
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:flutter_text_decorator/src/modules/underline/painter/curved_underline_painter.dart'; // Adjust import path as needed
4+
5+
void main() {
6+
group('CurvedUnderlinePainter Tests', () {
7+
testWidgets('renders CustomPaint with CurvedUnderlinePainter for single line text', (WidgetTester tester) async {
8+
const testText = 'Hello Curved Underline';
9+
const testTextStyle = TextStyle(fontSize: 24, color: Colors.black);
10+
const underlineColor = Colors.blue;
11+
const underlineStrokeWidth = 2.0;
12+
13+
await tester.pumpWidget(
14+
MaterialApp(
15+
home: Scaffold(
16+
body: Center(
17+
child: CustomPaint(
18+
painter: CurvedUnderlinePainter(
19+
text: testText,
20+
color: underlineColor,
21+
strokeWidth: underlineStrokeWidth,
22+
textStyle: testTextStyle,
23+
),
24+
child: const Text(testText, style: testTextStyle),
25+
),
26+
),
27+
),
28+
),
29+
);
30+
31+
expect(find.byType(CustomPaint), findsNWidgets(2));
32+
});
33+
});
34+
}

0 commit comments

Comments
 (0)