Experimental Editor: begin text selection code
Bug: skia:9020
Change-Id: I6a67ef4a3bcf443dbe14f58e81b405588f84848e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/211345
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
Auto-Submit: Hal Canary <halcanary@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 558bbf7..8c7d68b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2467,6 +2467,8 @@
"experimental/editor/editor_application.cpp",
"experimental/editor/editor.cpp",
"experimental/editor/editor.h",
+ "experimental/editor/run_handler.cpp",
+ "experimental/editor/run_handler.h",
]
deps = [
":sk_app",
diff --git a/experimental/editor/editor.cpp b/experimental/editor/editor.cpp
index e548832..032b56f 100644
--- a/experimental/editor/editor.cpp
+++ b/experimental/editor/editor.cpp
@@ -3,20 +3,121 @@
#include "editor.h"
-#include "include/core/SkExecutor.h"
#include "include/core/SkCanvas.h"
+#include "include/core/SkExecutor.h"
+#include "include/core/SkFontMetrics.h"
+#include "include/core/SkPath.h"
#include "modules/skshaper/include/SkShaper.h"
+#include "src/utils/SkUTF.h"
+
+#include "run_handler.h"
using namespace editor;
-static sk_sp<const SkTextBlob> shape(const SkString& text, const SkFont& font,
- const SkShaper* shaper, float width, int* height) {
- SkASSERT(height);
- SkTextBlobBuilderRunHandler textBlobBuilder(text.c_str(), {0, 0});
- shaper->shape(text.c_str(), text.size(), font, true, width, &textBlobBuilder);
- float h = std::max(textBlobBuilder.endPoint().y(), font.getSpacing());
- *height = (int)ceilf(h);
- return textBlobBuilder.makeBlob();
+static constexpr SkRect kUnsetRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX};
+
+static SkRect selection_box(const SkFontMetrics& metrics,
+ float advance,
+ SkPoint pos) {
+ if (fabsf(advance) < 1.0f) {
+ advance = copysignf(1.0f, advance);
+ }
+ return SkRect{pos.x(),
+ pos.y() + metrics.fAscent,
+ pos.x() + advance,
+ pos.y() + metrics.fDescent}.makeSorted();
+}
+
+
+void callback_fn(void* context,
+ const char* utf8Text,
+ size_t utf8TextBytes,
+ size_t glyphCount,
+ const SkGlyphID* glyphs,
+ const SkPoint* positions,
+ const uint32_t* clusters,
+ const SkFont& font)
+{
+ SkASSERT(context);
+ SkASSERT(glyphCount > 0);
+ SkRect* cursors = (SkRect*)context;
+
+ SkFontMetrics metrics;
+ font.getMetrics(&metrics);
+ std::unique_ptr<float[]> advances(new float[glyphCount]);
+ font.getWidths(glyphs, glyphCount, advances.get());
+
+ // Loop over each cluster in this run.
+ size_t clusterStart = 0;
+ for (size_t glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex) {
+ if (glyphIndex + 1 < glyphCount // more glyphs
+ && clusters[glyphIndex] == clusters[glyphIndex + 1]) {
+ continue; // multi-glyph cluster
+ }
+ unsigned textBegin = clusters[glyphIndex];
+ unsigned textEnd = utf8TextBytes;
+ for (size_t i = 0; i < glyphCount; ++i) {
+ if (clusters[i] >= textEnd) {
+ textEnd = clusters[i] + 1;
+ }
+ }
+ for (size_t i = 0; i < glyphCount; ++i) {
+ if (clusters[i] > textBegin && clusters[i] < textEnd) {
+ textEnd = clusters[i];
+ if (textEnd == textBegin + 1) { break; }
+ }
+ }
+ SkASSERT(glyphIndex + 1 > clusterStart);
+ unsigned clusterGlyphCount = glyphIndex + 1 - clusterStart;
+ const SkPoint* clusterGlyphPositions = &positions[clusterStart];
+ const float* clusterAdvances = &advances[clusterStart];
+ clusterStart = glyphIndex + 1; // for next loop
+
+ SkRect clusterBox = selection_box(metrics, clusterAdvances[0], clusterGlyphPositions[0]);
+ for (unsigned i = 1; i < clusterGlyphCount; ++i) { // multiple glyphs
+ clusterBox.join(selection_box(metrics, clusterAdvances[i], clusterGlyphPositions[i]));
+ }
+ if (textBegin + 1 == textEnd) { // single byte, fast path.
+ cursors[textBegin] = clusterBox;
+ continue;
+ }
+ int textCount = textEnd - textBegin;
+ int codePointCount = SkUTF::CountUTF8(utf8Text + textBegin, textCount);
+ if (codePointCount == 1) { // single codepoint, fast path.
+ cursors[textBegin] = clusterBox;
+ continue;
+ }
+
+ float width = clusterBox.width() / codePointCount;
+ SkASSERT(width > 0);
+ const char* ptr = utf8Text + textBegin;
+ const char* end = utf8Text + textEnd;
+ float x = clusterBox.left();
+ while (ptr < end) { // for each codepoint in cluster
+ const char* nextPtr = ptr;
+ SkUTF::NextUTF8(&nextPtr, end);
+ int firstIndex = ptr - utf8Text;
+ float nextX = x + width;
+ cursors[firstIndex] = SkRect{x, clusterBox.top(), nextX, clusterBox.bottom()};
+ x = nextX;
+ ptr = nextPtr;
+ }
+ }
+}
+
+void Editor::Shape(TextLine* line, SkShaper* shaper, float width, const SkFont& font) {
+ SkASSERT(line);
+ SkASSERT(shaper);
+ line->fCursorPos.resize(line->fText.size());
+ for (SkRect& c : line->fCursorPos) {
+ c = kUnsetRect;
+ }
+ RunHandler runHandler(line->fText.c_str(), line->fText.size());
+ runHandler.setRunCallback(callback_fn, line->fCursorPos.data());
+ shaper->shape(line->fText.c_str(), line->fText.size(), font, true, width, &runHandler);
+ float h = std::max(runHandler.endPoint().y(), font.getSpacing());
+ line->fHeight = (int)ceilf(h);
+ line->fBlob = runHandler.makeBlob();
}
// Kind of like Python's readlines(), but without any allocation.
@@ -54,31 +155,49 @@
int y = fMargin;
SkPaint p;
p.setColor4f(fForegroundColor, nullptr);
- SkPaint diff;
- diff.setColor(SK_ColorWHITE);
- diff.setBlendMode(SkBlendMode::kDifference);
+ //SkPaint diff;
+ //diff.setColor(SK_ColorWHITE);
+ //diff.setBlendMode(SkBlendMode::kDifference);
float left = (float)fMargin;
- float right = (float)(fWidth - fMargin);
+ //float right = (float)(fWidth - fMargin);
+ SkPaint stroke;
+ stroke.setStyle(SkPaint::kStroke_Style);
+ stroke.setColor(SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF));
+ SkRect lastRect = kUnsetRect;
for (const TextLine& line : fLines) {
+ if (line.fSelected) {
+ SkPath path;
+ for (unsigned i = 0; i < line.fText.size(); ++i) {
+ SkRect r = line.fCursorPos[i];
+ if (r == kUnsetRect) {
+ continue;
+ }
+ r.offset(left, (float)y);
+ if (r != lastRect) {
+ path.addRect(r);
+ //c->drawRect(r, stroke);
+ lastRect = r;
+ }
+ }
+ c->drawPath(path, stroke);
+ }
if (line.fBlob) {
c->drawTextBlob(line.fBlob.get(), left, (float)y, p);
}
- if (line.fSelected) {
- c->drawRect(SkRect{left, (float)y, right, (float)(y + line.fHeight)}, diff);
- }
y += line.fHeight;
}
}
void Editor::setWidth(int w) {
fWidth = w;
- float width = (float)(fWidth - 2 * fMargin);
+ float shape_width = (float)(fWidth - 2 * fMargin);
#ifdef SK_EDITOR_GO_FAST
SkSemaphore semaphore;
std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool(100);
for (TextLine& line : fLines) {
executor->add([&]() {
- line.fBlob = shape(line.fText, fFont, shaper.get(), width, &line.fHeight);
+ auto shaper = SkShaper::Make();
+ Editor::Shape(&line, shaper.get(), shape_width, fFont);
semaphore.signal();
});
}
@@ -86,7 +205,7 @@
#else
auto shaper = SkShaper::Make();
for (TextLine& line : fLines) {
- line.fBlob = shape(line.fText, fFont, shaper.get(), width, &line.fHeight);
+ Editor::Shape(&line, shaper.get(), shape_width, fFont);
}
#endif
float h = 2.0f * fMargin;
diff --git a/experimental/editor/editor.h b/experimental/editor/editor.h
index 649ed5d..15e73ae 100644
--- a/experimental/editor/editor.h
+++ b/experimental/editor/editor.h
@@ -11,6 +11,7 @@
#include <vector>
class SkCanvas;
+class SkShaper;
// TODO: modulize this; editor::Editor becomes SkEditor ?
@@ -40,6 +41,7 @@
private:
struct TextLine {
SkString fText;
+ std::vector<SkRect> fCursorPos;
int fHeight = 0;
sk_sp<const SkTextBlob> fBlob;
bool fSelected = false; // Will allow selection of subset of text later.
@@ -52,9 +54,11 @@
int fMargin = 10;
int fWidth = 0;
int fHeight = 0;
- SkFont fFont{nullptr, 24};
+ SkFont fFont{nullptr, 48};
SkColor4f fBackgroundColor = {0.8f, 0.8f, 0.8f, 1};
SkColor4f fForegroundColor = {0, 0, 0, 1};
+
+ static void Shape(TextLine*, SkShaper*, float width, const SkFont&);
};
} // namespace editor
#endif // editor_DEFINED
diff --git a/experimental/editor/editor_application.cpp b/experimental/editor/editor_application.cpp
index 7039c57..18c7790 100644
--- a/experimental/editor/editor_application.cpp
+++ b/experimental/editor/editor_application.cpp
@@ -42,7 +42,7 @@
double fTime;
const char* fDesc;
Timer(const char* desc = "") : fTime(SkTime::GetNSecs()), fDesc(desc) {}
- ~Timer() { SkDebugf("%s: %d ms\n", fDesc, (int)((SkTime::GetNSecs() - fTime) * 1e-6)); }
+ ~Timer() { SkDebugf("%s: %5d μs\n", fDesc, (int)((SkTime::GetNSecs() - fTime) * 1e-3)); }
};
diff --git a/experimental/editor/run_handler.cpp b/experimental/editor/run_handler.cpp
new file mode 100644
index 0000000..86bf458
--- /dev/null
+++ b/experimental/editor/run_handler.cpp
@@ -0,0 +1,83 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "run_handler.h"
+
+#include "include/core/SkFontMetrics.h"
+#include "src/core/SkTextBlobPriv.h"
+
+using namespace editor;
+
+void RunHandler::beginLine() {
+ fCurrentPosition = fOffset;
+ fMaxRunAscent = 0;
+ fMaxRunDescent = 0;
+ fMaxRunLeading = 0;
+}
+
+void RunHandler::runInfo(const SkShaper::RunHandler::RunInfo& info) {
+ SkFontMetrics metrics;
+ info.fFont.getMetrics(&metrics);
+ fMaxRunAscent = SkTMin(fMaxRunAscent, metrics.fAscent);
+ fMaxRunDescent = SkTMax(fMaxRunDescent, metrics.fDescent);
+ fMaxRunLeading = SkTMax(fMaxRunLeading, metrics.fLeading);
+}
+
+void RunHandler::commitRunInfo() {
+ fCurrentPosition.fY -= fMaxRunAscent;
+}
+
+SkShaper::RunHandler::Buffer RunHandler::runBuffer(const RunInfo& info) {
+ int glyphCount = SkTFitsIn<int>(info.glyphCount) ? info.glyphCount : INT_MAX;
+ int utf8RangeSize = SkTFitsIn<int>(info.utf8Range.size()) ? info.utf8Range.size() : INT_MAX;
+
+ const auto& runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(&fBuilder, info.fFont, glyphCount,
+ utf8RangeSize, SkString());
+ fCurrentGlyphs = runBuffer.glyphs;
+ fCurrentPoints = runBuffer.points();
+
+ if (runBuffer.utf8text && fUtf8Text) {
+ memcpy(runBuffer.utf8text, fUtf8Text + info.utf8Range.begin(), utf8RangeSize);
+ }
+ fClusters = runBuffer.clusters;
+ fGlyphCount = glyphCount;
+ fClusterOffset = info.utf8Range.begin();
+
+ return {runBuffer.glyphs,
+ runBuffer.points(),
+ nullptr,
+ runBuffer.clusters,
+ fCurrentPosition};
+}
+
+void RunHandler::commitRunBuffer(const RunInfo& info) {
+ // for (size_t i = 0; i < info.glyphCount; ++i) {
+ // SkASSERT(fClusters[i] >= info.utf8Range.begin());
+ // // this fails for khmer example.
+ // SkASSERT(fClusters[i] < info.utf8Range.end());
+ // }
+ if (fCallbackFunction) {
+ fCallbackFunction(fCallbackContext,
+ fUtf8Text,
+ info.utf8Range.end(),
+ info.glyphCount,
+ fCurrentGlyphs,
+ fCurrentPoints,
+ fClusters,
+ info.fFont);
+ }
+ SkASSERT(0 <= fClusterOffset);
+ for (int i = 0; i < fGlyphCount; ++i) {
+ SkASSERT(fClusters[i] >= (unsigned)fClusterOffset);
+ fClusters[i] -= fClusterOffset;
+ }
+ fCurrentPosition += info.fAdvance;
+}
+
+void RunHandler::commitLine() {
+ fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent };
+}
+
+sk_sp<SkTextBlob> RunHandler::makeBlob() {
+ return fBuilder.make();
+}
diff --git a/experimental/editor/run_handler.h b/experimental/editor/run_handler.h
new file mode 100644
index 0000000..2abd2be
--- /dev/null
+++ b/experimental/editor/run_handler.h
@@ -0,0 +1,53 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef run_handler_DEFINED
+#define run_handler_DEFINED
+
+#include "modules/skshaper/include/SkShaper.h"
+
+namespace editor {
+
+class RunHandler final : public SkShaper::RunHandler {
+public:
+ RunHandler(const char* utf8Text, size_t) : fUtf8Text(utf8Text) {}
+ using RunCallback = void (*)(void* context,
+ const char* utf8Text,
+ size_t utf8TextBytes,
+ size_t glyphCount,
+ const SkGlyphID* glyphs,
+ const SkPoint* positions,
+ const uint32_t* clusters,
+ const SkFont& font);
+ void setRunCallback(RunCallback f, void* context) {
+ fCallbackContext = context;
+ fCallbackFunction = f;
+ }
+
+ sk_sp<SkTextBlob> makeBlob();
+ SkPoint endPoint() { return fOffset; }
+
+ void beginLine() override;
+ void runInfo(const RunInfo&) override;
+ void commitRunInfo() override;
+ SkShaper::RunHandler::Buffer runBuffer(const RunInfo&) override;
+ void commitRunBuffer(const RunInfo&) override;
+ void commitLine() override;
+
+private:
+ SkTextBlobBuilder fBuilder;
+ const SkGlyphID* fCurrentGlyphs = nullptr;
+ const SkPoint* fCurrentPoints = nullptr;
+ void* fCallbackContext = nullptr;
+ RunCallback fCallbackFunction = nullptr;
+ char const * const fUtf8Text;
+ uint32_t* fClusters = nullptr;
+ int fClusterOffset = 0;
+ int fGlyphCount = 0;
+ SkScalar fMaxRunAscent = 0;
+ SkScalar fMaxRunDescent = 0;
+ SkScalar fMaxRunLeading = 0;
+ SkPoint fCurrentPosition = {0, 0};
+ SkPoint fOffset = {0, 0};
+};
+} // namespace editor
+#endif // run_handler_DEFINED