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(¶graph);
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(¶graph)) {
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(¶graph);
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(¶graph);
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