// 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 #include 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 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& 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 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(info.glyphCount) ? info.glyphCount : INT_MAX; int utf8RangeSize = SkTFitsIn(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 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 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 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; }