Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 91a37d8

Browse files
authored
Reland "[web] Calculate align offset for each paragraph line (LineMetrics.left)" (#14537) (#15151)
1 parent bb99d80 commit 91a37d8

File tree

5 files changed

+291
-107
lines changed

5 files changed

+291
-107
lines changed

lib/web_ui/lib/src/engine/bitmap_canvas.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ class BitmapCanvas extends EngineCanvas {
437437
double y,
438438
) {
439439
html.CanvasRenderingContext2D ctx = _canvasPool.context;
440+
x += line.left;
440441
final double letterSpacing = style.letterSpacing;
441442
if (letterSpacing == null || letterSpacing == 0.0) {
442443
ctx.fillText(line.text, x, y);
@@ -482,11 +483,10 @@ class BitmapCanvas extends EngineCanvas {
482483
}
483484
_applyPaint(paragraph._paint.paintData);
484485

485-
final double x = offset.dx + paragraph._alignOffset;
486486
double y = offset.dy + paragraph.alphabeticBaseline;
487487
final int len = lines.length;
488488
for (int i = 0; i < len; i++) {
489-
_drawTextLine(style, lines[i], x, y);
489+
_drawTextLine(style, lines[i], offset.dx, y);
490490
y += paragraph._lineHeight;
491491
}
492492
return;

lib/web_ui/lib/src/engine/text/measurement.dart

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,19 @@ class DomTextMeasurementService extends TextMeasurementService {
416416
List<EngineLineMetrics> lines;
417417
if (text != null) {
418418
final double lineWidth = maxIntrinsicWidth;
419+
final double alignOffset = _calculateAlignOffsetForLine(
420+
paragraph: paragraph,
421+
lineWidth: lineWidth,
422+
maxWidth: width,
423+
);
419424
lines = <EngineLineMetrics>[
420425
EngineLineMetrics.withText(
421426
text,
422427
startIndex: 0,
423428
endIndex: text.length,
424429
hardBreak: true,
425430
width: lineWidth,
431+
left: alignOffset,
426432
lineNumber: 0,
427433
),
428434
];
@@ -440,6 +446,8 @@ class DomTextMeasurementService extends TextMeasurementService {
440446
alphabeticBaseline: alphabeticBaseline,
441447
ideographicBaseline: ideographicBaseline,
442448
lines: lines,
449+
textAlign: paragraph._textAlign,
450+
textDirection: paragraph._textDirection,
443451
);
444452
}
445453

@@ -488,6 +496,8 @@ class DomTextMeasurementService extends TextMeasurementService {
488496
alphabeticBaseline: alphabeticBaseline,
489497
ideographicBaseline: ideographicBaseline,
490498
lines: null,
499+
textAlign: paragraph._textAlign,
500+
textDirection: paragraph._textDirection,
491501
);
492502
}
493503

@@ -546,7 +556,7 @@ class CanvasTextMeasurementService extends TextMeasurementService {
546556
// TODO(mdebbar): Check if the whole text can fit in a single-line. Then avoid all this ceremony.
547557
_canvasContext.font = style.cssFontString;
548558
final LinesCalculator linesCalculator =
549-
LinesCalculator(_canvasContext, text, style, constraints.width);
559+
LinesCalculator(_canvasContext, paragraph, constraints.width);
550560
final MinIntrinsicCalculator minIntrinsicCalculator =
551561
MinIntrinsicCalculator(_canvasContext, text, style);
552562
final MaxIntrinsicCalculator maxIntrinsicCalculator =
@@ -597,6 +607,8 @@ class CanvasTextMeasurementService extends TextMeasurementService {
597607
maxIntrinsicWidth: maxIntrinsicCalculator.value,
598608
width: constraints.width,
599609
lines: linesCalculator.lines,
610+
textAlign: paragraph._textAlign,
611+
textDirection: paragraph._textDirection,
600612
);
601613
return result;
602614
}
@@ -702,16 +714,18 @@ int _excludeTrailing(String text, int start, int end, CharPredicate predicate) {
702714
/// During the text layout phase, this class splits the lines of text so that it
703715
/// ends up fitting into the given width constraint.
704716
///
705-
/// It mimicks the Flutter engine's behavior when it comes to handling ellipsis
706-
/// and max lines.
717+
/// It implements the Flutter engine's behavior when it comes to handling
718+
/// ellipsis and max lines.
707719
class LinesCalculator {
708-
LinesCalculator(this._canvasContext, this._text, this._style, this._maxWidth);
720+
LinesCalculator(this._canvasContext, this._paragraph, this._maxWidth);
709721

710722
final html.CanvasRenderingContext2D _canvasContext;
711-
final String _text;
712-
final ParagraphGeometricStyle _style;
723+
final EngineParagraph _paragraph;
713724
final double _maxWidth;
714725

726+
String get _text => _paragraph._plainText;
727+
ParagraphGeometricStyle get _style => _paragraph._geometricStyle;
728+
715729
/// The lines that have been consumed so far.
716730
List<EngineLineMetrics> lines = <EngineLineMetrics>[];
717731

@@ -768,12 +782,20 @@ class LinesCalculator {
768782
start: _lineStart,
769783
end: chunkEndWithoutSpace,
770784
);
785+
final double widthOfResultingLine =
786+
measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth;
787+
final double alignOffset = _calculateAlignOffsetForLine(
788+
paragraph: _paragraph,
789+
lineWidth: widthOfResultingLine,
790+
maxWidth: _maxWidth,
791+
);
771792
lines.add(EngineLineMetrics.withText(
772793
_text.substring(_lineStart, breakingPoint) + _style.ellipsis,
773794
startIndex: _lineStart,
774795
endIndex: chunkEnd,
775796
hardBreak: false,
776-
width: measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth,
797+
width: widthOfResultingLine,
798+
left: alignOffset,
777799
lineNumber: lines.length,
778800
));
779801
} else if (isChunkTooLong) {
@@ -826,12 +848,19 @@ class LinesCalculator {
826848
_whitespacePredicate,
827849
);
828850
final int lineNumber = lines.length;
851+
final double lineWidth = measureSubstring(_lineStart, endWithoutSpace);
852+
final double alignOffset = _calculateAlignOffsetForLine(
853+
paragraph: _paragraph,
854+
lineWidth: lineWidth,
855+
maxWidth: _maxWidth,
856+
);
829857
final EngineLineMetrics metrics = EngineLineMetrics.withText(
830858
_text.substring(_lineStart, endWithoutNewlines),
831859
startIndex: _lineStart,
832860
endIndex: lineEnd,
833861
hardBreak: isHardBreak,
834-
width: measureSubstring(_lineStart, endWithoutSpace),
862+
width: lineWidth,
863+
left: alignOffset,
835864
lineNumber: lineNumber,
836865
);
837866
lines.add(metrics);
@@ -958,3 +987,30 @@ class MaxIntrinsicCalculator {
958987
_lastHardLineEnd = hardLineEnd;
959988
}
960989
}
990+
991+
/// Calculates the offset necessary for the given line to be correctly aligned.
992+
double _calculateAlignOffsetForLine({
993+
@required EngineParagraph paragraph,
994+
@required double lineWidth,
995+
@required double maxWidth,
996+
}) {
997+
final double emptySpace = maxWidth - lineWidth;
998+
// WARNING: the [paragraph] may not be laid out yet at this point. This
999+
// function must not use layout metrics, such as [paragraph.height].
1000+
switch (paragraph._textAlign) {
1001+
case ui.TextAlign.center:
1002+
return emptySpace / 2.0;
1003+
case ui.TextAlign.right:
1004+
return emptySpace;
1005+
case ui.TextAlign.start:
1006+
return paragraph._textDirection == ui.TextDirection.rtl
1007+
? emptySpace
1008+
: 0.0;
1009+
case ui.TextAlign.end:
1010+
return paragraph._textDirection == ui.TextDirection.rtl
1011+
? 0.0
1012+
: emptySpace;
1013+
default:
1014+
return 0.0;
1015+
}
1016+
}

lib/web_ui/lib/src/engine/text/paragraph.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ class EngineLineMetrics implements ui.LineMetrics {
2929
this.unscaledAscent,
3030
this.height,
3131
@required this.width,
32-
this.left,
32+
@required this.left,
3333
this.baseline,
3434
@required this.lineNumber,
3535
}) : assert(text != null),
3636
assert(hardBreak != null),
3737
assert(width != null),
38+
assert(left != null),
3839
assert(lineNumber != null && lineNumber >= 0);
3940

4041
/// The textual content representing this line.

lib/web_ui/lib/src/engine/text/ruler.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,9 @@ class ParagraphRuler {
815815
final int len = constraintCache.length;
816816
for (int i = 0; i < len; i++) {
817817
final MeasurementResult item = constraintCache[i];
818-
if (item.constraintWidth == constraints.width) {
818+
if (item.constraintWidth == constraints.width &&
819+
item.textAlign == paragraph._textAlign &&
820+
item.textDirection == paragraph._textDirection) {
819821
return item;
820822
}
821823
}
@@ -867,7 +869,13 @@ class MeasurementResult {
867869
/// of each laid out line.
868870
final List<EngineLineMetrics> lines;
869871

870-
const MeasurementResult(
872+
/// The text align value of the paragraph.
873+
final ui.TextAlign textAlign;
874+
875+
/// The text direction of the paragraph.
876+
final ui.TextDirection textDirection;
877+
878+
MeasurementResult(
871879
this.constraintWidth, {
872880
@required this.isSingleLine,
873881
@required this.width,
@@ -879,6 +887,8 @@ class MeasurementResult {
879887
@required this.alphabeticBaseline,
880888
@required this.ideographicBaseline,
881889
@required this.lines,
890+
@required textAlign,
891+
@required textDirection,
882892
}) : assert(constraintWidth != null),
883893
assert(isSingleLine != null),
884894
assert(width != null),
@@ -887,5 +897,7 @@ class MeasurementResult {
887897
assert(minIntrinsicWidth != null),
888898
assert(maxIntrinsicWidth != null),
889899
assert(alphabeticBaseline != null),
890-
assert(ideographicBaseline != null);
900+
assert(ideographicBaseline != null),
901+
this.textAlign = textAlign ?? ui.TextAlign.start,
902+
this.textDirection = textDirection ?? ui.TextDirection.ltr;
891903
}

0 commit comments

Comments
 (0)