295 lines
11 KiB
C++
295 lines
11 KiB
C++
|
// 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;
|
||
|
}
|