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

Commit 1ecfdcb

Browse files
authored
[web] Calculate align offset for each paragraph line (LineMetrics.left) (#14537)
1 parent 105eb66 commit 1ecfdcb

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
@@ -666,6 +666,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking {
666666
double x,
667667
double y,
668668
) {
669+
x += line.left;
669670
final double letterSpacing = style.letterSpacing;
670671
if (letterSpacing == null || letterSpacing == 0.0) {
671672
ctx.fillText(line.text, x, y);
@@ -712,11 +713,10 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking {
712713
}
713714
_applyPaint(paragraph._paint.paintData);
714715

715-
final double x = offset.dx + paragraph._alignOffset;
716716
double y = offset.dy + paragraph.alphabeticBaseline;
717717
final int len = lines.length;
718718
for (int i = 0; i < len; i++) {
719-
_drawTextLine(style, lines[i], x, y);
719+
_drawTextLine(style, lines[i], offset.dx, y);
720720
y += paragraph._lineHeight;
721721
}
722722
_resetPaint();

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
@@ -800,7 +800,9 @@ class ParagraphRuler {
800800
final int len = constraintCache.length;
801801
for (int i = 0; i < len; i++) {
802802
final MeasurementResult item = constraintCache[i];
803-
if (item.constraintWidth == constraints.width) {
803+
if (item.constraintWidth == constraints.width &&
804+
item.textAlign == paragraph._textAlign &&
805+
item.textDirection == paragraph._textDirection) {
804806
return item;
805807
}
806808
}
@@ -852,7 +854,13 @@ class MeasurementResult {
852854
/// of each laid out line.
853855
final List<EngineLineMetrics> lines;
854856

855-
const MeasurementResult(
857+
/// The text align value of the paragraph.
858+
final ui.TextAlign textAlign;
859+
860+
/// The text direction of the paragraph.
861+
final ui.TextDirection textDirection;
862+
863+
MeasurementResult(
856864
this.constraintWidth, {
857865
@required this.isSingleLine,
858866
@required this.width,
@@ -864,6 +872,8 @@ class MeasurementResult {
864872
@required this.alphabeticBaseline,
865873
@required this.ideographicBaseline,
866874
@required this.lines,
875+
@required this.textAlign,
876+
@required this.textDirection,
867877
}) : assert(constraintWidth != null),
868878
assert(isSingleLine != null),
869879
assert(width != null),
@@ -872,5 +882,7 @@ class MeasurementResult {
872882
assert(minIntrinsicWidth != null),
873883
assert(maxIntrinsicWidth != null),
874884
assert(alphabeticBaseline != null),
875-
assert(ideographicBaseline != null);
885+
assert(ideographicBaseline != null),
886+
assert(textAlign != null),
887+
assert(textDirection != null);
876888
}

0 commit comments

Comments
 (0)