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