@@ -22,60 +22,105 @@ class TextPaintService {
2222 // individually.
2323 final List <EngineLineMetrics > lines = paragraph.computeLineMetrics ();
2424
25+ if (lines.isEmpty) {
26+ return ;
27+ }
28+
29+ final EngineLineMetrics lastLine = lines.last;
2530 for (final EngineLineMetrics line in lines) {
26- for (final RangeBox box in line.boxes! ) {
27- _paintBox (canvas, offset, line, box);
31+ if (line.boxes.isEmpty) {
32+ continue ;
33+ }
34+
35+ final RangeBox lastBox = line.boxes.last;
36+ final double justifyPerSpaceBox =
37+ _calculateJustifyPerSpaceBox (paragraph, line, lastLine, lastBox);
38+
39+ ui.Offset justifiedOffset = offset;
40+
41+ for (final RangeBox box in line.boxes) {
42+ final bool isTrailingSpaceBox =
43+ box == lastBox && box is SpanBox && box.isSpaceOnly;
44+
45+ // Don't paint background for the trailing space in the line.
46+ if (! isTrailingSpaceBox) {
47+ _paintBackground (canvas, justifiedOffset, line, box, justifyPerSpaceBox);
48+ }
49+ _paintText (canvas, justifiedOffset, line, box);
50+
51+ if (box is SpanBox && box.isSpaceOnly && justifyPerSpaceBox != 0.0 ) {
52+ justifiedOffset = justifiedOffset.translate (justifyPerSpaceBox, 0.0 );
53+ }
2854 }
2955 }
3056 }
3157
32- void _paintBox (
58+ void _paintBackground (
3359 BitmapCanvas canvas,
3460 ui.Offset offset,
3561 EngineLineMetrics line,
3662 RangeBox box,
63+ double justifyPerSpaceBox,
3764 ) {
38- // Placeholder spans don't need any painting. Their boxes should remain
39- // empty so that their underlying widgets do their own painting.
4065 if (box is SpanBox ) {
4166 final FlatTextSpan span = box.span;
4267
4368 // Paint the background of the box, if the span has a background.
4469 final SurfacePaint ? background = span.style.background as SurfacePaint ? ;
4570 if (background != null ) {
46- canvas.drawRect (
47- box.toTextBox (line).toRect ().shift (offset),
48- background.paintData,
49- );
71+ ui.Rect rect = box.toTextBox (line).toRect ().shift (offset);
72+ if (box.isSpaceOnly) {
73+ rect = ui.Rect .fromPoints (
74+ rect.topLeft,
75+ rect.bottomRight.translate (justifyPerSpaceBox, 0.0 ),
76+ );
77+ }
78+ canvas.drawRect (rect, background.paintData);
5079 }
80+ }
81+ }
82+
83+ void _paintText (
84+ BitmapCanvas canvas,
85+ ui.Offset offset,
86+ EngineLineMetrics line,
87+ RangeBox box,
88+ ) {
89+ // There's no text to paint in placeholder spans.
90+ if (box is SpanBox ) {
91+ final FlatTextSpan span = box.span;
5192
52- // Paint the actual text.
5393 _applySpanStyleToCanvas (span, canvas);
5494 final double x = offset.dx + line.left + box.left;
5595 final double y = offset.dy + line.baseline;
56- final String text = paragraph.toPlainText ().substring (
57- box.start.index,
58- box.end.indexWithoutTrailingNewlines,
59- );
60- final double ? letterSpacing = span.style.letterSpacing;
61- if (letterSpacing == null || letterSpacing == 0.0 ) {
62- canvas.fillText (text, x, y, shadows: span.style.shadows);
63- } else {
64- // TODO(mdebbar): Implement letter-spacing on canvas more efficiently:
65- // https://github.com/flutter/flutter/issues/51234
66- double charX = x;
67- final int len = text.length;
68- for (int i = 0 ; i < len; i++ ) {
69- final String char = text[i];
70- canvas.fillText (char, charX.roundToDouble (), y,
71- shadows: span.style.shadows);
72- charX += letterSpacing + canvas.measureText (char).width! ;
96+
97+ // Don't paint the text for space-only boxes. This is just an
98+ // optimization, it doesn't have any effect on the output.
99+ if (! box.isSpaceOnly) {
100+ final String text = paragraph.toPlainText ().substring (
101+ box.start.index,
102+ box.end.indexWithoutTrailingNewlines,
103+ );
104+ final double ? letterSpacing = span.style.letterSpacing;
105+ if (letterSpacing == null || letterSpacing == 0.0 ) {
106+ canvas.fillText (text, x, y, shadows: span.style.shadows);
107+ } else {
108+ // TODO(mdebbar): Implement letter-spacing on canvas more efficiently:
109+ // https://github.com/flutter/flutter/issues/51234
110+ double charX = x;
111+ final int len = text.length;
112+ for (int i = 0 ; i < len; i++ ) {
113+ final String char = text[i];
114+ canvas.fillText (char, charX.roundToDouble (), y,
115+ shadows: span.style.shadows);
116+ charX += letterSpacing + canvas.measureText (char).width! ;
117+ }
73118 }
74119 }
75120
76121 // Paint the ellipsis using the same span styles.
77122 final String ? ellipsis = line.ellipsis;
78- if (ellipsis != null && box == line.boxes! .last) {
123+ if (ellipsis != null && box == line.boxes.last) {
79124 final double x = offset.dx + line.left + box.right;
80125 canvas.fillText (ellipsis, x, y);
81126 }
@@ -97,3 +142,31 @@ class TextPaintService {
97142 canvas.setUpPaint (paint.paintData, null );
98143 }
99144}
145+
146+ /// Calculates for the given [line] , the amount of extra width that needs to be
147+ /// added to each space box in order to align the line with the rest of the
148+ /// paragraph.
149+ double _calculateJustifyPerSpaceBox (
150+ CanvasParagraph paragraph,
151+ EngineLineMetrics line,
152+ EngineLineMetrics lastLine,
153+ RangeBox lastBox,
154+ ) {
155+ // Don't apply any justification on the last line.
156+ if (line != lastLine &&
157+ paragraph.width.isFinite &&
158+ paragraph.paragraphStyle.textAlign == ui.TextAlign .justify) {
159+ final double justifyTotal = paragraph.width - line.width;
160+
161+ int spaceBoxesToJustify = line.spaceBoxCount;
162+ // If the last box is a space box, we can't use it to justify text.
163+ if (lastBox is SpanBox && lastBox.isSpaceOnly) {
164+ spaceBoxesToJustify-- ;
165+ }
166+ if (spaceBoxesToJustify > 0 ) {
167+ return justifyTotal / spaceBoxesToJustify;
168+ }
169+ }
170+
171+ return 0.0 ;
172+ }
0 commit comments