Paragraph cache: caching the shaped results only.
That makes layout phase 10 times faster (since the shaping takes 90% of it). LRU cache is attached to the FontCollection object and has the same life time. Currently it has hardcoded limit on the entry numbers (128). One the number reached, the least recently used element is removed from the cache to free the space for a new one. Change-Id: I597e334422614e33715d7a9ed13acf7b1f9cd0e4 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230755 Commit-Queue: Julia Lavrova <jlavrova@google.com> Reviewed-by: Derek Sollenberger <djsollen@google.com> Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
parent
c4abade253
commit
b7b0b3ad53
@ -7,11 +7,14 @@
|
||||
#include "include/core/SkFontMgr.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "modules/skparagraph/include/ParagraphCache.h"
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
class TextStyle;
|
||||
class Paragraph;
|
||||
class FontCollection : public SkRefCnt {
|
||||
public:
|
||||
FontCollection();
|
||||
@ -35,6 +38,8 @@ public:
|
||||
void disableFontFallback();
|
||||
bool fontFallbackEnabled() { return fEnableFontFallback; }
|
||||
|
||||
ParagraphCache* getParagraphCache() { return &fParagraphCache; }
|
||||
|
||||
private:
|
||||
std::vector<sk_sp<SkFontMgr>> getFontManagerOrder() const;
|
||||
|
||||
@ -61,7 +66,9 @@ private:
|
||||
sk_sp<SkFontMgr> fAssetFontManager;
|
||||
sk_sp<SkFontMgr> fDynamicFontManager;
|
||||
sk_sp<SkFontMgr> fTestFontManager;
|
||||
SkString fDefaultFamilyName;
|
||||
|
||||
const char* fDefaultFamilyName;
|
||||
ParagraphCache fParagraphCache;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
85
modules/skparagraph/include/ParagraphCache.h
Normal file
85
modules/skparagraph/include/ParagraphCache.h
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#ifndef ParagraphCache_DEFINED
|
||||
#define ParagraphCache_DEFINED
|
||||
|
||||
#include "include/private/SkMutex.h"
|
||||
#include "src/core/SkLRUCache.h"
|
||||
|
||||
#define PARAGRAPH_CACHE_STATS
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
struct Measurement {
|
||||
SkScalar fAlphabeticBaseline;
|
||||
SkScalar fIdeographicBaseline;
|
||||
SkScalar fHeight;
|
||||
SkScalar fWidth;
|
||||
SkScalar fMaxIntrinsicWidth;
|
||||
SkScalar fMinIntrinsicWidth;
|
||||
};
|
||||
|
||||
enum InternalState {
|
||||
kUnknown = 0,
|
||||
kShaped = 1,
|
||||
kClusterized = 2,
|
||||
kMarked = 3,
|
||||
kLineBroken = 4,
|
||||
kFormatted = 5,
|
||||
kDrawn = 6
|
||||
};
|
||||
|
||||
class ParagraphImpl;
|
||||
class ParagraphCacheKey;
|
||||
class ParagraphCacheValue;
|
||||
|
||||
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b);
|
||||
|
||||
class ParagraphCache {
|
||||
public:
|
||||
ParagraphCache();
|
||||
~ParagraphCache();
|
||||
|
||||
void abandon();
|
||||
void reset();
|
||||
bool updateParagraph(ParagraphImpl* paragraph);
|
||||
bool findParagraph(ParagraphImpl* paragraph);
|
||||
|
||||
// For testing
|
||||
void setChecker(std::function<void(ParagraphImpl* impl, const char*, bool)> checker) {
|
||||
fChecker = std::move(checker);
|
||||
}
|
||||
void printStatistics();
|
||||
void turnOn(bool value) { fCacheIsOn = value; }
|
||||
int count() { return fLRUCacheMap.count(); }
|
||||
|
||||
private:
|
||||
|
||||
struct Entry;
|
||||
void updateFrom(const ParagraphImpl* paragraph, Entry* entry);
|
||||
void updateTo(ParagraphImpl* paragraph, const Entry* entry);
|
||||
|
||||
mutable SkMutex fParagraphMutex;
|
||||
std::function<void(ParagraphImpl* impl, const char*, bool)> fChecker;
|
||||
|
||||
static const int kMaxEntries = 128;
|
||||
|
||||
struct KeyHash {
|
||||
uint32_t mix(uint32_t hash, uint32_t data) const;
|
||||
uint32_t operator()(const ParagraphCacheKey& key) const;
|
||||
};
|
||||
|
||||
SkLRUCache<ParagraphCacheKey, std::unique_ptr<Entry>, KeyHash> fLRUCacheMap;
|
||||
bool fCacheIsOn;
|
||||
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
int fTotalRequests;
|
||||
int fCacheMisses;
|
||||
int fHashMisses; // cache hit but hash table missed
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
||||
#endif // ParagraphCache_DEFINED
|
@ -12,6 +12,7 @@ skparagraph_public = [
|
||||
"$_include/FontCollection.h",
|
||||
"$_include/Paragraph.h",
|
||||
"$_include/ParagraphBuilder.h",
|
||||
"$_include/ParagraphCache.h",
|
||||
"$_include/DartTypes.h",
|
||||
"$_include/TypefaceFontProvider.h",
|
||||
"$_utils/TestFontCollection.h",
|
||||
@ -24,7 +25,6 @@ skparagraph_sources = [
|
||||
"$_src/Iterators.h",
|
||||
"$_src/ParagraphBuilderImpl.h",
|
||||
"$_src/ParagraphBuilderImpl.cpp",
|
||||
"$_src/ParagraphCache.h",
|
||||
"$_src/ParagraphCache.cpp",
|
||||
"$_src/ParagraphImpl.h",
|
||||
"$_src/ParagraphImpl.cpp",
|
||||
|
@ -1,5 +1,8 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "include/core/SkTypeface.h"
|
||||
#include "modules/skparagraph/include/FontCollection.h"
|
||||
#include "modules/skparagraph/include/Paragraph.h"
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
@ -93,7 +96,7 @@ sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontS
|
||||
|
||||
sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
|
||||
// Look inside the font collections cache first
|
||||
FamilyKey familyKey(fDefaultFamilyName.c_str(), "en", fontStyle);
|
||||
FamilyKey familyKey(fDefaultFamilyName, "en", fontStyle);
|
||||
auto found = fTypefaces.find(familyKey);
|
||||
if (found) {
|
||||
return *found;
|
||||
@ -101,7 +104,7 @@ sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
|
||||
|
||||
sk_sp<SkTypeface> typeface = nullptr;
|
||||
for (const auto& manager : this->getFontManagerOrder()) {
|
||||
SkFontStyleSet* set = manager->matchFamily(fDefaultFamilyName.c_str());
|
||||
SkFontStyleSet* set = manager->matchFamily(fDefaultFamilyName);
|
||||
if (nullptr == set || set->count() == 0) {
|
||||
continue;
|
||||
}
|
||||
@ -137,7 +140,7 @@ sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle
|
||||
if (fDefaultFontManager == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), fontStyle);
|
||||
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName, fontStyle);
|
||||
return sk_ref_sp<SkTypeface>(result);
|
||||
}
|
||||
|
||||
|
@ -232,13 +232,11 @@ SkUnichar FontResolver::firstUnresolved() {
|
||||
return fCodepoints[index];
|
||||
}
|
||||
|
||||
void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
|
||||
SkSpan<Block> styles,
|
||||
sk_sp<FontCollection> fontCollection) {
|
||||
fFontCollection = fontCollection;
|
||||
fStyles = styles;
|
||||
fText = utf8;
|
||||
fTextRange = TextRange(0, utf8.size());
|
||||
void FontResolver::findAllFontsForAllStyledBlocks(ParagraphImpl* master) {
|
||||
fFontCollection = master->fontCollection();
|
||||
fStyles = master->styles();
|
||||
fText = master->text();
|
||||
fTextRange = TextRange(0, fText.size());
|
||||
|
||||
Block combined;
|
||||
for (auto& block : fStyles) {
|
||||
@ -262,7 +260,7 @@ void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
|
||||
|
||||
fFontSwitches.reset();
|
||||
FontDescr* prev = nullptr;
|
||||
for (auto& ch : utf8) {
|
||||
for (auto& ch : fText) {
|
||||
if (fFontSwitches.count() == fFontMapping.count()) {
|
||||
// Checked all
|
||||
break;
|
||||
|
@ -33,12 +33,10 @@ public:
|
||||
FontResolver() = default;
|
||||
~FontResolver() = default;
|
||||
|
||||
void findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
|
||||
SkSpan<Block> styles,
|
||||
sk_sp<FontCollection> fontCollection);
|
||||
void findAllFontsForAllStyledBlocks(ParagraphImpl* master);
|
||||
bool findNext(const char* codepoint, SkFont* font, SkScalar* height);
|
||||
|
||||
SkTArray<FontDescr>& switches() { return fFontSwitches; }
|
||||
const SkTArray<FontDescr>& switches() const { return fFontSwitches; }
|
||||
|
||||
private:
|
||||
void findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange);
|
||||
|
@ -1,104 +1,69 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#include "modules/skparagraph/src/ParagraphCache.h"
|
||||
#include "modules/skparagraph/include/ParagraphCache.h"
|
||||
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
// Just the flutter input for now
|
||||
// TODO: We don't really need to copy anything...
|
||||
ParagraphCacheKey::ParagraphCacheKey(ParagraphImpl* paragraph)
|
||||
: fText(paragraph->fText)
|
||||
class ParagraphCacheKey {
|
||||
public:
|
||||
ParagraphCacheKey(const ParagraphImpl* paragraph)
|
||||
: fText(paragraph->fText.c_str(), paragraph->fText.size())
|
||||
, fFontSwitches(paragraph->switches())
|
||||
, fTextStyles(paragraph->fTextStyles)
|
||||
, fParagraphStyle(paragraph->paragraphStyle()) { }
|
||||
|
||||
// TODO: copying clusters and runs for now (there are minor things changed on formatting)
|
||||
ParagraphCacheValue::ParagraphCacheValue(ParagraphImpl* paragraph)
|
||||
SkString fText;
|
||||
SkTArray<FontDescr> fFontSwitches;
|
||||
SkTArray<Block, true> fTextStyles;
|
||||
ParagraphStyle fParagraphStyle;
|
||||
};
|
||||
|
||||
class ParagraphCacheValue {
|
||||
public:
|
||||
ParagraphCacheValue(const ParagraphImpl* paragraph)
|
||||
: fKey(ParagraphCacheKey(paragraph))
|
||||
, fInternalState(paragraph->state())
|
||||
, fRuns(paragraph->fRuns)
|
||||
, fClusters(paragraph->fClusters)
|
||||
, fMeasurement(paragraph->measurement())
|
||||
, fPicture(paragraph->fPicture) { }
|
||||
, fClusters(paragraph->fClusters) { }
|
||||
|
||||
bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
|
||||
// Input == key
|
||||
ParagraphCacheKey fKey;
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
if (this->count() == 0) {
|
||||
return false;
|
||||
}
|
||||
// Shaped results:
|
||||
InternalState fInternalState;
|
||||
SkTArray<Run> fRuns;
|
||||
SkTArray<Cluster, true> fClusters;
|
||||
SkTArray<RunShifts, true> fRunShifts;
|
||||
};
|
||||
|
||||
ParagraphCacheKey key(paragraph);
|
||||
auto found = this->find(key);
|
||||
if (found == nullptr) {
|
||||
fChecker(paragraph, "findParagraph", false);
|
||||
return false;
|
||||
}
|
||||
paragraph->fRuns.reset();
|
||||
paragraph->fRuns = found->fRuns;
|
||||
for (auto& run : paragraph->fRuns) {
|
||||
run.setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fClusters.reset();
|
||||
paragraph->fClusters = found->fClusters;
|
||||
for (auto& cluster : paragraph->fClusters) {
|
||||
cluster.setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fLines.reset();
|
||||
for (auto& line : found->fLines) {
|
||||
paragraph->fLines.push_back(line);
|
||||
paragraph->fLines.back().setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fState = found->fInternalState;
|
||||
paragraph->setMeasurement(found->fMeasurement);
|
||||
|
||||
paragraph->fOldWidth = found->fMeasurement.fWidth;
|
||||
paragraph->fOldHeight = found->fMeasurement.fHeight;
|
||||
|
||||
paragraph->fPicture = found->fPicture;
|
||||
|
||||
fChecker(paragraph, "findParagraph", true);
|
||||
return true;
|
||||
uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
|
||||
hash += data;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
return hash;
|
||||
}
|
||||
uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
|
||||
uint32_t hash = 0;
|
||||
for (auto& fd : key.fFontSwitches) {
|
||||
hash = mix(hash, SkGoodHash()(fd.fStart));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
|
||||
|
||||
void ParagraphCache::addParagraph(ParagraphImpl* paragraph) {
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
auto value = new ParagraphCacheValue(paragraph);
|
||||
this->add(value);
|
||||
fChecker(paragraph, "addParagraph1", true);
|
||||
}
|
||||
|
||||
void ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
|
||||
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
ParagraphCacheKey key(paragraph);
|
||||
auto found = this->find(key);
|
||||
if (found != nullptr) {
|
||||
found->fInternalState = paragraph->fState;
|
||||
found->fMeasurement = paragraph->measurement();
|
||||
found->fLines = paragraph->fLines;
|
||||
for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
|
||||
auto& run = paragraph->fRuns[i];
|
||||
if (run.fSpaced) {
|
||||
found->fRuns[i] = run;
|
||||
}
|
||||
if (fd.fFont.getTypeface() != nullptr) {
|
||||
SkString name;
|
||||
fd.fFont.getTypeface()->getFamilyName(&name);
|
||||
hash = mix(hash, SkGoodHash()(name));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
|
||||
}
|
||||
found->fPicture = paragraph->fPicture;
|
||||
fChecker(paragraph, "updateParagraph", true);
|
||||
} else {
|
||||
auto value = new ParagraphCacheValue(paragraph);
|
||||
this->add(value);
|
||||
fChecker(paragraph, "addParagraph2", true);
|
||||
}
|
||||
}
|
||||
|
||||
const ParagraphCacheKey& LookupTrait::GetKey(const ParagraphCacheValue& paragraph) {
|
||||
return paragraph.fKey;
|
||||
for (auto& ts : key.fTextStyles) {
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
|
||||
hash = mix(hash, SkGoodHash()(ts.fRange));
|
||||
}
|
||||
hash = mix(hash, SkGoodHash()(key.fText));
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
|
||||
@ -116,7 +81,7 @@ bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
|
||||
}
|
||||
|
||||
if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
|
||||
// TODO: this is too strong, but at least we will not lose lines
|
||||
// This is too strong, but at least we will not lose lines
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -151,55 +116,129 @@ bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t LookupTrait::mix(uint32_t hash, uint32_t data) {
|
||||
hash += data;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
return hash;
|
||||
}
|
||||
struct ParagraphCache::Entry {
|
||||
|
||||
uint32_t LookupTrait::Hash(const ParagraphCacheKey& key) {
|
||||
uint32_t hash = 0;
|
||||
for (auto& fd : key.fFontSwitches) {
|
||||
hash = mix(hash, SkGoodHash()(fd.fStart));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
|
||||
Entry(ParagraphCacheValue* value) : fValue(value) {}
|
||||
ParagraphCacheValue* fValue;
|
||||
};
|
||||
|
||||
if (fd.fFont.getTypeface() != nullptr) {
|
||||
SkString name;
|
||||
fd.fFont.getTypeface()->getFamilyName(&name);
|
||||
hash = mix(hash, SkGoodHash()(name));
|
||||
hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
|
||||
ParagraphCache::ParagraphCache()
|
||||
: fChecker([](ParagraphImpl* impl, const char*, bool){ })
|
||||
, fLRUCacheMap(kMaxEntries)
|
||||
, fCacheIsOn(true)
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
, fTotalRequests(0)
|
||||
, fCacheMisses(0)
|
||||
, fHashMisses(0)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
ParagraphCache::~ParagraphCache() { }
|
||||
|
||||
void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
|
||||
|
||||
entry->fValue->fInternalState = paragraph->state();
|
||||
entry->fValue->fRunShifts = paragraph->fRunShifts;
|
||||
for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
|
||||
auto& run = paragraph->fRuns[i];
|
||||
if (run.fSpaced) {
|
||||
entry->fValue->fRuns[i] = run;
|
||||
}
|
||||
}
|
||||
for (auto& ts : key.fTextStyles) {
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
|
||||
hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
|
||||
}
|
||||
hash = mix(hash, SkGoodHash()(key.fText));
|
||||
return hash;
|
||||
}
|
||||
|
||||
void ParagraphCache::printCache(const char* title) {
|
||||
void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
|
||||
paragraph->fRuns.reset();
|
||||
paragraph->fRuns = entry->fValue->fRuns;
|
||||
for (auto& run : paragraph->fRuns) {
|
||||
run.setMaster(paragraph);
|
||||
}
|
||||
|
||||
SkDebugf("\n\n%s\n", title);
|
||||
SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait>::Iter iter(this);
|
||||
while (!iter.done()) {
|
||||
ParagraphCacheValue* v = &*iter;
|
||||
const ParagraphCacheKey& k = LookupTrait::GetKey(*v);
|
||||
SkDebugf("key: '%s' runs(%d) clusters(%d)\n", k.fText.c_str(), v->fRuns.size(), v->fClusters.size());
|
||||
++iter;
|
||||
paragraph->fClusters.reset();
|
||||
paragraph->fClusters = entry->fValue->fClusters;
|
||||
for (auto& cluster : paragraph->fClusters) {
|
||||
cluster.setMaster(paragraph);
|
||||
}
|
||||
|
||||
paragraph->fRunShifts.reset();
|
||||
for (auto& runShift : entry->fValue->fRunShifts) {
|
||||
paragraph->fRunShifts.push_back(runShift);
|
||||
}
|
||||
|
||||
paragraph->fState = entry->fValue->fInternalState;
|
||||
}
|
||||
|
||||
void ParagraphCache::printStatistics() {
|
||||
SkDebugf("--- Paragraph Cache ---\n");
|
||||
SkDebugf("Total requests: %d\n", fTotalRequests);
|
||||
SkDebugf("Cache misses: %d\n", fCacheMisses);
|
||||
SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
|
||||
int cacheHits = fTotalRequests - fCacheMisses;
|
||||
SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
|
||||
SkDebugf("---------------------\n");
|
||||
}
|
||||
|
||||
void ParagraphCache::abandon() {
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
fLRUCacheMap.foreach([](std::unique_ptr<Entry>* e) {
|
||||
});
|
||||
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void ParagraphCache::reset() {
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
fTotalRequests = 0;
|
||||
fCacheMisses = 0;
|
||||
fHashMisses = 0;
|
||||
#endif
|
||||
fLRUCacheMap.reset();
|
||||
}
|
||||
|
||||
bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
|
||||
if (!fCacheIsOn) {
|
||||
return false;
|
||||
}
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
++fTotalRequests;
|
||||
#endif
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
ParagraphCacheKey key(paragraph);
|
||||
std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
|
||||
if (!entry) {
|
||||
// We have a cache miss
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
++fCacheMisses;
|
||||
#endif
|
||||
fChecker(paragraph, "missingParagraph", true);
|
||||
return false;
|
||||
}
|
||||
updateTo(paragraph, entry->get());
|
||||
fChecker(paragraph, "foundParagraph", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
|
||||
if (!fCacheIsOn) {
|
||||
return false;
|
||||
}
|
||||
#ifdef PARAGRAPH_CACHE_STATS
|
||||
++fTotalRequests;
|
||||
#endif
|
||||
SkAutoMutexExclusive lock(fParagraphMutex);
|
||||
ParagraphCacheKey key(paragraph);
|
||||
std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
|
||||
if (!entry) {
|
||||
ParagraphCacheValue* value = new ParagraphCacheValue(paragraph);
|
||||
fLRUCacheMap.insert(key, std::unique_ptr<Entry>(new Entry(value)));
|
||||
fChecker(paragraph, "addedParagraph", true);
|
||||
return true;
|
||||
} else {
|
||||
updateFrom(paragraph, entry->get());
|
||||
fChecker(paragraph, "updatedParagraph", true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ParagraphCache::printKeyValue(const char* title, ParagraphImpl* paragraph, bool found) {
|
||||
/*
|
||||
SkDebugf("%s '%s' ", title, paragraph->text().data());
|
||||
for (auto& fd : paragraph->switches()) {
|
||||
SkDebugf("%d ", fd.fFont.getTypeface() != nullptr ? fd.fFont.getTypeface()->uniqueID(): 0);
|
||||
};
|
||||
SkDebugf("\n");
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,98 +0,0 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
#ifndef ParagraphCache_DEFINED
|
||||
#define ParagraphCache_DEFINED
|
||||
|
||||
#include "include/core/SkFont.h"
|
||||
#include "include/core/SkPicture.h"
|
||||
#include "include/private/SkMutex.h"
|
||||
#include "include/private/SkTHash.h"
|
||||
#include "modules/skparagraph/include/ParagraphStyle.h"
|
||||
#include "modules/skparagraph/src/FontResolver.h"
|
||||
#include "src/core/SkLRUCache.h"
|
||||
#include "src/core/SkSpan.h"
|
||||
#include "src/core/SkTDynamicHash.h"
|
||||
|
||||
// TODO: we can cache shaped results or line broken results or formatted results or recorded picture...
|
||||
// TODO: let's start from the first: the shaped results
|
||||
namespace skia {
|
||||
namespace textlayout {
|
||||
|
||||
struct Measurement {
|
||||
SkScalar fAlphabeticBaseline;
|
||||
SkScalar fIdeographicBaseline;
|
||||
SkScalar fHeight;
|
||||
SkScalar fWidth;
|
||||
SkScalar fMaxIntrinsicWidth;
|
||||
SkScalar fMinIntrinsicWidth;
|
||||
};
|
||||
|
||||
enum InternalState {
|
||||
kUnknown = 0,
|
||||
kShaped = 1,
|
||||
kClusterized = 2,
|
||||
kMarked = 3,
|
||||
kLineBroken = 4,
|
||||
kFormatted = 5,
|
||||
kDrawn = 6
|
||||
};
|
||||
|
||||
class ParagraphImpl;
|
||||
class ParagraphCacheKey {
|
||||
public:
|
||||
ParagraphCacheKey(ParagraphImpl* paragraph);
|
||||
|
||||
SkString fText;
|
||||
SkTArray<FontDescr> fFontSwitches;
|
||||
// TODO: Do not check for more than we have to (which is text styles with letter spacing and word spacing)
|
||||
SkTArray<Block, true> fTextStyles;
|
||||
ParagraphStyle fParagraphStyle;
|
||||
};
|
||||
|
||||
class ParagraphCacheValue {
|
||||
public:;
|
||||
ParagraphCacheValue(ParagraphImpl* paragraph);
|
||||
|
||||
// Input == key
|
||||
ParagraphCacheKey fKey;
|
||||
|
||||
// Shaped results:
|
||||
InternalState fInternalState;
|
||||
SkTArray<Run> fRuns;
|
||||
SkTArray<Cluster, true> fClusters;
|
||||
SkTArray<TextLine, true> fLines;
|
||||
Measurement fMeasurement;
|
||||
sk_sp<SkPicture> fPicture;
|
||||
};
|
||||
|
||||
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b);
|
||||
|
||||
struct LookupTrait {
|
||||
static const ParagraphCacheKey& GetKey(const ParagraphCacheValue& paragraph);
|
||||
static uint32_t Hash(const ParagraphCacheKey& key);
|
||||
static uint32_t mix(uint32_t hash, uint32_t data);
|
||||
};
|
||||
|
||||
// TODO: can we cache the key by the text?... So we do not need to do font resolution again
|
||||
class ParagraphCache : public SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait> {
|
||||
public:
|
||||
|
||||
ParagraphCache() : fChecker([](ParagraphImpl* impl, const char*, bool){ }){ }
|
||||
bool findParagraph(ParagraphImpl* paragraph);
|
||||
void addParagraph(ParagraphImpl* paragraph);
|
||||
void updateParagraph(ParagraphImpl* paragraph);
|
||||
|
||||
// For testing
|
||||
|
||||
void setChecker(std::function<void(ParagraphImpl* impl, const char*, bool)> checker) { fChecker = checker; }
|
||||
void printCache(const char* title);
|
||||
void printKeyValue(const char* title, ParagraphImpl* paragraph, bool found);
|
||||
|
||||
private:
|
||||
mutable SkMutex fParagraphMutex;
|
||||
std::function<void(ParagraphImpl* impl, const char*, bool)> fChecker;
|
||||
};
|
||||
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
||||
#endif // ParagraphCache_DEFINED
|
@ -85,8 +85,6 @@ TextRange operator*(const TextRange& a, const TextRange& b) {
|
||||
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
|
||||
}
|
||||
|
||||
ParagraphCache ParagraphImpl::fParagraphCache;
|
||||
|
||||
ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
ParagraphStyle style,
|
||||
SkTArray<Block, true> blocks,
|
||||
@ -98,8 +96,7 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0)
|
||||
, fParagraphCacheOn(false) {
|
||||
, fOldHeight(0) {
|
||||
// TODO: extractStyles();
|
||||
}
|
||||
|
||||
@ -112,8 +109,7 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
|
||||
, fState(kUnknown)
|
||||
, fPicture(nullptr)
|
||||
, fOldWidth(0)
|
||||
, fOldHeight(0)
|
||||
, fParagraphCacheOn(false) {
|
||||
, fOldHeight(0) {
|
||||
icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
|
||||
std::string str;
|
||||
unicode.toUTF8String(str);
|
||||
@ -161,10 +157,9 @@ void ParagraphImpl::layout(SkScalar width) {
|
||||
fState = kClusterized;
|
||||
this->markLineBreaks();
|
||||
fState = kMarked;
|
||||
|
||||
// Add the paragraph to the cache
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
fFontCollection->getParagraphCache()->updateParagraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,10 +182,6 @@ void ParagraphImpl::layout(SkScalar width) {
|
||||
// Build the picture lazily not until we actually have to paint (or never)
|
||||
this->formatLines(fWidth);
|
||||
fState = kFormatted;
|
||||
// Add the paragraph to the cache
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
this->fOldWidth = width;
|
||||
@ -203,9 +194,6 @@ void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
|
||||
// Record the picture anyway (but if we have some pieces in the cache they will be used)
|
||||
this->paintLinesIntoPicture();
|
||||
fState = kDrawn;
|
||||
if (fParagraphCacheOn) {
|
||||
fParagraphCache.updateParagraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
SkMatrix matrix = SkMatrix::MakeTrans(x, y);
|
||||
@ -355,28 +343,27 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() {
|
||||
}
|
||||
|
||||
// This is a pretty big step - resolving all characters against all given fonts
|
||||
fFontResolver.findAllFontsForAllStyledBlocks(fTextSpan, styles(), fFontCollection);
|
||||
fFontResolver.findAllFontsForAllStyledBlocks(this);
|
||||
|
||||
// Check the font-resolved text against the cache
|
||||
if (fParagraphCacheOn && fParagraphCache.findParagraph(this)) {
|
||||
return true;
|
||||
}
|
||||
if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
|
||||
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
|
||||
FontIterator font(fTextSpan, &fFontResolver);
|
||||
ShapeHandler handler(*this, &font);
|
||||
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
|
||||
SkASSERT_RELEASE(shaper != nullptr);
|
||||
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
|
||||
fTextSpan.begin(), fTextSpan.size(),
|
||||
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2
|
||||
: (uint8_t)1);
|
||||
if (bidi == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
|
||||
|
||||
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
|
||||
FontIterator font(fTextSpan, &fFontResolver);
|
||||
ShapeHandler handler(*this, &font);
|
||||
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
|
||||
SkASSERT_RELEASE(shaper != nullptr);
|
||||
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
|
||||
fTextSpan.begin(), fTextSpan.size(),
|
||||
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 : (uint8_t)1);
|
||||
if (bidi == nullptr) {
|
||||
return false;
|
||||
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
|
||||
std::numeric_limits<SkScalar>::max(), &handler);
|
||||
}
|
||||
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
|
||||
|
||||
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
|
||||
std::numeric_limits<SkScalar>::max(), &handler);
|
||||
|
||||
if (fParagraphStyle.getTextAlign() == TextAlign::kJustify) {
|
||||
fRunShifts.reset();
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "modules/skparagraph/include/ParagraphStyle.h"
|
||||
#include "modules/skparagraph/include/TextStyle.h"
|
||||
#include "modules/skparagraph/src/FontResolver.h"
|
||||
#include "modules/skparagraph/src/ParagraphCache.h"
|
||||
#include "modules/skparagraph/src/Run.h"
|
||||
#include "modules/skparagraph/src/TextLine.h"
|
||||
|
||||
@ -73,13 +72,14 @@ public:
|
||||
SkSpan<const char> text() const { return fTextSpan; }
|
||||
InternalState state() const { return fState; }
|
||||
SkSpan<Run> runs() { return SkSpan<Run>(fRuns.data(), fRuns.size()); }
|
||||
SkTArray<FontDescr>& switches() { return fFontResolver.switches(); }
|
||||
const SkTArray<FontDescr>& switches() const { return fFontResolver.switches(); }
|
||||
SkSpan<Block> styles() {
|
||||
return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
|
||||
}
|
||||
SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
|
||||
ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
|
||||
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
|
||||
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
|
||||
void formatLines(SkScalar maxWidth);
|
||||
|
||||
void shiftCluster(ClusterIndex index, SkScalar shift) {
|
||||
@ -130,9 +130,8 @@ public:
|
||||
Block& block(BlockIndex blockIndex);
|
||||
|
||||
void markDirty() override { fState = kUnknown; }
|
||||
void turnOnCache(bool on) { fParagraphCacheOn = on; }
|
||||
FontResolver& getResolver() { return fFontResolver; }
|
||||
void setState(InternalState state);
|
||||
void resetCache() { fParagraphCache.reset(); }
|
||||
sk_sp<SkPicture> getPicture() { return fPicture; }
|
||||
|
||||
void resetContext();
|
||||
@ -178,10 +177,6 @@ private:
|
||||
|
||||
SkScalar fOldWidth;
|
||||
SkScalar fOldHeight;
|
||||
|
||||
// Cache
|
||||
bool fParagraphCacheOn;
|
||||
static ParagraphCache fParagraphCache;
|
||||
};
|
||||
} // namespace textlayout
|
||||
} // namespace skia
|
||||
|
@ -29,9 +29,13 @@ namespace {
|
||||
|
||||
class ParagraphView_Base : public Sample {
|
||||
protected:
|
||||
sk_sp<TestFontCollection> fFC;
|
||||
void onOnceBeforeDraw() override {
|
||||
fFC = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str());
|
||||
sk_sp<TestFontCollection> getFontCollection() {
|
||||
// If we reset font collection we need to reset paragraph cache
|
||||
static sk_sp<TestFontCollection> fFC = nullptr;
|
||||
if (fFC == nullptr) {
|
||||
fFC = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str());
|
||||
}
|
||||
return fFC;
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,11 +102,11 @@ protected:
|
||||
defaultStyle.setForegroundColor(paint);
|
||||
ParagraphStyle paraStyle;
|
||||
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
for (auto i = 1; i < 5; ++i) {
|
||||
defaultStyle.setFontSize(24 * i);
|
||||
paraStyle.setTextStyle(defaultStyle);
|
||||
auto fontCollection = sk_make_sp<FontCollection>();
|
||||
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
|
||||
ParagraphBuilderImpl builder(paraStyle, fontCollection);
|
||||
std::string name = "Paragraph: " + std::to_string(24 * i);
|
||||
builder.addText(name.c_str());
|
||||
@ -276,7 +280,7 @@ protected:
|
||||
TextStyle defaultStyle;
|
||||
defaultStyle.setFontSize(20);
|
||||
paraStyle.setTextStyle(defaultStyle);
|
||||
ParagraphBuilderImpl builder(paraStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
|
||||
|
||||
SkPaint foreground;
|
||||
foreground.setColor(fg);
|
||||
@ -621,7 +625,7 @@ protected:
|
||||
const char* logo5 = "google_lo";
|
||||
const char* logo6 = "go";
|
||||
{
|
||||
ParagraphBuilderImpl builder(paraStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
|
||||
|
||||
builder.pushStyle(style0);
|
||||
builder.addText(logo1);
|
||||
@ -711,7 +715,7 @@ protected:
|
||||
|
||||
paraStyle.setEllipsis(ellipsis);
|
||||
|
||||
ParagraphBuilderImpl builder(paraStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
|
||||
|
||||
if (text.empty()) {
|
||||
const std::u16string text0 = u"\u202Dabc";
|
||||
@ -871,7 +875,7 @@ protected:
|
||||
const char* logo5 = "Ski";
|
||||
const char* logo6 = "a";
|
||||
{
|
||||
ParagraphBuilderImpl builder(paraStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
|
||||
|
||||
builder.pushStyle(style0);
|
||||
builder.addText(logo1);
|
||||
@ -911,7 +915,7 @@ protected:
|
||||
const char* logo15 = "S";
|
||||
const char* logo16 = "S";
|
||||
{
|
||||
ParagraphBuilderImpl builder(paraStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
|
||||
|
||||
builder.pushStyle(style0);
|
||||
builder.addText(logo11);
|
||||
@ -982,7 +986,7 @@ protected:
|
||||
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
|
||||
SkFontStyle::kUpright_Slant));
|
||||
|
||||
ParagraphBuilderImpl builder(paragraphStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
|
||||
builder.pushStyle(textStyle);
|
||||
builder.addText(line);
|
||||
builder.pop();
|
||||
@ -1052,7 +1056,7 @@ protected:
|
||||
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
|
||||
SkFontStyle::kUpright_Slant));
|
||||
|
||||
ParagraphBuilderImpl builder(paragraphStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
|
||||
builder.pushStyle(textStyle);
|
||||
builder.addText(line);
|
||||
builder.pop();
|
||||
@ -1143,7 +1147,7 @@ protected:
|
||||
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
|
||||
SkFontStyle::kUpright_Slant));
|
||||
|
||||
ParagraphBuilderImpl builder(paragraphStyle, fFC);
|
||||
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
|
||||
builder.pushStyle(textStyle);
|
||||
builder.addText(text);
|
||||
builder.pop();
|
||||
@ -1202,7 +1206,7 @@ protected:
|
||||
const char* text = "English English 字典 字典 😀😃😄 😀😃😄";
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fFC);
|
||||
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto"),
|
||||
@ -1242,7 +1246,7 @@ protected:
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
ParagraphStyle paragraph_style;
|
||||
ParagraphBuilderImpl builder(paragraph_style, fFC);
|
||||
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
@ -1270,7 +1274,7 @@ protected:
|
||||
paragraph_style.setMaxLines(14);
|
||||
paragraph_style.setTextAlign(TextAlign::kLeft);
|
||||
paragraph_style.turnHintingOff();
|
||||
ParagraphBuilderImpl builder(paragraph_style, fFC);
|
||||
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
@ -1285,7 +1289,6 @@ protected:
|
||||
|
||||
auto paragraph = builder.Build();
|
||||
auto impl = reinterpret_cast<ParagraphImpl*>(paragraph.get());
|
||||
impl->turnOnCache(false);
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
impl->setState(kUnknown);
|
||||
|
@ -2944,3 +2944,165 @@ DEF_TEST(SkParagraph_JSON2, reporter) {
|
||||
|
||||
SkASSERT(cluster <= 2);
|
||||
}
|
||||
|
||||
|
||||
DEF_TEST(SkParagraph_CacheText, reporter) {
|
||||
|
||||
ParagraphCache cache;
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
auto test = [&](const char* text, int count, bool expectedToBeFound) {
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text);
|
||||
builder.pop();
|
||||
auto paragraph = builder.Build();
|
||||
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
REPORTER_ASSERT(reporter, count == cache.count());
|
||||
auto found = cache.findParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, found == expectedToBeFound);
|
||||
auto added = cache.updateParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, added != expectedToBeFound);
|
||||
};
|
||||
|
||||
test("text1", 0, false);
|
||||
test("text1", 1, true);
|
||||
test("text2", 1, false);
|
||||
test("text2", 2, true);
|
||||
test("text3", 2, false);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_CacheFonts, reporter) {
|
||||
|
||||
ParagraphCache cache;
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
auto test = [&](int count, bool expectedToBeFound) {
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText("text");
|
||||
builder.pop();
|
||||
auto paragraph = builder.Build();
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
|
||||
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
|
||||
|
||||
REPORTER_ASSERT(reporter, count == cache.count());
|
||||
auto found = cache.findParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, found == expectedToBeFound);
|
||||
auto added = cache.updateParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, added != expectedToBeFound);
|
||||
};
|
||||
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
test(0, false);
|
||||
test(1, true);
|
||||
text_style.setFontFamilies({SkString("Homemade Apple")});
|
||||
test(1, false);
|
||||
test(2, true);
|
||||
text_style.setFontFamilies({SkString("Noto Color Emoji")});
|
||||
test(2, false);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_CacheFontRanges, reporter) {
|
||||
|
||||
ParagraphCache cache;
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
auto test = [&](const char* text1,
|
||||
const char* text2,
|
||||
const char* font1,
|
||||
const char* font2,
|
||||
int count,
|
||||
bool expectedToBeFound) {
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
text_style.setFontFamilies({SkString(font1)});
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text1);
|
||||
builder.pop();
|
||||
text_style.setFontFamilies({SkString(font2)});
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText(text2);
|
||||
builder.pop();
|
||||
auto paragraph = builder.Build();
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
|
||||
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
|
||||
|
||||
REPORTER_ASSERT(reporter, count == cache.count());
|
||||
auto found = cache.findParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, found == expectedToBeFound);
|
||||
auto added = cache.updateParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, added != expectedToBeFound);
|
||||
};
|
||||
|
||||
test("text", "", "Roboto", "Homemade Apple", 0, false);
|
||||
test("t", "ext", "Roboto", "Homemade Apple", 1, false);
|
||||
test("te", "xt", "Roboto", "Homemade Apple", 2, false);
|
||||
test("tex", "t", "Roboto", "Homemade Apple", 3, false);
|
||||
test("text", "", "Roboto", "Homemade Apple", 4, true);
|
||||
}
|
||||
|
||||
DEF_TEST(SkParagraph_CacheStyles, reporter) {
|
||||
|
||||
ParagraphCache cache;
|
||||
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
|
||||
if (!fontCollection->fontsFound()) return;
|
||||
|
||||
ParagraphStyle paragraph_style;
|
||||
paragraph_style.turnHintingOff();
|
||||
|
||||
TextStyle text_style;
|
||||
text_style.setFontFamilies({SkString("Roboto")});
|
||||
text_style.setColor(SK_ColorBLACK);
|
||||
|
||||
auto test = [&](int count, bool expectedToBeFound) {
|
||||
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
|
||||
builder.pushStyle(text_style);
|
||||
builder.addText("text");
|
||||
builder.pop();
|
||||
auto paragraph = builder.Build();
|
||||
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
|
||||
|
||||
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
|
||||
|
||||
REPORTER_ASSERT(reporter, count == cache.count());
|
||||
auto found = cache.findParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, found == expectedToBeFound);
|
||||
auto added = cache.updateParagraph(impl);
|
||||
REPORTER_ASSERT(reporter, added != expectedToBeFound);
|
||||
};
|
||||
|
||||
|
||||
test(0, false);
|
||||
test(1, true);
|
||||
text_style.setLetterSpacing(10);
|
||||
test(1, false);
|
||||
test(2, true);
|
||||
text_style.setWordSpacing(10);
|
||||
test(2, false);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user