• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&paragraph);
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(&paragraph)) {
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(&paragraph);
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(&paragraph);
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