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