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