Ellipsis attached to cluster, not word

Change-Id: I3bcef7e57a8f95a12743325363463a3a4039776e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/263206
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/modules/skparagraph/src/TextWrapper.cpp b/modules/skparagraph/src/TextWrapper.cpp
index 813152b..972a21a 100644
--- a/modules/skparagraph/src/TextWrapper.cpp
+++ b/modules/skparagraph/src/TextWrapper.cpp
@@ -72,23 +72,29 @@
     }
 }
 
-void TextWrapper::moveForward() {
-    do {
-        if (!fWords.empty()) {
-            fEndLine.extend(fWords);
-        } else if (!fClusters.empty()) {
-            fEndLine.extend(fClusters);
-            fTooLongWord = false;
-            fTooLongCluster = false;
-        } else if (!fClip.empty() || (fTooLongWord && fTooLongCluster)) {
-            // Flutter: forget the clipped cluster but keep the metrics
-            fEndLine.metrics().add(fClip.metrics());
-            fTooLongWord = false;
-            fTooLongCluster = false;
-        } else {
-            break;
+void TextWrapper::moveForward(bool hasEllipsis) {
+
+    // We normally break lines by words.
+    // The only way we may go to clusters is if the word is too long or
+    // it's the first word and it has an ellipsis attached to it.
+    // If nothing fits we show the clipping.
+    if (!fWords.empty()) {
+        fEndLine.extend(fWords);
+        if (!fTooLongWord || hasEllipsis) {
+            return;
         }
-    } while (fTooLongWord || fTooLongCluster);
+    }
+    if (!fClusters.empty()) {
+        fEndLine.extend(fClusters);
+        if (!fTooLongCluster) {
+            return;
+        }
+    }
+
+    if (!fClip.empty()) {
+        // Flutter: forget the clipped cluster but keep the metrics
+        fEndLine.metrics().add(fClip.metrics());
+    }
 }
 
 // Special case for start/end cluster since they can be clipped
@@ -160,6 +166,7 @@
     return std::make_tuple(cluster, 0, width);
 }
 
+// TODO: refactor the code for line ending (with/without ellipsis)
 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
                                      SkScalar maxWidth,
                                      const AddLineToParagraph& addLine) {
@@ -169,7 +176,7 @@
 
     auto span = parent->clusters();
     if (span.size() == 0) {
-      return;
+        return;
     }
     auto maxLines = parent->paragraphStyle().getMaxLines();
     auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
@@ -185,10 +192,10 @@
     while (fEndLine.endCluster() != end) {
 
         reset();
-
+        auto exceededLines = !endlessLine && fLineNumber >= maxLines;
         fEndLine.metrics().clean();
         lookAhead(maxWidth, end);
-        moveForward();
+        moveForward(exceededLines && !ellipsisStr.isEmpty());
 
         // Do not trim end spaces on the naturally last line of the left aligned text
         trimEndSpaces(align);
@@ -204,9 +211,11 @@
             SkScalarIsFinite(maxWidth) &&
             !ellipsisStr.isEmpty();
 
-        auto exceededLines = !endlessLine && fLineNumber >= maxLines;
-
-        // TODO: perform ellipsis work here
+        if (needEllipsis && exceededLines && !fHardLineBreak) {
+            // This is what we need to do to preserve a space before the ellipsis
+            fEndLine.restoreBreak();
+            widthWithSpaces = fEndLine.withWithGhostSpaces();
+        }
 
         // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
         if (fHardLineBreak && fEndLine.width() == 0) {
@@ -264,9 +273,11 @@
         fEndLine.startFrom(startLine, pos);
         parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
 
-        if (exceededLines || (needEllipsis && endlessLine && !fHardLineBreak)) {
+        if (exceededLines) {
             fHardLineBreak = false;
             break;
+        } else if (endlessLine && needEllipsis) {
+            break;
         }
 
         ++fLineNumber;
@@ -302,7 +313,6 @@
     }
 
     if (fHardLineBreak) {
-
         // Last character is a line break
         if (parent->strutEnabled()) {
             // Make sure font metrics are not less than the strut