1 // Copyright 2019 Google LLC.
2 #include <memory>
3
4 #include "modules/skparagraph/include/FontArguments.h"
5 #include "modules/skparagraph/include/ParagraphCache.h"
6 #include "modules/skparagraph/src/ParagraphImpl.h"
7 #include "src/base/SkFloatBits.h"
8 #ifdef ENABLE_TEXT_ENHANCE
9 #include "log.h"
10 #include "modules/skparagraph/include/TextGlobalConfig.h"
11 #include "src/TextParameter.h"
12 #include "trace.h"
13 #endif
14
15 using namespace skia_private;
16
17 namespace skia {
18 namespace textlayout {
19
20 namespace {
relax(SkScalar a)21 int32_t relax(SkScalar a) {
22 // This rounding is done to match Flutter tests. Must be removed..
23 if (SkIsFinite(a)) {
24 auto threshold = SkIntToScalar(1 << 12);
25 return SkFloat2Bits(SkScalarRoundToScalar(a * threshold)/threshold);
26 } else {
27 return SkFloat2Bits(a);
28 }
29 }
30
exactlyEqual(SkScalar x,SkScalar y)31 bool exactlyEqual(SkScalar x, SkScalar y) {
32 return x == y || (x != x && y != y);
33 }
34
35 } // namespace
36
37 class ParagraphCacheKey {
38 public:
ParagraphCacheKey(const ParagraphImpl * paragraph)39 ParagraphCacheKey(const ParagraphImpl* paragraph)
40 : fText(paragraph->fText.c_str(), paragraph->fText.size())
41 , fPlaceholders(paragraph->fPlaceholders)
42 , fTextStyles(paragraph->fTextStyles)
43 , fParagraphStyle(paragraph->paragraphStyle()) {
44 fHash = computeHash();
45 }
46
47 ParagraphCacheKey(const ParagraphCacheKey& other) = default;
48
ParagraphCacheKey(ParagraphCacheKey && other)49 ParagraphCacheKey(ParagraphCacheKey&& other)
50 : fText(std::move(other.fText))
51 , fPlaceholders(std::move(other.fPlaceholders))
52 , fTextStyles(std::move(other.fTextStyles))
53 , fParagraphStyle(std::move(other.fParagraphStyle))
54 , fHash(other.fHash) {
55 other.fHash = 0;
56 }
57
58 #ifdef ENABLE_TEXT_ENHANCE
59 // thin constructor suitable only for searching
ParagraphCacheKey(uint32_t hash)60 explicit ParagraphCacheKey(uint32_t hash) : fHash(hash) {}
61 #endif
62
63 bool operator==(const ParagraphCacheKey& other) const;
64
hash() const65 uint32_t hash() const { return fHash; }
66
text() const67 const SkString& text() const { return fText; }
68
69 private:
70 static uint32_t mix(uint32_t hash, uint32_t data);
71 uint32_t computeHash() const;
72 #ifdef ENABLE_TEXT_ENHANCE
73 void computeHashMix(uint32_t& hash) const;
74 #endif
75 SkString fText;
76 TArray<Placeholder, true> fPlaceholders;
77 TArray<Block, true> fTextStyles;
78 ParagraphStyle fParagraphStyle;
79 uint32_t fHash;
80 };
81
82 class ParagraphCacheValue {
83 public:
ParagraphCacheValue(ParagraphCacheKey && key,const ParagraphImpl * paragraph)84 ParagraphCacheValue(ParagraphCacheKey&& key, const ParagraphImpl* paragraph)
85 : fKey(std::move(key))
86 , fRuns(paragraph->fRuns)
87 , fClusters(paragraph->fClusters)
88 , fClustersIndexFromCodeUnit(paragraph->fClustersIndexFromCodeUnit)
89 , fCodeUnitProperties(paragraph->fCodeUnitProperties)
90 , fWords(paragraph->fWords)
91 , fBidiRegions(paragraph->fBidiRegions)
92 , fHasLineBreaks(paragraph->fHasLineBreaks)
93 , fHasWhitespacesInside(paragraph->fHasWhitespacesInside)
94 , fTrailingSpaces(paragraph->fTrailingSpaces)
95 #ifdef ENABLE_TEXT_ENHANCE
96 , fLayoutRawWidth(paragraph->fLayoutRawWidth) { }
97 #else
98 { }
99 #endif
100
101 // Input == key
102 ParagraphCacheKey fKey;
103
104 // Shaped results
105 TArray<Run, false> fRuns;
106 TArray<Cluster, true> fClusters;
107 TArray<size_t, true> fClustersIndexFromCodeUnit;
108 // ICU results
109 TArray<SkUnicode::CodeUnitFlags, true> fCodeUnitProperties;
110 std::vector<size_t> fWords;
111 std::vector<SkUnicode::BidiRegion> fBidiRegions;
112 bool fHasLineBreaks;
113 bool fHasWhitespacesInside;
114 TextIndex fTrailingSpaces;
115 #ifdef ENABLE_TEXT_ENHANCE
116 skia_private::TArray<TextLine, false> fLines;
117 SkScalar fHeight;
118 SkScalar fWidth;
119 SkScalar fMaxIntrinsicWidth;
120 SkScalar fMinIntrinsicWidth;
121 SkScalar fAlphabeticBaseline;
122 SkScalar fIdeographicBaseline;
123 SkScalar fLongestLine;
124 SkScalar fLongestLineWithIndent;
125 bool fExceededMaxLines;
126 // criteria to apply the layout cache, should presumably hash it, same
127 // hash could be used to check if the entry has cached layout available
128 LineBreakStrategy linebreakStrategy;
129 WordBreakType wordBreakType;
130 std::vector<SkScalar> indents;
131 SkScalar fLayoutRawWidth;
132 size_t maxlines;
133 bool hasEllipsis;
134 EllipsisModal ellipsisModal;
135 #endif
136 };
137
mix(uint32_t hash,uint32_t data)138 uint32_t ParagraphCacheKey::mix(uint32_t hash, uint32_t data) {
139 hash += data;
140 hash += (hash << 10);
141 hash ^= (hash >> 6);
142 return hash;
143 }
144
145 #ifdef ENABLE_TEXT_ENHANCE
computeHashMix(uint32_t & hash) const146 void ParagraphCacheKey::computeHashMix(uint32_t& hash) const {
147 hash = mix(hash, SkGoodHash()(relax(fParagraphStyle.getHeight())));
148 hash = mix(hash, SkGoodHash()(fParagraphStyle.getTextDirection()));
149 hash = mix(hash, SkGoodHash()(fParagraphStyle.getReplaceTabCharacters() ? 1 : 0));
150
151 hash = mix(hash, SkGoodHash()(fParagraphStyle.getTextHeightBehavior()));
152 hash = mix(hash, SkGoodHash()(relax(fParagraphStyle.getParagraphSpacing())));
153 hash = mix(hash, SkGoodHash()(fParagraphStyle.getIsEndAddParagraphSpacing()));
154 hash = mix(hash, SkGoodHash()(fParagraphStyle.getEnableAutoSpace()));
155 hash = mix(hash, SkGoodHash()(fParagraphStyle.getVerticalAlignment()));
156
157 auto& strutStyle = fParagraphStyle.getStrutStyle();
158 if (strutStyle.getStrutEnabled()) {
159 hash = mix(hash, SkGoodHash()(relax(strutStyle.getHeight())));
160 hash = mix(hash, SkGoodHash()(relax(strutStyle.getLeading())));
161 hash = mix(hash, SkGoodHash()(relax(strutStyle.getFontSize())));
162 hash = mix(hash, SkGoodHash()(strutStyle.getHeightOverride()));
163 hash = mix(hash, SkGoodHash()(strutStyle.getFontStyle()));
164 hash = mix(hash, SkGoodHash()(strutStyle.getForceStrutHeight()));
165 hash = mix(hash, SkGoodHash()(strutStyle.getHalfLeading()));
166
167 for (auto& ff : strutStyle.getFontFamilies()) {
168 hash = mix(hash, SkGoodHash()(ff));
169 }
170 }
171 }
172
computeHash() const173 uint32_t ParagraphCacheKey::computeHash() const {
174 uint32_t hash = 0;
175 hash = mix(hash, SkGoodHash()(TextGlobalConfig::UndefinedGlyphDisplayUseTofu()));
176 for (auto& ph : fPlaceholders) {
177 if (ph.fRange.width() == 0) {
178 continue;
179 }
180 hash = mix(hash, SkGoodHash()(ph.fRange));
181 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fHeight)));
182 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fWidth)));
183 hash = mix(hash, SkGoodHash()(ph.fStyle.fAlignment));
184 hash = mix(hash, SkGoodHash()(ph.fStyle.fBaseline));
185 if (ph.fStyle.fAlignment == PlaceholderAlignment::kBaseline) {
186 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fBaselineOffset)));
187 }
188 }
189
190 for (auto& ts : fTextStyles) {
191 if (ts.fStyle.isPlaceholder()) {
192 continue;
193 }
194 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getLetterSpacing())));
195 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getWordSpacing())));
196 hash = mix(hash, SkGoodHash()(ts.fStyle.getLocale()));
197 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getHeight())));
198 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getBaselineShift())));
199 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getHalfLeading())));
200
201 for (auto& ff : ts.fStyle.getFontFamilies()) {
202 hash = mix(hash, SkGoodHash()(ff));
203 }
204 for (auto& ff : ts.fStyle.getFontFeatures()) {
205 hash = mix(hash, SkGoodHash()(ff.fValue));
206 hash = mix(hash, SkGoodHash()(ff.fName));
207 }
208 hash = mix(hash, std::hash<std::optional<FontArguments>>()(ts.fStyle.getFontArguments()));
209 hash = mix(hash, SkGoodHash()(ts.fStyle.getFontStyle()));
210 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getFontSize())));
211 hash = mix(hash, SkGoodHash()(ts.fRange));
212 hash = mix(hash, SkGoodHash()(ts.fStyle.getTextBadgeType()));
213 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getTotalVerticalShift())));
214 }
215
216 computeHashMix(hash);
217
218 hash = mix(hash, SkGoodHash()(fText));
219 return hash;
220 }
221 #else
computeHash() const222 uint32_t ParagraphCacheKey::computeHash() const {
223 uint32_t hash = 0;
224 for (auto& ph : fPlaceholders) {
225 if (ph.fRange.width() == 0) {
226 continue;
227 }
228 hash = mix(hash, SkGoodHash()(ph.fRange));
229 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fHeight)));
230 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fWidth)));
231 hash = mix(hash, SkGoodHash()(ph.fStyle.fAlignment));
232 hash = mix(hash, SkGoodHash()(ph.fStyle.fBaseline));
233 if (ph.fStyle.fAlignment == PlaceholderAlignment::kBaseline) {
234 hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fBaselineOffset)));
235 }
236 }
237
238 for (auto& ts : fTextStyles) {
239 if (ts.fStyle.isPlaceholder()) {
240 continue;
241 }
242 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getLetterSpacing())));
243 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getWordSpacing())));
244 hash = mix(hash, SkGoodHash()(ts.fStyle.getLocale()));
245 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getHeight())));
246 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getBaselineShift())));
247 for (auto& ff : ts.fStyle.getFontFamilies()) {
248 hash = mix(hash, SkGoodHash()(ff));
249 }
250 for (auto& ff : ts.fStyle.getFontFeatures()) {
251 hash = mix(hash, SkGoodHash()(ff.fValue));
252 hash = mix(hash, SkGoodHash()(ff.fName));
253 }
254 hash = mix(hash, std::hash<std::optional<FontArguments>>()(ts.fStyle.getFontArguments()));
255 hash = mix(hash, SkGoodHash()(ts.fStyle.getFontStyle()));
256 hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getFontSize())));
257 hash = mix(hash, SkGoodHash()(ts.fRange));
258 }
259
260 hash = mix(hash, SkGoodHash()(relax(fParagraphStyle.getHeight())));
261 hash = mix(hash, SkGoodHash()(fParagraphStyle.getTextDirection()));
262 hash = mix(hash, SkGoodHash()(fParagraphStyle.getReplaceTabCharacters() ? 1 : 0));
263 hash = mix(hash, SkGoodHash()(fParagraphStyle.getTextHeightBehavior()));
264
265 auto& strutStyle = fParagraphStyle.getStrutStyle();
266 if (strutStyle.getStrutEnabled()) {
267 hash = mix(hash, SkGoodHash()(relax(strutStyle.getHeight())));
268 hash = mix(hash, SkGoodHash()(relax(strutStyle.getLeading())));
269 hash = mix(hash, SkGoodHash()(relax(strutStyle.getFontSize())));
270 hash = mix(hash, SkGoodHash()(strutStyle.getHeightOverride()));
271 hash = mix(hash, SkGoodHash()(strutStyle.getFontStyle()));
272 hash = mix(hash, SkGoodHash()(strutStyle.getForceStrutHeight()));
273 for (auto& ff : strutStyle.getFontFamilies()) {
274 hash = mix(hash, SkGoodHash()(ff));
275 }
276 }
277
278 hash = mix(hash, SkGoodHash()(fText));
279 return hash;
280 }
281 #endif
282
operator ()(const ParagraphCacheKey & key) const283 uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
284 return key.hash();
285 }
286
operator ==(const ParagraphCacheKey & other) const287 bool ParagraphCacheKey::operator==(const ParagraphCacheKey& other) const {
288 if (fText.size() != other.fText.size()) {
289 return false;
290 }
291 if (fPlaceholders.size() != other.fPlaceholders.size()) {
292 return false;
293 }
294 if (fText != other.fText) {
295 return false;
296 }
297 if (fTextStyles.size() != other.fTextStyles.size()) {
298 return false;
299 }
300
301 // There is no need to compare default paragraph styles - they are included into fTextStyles
302 if (!exactlyEqual(fParagraphStyle.getHeight(), other.fParagraphStyle.getHeight())) {
303 return false;
304 }
305 if (fParagraphStyle.getTextDirection() != other.fParagraphStyle.getTextDirection()) {
306 return false;
307 }
308
309 if (!(fParagraphStyle.getStrutStyle() == other.fParagraphStyle.getStrutStyle())) {
310 return false;
311 }
312
313 if (!(fParagraphStyle.getReplaceTabCharacters() == other.fParagraphStyle.getReplaceTabCharacters())) {
314 return false;
315 }
316
317 for (int i = 0; i < fTextStyles.size(); ++i) {
318 auto& tsa = fTextStyles[i];
319 auto& tsb = other.fTextStyles[i];
320 if (tsa.fStyle.isPlaceholder()) {
321 continue;
322 }
323 if (!(tsa.fStyle.equalsByFonts(tsb.fStyle))) {
324 return false;
325 }
326 if (tsa.fRange.width() != tsb.fRange.width()) {
327 return false;
328 }
329 if (tsa.fRange.start != tsb.fRange.start) {
330 return false;
331 }
332 }
333 for (int i = 0; i < fPlaceholders.size(); ++i) {
334 auto& tsa = fPlaceholders[i];
335 auto& tsb = other.fPlaceholders[i];
336 if (tsa.fRange.width() == 0 && tsb.fRange.width() == 0) {
337 continue;
338 }
339 if (!(tsa.fStyle.equals(tsb.fStyle))) {
340 return false;
341 }
342 if (tsa.fRange.width() != tsb.fRange.width()) {
343 return false;
344 }
345 if (tsa.fRange.start != tsb.fRange.start) {
346 return false;
347 }
348 }
349
350 return true;
351 }
352
353 struct ParagraphCache::Entry {
354
Entryskia::textlayout::ParagraphCache::Entry355 Entry(ParagraphCacheValue* value) : fValue(value) {}
356 std::unique_ptr<ParagraphCacheValue> fValue;
357 };
358
ParagraphCache()359 ParagraphCache::ParagraphCache()
360 : fChecker([](ParagraphImpl* impl, const char*, bool){ })
361 , fLRUCacheMap(kMaxEntries)
362 , fCacheIsOn(true)
363 , fLastCachedValue(nullptr)
364 #ifdef PARAGRAPH_CACHE_STATS
365 , fTotalRequests(0)
366 , fCacheMisses(0)
367 , fHashMisses(0)
368 #endif
369 { }
370
~ParagraphCache()371 ParagraphCache::~ParagraphCache() { }
372
updateTo(ParagraphImpl * paragraph,const Entry * entry)373 void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
374
375 paragraph->fRuns.clear();
376 paragraph->fRuns = entry->fValue->fRuns;
377 paragraph->fClusters = entry->fValue->fClusters;
378 paragraph->fClustersIndexFromCodeUnit = entry->fValue->fClustersIndexFromCodeUnit;
379 paragraph->fCodeUnitProperties = entry->fValue->fCodeUnitProperties;
380 paragraph->fWords = entry->fValue->fWords;
381 paragraph->fBidiRegions = entry->fValue->fBidiRegions;
382 paragraph->fHasLineBreaks = entry->fValue->fHasLineBreaks;
383 paragraph->fHasWhitespacesInside = entry->fValue->fHasWhitespacesInside;
384 paragraph->fTrailingSpaces = entry->fValue->fTrailingSpaces;
385 for (auto& run : paragraph->fRuns) {
386 run.setOwner(paragraph);
387 }
388 for (auto& cluster : paragraph->fClusters) {
389 cluster.setOwner(paragraph);
390 }
391 #ifdef ENABLE_TEXT_ENHANCE
392 paragraph->hash() = entry->fValue->fKey.hash();
393 #endif
394 }
395
printStatistics()396 void ParagraphCache::printStatistics() {
397 SkDebugf("--- Paragraph Cache ---\n");
398 SkDebugf("Total requests: %d\n", fTotalRequests);
399 SkDebugf("Cache misses: %d\n", fCacheMisses);
400 SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
401 int cacheHits = fTotalRequests - fCacheMisses;
402 SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
403 SkDebugf("---------------------\n");
404 }
405
abandon()406 void ParagraphCache::abandon() {
407 this->reset();
408 }
409
reset()410 void ParagraphCache::reset() {
411 SkAutoMutexExclusive lock(fParagraphMutex);
412 #ifdef PARAGRAPH_CACHE_STATS
413 fTotalRequests = 0;
414 fCacheMisses = 0;
415 fHashMisses = 0;
416 #endif
417 fLRUCacheMap.reset();
418 fLastCachedValue = nullptr;
419 }
420
421 #ifdef ENABLE_TEXT_ENHANCE
useCachedLayout(const ParagraphImpl & paragraph,const ParagraphCacheValue * value)422 bool ParagraphCache::useCachedLayout(const ParagraphImpl& paragraph, const ParagraphCacheValue* value) {
423 if (value && value->indents == paragraph.fIndents &&
424 paragraph.getLineBreakStrategy() == value->linebreakStrategy &&
425 paragraph.getWordBreakType() == value->wordBreakType &&
426 abs(paragraph.fLayoutRawWidth - value->fLayoutRawWidth) < 1.f &&
427 paragraph.fParagraphStyle.getMaxLines() == value->maxlines &&
428 paragraph.fParagraphStyle.ellipsized() == value->hasEllipsis &&
429 paragraph.fParagraphStyle.getEllipsisMod() == value->ellipsisModal &&
430 paragraph.fText.size() == value->fKey.text().size()) {
431 return true;
432 }
433 return false;
434 }
435
SetStoredLayout(ParagraphImpl & paragraph)436 void ParagraphCache::SetStoredLayout(ParagraphImpl& paragraph) {
437 SkAutoMutexExclusive lock(fParagraphMutex);
438 auto key = ParagraphCacheKey(¶graph);
439 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
440
441 if (entry && *entry) {
442 if (auto value = (*entry)->fValue.get()) {
443 SetStoredLayoutImpl(paragraph, value);
444 }
445 } else {
446 if (auto value = cacheLayout(¶graph)) {
447 SetStoredLayoutImpl(paragraph, value);
448 }
449 }
450 }
451
SetStoredLayoutImpl(ParagraphImpl & paragraph,ParagraphCacheValue * value)452 void ParagraphCache::SetStoredLayoutImpl(ParagraphImpl& paragraph, ParagraphCacheValue* value) {
453 if (paragraph.fRuns.size() == value->fRuns.size()) {
454 // update PlaceholderRun metrics cache value for placeholder alignment
455 for (size_t idx = 0; idx < value->fRuns.size(); ++idx) {
456 value->fRuns[idx].fAutoSpacings = paragraph.fRuns[idx].fAutoSpacings;
457 if (!value->fRuns[idx].isPlaceholder()) {
458 continue;
459 }
460 value->fRuns[idx].fFontMetrics = paragraph.fRuns[idx].fFontMetrics;
461 value->fRuns[idx].fCorrectAscent = paragraph.fRuns[idx].fCorrectAscent;
462 value->fRuns[idx].fCorrectDescent = paragraph.fRuns[idx].fCorrectDescent;
463 }
464 }
465 value->fLines.clear();
466 value->indents.clear();
467
468 for (auto& line : paragraph.fLines) {
469 value->fLines.emplace_back(line.CloneSelf());
470 }
471 paragraph.getSize(value->fHeight, value->fWidth, value->fLongestLine);
472 value->fLongestLineWithIndent = paragraph.getLongestLineWithIndent();
473 paragraph.getIntrinsicSize(value->fMaxIntrinsicWidth, value->fMinIntrinsicWidth,
474 value->fAlphabeticBaseline, value->fIdeographicBaseline,
475 value->fExceededMaxLines);
476 for (auto& indent : paragraph.fIndents) {
477 value->indents.push_back(indent);
478 }
479 value->linebreakStrategy = paragraph.getLineBreakStrategy();
480 value->wordBreakType = paragraph.getWordBreakType();
481 value->fLayoutRawWidth = paragraph.fLayoutRawWidth;
482 value->maxlines = paragraph.fParagraphStyle.getMaxLines();
483 value->hasEllipsis = paragraph.fParagraphStyle.ellipsized();
484 value->ellipsisModal = paragraph.fParagraphStyle.getEllipsisMod();
485 }
486
GetStoredLayout(ParagraphImpl & paragraph)487 bool ParagraphCache::GetStoredLayout(ParagraphImpl& paragraph) {
488 TEXT_TRACE_FUNC();
489 SkAutoMutexExclusive lock(fParagraphMutex);
490 auto key = ParagraphCacheKey(¶graph);
491 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
492 if (!entry || !*entry) {
493 return false;
494 }
495 ParagraphCacheValue* value = (*entry)->fValue.get();
496 if (!value) {
497 return false;
498 }
499 // Check if we have a match, that should be pretty much only lentgh and wrapping modes
500 // if the paragraph and text style match otherwise
501 if (!useCachedLayout(paragraph, value)) {
502 return false;
503 }
504 // Need to ensure we have sufficient info for restoring
505 // need some additiona? metrics
506 if (value->fLines.empty()) {
507 return false;
508 }
509 if (paragraph.fRuns.size() == value->fRuns.size()) {
510 // get PlaceholderRun metrics for placeholder alignment
511 for (size_t idx = 0; idx < value->fRuns.size(); ++idx) {
512 paragraph.fRuns[idx].fAutoSpacings = value->fRuns[idx].fAutoSpacings;
513 if (!value->fRuns[idx].isPlaceholder()) {
514 continue;
515 }
516 paragraph.fRuns[idx].fFontMetrics = value->fRuns[idx].fFontMetrics;
517 paragraph.fRuns[idx].fCorrectAscent = value->fRuns[idx].fCorrectAscent;
518 paragraph.fRuns[idx].fCorrectDescent = value->fRuns[idx].fCorrectDescent;
519 }
520 }
521 paragraph.fLines.clear();
522 for (auto& line : value->fLines) {
523 paragraph.fLines.emplace_back(line.CloneSelf());
524 paragraph.fLines.back().setParagraphImpl(¶graph);
525 }
526 paragraph.setSize(value->fHeight, value->fWidth, value->fLongestLine);
527 paragraph.setLongestLineWithIndent(value->fLongestLineWithIndent);
528 paragraph.setIntrinsicSize(value->fMaxIntrinsicWidth, value->fMinIntrinsicWidth,
529 value->fAlphabeticBaseline, value->fIdeographicBaseline,
530 value->fExceededMaxLines);
531 return true;
532 }
533 #endif
534
findParagraph(ParagraphImpl * paragraph)535 bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
536 #ifdef ENABLE_TEXT_ENHANCE
537 TEXT_TRACE_FUNC();
538 #endif
539 if (!fCacheIsOn) {
540 return false;
541 }
542 #ifdef PARAGRAPH_CACHE_STATS
543 ++fTotalRequests;
544 #endif
545 SkAutoMutexExclusive lock(fParagraphMutex);
546 ParagraphCacheKey key(paragraph);
547 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
548
549 if (!entry) {
550 #ifdef ENABLE_TEXT_ENHANCE
551 TEXT_LOGD("ParagraphCache: cache miss, hash-%{public}d", key.hash());
552 #endif
553 // We have a cache miss
554 #ifdef PARAGRAPH_CACHE_STATS
555 ++fCacheMisses;
556 #endif
557 fChecker(paragraph, "missingParagraph", true);
558 return false;
559 }
560 updateTo(paragraph, entry->get());
561 #ifdef ENABLE_TEXT_ENHANCE
562 TEXT_LOGD("ParagraphCache: cache hit, hash-%{public}d", key.hash());
563 paragraph->hash() = key.hash();
564 #endif
565 fChecker(paragraph, "foundParagraph", true);
566 return true;
567 }
568
updateParagraph(ParagraphImpl * paragraph)569 bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
570 if (!fCacheIsOn) {
571 return false;
572 }
573
574 #ifdef ENABLE_TEXT_ENHANCE
575 if (!canBeCached(paragraph)) {
576 return false;
577 }
578 #endif
579
580 #ifdef PARAGRAPH_CACHE_STATS
581 ++fTotalRequests;
582 #endif
583 SkAutoMutexExclusive lock(fParagraphMutex);
584
585 ParagraphCacheKey key(paragraph);
586 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
587 if (!entry) {
588 // isTooMuchMemoryWasted(paragraph) not needed for now
589 if (isPossiblyTextEditing(paragraph)) {
590 // Skip this paragraph
591 return false;
592 }
593 #ifdef ENABLE_TEXT_ENHANCE
594 paragraph->hash() = key.hash();
595 #endif
596 ParagraphCacheValue* value = new ParagraphCacheValue(std::move(key), paragraph);
597 fLRUCacheMap.insert(value->fKey, std::make_unique<Entry>(value));
598 fChecker(paragraph, "addedParagraph", true);
599 #ifndef ENABLE_TEXT_ENHANCE
600 fLastCachedValue = value;
601 #endif
602 return true;
603 } else {
604 // We do not have to update the paragraph
605 return false;
606 }
607 }
608
609 #ifdef ENABLE_TEXT_ENHANCE
610 // caller needs to hold fParagraphMutex
cacheLayout(ParagraphImpl * paragraph)611 ParagraphCacheValue* ParagraphCache::cacheLayout(ParagraphImpl* paragraph) {
612 if (!fCacheIsOn) {
613 return nullptr;
614 }
615
616 if (!canBeCached(paragraph)) {
617 return nullptr;
618 }
619 #ifdef PARAGRAPH_CACHE_STATS
620 ++fTotalRequests;
621 #endif
622
623 ParagraphCacheKey key(paragraph);
624 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
625 if (!entry) {
626 // isTooMuchMemoryWasted(paragraph) not needed for now
627 if (isPossiblyTextEditing(paragraph)) {
628 // Skip this paragraph
629 return nullptr;
630 }
631 paragraph->hash() = key.hash();
632 ParagraphCacheValue* value = new ParagraphCacheValue(std::move(key), paragraph);
633 fLRUCacheMap.insert(value->fKey, std::make_unique<Entry>(value));
634 fChecker(paragraph, "addedParagraph", true);
635 return value;
636 } else {
637 // Paragraph&layout already cached
638 return nullptr;
639 }
640 }
641
canBeCached(ParagraphImpl * paragraph) const642 bool ParagraphCache::canBeCached(ParagraphImpl* paragraph) const
643 {
644 if (paragraph == nullptr) {
645 return false;
646 }
647
648 if (paragraph->unicodeText().size() > TextParameter::GetUnicodeSizeLimitForParagraphCache()) {
649 TEXT_LOGD("Paragraph unicode size is out of limit, unicode size: %{public}zu",
650 paragraph->unicodeText().size());
651 return false;
652 }
653
654 return true;
655 }
656 #endif
657
658 // Special situation: (very) long paragraph that is close to the last formatted paragraph
659 #define NOCACHE_PREFIX_LENGTH 40
isPossiblyTextEditing(ParagraphImpl * paragraph)660 bool ParagraphCache::isPossiblyTextEditing(ParagraphImpl* paragraph) {
661 if (fLastCachedValue == nullptr) {
662 return false;
663 }
664
665 auto& lastText = fLastCachedValue->fKey.text();
666 auto& text = paragraph->fText;
667
668 if ((lastText.size() < NOCACHE_PREFIX_LENGTH) || (text.size() < NOCACHE_PREFIX_LENGTH)) {
669 // Either last text or the current are too short
670 return false;
671 }
672
673 if (std::strncmp(lastText.c_str(), text.c_str(), NOCACHE_PREFIX_LENGTH) == 0) {
674 // Texts have the same starts
675 return true;
676 }
677
678 if (std::strncmp(lastText.c_str() + lastText.size() - NOCACHE_PREFIX_LENGTH, &text[text.size() - NOCACHE_PREFIX_LENGTH], NOCACHE_PREFIX_LENGTH) == 0) {
679 // Texts have the same ends
680 return true;
681 }
682
683 // It does not look like editing the text
684 return false;
685 }
686 } // namespace textlayout
687 } // namespace skia
688