Revert "The very first version of a new SkText API."

This reverts commit 8a20cea604ebedb6d51472f08af425e253646674.

Reason for revert: Breaking some tests

Original change's description:
> The very first version of a new SkText API.
>
> SkText public API added.
> Interface.h updated.
>
> Change-Id: I82a87f33e6cf1394fa2520387f6895d33601376e
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/442003
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Commit-Queue: Julia Lavrova <jlavrova@google.com>

Change-Id: I879b198bc6fb44c46d11d967118f8d69eb74fee0
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/449296
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/experimental/sktext/BUILD.gn b/experimental/sktext/BUILD.gn
index 9be7930..d07a2b81 100644
--- a/experimental/sktext/BUILD.gn
+++ b/experimental/sktext/BUILD.gn
@@ -47,12 +47,7 @@
     source_set("tests") {
       if (text_tests_enabled) {
         testonly = true
-        sources = [
-          "tests/SelectableText.cpp",
-          "tests/ShapedText.cpp",
-          "tests/UnicodeText.cpp",
-          "tests/WrappedText.cpp",
-        ]
+        sources = []
         deps = [
           ":sktext",
           "../..:gpu_tool_utils",
diff --git a/experimental/sktext/editor/Editor.cpp b/experimental/sktext/editor/Editor.cpp
index 4003bab..e53525b 100644
--- a/experimental/sktext/editor/Editor.cpp
+++ b/experimental/sktext/editor/Editor.cpp
@@ -47,19 +47,19 @@
     // In order to get that position we look for a position outside of the text
     // and that will give us the last glyph on the line
     auto endOfText = fEditableText->lastElement(fDefaultPositionType);
-    //fEditableText->recalculateBoundaries(endOfText);
+    fEditableText->recalculateBoundaries(endOfText);
     fCursor->place(endOfText.fBoundaries);
 }
 
 void Editor::update() {
 
-    if (fEditableText->isValid()) {
+    if (!fEditableText->isInvalidated()) {
         return;
     }
 
     // Update the (shift it to point at the grapheme edge)
     auto position = fEditableText->adjustedPosition(fDefaultPositionType, fCursor->getCenterPosition());
-    //fEditableText->recalculateBoundaries(position);
+    fEditableText->recalculateBoundaries(position);
     fCursor->place(position.fBoundaries);
 
     // TODO: Update the mouse
@@ -71,7 +71,9 @@
 bool Editor::moveCursor(skui::Key key) {
     auto cursorPosition = fCursor->getCenterPosition();
     auto position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
-
+    if (position.fRun == nullptr) {
+        return false;
+    }
     if (key == skui::Key::kLeft) {
         position = fEditableText->previousElement(position);
     } else if (key == skui::Key::kRight) {
@@ -85,21 +87,19 @@
         if (position.fLineIndex == 0) {
             return false;
         }
-        auto prevLine = fEditableText->getLine(position.fLineIndex - 1);
-        cursorPosition.offset(0, - prevLine.fBounds.height());
+        cursorPosition.offset(0, - fEditableText->lineHeight(position.fLineIndex));
         position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
     } else if (key == skui::Key::kDown) {
         // Move one line down (if possible)
         if (position.fLineIndex == fEditableText->lineCount() - 1) {
             return false;
         }
-        auto nextLine = fEditableText->getLine(position.fLineIndex + 1);
-        cursorPosition.offset(0, nextLine.fBounds.height());
+        cursorPosition.offset(0, fEditableText->lineHeight(position.fLineIndex));
         position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
      }
 
     // Place the cursor at the new position
-    //fEditableText->recalculateBoundaries(position);
+    fEditableText->recalculateBoundaries(position);
     fCursor->place(position.fBoundaries);
     this->invalidate();
 
@@ -157,18 +157,28 @@
     // IMPORTANT: We assume that a single element (grapheme cluster) does not cross the run boundaries;
     // It's not exactly true but we are going to enforce in by breaking the grapheme by the run boundaries
     if (key == skui::Key::kBack) {
-        // TODO: Make sure previous element moves smoothly over the line break
-        position = fEditableText->previousElement(position);
-        textRange = position.fTextRange;
-        fCursor->place(position.fBoundaries);
-    } else {
-        // The cursor stays the the same place
+        // Move to the previous element (could be another run and/or line)
+        if (position.fLineIndex > 0 && fEditableText->isFirstOnTheLine(position)) {
+            auto line = fEditableText->line(position.fLineIndex - 1);
+            if (line->isHardLineBreak()) {
+                // We remove invisible hard line break from the previous line
+                textRange = line->whitespaces();
+            } else {
+                // We remove the last non-whitespace element on the previous line (ignoring trailing whitespaces)
+                position = fEditableText->previousElement(position);
+                textRange = position.fTextRange;
+            }
+        } else {
+            // We remove the previous element on the current line
+            position = fEditableText->previousElement(position);
+            textRange = position.fTextRange;
+        }
     }
 
     fEditableText->removeElement(textRange);
 
     // Find the grapheme the cursor points to
-    position = fEditableText->adjustedPosition(fDefaultPositionType, SkPoint::Make(position.fBoundaries.fLeft, position.fBoundaries.fTop));
+    position = fEditableText->adjustedPosition(fDefaultPositionType, textRange.fStart);
     fCursor->place(position.fBoundaries);
     this->invalidate();
 
@@ -192,7 +202,7 @@
 
     // Move the cursor to the next element
     position = fEditableText->nextElement(position);
-    //fEditableText->recalculateBoundaries(position);
+    fEditableText->recalculateBoundaries(position);
     fCursor->place(position.fBoundaries);
 
     this->invalidate();
diff --git a/experimental/sktext/editor/Texts.cpp b/experimental/sktext/editor/Texts.cpp
index 638c247..8a48a47 100644
--- a/experimental/sktext/editor/Texts.cpp
+++ b/experimental/sktext/editor/Texts.cpp
@@ -7,43 +7,20 @@
 namespace editor {
 
 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()));
-    }
 
-    auto foregroundPaint = fDecorations[0].foregroundPaint;
-    auto textBlobs = fDrawableText->getTextBlobs();
-    for (auto& textBLob : textBlobs) {
-        canvas->drawTextBlob(textBLob, 0, 0, foregroundPaint);
-    }
-}
-
-std::vector<TextIndex> DynamicText::getDecorationChunks(SkSpan<DecoratedBlock> decorations) const {
-    std::vector<TextIndex> result;
-    TextIndex textIndex = 0;
-    for (auto& decoration : decorations) {
-        textIndex += decoration.charCount;
-        result.emplace_back(textIndex);
-    }
-    return result;
+    Paint painter;
+    painter.paint(canvas, this->fOffset, this->fFormattedText.get(), this->fDecorations);
 }
 
 void EditableText::paint(SkCanvas* canvas) {
 
+    Paint painter;
+
     if (fSelection->isEmpty()) {
-        DynamicText::paint(canvas);
+        painter.paint(canvas, this->fOffset, this->fFormattedText.get(), fDecorations);
     } 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()));
-    }
-    auto foregroundPaint = fDecorations[0].foregroundPaint;
-    auto textBlobs = fDrawableText->getTextBlobs();
-    for (auto& textBLob : textBlobs) {
-        canvas->drawTextBlob(textBLob, 0, 0, foregroundPaint);
+        painter.paint(canvas, this->fOffset, this->fFormattedText.get(), SkSpan<DecoratedBlock>(decorations.data(), decorations.size()));
     }
 }
 
@@ -74,7 +51,7 @@
         }
         SkASSERT(decorPos == selected.fStart);
 
-        // So the next decoration intersects the selection (and the selection wins)
+        // So the next decoration intesects the selection (and the selection wins)
         merged.emplace_back(selected.width(), fSelection->fForeground, fSelection->fBackground);
         decorPos += selected.width();
         SkASSERT(decorPos == selected.fEnd);
diff --git a/experimental/sktext/editor/Texts.h b/experimental/sktext/editor/Texts.h
index a84bf20..e6482c8 100644
--- a/experimental/sktext/editor/Texts.h
+++ b/experimental/sktext/editor/Texts.h
@@ -8,6 +8,7 @@
 #include "experimental/sktext/editor/Selection.h"
 #include "experimental/sktext/include/Text.h"
 #include "experimental/sktext/include/Types.h"
+#include "experimental/sktext/src/Paint.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkSurface.h"
 #include "include/core/SkTime.h"
@@ -29,12 +30,10 @@
         fSize = size;
         fFontBlocks = fontBlocks;
         fText = std::move(text);
-
-        auto unicode = SkUnicode::Make();
-        fUnicodeText = std::make_unique<UnicodeText>(std::move(unicode), SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
+        fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
         fShapedText = fUnicodeText->shape(fontBlocks, DEFAULT_TEXT_DIRECTION);
-        fWrappedText = fShapedText->wrap(fUnicodeText.get(), size.width(), size.height());
-        fWrappedText->format(DEFAULT_TEXT_ALIGN, DEFAULT_TEXT_DIRECTION);
+        fWrappedText = fShapedText->wrap(size.width(), size.height(), fUnicodeText->getUnicode());
+        fFormattedText = fWrappedText->format(DEFAULT_TEXT_ALIGN, DEFAULT_TEXT_DIRECTION);
     }
 
     virtual ~StaticText() = default;
@@ -45,6 +44,7 @@
     std::unique_ptr<UnicodeText> fUnicodeText;
     std::unique_ptr<ShapedText> fShapedText;
     std::unique_ptr<WrappedText> fWrappedText;
+    sk_sp<FormattedText> fFormattedText;
     SkSize fSize;
     SkPoint fOffset;
     SkSpan<FontBlock> fFontBlocks;
@@ -65,12 +65,11 @@
         fTextAlign = textAlign;
         fTextDirection = textDirection;
         fText = std::move(text);
-
-        auto unicode = SkUnicode::Make();
-        fUnicodeText = std::make_unique<UnicodeText>(std::move(unicode), SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
+        fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
         fShapedText = fUnicodeText->shape(fontBlocks, fTextDirection);
-        fWrappedText = fShapedText->wrap(fUnicodeText.get(), size.width(), size.height());
-        fWrappedText->format(fTextAlign, fTextDirection);
+        fWrappedText = fShapedText->wrap(size.width(), size.height(), fUnicodeText->getUnicode());
+        fFormattedText = fWrappedText->format(fTextAlign, fTextDirection);
+        fInvalidated = false;
     }
 
     virtual ~DynamicText() = default;
@@ -79,10 +78,8 @@
         return SkRect::MakeXYWH(fOffset.fX, fOffset.fY, fRequiredSize.fWidth, fRequiredSize.fHeight).contains(x, y);
     }
 
-    void invalidate() { fDrawableText = nullptr; }
-    bool isValid() { return fDrawableText != nullptr; }
-
-    std::vector<TextIndex> getDecorationChunks(SkSpan<DecoratedBlock> decorations) const;
+    void invalidate() { fInvalidated = true; }
+    bool isInvalidated() { return fInvalidated; }
 
     bool rebuild(std::u16string text) {
         if (!this->fFontBlocks.empty()) {
@@ -90,24 +87,18 @@
             this->fFontBlocks[0].charCount = text.size();
         }
         this->fText = std::move(text);
-
-        auto unicode = SkUnicode::Make();
-        fUnicodeText = std::make_unique<UnicodeText>(std::move(unicode), SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
-        fShapedText = fUnicodeText->shape(this->fFontBlocks, fTextDirection);
-        fWrappedText = fShapedText->wrap(fUnicodeText.get(), this->fRequiredSize.fWidth, this->fRequiredSize.fHeight);
-        fWrappedText->format(fTextAlign, fTextDirection);
-        fSelectableText = fWrappedText->prepareToEdit(fUnicodeText.get());
-        fDrawableText = nullptr;
-        fActualSize = fWrappedText->actualSize();
+        this->fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)this->fText.data(), this->fText.size()));
+        this->fShapedText = fUnicodeText->shape(this->fFontBlocks, fTextDirection);
+        this->fWrappedText = fShapedText->wrap(this->fRequiredSize.fWidth, this->fRequiredSize.fHeight, this->fUnicodeText->getUnicode());
+        this->fFormattedText = fWrappedText->format(fTextAlign, fTextDirection);
+        this->fActualSize = fFormattedText->actualSize();
+        this->fInvalidated = false;
 
         return true;
     }
 
-    size_t lineCount() const { return fSelectableText->countLines(); }
-    BoxLine getLine(size_t lineIndex) {
-        SkASSERT(lineIndex < fSelectableText->countLines());
-        return fSelectableText->getLine(lineIndex);
-    }
+    SkScalar lineHeight(size_t index) const { return fFormattedText->line(index)->height(); }
+    size_t lineCount() const { return fFormattedText->countLines(); }
 
     SkRect actualSize() const {
         return SkRect::MakeXYWH(fOffset.fX, fOffset.fY, fActualSize.fWidth, fActualSize.fHeight);
@@ -120,8 +111,7 @@
     std::unique_ptr<UnicodeText> fUnicodeText;
     std::unique_ptr<ShapedText> fShapedText;
     std::unique_ptr<WrappedText> fWrappedText;
-    std::unique_ptr<DrawableText> fDrawableText;
-    std::unique_ptr<SelectableText> fSelectableText;
+    sk_sp<FormattedText> fFormattedText;
     SkSize fRequiredSize;
     SkSize fActualSize;
     SkPoint fOffset;
@@ -129,6 +119,7 @@
     SkSpan<DecoratedBlock> fDecorations;
     TextAlign fTextAlign;
     TextDirection fTextDirection;
+    bool fInvalidated;
 };
 
 // Text can change;  supports select/copy/paste
@@ -142,20 +133,26 @@
     bool isEmpty() { return fText.empty(); }
 
     Position adjustedPosition(PositionType positionType, SkPoint point) const {
-        return fSelectableText->adjustedPosition(positionType, point - fOffset);
+        return fFormattedText->adjustedPosition(positionType, point - fOffset);
     }
 
-    //Position adjustedPosition(PositionType positionType, TextIndex textIndex) const {
-    //    return fSelectableText->adjustedPosition(positionType, textIndex);
-    //}
+    Position adjustedPosition(PositionType positionType, TextIndex textIndex) const {
+        return fFormattedText->adjustedPosition(positionType, textIndex);
+    }
 
-    Position previousElement(Position element) const { return fSelectableText->previousPosition(element); }
-    Position nextElement(Position current) const { return fSelectableText->nextPosition(current); }
-    Position firstElement(PositionType positionType) const { return fSelectableText->firstPosition(positionType); }
-    Position lastElement(PositionType positionType) const { return fSelectableText->lastPosition(positionType); }
+    bool recalculateBoundaries(Position& position) const {
+        return fFormattedText->recalculateBoundaries(position);
+    }
 
-    bool isFirstOnTheLine(Position element) const { return fSelectableText->isFirstOnTheLine(element); }
-    bool isLastOnTheLine(Position element) const { return fSelectableText->isLastOnTheLine(element); }
+    const Line* line(size_t index) const { return fFormattedText->line(index); }
+
+    Position previousElement(Position element) const { return fFormattedText->previousElement(element); }
+    Position nextElement(Position current) const { return fFormattedText->nextElement(current); }
+    Position firstElement(PositionType positionType) const { return fFormattedText->firstElement(positionType); }
+    Position lastElement(PositionType positionType) const { return fFormattedText->lastElement(positionType); }
+
+    bool isFirstOnTheLine(Position element) const { return fFormattedText->isFirstOnTheLine(element); }
+    bool isLastOnTheLine(Position element) const { return fFormattedText->isLastOnTheLine(element); }
 
     void removeElement(TextRange toRemove) {
         std::u16string text;
diff --git a/experimental/sktext/include/Interface.h b/experimental/sktext/include/Interface.h
deleted file mode 100644
index 610ef94..0000000
--- a/experimental/sktext/include/Interface.h
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2021 Google LLC.
-#ifndef Interface_DEFINED
-#define Interface_DEFINED
-#include <string>
-#include "experimental/sktext/include/Types.h"
-#include "experimental/sktext/src/Line.h"
-#include "include/core/SkCanvas.h"
-#include "include/core/SkFontMgr.h"
-#include "include/core/SkFontStyle.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkSize.h"
-#include "include/core/SkString.h"
-#include "include/core/SkTextBlob.h"
-#include "modules/skshaper/include/SkShaper.h"
-#include "modules/skunicode/include/SkUnicode.h"
-
-using namespace skia::text;
-namespace skia {
-namespace API {
-/**
- * This class contains all the SKUnicode/ICU information.
- */
-class UnicodeText {
-public:
-    /** Makes calls to SkShaper and collects all the shaped data.
-        @param blocks         a range of FontBlock elements that keep information about
-                              fonts required to shape the text.
-                              It's utf16 range but internally it will have to be converted
-                              to utf8 (since all shaping operations use utf8 encoding)
-        @param textDirection  a starting text direction value
-        @return               an object that contains the result of shaping operations
-    */
-    std::unique_ptr<ShapedText> shape(SkSpan<FontBlock> blocks, TextDirection textDirection);
-
-    UnicodeText(std::unique_ptr<SkUnicode> unicode, SkSpan<uint16_t> utf16);
-    UnicodeText(std::unique_ptr<SkUnicode> unicode, const SkString& utf8);
-
-    bool hasProperty(TextIndex index, CodeUnitFlags flag) const
-    bool isHardLineBreak(TextIndex index) const ;
-    bool isSoftLineBreak(TextIndex index) const;
-    bool isWhitespaces(TextRange range) const;
-
-    SkUnicode* getUnicode() const;
-    SkSpan<const char16_t> getText16() const;
-};
-
-class WrappedText;
-/**
- * This class provides all the information from SkShaper/harfbuzz in a raw format.
- * It does require a single existing font for each codepoint.
- */
- // Question: do we provide a visitor for ShapedText?
-class ShapedText {
-public:
-    /** Break text by lines with a given width (and possible new lines).
-        @param unicodeText    a reference to UnicodeText that is used to query Unicode information
-        @param width          a line width at which the text gets wrapped
-        @param height         a text height, currently not supported
-        @return               an object that contains the result of shaping operations (wrapping and formatting).
-    */
-    std::unique_ptr<WrappedText> wrap(UnicodeText* unicodeText, float width, float height);
-    SkSpan<const LogicalRun> getLogicalRuns() const;
-};
-
-/**
- * This is a helper visitor class that allows a user to process the wrapped text
- * structures: lines and runs (to draw them, for instance)
- */
-class Visitor {
-public:
-    virtual ~Visitor() = default;
-    virtual void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) { }
-    virtual void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) { }
-    virtual void onGlyphRun(const SkFont& font,
-                            TextRange textRange,        // Currently we make sure that the run edges are the grapheme cluster edges
-                            SkRect bounds,              // bounds contains the physical boundaries of the run
-                            int trailingSpaces,         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-                            int glyphCount,             // Just the number of glyphs
-                            const uint16_t glyphs[],
-                            const SkPoint positions[],        // Positions relative to the line
-                            const TextIndex clusters[])       // Text indices inside the entire text
-    { }
-    virtual void onPlaceholder(TextRange, const SkRect& bounds) { }
-};
-
-class DrawableText;
-class SelectableText;
-/**
- * This class provides all the information about wrapped/formatted text.
- */
-class WrappedText {
-public:
-    /** Builds a list of SkTextBlobs to draw on a canvas.
-        @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-        @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;
-    /** 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.
-        @return               an object that contains all the data for navigation
-    */
-    std::unique_ptr<SelectableText> prepareToEdit(UnicodeText* unicodeText) const;
-    /** Formats a text line by line.
-        @param textAlign     specifies a text placement on the line:
-                             left, right, center and justified (last one currently not supported)
-        @param textDirection specifies a text direction that also used in formatting
-    */
-    void format(TextAlign textAlign, TextDirection textDirection);
-    /** Breaks the text runs into smaller runs by given list of chunks to be used for styling.
-        @param unicodeText    a reference to UnicodeText object
-        @param chunks         a range of text indices that cause an additional run breaking to be used for styling
-    */
-    void decorate(UnicodeText* unicodeText, SkSpan<TextIndex> chunks);
-
-    /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
-        @param visitor      a reference to Visitor object
-    */
-    void visit(Visitor* visitor) const;
-    /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
-        @param unicodeText  a reference to UnicodeText object
-        @param visitor      a reference to Visitor object
-        @param positionType specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-                            to map text blocks to glyph ranges.
-        @param blocks       a range of text indices that cause an additional run breaking to be used for styling
-    */
-    void visit(UnicodeText* unicodeText, Visitor* visitor, PositionType positionType, SkSpan<TextIndex> blocks) const;
-};
-
-/** This class contains all the data that allows easily paint the text on canvas.
-    Strictly speaking, it is not an important part of SkText API but
-    it presents a good example of SkText usages and simplifies testing.
-*/
-class DrawableText : public Visitor {
-public:
-    std::vector<sk_sp<SkTextBlob>>& getTextBlobs();
-};
-
-struct Position {
-    Position(PositionType positionType, size_t lineIndex, GlyphRange glyphRange, TextRange textRange, SkRect rect);
-    Position(PositionType positionType);
-    PositionType fPositionType;
-    size_t fLineIndex;
-    GlyphRange fGlyphRange;
-    TextRange fTextRange;
-    SkRect fBoundaries;
-};
-struct BoxLine {
-    BoxLine(size_t index, TextRange text, bool hardBreak, SkRect bounds);
-    SkTArray<SkRect, true> fBoxGlyphs;
-    SkTArray<TextIndex, true> fTextByGlyph; // by glyph cluster
-    GlyphIndex fTextEnd;
-    GlyphIndex fTrailingSpacesEnd;
-    TextRange fTextRange;
-    size_t fIndex;
-    bool fIsHardBreak;
-    SkRect fBounds;
-};
-
-/** This class contains all the data that allows all navigation operations on the text:
-    move up/down/left/right, select some units of text and such.
-*/
-class SelectableText : public Visitor {
-public:
-    SelectableText() = default;
-
-    /** Find the drawable unit (specified by positionType) closest to the screen point
-        @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-        @param point          a physical coordinates on a screen to find the closest glyph
-        @return               a position object that contains all required information
-    */
-    Position adjustedPosition(PositionType positionType, SkPoint point) const;
-
-    Position previousPosition(Position current) const;
-    Position nextPosition(Position current) const;
-    Position firstPosition(PositionType positionType) const;
-    Position lastPosition(PositionType positionType) const;
-    Position firstInLinePosition(PositionType positionType, LineIndex lineIndex) const;
-    Position lastInLinePosition(PositionType positionType, LineIndex lineIndex) const;
-
-    bool isFirstOnTheLine(Position element) const;
-    bool isLastOnTheLine(Position element) const;
-
-    size_t countLines() const;
-    BoxLine getLine(size_t lineIndex) const;
-};
-}  // namespace text
-}  // namespace skia
-
-#endif  // Processor_DEFINED
diff --git a/experimental/sktext/include/SkText.md b/experimental/sktext/include/SkText.md
deleted file mode 100644
index 1a4ea57..0000000
--- a/experimental/sktext/include/SkText.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-
-title: "SkText public API"
-linkTitle: "SkText public API"
-
----
-
-The main idea behind SkText is to design a flexible API that allows a user from different platforms to shape, manipulate and draw text.
-
-As text formatting works in stages, SkText presents a set of text objects that keep data from each stage. That staging approach allows a user to stop at any stage and drop all the data from the previous stages.
-Currently, we support the following stages:
-
-* <u>Parsing the text</u> to extract all the unicode information that may be needed later: graphemes, words, hard/soft line breaks, whitespaces, bidi regions and so on.
-Class <b>UnicodeText</b> contains the initial text in utf16 format, and the mapping of unicode information to codepoints in a form of enum bit flags.
-It also has a method <b>shape</b> that creates a ShapedText object.
-* <u>Shaping the text</u> into a single line of glyphs according to all given formatting information (fonts, text direction, scripts, languages and so on).
-Class <b>ShapedText</b> contains the results in the form of a list of shaped blocks with all the information that comes from shaping: glyph ids, positioning, and mapping to the initial text and so on.
-It also has a method <b>wrap</b> that creates a WrappedText object.
-* <u>Wrapping the text</u> into a list of lines (by a given width) and formatting it on the lines (left, right, center alignment or justification).
-Class <b>WrappedText</b> contains the shaped results from the previous stage only broken by lines and repositioned by lines.
-It also has a method <b>prepareToDraw</b> that creates a DrawableText object, and a method <b>prepareToNavigate</b> that creates a SelectableText object.
-* <u>Drawing the text</u> into a canvas. This is more of an example of how to draw the text because it only covers a rather simple case of drawing (limited decorating supported). Based on this example a user can create a custom drawing class as complex as needed.
-Class <b>DrawableText</b> contains a list of SkTextBlob objects ready to be drawn on a canvas.
-* <u>Navigating the text</u> by grapheme cluster (later, grapheme, glyph cluster or glyph). It has all the functionality for text editing.
-Class <b>SelectableText</b> contains all the methods for that: hit test, move position (left, right, up, down, to the beginning of the text or the line, to the end of the text or the line), select an arbitrary text (aligned to a grapheme cluster) and so on.
-
-All the objects described above can exist independently of each other, so a user can decide which ones to keep.
-
-Let’s consider few scenarios (flows):
-
-1. A user only needs to get the unicode information.
-<br>Strictly speaking, it’s not a text shaping operation but UnicodeText would allow it.
-1. A user needs to draw the text.
-<br>That requires performing the first 4 stages, but a user only needs to hold on to the DrawableText object afterwards (removing UnicodeText, ShapedText and WrappedText objects).
-1. A user needs to draw and edit the text.
-<br>That requires performing all the 5 stages. A user will have to hold on to DrawableText and SelectableText (removing the first 3 objects).
-1. A user only needs to be wrapped text, implementing a custom drawing procedure.
-<br>That requires performing the first 3 stages. A user will have to hold on to WrappedText (removing the first 2 objects).
-
-At the moment there is no support for updating the text (which theoretically could be a less expensive operation). Any changes in the initial text will require the full set of operations.
diff --git a/experimental/sktext/include/Text.h b/experimental/sktext/include/Text.h
index 507d567..27b35e7 100644
--- a/experimental/sktext/include/Text.h
+++ b/experimental/sktext/include/Text.h
@@ -1,11 +1,10 @@
 // Copyright 2021 Google LLC.
-#ifndef Text_DEFINED
-#define Text_DEFINED
+#ifndef Processor_DEFINED
+#define Processor_DEFINED
 #include <string>
 #include "experimental/sktext/include/Types.h"
 #include "experimental/sktext/src/Line.h"
-#include "experimental/sktext/src/LogicalRun.h"
-#include "experimental/sktext/src/VisualRun.h"
+#include "experimental/sktext/src/TextRun.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkFontMgr.h"
 #include "include/core/SkFontStyle.h"
@@ -19,306 +18,278 @@
 namespace skia {
 namespace text {
 
-class ShapedText;
-
-/**
- * This class contains all the SKUnicode/ICU information.
- */
-class UnicodeText {
+class UnicodeText;
+class Text {
 public:
-    /** Makes calls to SkShaper and collects all the shaped data.
-        @param blocks         a range of FontBlock elements that keep information about
-                              fonts required to shape the text.
-                              It's utf16 range but internally it will have to be converted
-                              to utf8 (since all shaping operations use utf8 encoding)
-        @param textDirection  a starting text direction value
-        @return               an object that contains the result of shaping operations
-    */
-    std::unique_ptr<ShapedText> shape(SkSpan<FontBlock> blocks, TextDirection textDirection);
-
-    UnicodeText(std::unique_ptr<SkUnicode> unicode, SkSpan<uint16_t> utf16);
-    UnicodeText(std::unique_ptr<SkUnicode> unicode, const SkString& utf8);
-    ~UnicodeText() = default;
-
-    bool hasProperty(TextIndex index, CodeUnitFlags flag) const {
-        return (fCodeUnitProperties[index] & flag) == flag;
-    }
-    bool isHardLineBreak(TextIndex index) const {
-        return this->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore);
-    }
-    bool isSoftLineBreak(TextIndex index) const {
-        return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
-    }
-    bool isWhitespaces(TextRange range) const;
-
-    SkUnicode* getUnicode() const { return fUnicode.get(); }
-    SkSpan<const char16_t> getText16() const { return SkSpan<const char16_t>(fText16.data(), fText16.size());}
-
-private:
-    void initialize(SkSpan<uint16_t> utf16);
-
-    SkTArray<CodeUnitFlags, true> fCodeUnitProperties;
-    std::u16string fText16;
-    std::unique_ptr<SkUnicode> fUnicode;
+    static std::unique_ptr<UnicodeText> parse(SkSpan<uint16_t> utf16);
 };
 
-class WrappedText;
-/**
- * This class provides all the information from SkShaper/harfbuzz in a raw format.
- * It does require a single existing font for each codepoint.
- */
- // Question: do we provide a visitor for ShapedText?
-class ShapedText : public SkShaper::RunHandler {
+// Font iterator that finds all formatting marks
+// and breaks runs on them (so we can select and interpret them later)
+class FormattingFontIterator final : public SkShaper::FontRunIterator {
 public:
-    /** Break text by lines with a given width (and possible new lines).
-        @param unicodeText    a reference to UnicodeText that is used to query Unicode information
-        @param width          a line width at which the text gets wrapped
-        @param height         a text height, currently not supported
-        @return               an object that contains the result of shaping operations (wrapping and formatting).
-    */
-    std::unique_ptr<WrappedText> wrap(UnicodeText* unicodeText, float width, float height);
+    FormattingFontIterator(TextIndex textCount,
+                           SkSpan<FontBlock> fontBlocks,
+                           SkSpan<TextIndex> marks)
+            : fTextCount(textCount)
+            , fFontBlocks(fontBlocks)
+            , fFormattingMarks(marks)
+            , fCurrentBlock(fontBlocks.begin())
+            , fCurrentMark(marks.begin())
+            , fCurrentFontIndex(0)
+            , fCurrentFont(fCurrentBlock->createFont()) {}
 
-    ShapedText()
-    : fCurrentRun(nullptr)
-    , fParagraphTextStart(0)
-    , fRunGlyphStart(0.0f)
-    , fTextHeight(0.0f) { }
+    void consume() override {
+        SkASSERT(fCurrentBlock < fFontBlocks.end());
+        SkASSERT(fCurrentMark < fFormattingMarks.end());
 
+        if (fCurrentFontIndex <= *fCurrentMark) {
+            if (fCurrentFontIndex == *fCurrentMark) {
+                ++fCurrentMark;
+            }
+            ++fCurrentBlock;
+            if (fCurrentBlock < fFontBlocks.end()) {
+                fCurrentFontIndex += fCurrentBlock->charCount;
+                fCurrentFont = fCurrentBlock->createFont();
+            }
+        } else {
+            ++fCurrentMark;
+        }
+    }
+    size_t endOfCurrentRun() const override {
+        SkASSERT(fCurrentMark != fFormattingMarks.end() || fCurrentBlock != fFontBlocks.end());
+        if (fCurrentMark == fFormattingMarks.end()) {
+            return fCurrentFontIndex;
+        } else if (fCurrentBlock == fFontBlocks.end()) {
+            return *fCurrentMark;
+        } else {
+            return fCurrentFontIndex <= *fCurrentMark ? fCurrentFontIndex : *fCurrentMark;
+        }
+    }
+    bool atEnd() const override {
+        return (fCurrentBlock == fFontBlocks.end() || fCurrentFontIndex == fTextCount) &&
+               (fCurrentMark == fFormattingMarks.end() || *fCurrentMark == fTextCount);
+    }
+
+    const SkFont& currentFont() const override { return fCurrentFont; }
+
+private:
+    TextIndex const fTextCount;
+    SkSpan<FontBlock> fFontBlocks;
+    SkSpan<TextIndex> fFormattingMarks;
+    FontBlock* fCurrentBlock;
+    TextIndex* fCurrentMark;
+    TextIndex fCurrentFontIndex;
+    SkFont fCurrentFont;
+};
+
+class ShapedText;
+class UnicodeText : public SkShaper::RunHandler {
+public:
+    std::unique_ptr<ShapedText> shape(SkSpan<FontBlock> blocks, TextDirection textDirection);
+
+    bool hasProperty(size_t index, CodeUnitFlags flag) {
+        return (fCodeUnitProperties[index] & flag) == flag;
+    }
+    bool isHardLineBreak(size_t index) {
+        return this->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore);
+    }
+    bool isSoftLineBreak(size_t index) {
+        return index != 0 && this->hasProperty(index, CodeUnitFlags::kSoftLineBreakBefore);
+    }
+    SkUnicode* getUnicode() const { return fUnicode.get(); }
+
+private:
+    friend class Text;
+    UnicodeText() : fCurrentRun(nullptr) {}
     void beginLine() override {}
     void runInfo(const RunInfo&) override {}
     void commitRunInfo() override {}
     void commitLine() override {}
-    void commitRunBuffer(const RunInfo&) override {
-        fCurrentRun->commit();
-        fLogicalRuns.emplace_back(std::move(*fCurrentRun));
-        fRunGlyphStart += fCurrentRun->width();
-    }
+    void commitRunBuffer(const RunInfo&) override;
     Buffer runBuffer(const RunInfo& info) override {
-        fCurrentRun = std::make_unique<LogicalRun>(info, fParagraphTextStart, fRunGlyphStart);
+        fCurrentRun = std::make_unique<TextRun>(info, fParagraphTextStart, fRunGlyphStart);
         return fCurrentRun->newRunBuffer();
     }
 
-    SkSpan<const LogicalRun> getLogicalRuns() const { return SkSpan<const LogicalRun>(fLogicalRuns.begin(), fLogicalRuns.size()); }
-private:
-    friend class UnicodeText;
+    SkFont createFont(const FontBlock& fontBlock);
 
-    void addLine(WrappedText* wrappedText, SkUnicode* unicode, Stretch& stretch, Stretch& spaces, bool hardLineBreak);
-
-    SkTArray<int32_t> getVisualOrder(SkUnicode* unicode, RunIndex start, RunIndex end);
-
-    // This is all the results from shaping
-    SkTArray<LogicalRun, false> fLogicalRuns;
-
-    // Temporary values
-    std::unique_ptr<LogicalRun> fCurrentRun;
+    SkTArray<size_t, true> fUTF16FromUTF8;
+    SkTArray<size_t, true> fUTF8FromUTF16;
+    SkTArray<CodeUnitFlags, true> fCodeUnitProperties;
+    SkString fText8;
+    std::u16string fText16;
+    std::unique_ptr<SkUnicode> fUnicode;
+    std::unique_ptr<TextRun> fCurrentRun;
     TextIndex fParagraphTextStart;
     SkScalar fRunGlyphStart;
-    SkScalar fTextHeight;
+    std::unique_ptr<ShapedText> fShapedText;
 };
 
-/**
- * This is a helper visitor class that allows a user to process the wrapped text
- * structures: lines and runs (to draw them, for instance)
- */
-class Visitor {
+class WrappedText;
+class ShapedText {
 public:
-    virtual ~Visitor() = default;
-    virtual void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) { }
-    virtual void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) { }
-    virtual void onGlyphRun(const SkFont& font,
-                            TextRange textRange,        // Currently we make sure that the run edges are the grapheme cluster edges
-                            SkRect bounds,              // bounds contains the physical boundaries of the run
-                            int trailingSpaces,         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-                            int glyphCount,             // Just the number of glyphs
-                            const uint16_t glyphs[],
-                            const SkPoint positions[],        // Positions relative to the line
-                            const TextIndex clusters[])       // Text indices inside the entire text
-    { }
-    virtual void onPlaceholder(TextRange, const SkRect& bounds) { }
+    std::unique_ptr<WrappedText> wrap(float width, float height, SkUnicode* unicode);
+    bool isClusterEdge(size_t index) const {
+        return (fGlyphUnitProperties[index] & GlyphUnitFlags::kGlyphClusterStart) ==
+               GlyphUnitFlags::kGlyphClusterStart;
+    }
+    void adjustLeft(size_t* index) const {
+        SkASSERT(index != nullptr);
+        while (*index != 0) {
+            if (isClusterEdge(*index)) {
+                return;
+            }
+            --index;
+        }
+    }
+    void adjustRight(size_t* index) const {
+        SkASSERT(index != nullptr);
+        while (*index < this->fGlyphUnitProperties.size()) {
+            if (isClusterEdge(*index)) {
+                return;
+            }
+            ++index;
+        }
+    }
+    bool hasProperty(size_t index, GlyphUnitFlags flag) {
+        return (fGlyphUnitProperties[index] & flag) == flag;
+    }
+    bool isHardLineBreak(size_t index) {
+        return this->hasProperty(index, GlyphUnitFlags::kHardLineBreakBefore);
+    }
+    bool isSoftLineBreak(size_t index) {
+        return index != 0 && this->hasProperty(index, GlyphUnitFlags::kSoftLineBreakBefore);
+    }
+    bool isWhitespaces(TextRange range) {
+        if (range.leftToRight()) {
+            for (auto i = range.fStart; i < range.fEnd; ++i) {
+                if (!this->hasProperty(i, GlyphUnitFlags::kPartOfWhiteSpace)) {
+                    return false;
+                }
+            }
+        } else {
+            for (auto i = range.fStart; i > range.fEnd; --i) {
+                if (!this->hasProperty(i, GlyphUnitFlags::kPartOfWhiteSpace)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+private:
+    friend class UnicodeText;
+    ShapedText() {}
+    SkTArray<TextRun, false> fRuns;
+    SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
 };
 
-class DrawableText;
-class SelectableText;
-/**
- * This class provides all the information about wrapped/formatted text.
- */
+class FormattedText;
 class WrappedText {
 public:
-    /** Builds a list of SkTextBlobs to draw on a canvas.
-        @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-        @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;
-    /** 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.
-        @return               an object that contains all the data for navigation
-    */
-    std::unique_ptr<SelectableText> prepareToEdit(UnicodeText* unicodeText) const;
-    /** Formats a text line by line.
-        @param textAlign     specifies a text placement on the line:
-                             left, right, center and justified (last one currently not supported)
-        @param textDirection specifies a text direction that also used in formatting
-    */
-    void format(TextAlign textAlign, TextDirection textDirection);
-    /** Breaks the text runs into smaller runs by given list of chunks to be used for styling.
-        @param unicodeText    a reference to UnicodeText object
-        @param chunks         a range of text indices that cause an additional run breaking to be used for styling
-    */
-    void decorate(UnicodeText* unicodeText, SkSpan<TextIndex> chunks);
-
+    sk_sp<FormattedText> format(TextAlign textAlign, TextDirection textDirection);
     SkSize actualSize() const { return fActualSize; }
-    size_t countLines() const { return fVisualLines.size(); }
-
-    /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
-        @param visitor      a reference to Visitor object
-    */
-    void visit(Visitor* visitor) const;
-    /** Walks though the data structures and makes certain callbacks on visitor so the visitor can collect all the information.
-        @param unicodeText  a reference to UnicodeText object
-        @param visitor      a reference to Visitor object
-        @param positionType specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-                            to map text blocks to glyph ranges.
-        @param blocks       a range of text indices that cause an additional run breaking to be used for styling
-    */
-    void visit(UnicodeText* unicodeText, Visitor* visitor, PositionType positionType, SkSpan<TextIndex> blocks) const;
+    size_t countLines() const { return fLines.size(); }
 
 private:
     friend class ShapedText;
-    WrappedText() : fActualSize(SkSize::MakeEmpty()), fAligned(TextAlign::kNothing) { }
-    GlyphRange textToGlyphs(UnicodeText* unicodeText, PositionType positionType, RunIndex runIndex, TextRange textRange) const;
-    SkTArray<VisualRun, true> fVisualRuns;    // Broken by lines
-    SkTArray<VisualLine, false> fVisualLines;
+    WrappedText() : fActualSize(SkSize::MakeEmpty()) {}
+    void addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode, bool hardLineBreak);
+    SkTArray<TextRun, false> fRuns;
+    SkTArray<Line, false> fLines;
+    SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
     SkSize fActualSize;
-    TextAlign fAligned;
 };
 
-/** This class contains all the data that allows easily paint the text on canvas.
-    Strictly speaking, it is not an important part of SkText API but
-    it presents a good example of SkText usages and simplifies testing.
-*/
-class DrawableText : public Visitor {
+class FormattedText : public SkRefCnt {
 public:
-    DrawableText() = default;
+    SkSize actualSize() const { return fActualSize; }
 
-    void onGlyphRun(const SkFont& font,
-                            TextRange textRange,
-                            SkRect bounds,
-                            int trailingSpaces,
-                            int glyphCount,
-                            const uint16_t glyphs[],
-                            const SkPoint positions[],
-                            const TextIndex clusters[]) override {
-        SkTextBlobBuilder builder;
-        const auto& blobBuffer = builder.allocRunPos(font, SkToInt(glyphCount));
-        sk_careful_memcpy(blobBuffer.glyphs, glyphs, glyphCount * sizeof(uint16_t));
-        sk_careful_memcpy(blobBuffer.points(), positions, glyphCount * sizeof(SkPoint));
-        fTextBlobs.emplace_back(builder.make());
-    }
-    std::vector<sk_sp<SkTextBlob>>& getTextBlobs() { return fTextBlobs; }
-private:
-    std::vector<sk_sp<SkTextBlob>> fTextBlobs;
-};
-
-struct Position {
-    Position(PositionType positionType, size_t lineIndex, GlyphRange glyphRange, TextRange textRange, SkRect rect)
-        : fPositionType(positionType)
-        , fLineIndex(lineIndex)
-        , fGlyphRange(glyphRange)
-        , fTextRange(textRange)
-        , fBoundaries(rect) { }
-
-    Position(PositionType positionType)
-        : Position(positionType, EMPTY_INDEX, EMPTY_RANGE, EMPTY_RANGE, SkRect::MakeEmpty()) { }
-
-    PositionType fPositionType;
-    size_t fLineIndex;
-    GlyphRange fGlyphRange;
-    TextRange fTextRange;
-    SkRect fBoundaries;
-};
-struct BoxLine {
-    BoxLine(size_t index, TextRange text, bool hardBreak, SkRect bounds)
-        : fTextRange(text), fIndex(index), fIsHardBreak(hardBreak), fBounds(bounds) { }
-    SkTArray<SkRect, true> fBoxGlyphs;
-    SkTArray<TextIndex, true> fTextByGlyph; // by glyph cluster
-    //SkTArray<GlyphRange, true> fRuns; // by glyph cluster
-    GlyphIndex fTextEnd;
-    GlyphIndex fTrailingSpacesEnd;
-    TextRange fTextRange;
-    size_t fIndex;
-    bool fIsHardBreak;
-    SkRect fBounds;
-};
-
-/** This class contains all the data that allows all navigation operations on the text:
-    move up/down/left/right, select some units of text and such.
-*/
-class SelectableText : public Visitor {
-public:
-    SelectableText() = default;
-
-    /** Find the drawable unit (specified by positionType) closest to the screen point
-        @param positionType   specifies a text adjustment granularity (grapheme cluster, grapheme, glypheme, glyph)
-        @param point          a physical coordinates on a screen to find the closest glyph
-        @return               a position object that contains all required information
-    */
+    // Operations starting from mouse/cursor require repositioning and adjusting by the screen
     Position adjustedPosition(PositionType positionType, SkPoint point) const;
 
-    Position previousPosition(Position current) const;
-    Position nextPosition(Position current) const;
-    Position upPosition(Position current) const;
-    Position downPosition(Position current) const;
-    Position firstPosition(PositionType positionType) const;
-    Position lastPosition(PositionType positionType) const;
-    Position firstInLinePosition(PositionType positionType, LineIndex lineIndex) const;
-    Position lastInLinePosition(PositionType positionType, LineIndex lineIndex) const;
+    // Operations of the text (adding, removing) require repositioning and adjusting by the text
+    Position adjustedPosition(PositionType positionType, TextIndex textIndex) const;
 
-    bool isFirstOnTheLine(Position element) const {
-        return (element.fGlyphRange.fStart == 0);
+    bool recalculateBoundaries(Position& position) const;
+
+    const TextRun* visuallyPreviousRun(size_t lineIndex, const TextRun* run) const;
+    const TextRun* visuallyNextRun(size_t lineIndex, const TextRun* run) const;
+    const TextRun* visuallyFirstRun(size_t lineIndex) const;
+    const TextRun* visuallyLastRun(size_t lineIndex) const;
+    bool isVisuallyFirst(size_t lineIndex, const TextRun* run) const;
+    bool isVisuallyLast(size_t lineIndex, const TextRun* run) const;
+
+    Position previousElement(Position element) const;
+    Position nextElement(Position current) const;
+    Position firstElement(PositionType positionType) const;
+    Position lastElement(PositionType positionType) const;
+
+    bool isFirstOnTheLine(Position element) const;
+    bool isLastOnTheLine(Position element) const;
+
+    size_t lineIndex(const Line* line) const { return line - fLines.data(); }
+    size_t countLines() const { return fLines.size(); }
+    const Line* line(size_t lineIndex) const {
+        return fLines.empty() ? nullptr : &fLines[lineIndex];
     }
-    bool isLastOnTheLine(Position element) const {
-        return (element.fGlyphRange.fEnd == fBoxLines.back().fBoxGlyphs.size());
+    size_t runIndex(const TextRun* run) const {
+        return run == nullptr ? EMPTY_INDEX : run - fRuns.data();
     }
 
-    size_t countLines() const { return fBoxLines.size(); }
-    BoxLine getLine(size_t lineIndex) const {
-        SkASSERT(lineIndex < fBoxLines.size());
-        return fBoxLines[lineIndex];
-    }
-
-    bool hasProperty(TextIndex index, GlyphUnitFlags flag) const {
+    bool hasProperty(size_t index, GlyphUnitFlags flag) const {
         return (fGlyphUnitProperties[index] & flag) == flag;
     }
 
-    void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) override;
-    void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) override;
+    class Visitor {
+    public:
+        virtual ~Visitor() = default;
+        virtual void onBeginLine(TextRange lineText) {}
+        virtual void onEndLine(TextRange lineText) {}
+        virtual void onGlyphRun(const SkFont& font,
+                                TextRange textRange,
+                                SkRect boundingRect,
+                                int glyphCount,
+                                const uint16_t glyphs[],
+                                const SkPoint positions[],
+                                const SkPoint offsets[]) {
+            SkTextBlobBuilder builder;
+            const auto& blobBuffer = builder.allocRunPos(font, SkToInt(glyphCount));
+            sk_careful_memcpy(blobBuffer.glyphs, glyphs, glyphCount * sizeof(uint16_t));
+            sk_careful_memcpy(blobBuffer.points(), positions, glyphCount * sizeof(SkPoint));
+            fTextBlobs.emplace_back(builder.make());
+        }
+        virtual void onPlaceholder(TextRange, const SkRect& bounds) {}
 
-    void onGlyphRun(const SkFont& font,
-                    TextRange textRange,
-                    SkRect bounds,
-                    int trailingSpaces,
-                    int glyphCount,
-                    const uint16_t glyphs[],
-                    const SkPoint positions[],
-                    const TextIndex clusters[]) override;
+        void buildTextBlobs(FormattedText* formattedText) {
+            fTextBlobs.clear();
+            formattedText->visit(this);
+        }
+        std::vector<sk_sp<SkTextBlob>>& getTextBlobs() { return fTextBlobs; }
+
+    private:
+        std::vector<sk_sp<SkTextBlob>> fTextBlobs;
+    };
+
+    // Visit runs as is by lines
+    void visit(Visitor*) const;
+    // Visit chunked runs
+    void visit(Visitor*, SkSpan<size_t> blocks) const;
 
 private:
+    void adjustTextRange(Position* position) const;
+
     friend class WrappedText;
-
-    Position findPosition(PositionType positionType, const BoxLine& line, SkScalar x) const;
-    // Just in theory a random glyph range can be represented by multiple text ranges (because of LTR/RTL)
-    // Currently we only support this method for a glyph, grapheme or grapheme cluster
-    // So it's guaranteed to be one text range
-    TextRange glyphsToText(Position position) const;
-
-    SkTArray<BoxLine, true> fBoxLines;
+    FormattedText() = default;
+    SkTArray<TextRun, false> fRuns;
+    SkTArray<Line, false> fLines;
     SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
     SkSize fActualSize;
+    std::unique_ptr<Visitor> fVisitor;
 };
 
 }  // namespace text
 }  // namespace skia
 
-#endif  // Text_DEFINED
+#endif  // Processor_DEFINED
diff --git a/experimental/sktext/include/Types.h b/experimental/sktext/include/Types.h
index 47102ce..26a409f 100644
--- a/experimental/sktext/include/Types.h
+++ b/experimental/sktext/include/Types.h
@@ -19,7 +19,6 @@
     kJustify,
     kStart,
     kEnd,
-    kNothing,
 };
 
 enum class TextDirection {
@@ -29,7 +28,6 @@
 
 // This enum lists all possible ways to query output positioning
 enum class PositionType {
-    kRandomText,
     kGraphemeCluster, // Both the edge of the glyph cluster and the text grapheme
     kGlyphCluster,
     kGlyph,
@@ -41,34 +39,26 @@
   kHardLineBreakBefore,
 };
 
-enum class LogicalRunType {
-    kText,
-    kLineBreak
-};
-
 enum class CodeUnitFlags : uint8_t {
     kNoCodeUnitFlag = (1 << 0),
     kPartOfWhiteSpace = (1 << 1),
     kGraphemeStart = (1 << 2),
     kSoftLineBreakBefore = (1 << 3),
     kHardLineBreakBefore = (1 << 4),
-    kAllCodeUnitFlags = ((1 << 5) - 1),
 };
 
 enum class GlyphUnitFlags : uint8_t {
     kNoGlyphUnitFlag = (1 << 0),
-    //kPartOfWhiteSpace = (1 << 1),
-    //kGraphemeStart = (1 << 2),
-    //kSoftLineBreakBefore = (1 << 3),
-    //kHardLineBreakBefore = (1 << 4),
+    kPartOfWhiteSpace = (1 << 1),
+    kGraphemeStart = (1 << 2),
+    kSoftLineBreakBefore = (1 << 3),
+    kHardLineBreakBefore = (1 << 4),
     kGlyphClusterStart = (1 << 5),
     kGraphemeClusterStart = (1 << 6),
 };
 
 typedef size_t TextIndex;
 typedef size_t GlyphIndex;
-typedef size_t RunIndex;
-typedef size_t LineIndex;
 const size_t EMPTY_INDEX = std::numeric_limits<size_t>::max();
 
 template <typename T>
@@ -77,22 +67,10 @@
     Range() : fStart(0), fEnd(0) { }
     Range(T start, T end) : fStart(start) , fEnd(end) { }
 
-    bool operator==(Range<T> other) {
-        return fStart == other.fStart && fEnd == other.fEnd;
-    }
-
     bool leftToRight() const {
         return fEnd >= fStart;
     }
 
-    bool before(T index) const {
-        if (leftToRight()) {
-            return index >= fEnd;
-        } else {
-            return index >= fStart;
-        }
-    }
-
     bool contains(T index) const {
         if (leftToRight()) {
             return index >= fStart && index < fEnd;
@@ -101,14 +79,6 @@
         }
     }
 
-    bool contains(Range<T> range) const {
-        if (leftToRight()) {
-            return range.fStart >= fStart && range.fEnd < fEnd;
-        } else {
-            return range.fStart < fStart && range.fEnd >= fEnd;
-        }
-    }
-
     void normalize() {
         if (!this->leftToRight()) {
             std::swap(this->fStart, this->fEnd);
@@ -206,11 +176,6 @@
         , charCount(count)
         , chain(fontChain) { }
     FontBlock() : FontBlock(0, nullptr) { }
-    FontBlock(FontBlock& block) {
-        this->type = block.type;
-        this->charCount = block.charCount;
-        this->chain = block.chain;
-    }
     ~FontBlock() { }
 
     SkFont createFont() const {
diff --git a/experimental/sktext/samples/Text.cpp b/experimental/sktext/samples/Text.cpp
index 65e7fea..ad0f8d0 100644
--- a/experimental/sktext/samples/Text.cpp
+++ b/experimental/sktext/samples/Text.cpp
@@ -153,7 +153,7 @@
             result += ch;
         }
         result += u"\u202C";
-        return SkUnicode::convertUtf16ToUtf8(result);
+        return fUnicode->convertUtf16ToUtf8(result);
     }
 
     void onDrawContent(SkCanvas* canvas) override {
diff --git a/experimental/sktext/sktext.gni b/experimental/sktext/sktext.gni
index c462588..3a7d1bc 100644
--- a/experimental/sktext/sktext.gni
+++ b/experimental/sktext/sktext.gni
@@ -11,8 +11,7 @@
 
 sktext_sources = [
   "$_src/Line.cpp",
-  "$_src/LogicalRun.cpp",
   "$_src/Paint.cpp",
   "$_src/Text.cpp",
-  "$_src/VisualRun.cpp",
+  "$_src/TextRun.cpp",
 ]
diff --git a/experimental/sktext/src/Line.cpp b/experimental/sktext/src/Line.cpp
index 51d9014..a843cc4 100644
--- a/experimental/sktext/src/Line.cpp
+++ b/experimental/sktext/src/Line.cpp
@@ -1,10 +1,11 @@
 // Copyright 2021 Google LLC.
 #include "experimental/sktext/include/Text.h"
 #include "experimental/sktext/src/Line.h"
+#include "experimental/sktext/src/TextRun.h"
 
 namespace skia {
 namespace text {
-LogicalLine::LogicalLine(const Stretch& stretch, const Stretch& spaces, SkScalar verticalOffset, bool hardLineBreak)
+Line::Line(const Stretch& stretch, const Stretch& spaces, SkSTArray<1, size_t, true> visualOrder, SkScalar verticalOffset, bool hardLineBreak)
     : fTextStart(stretch.glyphStart())
     , fTextEnd(stretch.glyphEnd())
     , fWhitespacesEnd (spaces.glyphEnd())
@@ -14,7 +15,9 @@
     , fSpacesWidth(spaces.width())
     , fHorizontalOffset(0.0f)
     , fVerticalOffset(verticalOffset)
-    , fHardLineBreak(hardLineBreak) {
+    , fHardLineBreak(hardLineBreak)
+    , fRunsInVisualOrder(std::move(visualOrder)) {
+
     SkASSERT(stretch.isEmpty() ||
                     spaces.isEmpty() ||
         (stretch.glyphEnd() == spaces.glyphStart()));
diff --git a/experimental/sktext/src/Line.h b/experimental/sktext/src/Line.h
index a1c5ce7..f92c5ce 100644
--- a/experimental/sktext/src/Line.h
+++ b/experimental/sktext/src/Line.h
@@ -82,7 +82,7 @@
 class Stretch {
 public:
 
-    Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(EMPTY_RANGE), fTextMetrics() { }
+    Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(0, 0), fTextMetrics() { }
 
     Stretch(GlyphPos glyphStart, size_t textIndex, const TextMetrics& metrics)
         : fGlyphStart(glyphStart)
@@ -97,10 +97,14 @@
     Stretch& operator=(const Stretch&) = default;
 
     bool isEmpty() const {
-        if (fGlyphStart.isEmpty() || fGlyphEnd.isEmpty()) {
+        if (this->fGlyphStart.isEmpty()) {
+            SkASSERT(this->fGlyphEnd.isEmpty());
             return true;
         } else {
-            return fGlyphStart == fGlyphEnd;
+            SkASSERT(!this->fGlyphEnd.isEmpty());
+            return false;
+            //return (this->fGlyphStart.runIndex() == this->fGlyphEnd.runIndex() &&
+            //        this->fGlyphStart.glyphIndex() == this->fGlyphEnd.glyphIndex());
         }
     }
 
@@ -162,10 +166,10 @@
     TextMetrics fTextMetrics;
 };
 
-class LogicalLine {
+class Line {
 public:
-    LogicalLine(const Stretch& stretch, const Stretch& spaces, SkScalar verticalOffset, bool hardLineBreak);
-    ~LogicalLine() = default;
+    Line(const Stretch& stretch, const Stretch& spaces, SkSTArray<1, size_t, true> visualOrder, SkScalar verticalOffset, bool hardLineBreak);
+    ~Line() = default;
 
     TextMetrics getMetrics() const { return fTextMetrics; }
     GlyphPos glyphStart() const { return fTextStart; }
@@ -175,11 +179,44 @@
     SkScalar withWithTrailingSpaces() const { return fTextWidth + fSpacesWidth; }
     SkScalar horizontalOffset() const { return fHorizontalOffset; }
     SkScalar verticalOffset() const { return fVerticalOffset; }
+    size_t runsNumber() const { return fRunsInVisualOrder.size(); }
+    size_t visualRun(size_t index) const { return fRunsInVisualOrder[index]; }
     SkScalar height() const { return fTextMetrics.height(); }
     SkScalar baseline() const { return fTextMetrics.baseline(); }
     TextRange text() const { return fText; }
     TextRange whitespaces() const { return fWhitespaces; }
     bool isHardLineBreak() const { return fHardLineBreak; }
+    GlyphRange glyphRange(size_t runIndex, size_t runSize, bool includingTrailingSpaces) const {
+
+        GlyphIndex start = runIndex != this->glyphStart().runIndex() ? 0 : this->glyphStart().glyphIndex();
+        GlyphIndex end = runIndex != this->glyphTrailingEnd().runIndex() ? runSize : this->glyphTrailingEnd().glyphIndex();
+
+        if (!includingTrailingSpaces) {
+            // It's possible that the run in question consists of trailing spaces and therefore should not be count
+            if (this->runMayHaveTrailingSpaces(runIndex)) {
+                end = this->glyphEnd().runIndex() != runIndex
+                              ? start                           // The run entirely consists of trailing spaces
+                              : this->glyphEnd().glyphIndex();  // The run has some trailing spaces
+            }
+        }
+        return GlyphRange(start, end);
+    }
+
+    bool runMayHaveTrailingSpaces(size_t runIndex) const {
+        size_t lastRunWithoutTrailingSpaces = this->glyphEnd().runIndex();
+        for (size_t v = fRunsInVisualOrder.size(); v > 0; --v) {
+            auto r = fRunsInVisualOrder[v - 1];
+            if (r == runIndex) {
+                // This run has trailing spaces (or entirely consists of them)
+                return true;
+            }
+            if (r == lastRunWithoutTrailingSpaces) {
+                return false;
+            }
+        }
+        SkASSERT(false);
+        return false;
+    }
 
 private:
     friend class WrappedText;
@@ -195,6 +232,7 @@
     SkScalar fVerticalOffset;
     TextMetrics fTextMetrics;
     bool fHardLineBreak;
+    SkSTArray<1, size_t, true> fRunsInVisualOrder;
 };
 
 } // namespace text
diff --git a/experimental/sktext/src/LogicalRun.cpp b/experimental/sktext/src/LogicalRun.cpp
deleted file mode 100644
index 6996436..0000000
--- a/experimental/sktext/src/LogicalRun.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2021 Google LLC.
-
-#include "experimental/sktext/src/LogicalRun.h"
-
-namespace skia {
-namespace text {
-
-LogicalRun::LogicalRun(const SkShaper::RunHandler::RunInfo& info, TextIndex textStart, SkScalar glyphOffset)
-    : fFont(info.fFont)
-    , fBidiLevel(info.fBidiLevel)
-    , fAdvance(info.fAdvance)
-    , fUtf8Range(info.utf8Range)
-    , fTextMetrics(info.fFont)
-    , fRunStart(textStart)
-    , fRunOffset(glyphOffset)
-    , fRunType(LogicalRunType::kText) {
-    fGlyphs.push_back_n(info.glyphCount);
-    fBounds.push_back_n(info.glyphCount);
-    fPositions.push_back_n(info.glyphCount + 1);
-    fOffsets.push_back_n(info.glyphCount);
-    fClusters.push_back_n(info.glyphCount + 1);
-}
-
-} // namespace text
-} // namespace skia
diff --git a/experimental/sktext/src/LogicalRun.h b/experimental/sktext/src/LogicalRun.h
deleted file mode 100644
index 79f2a48..0000000
--- a/experimental/sktext/src/LogicalRun.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2021 Google LLC.
-#ifndef LogicalRun_DEFINED
-#define LogicalRun_DEFINED
-
-#include "experimental/sktext/include/Types.h"
-#include "experimental/sktext/src/Line.h"
-#include "modules/skshaper/include/SkShaper.h"
-
-namespace skia {
-namespace text {
-
-class LogicalRun {
-    public:
-    LogicalRun(const SkShaper::RunHandler::RunInfo& info, TextIndex textStart, SkScalar glyphOffset);
-    SkShaper::RunHandler::Buffer newRunBuffer() {
-        return {fGlyphs.data(), fPositions.data(), fOffsets.data(), fClusters.data(), {0.0f, 0.0f} };
-    }
-    void commit() {
-        fFont.getBounds(fGlyphs.data(), fGlyphs.size(), fBounds.data(), nullptr);
-        fPositions[fGlyphs.size()] = fAdvance;
-        fClusters[fGlyphs.size()] = this->leftToRight() ? fUtf8Range.end() : fUtf8Range.begin();
-    }
-
-    TextRange getTextRange() const { return fUtf16Range; }
-
-    SkScalar calculateWidth(GlyphRange glyphRange) const {
-        SkASSERT(glyphRange.fStart <= glyphRange.fEnd && glyphRange.fEnd < fPositions.size());
-        return fPositions[glyphRange.fEnd].fX - fPositions[glyphRange.fStart].fX;
-    }
-    SkScalar calculateWidth(GlyphIndex start, GlyphIndex end) const {
-      return calculateWidth(GlyphRange(start, end));
-    }
-    SkScalar width() const { return fAdvance.fX; }
-    SkScalar firstGlyphPosition() const { return fPositions[0].fX; }
-
-    bool leftToRight() const { return fBidiLevel % 2 == 0; }
-    uint8_t bidiLevel() const { return fBidiLevel; }
-    size_t size() const { return fGlyphs.size(); }
-
-    LogicalRunType getRunType() const { return fRunType; }
-    void setRunType(LogicalRunType runType) { fRunType = runType; }
-
-    template <typename Callback>
-    void forEachCluster(Callback&& callback) {
-        GlyphIndex glyph = 0;
-        for(; glyph < fClusters.size(); ++glyph) {
-            callback(glyph, fRunStart + fClusters[glyph]);
-        }
-    }
-
-    template <typename Callback>
-    void convertUtf16Range(Callback&& callback) {
-        this->fUtf16Range.fStart = callback(this->fUtf8Range.begin());
-        this->fUtf16Range.fEnd = callback(this->fUtf8Range.end());
-    }
-
-    // Convert indexes into utf16 and also shift them to be on the entire text scale
-    template <typename Callback>
-    void convertClusterIndexes(Callback&& callback) {
-        for (size_t glyph = 0; glyph < fClusters.size(); ++glyph) {
-            fClusters[glyph] = callback(fClusters[glyph]);
-        }
-    }
-
-    private:
-    friend class ShapedText;
-    friend class WrappedText;
-    SkFont fFont;
-    TextMetrics fTextMetrics;
-
-    LogicalRunType fRunType;
-    SkVector fAdvance;
-    SkShaper::RunHandler::Range fUtf8Range;
-    TextRange fUtf16Range;
-    TextIndex fRunStart;
-    SkScalar  fRunOffset;
-    SkSTArray<128, SkGlyphID, true> fGlyphs;
-    SkSTArray<128, SkPoint, true> fPositions;
-    SkSTArray<128, SkPoint, true> fOffsets;
-    SkSTArray<128, uint32_t, true> fClusters;
-    SkSTArray<128, SkRect, true> fBounds;
-
-    uint8_t fBidiLevel;
-};
-
-} // namespace text
-} // namespace skia
-#endif
diff --git a/experimental/sktext/src/Paint.cpp b/experimental/sktext/src/Paint.cpp
index c06d3ab..2fd7e13 100644
--- a/experimental/sktext/src/Paint.cpp
+++ b/experimental/sktext/src/Paint.cpp
@@ -46,19 +46,21 @@
 
         DecoratedBlock decoratedBlock(textSize, foreground, background);
         Paint paint;
-        paint.paint(canvas, SkPoint::Make(x, y), nullptr, formattedText.get(), SkSpan<DecoratedBlock>(&decoratedBlock, 1));
+        paint.paint(canvas, SkPoint::Make(x, y), formattedText.get(), SkSpan<DecoratedBlock>(&decoratedBlock, 1));
 
         return true;
     }
 
+    void Paint::onBeginLine(TextRange lineText) { }
+    void Paint::onEndLine(TextRange lineText) { }
+    void Paint::onPlaceholder(TextRange lineText, const SkRect& bounds) { }
     void Paint::onGlyphRun(const SkFont& font,
                            TextRange textRange,
                            SkRect boundingRect,
-                           int trailingSpacesStart,
                            int glyphCount,
                            const uint16_t glyphs[],
-                           const SkPoint positions[],
-                           const TextIndex clusters[]) {
+                           const SkPoint  positions[],
+                           const SkPoint offsets[]) {
 
         DecoratedBlock decoratedBlock = findDecoratedBlock(textRange);
 
@@ -74,16 +76,15 @@
         fCanvas->drawTextBlob(blob, fXY.fX, fXY.fY, decoratedBlock.foregroundPaint);
     }
 
-    std::unique_ptr<WrappedText> Paint::layout(std::u16string text,
+    sk_sp<FormattedText> Paint::layout(std::u16string text,
                                        TextDirection textDirection, TextAlign textAlign,
                                        SkSize reqSize,
                                        SkSpan<FontBlock> fontBlocks) {
-        auto unicode = SkUnicode::Make();
-        auto unicodeText = std::make_unique<UnicodeText>(std::move(unicode), SkSpan<uint16_t>((uint16_t*)text.data(), text.size()));
+        auto unicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)text.data(), text.size()));
         auto shapedText = unicodeText->shape(fontBlocks, textDirection);
-        auto wrappedText = shapedText->wrap(unicodeText.get(), reqSize.width(), reqSize.height());
-        wrappedText->format(textAlign, textDirection);
-        return wrappedText;
+        auto wrappedText = shapedText->wrap(reqSize.width(), reqSize.height(), unicodeText->getUnicode());
+        auto formattedText = wrappedText->format(textAlign, textDirection);
+        return formattedText;
     }
 
     DecoratedBlock Paint::findDecoratedBlock(TextRange textRange) {
@@ -100,7 +101,7 @@
         return DecoratedBlock(0, SkPaint(), SkPaint());
     }
 
-    void Paint::paint(SkCanvas* canvas, SkPoint xy, UnicodeText* unicodeText, WrappedText* wrappedText, SkSpan<DecoratedBlock> decoratedBlocks) {
+    void Paint::paint(SkCanvas* canvas, SkPoint xy, FormattedText* formattedText, SkSpan<DecoratedBlock> decoratedBlocks) {
         fCanvas = canvas;
         fXY = xy;
         fDecoratedBlocks = decoratedBlocks;
@@ -113,11 +114,7 @@
             chunks[i] = index;
         }
 
-        if (chunks.size() == 1) {
-            wrappedText->visit( this);
-        } else {
-            wrappedText->visit(unicodeText, this, PositionType::kGraphemeCluster, SkSpan<size_t>(chunks.data(), chunks.size()));
-        }
+        formattedText->visit(this, SkSpan<size_t>(chunks.data(), chunks.size()));
     }
 
 } // namespace text
diff --git a/experimental/sktext/src/Paint.h b/experimental/sktext/src/Paint.h
index 201e907..ec60f97 100644
--- a/experimental/sktext/src/Paint.h
+++ b/experimental/sktext/src/Paint.h
@@ -30,8 +30,8 @@
             return fTypeface;
         }
         float size() const override { return fSize; }
+
         sk_sp<SkTypeface> getTypeface() const { return fTypeface; }
-        bool empty() const { return fTypeface == nullptr; }
 
     private:
         sk_sp<SkTypeface> fTypeface;
@@ -39,9 +39,9 @@
         SkFontStyle fFontStyle;
     };
 
-    class Paint : public Visitor {
+    class Paint : public FormattedText::Visitor {
     public:
-        void paint(SkCanvas* canvas, SkPoint xy, UnicodeText* unicodeText, WrappedText* wrappedText, SkSpan<DecoratedBlock> decoratedBlocks);
+        void paint(SkCanvas* canvas, SkPoint xy, FormattedText* formattedText, SkSpan<DecoratedBlock> decoratedBlocks);
         // Simplification (using default font manager, default font family and default everything possible)
         static bool drawText(std::u16string text, SkCanvas* canvas, SkScalar x, SkScalar y);
         static bool drawText(std::u16string text, SkCanvas* canvas, SkScalar width);
@@ -57,19 +57,21 @@
                              SkSize reqSize, SkScalar x, SkScalar y);
 
     private:
-        static std::unique_ptr<WrappedText> layout(std::u16string text,
-                                                   TextDirection textDirection, TextAlign textAlign,
-                                                   SkSize reqSize,
-                                                   SkSpan<FontBlock> fontBlocks);
+        static sk_sp<FormattedText> layout(std::u16string text,
+                                           TextDirection textDirection, TextAlign textAlign,
+                                           SkSize reqSize,
+                                           SkSpan<FontBlock> fontBlocks);
 
+        void onBeginLine(TextRange lineText) override;
+        void onEndLine(TextRange) override;
         void onGlyphRun(const SkFont& font,
                         TextRange textRange,
                         SkRect boundingRect,
-                        int trailingSpacesStart,
                         int glyphCount,
                         const uint16_t glyphs[],
-                        const SkPoint positions[],
-                        const TextIndex clusters[]) override;
+                        const SkPoint  positions[],
+                        const SkPoint offsets[]) override;
+        void onPlaceholder(TextRange, const SkRect& bounds) override;
 
         // We guarantee that the text range will be inside one of the decorated blocks
         DecoratedBlock findDecoratedBlock(TextRange textRange);
diff --git a/experimental/sktext/src/Text.cpp b/experimental/sktext/src/Text.cpp
index 1d4a380..b82f2d8 100644
--- a/experimental/sktext/src/Text.cpp
+++ b/experimental/sktext/src/Text.cpp
@@ -1,747 +1,758 @@
 // Copyright 2021 Google LLC.
+
 #include "experimental/sktext/include/Text.h"
-#include "experimental/sktext/src/LogicalRun.h"
-#include "experimental/sktext/src/VisualRun.h"
-#include <memory>
 #include <stack>
+
 namespace skia {
 namespace text {
-UnicodeText::UnicodeText(std::unique_ptr<SkUnicode> unicode, SkSpan<uint16_t> utf16)
-    : fUnicode(std::move(unicode))
-    , fText16(std::u16string((char16_t*)utf16.data(), utf16.size())) {
-    initialize(utf16);
-}
 
-UnicodeText::UnicodeText(std::unique_ptr<SkUnicode> unicode, const SkString& utf8)
-    : fUnicode(std::move(unicode)) {
-    fText16 = fUnicode->convertUtf8ToUtf16(utf8);
-    initialize(SkSpan<uint16_t>((uint16_t*)fText16.data(), fText16.size()));
-}
+std::unique_ptr<UnicodeText> Text::parse(SkSpan<uint16_t> utf16) {
 
-bool UnicodeText::isWhitespaces(TextRange range) const {
-    if (range.leftToRight()) {
-        for (auto i = range.fStart; i < range.fEnd; ++i) {
-            if (!this->hasProperty(i, CodeUnitFlags::kPartOfWhiteSpace)) {
-                return false;
-            }
-        }
-    } else {
-        for (auto i = range.fStart; i > range.fEnd; --i) {
-            if (!this->hasProperty(i, CodeUnitFlags::kPartOfWhiteSpace)) {
-                return false;
-            }
-        }
+    auto unicodeText = std::unique_ptr<UnicodeText>(new UnicodeText());
+    unicodeText->fUnicode = std::move(SkUnicode::Make());
+    if (nullptr == unicodeText->fUnicode) {
+        return nullptr;
     }
-    return true;
-}
 
-void UnicodeText::initialize(SkSpan<uint16_t> utf16) {
-    if (!fUnicode) {
-        SkASSERT(fUnicode);
-        return;
-    }
-    // Get white spaces
-    fCodeUnitProperties.push_back_n(utf16.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
-    this->fUnicode->forEachCodepoint((char16_t*)utf16.data(), utf16.size(),
-       [this](SkUnichar unichar, int32_t start, int32_t end) {
-            if (this->fUnicode->isWhitespace(unichar)) {
+    // Create utf8 -> utf16 conversion table
+    unicodeText->fText16 = std::u16string((char16_t*)utf16.data(), utf16.size());
+    unicodeText->fText8 = unicodeText->fUnicode->convertUtf16ToUtf8(unicodeText->fText16);
+    size_t utf16Index = 0;
+    unicodeText->fUTF16FromUTF8.push_back_n(unicodeText->fText8.size() + 1, utf16Index);
+    unicodeText->fUTF8FromUTF16.push_back_n(unicodeText->fText16.size() + 1, utf16Index);
+
+    // Fill out all code unit properties
+    unicodeText->fCodeUnitProperties.push_back_n(utf16.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
+    unicodeText->fUnicode->forEachCodepoint(unicodeText->fText8.c_str(), unicodeText->fText8.size(),
+    [&unicodeText, &utf16Index](SkUnichar unichar, int32_t start, int32_t end, size_t count) {
+        for (auto i = start; i < end; ++i) {
+            unicodeText->fUTF16FromUTF8[i] = utf16Index;
+        }
+        unicodeText->fUTF8FromUTF16[utf16Index] = start;
+        ++utf16Index;
+    });
+    unicodeText->fUTF16FromUTF8[unicodeText->fText8.size()] = unicodeText->fText16.size();
+    unicodeText->fUTF8FromUTF16[unicodeText->fText16.size()] = unicodeText->fText8.size();
+
+        // Get white spaces
+    // TODO: It's a bug. We need to operate on utf16 indexes everywhere
+    unicodeText->fUnicode->forEachCodepoint(unicodeText->fText8.c_str(), unicodeText->fText8.size(),
+       [&unicodeText](SkUnichar unichar, int32_t start, int32_t end, size_t count) {
+            if (unicodeText->fUnicode->isWhitespace(unichar)) {
                 for (auto i = start; i < end; ++i) {
-                    fCodeUnitProperties[i] |=  CodeUnitFlags::kPartOfWhiteSpace;
+                    unicodeText->fCodeUnitProperties[i] |=  CodeUnitFlags::kPartOfWhiteSpace;
                 }
             }
        });
+
     // Get graphemes
-    this->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kGraphemes,
-                           [this](SkBreakIterator::Position pos, SkBreakIterator::Status) {
-                                fCodeUnitProperties[pos]|= CodeUnitFlags::kGraphemeStart;
+    unicodeText->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kGraphemes,
+                           [&unicodeText](SkBreakIterator::Position pos, SkBreakIterator::Status){
+                                unicodeText->fCodeUnitProperties[pos]|= CodeUnitFlags::kGraphemeStart;
                             });
+
     // Get line breaks
-    this->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kLines,
-                           [this](SkBreakIterator::Position pos, SkBreakIterator::Status status) {
+    unicodeText->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kLines,
+                           [&unicodeText](SkBreakIterator::Position pos, SkBreakIterator::Status status) {
                                 if (status == (SkBreakIterator::Status)SkUnicode::LineBreakType::kHardLineBreak) {
                                     // Hard line breaks clears off all the other flags
                                     // TODO: Treat \n as a formatting mark and do not pass it to SkShaper
-                                    fCodeUnitProperties[pos - 1] = CodeUnitFlags::kHardLineBreakBefore;
+                                    unicodeText->fCodeUnitProperties[pos - 1] = CodeUnitFlags::kHardLineBreakBefore;
                                 } else {
-                                    fCodeUnitProperties[pos] |= CodeUnitFlags::kSoftLineBreakBefore;
+                                    unicodeText->fCodeUnitProperties[pos] |= CodeUnitFlags::kSoftLineBreakBefore;
                                 }
                             });
+
+   return std::move(unicodeText);
 }
 
-// Font iterator that finds all formatting marks
-// and breaks runs on them (so we can select and interpret them later)
-class FormattingFontIterator final : public SkShaper::FontRunIterator {
-public:
-    FormattingFontIterator(TextIndex textCount,
-                           SkSpan<FontBlock> fontBlocks,
-                           SkSpan<TextIndex> marks)
-            : fTextCount(textCount)
-            , fFontBlocks(fontBlocks)
-            , fFormattingMarks(marks)
-            , fCurrentBlock(fontBlocks.begin())
-            , fCurrentMark(marks.begin())
-            , fCurrentFontIndex(fCurrentBlock->charCount)
-            , fCurrentFont(this->createFont(*fCurrentBlock)) { }
-    void consume() override {
-        SkASSERT(fCurrentBlock < fFontBlocks.end());
-        SkASSERT(fCurrentMark < fFormattingMarks.end());
-        if (fCurrentFontIndex > *fCurrentMark) {
-            ++fCurrentMark;
-            return;
-        }
-        if (fCurrentFontIndex == *fCurrentMark) {
-            ++fCurrentMark;
-        }
-        ++fCurrentBlock;
-        if (fCurrentBlock < fFontBlocks.end()) {
-            fCurrentFontIndex += fCurrentBlock->charCount;
-            fCurrentFont = this->createFont(*fCurrentBlock);
-        }
-    }
-    size_t endOfCurrentRun() const override {
-        SkASSERT(fCurrentMark != fFormattingMarks.end() || fCurrentBlock != fFontBlocks.end());
-        if (fCurrentMark == fFormattingMarks.end()) {
-            return fCurrentFontIndex;
-        } else if (fCurrentBlock == fFontBlocks.end()) {
-            return *fCurrentMark;
-        } else {
-            return std::min(fCurrentFontIndex, *fCurrentMark);
-        }
-    }
-    bool atEnd() const override {
-        return (fCurrentBlock == fFontBlocks.end() || fCurrentFontIndex == fTextCount) &&
-               (fCurrentMark == fFormattingMarks.end() || *fCurrentMark == fTextCount);
-    }
-    const SkFont& currentFont() const override { return fCurrentFont; }
-    SkFont createFont(const FontBlock& fontBlock) const {
-        if (fontBlock.chain->count() == 0) {
-            return SkFont();
-        }
-        sk_sp<SkTypeface> typeface = fontBlock.chain->operator[](0);
-        SkFont font(std::move(typeface), fontBlock.chain->size());
-        font.setEdging(SkFont::Edging::kAntiAlias);
-        font.setHinting(SkFontHinting::kSlight);
-        font.setSubpixel(true);
-        return font;
-    }
-private:
-    TextIndex const fTextCount;
-    SkSpan<FontBlock> fFontBlocks;
-    SkSpan<TextIndex> fFormattingMarks;
-    FontBlock* fCurrentBlock;
-    TextIndex* fCurrentMark;
-    TextIndex fCurrentFontIndex;
-    SkFont fCurrentFont;
-};
-
+// Break text into pieces by font blocks and by formatting marks
+// Formatting marks: \n (and possibly some other later)
 std::unique_ptr<ShapedText> UnicodeText::shape(SkSpan<FontBlock> fontBlocks,
                                                TextDirection textDirection) {
-    // Get utf8 <-> utf16 conversion tables.
-    // We need to pass to SkShaper indices in utf8 and then convert them back to utf16 for SkText
-    auto text16 = this->getText16();
-    auto text8 = SkUnicode::convertUtf16ToUtf8(std::u16string(text16.data(), text16.size()));
-    size_t utf16Index = 0;
-    SkTArray<size_t, true> UTF16FromUTF8;
-    SkTArray<size_t, true> UTF8FromUTF16;
-    UTF16FromUTF8.push_back_n(text8.size() + 1, utf16Index);
-    UTF8FromUTF16.push_back_n(text16.size() + 1, utf16Index);
-    this->getUnicode()->forEachCodepoint(text8.c_str(), text8.size(),
-    [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
-        // utf8 index group of 1, 2 or 3 can be represented with one utf16 index group
-        for (auto i = start; i < end; ++i) {
-            UTF16FromUTF8[i] = utf16Index;
-        }
-        // utf16 index group of 1 or 2  can refer to the same group of utf8 indices
-        for (; count != 0; --count) {
-            UTF8FromUTF16[utf16Index++] = start;
-        }
-    });
-    UTF16FromUTF8[text8.size()] = text16.size();
-    UTF8FromUTF16[text16.size()] = text8.size();
-    // Break text into pieces by font blocks and by formatting marks
-    // Formatting marks: \n (and possibly some other later)
-    std::vector<size_t> formattingMarks;
-    for (size_t i = 0; i < text16.size(); ++i) {
+    // TODO: Deal with all the cases
+    // A run can start
+    // 1. From the beginning of the text
+    // 2. From the beginning of the paragraph after newline
+    // 3. From the beginning of the font block
+    // 4. One shaping call can produce multiple runs (for which we should apply 1 - 3)
+    fRunGlyphStart = 0.0f;
+    fParagraphTextStart = 0;
+    std::vector<TextIndex> formattingMarks;
+    formattingMarks.emplace_back(fText8.size());
+
+    // Copy flags and find all the formatting marks
+    fShapedText = std::unique_ptr<ShapedText>(new ShapedText());
+    fShapedText->fGlyphUnitProperties.push_back_n(this->fCodeUnitProperties.size(), GlyphUnitFlags::kNoGlyphUnitFlag);
+    for (size_t i = 0; i < this->fCodeUnitProperties.size(); ++i) {
+        fShapedText->fGlyphUnitProperties[i] = (GlyphUnitFlags)this->fCodeUnitProperties[i];
         if (this->isHardLineBreak(i)) {
-            formattingMarks.emplace_back(UTF8FromUTF16[i]);
-            formattingMarks.emplace_back(UTF8FromUTF16[i + 1]);
-            ++i;
+            formattingMarks.emplace_back(i);
         }
     }
-    formattingMarks.emplace_back(text8.size()/* UTF8FromUTF16[text16.size() */);
-    // Convert fontBlocks from utf16 to utf8
-    SkTArray<FontBlock, true> fontBlocks8;
-    TextIndex index16 = 0;
-    TextIndex index8 = 0;
-    for (auto& fb : fontBlocks) {
-        index16 += fb.charCount;
-        auto charCount8 = UTF8FromUTF16[index16] - index8;
-        fontBlocks8.emplace_back((uint32_t)charCount8, fb.chain);
-        index8 = UTF8FromUTF16[index16];
-    }
-    auto shapedText = std::make_unique<ShapedText>();
-    // Shape the text
-    FormattingFontIterator fontIter(text8.size(),
-                                    SkSpan<FontBlock>(fontBlocks8.data(), fontBlocks8.size()),
-                                    SkSpan<TextIndex>(&formattingMarks[0], formattingMarks.size()));
-    SkShaper::TrivialLanguageRunIterator langIter(text8.c_str(), text8.size());
+
+    // TODO: Deal with placeholders (have to be treated the same way to avoid all trouble with bidi)
+
+    // Break the text into a list of paragraphs by \n
+    // and shape each paragraph separately
+    // We start each paragraph from a new line
+    fRunGlyphStart = 0.0f;
+
+    FormattingFontIterator fontIter(fText8.size(), fontBlocks, SkSpan<TextIndex>(&formattingMarks[0], 1));
+    SkShaper::TrivialLanguageRunIterator langIter(fText8.c_str(), fText8.size());
     std::unique_ptr<SkShaper::BiDiRunIterator> bidiIter(
         SkShaper::MakeSkUnicodeBidiRunIterator(
-            this->getUnicode(), text8.c_str(), text8.size(), textDirection == TextDirection::kLtr ? 0 : 1));
+            this->fUnicode.get(), fText8.c_str(), fText8.size(), textDirection == TextDirection::kLtr ? 0 : 1));
     std::unique_ptr<SkShaper::ScriptRunIterator> scriptIter(
-        SkShaper::MakeSkUnicodeHbScriptRunIterator(this->getUnicode(), text8.c_str(), text8.size()));
+        SkShaper::MakeSkUnicodeHbScriptRunIterator(this->fUnicode.get(), fText8.c_str(), fText8.size()));
     auto shaper = SkShaper::MakeShapeDontWrapOrReorder();
     if (shaper == nullptr) {
         // For instance, loadICU does not work. We have to stop the process
         return nullptr;
     }
+
     shaper->shape(
-            text8.c_str(), text8.size(),
+            fText8.c_str(), fText8.size(),
             fontIter, *bidiIter, *scriptIter, langIter,
-            std::numeric_limits<SkScalar>::max(), shapedText.get());
-    if (shapedText->fLogicalRuns.empty()) {
-        // Create a fake run for an empty text (to avoid all the checks)
+            std::numeric_limits<SkScalar>::max(), this);
+
+    // Create a fake run for an empty text (to avoid all the checks)
+    if (fShapedText->fRuns.empty()) {
+        // We still need one empty run to keep the metrics
         SkShaper::RunHandler::RunInfo emptyInfo {
-            fontIter.createFont(fontBlocks.front()),
+            this->createFont(fontBlocks.front()),
             0,
             SkVector::Make(0.0f, 0.0f),
             0,
-            SkShaper::RunHandler::Range(0, 0)
+            Range(0, 0)
         };
-        shapedText->fLogicalRuns.emplace_back(emptyInfo, 0, 0.0f);
-        shapedText->fLogicalRuns.front().commit();
+        fShapedText->fRuns.emplace_back(emptyInfo, 0, 0.0f);
+        fShapedText->fRuns.front().commit();
     }
-    // Fill out all code unit properties
-    for (auto& logicalRun : shapedText->fLogicalRuns) {
-        // Convert utf8 range into utf16 range
-        logicalRun.convertUtf16Range([&](unsigned long index8) {
-            return UTF16FromUTF8[index8];
-        });
-        // Convert all utf8 indexes into utf16 indexes (and also shift them to be on the entire text scale, too)
-        logicalRun.convertClusterIndexes([&](TextIndex clusterIndex8) {
-            return UTF16FromUTF8[clusterIndex8];
-        });
-        // Detect and mark line break runs
-        if (logicalRun.getTextRange().width() == 1 &&
-            logicalRun.size() == 1 &&
-            this->isHardLineBreak(logicalRun.getTextRange().fStart)) {
-            logicalRun.setRunType(LogicalRunType::kLineBreak);
-        }
-    }
-    return std::move(shapedText);
+
+    return std::move(fShapedText);
 }
 
-// TODO: Implement the vertical restriction (height) and add ellipsis
-std::unique_ptr<WrappedText> ShapedText::wrap(UnicodeText* unicodeText, float width, float height) {
+void UnicodeText::commitRunBuffer(const RunInfo&) {
+    fCurrentRun->commit();
+
+    // Convert utf8 range into utf16 range
+    fCurrentRun->convertUtf16Range([this](unsigned long index) {
+        return this->fUTF16FromUTF8[index + fParagraphTextStart];
+    });
+
+    // Convert all utf8 indexes into utf16 indexes (and also shift them to be on the entire text scale, too)
+    fCurrentRun->convertClusterIndexes([this](TextIndex clusterIndex) {
+        auto converted = this->fUTF16FromUTF8[clusterIndex + fParagraphTextStart];
+        fShapedText->fGlyphUnitProperties[converted] |= GlyphUnitFlags::kGlyphClusterStart;
+        if (this->hasProperty(converted, CodeUnitFlags::kGraphemeStart)) {
+            fShapedText->fGlyphUnitProperties[converted] |= GlyphUnitFlags::kGraphemeClusterStart;
+        }
+        return converted;
+    });
+
+    fShapedText->fRuns.emplace_back(std::move(*fCurrentRun));
+
+    fRunGlyphStart += fCurrentRun->width();
+}
+
+SkFont UnicodeText::createFont(const FontBlock& fontBlock) {
+
+    if (fontBlock.chain->count() == 0) {
+        return SkFont();
+    }
+    sk_sp<SkTypeface> typeface = fontBlock.chain->operator[](0);
+
+    SkFont font(std::move(typeface), fontBlock.chain->size());
+    font.setEdging(SkFont::Edging::kAntiAlias);
+    font.setHinting(SkFontHinting::kSlight);
+    font.setSubpixel(true);
+
+    return font;
+}
+
+std::unique_ptr<WrappedText> ShapedText::wrap(SkScalar width, SkScalar heightCurrentlyIgnored, SkUnicode* unicode) {
+
     auto wrappedText = std::unique_ptr<WrappedText>(new WrappedText());
-    // line + spaces + clusters
+    wrappedText->fRuns = this->fRuns;
+    wrappedText->fGlyphUnitProperties = this->fGlyphUnitProperties; // copy
+
+    // line : spaces : clusters
     Stretch line;
     Stretch spaces;
     Stretch clusters;
     Stretch cluster;
-    for (size_t runIndex = 0; runIndex < this->fLogicalRuns.size(); ++runIndex ) {
-        auto& run = this->fLogicalRuns[runIndex];
-        if (run.getRunType() == LogicalRunType::kLineBreak) {
-            // This is the end of the word, the end of the line
-            if (!clusters.isEmpty()) {
-                line.moveTo(spaces);
-                line.moveTo(clusters);
-                spaces = clusters;
-            }
-            this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, true);
-            line = spaces;
-            clusters = spaces;
-            cluster = Stretch();
-            continue;
-        }
+
+    for (size_t runIndex = 0; runIndex < this->fRuns.size(); ++runIndex ) {
+
+        auto& run = this->fRuns[runIndex];
         TextMetrics runMetrics(run.fFont);
         if (!run.leftToRight()) {
             cluster.setTextRange({ run.fUtf16Range.fStart, run.fUtf16Range.fEnd});
         }
-        // Let's wrap the text
+
         for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
             auto textIndex = run.fClusters[glyphIndex];
-            if (cluster.textRange() == EMPTY_RANGE) {
-                // The beginning of a new line (or the first one)
+
+            if (cluster.isEmpty()) {
                 cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
-                line = cluster;
-                spaces = cluster;
-                clusters = cluster;
                 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);
-            auto isSoftLineBreak = unicodeText->isSoftLineBreak(cluster.textStart());
-            auto isWhitespaces = unicodeText->isWhitespaces(cluster.textRange());
-            auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf16Range.fEnd : textIndex == run.fUtf16Range.fStart;
-            // line + spaces + clusters + cluster
-            if (isWhitespaces) {
-                // This is the end of the word
-                if (!clusters.isEmpty()) {
-                    line.moveTo(spaces);
-                    line.moveTo(clusters);
-                    spaces = clusters;
-                }
-                spaces.moveTo(cluster);
-                clusters = cluster;
-                // Whitespaces do not extend the line width so no wrapping
-                continue;
-            } else if (!SkScalarIsFinite(width)) {
-                // No wrapping - the endless line
-                clusters.moveTo(cluster);
-                continue;
-            }
-            // Now let's find out if we can add the cluster to the line
-            auto currentWidth = line.width() + spaces.width() + clusters.width() + cluster.width();
-            if (currentWidth > width) {
-                // Finally, the wrapping case
-                if (line.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 {
+          }
+
+          // 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);
+
+          auto isHardLineBreak = this->isHardLineBreak(cluster.textStart());
+          auto isSoftLineBreak = this->isSoftLineBreak(cluster.textStart());
+          auto isWhitespaces = this->isWhitespaces(cluster.textRange());
+          auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf16Range.fEnd : textIndex == run.fUtf16Range.fStart;
+
+          if (isWhitespaces || isHardLineBreak) {
+              // This is the end of the word
+              if (!clusters.isEmpty()) {
+                  line.moveTo(spaces);
+                  line.moveTo(clusters);
+                  spaces = clusters;
+              }
+          }
+
+          // 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
+              spaces.moveTo(cluster);
+              wrappedText->addLine(line, spaces, unicode, true);
+              line = spaces;
+              clusters = spaces;
+              continue;
+          } else if (!SkScalarIsFinite(width)) {
+              clusters.moveTo(cluster);
+              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 {
+              if (line.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(wrappedText.get(), unicodeText->getUnicode(), line, spaces, false);
-                line = spaces;
-            }
-            clusters.moveTo(cluster);
-            cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
+              }
+              wrappedText->addLine(line, spaces, unicode, false);
+              line = spaces;
+              clusters.moveTo(cluster);
+          }
+
+          cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
         }
     }
-    // Deal with the last line
+
     if (!clusters.isEmpty()) {
         line.moveTo(spaces);
         line.moveTo(clusters);
         spaces = clusters;
-    } else if (wrappedText->fVisualLines.empty()) {
+    } else if (wrappedText->fLines.empty()) {
         // Empty text; we still need a line to avoid checking for empty lines every time
         line.moveTo(cluster);
         spaces.moveTo(cluster);
     }
-    this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, false);
+
+    wrappedText->addLine(line, spaces, unicode, false);
+
     wrappedText->fActualSize.fWidth = width;
     return std::move(wrappedText);
 }
 
-SkTArray<int32_t> ShapedText::getVisualOrder(SkUnicode* unicode, RunIndex startRun, RunIndex endRun) {
-    auto numRuns = endRun - startRun + 1;
-    SkTArray<int32_t> results;
-    results.push_back_n(numRuns);
-    if (numRuns == 0) {
-        return results;
-    }
-    SkTArray<SkUnicode::BidiLevel> runLevels;
-    runLevels.push_back_n(numRuns);
+void WrappedText::addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode, bool hardLineBreak) {
+
+    // This is just chosen to catch the common/fast cases. Feel free to tweak.
+    constexpr int kPreallocCount = 4;
+    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;
-    for (RunIndex runIndex = startRun; runIndex <= endRun; ++runIndex) {
-        runLevels[runLevelsIndex++] = fLogicalRuns[runIndex].bidiLevel();
+    for (auto runIndex = start; runIndex <= end; ++runIndex) {
+        auto& run = fRuns[runIndex];
+        runLevels[runLevelsIndex++] = run.bidiLevel();
     }
     SkASSERT(runLevelsIndex == numRuns);
-    unicode->reorderVisual(runLevels.data(), numRuns, results.data());
-    return results;
-}
-
-// TODO: Fill line fOffset.fY
-void ShapedText::addLine(WrappedText* wrappedText, SkUnicode* unicode, Stretch& stretch, Stretch& spaces, bool hardLineBreak) {
-    auto spacesStart = spaces.glyphStart();
-    Stretch lineStretch = stretch;
-    lineStretch.moveTo(spaces);
-    auto startRun = lineStretch.glyphStart().runIndex();
-    auto endRun = lineStretch.glyphEnd().runIndex();
-    // Reorder and cut (if needed) runs so they fit the line
-    auto visualOrder = std::move(this->getVisualOrder(unicode, startRun, endRun));
-    // Walk through the line's runs in visual order
-    auto firstRunIndex = startRun;
-    auto runStart = wrappedText->fVisualRuns.size();
-    SkScalar runOffsetInLine = 0.0f;
-    for (auto visualIndex : visualOrder) {
-        auto& logicalRun = fLogicalRuns[firstRunIndex + visualIndex];
-        if (logicalRun.getRunType() == LogicalRunType::kLineBreak) {
-            SkASSERT(false);
-        }
-        bool isFirstRun = startRun == (firstRunIndex + visualIndex);
-        bool isLastRun = endRun == (firstRunIndex + visualIndex);
-        bool isSpaceRun = spacesStart.runIndex() == (firstRunIndex + visualIndex);
-        auto glyphStart = isFirstRun ? lineStretch.glyphStart().glyphIndex() : 0;
-        auto glyphEnd = isLastRun ? lineStretch.glyphEnd().glyphIndex() : logicalRun.size();
-        auto glyphSize = glyphEnd - glyphStart;
-        auto glyphSpaces = isSpaceRun ? spacesStart.glyphIndex() : glyphEnd;
-        if (glyphSpaces > glyphStart) {
-            auto textStart = isFirstRun ? lineStretch.textRange().fStart : logicalRun.fUtf16Range.fStart;
-            auto textEnd = isLastRun ? lineStretch.textRange().fEnd : logicalRun.fUtf16Range.fEnd;
-            wrappedText->fVisualRuns.emplace_back(TextRange(textStart, textEnd),
-                                                  glyphSpaces - glyphStart,
-                                                  logicalRun.fTextMetrics,
-                                                  runOffsetInLine,
-                                                  SkSpan<SkPoint>(&logicalRun.fPositions[glyphStart], glyphSize + 1),
-                                                  SkSpan<SkGlyphID>(&logicalRun.fGlyphs[glyphStart], glyphSize),
-                                                  SkSpan<uint32_t>((uint32_t*)&logicalRun.fClusters[glyphStart], glyphSize + 1));
-        }
-        runOffsetInLine += logicalRun.calculateWidth(glyphStart, glyphEnd);
+    SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
+    SkSTArray<1, size_t, true> visualOrder;
+    unicode->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
+    auto firstRunIndex = start;
+    for (auto index : logicalOrder) {
+        visualOrder.push_back(firstRunIndex + index);
     }
-    auto runRange = wrappedText->fVisualRuns.size() == runStart
-                    ? SkSpan<VisualRun>(nullptr, 0)
-                    : 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();
+    this->fLines.emplace_back(stretch, spaces, std::move(visualOrder), fActualSize.fHeight, hardLineBreak);
+    fActualSize.fHeight += stretch.textMetrics().height();
+
     stretch.clean();
     spaces.clean();
 }
 
-void WrappedText::format(TextAlign textAlign, TextDirection textDirection) {
-    if (fAligned == textAlign) {
-        return;
-    }
-    SkScalar verticalOffset = 0.0f;
-    for (auto& line : this->fVisualLines) {
-        if (textAlign == TextAlign::kLeft) {
-            // Good by default
-        } else if (textAlign == TextAlign::kCenter) {
-            line.fOffset.fX = (this->fActualSize.width() - line.fActualWidth) / 2.0f;
-        } else {
-            // TODO: Implement all formatting features
+sk_sp<FormattedText> WrappedText::format(TextAlign textAlign, TextDirection textDirection) {
+    auto formattedText = sk_sp<FormattedText>(new FormattedText());
+
+    formattedText->fRuns = this->fRuns;
+    formattedText->fLines = this->fLines;
+    formattedText->fGlyphUnitProperties = this->fGlyphUnitProperties;
+    formattedText->fActualSize = this->fActualSize;
+    if (textAlign == TextAlign::kLeft) {
+        // Good by default
+    } else if (textAlign == TextAlign::kCenter) {
+        for (auto& line : formattedText->fLines) {
+            line.fHorizontalOffset = (this->fActualSize.width() - line.fTextWidth) / 2.0f;
         }
-        line.fOffset.fY = verticalOffset;
-        verticalOffset += line.fTextMetrics.height();
+        formattedText->fActualSize.fWidth = this->fActualSize.fWidth;
+    } else {
+        // TODO: Implement all formatting features
+    }
+
+    return std::move(formattedText);
+}
+
+void FormattedText::visit(Visitor* visitor) const {
+
+    SkPoint offset = SkPoint::Make(0 , 0);
+    for (auto& line : this->fLines) {
+        offset.fX = 0;
+        visitor->onBeginLine(line.text());
+        for (auto index = 0; index < line.runsNumber(); ++index) {
+            auto runIndex = line.visualRun(index);
+            auto& run = this->fRuns[runIndex];
+
+            GlyphRange glyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */));
+            // Update positions
+            SkAutoSTMalloc<256, SkPoint> positions(glyphRange.width() + 1);
+            SkPoint shift = SkPoint::Make(-run.fPositions[glyphRange.fStart].fX, line.baseline());
+            for (size_t i = glyphRange.fStart; i <= glyphRange.fEnd; ++i) {
+                positions[i - glyphRange.fStart] = run.fPositions[i] + shift + offset;
+            }
+            SkRect boundingRect = SkRect::MakeXYWH(shift.fX + offset.fX, offset.fY, run.fPositions[glyphRange.fEnd].fX  , run.fTextMetrics.height());
+            visitor->onGlyphRun(run.fFont, run.getTextRange(glyphRange), boundingRect, glyphRange.width(), run.fGlyphs.data() + glyphRange.fStart, positions.data(), run.fOffsets.data() + glyphRange.fStart);
+            offset.fX += run.calculateWidth(glyphRange);
+        }
+        visitor->onEndLine(line.text());
+        offset.fY += line.height();
     }
 }
 
-void WrappedText::visit(Visitor* visitor) const {
-    size_t lineIndex = 0;
-    SkScalar verticalOffset = 0.0f;
-    for (auto& line : fVisualLines) {
-        visitor->onBeginLine(lineIndex, line.text(), line.isHardBreak(), SkRect::MakeXYWH(0, verticalOffset, line.fActualWidth, line.fTextMetrics.height()));
-        // Select the runs that are on the line
-        size_t glyphCount = 0ul;
-        for (auto& run : line.fRuns) {
-            auto diff = line.fTextMetrics.above() - run.fTextMetrics.above();
-            SkRect boundingRect = SkRect::MakeXYWH(line.fOffset.fX + run.fPositions[0].fX, line.fOffset.fY + diff, run.width(), run.fTextMetrics.height());
-            visitor->onGlyphRun(run.fFont, run.textRange(), boundingRect, run.trailingSpacesStart(),
-                                run.size(), run.fGlyphs.data(), run.fPositions.data(), run.fClusters.data());
-            glyphCount += run.size();
-        }
-        visitor->onEndLine(lineIndex, line.text(), line.trailingSpaces(), glyphCount);
-        verticalOffset += line.fTextMetrics.height();
-        ++lineIndex;
-    }
-}
-
-void WrappedText::visit(UnicodeText* unicodeText, Visitor* visitor, PositionType positionType, SkSpan<TextIndex> textChunks) const {
+void FormattedText::visit(Visitor* visitor, SkSpan<TextIndex> textChunks) const {
     // Decor blocks have to be sorted by text cannot intersect but can skip some parts of the text
     // (in which case we use default text style from paragraph style)
     // The edges of the decor blocks don't have to match glyph, grapheme or even unicode code point edges
     // It's out responsibility to adjust them to some reasonable values
     // [a:b) -> [c:d) where
     // c is closest GG cluster edge to a from the left and d is closest GG cluster edge to b from the left
-    SkScalar verticalOffset = 0.0f;
-    LineIndex lineIndex = 0;
-    size_t glyphCount = 0ul;
-    for (auto& line : fVisualLines) {
-        visitor->onBeginLine(lineIndex, line.text(), line.isHardBreak(), SkRect::MakeXYWH(0, verticalOffset, line.fActualWidth, line.fTextMetrics.height()));
-        RunIndex runIndex = 0;
-        for (auto& run : fVisualRuns) {
-            run.forEachTextChunkInGlyphRange(textChunks, [&](TextRange textRange) {
-                // TODO: Translate text range into glyph range (with all the appropriate adjustments)
-                GlyphRange glyphRange = this->textToGlyphs(unicodeText, positionType, runIndex, textRange);
-                auto diff = line.fTextMetrics.above() - run.fTextMetrics.above();
-                SkRect boundingRect = SkRect::MakeXYWH(line.fOffset.fX + run.fPositions[glyphRange.fStart].fX, line.fOffset.fY + diff, glyphRange.width(), run.fTextMetrics.height());
-                visitor->onGlyphRun(run.fFont, textRange, boundingRect, run.trailingSpacesStart(),
-                                    glyphRange.width(), &run.fGlyphs[glyphRange.fStart], &run.fPositions[glyphRange.fStart], &run.fClusters[glyphRange.fStart]);
+    SkPoint offset = SkPoint::Make(0 , 0);
+    for (auto& line : this->fLines) {
+        offset.fX = 0;
+        visitor->onBeginLine(line.text());
+        for (auto index = 0; index < line.runsNumber(); ++index) {
+            auto runIndex = line.visualRun(index);
+            auto& run = this->fRuns[runIndex];
+            if (run.size() == 0) {
+                continue;
+            }
+
+            GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */));
+            SkPoint shift = SkPoint::Make(-run.fPositions[runGlyphRange.fStart].fX, line.baseline());
+            // The run edges are good (aligned to GGC)
+            // "ABCdef" -> "defCBA"
+            // "AB": red
+            // "Cd": green
+            // "ef": blue
+            // green[d] blue[ef] green [C] red [BA]
+
+            // chunks[text index] -> chunks [glyph index] -> walk through in glyph index order (the visual order)
+            run.forEachTextChunkInGlyphRange(textChunks, runGlyphRange, [&](TextRange textRange, GlyphRange glyphRange){
+                // Update positions & calculate the bounding rect
+                SkAutoSTMalloc<256, SkPoint> positions(glyphRange.width() + 1);
+                for (GlyphIndex i = glyphRange.fStart; i <= glyphRange.fEnd; ++i) {
+                    positions[i - glyphRange.fStart] = run.fPositions[i] + shift + offset + SkPoint::Make(line.horizontalOffset(), 0.0f);
+                }
+                SkRect boundingRect = SkRect::MakeXYWH(positions[0].fX + line.horizontalOffset(), offset.fY, positions[glyphRange.width()].fX - positions[0].fX, run.fTextMetrics.height());
+                visitor->onGlyphRun(run.fFont, run.getTextRange(glyphRange), boundingRect, glyphRange.width(), run.fGlyphs.data() + glyphRange.fStart, positions.data(), run.fOffsets.data() + glyphRange.fStart);
+
             });
-            ++runIndex;
-            glyphCount += run.size();
+
+            offset.fX += run.calculateWidth(runGlyphRange);
         }
-        visitor->onEndLine(lineIndex, line.text(), line.trailingSpaces(), glyphCount);
-        verticalOffset += line.fTextMetrics.height();
-        ++lineIndex;
+        visitor->onEndLine(line.text());
+        offset.fY += line.height();
     }
 }
 
-// TODO: Implement more effective search
-GlyphRange WrappedText::textToGlyphs(UnicodeText* unicodeText, PositionType positionType, RunIndex runIndex, TextRange textRange) const {
-    SkASSERT(runIndex < fVisualRuns.size());
-    auto& run = fVisualRuns[runIndex];
-    SkASSERT(run.fUtf16Range.contains(textRange));
-    GlyphRange glyphRange(0, run.size());
-    for (GlyphIndex glyph = 0; glyph < run.size(); ++glyph) {
-        auto textIndex = run.fClusters[glyph];
-        if (positionType == PositionType::kGraphemeCluster && unicodeText->hasProperty(textIndex, CodeUnitFlags::kGraphemeStart)) {
-            if (textIndex <= textRange.fStart) {
-                glyphRange.fStart = glyph;
-            } else if (textIndex <= textRange.fEnd) {
-                glyphRange.fEnd = glyph;
-            } else {
-                return glyphRange;
-            }
-        }
-    }
-    SkASSERT(false);
-    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());
-    selectableText->fGlyphUnitProperties.push_back_n(unicodeText->getText16().size() + 1, GlyphUnitFlags::kNoGlyphUnitFlag);
-    for (auto index = 0; index < unicodeText->getText16().size(); ++index) {
-        if (unicodeText->hasProperty(index, CodeUnitFlags::kHardLineBreakBefore)) {
-            selectableText->fGlyphUnitProperties[index] = GlyphUnitFlags::kGraphemeClusterStart;
-        }
-    }
-    for (const auto& run : fVisualRuns) {
-        for (auto& cluster : run.fClusters) {
-            if (unicodeText->hasProperty(cluster, CodeUnitFlags::kGraphemeStart)) {
-                selectableText->fGlyphUnitProperties[cluster] = GlyphUnitFlags::kGraphemeClusterStart;
-            }
-        }
-    }
-    return selectableText;
-}
-
-void SelectableText::onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) {
-    SkASSERT(fBoxLines.size() == index);
-    fBoxLines.emplace_back(index, lineText, hardBreak, bounds);
-}
-
-void SelectableText::onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) {
-    auto& line = fBoxLines.back();
-    line.fTextEnd = trailingSpaces.fStart;
-    line.fTrailingSpacesEnd = trailingSpaces.fEnd;
-    SkASSERT(line.fTextByGlyph.size() == glyphCount);
-    line.fBoxGlyphs.emplace_back(SkRect::MakeXYWH(line.fBounds.fRight, line.fBounds.fTop, 0.0f, line.fBounds.height()));
-    if (line.fTextByGlyph.empty()) {
-        // Let's create an empty fake box to avoid all the checks
-        line.fTextByGlyph.emplace_back(lineText.fEnd);
-    }
-    line.fTextByGlyph.emplace_back(lineText.fEnd);
-}
-
-void SelectableText::onGlyphRun(const SkFont& font,
-                                TextRange textRange,
-                                SkRect boundingRect,
-                                int trailingSpacesStart,
-                                int glyphCount,
-                                const uint16_t glyphs[],
-                                const SkPoint positions[],
-                                const TextIndex clusters[]) {
-    auto& line = fBoxLines.back();
-    auto start = line.fTextByGlyph.size();
-    line.fBoxGlyphs.push_back_n(glyphCount);
-    line.fTextByGlyph.push_back_n(glyphCount);
-    for (auto i = 0; i < glyphCount; ++i) {
-        auto pos = positions[i];
-        auto pos1 = positions[i + 1];
-        line.fBoxGlyphs[start + i] = SkRect::MakeXYWH(pos.fX, boundingRect.fTop + pos.fY, pos1.fX - pos.fX, boundingRect.height());
-        line.fTextByGlyph[start + i] = clusters[i];
-    }
-}
-
-// TODO: Do something (logN) that is not a linear search
-Position SelectableText::findPosition(PositionType positionType, const BoxLine& line, SkScalar x) const {
+// Find the element that includes textIndex
+Position FormattedText::adjustedPosition(PositionType positionType, TextIndex textIndex) const {
     Position position(positionType);
-    position.fGlyphRange = GlyphRange(0, line.fBoxGlyphs.size() - 1);
-    position.fTextRange = line.fTextRange;
-    position.fBoundaries.fTop = line.fBounds.fTop;
-    position.fBoundaries.fBottom = line.fBounds.fBottom;
-    // We look for the narrowest glyph range adjusted to positionType that contains the point.
-    // So far we made sure that one unit of any positionType does not cross the run edges
-    // Therefore it's going to be represented by a single text range only
-    for (; position.fGlyphRange.fStart < position.fGlyphRange.fEnd; ++position.fGlyphRange.fStart) {
-        auto glyphBox = line.fBoxGlyphs[position.fGlyphRange.fStart];
-        if (glyphBox.fLeft > x) {
-            break;
+    SkScalar shift = 0;
+    for (auto& line : fLines) {
+        position.fBoundaries.fTop = position.fBoundaries.fBottom;
+        position.fBoundaries.fBottom = line.verticalOffset() + line.height();
+        if (!line.text().contains(textIndex) && !line.whitespaces().contains(textIndex) ) {
+            continue;
         }
-        if (position.fPositionType == PositionType::kGraphemeCluster) {
-            auto textIndex = line.fTextByGlyph[position.fGlyphRange.fStart];
-            if (this->hasProperty(textIndex, GlyphUnitFlags::kGraphemeClusterStart)) {
-                position.fTextRange.fStart = textIndex;
+
+        shift = 0;
+        for (auto index = 0; index < line.runsNumber(); ++index) {
+            auto runIndex = line.visualRun(index);
+            auto& run = fRuns[runIndex];
+
+            GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), true /* including trailing spaces */));
+            auto runWidth = run.calculateWidth(runGlyphRange);
+
+            if (!run.fUtf16Range.contains(textIndex)) {
+                shift += runWidth;
+                continue;
             }
-        } else {
-            // TODO: Implement
-            SkASSERT(false);
+
+            // Find the position left
+            GlyphIndex found = run.findGlyphIndexLeftOf(runGlyphRange, textIndex);
+
+            position.fLineIndex = lineIndex(&line);
+            position.fRun = &run;
+            position.fGlyphRange = GlyphRange(found, found == runGlyphRange.fEnd ? found : found + 1);
+            position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
+            this->adjustTextRange(&position);
+            position.fBoundaries.fLeft += shift + run.calculateWidth(runGlyphRange.fStart, position.fGlyphRange.fStart);
+            position.fBoundaries.fRight += shift + run.calculateWidth(runGlyphRange.fStart, position.fGlyphRange.fEnd);
+            return position;
         }
+        // The cursor is not on the text anymore; position it after the last element
+        break;
     }
-    for (; position.fGlyphRange.fEnd > position.fGlyphRange.fStart ; --position.fGlyphRange.fEnd) {
-        auto glyphBox = line.fBoxGlyphs[position.fGlyphRange.fStart];
-        if (glyphBox.fRight <= x) {
-            break;
-        }
-        if (position.fPositionType == PositionType::kGraphemeCluster) {
-            auto textIndex = line.fTextByGlyph[position.fGlyphRange.fEnd];
-            if (this->hasProperty(textIndex, GlyphUnitFlags::kGraphemeClusterStart)) {
-                position.fTextRange.fEnd = textIndex;
-                break;
-            }
-        } else {
-            // TODO: Implement
-            SkASSERT(false);
-        }
-    }
-    position.fLineIndex = line.fIndex;
-    position.fBoundaries.fLeft = line.fBoxGlyphs[position.fGlyphRange.fStart].fLeft;
-    position.fBoundaries.fRight = line.fBoxGlyphs[position.fGlyphRange.fEnd].fRight;
+
+    position.fLineIndex = fLines.size() - 1;
+    position.fRun = this->visuallyLastRun(position.fLineIndex);
+    position.fGlyphRange = GlyphRange(position.fRun->size(), position.fRun->size());
+    position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
+    this->adjustTextRange(&position);
+    position.fBoundaries.fLeft = fLines.back().withWithTrailingSpaces();
+    position.fBoundaries.fRight = fLines.back().withWithTrailingSpaces();
     return position;
 }
 
-Position SelectableText::adjustedPosition(PositionType positionType, SkPoint xy) const {
+Position FormattedText::adjustedPosition(PositionType positionType, SkPoint xy) const {
+
     xy.fX = std::min(xy.fX, this->fActualSize.fWidth);
     xy.fY = std::min(xy.fY, this->fActualSize.fHeight);
+
     Position position(positionType);
-    for (auto& line : fBoxLines) {
-        if (line.fBounds.fTop > xy.fY) {
+    position.fRun = this->visuallyLastRun(this->fLines.size() - 1);
+    for (auto& line : fLines) {
+        position.fBoundaries.fTop = position.fBoundaries.fBottom;
+        position.fBoundaries.fBottom = line.verticalOffset() + line.height();
+        position.fLineIndex = this->lineIndex(&line);
+        if (position.fBoundaries.fTop > xy.fY) {
             // We are past the point vertically
             break;
-        } else if (line.fBounds.fBottom <= xy.fY) {
+        } else if (position.fBoundaries.fBottom <= xy.fY) {
             // We haven't reached the point vertically yet
             continue;
         }
-        return this->findPosition(positionType, line, xy.fX);
+
+        // We found the line that contains the point;
+        // let's walk through all its runs and find the element
+        SkScalar runOffsetInLine = 0;
+        for (auto index = 0; index < line.runsNumber(); ++index) {
+            auto runIndex = line.visualRun(index);
+            auto& run = fRuns[runIndex];
+            GlyphRange runGlyphRangeInLine = line.glyphRange(runIndex, run.size(), true /* including trailing space */);
+            SkScalar runWidthInLine = run.calculateWidth(runGlyphRangeInLine);
+            position.fRun = &run;
+            if (runOffsetInLine > xy.fX) {
+                // We are past the point horizontally
+                break;
+            } else if (runOffsetInLine + runWidthInLine < xy.fX) {
+                // We haven't reached the point horizontally yet
+                runOffsetInLine += runWidthInLine;
+                continue;
+            }
+
+            // We found the run that contains the point
+            // let's find the glyph
+            GlyphIndex found = runGlyphRangeInLine.fStart;
+            for (auto i = runGlyphRangeInLine.fStart; i <= runGlyphRangeInLine.fEnd; ++i) {
+                if (runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, i) > xy.fX) {
+                    break;
+                }
+                found = i;
+            }
+
+            position.fGlyphRange = GlyphRange(found, found == runGlyphRangeInLine.fEnd ? found : found + 1);
+            position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
+            this->adjustTextRange(&position);
+            position.fBoundaries.fLeft = runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, position.fGlyphRange.fStart);
+            position.fBoundaries.fRight = runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, position.fGlyphRange.fEnd);
+            return position;
+        }
+        // The cursor is not on the text anymore; position it after the last element of the last visual run of the current line
+        break;
     }
-    return this->lastPosition(positionType);
+
+    position.fRun = this->visuallyLastRun(position.fLineIndex);
+    auto line = this->line(position.fLineIndex);
+    position.fGlyphRange.fStart =
+    position.fGlyphRange.fEnd = line->glyphRange(this->runIndex(position.fRun), position.fRun->size(), true /* including trailing spaces */).fEnd;
+    position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
+    this->adjustTextRange(&position);
+    position.fBoundaries.fLeft =
+    position.fBoundaries.fRight = line->withWithTrailingSpaces();
+    return position;
 }
 
-Position SelectableText::previousPosition(Position current) const {
-    const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
-    if (this->isFirstOnTheLine(current)) {
-        // Go to the previous line
-        if (current.fLineIndex == 0) {
-            // We reached the end; there is nowhere to move
-            current.fGlyphRange = GlyphRange(0, 0);
-            return current;
-        } else {
-            current.fLineIndex -= 1;
-            currentLine = &fBoxLines[current.fLineIndex];
-            current.fGlyphRange.fStart = currentLine->fBoxGlyphs.size();
+// Adjust the text positions to the position type
+// (assuming for now that a grapheme cannot cross run edges; it's actually not true)
+void FormattedText::adjustTextRange(Position* position) const {
+    // The textRange is aligned on a glyph cluster
+    if (position->fPositionType == PositionType::kGraphemeCluster) {
+        // Move left to the beginning of the run
+        while (position->fTextRange.fStart > position->fRun->fUtf8Range.begin() &&
+               !this->hasProperty(position->fTextRange.fStart, GlyphUnitFlags::kGraphemeStart)) {
+            --position->fTextRange.fStart;
+        }
+        // Update glyphRange, too
+        while (position->fRun->fClusters[position->fGlyphRange.fStart] > position->fTextRange.fStart) {
+            --position->fGlyphRange.fStart;
+        }
+
+        // Move right to the end of the run updating glyphRange, too
+        while (position->fTextRange.fEnd < position->fRun->fUtf8Range.end() &&
+               !this->hasProperty(position->fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart)) {
+            ++position->fTextRange.fEnd;
+        }
+        // Update glyphRange, too
+        while (position->fRun->fClusters[position->fGlyphRange.fEnd] < position->fTextRange.fEnd) {
+            ++position->fGlyphRange.fEnd;
+        }
+    } else {
+        // TODO: Implement all the other position types
+        SkASSERT(false);
+    }
+    position->fTextRange = TextRange(position->fRun->fClusters[position->fGlyphRange.fStart], position->fRun->fClusters[position->fGlyphRange.fEnd]);
+}
+
+// The range is guaranteed on the same line
+bool FormattedText::recalculateBoundaries(Position& position) const {
+
+    auto line = this->line(position.fLineIndex);
+    auto runIndex = this->runIndex(position.fRun);
+
+    GlyphIndex start = runIndex == line->glyphStart().runIndex() ? line->glyphStart().glyphIndex() : 0;
+    GlyphIndex end = runIndex == line->glyphTrailingEnd().runIndex() ? line->glyphTrailingEnd().glyphIndex() : position.fRun->fGlyphs.size();
+
+    SkASSERT (start <= position.fGlyphRange.fStart && end >= position.fGlyphRange.fEnd);
+    auto left = position.fRun->calculateWidth(start, position.fGlyphRange.fStart);
+    auto width = position.fRun->calculateWidth(position.fGlyphRange);
+    position.fBoundaries = SkRect::MakeXYWH(left, line->verticalOffset(), width, line->getMetrics().height());
+    return true;
+}
+
+const TextRun* FormattedText::visuallyPreviousRun(size_t lineIndex, const TextRun* run) const {
+    auto line = this->line(lineIndex);
+    auto runIndex = this->runIndex(run);
+    for (auto i = 0; i < line->runsNumber(); ++i) {
+        if (line->visualRun(i) == runIndex) {
+            if (i == 0) {
+                // Go to the previous line
+                if (lineIndex == 0) {
+                    // This is the first line
+                    return nullptr;
+                }
+                // Previous line, last visual run
+                line = this->line(--lineIndex);
+                i = line->runsNumber() - 1;
+            }
+            return &fRuns[line->visualRun(i)];
         }
     }
-    auto position = this->findPosition(current.fPositionType, *currentLine, currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
-    if (current.fPositionType == PositionType::kGraphemeCluster) {
-        // Either way we found us a grapheme cluster (just make sure of it)
-        SkASSERT(this->hasProperty(current.fTextRange.fStart, GlyphUnitFlags::kGraphemeClusterStart));
-    }
-    return position;
+    SkASSERT(false);
+    return nullptr;
 }
 
-Position SelectableText::nextPosition(Position current) const {
-    const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
-    if (this->isLastOnTheLine(current)) {
-        // Go to the next line
-        if (current.fLineIndex == this->fBoxLines.size() - 1) {
-            // We reached the end; there is nowhere to move
-            current.fGlyphRange = GlyphRange(currentLine->fBoxGlyphs.size(), currentLine->fBoxGlyphs.size());
-            return current;
-        } else {
-            current.fLineIndex += 1;
-            currentLine = &fBoxLines[current.fLineIndex];
-            current.fGlyphRange.fEnd = 0;
+const TextRun* FormattedText::visuallyNextRun(size_t lineIndex, const TextRun* run) const {
+    auto line = this->line(lineIndex);
+    auto runIndex = this->runIndex(run);
+    for (auto i = 0; i < line->runsNumber(); ++i) {
+        if (line->visualRun(i) == runIndex) {
+            if (i == line->runsNumber() - 1) {
+                // Go to the next line
+                if (lineIndex == fLines.size() - 1) {
+                    // This is the last line
+                    return nullptr;
+                }
+                // Next line, first visual run
+                line = this->line(++lineIndex);
+                i = 0;
+            }
+            return &fRuns[line->visualRun(i)];
         }
     }
-    auto position = this->findPosition(current.fPositionType, *currentLine, currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
-    if (current.fPositionType == PositionType::kGraphemeCluster) {
-        // Either way we found us a grapheme cluster (just make sure of it)
-        SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
-    }
-    return position;
+    SkASSERT(false);
+    return nullptr;
 }
 
-Position SelectableText::upPosition(Position current) const {
-
-    if (current.fLineIndex == 0) {
-        // We are on the first line; just move to the first position
-        return this->firstPosition(current.fPositionType);
-    }
-
-    // Go to the previous line
-    const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
-    auto position = this->findPosition(current.fPositionType, fBoxLines[current.fLineIndex - 1], currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
-    if (current.fPositionType == PositionType::kGraphemeCluster) {
-        // Either way we found us a grapheme cluster (just make sure of it)
-        SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
-    }
-    return position;
+const TextRun* FormattedText::visuallyFirstRun(size_t lineIndex) const {
+    auto line = this->line(lineIndex);
+    return &fRuns[line->visualRun(0)];
 }
 
-Position SelectableText::downPosition(Position current) const {
-
-    if (current.fLineIndex == this->countLines() - 1) {
-        // We are on the last line; just move to the last position
-        return this->lastPosition(current.fPositionType);
-    }
-
-    // Go to the next line
-    const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
-    auto position = this->findPosition(current.fPositionType, fBoxLines[current.fLineIndex + 1], currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
-    if (current.fPositionType == PositionType::kGraphemeCluster) {
-        // Either way we found us a grapheme cluster (just make sure of it)
-        SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
-    }
-    return position;
+const TextRun* FormattedText::visuallyLastRun(size_t lineIndex) const {
+    auto line = this->line(lineIndex);
+    return &fRuns[line->visualRun(line->runsNumber() - 1)];
 }
 
-Position SelectableText::firstPosition(PositionType positionType) const {
-    auto firstLine = fBoxLines.front();
-    auto firstGlyph = firstLine.fBoxGlyphs.front();
+Position FormattedText::previousElement(Position element) const {
+
+    if (element.fGlyphRange.fStart == 0) {
+        if (this->isVisuallyFirst(element.fLineIndex, element.fRun)) {
+            element.fGlyphRange = GlyphRange { 0, 0};
+            return element;
+        }
+        // We need to go to the visually previous run
+        // (skipping all the empty runs if there are any)
+        element.fRun = this->visuallyPreviousRun(element.fLineIndex, element.fRun);
+        // Set the glyph range after the last glyph
+        element.fGlyphRange = GlyphRange { element.fRun->fGlyphs.size(), element.fRun->fGlyphs.size()};
+        if (element.fRun == nullptr) {
+            return element;
+        }
+    }
+
+    auto& clusters = element.fRun->fClusters;
+    element.fGlyphRange = GlyphRange(element.fGlyphRange.fStart, element.fGlyphRange.fStart);
+    element.fTextRange = TextRange(clusters[element.fGlyphRange.fStart],
+                                   clusters[element.fGlyphRange.fStart]);
+    while (element.fGlyphRange.fStart > 0) {
+        // Shift left visually
+        element.fTextRange.fStart = clusters[--element.fGlyphRange.fStart];
+        if (element.fPositionType == PositionType::kGraphemeCluster) {
+            if (this->hasProperty(element.fTextRange.fStart, GlyphUnitFlags::kGraphemeStart)) {
+                break;
+            }
+        }
+    }
+
+    // Update the line
+    auto line = this->line(element.fLineIndex);
+    if (line->glyphStart().runIndex() == this->runIndex(element.fRun) &&
+        line->glyphStart().glyphIndex() > element.fGlyphRange.fStart) {
+        --element.fLineIndex;
+    }
+
+    // Either way we found us a grapheme cluster (just make sure of it)
+    SkASSERT(this->hasProperty(element.fTextRange.fStart, GlyphUnitFlags::kGraphemeStart));
+    return element;
+}
+
+Position FormattedText::nextElement(Position element) const {
+
+    if (element.fGlyphRange.fEnd == element.fRun->size()) {
+        // We need to go to the visually next run
+        // (skipping all the empty runs if there are any)
+        if (this->isVisuallyLast(element.fLineIndex, element.fRun)) {
+            element.fGlyphRange = GlyphRange { element.fRun->size(), element.fRun->size() };
+            return element;
+        }
+        element.fRun = this->visuallyNextRun(element.fLineIndex, element.fRun);
+        // Set the glyph range after the last glyph
+        element.fGlyphRange = GlyphRange { 0, 0};
+        if (element.fRun == nullptr) {
+            return element;
+        }
+    }
+
+    auto& clusters = element.fRun->fClusters;
+    element.fGlyphRange = GlyphRange(element.fGlyphRange.fEnd, element.fGlyphRange.fEnd);
+    element.fTextRange = TextRange(clusters[element.fGlyphRange.fEnd],
+                                   clusters[element.fGlyphRange.fEnd]);
+    while (element.fGlyphRange.fEnd < element.fRun->size()) {
+        // Shift left visually
+        element.fTextRange.fEnd = clusters[++element.fGlyphRange.fEnd];
+        if (element.fPositionType == PositionType::kGraphemeCluster) {
+            if (this->hasProperty(element.fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart)) {
+                break;
+            }
+        }
+    }
+    // Update the line
+    auto line = this->line(element.fLineIndex);
+    if (line->glyphTrailingEnd().runIndex() == this->runIndex(element.fRun) &&
+        line->glyphTrailingEnd().glyphIndex() < element.fGlyphRange.fEnd) {
+        ++element.fLineIndex;
+    }
+
+    // Either way we found us a grapheme cluster (just make sure of it)
+    SkASSERT(this->hasProperty(element.fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart));
+    return element;
+}
+
+bool FormattedText::isFirstOnTheLine(Position element) const {
+    auto lineStart = this->line(element.fLineIndex)->glyphStart();
+    return lineStart.runIndex() == this->runIndex(element.fRun) &&
+           lineStart.glyphIndex() == element.fGlyphRange.fStart;
+}
+
+bool FormattedText::isLastOnTheLine(Position element) const {
+    auto lineEnd = this->line(element.fLineIndex)->glyphEnd();
+    return lineEnd.runIndex() == this->runIndex(element.fRun) &&
+           lineEnd.glyphIndex() == element.fGlyphRange.fStart;
+}
+
+Position FormattedText::firstElement(PositionType positionType) const {
+
     Position beginningOfText(positionType);
+    // We need to go to the visually next run
+    // (skipping all the empty runs if there are any)
+    beginningOfText.fRun = this->visuallyFirstRun(0);
     // Set the glyph range after the last glyph
     beginningOfText.fGlyphRange = GlyphRange { 0, 0};
     beginningOfText.fLineIndex = 0;
-    beginningOfText.fBoundaries = SkRect::MakeXYWH(firstGlyph.fLeft, firstGlyph.fTop, 0, firstGlyph.height());
-    beginningOfText.fTextRange = this->glyphsToText(beginningOfText);
-    beginningOfText.fLineIndex = 0;
-    return beginningOfText;
+
+    return beginningOfText;// this->nextElement(beginningOfText);
 }
 
-Position SelectableText::lastPosition(PositionType positionType) const {
-    auto lastLine = fBoxLines.back();
-    auto lastGlyph = lastLine.fBoxGlyphs.back();
+Position FormattedText::lastElement(PositionType positionType) const {
+
     Position endOfText(positionType);
-    endOfText.fLineIndex = lastLine.fIndex;
-    endOfText.fGlyphRange = GlyphRange(lastLine.fBoxGlyphs.size() - 1, lastLine.fBoxGlyphs.size() - 1);
-    endOfText.fBoundaries = SkRect::MakeXYWH(lastGlyph.fRight, lastGlyph.fTop, 0, lastGlyph.height());
-    endOfText.fTextRange = this->glyphsToText(endOfText);
-    endOfText.fLineIndex = lastLine.fIndex;
-    return endOfText;
+    // We need to go to the visually next run
+    // (skipping all the empty runs if there are any)
+    endOfText.fRun = this->visuallyLastRun(fLines.size() - 1);
+    // Set the glyph range after the last glyph
+    endOfText.fGlyphRange = GlyphRange { endOfText.fRun->size(), endOfText.fRun->size() };
+    endOfText.fLineIndex = this->countLines() - 1;
+
+    return endOfText; // this->previousElement(endOfText);
 }
 
-Position SelectableText::firstInLinePosition(PositionType positionType, LineIndex lineIndex) const {
-    SkASSERT(lineIndex >= 0 && lineIndex < fBoxLines.size());
-    auto& line = fBoxLines[lineIndex];
-    return this->findPosition(positionType, line, line.fBounds.left());
+bool FormattedText::isVisuallyFirst(size_t lineIndex, const TextRun* run) const {
+    auto firstLine = this->line(lineIndex);
+    return this->runIndex(run) == firstLine->visualRun(0);
 }
 
-Position SelectableText::lastInLinePosition(PositionType positionType, LineIndex lineIndex) const {
-    auto& line = fBoxLines[lineIndex];
-    return this->findPosition(positionType, line, line.fBounds.right());
-}
-
-
-TextRange SelectableText::glyphsToText(Position position) const {
-    SkASSERT(position.fPositionType != PositionType::kRandomText);
-    auto line = this->getLine(position.fLineIndex);
-    TextRange textRange = EMPTY_RANGE;
-    for (auto glyph = position.fGlyphRange.fStart; glyph <= position.fGlyphRange.fEnd; ++glyph) {
-        if (textRange.fStart == EMPTY_INDEX) {
-            textRange.fStart = line.fTextByGlyph[glyph];
-        }
-        textRange.fEnd = line.fTextByGlyph[glyph];
-    }
-    return textRange;
+bool FormattedText::isVisuallyLast(size_t lineIndex, const TextRun* run) const {
+    auto lastLine = this->line(lineIndex);
+    return this->runIndex(run) == lastLine->visualRun(lastLine->runsNumber() - 1);
 }
 } // namespace text
 } // namespace skia
diff --git a/experimental/sktext/src/TextRun.cpp b/experimental/sktext/src/TextRun.cpp
new file mode 100644
index 0000000..dd95c99
--- /dev/null
+++ b/experimental/sktext/src/TextRun.cpp
@@ -0,0 +1,70 @@
+// Copyright 2021 Google LLC.
+
+#include "experimental/sktext/src/TextRun.h"
+
+namespace skia {
+namespace text {
+
+class Processor;
+
+TextRun::TextRun(const SkShaper::RunHandler::RunInfo& info, TextIndex textStart, SkScalar glyphOffset)
+    : fFont(info.fFont)
+    , fBidiLevel(info.fBidiLevel)
+    , fAdvance(info.fAdvance)
+    , fUtf8Range(info.utf8Range)
+    , fTextMetrics(info.fFont)
+    , fRunStart(textStart)
+    , fRunOffset(glyphOffset) {
+    fGlyphs.push_back_n(info.glyphCount);
+    fBounds.push_back_n(info.glyphCount);
+    fPositions.push_back_n(info.glyphCount + 1);
+    fOffsets.push_back_n(info.glyphCount);
+    fClusters.push_back_n(info.glyphCount + 1);
+}
+
+void TextRun::commit() {
+    fFont.getBounds(fGlyphs.data(), fGlyphs.size(), fBounds.data(), nullptr);
+    // To make edge cases easier
+    fPositions[fGlyphs.size()] = fAdvance;
+    fClusters[fGlyphs.size()] = this->leftToRight() ? fUtf8Range.end() : fUtf8Range.begin();
+}
+
+SkShaper::RunHandler::Buffer TextRun::newRunBuffer() {
+    return {fGlyphs.data(), fPositions.data(), fOffsets.data(), fClusters.data(), {0.0f, 0.0f} };
+}
+
+SkScalar TextRun::calculateWidth(GlyphRange glyphRange) const {
+    SkASSERT(glyphRange.fStart <= glyphRange.fEnd && glyphRange.fEnd < fPositions.size());
+    return fPositions[glyphRange.fEnd].fX - fPositions[glyphRange.fStart].fX;
+}
+
+GlyphIndex TextRun::findGlyph(TextIndex textIndex) const {
+    GlyphIndex glyphIndex = EMPTY_INDEX;
+    for (size_t i = 0; i < fClusters.size(); ++i) {
+        auto cluster = fClusters[i];
+        if (this->leftToRight()) {
+            if (cluster > textIndex) {
+                break;
+            }
+        } else if (cluster < textIndex) {
+            break;
+        }
+        glyphIndex = i;
+    }
+    return glyphIndex;
+}
+
+TextRange TextRun::getTextRange(GlyphRange glyphRange) const {
+    return TextRange(this->fClusters[glyphRange.fStart], this->fClusters[glyphRange.fEnd]);
+}
+
+GlyphRange TextRun::getGlyphRange(TextRange textRange) const {
+    return GlyphRange(getGlyphIndex(textRange.fStart), getGlyphIndex(textRange.fEnd));
+}
+
+GlyphIndex TextRun::getGlyphIndex(TextIndex textIndex) const {
+    return fGlyphByText[textIndex];
+}
+
+} // namespace text
+} // namespace skia
diff --git a/experimental/sktext/src/TextRun.h b/experimental/sktext/src/TextRun.h
new file mode 100644
index 0000000..fb52958
--- /dev/null
+++ b/experimental/sktext/src/TextRun.h
@@ -0,0 +1,156 @@
+// Copyright 2021 Google LLC.
+#ifndef TextRun_DEFINED
+#define TextRun_DEFINED
+
+#include "experimental/sktext/include/Types.h"
+#include "experimental/sktext/src/Line.h"
+#include "modules/skshaper/include/SkShaper.h"
+
+namespace skia {
+namespace text {
+
+struct Position;
+class TextRun {
+    public:
+    TextRun(const SkShaper::RunHandler::RunInfo& info, TextIndex textStart, SkScalar glyphOffset);
+    SkShaper::RunHandler::Buffer newRunBuffer();
+    void commit();
+
+    SkScalar calculateWidth(GlyphRange glyphRange) const;
+    SkScalar calculateWidth(GlyphIndex start, GlyphIndex end) const {
+      return calculateWidth(GlyphRange(start, end));
+    }
+    SkScalar width() const { return fAdvance.fX; }
+    SkScalar firstGlyphPosition() const { return fPositions[0].fX; }
+
+    bool leftToRight() const { return fBidiLevel % 2 == 0; }
+    uint8_t bidiLevel() const { return fBidiLevel; }
+    GlyphIndex findGlyph(TextIndex textIndex) const;
+    size_t size() const { return fGlyphs.size(); }
+    TextRange getTextRange(GlyphRange glyphRange) const;
+    GlyphRange getGlyphRange(TextRange textRange) const;
+    GlyphIndex getGlyphIndex(TextIndex textIndex) const;
+
+    GlyphIndex findGlyphIndexLeftOf(GlyphRange runGlyphRange, TextIndex textIndex) const {
+        GlyphIndex found = runGlyphRange.fStart;
+        for (auto i = runGlyphRange.fStart; i <= runGlyphRange.fEnd; ++i) {
+            auto clusterIndex = fClusters[i];
+            if ((this->leftToRight() && clusterIndex > textIndex) ||
+                (!this->leftToRight() && clusterIndex < textIndex)) {
+                break;
+            }
+            found = i;
+        }
+        return found;
+    }
+
+    template <typename Callback>
+    void forEachTextChunkInGlyphRange(SkSpan<TextIndex> textChunks, GlyphRange runGlyphRange, Callback&& callback) const {
+        TextRange runTextRange = this->getTextRange(runGlyphRange);
+        if (this->leftToRight()) {
+            TextIndex currentIndex = 0;
+            TextRange textRange(runTextRange.fStart, runTextRange.fStart);
+            for (auto currentTextChunk : textChunks) {
+                if (currentIndex >= runTextRange.fEnd) {
+                    break;
+                }
+                currentIndex += currentTextChunk;
+                if (currentIndex < runTextRange.fStart) {
+                    continue;
+                }
+                textRange.fStart = textRange.fEnd;
+                textRange.fEnd += currentTextChunk;
+                textRange.fEnd = std::min(runTextRange.fEnd, textRange.fEnd);
+
+                callback(textRange, getGlyphRange(textRange));
+            }
+        } else {
+            // TODO: Implement RTL
+            SkASSERT(false);
+        }
+    }
+
+    template <typename Callback>
+    void forEachCluster(Callback&& callback) {
+        for(auto& clusterIndex : fClusters) {
+            callback(fRunStart + clusterIndex);
+        }
+    }
+
+    // Convert indexes into utf16 and also shift them to be on the entire text scale
+    template <typename Callback>
+    void convertClusterIndexes(Callback&& callback) {
+        fGlyphByText.push_back_n(fUtf16Range.width() + 1);
+        TextIndex start = fClusters[0];
+        for (size_t glyph = 0; glyph < fClusters.size(); ++glyph) {
+            auto& clusterIndex = fClusters[glyph];
+            clusterIndex = callback(clusterIndex);
+            // Convert fGlyphByText, too
+            if (this->leftToRight()) {
+                for (auto i = start; i <= clusterIndex; ++i) {
+                    fGlyphByText[i] = glyph;
+                }
+            } else {
+                for (auto i = clusterIndex; i >= start; --i) {
+                    fGlyphByText[i] = glyph;
+                }
+            }
+            start = clusterIndex + 1;
+        }
+    }
+
+    // Convert indexes into utf16 and also shift them to be on the entire text scale
+    template <typename Callback>
+    void convertUtf16Range(Callback&& callback) {
+        this->fUtf16Range.fStart = callback(this->fUtf8Range.begin());
+        this->fUtf16Range.fEnd = callback(this->fUtf8Range.end());
+    }
+
+    private:
+    friend class ShapedText;
+    friend class FormattedText;
+    SkFont fFont;
+
+    SkVector fAdvance;
+    SkShaper::RunHandler::Range fUtf8Range;
+    TextRange fUtf16Range;
+    TextIndex fRunStart;
+    SkScalar  fRunOffset;
+    SkSTArray<128, SkGlyphID, true> fGlyphs;
+    SkSTArray<128, SkPoint, true> fPositions;
+    SkSTArray<128, SkPoint, true> fOffsets;
+    SkSTArray<128, uint32_t, true> fClusters;
+    SkSTArray<128, SkRect, true> fBounds;
+
+    SkSTArray<128, GlyphIndex> fGlyphByText; // Sort of the opposite of fClusters
+
+    TextMetrics fTextMetrics;
+    uint8_t fBidiLevel;
+
+    size_t sizeWithoutTrailingSpaces() const;
+};
+
+// This is a input/output range within one run
+class Line;
+struct Position {
+    Position(PositionType positionType, size_t lineIndex, const TextRun* run, GlyphRange glyphRange, TextRange textRange, SkRect rect)
+        : fPositionType(positionType)
+        , fLineIndex(lineIndex)
+        , fRun(run)
+        , fGlyphRange(glyphRange)
+        , fTextRange(textRange)
+        , fBoundaries(rect) { }
+
+    Position(PositionType positionType)
+        : Position(positionType, EMPTY_INDEX, nullptr, EMPTY_RANGE, EMPTY_RANGE, SkRect::MakeEmpty()) { }
+
+    PositionType fPositionType;
+    size_t fLineIndex;
+    const TextRun* fRun;
+    GlyphRange fGlyphRange;
+    TextRange fTextRange;
+    SkRect fBoundaries;
+};
+} // namespace text
+} // namespace skia
+#endif
diff --git a/experimental/sktext/src/VisualRun.cpp b/experimental/sktext/src/VisualRun.cpp
deleted file mode 100644
index f8a8a90..0000000
--- a/experimental/sktext/src/VisualRun.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2021 Google LLC.
-
-#include "experimental/sktext/src/VisualRun.h"
-
-namespace skia {
-namespace text {
-
-} // namespace text
-} // namespace skia
diff --git a/experimental/sktext/src/VisualRun.h b/experimental/sktext/src/VisualRun.h
deleted file mode 100644
index 6f58ccc..0000000
--- a/experimental/sktext/src/VisualRun.h
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2021 Google LLC.
-#ifndef VisualRun_DEFINED
-#define VisualRun_DEFINED
-
-#include "experimental/sktext/include/Types.h"
-#include "experimental/sktext/src/Line.h"
-#include "modules/skshaper/include/SkShaper.h"
-
-namespace skia {
-namespace text {
-
-class VisualRun {
-    public:
-    VisualRun(TextRange textRange, GlyphIndex trailingSpacesStart, const TextMetrics& metrics, SkScalar runOffsetInLine,
-              SkSpan<SkPoint> positions, SkSpan<SkGlyphID> glyphs, SkSpan<uint32_t> clusters)
-        : fTextMetrics(metrics)
-        , fUtf16Range(textRange)
-        , fTrailingSpacesStart(trailingSpacesStart) {
-        fPositions.reserve_back(positions.size());
-        runOffsetInLine -= positions[0].fX;
-        for (auto& pos : positions) {
-            fPositions.emplace_back(pos + SkPoint::Make(runOffsetInLine, 0));
-        }
-        fGlyphs.reserve_back(glyphs.size());
-        for (auto glyph : glyphs) {
-            fGlyphs.emplace_back(glyph);
-        }
-        fClusters.reserve_back(clusters.size());
-        for (auto cluster : clusters) {
-            fClusters.emplace_back(SkToU16(cluster));
-        }
-
-        fAdvance.fX = calculateWidth(0, glyphs.size());
-        fAdvance.fY = metrics.height();
-    }
-
-    SkScalar calculateWidth(GlyphRange glyphRange) const {
-        SkASSERT(glyphRange.fStart <= glyphRange.fEnd && glyphRange.fEnd < fPositions.size());
-        return fPositions[glyphRange.fEnd].fX - fPositions[glyphRange.fStart].fX;
-    }
-    SkScalar calculateWidth(GlyphIndex start, GlyphIndex end) const {
-      return calculateWidth(GlyphRange(start, end));
-    }
-    SkScalar width() const { return fAdvance.fX; }
-    SkScalar firstGlyphPosition() const { return fPositions[0].fX; }
-
-    bool leftToRight() const { return fBidiLevel % 2 == 0; }
-    uint8_t bidiLevel() const { return fBidiLevel; }
-    size_t size() const { return fGlyphs.size(); }
-    TextMetrics textMetrics() const { return fTextMetrics; }
-    GlyphIndex trailingSpacesStart() const { return fTrailingSpacesStart; }
-    TextRange textRange() const { return fUtf16Range; }
-
-    template <typename Callback>
-    void forEachTextChunkInGlyphRange(SkSpan<TextIndex> textChunks, Callback&& callback) const {
-        if (this->leftToRight()) {
-            TextIndex currentIndex = 0;
-            TextRange textRange(fUtf16Range.fStart, fUtf16Range.fStart);
-            for (auto currentTextChunk : textChunks) {
-                if (currentIndex >= fUtf16Range.fEnd) {
-                    break;
-                }
-                currentIndex += currentTextChunk;
-                if (currentIndex < fUtf16Range.fStart) {
-                    continue;
-                }
-                textRange.fStart = textRange.fEnd;
-                textRange.fEnd += currentTextChunk;
-                textRange.fEnd = std::min(fUtf16Range.fEnd, textRange.fEnd);
-
-                callback(textRange);
-            }
-        } else {
-            // TODO: Implement RTL
-            SkASSERT(false);
-        }
-    }
-
-    private:
-    friend class WrappedText;
-    SkFont fFont;
-    TextMetrics fTextMetrics;
-
-    SkVector fAdvance;
-    SkShaper::RunHandler::Range fUtf8Range;
-    TextRange fUtf16Range;
-    TextIndex fRunStart;
-    SkScalar  fRunOffset;
-    SkSTArray<128, SkGlyphID, true> fGlyphs;
-    SkSTArray<128, SkPoint, true> fPositions;
-    SkSTArray<128, TextIndex, true> fClusters;
-    GlyphIndex fTrailingSpacesStart;
-    uint8_t fBidiLevel;
-};
-
-class VisualLine {
-public:
-    VisualLine(TextRange text, bool hardLineBreak, SkScalar verticalOffset, SkSpan<VisualRun> runs)
-        : fText(text)
-        , fRuns(runs)
-        , fTrailingSpaces(0, 0)
-        , fOffset(SkPoint::Make(0, verticalOffset))
-        , fActualWidth(0.0f)
-        , fTextMetrics()
-        , fIsHardBreak(hardLineBreak)
-        , fGlyphCount(0ul) {
-        // Calculate all the info
-        for (auto& run : fRuns) {
-            fTextMetrics.merge(run.textMetrics());
-            fActualWidth += run.width();  // What about trailing spaces?
-            if (run.trailingSpacesStart() == 0) {
-                // The entire run is trailing spaces, do not move the counter
-            } else {
-                // We can reset the trailing spaces counter
-                fTrailingSpaces.fStart = fTrailingSpaces.fEnd + run.trailingSpacesStart();
-            }
-            fTrailingSpaces.fEnd += run.size();
-        }
-    }
-
-    SkScalar baseline() const { return fTextMetrics.baseline(); }
-    TextRange text() const { return fText; }
-    GlyphRange trailingSpaces() const { return fTrailingSpaces; }
-    bool isHardBreak() const { return fIsHardBreak; }
-    size_t glyphCount() const { return fGlyphCount; }
-
-    bool isFirst(VisualRun* run) { return &fRuns.front() == run; }
-    bool isLast(VisualRun* run) { return &fRuns.back() == run; }
-private:
-    friend class WrappedText;
-    friend class VisualRun;
-    TextRange fText;
-    SkSpan<VisualRun> fRuns;
-    GlyphRange fTrailingSpaces; // This is a zero-based index across the line
-    SkPoint fOffset;            // For left/right/center formatting and for height
-    SkScalar fActualWidth;
-    TextMetrics fTextMetrics;
-    bool fIsHardBreak;
-    size_t fGlyphCount;
-};
-}; // namespace text
-} // namespace skia
-#endif
diff --git a/experimental/sktext/tests/SelectableText.cpp b/experimental/sktext/tests/SelectableText.cpp
deleted file mode 100644
index 46a4620..0000000
--- a/experimental/sktext/tests/SelectableText.cpp
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright 2021 Google LLC.
-#include "include/core/SkBitmap.h"
-#include "include/core/SkCanvas.h"
-#include "include/core/SkColor.h"
-#include "include/core/SkEncodedImageFormat.h"
-#include "include/core/SkFontMgr.h"
-#include "include/core/SkFontStyle.h"
-#include "include/core/SkImageEncoder.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkPoint.h"
-#include "include/core/SkRect.h"
-#include "include/core/SkRefCnt.h"
-#include "include/core/SkScalar.h"
-#include "include/core/SkSpan.h"
-#include "include/core/SkStream.h"
-#include "include/core/SkString.h"
-#include "include/core/SkTypeface.h"
-#include "include/core/SkTypes.h"
-#include "tests/Test.h"
-#include "tools/Resources.h"
-
-#include "experimental/sktext/include/Text.h"
-#include "experimental/sktext/src/Paint.h"
-
-#include <string.h>
-#include <algorithm>
-#include <limits>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-struct GrContextOptions;
-
-#define VeryLongCanvasWidth 1000000
-#define TestCanvasWidth 1000
-#define TestCanvasHeight 600
-
-#if defined(SK_BUILD_FOR_WIN)
-#define DEF_TEST(name, reporter) \
-static void test_##name(skiatest::Reporter* reporter, const GrContextOptions&); \
-skiatest::TestRegistry name##TestRegistry(skiatest::Test(#name, false, test_##name)); \
-void test_##name(skiatest::Reporter* reporter, const GrContextOptions&) { /* SkDebugf("Disabled:"#name "\n"); */ } \
-void disabled_##name(skiatest::Reporter* reporter, const GrContextOptions&)
-#endif
-
-using namespace skia::text;
-
-namespace {
-    bool operator==(SkSpan<const char16_t> a, SkSpan<const char16_t> b) {
-        if (a.size() != b.size()) {
-            return false;
-        }
-        for (size_t i = 0; i < a.size(); ++i) {
-            if (a[i] != b[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
-
-struct TestLine {
-    size_t index;
-    TextRange lineText;
-    bool hardBreak;
-    SkRect bounds;
-    GlyphRange trailingSpaces;
-    Range<RunIndex> runRange;
-    size_t glyphCount;
-};
-
-struct TestRun {
-    const SkFont& font;
-    TextRange textRange;        // Currently we make sure that the run edges are the grapheme cluster edges
-    SkRect bounds;              // bounds contains the physical boundaries of the run
-    int trailingSpaces;         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-    SkSpan<const uint16_t> glyphs;
-    SkSpan<const SkPoint> positions;
-    SkSpan<const TextIndex> clusters;
-};
-
-class TestVisitor : public Visitor {
-public:
-    void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) override {
-        SkASSERT(fTestLines.size() == index);
-        fTestLines.push_back({ index, lineText, hardBreak, bounds, EMPTY_RANGE, Range<RunIndex>(fTestRuns.size(), fTestRuns.size()) });
-    }
-    void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) override {
-        SkASSERT(fTestLines.size() == index + 1);
-        fTestLines.back().trailingSpaces = trailingSpaces;
-        fTestLines.back().runRange.fEnd = fTestRuns.size();
-        fTestLines.back().glyphCount = glyphCount;
-    }
-    void onGlyphRun(const SkFont& font,
-                    TextRange textRange,        // Currently we make sure that the run edges are the grapheme cluster edges
-                    SkRect bounds,              // bounds contains the physical boundaries of the run
-                    int trailingSpaces,         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-                    int glyphCount,             // Just the number of glyphs
-                    const uint16_t glyphs[],
-                    const SkPoint positions[],        // Positions relative to the line
-                    const TextIndex clusters[]) override
-    {
-        fTestRuns.push_back({font, textRange, bounds, trailingSpaces,
-                            SkSpan<const uint16_t>(&glyphs[0], glyphCount),
-                            SkSpan<const SkPoint>(&positions[0], glyphCount + 1),
-                            SkSpan<const TextIndex>(&clusters[0], glyphCount + 1),
-                            });
-    }
-    void onPlaceholder(TextRange, const SkRect& bounds) override { }
-
-    std::vector<TestLine> fTestLines;
-    std::vector<TestRun> fTestRuns;
-};
-
-DEF_TEST(SkText_SelectableText_Bounds, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Leading spaces\nTrailing spaces    \nLong text with collapsed      spaces inside wrapped into few lines");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-    auto selectableText = wrappedText->prepareToEdit(&unicodeText);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    REPORTER_ASSERT(reporter, selectableText->countLines() == 5);
-    for (LineIndex lineIndex = 0; lineIndex < selectableText->countLines(); ++lineIndex) {
-        auto& testLine = testVisitor.fTestLines[lineIndex];
-        auto boxLine = selectableText->getLine(lineIndex);
-        SkScalar left = boxLine.fBounds.fLeft;
-        for (auto& box : boxLine.fBoxGlyphs) {
-            REPORTER_ASSERT(reporter, boxLine.fBounds.contains(box) || box.isEmpty());
-            REPORTER_ASSERT(reporter, left <= box.fLeft);
-            left = box.fRight;
-        }
-
-        GlyphIndex trailingSpaces = boxLine.fBoxGlyphs.size() - 1;
-        for (RunIndex runIndex = testLine.runRange.fEnd; runIndex > testLine.runRange.fStart; --runIndex) {
-            auto& testRun = testVisitor.fTestRuns[runIndex - 1];
-            if (testRun.trailingSpaces == 0) {
-                trailingSpaces -= testRun.glyphs.size();
-            } else {
-                trailingSpaces -= (testRun.glyphs.size() - testRun.trailingSpaces);
-                break;
-            }
-        }
-
-        REPORTER_ASSERT(reporter, boxLine.fTrailingSpacesEnd == testLine.trailingSpaces.fEnd);
-        REPORTER_ASSERT(reporter, boxLine.fTextEnd == trailingSpaces);
-        REPORTER_ASSERT(reporter, boxLine.fTextRange == testLine.lineText);
-        REPORTER_ASSERT(reporter, boxLine.fIndex == lineIndex);
-        REPORTER_ASSERT(reporter, boxLine.fIsHardBreak == testLine.hardBreak);
-        REPORTER_ASSERT(reporter, boxLine.fBounds == testLine.bounds);
-    }
-}
-
-DEF_TEST(SkText_SelectableText_Navigation_FirstLast, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Leading spaces\nTrailing spaces    \nLong text with collapsed      spaces inside wrapped into few lines");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-    auto selectableText = wrappedText->prepareToEdit(&unicodeText);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    // First position
-    auto firstLine = testVisitor.fTestLines.front();
-    auto firstRun = testVisitor.fTestRuns.front();
-    auto firstPosition = selectableText->firstPosition(PositionType::kGraphemeCluster);
-    REPORTER_ASSERT(reporter, firstPosition.fLineIndex == 0);
-    REPORTER_ASSERT(reporter, firstPosition.fTextRange == TextRange(0, 0));
-    REPORTER_ASSERT(reporter, firstPosition.fGlyphRange == GlyphRange(0, 0));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(firstPosition.fBoundaries.fLeft, 0.0f));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(firstPosition.fBoundaries.fTop, 0.0f));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(firstPosition.fBoundaries.width(), 0.0f));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(firstPosition.fBoundaries.height(), firstLine.bounds.height()));
-
-    // Last position
-    auto lastLine = testVisitor.fTestLines.back();
-    auto lastRun = testVisitor.fTestRuns.back();
-    auto lastPosition = selectableText->lastPosition(PositionType::kGraphemeCluster);
-    REPORTER_ASSERT(reporter, lastPosition.fLineIndex == testVisitor.fTestLines.size() - 1);
-    REPORTER_ASSERT(reporter, lastPosition.fTextRange == TextRange(utf16.size(), utf16.size()));
-    REPORTER_ASSERT(reporter, lastPosition.fGlyphRange == GlyphRange(lastRun.glyphs.size(), lastRun.glyphs.size()));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lastPosition.fBoundaries.fLeft, lastRun.positions[lastRun.glyphs.size()].fX));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lastPosition.fBoundaries.fTop, lastLine.bounds.fTop));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lastPosition.fBoundaries.width(), 0.0f));
-    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(lastPosition.fBoundaries.height(), lastLine.bounds.height()));
-}
-
-DEF_TEST(SkText_SelectableText_ScanRightByGraphemeClusters, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Small Text   \n");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-    auto selectableText = wrappedText->prepareToEdit(&unicodeText);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    auto firstPosition = selectableText->firstPosition(PositionType::kGraphemeCluster);
-    auto lastPosition = selectableText->lastPosition(PositionType::kGraphemeCluster);
-
-    auto position = firstPosition;
-    while (!(position.fGlyphRange == lastPosition.fGlyphRange)) {
-        auto next = selectableText->nextPosition(position);
-        REPORTER_ASSERT(reporter, position.fTextRange.fEnd == next.fTextRange.fStart);
-        if (position.fLineIndex == next.fLineIndex - 1) {
-            auto line = selectableText->getLine(next.fLineIndex);
-            REPORTER_ASSERT(reporter, next.fGlyphRange.fStart == 0);
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(next.fBoundaries.fLeft, 0.0f));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(next.fBoundaries.fTop, line.fBounds.fTop));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(next.fBoundaries.height(), line.fBounds.height()));
-        } else {
-            REPORTER_ASSERT(reporter, position.fLineIndex == next.fLineIndex);
-            REPORTER_ASSERT(reporter, position.fGlyphRange.fEnd == next.fGlyphRange.fStart);
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.fRight, next.fBoundaries.fLeft));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.fTop, next.fBoundaries.fTop));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.height(), next.fBoundaries.height()));
-        }
-        position = next;
-    }
-}
-
-DEF_TEST(SkText_SelectableText_ScanLeftByGraphemeClusters, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Small Text   \n");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-    auto selectableText = wrappedText->prepareToEdit(&unicodeText);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    auto firstPosition = selectableText->firstPosition(PositionType::kGraphemeCluster);
-    auto lastPosition = selectableText->lastPosition(PositionType::kGraphemeCluster);
-
-    auto position = lastPosition;
-    while (!(position.fGlyphRange == firstPosition.fGlyphRange)) {
-        auto prev = selectableText->previousPosition(position);
-        REPORTER_ASSERT(reporter, position.fTextRange.fEnd == prev.fTextRange.fStart);
-        if (position.fLineIndex == prev.fLineIndex + 1) {
-            auto line = selectableText->getLine(prev.fLineIndex);
-            REPORTER_ASSERT(reporter, prev.fGlyphRange.fEnd == line.fBoxGlyphs.size());
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prev.fBoundaries.fRight, line.fBounds.fRight));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prev.fBoundaries.fTop, line.fBounds.fTop));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prev.fBoundaries.height(), line.fBounds.height()));
-        } else {
-            REPORTER_ASSERT(reporter, position.fLineIndex == prev.fLineIndex);
-            REPORTER_ASSERT(reporter, position.fGlyphRange.fStart == prev.fGlyphRange.fEnd);
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.fLeft, prev.fBoundaries.fRight));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.fTop, prev.fBoundaries.fTop));
-            REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fBoundaries.height(), prev.fBoundaries.height()));
-        }
-        position = prev;
-    }
-}
-
-DEF_TEST(SkText_SelectableText_Navigation_UpDown, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Leading spaces\nTrailing spaces    \nLong text with collapsed      spaces inside wrapped into few lines");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-    auto selectableText = wrappedText->prepareToEdit(&unicodeText);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    // Upper position
-    auto position = selectableText->lastInLinePosition(PositionType::kGraphemeCluster, 0);
-    while (position.fLineIndex > 0) {
-        auto down = selectableText->downPosition(position);
-        REPORTER_ASSERT(reporter, position.fLineIndex + 1 == down.fLineIndex);
-        REPORTER_ASSERT(reporter, position.fBoundaries.centerX() >= down.fBoundaries.centerX());
-        position = down;
-    }
-
-    // Down position
-    position = selectableText->lastInLinePosition(PositionType::kGraphemeCluster, selectableText->countLines() - 1);
-    while (position.fLineIndex < selectableText->countLines() - 1) {
-        auto down = selectableText->downPosition(position);
-        REPORTER_ASSERT(reporter, position.fLineIndex - 1 == down.fLineIndex);
-        REPORTER_ASSERT(reporter, position.fBoundaries.centerX() >= down.fBoundaries.centerX());
-        position = down;
-    }
-}
-
diff --git a/experimental/sktext/tests/ShapedText.cpp b/experimental/sktext/tests/ShapedText.cpp
deleted file mode 100644
index 22a2b71..0000000
--- a/experimental/sktext/tests/ShapedText.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2021 Google LLC.
-#include "include/core/SkBitmap.h"
-#include "include/core/SkCanvas.h"
-#include "include/core/SkColor.h"
-#include "include/core/SkEncodedImageFormat.h"
-#include "include/core/SkFontMgr.h"
-#include "include/core/SkFontStyle.h"
-#include "include/core/SkImageEncoder.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkPoint.h"
-#include "include/core/SkRect.h"
-#include "include/core/SkRefCnt.h"
-#include "include/core/SkScalar.h"
-#include "include/core/SkSpan.h"
-#include "include/core/SkStream.h"
-#include "include/core/SkString.h"
-#include "include/core/SkTypeface.h"
-#include "include/core/SkTypes.h"
-#include "tests/Test.h"
-#include "tools/Resources.h"
-
-#include "experimental/sktext/include/Text.h"
-#include "experimental/sktext/src/Paint.h"
-
-#include <string.h>
-#include <algorithm>
-#include <limits>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-struct GrContextOptions;
-
-#define VeryLongCanvasWidth 1000000
-#define TestCanvasWidth 1000
-#define TestCanvasHeight 600
-
-#if defined(SK_BUILD_FOR_WIN)
-#define DEF_TEST(name, reporter) \
-static void test_##name(skiatest::Reporter* reporter, const GrContextOptions&); \
-skiatest::TestRegistry name##TestRegistry(skiatest::Test(#name, false, test_##name)); \
-void test_##name(skiatest::Reporter* reporter, const GrContextOptions&) { /* SkDebugf("Disabled:"#name "\n"); */ } \
-void disabled_##name(skiatest::Reporter* reporter, const GrContextOptions&)
-#endif
-
-using namespace skia::text;
-
-namespace {
-    bool operator==(SkSpan<const char16_t> a, SkSpan<const char16_t> b) {
-        if (a.size() != b.size()) {
-            return false;
-        }
-        for (size_t i = 0; i < a.size(); ++i) {
-            if (a[i] != b[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
-
-DEF_TEST(SkText_ShapedText_LTR, reporter) {
-    TrivialFontChain* fontChain = new TrivialFontChain("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"Hello world\nHello world");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), sk_ref_sp<FontChain>(fontChain));
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto logicalRuns = shapedText->getLogicalRuns();
-
-    auto newLine = utf16.find_first_of(u"\n");
-    REPORTER_ASSERT(reporter, logicalRuns.size() == 3);
-    REPORTER_ASSERT(reporter, logicalRuns[1].getRunType() == LogicalRunType::kLineBreak);
-    REPORTER_ASSERT(reporter, logicalRuns[1].getTextRange() == TextRange(newLine, newLine + 1));
-}
-
-DEF_TEST(SkText_ShapedText_RTL, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"\u202EHELLO WORLD\nHELLO WORLD");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto logicalRuns = shapedText->getLogicalRuns();
-
-    auto newLine = utf16.find_first_of(u"\n");
-    REPORTER_ASSERT(reporter, logicalRuns.size() == 3);
-    REPORTER_ASSERT(reporter, logicalRuns[1].getRunType() == LogicalRunType::kLineBreak);
-    REPORTER_ASSERT(reporter, logicalRuns[1].getTextRange() == TextRange(newLine, newLine + 1));
-}
diff --git a/experimental/sktext/tests/UnicodeText.cpp b/experimental/sktext/tests/UnicodeText.cpp
deleted file mode 100644
index ca1879c..0000000
--- a/experimental/sktext/tests/UnicodeText.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 Google LLC.
-#include "include/core/SkBitmap.h"
-#include "include/core/SkCanvas.h"
-#include "include/core/SkColor.h"
-#include "include/core/SkEncodedImageFormat.h"
-#include "include/core/SkFontMgr.h"
-#include "include/core/SkFontStyle.h"
-#include "include/core/SkImageEncoder.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkPoint.h"
-#include "include/core/SkRect.h"
-#include "include/core/SkRefCnt.h"
-#include "include/core/SkScalar.h"
-#include "include/core/SkSpan.h"
-#include "include/core/SkStream.h"
-#include "include/core/SkString.h"
-#include "include/core/SkTypeface.h"
-#include "include/core/SkTypes.h"
-#include "tests/Test.h"
-#include "tools/Resources.h"
-
-#include "experimental/sktext/include/Text.h"
-
-#include <string.h>
-#include <algorithm>
-#include <limits>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-struct GrContextOptions;
-
-#define VeryLongCanvasWidth 1000000
-#define TestCanvasWidth 1000
-#define TestCanvasHeight 600
-
-#if defined(SK_BUILD_FOR_WIN)
-#define DEF_TEST(name, reporter) \
-static void test_##name(skiatest::Reporter* reporter, const GrContextOptions&); \
-skiatest::TestRegistry name##TestRegistry(skiatest::Test(#name, false, test_##name)); \
-void test_##name(skiatest::Reporter* reporter, const GrContextOptions&) { /* SkDebugf("Disabled:"#name "\n"); */ } \
-void disabled_##name(skiatest::Reporter* reporter, const GrContextOptions&)
-#endif
-
-using namespace skia::text;
-
-namespace {
-    bool operator==(SkSpan<const char16_t> a, SkSpan<const char16_t> b) {
-        if (a.size() != b.size()) {
-            return false;
-        }
-        for (size_t i = 0; i < a.size(); ++i) {
-            if (a[i] != b[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
-
-DEF_TEST(SkText_UnicodeText_Flags, reporter) {
-    REPORTER_ASSERT(reporter, true);
-    //                       01234567890  1234567890
-    std::u16string utf16(u"Hello word\nHello world");
-    SkString utf8("Hello word\nHello world");
-    UnicodeText unicodeText16(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    UnicodeText unicodeText8(SkUnicode::Make(), utf8);
-
-    REPORTER_ASSERT(reporter, unicodeText16.getText16() == unicodeText8.getText16(), "UTF16 and UTF8 texts should be the same\n");
-    auto lineBreak = utf16.find_first_of(u"\n");
-    for (size_t i = 0; i < unicodeText16.getText16().size(); ++i) {
-        if (i == lineBreak) {
-            REPORTER_ASSERT(reporter, unicodeText16.hasProperty(i, CodeUnitFlags::kHardLineBreakBefore), "Pos16 %d should point to hard line break\n", lineBreak);
-            REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(i, CodeUnitFlags::kHardLineBreakBefore), "Pos8 %d should point to hard line break\n", lineBreak);
-        } else {
-            REPORTER_ASSERT(reporter, unicodeText16.hasProperty(i, CodeUnitFlags::kGraphemeStart), "Pos16 %d should be a grapheme start\n", i);
-            REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(i, CodeUnitFlags::kGraphemeStart), "Pos8 %d should be a grapheme start\n", i);
-        }
-    }
-
-    auto space1 = utf16.find_first_of(u" ");
-    auto space2 = utf16.find_last_of(u" ");
-
-    REPORTER_ASSERT(reporter, unicodeText16.hasProperty(space1, CodeUnitFlags::kPartOfWhiteSpace), "Pos16 %d should be a part of whitespaces\n", space1);
-    REPORTER_ASSERT(reporter, unicodeText16.hasProperty(space1 + 1, CodeUnitFlags::kSoftLineBreakBefore), "Pos16 %d should have soft line break before\n", space1 + 1);
-    REPORTER_ASSERT(reporter, unicodeText16.hasProperty(space2, CodeUnitFlags::kPartOfWhiteSpace), "Pos16 %d should be a part of whitespaces\n", space2);
-    REPORTER_ASSERT(reporter, unicodeText16.hasProperty(space2 + 1, CodeUnitFlags::kSoftLineBreakBefore), "Pos16 %d should have soft line break before\n", space2 + 1);
-
-    REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(space1, CodeUnitFlags::kPartOfWhiteSpace), "Pos8 %d should be a part of whitespaces\n", space1);
-    REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(space1 + 1, CodeUnitFlags::kSoftLineBreakBefore), "Pos8 %d should have soft line break before\n", space1 + 1);
-    REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(space2, CodeUnitFlags::kPartOfWhiteSpace), "Pos8 %d should be a part of whitespaces\n", space2);
-    REPORTER_ASSERT(reporter, unicodeText8 .hasProperty(space2 + 1, CodeUnitFlags::kSoftLineBreakBefore), "Pos8 %d should have soft line break before\n", space2 + 1);
-}
-
-// TODO: Test RTL text
diff --git a/experimental/sktext/tests/WrappedText.cpp b/experimental/sktext/tests/WrappedText.cpp
deleted file mode 100644
index 091d06e..0000000
--- a/experimental/sktext/tests/WrappedText.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2021 Google LLC.
-#include "include/core/SkBitmap.h"
-#include "include/core/SkCanvas.h"
-#include "include/core/SkColor.h"
-#include "include/core/SkEncodedImageFormat.h"
-#include "include/core/SkFontMgr.h"
-#include "include/core/SkFontStyle.h"
-#include "include/core/SkImageEncoder.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkPoint.h"
-#include "include/core/SkRect.h"
-#include "include/core/SkRefCnt.h"
-#include "include/core/SkScalar.h"
-#include "include/core/SkSpan.h"
-#include "include/core/SkStream.h"
-#include "include/core/SkString.h"
-#include "include/core/SkTypeface.h"
-#include "include/core/SkTypes.h"
-#include "tests/Test.h"
-#include "tools/Resources.h"
-
-#include "experimental/sktext/include/Text.h"
-#include "experimental/sktext/src/Paint.h"
-
-#include <string.h>
-#include <algorithm>
-#include <limits>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-struct GrContextOptions;
-
-#define VeryLongCanvasWidth 1000000
-#define TestCanvasWidth 1000
-#define TestCanvasHeight 600
-
-#if defined(SK_BUILD_FOR_WIN)
-#define DEF_TEST(name, reporter) \
-static void test_##name(skiatest::Reporter* reporter, const GrContextOptions&); \
-skiatest::TestRegistry name##TestRegistry(skiatest::Test(#name, false, test_##name)); \
-void test_##name(skiatest::Reporter* reporter, const GrContextOptions&) { /* SkDebugf("Disabled:"#name "\n"); */ } \
-void disabled_##name(skiatest::Reporter* reporter, const GrContextOptions&)
-#endif
-using namespace skia::text;
-
-namespace {
-    bool operator==(SkSpan<const char16_t> a, SkSpan<const char16_t> b) {
-        if (a.size() != b.size()) {
-            return false;
-        }
-        for (size_t i = 0; i < a.size(); ++i) {
-            if (a[i] != b[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
-
-struct TestLine {
-    size_t index;
-    TextRange lineText;
-    bool hardBreak;
-    SkRect bounds;
-    GlyphRange trailingSpaces;
-    Range<RunIndex> runRange;
-};
-
-struct TestRun {
-    const SkFont& font;
-    TextRange textRange;        // Currently we make sure that the run edges are the grapheme cluster edges
-    SkRect bounds;              // bounds contains the physical boundaries of the run
-    int trailingSpaces;         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-    SkSpan<const uint16_t> glyphs;
-    SkSpan<const SkPoint> positions;
-    SkSpan<const TextIndex> clusters;
-};
-
-class TestVisitor : public Visitor {
-public:
-    void onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) override {
-        SkASSERT(fTestLines.size() == index);
-        fTestLines.push_back({ index, lineText, hardBreak, bounds, EMPTY_RANGE, Range<RunIndex>(fTestRuns.size(), fTestRuns.size()) });
-    }
-    void onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) override {
-        SkASSERT(fTestLines.size() == index + 1);
-        fTestLines.back().trailingSpaces = trailingSpaces;
-        fTestLines.back().runRange.fEnd = fTestRuns.size();
-    }
-    void onGlyphRun(const SkFont& font,
-                    TextRange textRange,        // Currently we make sure that the run edges are the grapheme cluster edges
-                    SkRect bounds,              // bounds contains the physical boundaries of the run
-                    int trailingSpaces,         // Depending of TextDirection it goes right to the end (LTR) or left to the start (RTL)
-                    int glyphCount,             // Just the number of glyphs
-                    const uint16_t glyphs[],
-                    const SkPoint positions[],        // Positions relative to the line
-                    const TextIndex clusters[]) override
-    {
-        fTestRuns.push_back({font, textRange, bounds, trailingSpaces,
-                            SkSpan<const uint16_t>(&glyphs[0], glyphCount),
-                            SkSpan<const SkPoint>(&positions[0], glyphCount + 1),
-                            SkSpan<const TextIndex>(&clusters[0], glyphCount + 1),
-                            });
-    }
-    void onPlaceholder(TextRange, const SkRect& bounds) override { }
-
-    std::vector<TestLine> fTestLines;
-    std::vector<TestRun> fTestRuns;
-};
-
-
-DEF_TEST(SkText_WrappedText_Spaces, reporter) {
-    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40.0f, SkFontStyle::Normal());
-    if (fontChain->empty()) return;
-
-    std::u16string utf16(u"    Leading spaces\nTrailing spaces    \nLong text with collapsed      spaces inside wrapped into few lines");
-    UnicodeText unicodeText(SkUnicode::Make(), SkSpan<uint16_t>((uint16_t*)utf16.data(), utf16.size()));
-    if (!unicodeText.getUnicode()) return;
-
-    FontBlock fontBlock(utf16.size(), fontChain);
-    auto shapedText = unicodeText.shape(SkSpan<FontBlock>(&fontBlock, 1), TextDirection::kLtr);
-    auto wrappedText = shapedText->wrap(&unicodeText, 440.0f, 500.0f);
-
-    TestVisitor testVisitor;
-    wrappedText->visit(&testVisitor);
-
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines.size() == 5);
-    REPORTER_ASSERT(reporter, testVisitor.fTestRuns.size() == 5);
-
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].trailingSpaces.width() == 0);
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].trailingSpaces.width() == 4);
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[2].trailingSpaces.width() == 6);
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[3].trailingSpaces.width() == 1);
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[4].trailingSpaces.width() == 0);
-
-    auto break1 = utf16.find_first_of(u"\n");
-    auto break2 = utf16.find_last_of(u"\n");
-
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[0].lineText.width() == break1);
-    REPORTER_ASSERT(reporter, testVisitor.fTestLines[1].lineText.width() == break2 - break1 - 1);
-
-    RunIndex runIndex = 0;
-    SkScalar verticalOffset = 0.0f;
-    for (int lineIndex = 0; lineIndex < testVisitor.fTestLines.size(); ++lineIndex) {
-        auto& line = testVisitor.fTestLines[lineIndex];
-        REPORTER_ASSERT(reporter, line.runRange == Range<RunIndex>(runIndex, runIndex + 1));
-        REPORTER_ASSERT(reporter, line.runRange.width() == 1);
-        auto& run = testVisitor.fTestRuns[runIndex];
-        REPORTER_ASSERT(reporter, line.lineText == run.textRange);
-        REPORTER_ASSERT(reporter, runIndex <= 1 ? line.hardBreak : !line.hardBreak);
-        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(verticalOffset, line.bounds.fTop));
-
-        // There is only one line that is wrapped and it has enough trailing spaces to exceed the line width
-        REPORTER_ASSERT(reporter, (line.index == 2 ? line.bounds.width() > 440.0f: line.bounds.width() < 440.0f));
-        verticalOffset = line.bounds.fBottom;
-        ++runIndex;
-    }
-}