Multi line + LTR/RTL

Change-Id: I0859773c00b6a4bd19047d25b75ee7c6727bc470
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/403837
Reviewed-by: Julia Lavrova <jlavrova@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/experimental/sktext/include/Processor.h b/experimental/sktext/include/Processor.h
index 4c31c70..521f9a7 100644
--- a/experimental/sktext/include/Processor.h
+++ b/experimental/sktext/include/Processor.h
@@ -25,16 +25,16 @@
         kDecor,
         kFormat,
     };
-    Block(BlockType type, Range range)
+    Block(BlockType type, TextRange range)
         : fType(type)
         , fRange(range) { }
     BlockType fType;
-    Range fRange;
+    TextRange fRange;
 };
 
 class FontBlock : public Block {
 public:
-    FontBlock(const SkString& family, SkScalar size, SkFontStyle style, Range range)
+    FontBlock(const SkString& family, SkScalar size, SkFontStyle style, TextRange range)
         : Block(Block::kFont, range)
         , fFontFamily(family)
         , fFontSize(size)
@@ -48,12 +48,12 @@
 
 class DecorBlock : public Block {
 public:
-    DecorBlock(const SkPaint* foreground, const SkPaint* background, Range range)
+    DecorBlock(const SkPaint* foreground, const SkPaint* background, TextRange range)
         : Block(Block::kDecor, range)
         , fForegroundColor(foreground)
         , fBackgroundColor(background) { }
 
-    DecorBlock(Range range)
+    DecorBlock(TextRange range)
         : DecorBlock(nullptr, nullptr, range) { }
 
     const SkPaint* fForegroundColor;
@@ -119,10 +119,10 @@
     }
 
     bool isSoftLineBreak(size_t index) {
-        return this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
+        return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
     }
 
-    bool isWhitespaces(Range range) {
+    bool isWhitespaces(TextRange range) {
         if (range.leftToRight()) {
             for (auto i = range.fStart; i < range.fEnd; ++i) {
                 if (!this->hasProperty(i, CodeUnitFlags::kPartOfWhiteSpace)) {
@@ -158,8 +158,11 @@
 
     // Simplification (using default font manager, default font family and default everything possible)
     static bool drawText(const char* text, SkCanvas* canvas, SkScalar x, SkScalar y);
-    //static bool drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y);
+    static bool drawText(const char* text, SkCanvas* canvas, SkScalar width);
     static bool drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y);
+    static bool drawText(const char* text, SkCanvas* canvas,
+                         TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle,
+                         SkSize reqSize, SkScalar x, SkScalar y);
 
     void sortDecorBlocks(SkTArray<DecorBlock, true>& decorBlocks);
 
@@ -167,10 +170,6 @@
 
     void markGlyphs();
 
-    // Iterating through the input code units and breaking the runs by units flag (no breaking if units == CodeUnitFlags::kNonExistingFlag)
-    template<typename Visitor>
-    void iterateByLogicalOrder(CodeUnitFlags units, Visitor visitor);
-
     // Iterating through the output glyphs and breaking the runs by units flag (no breaking if units == CodeUnitFlags::kNonExistingFlag)
     template<typename Visitor>
     void iterateByVisualOrder(CodeUnitFlags units, Visitor visitor);
diff --git a/experimental/sktext/include/Types.h b/experimental/sktext/include/Types.h
index e46a387..594e64b 100644
--- a/experimental/sktext/include/Types.h
+++ b/experimental/sktext/include/Types.h
@@ -28,22 +28,27 @@
   kHardLineBreakBefore,
 };
 
-enum CodeUnitFlags : uint64_t {
-    kNoCodeUnitFlag = 0x0,
-    kPartOfWhiteSpace = 0x1,
-    kGraphemeStart = 0x2,
-    kSoftLineBreakBefore = 0x4,
-    kHardLineBreakBefore = 0x8,
+enum CodeUnitFlags : uint32_t {
+    kNoCodeUnitFlag = 0x000,
+    kPartOfWhiteSpace = 0x001,
+    kGraphemeStart = 0x002,
+    kSoftLineBreakBefore = 0x004,
+    kHardLineBreakBefore = 0x008,
     // This information we get from SkShaper
-    kGlyphStart = 0x16,
-    kGlyphClusterStart = 0x32,
-    kNonExistingFlag = 0x64,
+    kGlyphStart = 0x010,
+    kGlyphClusterStart = 0x020,
+    kNonExistingFlag = 0x040,
 };
 
+typedef size_t TextIndex;
+typedef size_t GlyphIndex;
+const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
+
+template <typename T>
 class Range {
 public:
     Range() : fStart(0), fEnd(0) { }
-    Range(size_t start, size_t end) : fStart(start) , fEnd(end) { }
+    Range(T start, T end) : fStart(start) , fEnd(end) { }
 
     bool leftToRight() const {
         return fEnd >= fStart;
@@ -70,15 +75,17 @@
     }
 
     void merge(Range tail) {
-        auto ltr = this->leftToRight();
-
+        auto ltr1 = this->leftToRight();
+        auto ltr2 = tail.leftToRight();
         this->normalize();
         tail.normalize();
         SkASSERT(this->fEnd == tail.fStart || this->fStart == tail.fEnd);
         this->fStart = std::min(this->fStart, tail.fStart);
         this->fEnd = std::max(this->fEnd, tail.fEnd);
-        if (!ltr) {
+        // TODO: Merging 2 different directions
+        if (!ltr1 || !ltr2) {
             std::swap(this->fStart, this->fEnd);
+            std::swap(tail.fStart, tail.fEnd);
         }
     }
 
@@ -110,11 +117,12 @@
         }
     }
 
-    size_t fStart;
-    size_t fEnd;
+    T fStart;
+    T fEnd;
 };
 
-const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
+typedef Range<TextIndex> TextRange;
+typedef Range<GlyphIndex> GlyphRange;
 const Range EMPTY_RANGE = Range(EMPTY_INDEX, EMPTY_INDEX);
 
 }  // namespace text
diff --git a/experimental/sktext/samples/Text.cpp b/experimental/sktext/samples/Text.cpp
index c72cdf9..af59cd63a 100644
--- a/experimental/sktext/samples/Text.cpp
+++ b/experimental/sktext/samples/Text.cpp
@@ -116,7 +116,54 @@
     using INHERITED = Sample;
     std::unique_ptr<SkUnicode> fUnicode;
 };
+
+class TextSample_LongLTR : public Sample {
+protected:
+    SkString name() override { return SkString("TextSample_LongLTR"); }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        canvas->drawColor(SK_ColorWHITE);
+        Processor::drawText("A very_very_very_very_very_very_very_very_very_very "
+                "very_very_very_very_very_very_very_very_very_very very very very very very very "
+                "very very very very very very very very very very very very very very very very "
+                "very very very very very very very very very very very very very long text", canvas, this->width());
+
+    }
+
+private:
+    using INHERITED = Sample;
+    std::unique_ptr<SkUnicode> fUnicode;
+};
+
+class TextSample_LongRTL : public Sample {
+protected:
+    SkString name() override { return SkString("TextSample_LongRTL"); }
+
+    SkString mirror(const std::string& text) {
+        std::u16string result;
+        result += u"\u202E";
+        for (auto i = text.size(); i > 0; --i) {
+          result += text[i - 1];
+        }
+        for (auto ch : text) {
+            result += ch;
+        }
+        result += u"\u202C";
+        return fUnicode->convertUtf16ToUtf8(result);
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        canvas->drawColor(SK_ColorWHITE);
+        Processor::drawText("LONG MIRRORED TEXT SHOULD SHOW RIGHT TO LEFT (AS NORMAL)", canvas, 0, 0);
+    }
+
+private:
+    using INHERITED = Sample;
+    std::unique_ptr<SkUnicode> fUnicode;
+};
 }  // namespace
 
 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();)
diff --git a/experimental/sktext/src/Line.cpp b/experimental/sktext/src/Line.cpp
index d13008f..8c289a1 100644
--- a/experimental/sktext/src/Line.cpp
+++ b/experimental/sktext/src/Line.cpp
@@ -8,29 +8,29 @@
 namespace skia {
 namespace text {
 Line::Line(Processor* processor, const Stretch& stretch, const Stretch& spaces)
-    : fTextStart(stretch.fGlyphStart)
-    , fTextEnd(stretch.fGlyphEnd)
-    , fWhitespacesEnd (spaces.fGlyphEnd)
-    , fText(stretch.fTextRange)
-    , fWhitespaces(spaces.fTextRange)
-    , fTextWidth(stretch.fWidth)
-    , fSpacesWidth(spaces.fWidth) {
+    : fTextStart(stretch.glyphStart())
+    , fTextEnd(stretch.glyphEnd())
+    , fWhitespacesEnd (spaces.glyphEnd())
+    , fText(stretch.textRange())
+    , fWhitespaces(spaces.textRange())
+    , fTextWidth(stretch.width())
+    , fSpacesWidth(spaces.width()) {
 
     SkASSERT(stretch.isEmpty() ||
                     spaces.isEmpty() ||
-        (stretch.fGlyphEnd == spaces.fGlyphStart));
+        (stretch.glyphEnd() == spaces.glyphStart()));
 
     if (!stretch.isEmpty()) {
-        this->fTextMetrics.merge(stretch.fTextMetrics);
+        this->fTextMetrics.merge(stretch.textMetrics());
     }
     if (!spaces.isEmpty()) {
-        this->fTextMetrics.merge(spaces.fTextMetrics);
+        this->fTextMetrics.merge(spaces.textMetrics());
     }
 
     // This is just chosen to catch the common/fast cases. Feel free to tweak.
     constexpr int kPreallocCount = 4;
-    auto start = stretch.fGlyphStart.fRunIndex;
-    auto end = spaces.fGlyphEnd.fRunIndex;
+    auto start = stretch.glyphStart().runIndex();
+    auto end = spaces.glyphEnd().runIndex();
     auto numRuns = end - start + 1;
     SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
     size_t runLevelsIndex = 0;
diff --git a/experimental/sktext/src/Line.h b/experimental/sktext/src/Line.h
index b01ddeb..9951c93 100644
--- a/experimental/sktext/src/Line.h
+++ b/experimental/sktext/src/Line.h
@@ -58,13 +58,20 @@
 class GlyphPos {
 public:
 
-    GlyphPos() { }
+    GlyphPos() : fRunIndex(EMPTY_INDEX), fGlyphIndex(EMPTY_INDEX) { }
     GlyphPos(size_t runIndex, size_t glyphIndex) : fRunIndex(runIndex), fGlyphIndex(glyphIndex) { }
 
     bool operator==(const GlyphPos& other) const {
         return this->fRunIndex == other.fRunIndex && this->fGlyphIndex == other.fGlyphIndex;
     }
 
+    size_t runIndex() const { return fRunIndex; }
+    size_t glyphIndex() const { return fGlyphIndex; }
+    void setGlyphIndex(size_t index) { fGlyphIndex = index; }
+
+    bool isEmpty() const { return fRunIndex == EMPTY_INDEX; }
+
+private:
     size_t fRunIndex;
     size_t fGlyphIndex;
 };
@@ -72,50 +79,59 @@
 class Stretch {
 public:
 
-    Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(0, 0), fTextMetrics(), fEmpty(true) { }
+    Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(0, 0), fTextMetrics() { }
 
     Stretch(GlyphPos glyphStart, size_t textIndex, const TextMetrics& metrics)
         : fGlyphStart(glyphStart)
         , fGlyphEnd(glyphStart)
         , fWidth(0.0)
         , fTextRange(textIndex, textIndex)
-        , fTextMetrics(metrics)
-        , fEmpty(false) { }
+        , fTextMetrics(metrics) { }
 
+    Stretch(const Stretch&) = default;
     Stretch(Stretch&&) = default;
     Stretch& operator=(Stretch&&) = default;
+    Stretch& operator=(const Stretch&) = default;
 
     bool isEmpty() const {
-      return  this->fEmpty/* ||
-             (this->fGlyphStart.fRunIndex == this->fGlyphEnd.fRunIndex &&
-              this->fGlyphStart.fGlyphIndex == this->fGlyphEnd.fGlyphIndex)*/;
+        if (this->fGlyphStart.isEmpty()) {
+            SkASSERT(this->fGlyphEnd.isEmpty());
+            return true;
+        } else {
+            SkASSERT(!this->fGlyphEnd.isEmpty());
+            return false;
+            //return (this->fGlyphStart.runIndex() == this->fGlyphEnd.runIndex() &&
+            //        this->fGlyphStart.glyphIndex() == this->fGlyphEnd.glyphIndex());
+        }
     }
 
     void clean() {
-        this->fEmpty = true;
+        fGlyphStart = fGlyphEnd;
+        fTextRange.fStart = fTextRange.fEnd;
+        fWidth = 0.0f;
+        fTextMetrics.clean();
     }
 
     void moveTo(Stretch& tail) {
 
-        if (tail.fEmpty) {
+        if (tail.isEmpty()) {
             return;
         }
 
-        if (this->fEmpty) {
-            if (!tail.fEmpty) {
+        if (this->isEmpty()) {
+            if (!tail.isEmpty()) {
                 this->fGlyphStart = tail.fGlyphStart;
                 this->fGlyphEnd = tail.fGlyphEnd;
                 this->fWidth = tail.fWidth;
                 this->fTextRange = tail.fTextRange;
                 this->fTextMetrics = tail.fTextMetrics;
-                this->fEmpty = tail.fEmpty;
             }
             tail.clean();
             return;
         }
 
-        SkASSERT(this->fGlyphEnd.fRunIndex != tail.fGlyphStart.fRunIndex ||
-                       this->fGlyphEnd.fGlyphIndex == tail.fGlyphStart.fGlyphIndex);
+        SkASSERT(this->fGlyphEnd.runIndex() != tail.fGlyphStart.runIndex() ||
+                       this->fGlyphEnd.glyphIndex() == tail.fGlyphStart.glyphIndex());
         this->fGlyphEnd = tail.fGlyphEnd;
         this->fWidth += tail.fWidth;
         this->fTextRange.merge(tail.fTextRange);
@@ -125,16 +141,26 @@
 
     void finish(size_t glyphIndex, size_t textIndex, SkScalar width) {
         this->fTextRange.fEnd = textIndex;
-        this->fGlyphEnd.fGlyphIndex = glyphIndex;
+        this->fGlyphEnd.setGlyphIndex(glyphIndex);
         this->fWidth = width;
     }
 
+    SkScalar width() const { return fWidth; }
+    TextRange textRange() const { return fTextRange; }
+    void setTextRange(TextRange range) { fTextRange = range; }
+
+    const TextMetrics& textMetrics() const { return fTextMetrics; }
+    GlyphPos glyphStart() const { return fGlyphStart; }
+    GlyphPos glyphEnd() const { return fGlyphEnd; }
+    size_t glyphStartIndex() const { return fGlyphStart.glyphIndex(); }
+    size_t textStart() const { return fTextRange.fStart; }
+
+private:
     GlyphPos fGlyphStart;
     GlyphPos fGlyphEnd;
     SkScalar fWidth;
-    Range fTextRange;
+    TextRange fTextRange;
     TextMetrics fTextMetrics;
-    bool fEmpty;
 };
 
 class Line {
@@ -146,10 +172,10 @@
     friend class Processor;
 
     GlyphPos fTextStart;
-    GlyphPos fTextEnd ;
+    GlyphPos fTextEnd;
     GlyphPos fWhitespacesEnd;
-    Range fText;
-    Range fWhitespaces;
+    TextRange fText;
+    TextRange fWhitespaces;
     SkScalar fTextWidth;
     SkScalar fSpacesWidth;
     TextMetrics fTextMetrics;
diff --git a/experimental/sktext/src/Processor.cpp b/experimental/sktext/src/Processor.cpp
index f92a2c4..676a612 100644
--- a/experimental/sktext/src/Processor.cpp
+++ b/experimental/sktext/src/Processor.cpp
@@ -55,7 +55,7 @@
 bool Processor::decorate(SkTArray<DecorBlock, true> decorBlocks) {
 
     this->iterateByVisualOrder(decorBlocks,
-       [&](SkSize offset, SkScalar baseline, const TextRun* run, Range textRange, Range glyphRange, const DecorBlock& block) {
+       [&](SkSize offset, SkScalar baseline, const TextRun* run, TextRange textRange, GlyphRange glyphRange, const DecorBlock& block) {
         SkTextBlobBuilder builder;
         const auto& blobBuffer = builder.allocRunPos(run->fFont, SkToInt(glyphRange.width()));
         sk_careful_memcpy(blobBuffer.glyphs, run->fGlyphs.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkGlyphID));
@@ -74,10 +74,22 @@
     return drawText(text, canvas, TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), x, y);
 }
 
+bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar width) {
+    return drawText(text, canvas,
+                    TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(),
+                    SkSize::Make(width, SK_ScalarInfinity), 0, 0);
+}
+
 bool Processor::drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y) {
+    return drawText(text, canvas, textFormat, foreground, background, fontFamily, fontSize, fontStyle, SkSize::Make(SK_ScalarInfinity, SK_ScalarInfinity), x, y);
+}
+
+bool Processor::drawText(const char* text, SkCanvas* canvas,
+                         TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle,
+                         SkSize reqSize, SkScalar x, SkScalar y) {
 
     SkString str(text);
-    Range textRange(0, str.size());
+    TextRange textRange(0, str.size());
     Processor processor(str);
     if (!processor.computeCodeUnitProperties()) {
         return false;
@@ -85,7 +97,7 @@
     if (!processor.shape({ textFormat.fDefaultTextDirection, SkFontMgr::RefDefault()}, {{{ fontFamily, fontSize, fontStyle, textRange }}})) {
         return false;
     }
-    if (!processor.wrap(SK_ScalarInfinity, SK_ScalarInfinity)) {
+    if (!processor.wrap(reqSize.fWidth, reqSize.fHeight)) {
         return false;
     }
     if (!processor.format(textFormat)) {
@@ -117,7 +129,7 @@
     SkPaint* foreground = new SkPaint();
     foreground->setColor(SK_ColorBLACK);
     std::stack<DecorBlock> defaultBlocks;
-    defaultBlocks.emplace(foreground, nullptr, Range(0, fText.size()));
+    defaultBlocks.emplace(foreground, nullptr, TextRange(0, fText.size()));
     size_t start = 0;
     for (auto& block : decorBlocks) {
         this->adjustLeft(&block.fRange.fStart);
@@ -193,18 +205,6 @@
 }
 
 template<typename Visitor>
-void Processor::iterateByLogicalOrder(CodeUnitFlags units, Visitor visitor) {
-    Range range(0, 0);
-    for (size_t index = 0; index < fCodeUnitProperties.size(); ++index) {
-        if (this->hasProperty(index, units)) {
-            range.fEnd = index;
-            visitor(range, this->fCodeUnitProperties[index]);
-            range.fStart = index;
-        }
-    }
-}
-
-template<typename Visitor>
 void Processor::iterateByVisualOrder(CodeUnitFlags units, Visitor visitor) {
     SkSize offset = SkSize::MakeEmpty();
     for (auto& line : fLines) {
@@ -212,8 +212,8 @@
         for (auto& runIndex : line.fRunsInVisualOrder) {
             auto& run = fRuns[runIndex];
 
-            auto startGlyph = runIndex == line.fTextStart.fRunIndex == runIndex ? line.fTextStart.fGlyphIndex : 0;
-            auto endGlyph = runIndex == line.fTextEnd.fRunIndex ? line.fTextEnd.fGlyphIndex : run.fGlyphs.size();
+            auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
+            auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
 
             Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end());
             Range glyphRange(startGlyph, endGlyph);
@@ -257,11 +257,11 @@
             // "Cd": green
             // "ef": blue
             // green[d] blue[ef] green [C] red [BA]
-            auto startGlyph = runIndex == line.fTextStart.fRunIndex == runIndex ? line.fTextStart.fGlyphIndex : 0;
-            auto endGlyph = runIndex == line.fTextEnd.fRunIndex ? line.fTextEnd.fGlyphIndex : run.fGlyphs.size();
+            auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
+            auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
 
-            Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end());
-            Range glyphRange(startGlyph, endGlyph);
+            TextRange textRange(run.fClusters[startGlyph], run.fClusters[endGlyph]);
+            GlyphRange glyphRange(startGlyph, endGlyph);
 
             SkASSERT(currentBlock->fRange.fStart <= textRange.fStart);
             for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) {
@@ -278,7 +278,8 @@
                     textRange.fStart = textIndex;
                 }
                 glyphRange.fEnd = glyphIndex;
-                visitor(offset, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
+                SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
+                visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
                 if (run.leftToRight()) {
                     textRange.fStart = textIndex;
                 } else {
@@ -287,6 +288,16 @@
                 glyphRange.fStart = glyphIndex;
                 offset.fWidth += run.calculateWidth(glyphRange);
             }
+
+            // The last line
+            if (run.leftToRight()) {
+                textRange.fEnd = run.fClusters[endGlyph];
+            } else {
+                textRange.fStart = run.fClusters[endGlyph];
+            }
+            glyphRange.fEnd = endGlyph;
+            SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
+            visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
         }
         offset.fHeight += line.fTextMetrics.height();
     }
diff --git a/experimental/sktext/src/TextRun.cpp b/experimental/sktext/src/TextRun.cpp
index e1b0952..8e2e450 100644
--- a/experimental/sktext/src/TextRun.cpp
+++ b/experimental/sktext/src/TextRun.cpp
@@ -34,7 +34,7 @@
     return {fGlyphs.data(), fPositions.data(), nullptr, fClusters.data(), {0.0f, 0.0f} };
 }
 
-SkScalar TextRun::calculateWidth(Range glyphRange) const {
+SkScalar TextRun::calculateWidth(GlyphRange glyphRange) const {
     SkASSERT(glyphRange.fStart <= glyphRange.fEnd && glyphRange.fEnd < fPositions.size());
     return fPositions[glyphRange.fEnd].fX - fPositions[glyphRange.fStart].fX;
 }
diff --git a/experimental/sktext/src/TextRun.h b/experimental/sktext/src/TextRun.h
index ec7a890..6bc0e12 100644
--- a/experimental/sktext/src/TextRun.h
+++ b/experimental/sktext/src/TextRun.h
@@ -18,7 +18,7 @@
   SkShaper::RunHandler::Buffer newRunBuffer();
   void commit();
 
-  SkScalar calculateWidth(Range glyphRange) const;
+  SkScalar calculateWidth(GlyphRange glyphRange) const;
 
   bool leftToRight() const { return fBidiLevel % 2 == 0; }
   uint8_t bidiLevel() const { return fBidiLevel; }
diff --git a/experimental/sktext/src/Wrapper.cpp b/experimental/sktext/src/Wrapper.cpp
index e0a4f31..13e4acc 100644
--- a/experimental/sktext/src/Wrapper.cpp
+++ b/experimental/sktext/src/Wrapper.cpp
@@ -11,7 +11,7 @@
     return this->breakTextIntoLines(this->fWidth);
 }
 
-Range Wrapper::glyphRange(const TextRun* run, const Range& textRange) {
+GlyphRange Wrapper::glyphRange(const TextRun* run, const TextRange& textRange) {
     Range glyphRange = EMPTY_RANGE;
     for (size_t i = 0; i < run->fClusters.size(); ++i) {
         auto cluster = run->fClusters[i];
@@ -26,7 +26,7 @@
     return glyphRange;
 }
 
-Range Wrapper::textRange(const TextRun* run, const Range& glyphRange) {
+TextRange Wrapper::textRange(const TextRun* run, const GlyphRange& glyphRange) {
     Range textRange = EMPTY_RANGE;
     for (size_t i = 0; i < run->fClusters.size(); ++i) {
         auto cluster = run->fClusters[i];
@@ -54,7 +54,7 @@
         TextMetrics runMetrics(run.fFont);
         Stretch cluster;
         if (!run.leftToRight()) {
-            cluster.fTextRange = { run.fUtf8Range.end(), run.fUtf8Range.end()};
+            cluster.setTextRange({ run.fUtf8Range.end(), run.fUtf8Range.end()});
         }
 
         for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
@@ -63,66 +63,77 @@
             if (cluster.isEmpty()) {
                 cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
                 continue;
-            /*
-            } else if (cluster.fTextRange.fStart == textIndex) {
-                break;
-            } else if (cluster.fTextRange.fEnd == textIndex) {
-                break;
-            */
           }
 
           // The entire cluster belongs to a single run
-          SkASSERT(cluster.fGlyphStart.fRunIndex == runIndex);
+          SkASSERT(cluster.glyphStart().runIndex() == runIndex);
 
-          auto clusterWidth = run.fPositions[glyphIndex].fX - run.fPositions[cluster.fGlyphStart.fGlyphIndex].fX;
+          auto clusterWidth = run.fPositions[glyphIndex].fX - run.fPositions[cluster.glyphStartIndex()].fX;
           cluster.finish(glyphIndex, textIndex, clusterWidth);
 
-          auto isHardLineBreak = fProcessor->isHardLineBreak(cluster.fTextRange.fStart);
-          auto isSoftLineBreak = fProcessor->isSoftLineBreak(cluster.fTextRange.fStart);
-          auto isWhitespaces = fProcessor->isWhitespaces(cluster.fTextRange);
+          auto isHardLineBreak = fProcessor->isHardLineBreak(cluster.textStart());
+          auto isSoftLineBreak = fProcessor->isSoftLineBreak(cluster.textStart());
+          auto isWhitespaces = fProcessor->isWhitespaces(cluster.textRange());
           auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf8Range.end() : textIndex == run.fUtf8Range.begin();
 
-          if (isHardLineBreak || isEndOfText || isSoftLineBreak || isWhitespaces) {
+          if (isSoftLineBreak || isWhitespaces || isHardLineBreak) {
+              // This is the end of the word
               if (!clusters.isEmpty()) {
                   line.moveTo(spaces);
                   line.moveTo(clusters);
-              }
-              if (isWhitespaces) {
-                  spaces.moveTo(cluster);
-              }
-              if (isHardLineBreak) {
-                  this->addLine(line, spaces);
-                  break;
-              }
-              if (isEndOfText) {
-                  line.moveTo(cluster);
-                  if (!line.isEmpty()) {
-                      this->addLine(line, spaces);
-                  }
-                  break;
+                  spaces = clusters;
               }
           }
 
-          if (!SkScalarIsFinite(width)) {
+          // line + spaces + clusters + cluster
+          if (isWhitespaces) {
+              // Whitespaces do not extend the line width
+              spaces.moveTo(cluster);
+              clusters = cluster;
+              continue;
+          } else if (isHardLineBreak) {
+              // Hard line break ends the line but does not extend the width
+              // Same goes for the end of the text
+              this->addLine(line, spaces);
+              break;
+          } else if (!SkScalarIsFinite(width)) {
               clusters.moveTo(cluster);
-          } else if ((line.fWidth + spaces.fWidth + clusters.fWidth + cluster.fWidth) <= width) {
+              continue;
+          }
+
+          // Now let's find out if we can add the cluster to the line
+          if ((line.width() + spaces.width() + clusters.width() + cluster.width()) <= width) {
               clusters.moveTo(cluster);
           } else {
-              // Wrapping the text by whitespaces
               if (line.isEmpty()) {
-                  if (clusters.isEmpty()) {
+                  if (spaces.isEmpty() && clusters.isEmpty()) {
+                      // There is only this cluster and it's too long;
+                      // we are drawing it anyway
                       line.moveTo(cluster);
                   } else {
+                      // We break the only one word on the line by this cluster
                       line.moveTo(clusters);
                   }
+              } else {
+                  // We move clusters + cluster on the next line
+                  // TODO: Parametrise possible ways of breaking too long word
+                  //  (start it from a new line or squeeze the part of it on this line)
               }
               this->addLine(line, spaces);
               clusters.moveTo(cluster);
           }
+
           cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
         }
     }
 
+    if (!clusters.isEmpty()) {
+        line.moveTo(spaces);
+        line.moveTo(clusters);
+        spaces = clusters;
+    }
+    this->addLine(line, spaces);
+
     return true;
 }
 
diff --git a/experimental/sktext/src/Wrapper.h b/experimental/sktext/src/Wrapper.h
index 90d5ffa..c54f177 100644
--- a/experimental/sktext/src/Wrapper.h
+++ b/experimental/sktext/src/Wrapper.h
@@ -21,17 +21,18 @@
         spaces.clean();
     }
 
-    SkScalar glyphRangeWidth(const TextRun* run, const Range& glyphRange) {
+    SkScalar glyphRangeWidth(const TextRun* run, const GlyphRange& glyphRange) {
         return run->fPositions[glyphRange.fEnd].fX - run->fPositions[glyphRange.fStart].fX;
     }
 
-    static Range glyphRange(const TextRun* run, const Range& textRange);
-    static Range textRange(const TextRun* run, const Range& glyphRange);
+    static GlyphRange glyphRange(const TextRun* run, const TextRange& textRange);
+    static TextRange textRange(const TextRun* run, const GlyphRange& glyphRange);
     bool breakTextIntoLines(SkScalar width);
 
 private:
   Processor* fProcessor;
   SkScalar fWidth;
+  // TODO: Implement
   SkScalar fHeight;
 };