@@ -314,21 +314,32 @@ class _TextLayout {
314
314
TextBaseline .ideographic => _paragraph.ideographicBaseline,
315
315
};
316
316
}
317
+
318
+ double _contentWidthFor (double minWidth, double maxWidth, TextWidthBasis widthBasis) {
319
+ return switch (widthBasis) {
320
+ TextWidthBasis .longestLine => clampDouble (longestLine, minWidth, maxWidth),
321
+ TextWidthBasis .parent => clampDouble (maxIntrinsicLineExtent, minWidth, maxWidth),
322
+ };
323
+ }
317
324
}
318
325
319
326
// This class stores the current text layout and the corresponding
320
327
// paintOffset/contentWidth, as well as some cached text metrics values that
321
328
// depends on the current text layout, which will be invalidated as soon as the
322
329
// text layout is invalidated.
323
330
class _TextPainterLayoutCacheWithOffset {
324
- _TextPainterLayoutCacheWithOffset (this .layout, this .textAlignment, double minWidth, double maxWidth, TextWidthBasis widthBasis)
325
- : contentWidth = _contentWidthFor (minWidth, maxWidth, widthBasis, layout),
326
- assert (textAlignment >= 0.0 && textAlignment <= 1.0 );
331
+ _TextPainterLayoutCacheWithOffset (this .layout, this .textAlignment, this .layoutMaxWidth, this .contentWidth)
332
+ : assert (textAlignment >= 0.0 && textAlignment <= 1.0 ),
333
+ assert (! layoutMaxWidth.isNaN),
334
+ assert (! contentWidth.isNaN);
327
335
328
336
final _TextLayout layout;
329
337
338
+ // The input width used to lay out the paragraph.
339
+ final double layoutMaxWidth;
340
+
330
341
// The content width the text painter should report in TextPainter.width.
331
- // This is also used to compute `paintOffset`
342
+ // This is also used to compute `paintOffset`.
332
343
double contentWidth;
333
344
334
345
// The effective text alignment in the TextPainter's canvas. The value is
@@ -352,20 +363,14 @@ class _TextPainterLayoutCacheWithOffset {
352
363
353
364
ui.Paragraph get paragraph => layout._paragraph;
354
365
355
- static double _contentWidthFor (double minWidth, double maxWidth, TextWidthBasis widthBasis, _TextLayout layout) {
356
- return switch (widthBasis) {
357
- TextWidthBasis .longestLine => clampDouble (layout.longestLine, minWidth, maxWidth),
358
- TextWidthBasis .parent => clampDouble (layout.maxIntrinsicLineExtent, minWidth, maxWidth),
359
- };
360
- }
361
-
362
366
// Try to resize the contentWidth to fit the new input constraints, by just
363
367
// adjusting the paint offset (so no line-breaking changes needed).
364
368
//
365
- // Returns false if the new constraints require re-computing the line breaks,
366
- // in which case no side effects will occur .
369
+ // Returns false if the new constraints require the text layout library to
370
+ // re-compute the line breaks .
367
371
bool _resizeToFit (double minWidth, double maxWidth, TextWidthBasis widthBasis) {
368
372
assert (layout.maxIntrinsicLineExtent.isFinite);
373
+ assert (minWidth <= maxWidth);
369
374
// The assumption here is that if a Paragraph's width is already >= its
370
375
// maxIntrinsicWidth, further increasing the input width does not change its
371
376
// layout (but may change the paint offset if it's not left-aligned). This is
@@ -377,21 +382,30 @@ class _TextPainterLayoutCacheWithOffset {
377
382
// of double.infinity, and to make the text visible the paintOffset.dx is
378
383
// bound to be double.negativeInfinity, which invalidates all arithmetic
379
384
// operations.
380
- final double newContentWidth = _contentWidthFor (minWidth, maxWidth, widthBasis, layout);
381
- if (newContentWidth == contentWidth) {
385
+
386
+ if (maxWidth == contentWidth && minWidth == contentWidth) {
387
+ contentWidth = layout._contentWidthFor (minWidth, maxWidth, widthBasis);
382
388
return true ;
383
389
}
384
- assert (minWidth <= maxWidth);
385
- // Always needsLayout when the current paintOffset and the paragraph width are not finite.
390
+
391
+ // Special case:
392
+ // When the paint offset and the paragraph width are both +∞, it's likely
393
+ // that the text layout engine skipped layout because there weren't anything
394
+ // to paint. Always try to re-compute the text layout.
386
395
if (! paintOffset.dx.isFinite && ! paragraph.width.isFinite && minWidth.isFinite) {
387
396
assert (paintOffset.dx == double .infinity);
388
397
assert (paragraph.width == double .infinity);
389
398
return false ;
390
399
}
400
+
391
401
final double maxIntrinsicWidth = paragraph.maxIntrinsicWidth;
392
- if ((paragraph.width - maxIntrinsicWidth) > - precisionErrorTolerance && (maxWidth - maxIntrinsicWidth) > - precisionErrorTolerance) {
393
- // Adjust the paintOffset and contentWidth to the new input constraints.
394
- contentWidth = newContentWidth;
402
+ // Skip line breaking if the input width remains the same, of there will be
403
+ // no soft breaks.
404
+ final bool skipLineBreaking = maxWidth == layoutMaxWidth // Same input max width so relayout is unnecessary.
405
+ || ((paragraph.width - maxIntrinsicWidth) > - precisionErrorTolerance && (maxWidth - maxIntrinsicWidth) > - precisionErrorTolerance);
406
+ if (skipLineBreaking) {
407
+ // Adjust the content width in case the TextWidthBasis changed.
408
+ contentWidth = layout._contentWidthFor (minWidth, maxWidth, widthBasis);
395
409
return true ;
396
410
}
397
411
return false ;
@@ -631,10 +645,6 @@ class TextPainter {
631
645
// recreated. The caller may not call `layout` again after text color is
632
646
// updated. See: https://github.com/flutter/flutter/issues/85108
633
647
bool _rebuildParagraphForPaint = true ;
634
- // `_layoutCache`'s input width. This is only needed because there's no API to
635
- // create paint only updates that don't affect the text layout (e.g., changing
636
- // the color of the text), on ui.Paragraph or ui.ParagraphBuilder.
637
- double _inputWidth = double .nan;
638
648
639
649
bool get _debugAssertTextLayoutIsValid {
640
650
assert (! debugDisposed);
@@ -1127,7 +1137,7 @@ class TextPainter {
1127
1137
// infinite paint offset.
1128
1138
final bool adjustMaxWidth = ! maxWidth.isFinite && paintOffsetAlignment != 0 ;
1129
1139
final double ? adjustedMaxWidth = ! adjustMaxWidth ? maxWidth : cachedLayout? .layout.maxIntrinsicLineExtent;
1130
- _inputWidth = adjustedMaxWidth ?? maxWidth;
1140
+ final double layoutMaxWidth = adjustedMaxWidth ?? maxWidth;
1131
1141
1132
1142
// Only rebuild the paragraph when there're layout changes, even when
1133
1143
// `_rebuildParagraphForPaint` is true. It's best to not eagerly rebuild
@@ -1137,18 +1147,21 @@ class TextPainter {
1137
1147
// 2. the user could be measuring the text layout so `paint` will never be
1138
1148
// called.
1139
1149
final ui.Paragraph paragraph = (cachedLayout? .paragraph ?? _createParagraph (text))
1140
- ..layout (ui.ParagraphConstraints (width: _inputWidth));
1141
- final _TextPainterLayoutCacheWithOffset newLayoutCache = _TextPainterLayoutCacheWithOffset (
1142
- _TextLayout ._(paragraph), paintOffsetAlignment, minWidth, maxWidth, textWidthBasis,
1143
- );
1150
+ ..layout (ui.ParagraphConstraints (width: layoutMaxWidth));
1151
+ final _TextLayout layout = _TextLayout ._(paragraph);
1152
+ final double contentWidth = layout._contentWidthFor (minWidth, maxWidth, textWidthBasis);
1153
+
1154
+ final _TextPainterLayoutCacheWithOffset newLayoutCache;
1144
1155
// Call layout again if newLayoutCache had an infinite paint offset.
1145
1156
// This is not as expensive as it seems, line breaking is relatively cheap
1146
1157
// as compared to shaping.
1147
1158
if (adjustedMaxWidth == null && minWidth.isFinite) {
1148
1159
assert (maxWidth.isInfinite);
1149
- final double newInputWidth = newLayoutCache. layout.maxIntrinsicLineExtent;
1160
+ final double newInputWidth = layout.maxIntrinsicLineExtent;
1150
1161
paragraph.layout (ui.ParagraphConstraints (width: newInputWidth));
1151
- _inputWidth = newInputWidth;
1162
+ newLayoutCache = _TextPainterLayoutCacheWithOffset (layout, paintOffsetAlignment, newInputWidth, contentWidth);
1163
+ } else {
1164
+ newLayoutCache = _TextPainterLayoutCacheWithOffset (layout, paintOffsetAlignment, layoutMaxWidth, contentWidth);
1152
1165
}
1153
1166
_layoutCache = newLayoutCache;
1154
1167
}
@@ -1189,8 +1202,8 @@ class TextPainter {
1189
1202
// Unfortunately even if we know that there is only paint changes, there's
1190
1203
// no API to only make those updates so the paragraph has to be recreated
1191
1204
// and re-laid out.
1192
- assert (! _inputWidth .isNaN);
1193
- layoutCache.layout._paragraph = _createParagraph (text! )..layout (ui.ParagraphConstraints (width: _inputWidth ));
1205
+ assert (! layoutCache.layoutMaxWidth .isNaN);
1206
+ layoutCache.layout._paragraph = _createParagraph (text! )..layout (ui.ParagraphConstraints (width: layoutCache.layoutMaxWidth ));
1194
1207
assert (paragraph.width == layoutCache.layout._paragraph.width);
1195
1208
paragraph.dispose ();
1196
1209
assert (debugSize == size);
0 commit comments