CPP Text Editor

The very first version:
1. Moves cursort up, down, left, right by grapheme/glyph clusters
2. Breaks lines by grapheme/glyph cluster
3. Just started!

Change-Id: Ib2881794ff33af9e428828f3a9e2d3b54946fa8f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/417476
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 5bbf761..7bdca07 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2910,6 +2910,10 @@
       is_shared_library = is_android
       deps = [ "modules/skplaintexteditor:editor_app" ]
     }
+    test_app("text_editor") {
+      is_shared_library = is_android
+      deps = [ "experimental/sktext:text_editor" ]
+    }
   }
 
   skia_executable("image_diff_metric") {
diff --git a/experimental/sktext/BUILD.gn b/experimental/sktext/BUILD.gn
index d0bc701..b725296 100644
--- a/experimental/sktext/BUILD.gn
+++ b/experimental/sktext/BUILD.gn
@@ -74,13 +74,26 @@
 
     source_set("samples") {
       testonly = true
-      sources = [ "samples/Text.cpp" ]
+      sources = [
+        "editor/Editor.cpp",
+        "samples/Text.cpp",
+      ]
       deps = [
         ":sktext",
         "../..:skia",
         "../../modules/skshaper",
       ]
     }
+
+    skia_source_set("text_editor") {
+      testonly = true
+      sources = [
+        "editor/App.cpp",
+        "editor/Editor.cpp",
+      ]
+      public_deps = [ "../..:sk_app" ]
+      deps = [ ":sktext" ]
+    }
   }
 } else {
   group("sktext") {
diff --git a/experimental/sktext/editor/App.cpp b/experimental/sktext/editor/App.cpp
new file mode 100644
index 0000000..3aa1aed
--- /dev/null
+++ b/experimental/sktext/editor/App.cpp
@@ -0,0 +1,69 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+// Proof of principle of a text editor written with Skia & SkShaper.
+// https://bugs.skia.org/9020
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkSurface.h"
+#include "include/core/SkTime.h"
+
+#include "tools/sk_app/Application.h"
+#include "tools/sk_app/Window.h"
+#include "tools/skui/ModifierKey.h"
+
+#include "experimental/sktext/editor/Editor.h"
+
+#include "third_party/icu/SkLoadICU.h"
+
+#include <fstream>
+#include <memory>
+
+namespace skia {
+namespace text {
+struct EditorApplication : public sk_app::Application {
+    std::unique_ptr<sk_app::Window> fWindow;
+    std::unique_ptr<Editor> fLayer;
+    double fNextTime = -DBL_MAX;
+
+    EditorApplication(std::unique_ptr<sk_app::Window> win) : fWindow(std::move(win)) {}
+
+    bool init(const char* path) {
+        fWindow->attach(sk_app::Window::kRaster_BackendType);
+
+        fLayer = Editor::MakeDemo(fWindow->width());
+
+        fWindow->pushLayer(fLayer.get());
+        fWindow->setTitle("Editor");
+
+        fLayer->onResize(fWindow->width(), fWindow->height());
+        fWindow->show();
+        return true;
+    }
+    ~EditorApplication() override { fWindow->detach(); }
+
+    void onIdle() override {
+        double now = SkTime::GetNSecs();
+        if (now >= fNextTime) {
+            constexpr double kHalfPeriodNanoSeconds = 0.5 * 1e9;
+            fNextTime = now + kHalfPeriodNanoSeconds;
+            fLayer->blink();
+            fWindow->inval();
+        }
+    }
+};
+} // namespace text
+} // namespace skia
+
+sk_app::Application* sk_app::Application::Create(int argc, char** argv, void* dat) {
+    if (!SkLoadICU()) {
+        SK_ABORT("SkLoadICU failed.");
+    }
+    std::unique_ptr<sk_app::Window> win(sk_app::Window::CreateNativeWindow(dat));
+    if (!win) {
+        SK_ABORT("CreateNativeWindow failed.");
+    }
+    std::unique_ptr<skia::text::EditorApplication> app(new skia::text::EditorApplication(std::move(win)));
+    (void)app->init(argc > 1 ? argv[1] : nullptr);
+    return app.release();
+}
diff --git a/experimental/sktext/editor/Editor.cpp b/experimental/sktext/editor/Editor.cpp
new file mode 100644
index 0000000..f1f5faf
--- /dev/null
+++ b/experimental/sktext/editor/Editor.cpp
@@ -0,0 +1,235 @@
+// Copyright 2021 Google LLC.
+#include "experimental/sktext/editor/Editor.h"
+#include "experimental/sktext/src/Paint.h"
+
+namespace skia {
+namespace text {
+
+const TextDirection TEXT_DIRECTION = TextDirection::kLtr;
+const TextAlign TEXT_ALIGN = TextAlign::kLeft;
+const SkColor DEFAULT_FOREGROUND = SK_ColorBLACK;
+const SkScalar DEFAULT_CURSOR_WIDTH = 2;
+const SkColor DEFAULT_CURSOR_COLOR = SK_ColorGRAY;
+
+std::unique_ptr<Cursor> Cursor::Make() { return std::unique_ptr<Cursor>(new Cursor()); }
+
+Cursor::Cursor() {
+    fLinePaint.setColor(SK_ColorGRAY);
+    fLinePaint.setAntiAlias(true);
+
+    fRectPaint.setColor(DEFAULT_CURSOR_COLOR);
+    fRectPaint.setStyle(SkPaint::kStroke_Style);
+    fRectPaint.setStrokeWidth(2);
+    fRectPaint.setAntiAlias(true);
+
+    fXY = SkPoint::Make(0, 0);
+    fSize = SkSize::Make(0, 0);
+    fBlink = true;
+}
+
+void Cursor::paint(SkCanvas* canvas, SkPoint xy) {
+
+    if (fBlink) {
+       canvas->drawRect(SkRect::MakeXYWH(fXY.fX + xy.fX, fXY.fY + xy.fY, DEFAULT_CURSOR_WIDTH, fSize.fHeight),
+                fRectPaint);
+    } else {
+        canvas->drawLine(fXY + xy, fXY + xy + SkPoint::Make(1, fSize.fHeight), fLinePaint);
+    }
+}
+
+std::unique_ptr<Editor> Editor::Make(std::u16string text, SkSize size, SkSpan<Block> fontBlocks) {
+    return std::unique_ptr<Editor>(new Editor(text, size, fontBlocks));
+}
+
+Editor::Editor(std::u16string text, SkSize size, SkSpan<Block> fontBlocks) {
+    fParent = nullptr;
+    fText = std::move(text);
+    fCursor = Cursor::Make();
+    fMouse = std::make_unique<Mouse>();
+    fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
+    fShapedText = fUnicodeText->shape(fontBlocks, TEXT_DIRECTION);
+    fWrappedText = fShapedText->wrap(size.width(), size.height(), fUnicodeText->getUnicode());
+    fFormattedText = fWrappedText->format(TEXT_ALIGN, TEXT_DIRECTION);
+
+    auto endOfText = fFormattedText->indexToAdjustedGraphemePosition(fText.size());
+    auto rect = std::get<3>(endOfText);
+    fCursor->place(SkPoint::Make(rect.fLeft, rect.fTop),
+                   SkSize::Make(std::max(DEFAULT_CURSOR_WIDTH, rect.width()), rect.height()));
+                   //SkSize::Make(DEFAULT_CURSOR_WIDTH, rect.height()));
+}
+
+bool Editor::moveCursor(skui::Key key) {
+    auto cursorPosition = fCursor->getCenterPosition();
+    auto textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
+
+    if (key == skui::Key::kLeft) {
+        if (textIndex == 0) {
+            return false;
+        } else {
+            --textIndex;
+        }
+    } else if (key == skui::Key::kRight) {
+        if (textIndex >= fText.size()) {
+            return false;
+        } else {
+            ++textIndex;
+        }
+    } else if (key == skui::Key::kHome) {
+        textIndex = 0;
+    } else if (key == skui::Key::kEnd) {
+        textIndex = fText.size();
+    } else if (key == skui::Key::kUp) {
+        auto currentPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
+        auto line = std::get<0>(currentPosition);
+        auto lineIndex = fFormattedText->lineIndex(line);
+        if (lineIndex == 0) {
+            return false;
+        }
+        cursorPosition.offset(0, - line->getMetrics().height());
+        textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
+    } else if (key == skui::Key::kDown) {
+        auto currentPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
+        auto line = std::get<0>(currentPosition);
+        auto lineIndex = fFormattedText->lineIndex(line);
+        if (lineIndex == fFormattedText->countLines()) {
+            return false;
+        }
+        cursorPosition.offset(0, line->getMetrics().height());
+        textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
+    }
+    auto nextPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
+    auto rect = std::get<3>(nextPosition);
+    fCursor->place(SkPoint::Make(rect.fLeft, rect.fTop), SkSize::Make(rect.width(), rect.height()));
+
+    return true;
+}
+
+void Editor::onPaint(SkSurface* surface) {
+    SkCanvas* canvas = surface->getCanvas();
+    SkAutoCanvasRestore acr(canvas, true);
+    canvas->clipRect({0, 0, (float)fWidth, (float)fHeight});
+    canvas->drawColor(SK_ColorWHITE);
+    this->paint(canvas, SkPoint::Make(0, 0));
+}
+
+void Editor::onResize(int width, int height) {
+    if (SkISize{fWidth, fHeight} != SkISize{width, height}) {
+        fHeight = height;
+        if (width != fWidth) {
+            fWidth = width;
+        }
+        this->invalidate();
+    }
+}
+
+bool Editor::onChar(SkUnichar c, skui::ModifierKey modi) {
+    using sknonstd::Any;
+    modi &= ~skui::ModifierKey::kFirstPress;
+    if (!Any(modi & (skui::ModifierKey::kControl | skui::ModifierKey::kOption |
+                     skui::ModifierKey::kCommand))) {
+        /*
+        if (((unsigned)c < 0x7F && (unsigned)c >= 0x20) || c == '\n') {
+            char ch = (char)c;
+            fEditor.insert(fTextPos, &ch, 1);
+            #ifdef SK_EDITOR_DEBUG_OUT
+            SkDebugf("insert: %X'%c'\n", (unsigned)c, ch);
+            #endif  // SK_EDITOR_DEBUG_OUT
+            return this->moveCursor(Editor::Movement::kRight);
+        }
+        */
+    }
+    static constexpr skui::ModifierKey kCommandOrControl =
+            skui::ModifierKey::kCommand | skui::ModifierKey::kControl;
+    if (Any(modi & kCommandOrControl) && !Any(modi & ~kCommandOrControl)) {
+        return false;
+    }
+    return false;
+}
+
+bool Editor::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {
+    if (state != skui::InputState::kDown) {
+        return false;
+    }
+    using sknonstd::Any;
+    skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
+                                                skui::ModifierKey::kOption  |
+                                                skui::ModifierKey::kCommand);
+    //bool shift = Any(modifiers & (skui::ModifierKey::kShift));
+    if (!Any(ctrlAltCmd)) {
+        // no modifiers
+        switch (key) {
+            case skui::Key::kLeft:
+            case skui::Key::kRight:
+            case skui::Key::kUp:
+            case skui::Key::kDown:
+            case skui::Key::kHome:
+            case skui::Key::kEnd:
+                this->moveCursor(key);
+                break;
+            /*
+            case skui::Key::kDelete:
+                if (fMarkPos != Editor::TextPosition()) {
+                    (void)this->move(fEditor.remove(fMarkPos, fTextPos), false);
+                } else {
+                    auto pos = fEditor.move(Editor::Movement::kRight, fTextPos);
+                    (void)this->move(fEditor.remove(fTextPos, pos), false);
+                }
+                this->inval();
+                return true;
+            case skui::Key::kBack:
+                if (fMarkPos != Editor::TextPosition()) {
+                    (void)this->move(fEditor.remove(fMarkPos, fTextPos), false);
+                } else {
+                    auto pos = fEditor.move(Editor::Movement::kLeft, fTextPos);
+                    (void)this->move(fEditor.remove(fTextPos, pos), false);
+                }
+                this->inval();
+                return true;
+            case skui::Key::kOK:
+                return this->onChar('\n', modifiers);
+            */
+            default:
+                break;
+        }
+    }
+    return false;
+}
+
+void Editor::onBeginLine(TextRange, float baselineY) {}
+void Editor::onEndLine(TextRange, float baselineY) {}
+void Editor::onPlaceholder(TextRange, const SkRect& bounds) {}
+void Editor::onGlyphRun(SkFont font,
+                        TextRange textRange,
+                        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));
+    SkPaint foreground;
+    foreground.setColor(DEFAULT_FOREGROUND);
+    fCanvas->drawTextBlob(builder.make(), fXY.fX, fXY.fY, foreground);
+}
+
+void Editor::paint(SkCanvas* canvas, SkPoint xy) {
+    fCanvas = canvas;
+    fXY = xy;
+    this->fFormattedText->visit(this);
+
+    fCursor->paint(canvas, xy);
+}
+
+std::unique_ptr<Editor> Editor::MakeDemo(SkScalar width) {
+
+    std::u16string text0 = u"In a hole in the ground there lived a hobbit. Not a nasty, dirty, "
+                            "wet hole full of worms and oozy smells. This was a hobbit-hole and "
+                            "that means good food, a warm hearth, and all the comforts of home.";
+
+    sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40);
+    Block block(text0.size(), fontChain);
+    return Editor::Make(text0, SkSize::Make(width, SK_ScalarInfinity), SkSpan<Block>(&block, 1));
+}
+} // namespace text
+} // namespace skia
diff --git a/experimental/sktext/editor/Editor.h b/experimental/sktext/editor/Editor.h
new file mode 100644
index 0000000..2e9b010
--- /dev/null
+++ b/experimental/sktext/editor/Editor.h
@@ -0,0 +1,116 @@
+// Copyright 2021 Google LLC.
+#ifndef Editor_DEFINED
+#define Editor_DEFINED
+#include <sstream>
+#include "experimental/sktext/include/Text.h"
+#include "experimental/sktext/include/Types.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkSurface.h"
+#include "include/core/SkTime.h"
+#include "tools/sk_app/Application.h"
+#include "tools/sk_app/Window.h"
+#include "tools/skui/ModifierKey.h"
+
+namespace skia {
+namespace text {
+
+class Cursor {
+public:
+    static std::unique_ptr<Cursor> Make();
+    void place(SkPoint xy, SkSize size) {
+        fXY = xy;
+        fSize = size;
+    }
+
+    void blink() {
+        fBlink = !fBlink;
+    }
+
+    SkPoint getPosition() const { return fXY; }
+    SkPoint getCenterPosition() const {
+        return fXY + SkPoint::Make(0, fSize.fHeight / 2);
+    }
+
+    void paint(SkCanvas* canvas, SkPoint xy);
+
+private:
+    Cursor();
+    SkPaint fLinePaint;
+    SkPaint fRectPaint;
+    SkPoint fXY;
+    SkSize fSize;
+    bool fBlink;
+};
+
+class Mouse {
+
+};
+
+struct Style {
+    sk_sp<SkTypeface> fTypeface;
+    SkScalar fFontSize;
+    SkFontStyle fFontStyle;
+    SkColor fForeground;
+    size_t fLength;
+
+    std::string toString() {
+        SkString ff;
+        fTypeface->getFamilyName(&ff);
+        std::stringstream ss;
+        ss << ff.c_str() << ", "
+           << fFontSize
+           << (fFontStyle.slant() == SkFontStyle::Slant::kItalic_Slant ? ", italic " : "");
+        if (fFontStyle.weight() != SkFontStyle::Weight::kNormal_Weight) {
+            ss << ", " << fFontStyle.weight();
+        }
+        return ss.str();
+    }
+};
+
+class Editor : public sk_app::Window::Layer, FormattedText::Visitor {
+public:
+    static std::unique_ptr<Editor> Make(std::u16string text, SkSize size, SkSpan<Block> fontBlocks);
+    static std::unique_ptr<Editor> MakeDemo(SkScalar width);
+    void paint(SkCanvas* canvas, SkPoint point);
+    void blink() { fCursor->blink(); }
+    void onResize(int width, int height) override;
+private:
+
+    Editor(std::u16string text, SkSize size, SkSpan<Block> fontBlocks);
+
+    void onAttach(sk_app::Window* w) override { fParent = w; }
+    void onPaint(SkSurface* surface) override;
+
+    bool onKey(skui::Key, skui::InputState, skui::ModifierKey) override;
+    bool onChar(SkUnichar c, skui::ModifierKey modifier) override;
+    void invalidate() { if (fParent) { fParent->inval(); } }
+
+    void onBeginLine(TextRange, float baselineY) override;
+    void onEndLine(TextRange, float baselineY) override;
+    void onGlyphRun(SkFont font,
+                    TextRange textRange,
+                    int glyphCount,
+                    const uint16_t glyphs[],
+                    const SkPoint  positions[],
+                    const SkPoint offsets[]) override;
+    void onPlaceholder(TextRange, const SkRect& bounds) override;
+
+    bool moveCursor(skui::Key key);
+
+    std::unique_ptr<Cursor> fCursor;
+    std::unique_ptr<Mouse> fMouse;
+    std::u16string fText;
+    std::unique_ptr<UnicodeText> fUnicodeText;
+    std::unique_ptr<ShapedText> fShapedText;
+    std::unique_ptr<WrappedText> fWrappedText;
+    sk_sp<FormattedText> fFormattedText;
+    SkCanvas* fCanvas;
+    SkPoint fXY;
+    sk_app::Window* fParent;
+    int fWidth;
+    int fHeight;
+    //std::vector<Style> fStyles;
+};
+} // namespace text
+} // namespace skia
+#endif // Editor_DEFINED
diff --git a/experimental/sktext/include/Text.h b/experimental/sktext/include/Text.h
index edd13f3..ddda44f 100644
--- a/experimental/sktext/include/Text.h
+++ b/experimental/sktext/include/Text.h
@@ -114,30 +114,31 @@
     SkTArray<TextRun, false> fRuns;
     SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
 };
-
 class FormattedText;
 class WrappedText {
 public:
     sk_sp<FormattedText> format(TextAlign, TextDirection);
     SkSize size() const { return fSize; }
-    int countLines() const { return fLines.size(); }
+    size_t countLines() const { return fLines.size(); }
 private:
     friend class ShapedText;
-    WrappedText() { }
+    WrappedText() : fSize(SkSize::MakeEmpty()) { }
     void addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode);
     SkTArray<TextRun, false> fRuns;
     SkTArray<Line, false> fLines;
     SkTArray<GlyphUnitFlags, true> fGlyphUnitProperties;
     SkSize fSize;
 };
-
 class FormattedText : public SkRefCnt {
 public:
     SkSize  size() const { return fSize; }
 
-    std::tuple<const Line*, const TextRun*, GlyphIndex> indexToAdjustedGraphemePosition(TextIndex textIndex) const;
-
+    std::tuple<const Line*, const TextRun*, GlyphIndex, SkRect> indexToAdjustedGraphemePosition(TextIndex textIndex) const;
     TextIndex positionToAdjustedGraphemeIndex(SkPoint xy) const;
+    size_t lineIndex(const Line* line) const {
+        return line - fLines.data();
+    }
+    size_t countLines() const { return fLines.size(); }
 
     // This one does not make sense; how would we get glyphRange in the first place?
     // It has to point to the same run or we cannot even index it
@@ -145,6 +146,7 @@
 
     class Visitor {
     public:
+        virtual ~Visitor() = default;
         virtual void onBeginLine(TextRange, float baselineY) = 0;
         virtual void onEndLine(TextRange, float baselineY) = 0;
         virtual void onGlyphRun(SkFont font,
diff --git a/experimental/sktext/include/Types.h b/experimental/sktext/include/Types.h
index ec4138b..1bcf6bb 100644
--- a/experimental/sktext/include/Types.h
+++ b/experimental/sktext/include/Types.h
@@ -138,7 +138,7 @@
 
 typedef Range<TextIndex> TextRange;
 typedef Range<GlyphIndex> GlyphRange;
-const Range EMPTY_RANGE = Range(EMPTY_INDEX, EMPTY_INDEX);
+const Range<size_t> EMPTY_RANGE = Range<size_t>(EMPTY_INDEX, EMPTY_INDEX);
 
 // Blocks
 enum BlockType {
diff --git a/experimental/sktext/samples/Text.cpp b/experimental/sktext/samples/Text.cpp
index ffaa576..3a3afb6 100644
--- a/experimental/sktext/samples/Text.cpp
+++ b/experimental/sktext/samples/Text.cpp
@@ -1,5 +1,6 @@
 // Copyright 2021 Google LLC.
 
+#include "experimental/sktext/editor/Editor.h"
 #include "experimental/sktext/src/Paint.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColorFilter.h"
@@ -162,9 +163,11 @@
     using INHERITED = Sample;
     std::unique_ptr<SkUnicode> fUnicode;
 };
+
 }  // namespace
 
 DEF_SAMPLE(return new TextSample_HelloWorld();)
 DEF_SAMPLE(return new TextSample_Align_Dir();)
 DEF_SAMPLE(return new TextSample_LongLTR();)
 DEF_SAMPLE(return new TextSample_LongRTL();)
+
diff --git a/experimental/sktext/src/Line.h b/experimental/sktext/src/Line.h
index ed6ff62..a47d1e7 100644
--- a/experimental/sktext/src/Line.h
+++ b/experimental/sktext/src/Line.h
@@ -169,6 +169,8 @@
     Line(const Stretch& stretch, const Stretch& spaces, SkSTArray<1, size_t, true> visualOrder);
     ~Line() = default;
 
+    TextMetrics getMetrics() const { return fTextMetrics; }
+
 private:
     friend class WrappedText;
     friend class FormattedText;
diff --git a/experimental/sktext/src/Paint.cpp b/experimental/sktext/src/Paint.cpp
index 7bb9bf3..0fbebc9 100644
--- a/experimental/sktext/src/Paint.cpp
+++ b/experimental/sktext/src/Paint.cpp
@@ -4,7 +4,6 @@
 namespace skia {
 namespace text {
 
-
 bool Paint::drawText(std::u16string text, SkCanvas* canvas, SkScalar x, SkScalar y) {
 
     return drawText(std::move(text), canvas, TextDirection::kLtr, TextAlign::kLeft, SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), x, y);
diff --git a/experimental/sktext/src/Text.cpp b/experimental/sktext/src/Text.cpp
index a5b7035..8692825 100644
--- a/experimental/sktext/src/Text.cpp
+++ b/experimental/sktext/src/Text.cpp
@@ -130,6 +130,7 @@
 
     auto wrappedText = std::unique_ptr<WrappedText>(new WrappedText());
     wrappedText->fRuns = this->fRuns;
+    wrappedText->fGlyphUnitProperties = this->fGlyphUnitProperties; // copy
 
     // line : spaces : clusters
     Stretch line;
@@ -222,6 +223,7 @@
     }
     wrappedText->addLine(line, spaces, unicode);
 
+    wrappedText->fSize.fWidth = width;
     return std::move(wrappedText);
 }
 
@@ -247,6 +249,7 @@
         visualOrder.push_back(firstRunIndex + index);
     }
     this->fLines.emplace_back(stretch, spaces, std::move(visualOrder));
+    fSize.fHeight += stretch.textMetrics().height();
 
     stretch.clean();
     spaces.clean();
@@ -275,15 +278,16 @@
             auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
             TextRange textRange(run.fClusters[startGlyph], run.fClusters[endGlyph]);
             auto count = endGlyph - startGlyph;
+            SkScalar runWidth = run.calculateWidth(Range<GlyphIndex>(startGlyph, endGlyph));
 
             // Update positions
             SkAutoSTMalloc<256, SkPoint> positions(count + 1);
-            offset.fX -= run.fPositions[startGlyph].fX;
+            SkPoint shift = SkPoint::Make(-run.fPositions[startGlyph].fX, line.fTextMetrics.baseline());
             for (size_t i = startGlyph; i <= endGlyph; ++i) {
-                positions[i - startGlyph] = run.fPositions[i] + offset + SkPoint::Make(0, line.fTextMetrics.baseline());
+                positions[i - startGlyph] = run.fPositions[i] + shift + offset;
             }
-            offset.fX += run.fPositions[endGlyph].fX;
-            visitor->onGlyphRun(run.fFont, run.fUtf16Range, run.fGlyphs.size(), run.fGlyphs.data(), run.fPositions.data(), run.fOffsets.data());
+            offset.fX += runWidth;
+            visitor->onGlyphRun(run.fFont, textRange, count, run.fGlyphs.data() + startGlyph, positions.data(), run.fOffsets.data() + startGlyph);
         }
         visitor->onEndLine(line.fText, line.fTextMetrics.baseline());
         offset.fY += line.fTextMetrics.height();
@@ -356,25 +360,40 @@
     }
 }
 
-std::tuple<const Line*, const TextRun*, GlyphIndex> FormattedText::indexToAdjustedGraphemePosition(TextIndex textIndex) const {
-    if (textIndex >= this->fGlyphUnitProperties.size()) {
-        return std::make_tuple(nullptr, nullptr, EMPTY_INDEX);
-    }
+std::tuple<const Line*, const TextRun*, GlyphIndex, SkRect> FormattedText::indexToAdjustedGraphemePosition(TextIndex textIndex) const {
+
+    SkRect rect = SkRect::MakeEmpty();
     for (auto& line : fLines) {
+        rect.fTop = rect.fBottom;
+        rect.fBottom += line.fTextMetrics.height();
         if (!line.fText.contains(textIndex)) {
             continue;
         }
+
         for (auto runIndex : line.fRunsInVisualOrder) {
             auto& run = fRuns[runIndex];
             GlyphIndex start = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
             GlyphIndex end = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
             TextRange textRange(run.fClusters[start], run.fClusters[end]);
             if (textRange.contains(textIndex)) {
-                return std::make_tuple(&line, &run, run.findGlyph(textIndex));
+                auto glyphIndex = run.findGlyph(textIndex);
+                rect.fLeft += run.calculateWidth(start, glyphIndex);
+                rect.fRight += run.calculateWidth(start, glyphIndex + 1);
+                return std::make_tuple(&line, &run, glyphIndex, rect);
             }
+            rect.fLeft += run.calculateWidth(GlyphRange(start, end));
+            rect.fRight = rect.fLeft;
         }
+        // IF we found the index in the line we should have found it in one of the runs on the line
+        SkASSERT(false);
     }
-    return std::make_tuple(nullptr, nullptr, EMPTY_INDEX);
+
+    // We are right from the end of the text;
+    const auto lastLine = &fLines.back();
+    const auto lastRun = &fRuns[lastLine->fTextEnd.runIndex()];
+    rect.fLeft =
+    rect.fRight = lastLine->fTextWidth;
+    return std::make_tuple(lastLine, lastRun, lastRun->fGlyphs.size(), rect);
 }
 
 TextIndex FormattedText::positionToAdjustedGraphemeIndex(SkPoint xy) const {
@@ -387,9 +406,10 @@
 
     SkScalar height = 0;
     for (auto& line : fLines) {
-        if (height + line.fTextMetrics.height() > xy.fY) {
+        if (height > xy.fY) {
             break;
-        } else if (height > xy.fY) {
+        } else if (height + line.fTextMetrics.height() <= xy.fY) {
+            height += line.fTextMetrics.height();
             continue;
         }
 
@@ -399,18 +419,20 @@
             GlyphIndex start = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
             GlyphIndex end = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
             auto runWidth = run.calculateWidth(GlyphRange(start, end));
-            if (shift + runWidth > xy.fX) {
+            if (shift > xy.fX) {
                 break;
-            } else if (shift > xy.fX) {
+            } else if (shift + runWidth < xy.fX) {
                 shift += runWidth;
                 continue;
             }
+            SkScalar startPos = run.fPositions[start].fX;
             GlyphIndex found = start;
-            for (; found < end; ++found) {
-                auto pos = run.fPositions[found];
-                if (pos.fX > xy.fX) {
+            for (auto i = start; i <= end; ++i) {
+                auto currentPos = run.fPositions[i].fX - startPos;
+                if (currentPos > xy.fX) {
                     break;
                 }
+                found = i;
             }
             return run.fClusters[found];
         }
diff --git a/experimental/sktext/src/TextRun.h b/experimental/sktext/src/TextRun.h
index a39bf6c..85fbe06 100644
--- a/experimental/sktext/src/TextRun.h
+++ b/experimental/sktext/src/TextRun.h
@@ -18,6 +18,9 @@
   void commit();
 
   SkScalar calculateWidth(GlyphRange glyphRange) const;
+  SkScalar calculateWidth(GlyphIndex start, GlyphIndex end) const {
+      return calculateWidth(GlyphRange(start, end));
+  }
 
   bool leftToRight() const { return fBidiLevel % 2 == 0; }
   uint8_t bidiLevel() const { return fBidiLevel; }