RTL

Change-Id: I5ff001f2c839c08b91dd50e4e813c4d3c885a857
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/456474
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/experimental/sktext/editor/Texts.cpp b/experimental/sktext/editor/Texts.cpp
index 638c247..5f53422 100644
--- a/experimental/sktext/editor/Texts.cpp
+++ b/experimental/sktext/editor/Texts.cpp
@@ -9,9 +9,9 @@
 void DynamicText::paint(SkCanvas* canvas) {
     if (!fDrawableText) {
         auto chunks = this->getDecorationChunks(fDecorations);
-        fDrawableText = fWrappedText->prepareToDraw(fUnicodeText.get(),
-                                                    PositionType::kGraphemeCluster,
-                                                    SkSpan<TextIndex>(chunks.data(), chunks.size()));
+        fDrawableText = fWrappedText->prepareToDraw<DrawableText>(fUnicodeText.get(),
+                                                                  PositionType::kGraphemeCluster,
+                                                                  SkSpan<TextIndex>(chunks.data(), chunks.size()));
     }
 
     auto foregroundPaint = fDecorations[0].foregroundPaint;
@@ -38,7 +38,9 @@
     } else {
         auto decorations = mergeSelectionIntoDecorations();
         auto chunks = this->getDecorationChunks(SkSpan<DecoratedBlock>(decorations.data(), decorations.size()));
-        fDrawableText = fWrappedText->prepareToDraw(fUnicodeText.get(), PositionType::kGraphemeCluster, SkSpan<TextIndex>(chunks.data(), chunks.size()));
+        fDrawableText = fWrappedText->prepareToDraw<DrawableText>(fUnicodeText.get(),
+                                                                  PositionType::kGraphemeCluster,
+                                                                  SkSpan<TextIndex>(chunks.data(), chunks.size()));
     }
     auto foregroundPaint = fDecorations[0].foregroundPaint;
     auto textBlobs = fDrawableText->getTextBlobs();
diff --git a/experimental/sktext/include/Text.h b/experimental/sktext/include/Text.h
index 9c95a90..16c01b5 100644
--- a/experimental/sktext/include/Text.h
+++ b/experimental/sktext/include/Text.h
@@ -197,7 +197,12 @@
         @param blocks         a range of text indices that cause an additional run breaking to be used for styling
         @return               an object that contains a list of SkTextBlobs to draw on a canvas
     */
-    std::unique_ptr<DrawableText> prepareToDraw(UnicodeText* unicodeText, PositionType positionType, SkSpan<TextIndex> blocks) const;
+    template <class Drawable>
+    std::unique_ptr<Drawable> prepareToDraw(UnicodeText* unicodeText, PositionType positionType, SkSpan<TextIndex> blocks) const {
+        auto drawableText = std::make_unique<Drawable>();
+        this->visit(unicodeText, drawableText.get(), positionType, blocks);
+        return drawableText;
+    }
     /** Aggregates all the data to navigate the text (move up, down, left, right),
         select some text near the cursor point, adjust all text position to word,
         grapheme cluster and such.
diff --git a/experimental/sktext/include/Types.h b/experimental/sktext/include/Types.h
index 93e3804..09e22fc 100644
--- a/experimental/sktext/include/Types.h
+++ b/experimental/sktext/include/Types.h
@@ -151,7 +151,15 @@
     bool after(TextIndex index) const {
         return fLeftToRight ? index < fEnd : index >= fStart;
     }
-
+    bool left() const {
+        return fLeftToRight ? fStart : fEnd;
+    }
+    bool right() const {
+        return fLeftToRight ? fEnd : fStart;
+    }
+    TextRange normalized() const {
+        return fLeftToRight ? TextRange(fStart, fEnd) : TextRange(fEnd, fStart);
+    }
     bool fLeftToRight;
 };
 
diff --git a/experimental/sktext/samples/Text.cpp b/experimental/sktext/samples/Text.cpp
index 65e7fea..5dd3ec4 100644
--- a/experimental/sktext/samples/Text.cpp
+++ b/experimental/sktext/samples/Text.cpp
@@ -139,11 +139,11 @@
     std::unique_ptr<SkUnicode> fUnicode;
 };
 
-class TextSample_LongRTL : public Sample {
+class TextSample_LongRTL1 : public Sample {
 protected:
     SkString name() override { return SkString("TextSample_LongRTL"); }
 
-    SkString mirror(const std::string& text) {
+    std::u16string mirror(const std::string& text) {
         std::u16string result;
         result += u"\u202E";
         for (auto i = text.size(); i > 0; --i) {
@@ -153,12 +153,31 @@
             result += ch;
         }
         result += u"\u202C";
-        return SkUnicode::convertUtf16ToUtf8(result);
+        return result;
     }
 
     void onDrawContent(SkCanvas* canvas) override {
         canvas->drawColor(SK_ColorWHITE);
-        Paint::drawText(u"LONG MIRRORED TEXT SHOULD SHOW RIGHT TO LEFT (AS NORMAL)", canvas, 0, 0);
+        Paint::drawText(mirror("LONG MIRRORED TEXT SHOULD SHOW RIGHT TO LEFT (AS NORMAL)"), canvas, 0, 0);
+    }
+
+private:
+    using INHERITED = Sample;
+    std::unique_ptr<SkUnicode> fUnicode;
+};
+
+class TextSample_LongRTL2 : public Sample {
+protected:
+    SkString name() override { return SkString("TextSample_LongRTL"); }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        canvas->drawColor(SK_ColorWHITE);
+        std::u16string utf16(u"يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ");
+        Paint::drawText(utf16, canvas,
+                        TextDirection::kRtl, TextAlign::kRight,
+                        SkPaint(SkColors::kBlack), SkPaint(SkColors::kLtGray),
+                        SkString("Noto Naskh Arabic"), 40.0f, SkFontStyle::Normal(),
+                        SkSize::Make(800, 800), 0, 0);
     }
 
 private:
@@ -171,5 +190,5 @@
 DEF_SAMPLE(return new TextSample_HelloWorld();)
 DEF_SAMPLE(return new TextSample_Align_Dir();)
 DEF_SAMPLE(return new TextSample_LongLTR();)
-DEF_SAMPLE(return new TextSample_LongRTL();)
-
+DEF_SAMPLE(return new TextSample_LongRTL1();)
+DEF_SAMPLE(return new TextSample_LongRTL2();)
diff --git a/experimental/sktext/src/Line.h b/experimental/sktext/src/Line.h
index a1c5ce7..d205b2b 100644
--- a/experimental/sktext/src/Line.h
+++ b/experimental/sktext/src/Line.h
@@ -91,6 +91,13 @@
         , fTextRange(textIndex, textIndex)
         , fTextMetrics(metrics) { }
 
+    Stretch(RunIndex runIndex, GlyphRange glyphRange, TextRange textRange, SkScalar width, const TextMetrics& metrics)
+        : fGlyphStart(runIndex, glyphRange.fStart)
+        , fGlyphEnd(runIndex, glyphRange.fEnd)
+        , fWidth(width)
+        , fTextRange(textRange)
+        , fTextMetrics(metrics) { }
+
     Stretch(const Stretch&) = default;
     Stretch(Stretch&&) = default;
     Stretch& operator=(Stretch&&) = default;
diff --git a/experimental/sktext/src/Text.cpp b/experimental/sktext/src/Text.cpp
index dd0b8cc..63d9d9f 100644
--- a/experimental/sktext/src/Text.cpp
+++ b/experimental/sktext/src/Text.cpp
@@ -336,28 +336,40 @@
             this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, true);
             line = spaces;
             clusters = spaces;
-            cluster = Stretch();
             continue;
         }
         TextMetrics runMetrics(run.fFont);
-        if (!run.leftToRight()) {
-            cluster.setTextRange({ run.fUtf16Range.fStart, run.fUtf16Range.fEnd});
-        }
+
         // Let's wrap the text
+        GlyphRange clusterGlyphs;
+        DirTextRange clusterText(EMPTY_RANGE, run.leftToRight());
         for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
             auto textIndex = run.fClusters[glyphIndex];
-            if (cluster.textRange() == EMPTY_RANGE) {
+
+            if (clusterText == EMPTY_RANGE) {
                 // The beginning of a new line (or the first one)
-                cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
-                line = cluster;
-                spaces = cluster;
-                clusters = cluster;
+                clusterText = DirTextRange(textIndex, textIndex, run.leftToRight());
+                clusterGlyphs = GlyphRange(glyphIndex, glyphIndex);
+
+                Stretch empty(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
+                line = empty;
+                spaces = empty;
+                clusters = empty;
                 continue;
             }
-            // The entire cluster belongs to a single run
-            SkASSERT(cluster.glyphStart().runIndex() == runIndex);
-            auto clusterWidth = run.calculateWidth(cluster.glyphStartIndex(), glyphIndex);
-            cluster.finish(glyphIndex, textIndex, clusterWidth);
+
+            if (textIndex == clusterText.fStart) {
+                // Skip until the next cluster
+                continue;
+            }
+
+            // Finish the cluster (notice that it belongs to a single run)
+            clusterText.fStart = clusterText.fEnd;
+            clusterText.fEnd = textIndex;
+            clusterGlyphs.fStart = clusterGlyphs.fEnd;
+            clusterGlyphs.fEnd = glyphIndex;
+            cluster = Stretch(runIndex, clusterGlyphs, clusterText.normalized(), run.calculateWidth(clusterGlyphs), runMetrics);
+
             auto isSoftLineBreak = unicodeText->isSoftLineBreak(cluster.textStart());
             auto isWhitespaces = unicodeText->isWhitespaces(cluster.textRange());
             auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf16Range.fEnd : textIndex == run.fUtf16Range.fStart;
@@ -399,9 +411,12 @@
                 line = spaces;
             }
             clusters.moveTo(cluster);
-            cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
+
+            clusterGlyphs.fStart = clusterGlyphs.fEnd;
+            clusterText.fStart = clusterText.fEnd;
         }
     }
+
     // Deal with the last line
     if (!clusters.isEmpty()) {
         line.moveTo(spaces);
@@ -465,8 +480,9 @@
             auto textEnd = isLastRun ? lineStretch.textRange().fEnd : logicalRun.fUtf16Range.fEnd;
             wrappedText->fVisualRuns.emplace_back(TextRange(textStart, textEnd),
                                                   glyphSpaces - glyphStart,
-                                                  logicalRun.fTextMetrics,
-                                                  runOffsetInLine,
+                                                  logicalRun.fFont,
+                                                  lineStretch.textMetrics().baseline(),
+                                                  SkPoint::Make(runOffsetInLine, wrappedText->fActualSize.fHeight),
                                                   logicalRun.leftToRight(),
                                                   SkSpan<SkPoint>(&logicalRun.fPositions[glyphStart], glyphSize + 1),
                                                   SkSpan<SkGlyphID>(&logicalRun.fGlyphs[glyphStart], glyphSize),
@@ -479,7 +495,7 @@
                     : SkSpan<VisualRun>(&wrappedText->fVisualRuns[runStart], wrappedText->fVisualRuns.size() - runStart);
     wrappedText->fVisualLines.emplace_back(lineStretch.textRange(), hardLineBreak, wrappedText->fActualSize.fHeight, runRange);
     wrappedText->fActualSize.fHeight += lineStretch.textMetrics().height();
-    wrappedText->fActualSize.fWidth = lineStretch.width();
+    wrappedText->fActualSize.fWidth = std::max(wrappedText->fActualSize.fWidth, lineStretch.width());
     stretch.clean();
     spaces.clean();
 }
@@ -615,12 +631,6 @@
     return glyphRange;
 }
 
-std::unique_ptr<DrawableText> WrappedText::prepareToDraw(UnicodeText* unicodeText, PositionType positionType, SkSpan<TextIndex> blocks) const {
-    auto drawableText = std::make_unique<DrawableText>();
-    this->visit(unicodeText, drawableText.get(), positionType, blocks);
-    return std::move(drawableText);
-}
-
 std::unique_ptr<SelectableText> WrappedText::prepareToEdit(UnicodeText* unicodeText) const {
     auto selectableText = std::make_unique<SelectableText>();
     this->visit(selectableText.get());
@@ -673,7 +683,7 @@
     for (auto i = 0; i < glyphCount; ++i) {
         auto pos = positions[i];
         auto pos1 = positions[i + 1];
-        line.fBoxGlyphs[start + i] = SkRect::MakeXYWH(pos.fX, bounds.fTop + pos.fY, pos1.fX - pos.fX, bounds.height());
+        line.fBoxGlyphs[start + i] = SkRect::MakeXYWH(pos.fX, bounds.fTop, pos1.fX - pos.fX, bounds.height());
         line.fTextByGlyph[start + i] = clusters[i];
     }
 }
diff --git a/experimental/sktext/src/VisualRun.h b/experimental/sktext/src/VisualRun.h
index 8eb5335..d348e13 100644
--- a/experimental/sktext/src/VisualRun.h
+++ b/experimental/sktext/src/VisualRun.h
@@ -11,16 +11,23 @@
 
 class VisualRun {
     public:
-    VisualRun(TextRange textRange, GlyphIndex trailingSpacesStart, const TextMetrics& metrics, SkScalar runOffsetInLine,
+    VisualRun(TextRange textRange, GlyphIndex trailingSpacesStart, const SkFont& font, SkScalar lineBaseLine,
+              SkPoint runOffset,
               bool leftToRight,
               SkSpan<SkPoint> positions, SkSpan<SkGlyphID> glyphs, SkSpan<uint32_t> clusters)
-        : fTextMetrics(metrics)
+        : fFont(font)
+        , fTextMetrics(TextMetrics(fFont))
+        , fLineBaseLine(lineBaseLine)
         , fDirTextRange(textRange, leftToRight)
         , fTrailingSpacesStart(trailingSpacesStart) {
+        if (positions.size() == 0) {
+            SkASSERT(false);
+            return;
+        }
         fPositions.reserve_back(positions.size());
-        runOffsetInLine -= positions[0].fX;
+        runOffset -= SkPoint::Make(positions[0].fX, - fLineBaseLine);
         for (auto& pos : positions) {
-            fPositions.emplace_back(pos + SkPoint::Make(runOffsetInLine, 0));
+            fPositions.emplace_back(pos + runOffset);
         }
         fGlyphs.reserve_back(glyphs.size());
         for (auto glyph : glyphs) {
@@ -30,9 +37,7 @@
         for (auto cluster : clusters) {
             fClusters.emplace_back(SkToU16(cluster));
         }
-
-        fAdvance.fX = calculateWidth(0, glyphs.size());
-        fAdvance.fY = metrics.height();
+        fAdvance= SkVector::Make(this->calculateWidth(0, glyphs.size()), fTextMetrics.height());
     }
 
     SkScalar calculateWidth(GlyphRange glyphRange) const {
@@ -43,11 +48,13 @@
       return calculateWidth(GlyphRange(start, end));
     }
     SkScalar width() const { return fAdvance.fX; }
+    SkScalar height() const { return fAdvance.fY; }
     SkScalar firstGlyphPosition() const { return fPositions[0].fX; }
+    TextMetrics textMetrics() const { return fTextMetrics; }
 
     bool leftToRight() const { return fDirTextRange.fLeftToRight; }
     size_t size() const { return fGlyphs.size(); }
-    TextMetrics textMetrics() const { return fTextMetrics; }
+    SkScalar baseLine() const { return fLineBaseLine; }
     GlyphIndex trailingSpacesStart() const { return fTrailingSpacesStart; }
     DirTextRange dirTextRange() const { return fDirTextRange; }
 
@@ -78,6 +85,7 @@
     friend class WrappedText;
     SkFont fFont;
     TextMetrics fTextMetrics;
+    SkScalar fLineBaseLine;
 
     SkVector fAdvance;
     DirTextRange fDirTextRange;
diff --git a/experimental/sktext/tests/WrappedText.cpp b/experimental/sktext/tests/WrappedText.cpp
index ba899d1..2ed3a20 100644
--- a/experimental/sktext/tests/WrappedText.cpp
+++ b/experimental/sktext/tests/WrappedText.cpp
@@ -89,7 +89,6 @@
     std::vector<TestRun> fTestRuns;
 };
 
-
 UNIX_ONLY_TEST(SkText_WrappedText_Spaces, reporter) {
     sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
     if (fontChain->empty()) return;
@@ -138,3 +137,29 @@
         ++runIndex;
     }
 }
+
+DEF_TEST(SkText_WrappedText_LongRTL, reporter) {
+    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Noto Naskh Arabic", 40.0f, SkFontStyle::Normal());
+    if (fontChain->empty()) return;
+
+    std::u16string utf16(u"يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُيَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ يَهْدِيْكُمُ اللَّهُ وَيُصْلِحُ بَالَكُمُ");
+    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
+    if (!unicodeText.getUnicode()) return;
+
+    FontBlock fontBlock(utf16.size(), fontChain);
+    auto fontResolvedText = unicodeText.resolveFonts(SkSpan<FontBlock>(&fontBlock, 1));
+    auto shapedText = fontResolvedText->shape(&unicodeText, TextDirection::kLtr);
+    auto wrappedText = shapedText->wrap(&unicodeText, 800.0f, 800.0f);
+
+    TestVisitor testVisitor;
+    wrappedText->visit(&testVisitor);
+
+    REPORTER_ASSERT(reporter, testVisitor.fTestLines.size() == 4);
+    REPORTER_ASSERT(reporter, testVisitor.fTestRuns.size() == 4);
+
+    REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].trailingSpaces.width() == 1);
+    REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].trailingSpaces.width() == 1);
+    REPORTER_ASSERT(reporter, testVisitor.fTestLines[2].trailingSpaces.width() == 1);
+    REPORTER_ASSERT(reporter, testVisitor.fTestLines[3].trailingSpaces.width() == 0);
+}
+