ICU optimization
Mainly rearranging the code to perform all ICU iterations once and cache the results for the next text layouts. Change-Id: I2c2a502c705510eb169bf62efbfcc13b658591e3 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/293336 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
4926b07217
commit
cc6349d390
@ -13,12 +13,12 @@ namespace textlayout {
|
||||
|
||||
enum InternalState {
|
||||
kUnknown = 0,
|
||||
kShaped = 1,
|
||||
kClusterized = 2,
|
||||
kMarked = 3,
|
||||
kLineBroken = 4,
|
||||
kFormatted = 5,
|
||||
kDrawn = 6
|
||||
kShaped = 2,
|
||||
kClusterized = 3,
|
||||
kMarked = 4,
|
||||
kLineBroken = 5,
|
||||
kFormatted = 6,
|
||||
kDrawn = 7
|
||||
};
|
||||
|
||||
class ParagraphImpl;
|
||||
|
@ -473,8 +473,7 @@ void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
|
||||
|
||||
bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
|
||||
|
||||
SkTArray<BidiRegion> bidiRegions;
|
||||
if (!fParagraph->calculateBidiRegions(&bidiRegions)) {
|
||||
if (!fParagraph->getBidiRegions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -485,8 +484,8 @@ bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
|
||||
|
||||
if (placeholder.fTextBefore.width() > 0) {
|
||||
// Shape the text by bidi regions
|
||||
while (bidiIndex < bidiRegions.size()) {
|
||||
BidiRegion& bidiRegion = bidiRegions[bidiIndex];
|
||||
while (bidiIndex < fParagraph->fBidiRegions.size()) {
|
||||
BidiRegion& bidiRegion = fParagraph->fBidiRegions[bidiIndex];
|
||||
auto start = std::max(bidiRegion.text.start, placeholder.fTextBefore.start);
|
||||
auto end = std::min(bidiRegion.text.end, placeholder.fTextBefore.end);
|
||||
|
||||
@ -645,15 +644,17 @@ TextRange OneLineShaper::clusteredText(GlyphRange& glyphs) {
|
||||
|
||||
if (dir == Dir::right) {
|
||||
while (index < fCurrentRun->fTextRange.end) {
|
||||
if (this->fParagraph->fGraphemes.contains(index)) {
|
||||
if (this->fParagraph->codeUnitHasProperty(index,
|
||||
CodeUnitFlags::kGraphemeBreakBefore)) {
|
||||
return index;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
return fCurrentRun->fTextRange.end;
|
||||
} else {
|
||||
while (index >= fCurrentRun->fTextRange.start) {
|
||||
if (this->fParagraph->fGraphemes.contains(index)) {
|
||||
while (index > fCurrentRun->fTextRange.start) {
|
||||
if (this->fParagraph->codeUnitHasProperty(index,
|
||||
CodeUnitFlags::kGraphemeBreakBefore)) {
|
||||
return index;
|
||||
}
|
||||
--index;
|
||||
|
@ -35,13 +35,24 @@ class ParagraphCacheValue {
|
||||
public:
|
||||
ParagraphCacheValue(const ParagraphImpl* paragraph)
|
||||
: fKey(ParagraphCacheKey(paragraph))
|
||||
, fRuns(paragraph->fRuns) { }
|
||||
, fRuns(paragraph->fRuns)
|
||||
, fCodeUnitProperties(paragraph->fCodeUnitProperties)
|
||||
, fWords(paragraph->fWords)
|
||||
, fBidiRegions(paragraph->fBidiRegions)
|
||||
, fGraphemes16(paragraph->fGraphemes16)
|
||||
, fCodepoints(paragraph->fCodepoints) { }
|
||||
|
||||
// Input == key
|
||||
ParagraphCacheKey fKey;
|
||||
|
||||
// Shaped results
|
||||
SkTArray<Run, false> fRuns;
|
||||
// ICU results
|
||||
SkTArray<CodeUnitFlags> fCodeUnitProperties;
|
||||
std::vector<size_t> fWords;
|
||||
SkTArray<BidiRegion> fBidiRegions;
|
||||
SkTArray<Grapheme, true> fGraphemes16;
|
||||
SkTArray<CodepointRepresentation, true> fCodepoints;
|
||||
};
|
||||
|
||||
uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
|
||||
@ -193,6 +204,11 @@ void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
|
||||
|
||||
paragraph->fRuns.reset();
|
||||
paragraph->fRuns = entry->fValue->fRuns;
|
||||
paragraph->fCodeUnitProperties = entry->fValue->fCodeUnitProperties;
|
||||
paragraph->fWords = entry->fValue->fWords;
|
||||
paragraph->fBidiRegions = entry->fValue->fBidiRegions;
|
||||
paragraph->fGraphemes16 = entry->fValue->fGraphemes16;
|
||||
paragraph->fCodepoints = entry->fValue->fCodepoints;
|
||||
for (auto& run : paragraph->fRuns) {
|
||||
run.setMaster(paragraph);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkFontMetrics.h"
|
||||
#include "include/core/SkMatrix.h"
|
||||
@ -69,40 +70,6 @@ TextRange operator*(const TextRange& a, const TextRange& b) {
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
}
|
||||
|
||||
bool TextBreaker::initialize(SkSpan<const char> text, UBreakIteratorType type) {
|
||||
#if defined(SK_USING_THIRD_PARTY_ICU)
|
||||
if (!SkLoadICU()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
fIterator = nullptr;
|
||||
fSize = text.size();
|
||||
UText sUtf8UText = UTEXT_INITIALIZER;
|
||||
std::unique_ptr<UText, SkFunctionWrapper<decltype(utext_close), utext_close>> utf8UText(
|
||||
utext_openUTF8(&sUtf8UText, text.begin(), text.size(), &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not create utf8UText: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not create line break iterator: %s", u_errorName(status));
|
||||
SK_ABORT("");
|
||||
}
|
||||
|
||||
ubrk_setUText(fIterator.get(), utf8UText.get(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not setText on break iterator: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
fInitialized = true;
|
||||
fPos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
ParagraphStyle style,
|
||||
SkTArray<Block, true> blocks,
|
||||
@ -118,9 +85,7 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
, fStrutMetrics(false)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0)
|
||||
, fOrigin(SkRect::MakeEmpty()) {
|
||||
// TODO: extractStyles();
|
||||
}
|
||||
, fOrigin(SkRect::MakeEmpty()) { }
|
||||
|
||||
ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
ParagraphStyle style,
|
||||
@ -137,9 +102,7 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
, fStrutMetrics(false)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0)
|
||||
, fOrigin(SkRect::MakeEmpty()) {
|
||||
// TODO: extractStyles();
|
||||
}
|
||||
, fOrigin(SkRect::MakeEmpty()) {}
|
||||
|
||||
ParagraphImpl::~ParagraphImpl() = default;
|
||||
|
||||
@ -155,22 +118,28 @@ void ParagraphImpl::layout(SkScalar rawWidth) {
|
||||
|
||||
// TODO: This rounding is done to match Flutter tests. Must be removed...
|
||||
auto floorWidth = SkScalarFloorToScalar(rawWidth);
|
||||
if (fState < kShaped) {
|
||||
// Layout marked as dirty for performance/testing reasons
|
||||
this->fRuns.reset();
|
||||
this->fClusters.reset();
|
||||
this->resetShifts();
|
||||
} else if (fState >= kLineBroken && (fOldWidth != floorWidth || fOldHeight != fHeight)) {
|
||||
|
||||
if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
|
||||
fState >= kLineBroken &&
|
||||
fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
|
||||
// Most common case: one line of text (and one line is never justified, so no cluster shifts)
|
||||
fWidth = floorWidth;
|
||||
fState = kLineBroken;
|
||||
} else if (fState >= kLineBroken && fOldWidth != floorWidth) {
|
||||
// We can use the results from SkShaper but have to do EVERYTHING ELSE again
|
||||
this->fClusters.reset();
|
||||
this->resetShifts();
|
||||
fState = kShaped;
|
||||
} else {
|
||||
// Nothing changed case: we can reuse the data from the last layout
|
||||
}
|
||||
|
||||
if (fState < kShaped) {
|
||||
fGraphemes.reset();
|
||||
this->markGraphemes();
|
||||
|
||||
this->fCodeUnitProperties.reset();
|
||||
this->fCodeUnitProperties.push_back_n(fText.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
|
||||
this->fWords.clear();
|
||||
this->fBidiRegions.reset();
|
||||
this->fGraphemes16.reset();
|
||||
this->fCodepoints.reset();
|
||||
this->fRuns.reset();
|
||||
if (!this->shapeTextIntoEndlessLine()) {
|
||||
this->resetContext();
|
||||
// TODO: merge the two next calls - they always come together
|
||||
@ -187,6 +156,7 @@ void ParagraphImpl::layout(SkScalar rawWidth) {
|
||||
}
|
||||
fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
|
||||
fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
|
||||
fLongestLine = FLT_MIN - FLT_MAX; // That is what flutter has
|
||||
fMinIntrinsicWidth = 0;
|
||||
fMaxIntrinsicWidth = 0;
|
||||
this->fOldWidth = floorWidth;
|
||||
@ -194,27 +164,18 @@ void ParagraphImpl::layout(SkScalar rawWidth) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this->fClusters.reset();
|
||||
this->resetShifts();
|
||||
fState = kShaped;
|
||||
}
|
||||
|
||||
if (fState < kMarked) {
|
||||
this->fClusters.reset();
|
||||
this->resetShifts();
|
||||
this->buildClusterTable();
|
||||
fState = kClusterized;
|
||||
|
||||
this->markLineBreaks();
|
||||
this->spaceGlyphs();
|
||||
fState = kMarked;
|
||||
}
|
||||
|
||||
if (fState >= kLineBroken) {
|
||||
if (fOldWidth != floorWidth || fOldHeight != fHeight) {
|
||||
fState = kMarked;
|
||||
}
|
||||
}
|
||||
|
||||
if (fState < kLineBroken) {
|
||||
this->resetContext();
|
||||
this->resolveStrut();
|
||||
@ -272,6 +233,264 @@ void ParagraphImpl::resetContext() {
|
||||
fExceededMaxLines = false;
|
||||
}
|
||||
|
||||
class TextBreaker {
|
||||
public:
|
||||
TextBreaker() : fInitialized(false), fPos(-1) {}
|
||||
|
||||
bool initialize(SkSpan<const char> text, UBreakIteratorType type) {
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
fIterator = nullptr;
|
||||
fSize = text.size();
|
||||
UText sUtf8UText = UTEXT_INITIALIZER;
|
||||
std::unique_ptr<UText, SkFunctionWrapper<decltype(utext_close), utext_close>> utf8UText(
|
||||
utext_openUTF8(&sUtf8UText, text.begin(), text.size(), &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not create utf8UText: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not create line break iterator: %s", u_errorName(status));
|
||||
SK_ABORT("");
|
||||
}
|
||||
|
||||
ubrk_setUText(fIterator.get(), utf8UText.get(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Could not setText on break iterator: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
fInitialized = true;
|
||||
fPos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initialized() const { return fInitialized; }
|
||||
|
||||
size_t first() {
|
||||
fPos = ubrk_first(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
size_t next() {
|
||||
fPos = ubrk_next(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
size_t preceding(size_t offset) {
|
||||
auto pos = ubrk_preceding(fIterator.get(), offset);
|
||||
return pos == UBRK_DONE ? 0 : pos;
|
||||
}
|
||||
|
||||
size_t following(size_t offset) {
|
||||
auto pos = ubrk_following(fIterator.get(), offset);
|
||||
return pos == UBRK_DONE ? fSize : pos;
|
||||
}
|
||||
|
||||
int32_t status() { return ubrk_getRuleStatus(fIterator.get()); }
|
||||
|
||||
bool eof() { return fPos == UBRK_DONE; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<UBreakIterator, SkFunctionWrapper<decltype(ubrk_close), ubrk_close>> fIterator;
|
||||
bool fInitialized;
|
||||
int32_t fPos;
|
||||
size_t fSize;
|
||||
};
|
||||
|
||||
// shapeTextIntoEndlessLine is the thing that calls this method
|
||||
// (that contains all ICU dependencies except for words)
|
||||
bool ParagraphImpl::computeCodeUnitProperties() {
|
||||
|
||||
#if defined(SK_USING_THIRD_PARTY_ICU)
|
||||
if (!SkLoadICU()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
const char* start = fText.c_str();
|
||||
const char* end = start + fText.size();
|
||||
const char* ch = start;
|
||||
while (ch < end) {
|
||||
auto index = ch - start;
|
||||
auto unichar = utf8_next(&ch, end);
|
||||
if (u_isWhitespace(unichar)) {
|
||||
auto ending = ch - start;
|
||||
for (auto k = index; k < ending; ++k) {
|
||||
fCodeUnitProperties[k] |= CodeUnitFlags::kPartOfWhiteSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(this->text(), UBRK_LINE)) {
|
||||
return false;
|
||||
}
|
||||
while (!breaker.eof()) {
|
||||
size_t currentPos = breaker.next();
|
||||
fCodeUnitProperties[currentPos] |=
|
||||
breaker.status() == UBRK_LINE_HARD ? CodeUnitFlags::kHardLineBreakBefore : CodeUnitFlags::kSoftLineBreakBefore;
|
||||
}
|
||||
}
|
||||
{
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(this->text(), UBRK_CHARACTER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!breaker.eof()) {
|
||||
auto currentPos = breaker.next();
|
||||
fCodeUnitProperties[currentPos] |= CodeUnitFlags::kGraphemeBreakBefore;
|
||||
}
|
||||
}
|
||||
/*
|
||||
SkString breaks;
|
||||
SkString graphemes;
|
||||
SkString whitespaces;
|
||||
size_t index = 0;
|
||||
for (auto flag : fIcuFlags) {
|
||||
if ((flag & IcuFlagTypes::kHardLineBreak) != 0) {
|
||||
breaks += "H";
|
||||
} else if ((flag & IcuFlagTypes::kSoftLineBreak) != 0) {
|
||||
breaks += "S";
|
||||
} else {
|
||||
breaks += " ";
|
||||
}
|
||||
graphemes += (flag & IcuFlagTypes::kGrapheme) == 0 ? " " : "G";
|
||||
whitespaces += (flag & IcuFlagTypes::kWhiteSpace) == 0 ? " " : "W";
|
||||
++index;
|
||||
}
|
||||
SkDebugf("%s\n%s\n%s\n", breaks.c_str(), graphemes.c_str(), whitespaces.c_str());
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// getWordBoundary is the thing that calls this method lazily
|
||||
bool ParagraphImpl::computeWords() {
|
||||
|
||||
if (!fWords.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UErrorCode errorCode = U_ZERO_ERROR;
|
||||
|
||||
auto iter = ubrk_open(UBRK_WORD, uloc_getDefault(), nullptr, 0, &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not create line break iterator: %s", u_errorName(errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
|
||||
int32_t utf16Units;
|
||||
u_strFromUTF8(nullptr, 0, &utf16Units, fText.c_str(), fText.size(), &errorCode);
|
||||
errorCode = U_ZERO_ERROR;
|
||||
std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
|
||||
u_strFromUTF8(utf16.get(), utf16Units, nullptr, fText.c_str(), fText.size(), &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Invalid utf8 input: %s", u_errorName(errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
UText sUtf16UText = UTEXT_INITIALIZER;
|
||||
ICUUText utf8UText(utext_openUChars(&sUtf16UText, utf16.get(), utf16Units, &errorCode));
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not create utf8UText: %s", u_errorName(errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
ubrk_setUText(iter, utf8UText.get(), &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not setText on break iterator: %s", u_errorName(errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t pos = ubrk_first(iter);
|
||||
while (pos != UBRK_DONE) {
|
||||
fWords.emplace_back(pos);
|
||||
pos = ubrk_next(iter);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParagraphImpl::getBidiRegions() {
|
||||
|
||||
if (!fBidiRegions.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ubidi only accepts utf16 (though internally it basically works on utf32 chars).
|
||||
// We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*);
|
||||
size_t utf8Bytes = fText.size();
|
||||
const char* utf8 = fText.c_str();
|
||||
uint8_t bidiLevel = fParagraphStyle.getTextDirection() == TextDirection::kLtr
|
||||
? UBIDI_LTR
|
||||
: UBIDI_RTL;
|
||||
if (!SkTFitsIn<int32_t>(utf8Bytes)) {
|
||||
SkDEBUGF("Bidi error: text too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t utf16Units;
|
||||
u_strFromUTF8(nullptr, 0, &utf16Units, utf8, utf8Bytes, &status);
|
||||
status = U_ZERO_ERROR;
|
||||
std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
|
||||
u_strFromUTF8(utf16.get(), utf16Units, nullptr, utf8, utf8Bytes, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Invalid utf8 input: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
ICUBiDi bidi(ubidi_openSized(utf16Units, 0, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Bidi error: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
SkASSERT(bidi);
|
||||
|
||||
// The required lifetime of utf16 isn't well documented.
|
||||
// It appears it isn't used after ubidi_setPara except through ubidi_getText.
|
||||
ubidi_setPara(bidi.get(), utf16.get(), utf16Units, bidiLevel, nullptr, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Bidi error: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
SkTArray<BidiRegion> bidiRegions;
|
||||
const char* start8 = utf8;
|
||||
const char* end8 = utf8 + utf8Bytes;
|
||||
TextRange textRange(0, 0);
|
||||
UBiDiLevel currentLevel = 0;
|
||||
|
||||
int32_t pos16 = 0;
|
||||
int32_t end16 = ubidi_getLength(bidi.get());
|
||||
while (pos16 < end16) {
|
||||
auto level = ubidi_getLevelAt(bidi.get(), pos16);
|
||||
if (pos16 == 0) {
|
||||
currentLevel = level;
|
||||
} else if (level != currentLevel) {
|
||||
textRange.end = start8 - utf8;
|
||||
fBidiRegions.emplace_back(textRange.start, textRange.end, currentLevel);
|
||||
currentLevel = level;
|
||||
textRange = TextRange(textRange.end, textRange.end);
|
||||
}
|
||||
SkUnichar u = utf8_next(&start8, end8);
|
||||
pos16 += SkUTF::ToUTF16(u);
|
||||
}
|
||||
|
||||
textRange.end = start8 - utf8;
|
||||
if (!textRange.empty()) {
|
||||
fBidiRegions.emplace_back(textRange.start, textRange.end, currentLevel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clusters in the order of the input text
|
||||
void ParagraphImpl::buildClusterTable() {
|
||||
|
||||
@ -281,13 +500,9 @@ void ParagraphImpl::buildClusterTable() {
|
||||
auto runStart = fClusters.size();
|
||||
if (run.isPlaceholder()) {
|
||||
// There are no glyphs but we want to have one cluster
|
||||
SkSpan<const char> text = this->text(run.textRange());
|
||||
if (!fClusters.empty()) {
|
||||
fClusters.back().setBreakType(Cluster::SoftLineBreak);
|
||||
}
|
||||
auto& cluster = fClusters.emplace_back(this, runIndex, 0ul, 1ul, text, run.advance().fX,
|
||||
run.advance().fY);
|
||||
cluster.setBreakType(Cluster::SoftLineBreak);
|
||||
fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
|
||||
fCodeUnitProperties[run.textRange().start] |= CodeUnitFlags::kSoftLineBreakBefore;
|
||||
fCodeUnitProperties[run.textRange().end] |= CodeUnitFlags::kSoftLineBreakBefore;
|
||||
} else {
|
||||
fClusters.reserve(fClusters.size() + run.size());
|
||||
// Walk through the glyph in the direction of input text
|
||||
@ -299,19 +514,14 @@ void ParagraphImpl::buildClusterTable() {
|
||||
SkScalar height) {
|
||||
SkASSERT(charEnd >= charStart);
|
||||
SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
|
||||
auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text,
|
||||
width, height);
|
||||
cluster.setIsWhiteSpaces();
|
||||
if (fGraphemes.find(cluster.fTextRange.end) != nullptr) {
|
||||
cluster.setBreakType(Cluster::BreakType::GraphemeBreak);
|
||||
}
|
||||
fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
run.setClusterRange(runStart, fClusters.size());
|
||||
fMaxIntrinsicWidth += run.advance().fX;
|
||||
}
|
||||
fClusters.emplace_back(this, EMPTY_RUN, 0, 0, SkSpan<const char>(), 0, 0);
|
||||
fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
|
||||
}
|
||||
|
||||
void ParagraphImpl::spaceGlyphs() {
|
||||
@ -360,41 +570,6 @@ void ParagraphImpl::spaceGlyphs() {
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphImpl::markLineBreaks() {
|
||||
|
||||
// Find all possible (soft) line breaks
|
||||
// This iterator is used only once for a paragraph so we don't have to keep it
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(this->text(), UBRK_LINE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all soft line breaks
|
||||
// Remove soft line breaks that are not on grapheme cluster edge
|
||||
Cluster* current = fClusters.begin();
|
||||
while (!breaker.eof() && current < fClusters.end()) {
|
||||
size_t currentPos = breaker.next();
|
||||
while (current < fClusters.end()) {
|
||||
if (current->textRange().end > currentPos) {
|
||||
break;
|
||||
} else if (current->textRange().end == currentPos) {
|
||||
if (breaker.status() == UBRK_LINE_HARD) {
|
||||
// Hard line break stronger than anything
|
||||
current->setBreakType(Cluster::BreakType::HardLineBreak);
|
||||
} else if (current->isGraphemeBreak()) {
|
||||
// Only allow soft line break if it's grapheme break
|
||||
current->setBreakType(Cluster::BreakType::SoftLineBreak);
|
||||
} else {
|
||||
// Leave it as is (either it's no break or a placeholder)
|
||||
}
|
||||
++current;
|
||||
break;
|
||||
}
|
||||
++current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
|
||||
if (fText.size() == 0) {
|
||||
@ -406,6 +581,10 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!computeCodeUnitProperties()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fFontSwitches.reset();
|
||||
|
||||
OneLineShaper oneLineShaper(this);
|
||||
@ -588,12 +767,7 @@ void ParagraphImpl::markGraphemes16() {
|
||||
return;
|
||||
}
|
||||
|
||||
// This breaker gets called only once for a paragraph so we don't have to keep it
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(this->text(), UBRK_CHARACTER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill out code points 16
|
||||
auto ptr = fText.c_str();
|
||||
auto end = fText.c_str() + fText.size();
|
||||
while (ptr < end) {
|
||||
@ -602,54 +776,39 @@ void ParagraphImpl::markGraphemes16() {
|
||||
SkUnichar u = SkUTF::NextUTF8(&ptr, end);
|
||||
uint16_t buffer[2];
|
||||
size_t count = SkUTF::ToUTF16(u, buffer);
|
||||
fCodePoints.emplace_back(EMPTY_INDEX, index, count > 1 ? 2 : 1);
|
||||
fCodepoints.emplace_back(EMPTY_INDEX, index, count > 1 ? 2 : 1);
|
||||
if (count > 1) {
|
||||
fCodePoints.emplace_back(EMPTY_INDEX, index, 1);
|
||||
fCodepoints.emplace_back(EMPTY_INDEX, index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
CodepointRange codepoints(0ul, 0ul);
|
||||
|
||||
size_t endPos = 0;
|
||||
while (!breaker.eof()) {
|
||||
auto startPos = endPos;
|
||||
endPos = breaker.next();
|
||||
|
||||
forEachCodeUnitPropertyRange(
|
||||
CodeUnitFlags::kGraphemeBreakBefore,
|
||||
[&](TextRange textRange) {
|
||||
// Collect all the codepoints that belong to the grapheme
|
||||
while (codepoints.end < fCodePoints.size() && fCodePoints[codepoints.end].fTextIndex < endPos) {
|
||||
++codepoints.end;
|
||||
while (codepoints.end < fCodepoints.size()
|
||||
&& fCodepoints[codepoints.end].fTextIndex < textRange.end) {
|
||||
++codepoints.end;
|
||||
}
|
||||
|
||||
if (startPos == endPos) {
|
||||
continue;
|
||||
if (textRange.start == textRange.end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//SkDebugf("Grapheme #%d [%d:%d)\n", fGraphemes16.size(), startPos, endPos);
|
||||
|
||||
// Update all the codepoints that belong to this grapheme
|
||||
for (auto i = codepoints.start; i < codepoints.end; ++i) {
|
||||
//SkDebugf(" [%d] = %d + %d\n", i, fCodePoints[i].fTextIndex, fCodePoints[i].fIndex);
|
||||
fCodePoints[i].fGrapheme = fGraphemes16.size();
|
||||
//SkDebugf(" [%d] = %d + %d\n", i, fCodePoints[i].fTextIndex, fCodePoints[i].fIndex);
|
||||
fCodepoints[i].fGrapheme = fGraphemes16.size();
|
||||
}
|
||||
|
||||
fGraphemes16.emplace_back(codepoints, TextRange(startPos, endPos));
|
||||
fGraphemes16.emplace_back(codepoints, textRange);
|
||||
codepoints.start = codepoints.end;
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphImpl::markGraphemes() {
|
||||
|
||||
// This breaker gets called only once for a paragraph so we don't have to keep it
|
||||
TextBreaker breaker;
|
||||
if (!breaker.initialize(this->text(), UBRK_CHARACTER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto endPos = breaker.first();
|
||||
while (!breaker.eof()) {
|
||||
fGraphemes.add(endPos);
|
||||
endPos = breaker.next();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a vector of bounding boxes that enclose all text between
|
||||
@ -670,7 +829,7 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
|
||||
markGraphemes16();
|
||||
|
||||
if (start >= end || start > fCodePoints.size() || end == 0) {
|
||||
if (start >= end || start > fCodepoints.size() || end == 0) {
|
||||
return results;
|
||||
}
|
||||
|
||||
@ -683,14 +842,14 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
|
||||
// One flutter test fails because of it but the editing experience is correct
|
||||
// (although you have to press the cursor many times before it moves to the next grapheme).
|
||||
TextRange text(fText.size(), fText.size());
|
||||
if (start < fCodePoints.size()) {
|
||||
auto codepoint = fCodePoints[start];
|
||||
if (start < fCodepoints.size()) {
|
||||
auto codepoint = fCodepoints[start];
|
||||
auto grapheme = fGraphemes16[codepoint.fGrapheme];
|
||||
text.start = grapheme.fTextRange.start;
|
||||
}
|
||||
|
||||
if (end < fCodePoints.size()) {
|
||||
auto codepoint = fCodePoints[end];
|
||||
if (end < fCodepoints.size()) {
|
||||
auto codepoint = fCodepoints[end];
|
||||
auto grapheme = fGraphemes16[codepoint.fGrapheme];
|
||||
text.end = grapheme.fTextRange.start;
|
||||
}
|
||||
@ -775,44 +934,9 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
|
||||
// the glyph at index offset.
|
||||
// By "glyph" they mean a character index - indicated by Minikin's code
|
||||
SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
|
||||
if (fWords.empty()) {
|
||||
UErrorCode errorCode = U_ZERO_ERROR;
|
||||
|
||||
auto iter = ubrk_open(UBRK_WORD, uloc_getDefault(), nullptr, 0, &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not create line break iterator: %s", u_errorName(errorCode));
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
// Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
|
||||
int32_t utf16Units;
|
||||
u_strFromUTF8(nullptr, 0, &utf16Units, fText.c_str(), fText.size(), &errorCode);
|
||||
errorCode = U_ZERO_ERROR;
|
||||
std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
|
||||
u_strFromUTF8(utf16.get(), utf16Units, nullptr, fText.c_str(), fText.size(), &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Invalid utf8 input: %s", u_errorName(errorCode));
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
UText sUtf16UText = UTEXT_INITIALIZER;
|
||||
ICUUText utf8UText(utext_openUChars(&sUtf16UText, utf16.get(), utf16Units, &errorCode));
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not create utf8UText: %s", u_errorName(errorCode));
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
ubrk_setUText(iter, utf8UText.get(), &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
SkDEBUGF("Could not setText on break iterator: %s", u_errorName(errorCode));
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
int32_t pos = ubrk_first(iter);
|
||||
while (pos != UBRK_DONE) {
|
||||
fWords.emplace_back(pos);
|
||||
pos = ubrk_next(iter);
|
||||
}
|
||||
if (!computeWords()) {
|
||||
return {0, 0 };
|
||||
}
|
||||
|
||||
int32_t start = 0;
|
||||
@ -827,10 +951,36 @@ SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
|
||||
return { SkToU32(start), SkToU32(end) };
|
||||
}
|
||||
|
||||
void ParagraphImpl::forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor) {
|
||||
|
||||
size_t first = 0;
|
||||
for (size_t i = 1; i < fText.size(); ++i) {
|
||||
auto properties = fCodeUnitProperties[i];
|
||||
if (properties & property) {
|
||||
visitor({first, i});
|
||||
first = i;
|
||||
}
|
||||
|
||||
}
|
||||
visitor({first, fText.size()});
|
||||
}
|
||||
|
||||
size_t ParagraphImpl::getWhitespacesLength(TextRange textRange) {
|
||||
size_t len = 0;
|
||||
for (auto i = textRange.start; i < textRange.end; ++i) {
|
||||
auto properties = fCodeUnitProperties[i];
|
||||
if (properties & CodeUnitFlags::kPartOfWhiteSpace) {
|
||||
++len;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
|
||||
metrics.clear();
|
||||
for (auto& line : fLines) {
|
||||
@ -884,6 +1034,12 @@ void ParagraphImpl::setState(InternalState state) {
|
||||
switch (fState) {
|
||||
case kUnknown:
|
||||
fRuns.reset();
|
||||
fCodeUnitProperties.reset();
|
||||
fCodeUnitProperties.push_back_n(fText.size() + 1, kNoCodeUnitFlag);
|
||||
fWords.clear();
|
||||
fBidiRegions.reset();
|
||||
fGraphemes16.reset();
|
||||
fCodepoints.reset();
|
||||
case kShaped:
|
||||
fClusters.reset();
|
||||
case kClusterized:
|
||||
@ -981,78 +1137,5 @@ void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint)
|
||||
}
|
||||
}
|
||||
|
||||
bool ParagraphImpl::calculateBidiRegions(SkTArray<BidiRegion>* regions) {
|
||||
|
||||
regions->reset();
|
||||
|
||||
// ubidi only accepts utf16 (though internally it basically works on utf32 chars).
|
||||
// We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*);
|
||||
size_t utf8Bytes = fText.size();
|
||||
const char* utf8 = fText.c_str();
|
||||
uint8_t bidiLevel = fParagraphStyle.getTextDirection() == TextDirection::kLtr
|
||||
? UBIDI_LTR
|
||||
: UBIDI_RTL;
|
||||
if (!SkTFitsIn<int32_t>(utf8Bytes)) {
|
||||
SkDEBUGF("Bidi error: text too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Getting the length like this seems to always set U_BUFFER_OVERFLOW_ERROR
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t utf16Units;
|
||||
u_strFromUTF8(nullptr, 0, &utf16Units, utf8, utf8Bytes, &status);
|
||||
status = U_ZERO_ERROR;
|
||||
std::unique_ptr<UChar[]> utf16(new UChar[utf16Units]);
|
||||
u_strFromUTF8(utf16.get(), utf16Units, nullptr, utf8, utf8Bytes, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Invalid utf8 input: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
ICUBiDi bidi(ubidi_openSized(utf16Units, 0, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Bidi error: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
SkASSERT(bidi);
|
||||
|
||||
// The required lifetime of utf16 isn't well documented.
|
||||
// It appears it isn't used after ubidi_setPara except through ubidi_getText.
|
||||
ubidi_setPara(bidi.get(), utf16.get(), utf16Units, bidiLevel, nullptr, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
SkDEBUGF("Bidi error: %s", u_errorName(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
SkTArray<BidiRegion> bidiRegions;
|
||||
const char* start8 = utf8;
|
||||
const char* end8 = utf8 + utf8Bytes;
|
||||
TextRange textRange(0, 0);
|
||||
UBiDiLevel currentLevel = 0;
|
||||
|
||||
int32_t pos16 = 0;
|
||||
int32_t end16 = ubidi_getLength(bidi.get());
|
||||
while (pos16 < end16) {
|
||||
auto level = ubidi_getLevelAt(bidi.get(), pos16);
|
||||
if (pos16 == 0) {
|
||||
currentLevel = level;
|
||||
} else if (level != currentLevel) {
|
||||
textRange.end = start8 - utf8;
|
||||
regions->emplace_back(textRange.start, textRange.end, currentLevel);
|
||||
currentLevel = level;
|
||||
textRange = TextRange(textRange.end, textRange.end);
|
||||
}
|
||||
SkUnichar u = utf8_next(&start8, end8);
|
||||
pos16 += SkUTF::ToUTF16(u);
|
||||
}
|
||||
|
||||
textRange.end = start8 - utf8;
|
||||
if (!textRange.empty()) {
|
||||
regions->emplace_back(textRange.start, textRange.end, currentLevel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "include/core/SkScalar.h"
|
||||
#include "include/core/SkString.h"
|
||||
#include "include/core/SkTypes.h"
|
||||
#include "include/private/SkBitmaskEnum.h"
|
||||
#include "include/private/SkTArray.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "include/private/SkTemplates.h"
|
||||
@ -34,6 +35,23 @@ class SkCanvas;
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
enum CodeUnitFlags {
|
||||
kNoCodeUnitFlag = 0x0,
|
||||
kPartOfWhiteSpace = 0x1,
|
||||
kGraphemeBreakBefore = 0x2,
|
||||
kSoftLineBreakBefore = 0x4,
|
||||
kHardLineBreakBefore = 0x8,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace sknonstd {
|
||||
template <> struct is_bitmask_enum<skia::textlayout::CodeUnitFlags> : std::true_type {};
|
||||
}
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
class LineMetrics;
|
||||
class TextLine;
|
||||
|
||||
@ -73,45 +91,6 @@ struct BidiRegion {
|
||||
uint8_t direction;
|
||||
};
|
||||
|
||||
class TextBreaker {
|
||||
public:
|
||||
TextBreaker() : fInitialized(false), fPos(-1) {}
|
||||
|
||||
bool initialize(SkSpan<const char> text, UBreakIteratorType type);
|
||||
|
||||
bool initialized() const { return fInitialized; }
|
||||
|
||||
size_t first() {
|
||||
fPos = ubrk_first(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
size_t next() {
|
||||
fPos = ubrk_next(fIterator.get());
|
||||
return eof() ? fSize : fPos;
|
||||
}
|
||||
|
||||
size_t preceding(size_t offset) {
|
||||
auto pos = ubrk_preceding(fIterator.get(), offset);
|
||||
return pos == UBRK_DONE ? 0 : pos;
|
||||
}
|
||||
|
||||
size_t following(size_t offset) {
|
||||
auto pos = ubrk_following(fIterator.get(), offset);
|
||||
return pos == UBRK_DONE ? fSize : pos;
|
||||
}
|
||||
|
||||
int32_t status() { return ubrk_getRuleStatus(fIterator.get()); }
|
||||
|
||||
bool eof() { return fPos == UBRK_DONE; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<UBreakIterator, SkFunctionWrapper<decltype(ubrk_close), ubrk_close>> fIterator;
|
||||
bool fInitialized;
|
||||
int32_t fPos;
|
||||
size_t fSize;
|
||||
};
|
||||
|
||||
class ParagraphImpl final : public Paragraph {
|
||||
|
||||
public:
|
||||
@ -159,8 +138,7 @@ public:
|
||||
const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; }
|
||||
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
|
||||
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
|
||||
const SkTHashSet<size_t>& graphemes() const { return fGraphemes; }
|
||||
SkSpan<Codepoint> codepoints(){ return SkSpan<Codepoint>(fCodePoints.begin(), fCodePoints.size()); }
|
||||
SkSpan<CodepointRepresentation> codepoints(){ return SkSpan<CodepointRepresentation>(fCodepoints.begin(), fCodepoints.size()); }
|
||||
void formatLines(SkScalar maxWidth);
|
||||
|
||||
bool strutEnabled() const { return paragraphStyle().getStrutStyle().getStrutEnabled(); }
|
||||
@ -193,8 +171,12 @@ public:
|
||||
|
||||
void resetContext();
|
||||
void resolveStrut();
|
||||
|
||||
bool computeCodeUnitProperties();
|
||||
bool computeWords();
|
||||
bool getBidiRegions();
|
||||
|
||||
void buildClusterTable();
|
||||
void markLineBreaks();
|
||||
void spaceGlyphs();
|
||||
bool shapeTextIntoEndlessLine();
|
||||
void breakShapedTextIntoLines(SkScalar maxWidth);
|
||||
@ -218,6 +200,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
using CodeUnitRangeVisitor = std::function<bool(TextRange textRange)>;
|
||||
void forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor);
|
||||
size_t getWhitespacesLength(TextRange textRange);
|
||||
|
||||
bool codeUnitHasProperty(size_t index, CodeUnitFlags property) const { return (fCodeUnitProperties[index] & property) == property; }
|
||||
|
||||
private:
|
||||
friend class ParagraphBuilder;
|
||||
friend class ParagraphCacheKey;
|
||||
@ -230,12 +218,9 @@ private:
|
||||
void calculateBoundaries();
|
||||
|
||||
void markGraphemes16();
|
||||
void markGraphemes();
|
||||
|
||||
void computeEmptyMetrics();
|
||||
|
||||
bool calculateBidiRegions(SkTArray<BidiRegion>* regions);
|
||||
|
||||
// Input
|
||||
SkTArray<StyleBlock<SkScalar>> fLetterSpaceStyles;
|
||||
SkTArray<StyleBlock<SkScalar>> fWordSpaceStyles;
|
||||
@ -251,9 +236,11 @@ private:
|
||||
InternalState fState;
|
||||
SkTArray<Run, false> fRuns; // kShaped
|
||||
SkTArray<Cluster, true> fClusters; // kClusterized (cached: text, word spacing, letter spacing, resolved fonts)
|
||||
SkTArray<CodeUnitFlags> fCodeUnitProperties;
|
||||
std::vector<size_t> fWords;
|
||||
SkTArray<BidiRegion> fBidiRegions;
|
||||
SkTArray<Grapheme, true> fGraphemes16;
|
||||
SkTArray<Codepoint, true> fCodePoints;
|
||||
SkTHashSet<size_t> fGraphemes;
|
||||
SkTArray<CodepointRepresentation, true> fCodepoints;
|
||||
size_t fUnresolvedGlyphs;
|
||||
|
||||
SkTArray<TextLine, false> fLines; // kFormatted (cached: width, max lines, ellipsis, text align)
|
||||
@ -268,9 +255,9 @@ private:
|
||||
SkScalar fOldHeight;
|
||||
SkScalar fMaxWidthWithTrailingSpaces;
|
||||
SkRect fOrigin;
|
||||
std::vector<size_t> fWords;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
||||
|
||||
#endif // ParagraphImpl_DEFINED
|
||||
|
@ -11,19 +11,6 @@
|
||||
#include "modules/skshaper/include/SkShaper.h"
|
||||
#include "src/utils/SkUTF.h"
|
||||
|
||||
#include <unicode/uchar.h>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
SkUnichar utf8_next(const char** ptr, const char* end) {
|
||||
SkUnichar val = SkUTF::NextUTF8(ptr, end);
|
||||
return val < 0 ? 0xFFFD : val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
@ -320,21 +307,6 @@ void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
|
||||
endlineMetrics->add(this);
|
||||
}
|
||||
|
||||
void Cluster::setIsWhiteSpaces() {
|
||||
|
||||
fWhiteSpaces = false;
|
||||
|
||||
auto span = fMaster->text(fTextRange);
|
||||
const char* ch = span.begin();
|
||||
while (ch < span.end()) {
|
||||
auto unichar = utf8_next(&ch, span.end());
|
||||
if (!u_isWhitespace(unichar)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fWhiteSpaces = true;
|
||||
}
|
||||
|
||||
SkScalar Cluster::sizeToChar(TextIndex ch) const {
|
||||
if (ch < fTextRange.start || ch >= fTextRange.end) {
|
||||
return 0;
|
||||
@ -391,6 +363,18 @@ SkFont Cluster::font() const {
|
||||
return fMaster->run(fRunIndex).font();
|
||||
}
|
||||
|
||||
bool Cluster::isHardBreak() const {
|
||||
return fMaster->codeUnitHasProperty(fTextRange.end,CodeUnitFlags::kHardLineBreakBefore);
|
||||
}
|
||||
|
||||
bool Cluster::isSoftBreak() const {
|
||||
return fMaster->codeUnitHasProperty(fTextRange.end,CodeUnitFlags::kSoftLineBreakBefore);
|
||||
}
|
||||
|
||||
bool Cluster::isGraphemeBreak() const {
|
||||
return fMaster->codeUnitHasProperty(fTextRange.end,CodeUnitFlags::kGraphemeBreakBefore);
|
||||
}
|
||||
|
||||
Cluster::Cluster(ParagraphImpl* master,
|
||||
RunIndex runIndex,
|
||||
size_t start,
|
||||
@ -407,9 +391,9 @@ Cluster::Cluster(ParagraphImpl* master,
|
||||
, fWidth(width)
|
||||
, fSpacing(0)
|
||||
, fHeight(height)
|
||||
, fHalfLetterSpacing(0.0)
|
||||
, fWhiteSpaces(false)
|
||||
, fBreakType(None) {
|
||||
, fHalfLetterSpacing(0.0) {
|
||||
size_t len = fMaster->getWhitespacesLength(fTextRange);
|
||||
fIsWhiteSpaces = (len == this->fTextRange.width());
|
||||
}
|
||||
|
||||
} // namespace textlayout
|
||||
|
@ -232,9 +232,9 @@ private:
|
||||
bool fSpaced;
|
||||
};
|
||||
|
||||
struct Codepoint {
|
||||
struct CodepointRepresentation {
|
||||
|
||||
Codepoint(GraphemeIndex graphemeIndex, TextIndex textIndex, size_t index)
|
||||
CodepointRepresentation(GraphemeIndex graphemeIndex, TextIndex textIndex, size_t index)
|
||||
: fGrapheme(graphemeIndex), fTextIndex(textIndex), fIndex(index) { }
|
||||
|
||||
GraphemeIndex fGrapheme;
|
||||
@ -268,9 +268,7 @@ public:
|
||||
, fWidth()
|
||||
, fSpacing(0)
|
||||
, fHeight()
|
||||
, fHalfLetterSpacing(0.0)
|
||||
, fWhiteSpaces(false)
|
||||
, fBreakType(None) {}
|
||||
, fHalfLetterSpacing(0.0) {}
|
||||
|
||||
Cluster(ParagraphImpl* master,
|
||||
RunIndex runIndex,
|
||||
@ -295,14 +293,11 @@ public:
|
||||
fWidth += shift;
|
||||
}
|
||||
|
||||
void setBreakType(BreakType type) { fBreakType = type; }
|
||||
bool isWhitespaces() const { return fWhiteSpaces; }
|
||||
bool canBreakLineAfter() const {
|
||||
return fBreakType == SoftLineBreak || fBreakType == HardLineBreak;
|
||||
}
|
||||
bool isHardBreak() const { return fBreakType == HardLineBreak; }
|
||||
bool isSoftBreak() const { return fBreakType == SoftLineBreak; }
|
||||
bool isGraphemeBreak() const { return fBreakType == GraphemeBreak; }
|
||||
bool isWhitespaces() const { return fIsWhiteSpaces; }
|
||||
bool isHardBreak() const;
|
||||
bool isSoftBreak() const;
|
||||
bool isGraphemeBreak() const;
|
||||
bool canBreakLineAfter() const { return isHardBreak() || isSoftBreak(); }
|
||||
size_t startPos() const { return fStart; }
|
||||
size_t endPos() const { return fEnd; }
|
||||
SkScalar width() const { return fWidth; }
|
||||
@ -322,8 +317,6 @@ public:
|
||||
|
||||
SkScalar trimmedWidth(size_t pos) const;
|
||||
|
||||
void setIsWhiteSpaces();
|
||||
|
||||
bool contains(TextIndex ch) const { return ch >= fTextRange.start && ch < fTextRange.end; }
|
||||
|
||||
bool belongs(TextRange text) const {
|
||||
@ -349,8 +342,7 @@ private:
|
||||
SkScalar fSpacing;
|
||||
SkScalar fHeight;
|
||||
SkScalar fHalfLetterSpacing;
|
||||
bool fWhiteSpaces;
|
||||
BreakType fBreakType;
|
||||
bool fIsWhiteSpaces;
|
||||
};
|
||||
|
||||
class InternalLineMetrics {
|
||||
|
@ -1131,7 +1131,7 @@ PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
|
||||
auto codepoint = std::lower_bound(
|
||||
codepoints.begin(), codepoints.end(),
|
||||
clusterIndex8,
|
||||
[](const Codepoint& lhs,size_t rhs) -> bool { return lhs.fTextIndex < rhs; });
|
||||
[](const CodepointRepresentation& lhs, size_t rhs) -> bool { return lhs.fTextIndex < rhs; });
|
||||
|
||||
return codepoint - codepoints.begin();
|
||||
};
|
||||
|
@ -2388,7 +2388,13 @@ DEF_TEST(SkParagraph_GetRectsForRangeTight, reporter) {
|
||||
" ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)("
|
||||
" ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)";
|
||||
const size_t len = strlen(text);
|
||||
/*
|
||||
( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)
|
||||
S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S
|
||||
G G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GGG G G G G G GG
|
||||
W W W W W W W W W W W W W W W W W W W W
|
||||
|
||||
*/
|
||||
ParagraphStyle paragraphStyle;
|
||||
paragraphStyle.setTextAlign(TextAlign::kLeft);
|
||||
paragraphStyle.setMaxLines(10);
|
||||
|
Loading…
Reference in New Issue
Block a user