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; }