experimental/editor: shape.h to unify all shaping code
also simplify handling of implicit position at end of line.
also simplfy move() operation.
Change-Id: Ic242b5413c65295b1ac1bf7aa3a4948c3ed1c742
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/233303
Commit-Queue: Hal Canary <halcanary@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/experimental/editor/shape.cpp b/experimental/editor/shape.cpp
new file mode 100644
index 0000000..d82f7c6
--- /dev/null
+++ b/experimental/editor/shape.cpp
@@ -0,0 +1,294 @@
+// 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 "experimental/editor/shape.h"
+
+#include "experimental/editor/word_boundaries.h"
+#include "include/core/SkFont.h"
+#include "include/core/SkFontMetrics.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkString.h"
+#include "include/core/SkTextBlob.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkTFitsIn.h"
+#include "modules/skshaper/include/SkShaper.h"
+#include "src/core/SkTextBlobPriv.h"
+#include "src/utils/SkUTF.h"
+
+#include <limits.h>
+#include <string.h>
+
+
+using namespace editor;
+
+namespace {
+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() const { return fOffset; }
+ SkPoint finalPosition() const { return fCurrentPosition; }
+
+ 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;
+
+ const std::vector<size_t>& lineEndOffsets() const { return fLineEndOffsets; }
+
+ SkRect finalRect(const SkFont& font) const {
+ if (0 == fMaxRunAscent || 0 == fMaxRunDescent) {
+ SkFontMetrics metrics;
+ font.getMetrics(&metrics);
+ return {fCurrentPosition.x(),
+ fCurrentPosition.y(),
+ fCurrentPosition.x() + font.getSize(),
+ fCurrentPosition.y() + metrics.fDescent - metrics.fAscent};
+ } else {
+ return {fCurrentPosition.x(),
+ fCurrentPosition.y() + fMaxRunAscent,
+ fCurrentPosition.x() + font.getSize(),
+ fCurrentPosition.y() + fMaxRunDescent};
+ }
+ }
+
+
+private:
+ SkTextBlobBuilder fBuilder;
+ std::vector<size_t> fLineEndOffsets;
+ const SkGlyphID* fCurrentGlyphs = nullptr;
+ const SkPoint* fCurrentPoints = nullptr;
+ void* fCallbackContext = nullptr;
+ RunCallback fCallbackFunction = nullptr;
+ char const * const fUtf8Text;
+ size_t fTextOffset = 0;
+ 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
+
+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;
+ fTextOffset = SkTMax(fTextOffset, info.utf8Range.end());
+}
+
+void RunHandler::commitLine() {
+ if (fLineEndOffsets.empty() || fTextOffset > fLineEndOffsets.back()) {
+ // Ensure that fLineEndOffsets is monotonic.
+ fLineEndOffsets.push_back(fTextOffset);
+ }
+ fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent };
+}
+
+sk_sp<SkTextBlob> RunHandler::makeBlob() {
+ return fBuilder.make();
+}
+
+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();
+}
+
+static void set_character_bounds(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;
+ }
+ }
+}
+
+ShapeResult editor::Shape(const char* utf8Text,
+ size_t textByteLen,
+ const SkFont& font,
+ const char* locale,
+ float width)
+{
+ ShapeResult result;
+ if (SkUTF::CountUTF8(utf8Text, textByteLen) < 0) {
+ utf8Text = nullptr;
+ textByteLen = 0;
+ }
+ std::unique_ptr<SkShaper> shaper = SkShaper::Make();
+ float height = font.getSpacing();
+ RunHandler runHandler(utf8Text, textByteLen);
+ if (textByteLen) {
+ result.glyphBounds.resize(textByteLen);
+ for (SkRect& c : result.glyphBounds) {
+ c = SkRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX};
+ }
+ runHandler.setRunCallback(set_character_bounds, result.glyphBounds.data());
+ // TODO: make use of locale in shaping.
+ shaper->shape(utf8Text, textByteLen, font, true, width, &runHandler);
+ if (runHandler.lineEndOffsets().size() > 1) {
+ result.lineBreakOffsets = runHandler.lineEndOffsets();
+ SkASSERT(result.lineBreakOffsets.size() > 0);
+ result.lineBreakOffsets.pop_back();
+ }
+ height = std::max(height, runHandler.endPoint().y());
+ result.blob = runHandler.makeBlob();
+ }
+ result.glyphBounds.push_back(runHandler.finalRect(font));
+ result.verticalAdvance = (int)ceilf(height);
+ result.wordBreaks = GetUtf8WordBoundaries(utf8Text, textByteLen, locale);
+ return result;
+}