diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index e4dde41d591ca..e06d0abc87ac3 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -666,6 +666,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { double x, double y, ) { + x += line.left; final double letterSpacing = style.letterSpacing; if (letterSpacing == null || letterSpacing == 0.0) { ctx.fillText(line.text, x, y); @@ -712,11 +713,10 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } _applyPaint(paragraph._paint.paintData); - final double x = offset.dx + paragraph._alignOffset; double y = offset.dy + paragraph.alphabeticBaseline; final int len = lines.length; for (int i = 0; i < len; i++) { - _drawTextLine(style, lines[i], x, y); + _drawTextLine(style, lines[i], offset.dx, y); y += paragraph._lineHeight; } _resetPaint(); diff --git a/lib/web_ui/lib/src/engine/text/measurement.dart b/lib/web_ui/lib/src/engine/text/measurement.dart index ccd05f5eab336..4ea64aa20d433 100644 --- a/lib/web_ui/lib/src/engine/text/measurement.dart +++ b/lib/web_ui/lib/src/engine/text/measurement.dart @@ -416,6 +416,11 @@ class DomTextMeasurementService extends TextMeasurementService { List lines; if (text != null) { final double lineWidth = maxIntrinsicWidth; + final double alignOffset = _calculateAlignOffsetForLine( + paragraph: paragraph, + lineWidth: lineWidth, + maxWidth: width, + ); lines = [ EngineLineMetrics.withText( text, @@ -423,6 +428,7 @@ class DomTextMeasurementService extends TextMeasurementService { endIndex: text.length, hardBreak: true, width: lineWidth, + left: alignOffset, lineNumber: 0, ), ]; @@ -440,6 +446,8 @@ class DomTextMeasurementService extends TextMeasurementService { alphabeticBaseline: alphabeticBaseline, ideographicBaseline: ideographicBaseline, lines: lines, + textAlign: paragraph._textAlign, + textDirection: paragraph._textDirection, ); } @@ -488,6 +496,8 @@ class DomTextMeasurementService extends TextMeasurementService { alphabeticBaseline: alphabeticBaseline, ideographicBaseline: ideographicBaseline, lines: null, + textAlign: paragraph._textAlign, + textDirection: paragraph._textDirection, ); } @@ -546,7 +556,7 @@ class CanvasTextMeasurementService extends TextMeasurementService { // TODO(mdebbar): Check if the whole text can fit in a single-line. Then avoid all this ceremony. _canvasContext.font = style.cssFontString; final LinesCalculator linesCalculator = - LinesCalculator(_canvasContext, text, style, constraints.width); + LinesCalculator(_canvasContext, paragraph, constraints.width); final MinIntrinsicCalculator minIntrinsicCalculator = MinIntrinsicCalculator(_canvasContext, text, style); final MaxIntrinsicCalculator maxIntrinsicCalculator = @@ -597,6 +607,8 @@ class CanvasTextMeasurementService extends TextMeasurementService { maxIntrinsicWidth: maxIntrinsicCalculator.value, width: constraints.width, lines: linesCalculator.lines, + textAlign: paragraph._textAlign, + textDirection: paragraph._textDirection, ); return result; } @@ -702,16 +714,18 @@ int _excludeTrailing(String text, int start, int end, CharPredicate predicate) { /// During the text layout phase, this class splits the lines of text so that it /// ends up fitting into the given width constraint. /// -/// It mimicks the Flutter engine's behavior when it comes to handling ellipsis -/// and max lines. +/// It implements the Flutter engine's behavior when it comes to handling +/// ellipsis and max lines. class LinesCalculator { - LinesCalculator(this._canvasContext, this._text, this._style, this._maxWidth); + LinesCalculator(this._canvasContext, this._paragraph, this._maxWidth); final html.CanvasRenderingContext2D _canvasContext; - final String _text; - final ParagraphGeometricStyle _style; + final EngineParagraph _paragraph; final double _maxWidth; + String get _text => _paragraph._plainText; + ParagraphGeometricStyle get _style => _paragraph._geometricStyle; + /// The lines that have been consumed so far. List lines = []; @@ -768,12 +782,20 @@ class LinesCalculator { start: _lineStart, end: chunkEndWithoutSpace, ); + final double widthOfResultingLine = + measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth; + final double alignOffset = _calculateAlignOffsetForLine( + paragraph: _paragraph, + lineWidth: widthOfResultingLine, + maxWidth: _maxWidth, + ); lines.add(EngineLineMetrics.withText( _text.substring(_lineStart, breakingPoint) + _style.ellipsis, startIndex: _lineStart, endIndex: chunkEnd, hardBreak: false, - width: measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth, + width: widthOfResultingLine, + left: alignOffset, lineNumber: lines.length, )); } else if (isChunkTooLong) { @@ -826,12 +848,19 @@ class LinesCalculator { _whitespacePredicate, ); final int lineNumber = lines.length; + final double lineWidth = measureSubstring(_lineStart, endWithoutSpace); + final double alignOffset = _calculateAlignOffsetForLine( + paragraph: _paragraph, + lineWidth: lineWidth, + maxWidth: _maxWidth, + ); final EngineLineMetrics metrics = EngineLineMetrics.withText( _text.substring(_lineStart, endWithoutNewlines), startIndex: _lineStart, endIndex: lineEnd, hardBreak: isHardBreak, - width: measureSubstring(_lineStart, endWithoutSpace), + width: lineWidth, + left: alignOffset, lineNumber: lineNumber, ); lines.add(metrics); @@ -958,3 +987,30 @@ class MaxIntrinsicCalculator { _lastHardLineEnd = hardLineEnd; } } + +/// Calculates the offset necessary for the given line to be correctly aligned. +double _calculateAlignOffsetForLine({ + @required EngineParagraph paragraph, + @required double lineWidth, + @required double maxWidth, +}) { + final double emptySpace = maxWidth - lineWidth; + // WARNING: the [paragraph] may not be laid out yet at this point. This + // function must not use layout metrics, such as [paragraph.height]. + switch (paragraph._textAlign) { + case ui.TextAlign.center: + return emptySpace / 2.0; + case ui.TextAlign.right: + return emptySpace; + case ui.TextAlign.start: + return paragraph._textDirection == ui.TextDirection.rtl + ? emptySpace + : 0.0; + case ui.TextAlign.end: + return paragraph._textDirection == ui.TextDirection.rtl + ? 0.0 + : emptySpace; + default: + return 0.0; + } +} diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index f9ea3e2140b6b..32d7574ea1142 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -29,12 +29,13 @@ class EngineLineMetrics implements ui.LineMetrics { this.unscaledAscent, this.height, @required this.width, - this.left, + @required this.left, this.baseline, @required this.lineNumber, }) : assert(text != null), assert(hardBreak != null), assert(width != null), + assert(left != null), assert(lineNumber != null && lineNumber >= 0); /// The textual content representing this line. diff --git a/lib/web_ui/lib/src/engine/text/ruler.dart b/lib/web_ui/lib/src/engine/text/ruler.dart index 7ea7cc4973dc7..43d30d408d1e8 100644 --- a/lib/web_ui/lib/src/engine/text/ruler.dart +++ b/lib/web_ui/lib/src/engine/text/ruler.dart @@ -800,7 +800,9 @@ class ParagraphRuler { final int len = constraintCache.length; for (int i = 0; i < len; i++) { final MeasurementResult item = constraintCache[i]; - if (item.constraintWidth == constraints.width) { + if (item.constraintWidth == constraints.width && + item.textAlign == paragraph._textAlign && + item.textDirection == paragraph._textDirection) { return item; } } @@ -852,7 +854,13 @@ class MeasurementResult { /// of each laid out line. final List lines; - const MeasurementResult( + /// The text align value of the paragraph. + final ui.TextAlign textAlign; + + /// The text direction of the paragraph. + final ui.TextDirection textDirection; + + MeasurementResult( this.constraintWidth, { @required this.isSingleLine, @required this.width, @@ -864,6 +872,8 @@ class MeasurementResult { @required this.alphabeticBaseline, @required this.ideographicBaseline, @required this.lines, + @required this.textAlign, + @required this.textDirection, }) : assert(constraintWidth != null), assert(isSingleLine != null), assert(width != null), @@ -872,5 +882,7 @@ class MeasurementResult { assert(minIntrinsicWidth != null), assert(maxIntrinsicWidth != null), assert(alphabeticBaseline != null), - assert(ideographicBaseline != null); + assert(ideographicBaseline != null), + assert(textAlign != null), + assert(textDirection != null); } diff --git a/lib/web_ui/test/text/measurement_test.dart b/lib/web_ui/test/text/measurement_test.dart index 081e899a66c78..10a31bcef15a6 100644 --- a/lib/web_ui/test/text/measurement_test.dart +++ b/lib/web_ui/test/text/measurement_test.dart @@ -156,7 +156,7 @@ void main() async { expect(result.minIntrinsicWidth, 30); expect(result.height, 10); expect(result.lines, [ - line(' abc', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0), + line(' abc', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0, left: 0.0), ]); // trailing whitespaces @@ -167,13 +167,13 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 6, hardBreak: true, width: 30.0, lineNumber: 0), + line('abc ', 0, 6, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement always includes trailing whitespace in the // width, while Flutter and Canvas-based measurement don't. expect(result.lines, [ - line('abc ', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0), + line('abc ', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0, left: 0.0), ]); } @@ -185,13 +185,13 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line(' ab c ', 0, 10, hardBreak: true, width: 80.0, lineNumber: 0), + line(' ab c ', 0, 10, hardBreak: true, width: 80.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement always includes trailing whitespace in the // width, while Flutter and Canvas-based measurement don't. expect(result.lines, [ - line(' ab c ', 0, 10, hardBreak: true, width: 100.0, lineNumber: 0), + line(' ab c ', 0, 10, hardBreak: true, width: 100.0, lineNumber: 0, left: 0.0), ]); } @@ -203,13 +203,13 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line(' ', 0, 1, hardBreak: true, width: 0.0, lineNumber: 0), + line(' ', 0, 1, hardBreak: true, width: 0.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement always includes trailing whitespace in the // width, while Flutter and Canvas-based measurement don't. expect(result.lines, [ - line(' ', 0, 1, hardBreak: true, width: 10.0, lineNumber: 0), + line(' ', 0, 1, hardBreak: true, width: 10.0, lineNumber: 0, left: 0.0), ]); } @@ -221,13 +221,13 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line(' ', 0, 5, hardBreak: true, width: 0.0, lineNumber: 0), + line(' ', 0, 5, hardBreak: true, width: 0.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement always includes trailing whitespace in the // width, while Flutter and Canvas-based measurement don't. expect(result.lines, [ - line(' ', 0, 5, hardBreak: true, width: 50.0, lineNumber: 0), + line(' ', 0, 5, hardBreak: true, width: 50.0, lineNumber: 0, left: 0.0), ]); } }, @@ -246,7 +246,7 @@ void main() async { expect(result.width, 50); expect(result.height, 10); expect(result.lines, [ - line('12345', 0, 5, hardBreak: true, width: 50.0, lineNumber: 0), + line('12345', 0, 5, hardBreak: true, width: 50.0, lineNumber: 0, left: 0.0), ]); }, ); @@ -267,8 +267,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('foo bar ', 0, 8, hardBreak: false, width: 70.0, lineNumber: 0), - line('baz', 8, 11, hardBreak: true, width: 30.0, lineNumber: 1), + line('foo bar ', 0, 8, hardBreak: false, width: 70.0, lineNumber: 0, left: 0.0), + line('baz', 8, 11, hardBreak: true, width: 30.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -292,8 +292,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('12345', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0), - line('67890', 5, 10, hardBreak: true, width: 50.0, lineNumber: 1), + line('12345', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('67890', 5, 10, hardBreak: true, width: 50.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -311,9 +311,9 @@ void main() async { expect(result.height, 30); if (instance.isCanvas) { expect(result.lines, [ - line('abcde', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0), - line('fghij', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1), - line('k lm', 10, 14, hardBreak: true, width: 40.0, lineNumber: 2), + line('abcde', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('fghij', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('k lm', 10, 14, hardBreak: true, width: 40.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -333,8 +333,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0), - line('A', 1, 2, hardBreak: true, width: 10.0, lineNumber: 1), + line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0, left: 0.0), + line('A', 1, 2, hardBreak: true, width: 10.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -351,9 +351,9 @@ void main() async { expect(result.height, 30); if (instance.isCanvas) { expect(result.lines, [ - line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0), - line('A', 1, 3, hardBreak: true, width: 10.0, lineNumber: 1), - line('A', 3, 4, hardBreak: true, width: 10.0, lineNumber: 2), + line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0, left: 0.0), + line('A', 1, 3, hardBreak: true, width: 10.0, lineNumber: 1, left: 0.0), + line('A', 3, 4, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -370,10 +370,10 @@ void main() async { expect(result.height, 40); if (instance.isCanvas) { expect(result.lines, [ - line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0), - line('A', 1, 2, hardBreak: false, width: 10.0, lineNumber: 1), - line('A', 2, 4, hardBreak: true, width: 10.0, lineNumber: 2), - line('', 4, 4, hardBreak: true, width: 0.0, lineNumber: 3), + line('A', 0, 1, hardBreak: false, width: 10.0, lineNumber: 0, left: 0.0), + line('A', 1, 2, hardBreak: false, width: 10.0, lineNumber: 1, left: 0.0), + line('A', 2, 4, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), + line('', 4, 4, hardBreak: true, width: 0.0, lineNumber: 3, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -397,8 +397,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('12', 0, 3, hardBreak: true, width: 20.0, lineNumber: 0), - line('34', 3, 5, hardBreak: true, width: 20.0, lineNumber: 1), + line('12', 0, 3, hardBreak: true, width: 20.0, lineNumber: 0, left: 0.0), + line('34', 3, 5, hardBreak: true, width: 20.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -418,9 +418,9 @@ void main() async { expect(result.height, 30); if (instance.isCanvas) { expect(result.lines, [ - line('', 0, 1, hardBreak: true, width: 0.0, lineNumber: 0), - line('', 1, 2, hardBreak: true, width: 0.0, lineNumber: 1), - line('1234', 2, 6, hardBreak: true, width: 40.0, lineNumber: 2), + line('', 0, 1, hardBreak: true, width: 0.0, lineNumber: 0, left: 0.0), + line('', 1, 2, hardBreak: true, width: 0.0, lineNumber: 1, left: 0.0), + line('1234', 2, 6, hardBreak: true, width: 40.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -435,9 +435,9 @@ void main() async { expect(result.height, 30); if (instance.isCanvas) { expect(result.lines, [ - line('12', 0, 3, hardBreak: true, width: 20.0, lineNumber: 0), - line('', 3, 4, hardBreak: true, width: 0.0, lineNumber: 1), - line('345', 4, 7, hardBreak: true, width: 30.0, lineNumber: 2), + line('12', 0, 3, hardBreak: true, width: 20.0, lineNumber: 0, left: 0.0), + line('', 3, 4, hardBreak: true, width: 0.0, lineNumber: 1, left: 0.0), + line('345', 4, 7, hardBreak: true, width: 30.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -453,9 +453,9 @@ void main() async { // This can only be done correctly in the canvas-based implementation. expect(result.height, 30); expect(result.lines, [ - line('1234', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0), - line('', 5, 6, hardBreak: true, width: 0.0, lineNumber: 1), - line('', 6, 6, hardBreak: true, width: 0.0, lineNumber: 2), + line('1234', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0, left: 0.0), + line('', 5, 6, hardBreak: true, width: 0.0, lineNumber: 1, left: 0.0), + line('', 6, 6, hardBreak: true, width: 0.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -479,8 +479,8 @@ void main() async { if (instance.isCanvas) { expect(result.lines, [ - line('123', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0), - line('456 789', 4, 11, hardBreak: true, width: 70.0, lineNumber: 1), + line('123', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('456 789', 4, 11, hardBreak: true, width: 70.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -544,9 +544,9 @@ void main() async { expect(result.minIntrinsicWidth, 40); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), - line('de ', 4, 7, hardBreak: false, width: 20.0, lineNumber: 1), - line('fghi', 7, 11, hardBreak: true, width: 40.0, lineNumber: 2), + line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), + line('de ', 4, 7, hardBreak: false, width: 20.0, lineNumber: 1, left: 0.0), + line('fghi', 7, 11, hardBreak: true, width: 40.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -559,9 +559,9 @@ void main() async { expect(result.minIntrinsicWidth, 40); if (instance.isCanvas) { expect(result.lines, [ - line('abcd', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0), - line('ef', 5, 8, hardBreak: true, width: 20.0, lineNumber: 1), - line('ghi', 8, 11, hardBreak: true, width: 30.0, lineNumber: 2), + line('abcd', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0, left: 0.0), + line('ef', 5, 8, hardBreak: true, width: 20.0, lineNumber: 1, left: 0.0), + line('ghi', 8, 11, hardBreak: true, width: 30.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -574,8 +574,8 @@ void main() async { expect(result.minIntrinsicWidth, 40); if (instance.isCanvas) { expect(result.lines, [ - line('abcd ', 0, 10, hardBreak: false, width: 40.0, lineNumber: 0), - line('efg', 10, 13, hardBreak: true, width: 30.0, lineNumber: 1), + line('abcd ', 0, 10, hardBreak: false, width: 40.0, lineNumber: 0, left: 0.0), + line('efg', 10, 13, hardBreak: true, width: 30.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -588,8 +588,8 @@ void main() async { expect(result.minIntrinsicWidth, 40); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 8, hardBreak: true, width: 30.0, lineNumber: 0), - line('defg', 8, 12, hardBreak: true, width: 40.0, lineNumber: 1), + line('abc ', 0, 8, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('defg', 8, 12, hardBreak: true, width: 40.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -602,9 +602,9 @@ void main() async { expect(result.minIntrinsicWidth, 120); if (instance.isCanvas) { expect(result.lines, [ - line('AAAAA', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0), - line('AAAAA', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1), - line('AA', 10, 12, hardBreak: true, width: 20.0, lineNumber: 2), + line('AAAAA', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('AAAAA', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('AA', 10, 12, hardBreak: true, width: 20.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -621,9 +621,9 @@ void main() async { expect(result.maxIntrinsicWidth, 110); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), - line('de ', 4, 7, hardBreak: false, width: 20.0, lineNumber: 1), - line('fghi', 7, 11, hardBreak: true, width: 40.0, lineNumber: 2), + line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), + line('de ', 4, 7, hardBreak: false, width: 20.0, lineNumber: 1, left: 0.0), + line('fghi', 7, 11, hardBreak: true, width: 40.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -636,9 +636,9 @@ void main() async { expect(result.maxIntrinsicWidth, 40); if (instance.isCanvas) { expect(result.lines, [ - line('abcd', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0), - line('ef', 5, 8, hardBreak: true, width: 20.0, lineNumber: 1), - line('ghi', 8, 11, hardBreak: true, width: 30.0, lineNumber: 2), + line('abcd', 0, 5, hardBreak: true, width: 40.0, lineNumber: 0, left: 0.0), + line('ef', 5, 8, hardBreak: true, width: 20.0, lineNumber: 1, left: 0.0), + line('ghi', 8, 11, hardBreak: true, width: 30.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -651,8 +651,8 @@ void main() async { expect(result.maxIntrinsicWidth, 100); if (instance.isCanvas) { expect(result.lines, [ - line('abcd ', 0, 7, hardBreak: false, width: 40.0, lineNumber: 0), - line('efg', 7, 10, hardBreak: true, width: 30.0, lineNumber: 1), + line('abcd ', 0, 7, hardBreak: false, width: 40.0, lineNumber: 0, left: 0.0), + line('efg', 7, 10, hardBreak: true, width: 30.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -665,8 +665,8 @@ void main() async { expect(result.maxIntrinsicWidth, 100); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), - line('def ', 4, 10, hardBreak: true, width: 30.0, lineNumber: 1), + line('abc ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), + line('def ', 4, 10, hardBreak: true, width: 30.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -679,8 +679,8 @@ void main() async { expect(result.maxIntrinsicWidth, 60); if (instance.isCanvas) { expect(result.lines, [ - line('abc ', 0, 5, hardBreak: true, width: 30.0, lineNumber: 0), - line('def ', 5, 11, hardBreak: true, width: 30.0, lineNumber: 1), + line('abc ', 0, 5, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('def ', 5, 11, hardBreak: true, width: 30.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -693,9 +693,9 @@ void main() async { expect(result.maxIntrinsicWidth, 120); if (instance.isCanvas) { expect(result.lines, [ - line('AAAAA', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0), - line('AAAAA', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1), - line('AA', 10, 12, hardBreak: true, width: 20.0, lineNumber: 2), + line('AAAAA', 0, 5, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('AAAAA', 5, 10, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('AA', 10, 12, hardBreak: true, width: 20.0, lineNumber: 2, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -727,7 +727,7 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line('AA...', 0, 48, hardBreak: false, width: 50.0, lineNumber: 0), + line('AA...', 0, 48, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -747,8 +747,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('AAA', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0), - line('AA...', 4, 49, hardBreak: false, width: 50.0, lineNumber: 1), + line('AAA', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('AA...', 4, 49, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -766,7 +766,7 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line('...', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), + line('...', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -784,10 +784,10 @@ void main() async { if (instance.isCanvas) { // TODO(flutter_web): https://github.com/flutter/flutter/issues/34346 // expect(result.lines, [ - // line('.', hardBreak: false, width: 10.0, lineNumber: 0), + // line('.', 0, 4, hardBreak: false, width: 10.0, lineNumber: 0, left: 0.0), // ]); expect(result.lines, [ - line('...', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), + line('...', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -811,7 +811,7 @@ void main() async { result = instance.measure(oneline, infiniteConstraints); expect(result.height, 10); expect(result.lines, [ - line('One line', 0, 8, hardBreak: true, width: 80.0, lineNumber: 0), + line('One line', 0, 8, hardBreak: true, width: 80.0, lineNumber: 0, left: 0.0), ]); // The height should respect max lines and be limited to two lines here. @@ -821,8 +821,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('First', 0, 6, hardBreak: true, width: 50.0, lineNumber: 0), - line('Second', 6, 13, hardBreak: true, width: 60.0, lineNumber: 1), + line('First', 0, 6, hardBreak: true, width: 50.0, lineNumber: 0, left: 0.0), + line('Second', 6, 13, hardBreak: true, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -839,8 +839,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('Lorem ', 0, 6, hardBreak: false, width: 50.0, lineNumber: 0), - line('ipsum ', 6, 12, hardBreak: false, width: 50.0, lineNumber: 1), + line('Lorem ', 0, 6, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('ipsum ', 6, 12, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -857,8 +857,8 @@ void main() async { expect(result.height, 20); if (instance.isCanvas) { expect(result.lines, [ - line('AAA ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0), - line('AAAAA', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1), + line('AAA ', 0, 4, hardBreak: false, width: 30.0, lineNumber: 0, left: 0.0), + line('AAAAA', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -893,7 +893,7 @@ void main() async { result = instance.measure(p, constraints); expect(result.height, 10); expect(result.lines, [ - line('abcdef', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0), + line('abcdef', 0, 6, hardBreak: true, width: 60.0, lineNumber: 0, left: 0.0), ]); // Simple overflow case. @@ -902,7 +902,7 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line('abc...', 0, 8, hardBreak: false, width: 60.0, lineNumber: 0), + line('abc...', 0, 8, hardBreak: false, width: 60.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -916,7 +916,7 @@ void main() async { expect(result.height, 10); if (instance.isCanvas) { expect(result.lines, [ - line('a b...', 0, 10, hardBreak: false, width: 60.0, lineNumber: 0), + line('a b...', 0, 10, hardBreak: false, width: 60.0, lineNumber: 0, left: 0.0), ]); } else { // DOM-based measurement can't handle the ellipsis case very well. The @@ -933,8 +933,8 @@ void main() async { expect(result.height, 20); expect(result.lines, [ - line('abcdef ', 0, 7, hardBreak: false, width: 60.0, lineNumber: 0), - line('ghijkl', 7, 13, hardBreak: true, width: 60.0, lineNumber: 1), + line('abcdef ', 0, 7, hardBreak: false, width: 60.0, lineNumber: 0, left: 0.0), + line('ghijkl', 7, 13, hardBreak: true, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -950,8 +950,8 @@ void main() async { expect(result.height, 20); expect(result.lines, [ - line('abcd ', 0, 5, hardBreak: false, width: 40.0, lineNumber: 0), - line('efg...', 5, 13, hardBreak: false, width: 60.0, lineNumber: 1), + line('abcd ', 0, 5, hardBreak: false, width: 40.0, lineNumber: 0, left: 0.0), + line('efg...', 5, 13, hardBreak: false, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -968,8 +968,8 @@ void main() async { expect(result.height, 20); expect(result.lines, [ - line('abcde ', 0, 6, hardBreak: false, width: 50.0, lineNumber: 0), - line('f g...', 6, 14, hardBreak: false, width: 60.0, lineNumber: 1), + line('abcde ', 0, 6, hardBreak: false, width: 50.0, lineNumber: 0, left: 0.0), + line('f g...', 6, 14, hardBreak: false, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -985,8 +985,8 @@ void main() async { expect(result.height, 20); expect(result.lines, [ - line('abcdef', 0, 6, hardBreak: false, width: 60.0, lineNumber: 0), - line('g hijk', 6, 12, hardBreak: true, width: 60.0, lineNumber: 1), + line('abcdef', 0, 6, hardBreak: false, width: 60.0, lineNumber: 0, left: 0.0), + line('g hijk', 6, 12, hardBreak: true, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -1002,8 +1002,8 @@ void main() async { expect(result.height, 20); expect(result.lines, [ - line('abcdef', 0, 6, hardBreak: false, width: 60.0, lineNumber: 0), - line('g h...', 6, 17, hardBreak: false, width: 60.0, lineNumber: 1), + line('abcdef', 0, 6, hardBreak: false, width: 60.0, lineNumber: 0, left: 0.0), + line('g h...', 6, 17, hardBreak: false, width: 60.0, lineNumber: 1, left: 0.0), ]); } else { // DOM-based measurement can't produce line metrics for multi-line @@ -1012,6 +1012,119 @@ void main() async { } }, ); + + test('handles textAlign', () { + TextMeasurementService instance = TextMeasurementService.canvasInstance; + ui.Paragraph p; + MeasurementResult result; + + ui.ParagraphStyle createStyle(ui.TextAlign textAlign) { + return ui.ParagraphStyle( + fontFamily: 'ahem', + fontSize: 10, + textAlign: textAlign, + textDirection: ui.TextDirection.ltr, + ); + } + + p = build(createStyle(ui.TextAlign.start), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), + ]); + + p = build(createStyle(ui.TextAlign.end), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 20.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 40.0), + ]); + + p = build(createStyle(ui.TextAlign.center), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 10.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 20.0), + ]); + + p = build(createStyle(ui.TextAlign.left), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), + ]); + + p = build(createStyle(ui.TextAlign.right), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 20.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 40.0), + ]); + }); + + testMeasurements( + 'handles rtl with textAlign', + (TextMeasurementService instance) { + TextMeasurementService instance = TextMeasurementService.canvasInstance; + ui.Paragraph p; + MeasurementResult result; + + ui.ParagraphStyle createStyle(ui.TextAlign textAlign) { + return ui.ParagraphStyle( + fontFamily: 'ahem', + fontSize: 10, + textAlign: textAlign, + textDirection: ui.TextDirection.rtl, + ); + } + + p = build(createStyle(ui.TextAlign.start), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 20.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 40.0), + ]); + + p = build(createStyle(ui.TextAlign.end), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), + ]); + + p = build(createStyle(ui.TextAlign.center), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 10.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 20.0), + ]); + + p = build(createStyle(ui.TextAlign.left), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 0.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 0.0), + ]); + + p = build(createStyle(ui.TextAlign.right), 'abc\ndefghi'); + result = instance.measure(p, constraints); + expect(result.lines, [ + line('abc', 0, 4, hardBreak: true, width: 30.0, lineNumber: 0, left: 20.0), + line('defgh', 4, 9, hardBreak: false, width: 50.0, lineNumber: 1, left: 0.0), + line('i', 9, 10, hardBreak: true, width: 10.0, lineNumber: 2, left: 40.0), + ]); + }, + ); }); } @@ -1023,6 +1136,7 @@ EngineLineMetrics line( double width, int lineNumber, bool hardBreak, + double left, }) { return EngineLineMetrics.withText( text, @@ -1031,5 +1145,6 @@ EngineLineMetrics line( hardBreak: hardBreak, width: width, lineNumber: lineNumber, + left: left, ); }