• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 #include "include/core/SkCanvas.h"
3 #include "include/core/SkFontMetrics.h"
4 #include "include/core/SkMatrix.h"
5 #include "include/core/SkPath.h"
6 #include "include/core/SkPictureRecorder.h"
7 #include "include/core/SkSpan.h"
8 #include "include/core/SkTypeface.h"
9 #include "include/private/base/SkTFitsIn.h"
10 #include "include/private/base/SkTo.h"
11 #include "modules/skparagraph/include/Metrics.h"
12 #include "modules/skparagraph/include/Paragraph.h"
13 #include "modules/skparagraph/include/ParagraphPainter.h"
14 #include "modules/skparagraph/include/ParagraphStyle.h"
15 #include "modules/skparagraph/include/TextStyle.h"
16 #include "modules/skparagraph/src/OneLineShaper.h"
17 #include "modules/skparagraph/src/ParagraphImpl.h"
18 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
19 #include "modules/skparagraph/src/Run.h"
20 #include "modules/skparagraph/src/TextLine.h"
21 #include "modules/skparagraph/src/TextWrapper.h"
22 #include "modules/skunicode/include/SkUnicode.h"
23 #include "src/base/SkUTF.h"
24 #include "src/core/SkTextBlobPriv.h"
25 
26 #include <algorithm>
27 #include <cfloat>
28 #include <cmath>
29 #include <utility>
30 
31 #ifdef ENABLE_TEXT_ENHANCE
32 #include <sstream>
33 #include "trace.h"
34 #include "log.h"
35 #include "include/TextGlobalConfig.h"
36 #include "modules/skparagraph/src/TextLineBaseImpl.h"
37 #include "TextParameter.h"
38 #endif
39 
40 using namespace skia_private;
41 
42 namespace skia {
43 namespace textlayout {
44 
45 namespace {
46 #ifdef ENABLE_TEXT_ENHANCE
47 constexpr ParagraphPainter::PaintID INVALID_PAINT_ID = -1;
48 #endif
49 
littleRound(SkScalar a)50 SkScalar littleRound(SkScalar a) {
51     // This rounding is done to match Flutter tests. Must be removed..
52     auto val = std::fabs(a);
53     if (val < 10000) {
54         return SkScalarRoundToScalar(a * 100.0)/100.0;
55     } else if (val < 100000) {
56         return SkScalarRoundToScalar(a * 10.0)/10.0;
57     } else {
58         return SkScalarFloorToScalar(a);
59     }
60 }
61 }  // namespace
62 
operator *(const TextRange & a,const TextRange & b)63 TextRange operator*(const TextRange& a, const TextRange& b) {
64     if (a.start == b.start && a.end == b.end) return a;
65     auto begin = std::max(a.start, b.start);
66     auto end = std::min(a.end, b.end);
67     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
68 }
69 
70 #ifdef ENABLE_TEXT_ENHANCE
textRangeMergeBtoA(const TextRange & a,const TextRange & b)71 TextRange textRangeMergeBtoA(const TextRange& a, const TextRange& b) {
72     if (a.width() <= 0 || b.width() <= 0 || a.end < b.start || a.start > b.end) {
73         return a;
74     }
75 
76     return TextRange(std::min(a.start, b.start), std::max(a.end, b.end));
77 }
78 
convertUtf8ToUnicode(const SkString & utf8)79 std::vector<SkUnichar> ParagraphImpl::convertUtf8ToUnicode(const SkString& utf8)
80 {
81     fUnicodeIndexForUTF8Index.clear();
82     std::vector<SkUnichar> result;
83     auto p = utf8.c_str();
84     auto end = p + utf8.size();
85     while (p < end) {
86         auto tmp = p;
87         auto unichar = SkUTF::NextUTF8(&p, end);
88         for (auto i = 0; i < p - tmp; ++i) {
89             fUnicodeIndexForUTF8Index.emplace_back(result.size());
90         }
91         result.emplace_back(unichar);
92     }
93     fUnicodeIndexForUTF8Index.emplace_back(result.size());
94     return result;
95 }
96 
needCreateMiddleEllipsis()97 bool ParagraphImpl::needCreateMiddleEllipsis()
98 {
99     if (fParagraphStyle.getMaxLines() == 1 && fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE &&
100         fParagraphStyle.ellipsized()) {
101         return true;
102     }
103     return false;
104 }
105 
getPlaceholderByIndex(size_t placeholderIndex)106 Placeholder* ParagraphImpl::getPlaceholderByIndex(size_t placeholderIndex)
107 {
108     if (fPlaceholders.size() <= placeholderIndex) {
109         LOGE("Failed to get placeholder");
110         return nullptr;
111     }
112     return &fPlaceholders[placeholderIndex];
113 }
114 
IsPlaceholderAlignedFollowParagraph(size_t placeholderIndex)115 bool ParagraphImpl::IsPlaceholderAlignedFollowParagraph(size_t placeholderIndex)
116 {
117     Placeholder* placeholder = getPlaceholderByIndex(placeholderIndex);
118     if (placeholder == nullptr) {
119         return false;
120     }
121     return placeholder->fStyle.fAlignment == PlaceholderAlignment::kFollow;
122 }
123 
setPlaceholderAlignment(size_t placeholderIndex,PlaceholderAlignment alignment)124 bool ParagraphImpl::setPlaceholderAlignment(size_t placeholderIndex, PlaceholderAlignment alignment)
125 {
126     Placeholder* placeholder = getPlaceholderByIndex(placeholderIndex);
127     if (placeholder == nullptr) {
128         return false;
129     }
130     placeholder->fStyle.fAlignment = alignment;
131     return true;
132 }
133 
134 
getBlockByRun(const Run & run)135 Block& ParagraphImpl::getBlockByRun(const Run& run)
136 {
137     TextRange textRange = run.textRange();
138     BlockRange blocksRange = findAllBlocks(textRange);
139     return block(blocksRange.start);
140 }
141 #endif
142 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)143 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
144             : fFontCollection(std::move(fonts))
145             , fParagraphStyle(std::move(style))
146             , fAlphabeticBaseline(0)
147             , fIdeographicBaseline(0)
148             , fHeight(0)
149             , fWidth(0)
150             , fMaxIntrinsicWidth(0)
151             , fMinIntrinsicWidth(0)
152             , fLongestLine(0)
153 #ifdef ENABLE_TEXT_ENHANCE
154             , fLongestLineWithIndent(0)
155 #endif
156             , fExceededMaxLines(0)
157 {
158     SkASSERT(fFontCollection);
159 }
160 
ParagraphImpl(const SkString & text,ParagraphStyle style,TArray<Block,true> blocks,TArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,sk_sp<SkUnicode> unicode)161 ParagraphImpl::ParagraphImpl(const SkString& text,
162                              ParagraphStyle style,
163                              TArray<Block, true> blocks,
164                              TArray<Placeholder, true> placeholders,
165                              sk_sp<FontCollection> fonts,
166                              sk_sp<SkUnicode> unicode)
167         : Paragraph(std::move(style), std::move(fonts))
168         , fTextStyles(std::move(blocks))
169         , fPlaceholders(std::move(placeholders))
170         , fText(text)
171         , fState(kUnknown)
172         , fUnresolvedGlyphs(0)
173         , fPicture(nullptr)
174         , fStrutMetrics(false)
175         , fOldWidth(0)
176         , fOldHeight(0)
177         , fUnicode(std::move(unicode))
178         , fHasLineBreaks(false)
179         , fHasWhitespacesInside(false)
180         , fTrailingSpaces(0)
181 {
182     SkASSERT(fUnicode);
183 }
184 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,TArray<Block,true> blocks,TArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,sk_sp<SkUnicode> unicode)185 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
186                              ParagraphStyle style,
187                              TArray<Block, true> blocks,
188                              TArray<Placeholder, true> placeholders,
189                              sk_sp<FontCollection> fonts,
190                              sk_sp<SkUnicode> unicode)
191         : ParagraphImpl(SkString(),
192                         std::move(style),
193                         std::move(blocks),
194                         std::move(placeholders),
195                         std::move(fonts),
196                         std::move(unicode))
197 {
198     SkASSERT(fUnicode);
199     fText =  SkUnicode::convertUtf16ToUtf8(utf16text);
200 }
201 
202 ParagraphImpl::~ParagraphImpl() = default;
203 
unresolvedGlyphs()204 int32_t ParagraphImpl::unresolvedGlyphs() {
205     if (fState < kShaped) {
206         return -1;
207     }
208 
209     return fUnresolvedGlyphs;
210 }
211 
212 #ifdef ENABLE_TEXT_ENHANCE
GetLineFontMetrics(const size_t lineNumber,size_t & charNumber,std::vector<RSFontMetrics> & fontMetrics)213 bool ParagraphImpl::GetLineFontMetrics(const size_t lineNumber, size_t& charNumber,
214     std::vector<RSFontMetrics>& fontMetrics) {
215     if (lineNumber > fLines.size() || !lineNumber ||
216         !fLines[lineNumber - 1].getLineAllRuns().size()) {
217         return false;
218     }
219 
220     size_t textRange = 0;
221     size_t lineCharCount = fLines[lineNumber - 1].clusters().end -
222         fLines[lineNumber - 1].clusters().start;
223 
224     for (auto& runIndex : fLines[lineNumber - 1].getLineAllRuns()) {
225         Run& targetRun = this->run(runIndex);
226         size_t runClock = 0;
227         size_t currentRunCharNumber = targetRun.clusterRange().end -
228             targetRun.clusterRange().start;
229         for (;textRange < lineCharCount; textRange++) {
230             if (++runClock > currentRunCharNumber) {
231                 break;
232             }
233             RSFontMetrics newFontMetrics;
234             targetRun.fFont.GetMetrics(&newFontMetrics);
235             auto decompressFont = targetRun.fFont;
236             scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
237             metricsIncludeFontPadding(&newFontMetrics, decompressFont);
238             fontMetrics.emplace_back(newFontMetrics);
239         }
240     }
241 
242     charNumber = lineCharCount;
243     return true;
244 }
245 #endif
246 
unresolvedCodepoints()247 std::unordered_set<SkUnichar> ParagraphImpl::unresolvedCodepoints() {
248     return fUnresolvedCodepoints;
249 }
250 
addUnresolvedCodepoints(TextRange textRange)251 void ParagraphImpl::addUnresolvedCodepoints(TextRange textRange) {
252     fUnicode->forEachCodepoint(
253         &fText[textRange.start], textRange.width(),
254         [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
255             fUnresolvedCodepoints.emplace(unichar);
256         }
257     );
258 }
259 
260 #ifdef ENABLE_TEXT_ENHANCE
resetRangeWithDeletedRange(const TextRange & sourceRange,const TextRange & deletedRange,const size_t & ellSize)261 TextRange ParagraphImpl::resetRangeWithDeletedRange(const TextRange& sourceRange,
262     const TextRange& deletedRange, const size_t& ellSize)
263 {
264     if (sourceRange.end <= deletedRange.start) {
265         return sourceRange;
266     }
267     auto changeSize = ellSize - deletedRange.width();
268 
269     if (sourceRange.start >= deletedRange.end) {
270         return TextRange(sourceRange.start + changeSize, sourceRange.end + changeSize);
271     }
272 
273     TextRange target;
274     target.start = sourceRange.start <= deletedRange.start ? sourceRange.start : deletedRange.start + ellSize;
275     target.end = sourceRange.end <= deletedRange.end ? deletedRange.start + ellSize : sourceRange.end + changeSize;
276     return target.start <= target.end ? target : EMPTY_RANGE;
277 }
278 
resetTextStyleRange(const TextRange & deletedRange)279 void ParagraphImpl::resetTextStyleRange(const TextRange& deletedRange)
280 {
281     auto tmpTextStyle = fTextStyles;
282     fTextStyles.clear();
283     for (auto fs : tmpTextStyle) {
284         auto newTextRange = resetRangeWithDeletedRange(fs.fRange, deletedRange, this->getEllipsis().size());
285         LOGD("ParagraphImpl::resetTextStyleRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
286             static_cast<unsigned long>(fs.fRange.start), static_cast<unsigned long>(fs.fRange.end),
287             static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
288         if (newTextRange.width() == 0) {
289             continue;
290         }
291         fs.fRange = newTextRange;
292         fTextStyles.emplace_back(fs);
293     }
294 }
295 
resetPlaceholderRange(const TextRange & deletedRange)296 void ParagraphImpl::resetPlaceholderRange(const TextRange& deletedRange)
297 {
298     // reset fRange && fTextBefore && fBlockBefore
299     auto ellSize = this->getEllipsis().size();
300     auto tmpPlaceholders = fPlaceholders;
301     fPlaceholders.clear();
302     for (auto ph : tmpPlaceholders) {
303         auto newTextRange = resetRangeWithDeletedRange(ph.fRange, deletedRange, ellSize);
304         LOGD("ParagraphImpl::resetPlaceholderRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
305             static_cast<unsigned long>(ph.fRange.start), static_cast<unsigned long>(ph.fRange.end),
306             static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
307         if (newTextRange.empty()) {
308             continue;
309         }
310         ph.fRange = newTextRange;
311         newTextRange = ph.fTextBefore;
312         newTextRange.start = fPlaceholders.empty() ? 0 : fPlaceholders.back().fRange.end;
313         if (newTextRange.end > deletedRange.start) {
314             newTextRange.end = newTextRange.end <= deletedRange.end ?
315                 deletedRange.start + ellSize : newTextRange.end + ellSize - deletedRange.width();
316         }
317         ph.fTextBefore = newTextRange;
318         fPlaceholders.emplace_back(ph);
319     }
320 }
321 #endif
322 
layout(SkScalar rawWidth)323 void ParagraphImpl::layout(SkScalar rawWidth) {
324 #ifdef ENABLE_TEXT_ENHANCE
325     TEXT_TRACE_FUNC();
326     fLineNumber = 1;
327     fLayoutRawWidth = rawWidth;
328 #endif
329     // TODO: This rounding is done to match Flutter tests. Must be removed...
330     auto floorWidth = rawWidth;
331 
332     if (getApplyRoundingHack()) {
333         floorWidth = SkScalarFloorToScalar(floorWidth);
334     }
335 
336 #ifdef ENABLE_TEXT_ENHANCE
337     fPaintRegion.reset();
338     bool isMaxLinesZero = false;
339     if (fParagraphStyle.getMaxLines() == 0) {
340         if (fText.size() != 0) {
341             isMaxLinesZero = true;
342         }
343     }
344 #endif
345 
346     if ((!SkIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
347         fState >= kLineBroken &&
348          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
349         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
350         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
351         fWidth = floorWidth;
352         fState = kShaped;
353     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
354         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
355         fState = kShaped;
356     } else {
357         // Nothing changed case: we can reuse the data from the last layout
358     }
359 
360 #ifdef ENABLE_TEXT_ENHANCE
361     this->fUnicodeText = convertUtf8ToUnicode(fText);
362     auto paragraphCache = fFontCollection->getParagraphCache();
363 #endif
364 
365     if (fState < kShaped) {
366         // Check if we have the text in the cache and don't need to shape it again
367 #ifdef ENABLE_TEXT_ENHANCE
368         if (!paragraphCache->findParagraph(this)) {
369 #else
370         if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
371 #endif
372             if (fState < kIndexed) {
373                 // This only happens once at the first layout; the text is immutable
374                 // and there is no reason to repeat it
375                 if (this->computeCodeUnitProperties()) {
376                     fState = kIndexed;
377                 }
378             }
379             this->fRuns.clear();
380             this->fClusters.clear();
381             this->fClustersIndexFromCodeUnit.clear();
382             this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
383             if (!this->shapeTextIntoEndlessLine()) {
384                 this->resetContext();
385 
386 #ifdef ENABLE_TEXT_ENHANCE
387                 if (isMaxLinesZero) {
388                     fExceededMaxLines  = true;
389                 }
390 #endif
391                 // TODO: merge the two next calls - they always come together
392                 this->resolveStrut();
393                 this->computeEmptyMetrics();
394                 this->fLines.clear();
395 
396                 // Set the important values that are not zero
397                 fWidth = floorWidth;
398                 fHeight = fEmptyMetrics.height();
399                 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
400                     fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
401                     fHeight = fStrutMetrics.height();
402                 }
403 #ifdef ENABLE_TEXT_ENHANCE
404                 if (fParagraphStyle.getMaxLines() == 0) {
405                     fHeight = 0;
406                 }
407 #endif
408                 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
409                 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
410                 fLongestLine = FLT_MIN - FLT_MAX;  // That is what flutter has
411                 fMinIntrinsicWidth = 0;
412                 fMaxIntrinsicWidth = 0;
413                 this->fOldWidth = floorWidth;
414                 this->fOldHeight = this->fHeight;
415 
416                 return;
417 #ifdef ENABLE_TEXT_ENHANCE
418             } else {
419                 // Add the paragraph to the cache
420                 paragraphCache->updateParagraph(this);
421             }
422 #else
423             } else {
424                 // Add the paragraph to the cache
425                 fFontCollection->getParagraphCache()->updateParagraph(this);
426             }
427 #endif
428         }
429         fState = kShaped;
430     }
431 
432     if (fState == kShaped) {
433         this->resetContext();
434         this->resolveStrut();
435         this->computeEmptyMetrics();
436         this->fLines.clear();
437 #ifdef ENABLE_TEXT_ENHANCE
438         // fast path
439         if (!fHasLineBreaks &&
440             !fHasWhitespacesInside &&
441             fPlaceholders.size() == 1 &&
442             (fRuns.size() == 1 && preCalculateSingleRunAutoSpaceWidth(floorWidth))) {
443             positionShapedTextIntoLine(floorWidth);
444         } else if (!paragraphCache->GetStoredLayout(*this)) {
445             breakShapedTextIntoLines(floorWidth);
446             // text breaking did not go to fast path and we did not have cached layout
447             paragraphCache->SetStoredLayout(*this);
448         }
449 #else
450         this->breakShapedTextIntoLines(floorWidth);
451 #endif
452         fState = kLineBroken;
453     }
454 
455     if (fState == kLineBroken) {
456 #ifdef ENABLE_TEXT_ENHANCE
457         if (paragraphStyle().getVerticalAlignment() != TextVerticalAlign::BASELINE &&
458             paragraphStyle().getMaxLines() > 1) {
459             splitRuns();
460         }
461 #endif
462 
463         // Build the picture lazily not until we actually have to paint (or never)
464         this->resetShifts();
465         this->formatLines(fWidth);
466         fState = kFormatted;
467     }
468 
469 #ifdef ENABLE_TEXT_ENHANCE
470     if (fParagraphStyle.getMaxLines() == 0) {
471         fHeight = 0;
472         fLines.clear();
473     }
474 #endif
475 
476     this->fOldWidth = floorWidth;
477     this->fOldHeight = this->fHeight;
478 
479     if (getApplyRoundingHack()) {
480         // TODO: This rounding is done to match Flutter tests. Must be removed...
481         fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
482         fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
483     }
484 
485     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
486     if (fParagraphStyle.getMaxLines() == 1 ||
487         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
488         fMinIntrinsicWidth = fMaxIntrinsicWidth;
489     }
490 
491     // TODO: Since min and max are calculated differently it's possible to get a rounding error
492     //  that would make min > max. Sort it out later, make it the same for now
493     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
494         fMaxIntrinsicWidth = fMinIntrinsicWidth;
495     }
496 #ifdef ENABLE_TEXT_ENHANCE
497     if (fParagraphStyle.getMaxLines() == 0) {
498         fLineNumber = 0;
499     } else {
500         fLineNumber = std::max(size_t(1), static_cast<size_t>(fLines.size()));
501     }
502 #endif
503     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
504 }
505 
506 #ifdef ENABLE_TEXT_ENHANCE
updateSplitRunClusterInfo(const Run & run,bool isSplitRun)507 void ParagraphImpl::updateSplitRunClusterInfo(const Run& run, bool isSplitRun) {
508     size_t posOffset = run.leftToRight() ? cluster(run.clusterRange().start).startPos() :
509         cluster(run.clusterRange().end - 1).startPos();
510     for (size_t clusterIndex = run.clusterRange().start; clusterIndex < run.clusterRange().end; ++clusterIndex) {
511         Cluster& updateCluster = this->cluster(clusterIndex);
512         updateCluster.fRunIndex = run.index();
513         // If the run has not been split, it only needs to update the run index
514         if (!isSplitRun) {
515             continue;
516         }
517         size_t width = updateCluster.size();
518         updateCluster.fStart -= posOffset;
519         updateCluster.fEnd = updateCluster.fStart + width;
520     }
521 }
522 
refreshLines()523 void ParagraphImpl::refreshLines() {
524     for (TextLine& line : fLines) {
525         line.refresh();
526     }
527 }
528 
isTailOfLineNeedSplit(const Run & lineLastRun,size_t lineEnd,bool hasGenerated)529 bool ParagraphImpl::isTailOfLineNeedSplit(const Run& lineLastRun, size_t lineEnd, bool hasGenerated) {
530     return !hasGenerated && ((lineLastRun.clusterRange().end != lineEnd) ||
531         // Special case: the line of last run combines a hard break, such as "<\n"
532         (cluster(lineEnd - 1).isHardBreak() &&
533         !cluster(runByCluster(lineEnd - 1).clusterRange().start).isHardBreak()));
534 }
535 
generateSplitPoint(std::vector<SplitPoint> & splitPoints,const Run & run,ClusterRange lineRange,size_t lineIndex)536 void ParagraphImpl::generateSplitPoint(
537     std::vector<SplitPoint>& splitPoints, const Run& run, ClusterRange lineRange, size_t lineIndex) {
538     size_t startIndex =
539         std::max(cluster(lineRange.start).textRange().start, cluster(run.clusterRange().start).textRange().start);
540     size_t endIndex =
541         std::min(cluster(lineRange.end - 1).textRange().end, cluster(run.clusterRange().end- 1).textRange().end);
542     // The run cross line
543     splitPoints.push_back({lineIndex, run.index(), startIndex, endIndex});
544 }
545 
generateSplitPoints(std::vector<SplitPoint> & splitPoints)546 void ParagraphImpl::generateSplitPoints(std::vector<SplitPoint>& splitPoints) {
547     for (size_t lineIndex = 0; lineIndex < fLines.size(); ++lineIndex) {
548         const TextLine& line = fLines[lineIndex];
549         ClusterRange lineClusterRange = line.clustersWithSpaces();
550         // Avoid abnormal split of the last line
551         if (lineIndex == fLines.size() - 1) {
552             size_t lineEnd = line.clusters().end == 0 ? 0 : line.clusters().end - 1;
553             lineClusterRange.end = cluster(lineEnd).run().clusterRange().end;
554         }
555         // Skip blank line
556         if (lineClusterRange.empty()) {
557             continue;
558         }
559         size_t lineStart = lineClusterRange.start;
560         size_t lineEnd = lineClusterRange.end;
561         // The next line's starting cluster index
562         const Run& lineFirstRun = runByCluster(lineStart);
563         const Run& lineLastRun = runByCluster(lineEnd - 1);
564         // Each line may have 0, 1, or 2 Runs need to be split
565         bool onlyGenerateOnce{false};
566         if (lineFirstRun.clusterRange().start != lineStart) {
567             generateSplitPoint(splitPoints, lineFirstRun, lineClusterRange, lineIndex);
568 
569             if (lineFirstRun.index() == lineLastRun.index()) {
570                 onlyGenerateOnce = true;
571             }
572         }
573 
574         if (isTailOfLineNeedSplit(lineLastRun, lineEnd, onlyGenerateOnce)) {
575             generateSplitPoint(splitPoints, lineLastRun, lineClusterRange, lineIndex);
576         }
577     }
578 }
579 
generateRunsBySplitPoints(std::vector<SplitPoint> & splitPoints,skia_private::TArray<Run,false> & runs)580 void ParagraphImpl::generateRunsBySplitPoints(std::vector<SplitPoint>& splitPoints,
581     skia_private::TArray<Run, false>& runs) {
582     std::reverse(splitPoints.begin(), splitPoints.end());
583     size_t newRunGlobalIndex = 0;
584     for (size_t runIndex = 0; runIndex < fRuns.size(); ++runIndex) {
585         Run& run = fRuns[runIndex];
586         if (splitPoints.empty() || splitPoints.back().runIndex != run.fIndex) {
587             // No need to split
588             run.fIndex = newRunGlobalIndex++;
589             updateSplitRunClusterInfo(run, false);
590             runs.push_back(run);
591             continue;
592         }
593 
594         while (!splitPoints.empty()) {
595             SplitPoint& splitPoint = splitPoints.back();
596             size_t splitRunIndex = splitPoint.runIndex;
597             if (splitRunIndex != runIndex) {
598                 break;
599             }
600 
601             Run splitRun{run, newRunGlobalIndex++};
602             run.generateSplitRun(splitRun, splitPoint);
603             updateSplitRunClusterInfo(splitRun, true);
604             runs.push_back(std::move(splitRun));
605             splitPoints.pop_back();
606         }
607     }
608 }
609 
splitRuns()610 void ParagraphImpl::splitRuns() {
611     // Collect split info for crossing line's run
612     std::vector<SplitPoint> splitPoints{};
613     generateSplitPoints(splitPoints);
614     if (splitPoints.empty()) {
615         return;
616     }
617     skia_private::TArray<Run, false> newRuns;
618     generateRunsBySplitPoints(splitPoints, newRuns);
619     if (newRuns.empty()) {
620         return;
621     }
622     fRuns = std::move(newRuns);
623     refreshLines();
624 }
625 #endif
626 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)627 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
628 #ifdef ENABLE_TEXT_ENHANCE
629     TEXT_TRACE_FUNC();
630     fState = kDrawn;
631 #endif
632     CanvasParagraphPainter painter(canvas);
633     paint(&painter, x, y);
634 }
635 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)636 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
637 #ifdef ENABLE_TEXT_ENHANCE
638     TEXT_TRACE_FUNC();
639     fState = kDrawn;
640     // Reset all text style vertical shift
641     for (Block& block : fTextStyles) {
642         block.fStyle.setVerticalAlignShift(0.0);
643     }
644 #endif
645     for (auto& line : fLines) {
646 #ifdef ENABLE_TEXT_ENHANCE
647         line.updateTextLinePaintAttributes();
648 #endif
649         line.paint(painter, x, y);
650     }
651 }
652 
653 #ifdef ENABLE_TEXT_ENHANCE
paint(ParagraphPainter * painter,RSPath * path,SkScalar hOffset,SkScalar vOffset)654 void ParagraphImpl::paint(ParagraphPainter* painter, RSPath* path, SkScalar hOffset, SkScalar vOffset) {
655     TEXT_TRACE_FUNC();
656     fState = kDrawn;
657     auto& style = fTextStyles[0].fStyle;
658     float align = 0.0f;
659     switch (paragraphStyle().getTextAlign()) {
660         case TextAlign::kCenter:
661             align = -0.5f;
662             break;
663         case TextAlign::kRight:
664             align = -1.0f;
665             break;
666         default:
667             break;
668     }
669     hOffset += align * (fMaxIntrinsicWidth - style.getLetterSpacing() - path->GetLength(false));
670     for (auto& line : fLines) {
671         line.paint(painter, path, hOffset, vOffset);
672     }
673 }
674 
getEllipsisTextRange()675 TextRange ParagraphImpl::getEllipsisTextRange() {
676     if (fState < kLineBroken) {
677         return EMPTY_RANGE;
678     }
679     if (!fEllipsisRange.empty()) {
680         return fEllipsisRange;
681     }
682     this->ensureUTF16Mapping();
683     for (const auto& line: fLines) {
684         if (line.getTextRangeReplacedByEllipsis().empty()) {
685             continue;
686         }
687         auto ellipsisClusterRange = line.getTextRangeReplacedByEllipsis();
688         return TextRange(getUTF16Index(ellipsisClusterRange.start),
689                                       getUTF16Index(ellipsisClusterRange.end));
690     }
691     return EMPTY_RANGE;
692 }
693 #endif
694 
resetContext()695 void ParagraphImpl::resetContext() {
696     fAlphabeticBaseline = 0;
697     fHeight = 0;
698     fWidth = 0;
699     fIdeographicBaseline = 0;
700     fMaxIntrinsicWidth = 0;
701     fMinIntrinsicWidth = 0;
702     fLongestLine = 0;
703 #ifdef ENABLE_TEXT_ENHANCE
704     fLongestLineWithIndent = 0;
705 #endif
706     fMaxWidthWithTrailingSpaces = 0;
707     fExceededMaxLines = false;
708 }
709 
710 // shapeTextIntoEndlessLine is the thing that calls this method
computeCodeUnitProperties()711 bool ParagraphImpl::computeCodeUnitProperties() {
712 #ifdef ENABLE_TEXT_ENHANCE
713     TEXT_TRACE_FUNC();
714 #endif
715     if (nullptr == fUnicode) {
716         return false;
717     }
718 
719     // Get bidi regions
720     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
721                               ? SkUnicode::TextDirection::kLTR
722                               : SkUnicode::TextDirection::kRTL;
723     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
724         return false;
725     }
726 
727 #ifdef ENABLE_TEXT_ENHANCE
728     const char *locale = fParagraphStyle.getTextStyle().getLocale().c_str();
729 #endif
730     // Collect all spaces and some extra information
731     // (and also substitute \t with a space while we are at it)
732     if (!fUnicode->computeCodeUnitFlags(&fText[0],
733                                         fText.size(),
734 #ifdef ENABLE_TEXT_ENHANCE
735                                         this->paragraphStyle().getReplaceTabCharacters() ||
736                                         (!(this->paragraphStyle().getTextTab().location < 1.0)),
737                                         locale,
738 #else
739                                         this->paragraphStyle().getReplaceTabCharacters(),
740 #endif
741                                         &fCodeUnitProperties)) {
742         return false;
743     }
744 
745     // Get some information about trailing spaces / hard line breaks
746     fTrailingSpaces = fText.size();
747     TextIndex firstWhitespace = EMPTY_INDEX;
748     for (int i = 0; i < fCodeUnitProperties.size(); ++i) {
749         auto flags = fCodeUnitProperties[i];
750         if (SkUnicode::hasPartOfWhiteSpaceBreakFlag(flags)) {
751             if (fTrailingSpaces  == fText.size()) {
752                 fTrailingSpaces = i;
753             }
754             if (firstWhitespace == EMPTY_INDEX) {
755                 firstWhitespace = i;
756             }
757         } else {
758             fTrailingSpaces = fText.size();
759         }
760         if (SkUnicode::hasHardLineBreakFlag(flags)) {
761             fHasLineBreaks = true;
762         }
763     }
764 
765     if (firstWhitespace < fTrailingSpaces) {
766         fHasWhitespacesInside = true;
767     }
768 
769     return true;
770 }
771 
is_ascii_7bit_space(int c)772 static bool is_ascii_7bit_space(int c) {
773     SkASSERT(c >= 0 && c <= 127);
774 
775     // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
776     //
777     enum WS {
778         kHT    = 9,
779         kLF    = 10,
780         kVT    = 11,
781         kFF    = 12,
782         kCR    = 13,
783         kSP    = 32,    // too big to use as shift
784     };
785 #define M(shift)    (1 << (shift))
786     constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
787     // we check for Space (32) explicitly, since it is too large to shift
788     return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
789 #undef M
790 }
791 
792 #ifdef ENABLE_TEXT_ENHANCE
793 static const std::vector<SkRange<SkUnichar>> CJK_UNICODE_SET = {
794     SkRange<SkUnichar>(0x1100, 0x11FF),
795     SkRange<SkUnichar>(0x2E80, 0x2EFF),
796     // [0x3040, 0x309F](Hiragana) + [0x30A0, 0x30FF](Katakana)
797     SkRange<SkUnichar>(0x3040, 0x30FF),
798     SkRange<SkUnichar>(0x3130, 0x318F),
799     // [0x31C0, 0x31EF](CJK Strokes) + [0x31F0, 0x31FF](Katakana Phonetic Extensions)
800     SkRange<SkUnichar>(0x31C0, 0x31FF),
801     SkRange<SkUnichar>(0x3400, 0x4DBF),
802     SkRange<SkUnichar>(0x4E00, 0x9FFF),
803     SkRange<SkUnichar>(0xAC00, 0xD7AF),
804     SkRange<SkUnichar>(0xF900, 0xFAFF),
805     SkRange<SkUnichar>(0x20000, 0x2A6DF),
806 /*
807     [0x2A700, 0x2B73F](CJK Unified Ideographs Extension C) +
808     [0x2B740, 0x2B81F](CJK Unified Ideographs Extension D) +
809     [0x2B820, 0x2CEAF](CJK Unified Ideographs Extension E) +
810     [0x2CEB0, 0x2EBEF](CJK Unified Ideographs Extension F)
811 */
812     SkRange<SkUnichar>(0x2A700, 0x2EBEF),
813     SkRange<SkUnichar>(0x2F800, 0x2FA1F),
814     SkRange<SkUnichar>(0x30000, 0x3134F),
815 };
816 
817 static const std::vector<SkRange<SkUnichar>> WESTERN_UNICODE_SET = {
818     SkRange<SkUnichar>(0x0030, 0x0039), // Number
819     SkRange<SkUnichar>(0x0041, 0x005A), // Base Latin
820     SkRange<SkUnichar>(0x0061, 0x007A),
821     SkRange<SkUnichar>(0x00C0, 0x00FF), // Latin Extended-1: À-ÿ
822     SkRange<SkUnichar>(0x0100, 0x017F), // Latin Extended-A: Ā-ſ
823     SkRange<SkUnichar>(0x018F, 0x0192), // Latin Extended-B (specific ranges)
824     SkRange<SkUnichar>(0x01A0, 0x01A1),
825     SkRange<SkUnichar>(0x01AF, 0x01B0),
826     SkRange<SkUnichar>(0x01CD, 0x01DC),
827     SkRange<SkUnichar>(0x01E5, 0x01E5),
828     SkRange<SkUnichar>(0x01E7, 0x01E7),
829     SkRange<SkUnichar>(0x01E9, 0x01E9),
830     SkRange<SkUnichar>(0x01EF, 0x01F0),
831     SkRange<SkUnichar>(0x01F9, 0x01FF),
832     SkRange<SkUnichar>(0x0218, 0x0219),
833     SkRange<SkUnichar>(0x021A, 0x021B),
834     SkRange<SkUnichar>(0x021F, 0x021F),
835     SkRange<SkUnichar>(0x0237, 0x0237),
836     SkRange<SkUnichar>(0x0386, 0x0386), // Greek and Coptic
837     SkRange<SkUnichar>(0x0388, 0x038A),
838     SkRange<SkUnichar>(0x038C, 0x038C),
839     SkRange<SkUnichar>(0x038E, 0x038F),
840     SkRange<SkUnichar>(0x0390, 0x03A1),
841     SkRange<SkUnichar>(0x03A3, 0x03CE),
842     SkRange<SkUnichar>(0x03D1, 0x03D2),
843     SkRange<SkUnichar>(0x03D6, 0x03D6),
844     SkRange<SkUnichar>(0x0400, 0x045F), // Cyrillic
845     SkRange<SkUnichar>(0x0462, 0x0463),
846     SkRange<SkUnichar>(0x046B, 0x046B),
847     SkRange<SkUnichar>(0x0472, 0x0475),
848     SkRange<SkUnichar>(0x0490, 0x0493),
849     SkRange<SkUnichar>(0x0497, 0x0497),
850     SkRange<SkUnichar>(0x049A, 0x049D),
851     SkRange<SkUnichar>(0x04A2, 0x04A3),
852     SkRange<SkUnichar>(0x04AE, 0x04B3),
853     SkRange<SkUnichar>(0x04B8, 0x04BB),
854     SkRange<SkUnichar>(0x04CA, 0x04CA),
855     SkRange<SkUnichar>(0x04D8, 0x04D9),
856     SkRange<SkUnichar>(0x04E8, 0x04E9),
857     SkRange<SkUnichar>(0x1E00, 0x1E01), // Latin Extended Additional
858     SkRange<SkUnichar>(0x1E3E, 0x1E3F),
859     SkRange<SkUnichar>(0x1E80, 0x1E85),
860     SkRange<SkUnichar>(0x1EA0, 0x1EF9),
861     SkRange<SkUnichar>(0x1F45, 0x1F45), // Greek Extended
862     SkRange<SkUnichar>(0x1F4D, 0x1F4D)
863 };
864 
865 constexpr SkUnichar COPYRIGHT_UNICODE = 0x00A9;
866 
867 struct UnicodeIdentifier {
cmpskia::textlayout::UnicodeIdentifier868     static bool cmp(SkRange<SkUnichar> a, SkRange<SkUnichar> b) {
869         return a.start < b.start;
870     }
871     const std::vector<SkRange<SkUnichar>>& fUnicodeSet;
UnicodeIdentifierskia::textlayout::UnicodeIdentifier872     explicit UnicodeIdentifier(const std::vector<SkRange<SkUnichar>>& unicodeSet) : fUnicodeSet(unicodeSet) {}
existskia::textlayout::UnicodeIdentifier873     bool exist(SkUnichar c) const {
874         auto pos = std::upper_bound(fUnicodeSet.begin(), fUnicodeSet.end(), SkRange<SkUnichar>(c, c), cmp);
875         if (pos == fUnicodeSet.begin()) {
876             return false;
877         }
878         --pos;
879         return pos->end >= c;
880     }
881 };
882 
883 static const UnicodeIdentifier CJK_IDENTIFIER(CJK_UNICODE_SET);
884 static const UnicodeIdentifier WESTERN_IDENTIFIER(WESTERN_UNICODE_SET);
885 
recognizeUnicodeAutoSpacingFlag(ParagraphImpl & paragraph,SkUnichar unicode)886 static Cluster::AutoSpacingFlag recognizeUnicodeAutoSpacingFlag(ParagraphImpl& paragraph, SkUnichar unicode)
887 {
888     if (!paragraph.isAutoSpaceEnabled()) {
889         return Cluster::AutoSpacingFlag::NoFlag;
890     }
891     if (WESTERN_IDENTIFIER.exist(unicode)) {
892         return Cluster::AutoSpacingFlag::Western;
893     }
894     if (CJK_IDENTIFIER.exist(unicode)) {
895         return Cluster::AutoSpacingFlag::CJK;
896     }
897     if (unicode == COPYRIGHT_UNICODE) {
898         return Cluster::AutoSpacingFlag::Copyright;
899     }
900     return Cluster::AutoSpacingFlag::NoFlag;
901 }
902 #endif
Cluster(ParagraphImpl * owner,RunIndex runIndex,size_t start,size_t end,SkSpan<const char> text,SkScalar width,SkScalar height)903 Cluster::Cluster(ParagraphImpl* owner,
904                  RunIndex runIndex,
905                  size_t start,
906                  size_t end,
907                  SkSpan<const char> text,
908                  SkScalar width,
909                  SkScalar height)
910         : fOwner(owner)
911         , fRunIndex(runIndex)
912         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
913         , fGraphemeRange(EMPTY_RANGE)
914         , fStart(start)
915         , fEnd(end)
916         , fWidth(width)
917         , fHeight(height)
918         , fHalfLetterSpacing(0.0)
919         , fIsIdeographic(false) {
920     size_t whiteSpacesBreakLen = 0;
921     size_t intraWordBreakLen = 0;
922 
923     const char* ch = text.begin();
924     if (text.end() - ch == 1 && *(const unsigned char*)ch <= 0x7F) {
925         // I am not even sure it's worth it if we do not save a unicode call
926         if (is_ascii_7bit_space(*ch)) {
927             ++whiteSpacesBreakLen;
928         }
929 #ifdef ENABLE_TEXT_ENHANCE
930         fIsPunctuation = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kPunctuation);
931         fIsEllipsis = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kEllipsis);
932 #endif
933     } else {
934         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
935             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
936                 ++whiteSpacesBreakLen;
937             }
938             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
939                 ++intraWordBreakLen;
940             }
941             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kIdeographic)) {
942                 fIsIdeographic = true;
943             }
944 #ifdef ENABLE_TEXT_ENHANCE
945             fIsPunctuation = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPunctuation) | fIsPunctuation;
946             fIsEllipsis = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kEllipsis) | fIsEllipsis;
947 #endif
948         }
949     }
950 
951     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
952     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
953     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
954                                                SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
955 #ifdef ENABLE_TEXT_ENHANCE
956     fIsTabulation = fOwner->codeUnitHasProperty(fTextRange.start,
957                                                 SkUnicode::CodeUnitFlags::kTabulation);
958     auto unicodeStart = fOwner->getUnicodeIndex(fTextRange.start);
959     auto unicodeEnd = fOwner->getUnicodeIndex(fTextRange.end);
960     SkUnichar unicode = 0;
961     if (unicodeEnd - unicodeStart == 1 && unicodeStart < fOwner->unicodeText().size()) {
962         unicode = fOwner->unicodeText()[unicodeStart];
963     }
964 
965     auto curAutoSpacingFlag = recognizeUnicodeAutoSpacingFlag(*fOwner, unicode);
966     auto lastAutoSpacingFlag = fOwner->getLastAutoSpacingFlag();
967     fNeedAutoSpacing = curAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag &&
968         curAutoSpacingFlag != lastAutoSpacingFlag && lastAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag;
969     fOwner->setLastAutoSpacingFlag(curAutoSpacingFlag);
970 #endif
971 }
972 
calculateWidth(size_t start,size_t end,bool clip) const973 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
974     SkASSERT(start <= end);
975     // clip |= end == size();  // Clip at the end of the run?
976     auto correction = 0.0f;
977     if (end > start && !fJustificationShifts.empty()) {
978         // This is not a typo: we are using Point as a pair of SkScalars
979         correction = fJustificationShifts[end - 1].fX -
980                      fJustificationShifts[start].fY;
981     }
982 #ifdef ENABLE_TEXT_ENHANCE
983     if (end > start && !fAutoSpacings.empty()) {
984         // This is not a tyopo: we are using Point as a pair of SkScalars
985         correction += fAutoSpacings[end - 1].fX - fAutoSpacings[start].fY;
986     }
987 #endif
988     return posX(end) - posX(start) + correction;
989 }
990 
991 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
992 // the opposite - just to optimize the most common case.
applySpacingAndBuildClusterTable()993 void ParagraphImpl::applySpacingAndBuildClusterTable() {
994 #ifdef ENABLE_TEXT_ENHANCE
995     TEXT_TRACE_FUNC();
996 #endif
997     // Check all text styles to see what we have to do (if anything)
998     size_t letterSpacingStyles = 0;
999     bool hasWordSpacing = false;
1000     for (auto& block : fTextStyles) {
1001         if (block.fRange.width() > 0) {
1002             if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
1003                 ++letterSpacingStyles;
1004             }
1005             if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
1006                 hasWordSpacing = true;
1007             }
1008         }
1009     }
1010 
1011     if (letterSpacingStyles == 0 && !hasWordSpacing) {
1012         // We don't have to do anything about spacing (most common case)
1013         this->buildClusterTable();
1014         return;
1015     }
1016 
1017     if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
1018         fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
1019         // We have to letter space the entire paragraph (second most common case)
1020         auto& run = fRuns[0];
1021         auto& style = fTextStyles[0].fStyle;
1022 #ifndef ENABLE_TEXT_ENHANCE
1023         run.addSpacesEvenly(style.getLetterSpacing());
1024 #endif
1025         this->buildClusterTable();
1026 #ifdef ENABLE_TEXT_ENHANCE
1027         SkScalar shift = 0;
1028         run.iterateThroughClusters([this, &run, &shift, &style](Cluster* cluster) {
1029             run.shift(cluster, shift);
1030             shift += run.addSpacesEvenly(style.getLetterSpacing(), cluster);
1031         });
1032 #else
1033         // This is something Flutter requires
1034         for (auto& cluster : fClusters) {
1035             cluster.setHalfLetterSpacing(style.getLetterSpacing()/2);
1036         }
1037 #endif
1038         return;
1039     }
1040 
1041     // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
1042     this->buildClusterTable();
1043 
1044     // Walk through all the clusters in the direction of shaped text
1045     // (we have to walk through the styles in the same order, too)
1046     // Not breaking the iteration on every run!
1047     SkScalar shift = 0;
1048     bool soFarWhitespacesOnly = true;
1049     bool wordSpacingPending = false;
1050     Cluster* lastSpaceCluster = nullptr;
1051     for (auto& run : fRuns) {
1052 
1053         // Skip placeholder runs
1054         if (run.isPlaceholder()) {
1055             continue;
1056         }
1057 
1058         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
1059             // Shift the cluster (shift collected from the previous clusters)
1060             run.shift(cluster, shift);
1061 
1062             // Synchronize styles (one cluster can be covered by few styles)
1063             Block* currentStyle = fTextStyles.begin();
1064             while (!cluster->startsIn(currentStyle->fRange)) {
1065                 currentStyle++;
1066                 SkASSERT(currentStyle != fTextStyles.end());
1067             }
1068 
1069             SkASSERT(!currentStyle->fStyle.isPlaceholder());
1070 
1071             // Process word spacing
1072             if (currentStyle->fStyle.getWordSpacing() != 0) {
1073                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
1074                     if (!soFarWhitespacesOnly) {
1075                         lastSpaceCluster = cluster;
1076                         wordSpacingPending = true;
1077                     }
1078                 } else if (wordSpacingPending) {
1079                     SkScalar spacing = currentStyle->fStyle.getWordSpacing();
1080                     if (cluster->fRunIndex != lastSpaceCluster->fRunIndex) {
1081                         // If the last space cluster belongs to the previous run
1082                         // we have to extend that cluster and that run
1083                         lastSpaceCluster->run().addSpacesAtTheEnd(spacing, lastSpaceCluster);
1084                         lastSpaceCluster->run().extend(lastSpaceCluster, spacing);
1085                     } else {
1086                         run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
1087                     }
1088                     run.shift(cluster, spacing);
1089                     shift += spacing;
1090                     wordSpacingPending = false;
1091                 }
1092             }
1093             // Process letter spacing
1094             if (currentStyle->fStyle.getLetterSpacing() != 0) {
1095                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
1096             }
1097 
1098             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
1099                 soFarWhitespacesOnly = false;
1100             }
1101         });
1102     }
1103 }
1104 
1105 #ifdef ENABLE_TEXT_ENHANCE
buildClusterPlaceholder(Run & run,size_t runIndex)1106 void ParagraphImpl::buildClusterPlaceholder(Run& run, size_t runIndex)
1107 {
1108     if (run.isPlaceholder()) {
1109         // Add info to cluster indexes table (text -> cluster)
1110         for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
1111           fClustersIndexFromCodeUnit[i] = fClusters.size();
1112         }
1113         // There are no glyphs but we want to have one cluster
1114         fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()),
1115             run.advance().fX, run.advance().fY);
1116         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1117         fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1118     } else {
1119         // Walk through the glyph in the direction of input text
1120         run.iterateThroughClustersInTextOrder([&run, runIndex, this](size_t glyphStart,
1121             size_t glyphEnd, size_t charStart, size_t charEnd, SkScalar width, SkScalar height) {
1122             SkASSERT(charEnd >= charStart);
1123             // Add info to cluster indexes table (text -> cluster)
1124             for (auto i = charStart; i < charEnd; ++i) {
1125               fClustersIndexFromCodeUnit[i] = fClusters.size();
1126             }
1127             SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
1128             fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
1129             fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1130         });
1131     }
1132 }
1133 #endif
1134 
1135 // Clusters in the order of the input text
buildClusterTable()1136 void ParagraphImpl::buildClusterTable()
1137 {
1138     // It's possible that one grapheme includes few runs; we cannot handle it
1139     // so we break graphemes by the runs instead
1140     // It's not the ideal solution and has to be revisited later
1141     int cluster_count = 1;
1142     for (auto& run : fRuns) {
1143         cluster_count += run.isPlaceholder() ? 1 : run.size();
1144         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1145         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1146     }
1147     if (!fRuns.empty()) {
1148         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1149         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1150     }
1151     fClusters.reserve_exact(fClusters.size() + cluster_count);
1152 
1153     // Walk through all the run in the direction of input text
1154     for (auto& run : fRuns) {
1155         auto runIndex = run.index();
1156         auto runStart = fClusters.size();
1157         if (run.isPlaceholder()) {
1158             // Add info to cluster indexes table (text -> cluster)
1159             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
1160               fClustersIndexFromCodeUnit[i] = fClusters.size();
1161             }
1162             // There are no glyphs but we want to have one cluster
1163             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
1164             fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1165             fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1166         } else {
1167             // Walk through the glyph in the direction of input text
1168 #ifdef ENABLE_TEXT_ENHANCE
1169             run.iterateThroughClustersInTextOrder([&run, runIndex, this](size_t glyphStart,
1170                                                                    size_t glyphEnd,
1171                                                                    size_t charStart,
1172                                                                    size_t charEnd,
1173                                                                    SkScalar width,
1174                                                                    SkScalar height) {
1175 #else
1176             run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart,
1177                                                                    size_t glyphEnd,
1178                                                                    size_t charStart,
1179                                                                    size_t charEnd,
1180                                                                    SkScalar width,
1181                                                                    SkScalar height) {
1182 #endif
1183                 SkASSERT(charEnd >= charStart);
1184                 // Add info to cluster indexes table (text -> cluster)
1185                 for (auto i = charStart; i < charEnd; ++i) {
1186                   fClustersIndexFromCodeUnit[i] = fClusters.size();
1187                 }
1188                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
1189                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
1190                 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1191             });
1192         }
1193         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1194 
1195         run.setClusterRange(runStart, fClusters.size());
1196         fMaxIntrinsicWidth += run.advance().fX;
1197     }
1198     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
1199     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
1200 }
1201 
1202 bool ParagraphImpl::shapeTextIntoEndlessLine() {
1203 #ifdef ENABLE_TEXT_ENHANCE
1204     TEXT_TRACE_FUNC();
1205 #endif
1206     if (fText.size() == 0) {
1207         return false;
1208     }
1209 
1210     fUnresolvedCodepoints.clear();
1211     fFontSwitches.clear();
1212 
1213     OneLineShaper oneLineShaper(this);
1214     auto result = oneLineShaper.shape();
1215     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
1216 
1217     this->applySpacingAndBuildClusterTable();
1218 
1219     return result;
1220 }
1221 
1222 #ifdef ENABLE_TEXT_ENHANCE
1223 void ParagraphImpl::setIndents(const std::vector<SkScalar>& indents)
1224 {
1225     fIndents = indents;
1226 }
1227 
1228 SkScalar ParagraphImpl::detectIndents(size_t index)
1229 {
1230     SkScalar indent = 0.0;
1231     if (fIndents.size() > 0 && index < fIndents.size()) {
1232         indent = fIndents[index];
1233     } else {
1234         indent = fIndents.size() > 0 ? fIndents.back() : 0.0;
1235     }
1236 
1237     return indent;
1238 }
1239 
1240 void ParagraphImpl::positionShapedTextIntoLine(SkScalar maxWidth) {
1241     resetAutoSpacing();
1242     // This is a short version of a line breaking when we know that:
1243     // 1. We have only one line of text
1244     // 2. It's shaped into a single run
1245     // 3. There are no placeholders
1246     // 4. There are no linebreaks (which will format text into multiple lines)
1247     // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1248     // (To think about that, the last condition is not quite right;
1249     // we should calculate minIntrinsicWidth by soft line breaks.
1250     // However, it's how it's done in Flutter now)
1251     auto& run = this->fRuns[0];
1252     auto advance = run.advance();
1253     auto textRange = TextRange(0, this->text().size());
1254     auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1255     InternalLineMetrics metrics(strutForceHeight() && strutEnabled());
1256     metrics.add(&run);
1257     auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
1258     auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
1259     if (disableFirstAscent) {
1260         metrics.fAscent = metrics.fRawAscent;
1261     }
1262     if (disableLastDescent) {
1263         metrics.fDescent = metrics.fRawDescent;
1264     }
1265     if (this->strutEnabled()) {
1266         this->strutMetrics().updateLineMetrics(metrics);
1267     }
1268     ClusterIndex trailingSpaces = fClusters.size();
1269     do {
1270         --trailingSpaces;
1271         auto& cluster = fClusters[trailingSpaces];
1272         if (!cluster.isWhitespaceBreak()) {
1273             ++trailingSpaces;
1274             break;
1275         }
1276         advance.fX -= cluster.width();
1277     } while (trailingSpaces != 0);
1278 
1279     advance.fY = metrics.height();
1280     SkScalar heightWithParagraphSpacing = advance.fY;
1281     if (this->paragraphStyle().getIsEndAddParagraphSpacing() &&
1282         this->paragraphStyle().getParagraphSpacing() > 0) {
1283         heightWithParagraphSpacing += this->paragraphStyle().getParagraphSpacing();
1284     }
1285     auto clusterRange = ClusterRange(0, trailingSpaces);
1286     auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1287     SkScalar offsetX = this->detectIndents(0);
1288     auto& line = this->addLine(SkPoint::Make(offsetX, 0), advance, textExcludingSpaces, textRange, textRange,
1289         clusterRange, clusterRangeWithGhosts, run.advance().x(), metrics);
1290     auto spacing = line.autoSpacing();
1291     auto longestLine = std::max(run.advance().fX, advance.fX) + spacing;
1292     setSize(heightWithParagraphSpacing, maxWidth, longestLine);
1293     setLongestLineWithIndent(longestLine + offsetX);
1294     setIntrinsicSize(run.advance().fX, advance.fX,
1295         fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1296         fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1297         false);
1298 }
1299 
1300 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1301     TEXT_TRACE_FUNC();
1302     resetAutoSpacing();
1303     TextWrapper textWrapper;
1304     textWrapper.breakTextIntoLines(
1305             this,
1306             maxWidth,
1307             [&](TextRange textExcludingSpaces,
1308                 TextRange text,
1309                 TextRange textWithNewlines,
1310                 ClusterRange clusters,
1311                 ClusterRange clustersWithGhosts,
1312                 SkScalar widthWithSpaces,
1313                 size_t startPos,
1314                 size_t endPos,
1315                 SkVector offset,
1316                 SkVector advance,
1317                 InternalLineMetrics metrics,
1318                 bool addEllipsis,
1319                 SkScalar indent,
1320                 SkScalar noIndentWidth) {
1321                 // TODO: Take in account clipped edges
1322                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines,
1323                     clusters, clustersWithGhosts, widthWithSpaces, metrics);
1324                 line.autoSpacing();
1325                 if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::TAIL) {
1326                     line.createTailEllipsis(noIndentWidth, this->getEllipsis(), true, this->getWordBreakType());
1327                 } else if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD) {
1328                     line.createHeadEllipsis(noIndentWidth, this->getEllipsis(), true);
1329                 }
1330                 else if (needCreateMiddleEllipsis()) {
1331                     line.createMiddleEllipsis(noIndentWidth, this->getEllipsis());
1332                 } else if (textWrapper.brokeLineWithHyphen()
1333                            || ((clusters.end == clustersWithGhosts.end) && (clusters.end >= 1)
1334                                && (clusters.end < this->fUnicodeText.size())
1335                                && (this->fUnicodeText[clusters.end - 1] == 0xad))) { // 0xad represents a soft hyphen
1336                     line.setBreakWithHyphen(true);
1337                 }
1338                 auto longestLine = std::max(line.width(), line.widthWithEllipsisSpaces());
1339                 fLongestLine = std::max(fLongestLine, longestLine);
1340                 fLongestLineWithIndent = std::max(fLongestLineWithIndent, longestLine + indent);
1341             });
1342     setSize(textWrapper.height(), maxWidth, fLongestLine);
1343     setIntrinsicSize(textWrapper.maxIntrinsicWidth(), textWrapper.minIntrinsicWidth(),
1344         fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1345         fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1346         textWrapper.exceededMaxLines());
1347 }
1348 #else
1349 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1350 
1351     if (!fHasLineBreaks &&
1352         !fHasWhitespacesInside &&
1353         fPlaceholders.size() == 1 &&
1354         fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
1355         // This is a short version of a line breaking when we know that:
1356         // 1. We have only one line of text
1357         // 2. It's shaped into a single run
1358         // 3. There are no placeholders
1359         // 4. There are no linebreaks (which will format text into multiple lines)
1360         // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1361         // (To think about that, the last condition is not quite right;
1362         // we should calculate minIntrinsicWidth by soft line breaks.
1363         // However, it's how it's done in Flutter now)
1364         auto& run = this->fRuns[0];
1365         auto advance = run.advance();
1366         auto textRange = TextRange(0, this->text().size());
1367         auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1368         InternalLineMetrics metrics(this->strutForceHeight());
1369         metrics.add(&run);
1370         auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
1371                                   TextHeightBehavior::kDisableFirstAscent;
1372         auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
1373                                   TextHeightBehavior::kDisableLastDescent;
1374         if (disableFirstAscent) {
1375             metrics.fAscent = metrics.fRawAscent;
1376         }
1377         if (disableLastDescent) {
1378             metrics.fDescent = metrics.fRawDescent;
1379         }
1380         if (this->strutEnabled()) {
1381             this->strutMetrics().updateLineMetrics(metrics);
1382         }
1383         ClusterIndex trailingSpaces = fClusters.size();
1384         do {
1385             --trailingSpaces;
1386             auto& cluster = fClusters[trailingSpaces];
1387             if (!cluster.isWhitespaceBreak()) {
1388                 ++trailingSpaces;
1389                 break;
1390             }
1391             advance.fX -= cluster.width();
1392         } while (trailingSpaces != 0);
1393 
1394         advance.fY = metrics.height();
1395         auto clusterRange = ClusterRange(0, trailingSpaces);
1396         auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1397         this->addLine(SkPoint::Make(0, 0), advance,
1398                       textExcludingSpaces, textRange, textRange,
1399                       clusterRange, clusterRangeWithGhosts, run.advance().x(),
1400                       metrics);
1401 
1402         fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
1403         fHeight = advance.fY;
1404         fWidth = maxWidth;
1405         fMaxIntrinsicWidth = run.advance().fX;
1406         fMinIntrinsicWidth = advance.fX;
1407         fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1408         fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1409         fExceededMaxLines = false;
1410         return;
1411     }
1412 
1413     TextWrapper textWrapper;
1414     textWrapper.breakTextIntoLines(
1415             this,
1416             maxWidth,
1417             [&](TextRange textExcludingSpaces,
1418                 TextRange text,
1419                 TextRange textWithNewlines,
1420                 ClusterRange clusters,
1421                 ClusterRange clustersWithGhosts,
1422                 SkScalar widthWithSpaces,
1423                 size_t startPos,
1424                 size_t endPos,
1425                 SkVector offset,
1426                 SkVector advance,
1427                 InternalLineMetrics metrics,
1428                 bool addEllipsis) {
1429                 // TODO: Take in account clipped edges
1430                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
1431                 if (addEllipsis) {
1432                     line.createEllipsis(maxWidth, this->getEllipsis(), true);
1433                 }
1434                 fLongestLine = std::max(fLongestLine, nearlyZero(line.width()) ? widthWithSpaces : line.width());
1435             });
1436 
1437     fHeight = textWrapper.height();
1438     fWidth = maxWidth;
1439     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
1440     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
1441     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1442     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1443     fExceededMaxLines = textWrapper.exceededMaxLines();
1444 }
1445 #endif
1446 
1447 void ParagraphImpl::formatLines(SkScalar maxWidth) {
1448 #ifdef ENABLE_TEXT_ENHANCE
1449     TEXT_TRACE_FUNC();
1450 #endif
1451     auto effectiveAlign = fParagraphStyle.effective_align();
1452     const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
1453         || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
1454 
1455     if (!SkIsFinite(maxWidth) && !isLeftAligned) {
1456         // Special case: clean all text in case of maxWidth == INF & align != left
1457         // We had to go through shaping though because we need all the measurement numbers
1458         fLines.clear();
1459         return;
1460     }
1461 
1462 #ifdef ENABLE_TEXT_ENHANCE
1463     size_t iLineNumber = 0;
1464     for (auto& line : fLines) {
1465         SkScalar noIndentWidth = maxWidth - detectIndents(iLineNumber++);
1466         if (fParagraphStyle.getTextDirection() == TextDirection::kRtl) {
1467             line.setLineOffsetX(0);
1468         }
1469         line.format(effectiveAlign, noIndentWidth, this->paragraphStyle().getEllipsisMod());
1470 
1471         if (paragraphStyle().getVerticalAlignment() != TextVerticalAlign::BASELINE) {
1472             line.applyVerticalShift();
1473         }
1474     }
1475 #else
1476     for (auto& line : fLines) {
1477         line.format(effectiveAlign, maxWidth);
1478     }
1479 #endif
1480 }
1481 
1482 #ifdef ENABLE_TEXT_ENHANCE
1483 void ParagraphImpl::resolveStrut() {
1484     auto strutStyle = this->paragraphStyle().getStrutStyle();
1485     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
1486         return;
1487     }
1488 
1489     auto typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(),
1490         strutStyle.getFontStyle(), std::nullopt);
1491     if (typefaces.empty()) {
1492         SkDEBUGF("Could not resolve strut font\n");
1493         return;
1494     }
1495 
1496     RSFont font(typefaces.front(), strutStyle.getFontSize(), 1, 0);
1497     RSFontMetrics metrics;
1498     RSFont compressFont = font;
1499     scaleFontWithCompressionConfig(compressFont, ScaleOP::COMPRESS);
1500     compressFont.GetMetrics(&metrics);
1501     metricsIncludeFontPadding(&metrics, font);
1502 
1503     if (strutStyle.getHeightOverride()) {
1504         auto strutHeight = metrics.fDescent - metrics.fAscent;
1505         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
1506         fStrutMetrics = InternalLineMetrics(
1507             (metrics.fAscent / strutHeight) * strutMultiplier,
1508             (metrics.fDescent / strutHeight) * strutMultiplier,
1509                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
1510             metrics.fAscent, metrics.fDescent, metrics.fLeading);
1511     } else {
1512         fStrutMetrics = InternalLineMetrics(
1513                 metrics.fAscent,
1514                 metrics.fDescent,
1515                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
1516     }
1517     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
1518 }
1519 #else
1520 void ParagraphImpl::resolveStrut() {
1521     auto strutStyle = this->paragraphStyle().getStrutStyle();
1522     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
1523         return;
1524     }
1525 
1526     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(),
1527         strutStyle.getFontStyle(), std::nullopt);
1528     if (typefaces.empty()) {
1529         SkDEBUGF("Could not resolve strut font\n");
1530         return;
1531     }
1532 
1533     SkFont font(typefaces.front(), strutStyle.getFontSize());
1534     SkFontMetrics metrics;
1535     font.getMetrics(&metrics);
1536     const SkScalar strutLeading = strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize();
1537 
1538     if (strutStyle.getHeightOverride()) {
1539         SkScalar strutAscent = 0.0f;
1540         SkScalar strutDescent = 0.0f;
1541         // The half leading flag doesn't take effect unless there's height override.
1542         if (strutStyle.getHalfLeading()) {
1543             const auto occupiedHeight = metrics.fDescent - metrics.fAscent;
1544             auto flexibleHeight = strutStyle.getHeight() * strutStyle.getFontSize() - occupiedHeight;
1545             // Distribute the flexible height evenly over and under.
1546             flexibleHeight /= 2;
1547             strutAscent = metrics.fAscent - flexibleHeight;
1548             strutDescent = metrics.fDescent + flexibleHeight;
1549         } else {
1550             const SkScalar strutMetricsHeight = metrics.fDescent - metrics.fAscent + metrics.fLeading;
1551             const auto strutHeightMultiplier = strutMetricsHeight == 0
1552               ? strutStyle.getHeight()
1553               : strutStyle.getHeight() * strutStyle.getFontSize() / strutMetricsHeight;
1554             strutAscent = metrics.fAscent * strutHeightMultiplier;
1555             strutDescent = metrics.fDescent * strutHeightMultiplier;
1556         }
1557         fStrutMetrics = InternalLineMetrics(
1558             strutAscent,
1559             strutDescent,
1560             strutLeading,
1561             metrics.fAscent, metrics.fDescent, metrics.fLeading);
1562     } else {
1563         fStrutMetrics = InternalLineMetrics(
1564                 metrics.fAscent,
1565                 metrics.fDescent,
1566                 strutLeading);
1567     }
1568     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
1569 }
1570 #endif
1571 
1572 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
1573     BlockIndex begin = EMPTY_BLOCK;
1574     BlockIndex end = EMPTY_BLOCK;
1575     for (int index = 0; index < fTextStyles.size(); ++index) {
1576         auto& block = fTextStyles[index];
1577         if (block.fRange.end <= textRange.start) {
1578             continue;
1579         }
1580         if (block.fRange.start >= textRange.end) {
1581             break;
1582         }
1583         if (begin == EMPTY_BLOCK) {
1584             begin = index;
1585         }
1586         end = index;
1587     }
1588 
1589     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
1590         // It's possible if some text is not covered with any text style
1591         // Not in Flutter but in direct use of SkParagraph
1592         return EMPTY_RANGE;
1593     }
1594 
1595     return { begin, end + 1 };
1596 }
1597 
1598 TextLine& ParagraphImpl::addLine(SkVector offset,
1599                                  SkVector advance,
1600                                  TextRange textExcludingSpaces,
1601                                  TextRange text,
1602                                  TextRange textIncludingNewLines,
1603                                  ClusterRange clusters,
1604                                  ClusterRange clustersWithGhosts,
1605                                  SkScalar widthWithSpaces,
1606                                  InternalLineMetrics sizes) {
1607     // Define a list of styles that covers the line
1608 #ifdef ENABLE_TEXT_ENHANCE
1609     auto blocks = findAllBlocks(textIncludingNewLines);
1610 #else
1611     auto blocks = findAllBlocks(textExcludingSpaces);
1612 #endif
1613     return fLines.emplace_back(this, offset, advance, blocks,
1614                                textExcludingSpaces, text, textIncludingNewLines,
1615                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
1616 }
1617 
1618 #ifdef ENABLE_TEXT_ENHANCE
1619 TextBox ParagraphImpl::getEmptyTextRect(RectHeightStyle rectHeightStyle) const
1620 {
1621     // get textStyle to calculate text box when text is empty(reference to ParagraphImpl::computeEmptyMetrics())
1622     bool emptyParagraph = fRuns.empty();
1623     TextStyle textStyle = paragraphStyle().getTextStyle();
1624     if (emptyParagraph && !fTextStyles.empty()) {
1625         textStyle = fTextStyles.back().fStyle;
1626     }
1627 
1628     // calculate text box(reference to TextLine::getRectsForRange(), switch(rectHeightStyle))
1629     TextBox box(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1630     auto verticalShift = fEmptyMetrics.rawAscent() - fEmptyMetrics.ascent();
1631     switch (rectHeightStyle) {
1632         case RectHeightStyle::kTight:
1633             if (textStyle.getHeightOverride() && textStyle.getHeight() > 0) {
1634                 const auto effectiveBaseline = fEmptyMetrics.baseline() + fEmptyMetrics.delta();
1635                 box.rect.fTop = effectiveBaseline + fEmptyMetrics.rawAscent();
1636                 box.rect.fBottom = effectiveBaseline + fEmptyMetrics.rawDescent();
1637             }
1638             return box;
1639         case RectHeightStyle::kMax:
1640             box.rect.fBottom = fHeight;
1641             box.rect.fTop = fEmptyMetrics.delta();
1642             return box;
1643         case RectHeightStyle::kIncludeLineSpacingMiddle:
1644         case RectHeightStyle::kIncludeLineSpacingTop:
1645         case RectHeightStyle::kIncludeLineSpacingBottom:
1646             box.rect.fBottom = fHeight;
1647             box.rect.fTop = fEmptyMetrics.delta() + verticalShift;
1648             return box;
1649         case RectHeightStyle::kStrut:
1650             if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
1651                 fParagraphStyle.getStrutStyle().getFontSize() > 0) {
1652                 auto baseline = fEmptyMetrics.baseline();
1653                 box.rect.fTop = baseline + fStrutMetrics.ascent();
1654                 box.rect.fBottom = baseline + fStrutMetrics.descent();
1655             }
1656             return box;
1657         default:
1658             return box;
1659     }
1660 }
1661 #endif
1662 
1663 // Returns a vector of bounding boxes that enclose all text between
1664 // start and end glyph indexes, including start and excluding end
1665 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
1666                                                      unsigned end,
1667                                                      RectHeightStyle rectHeightStyle,
1668                                                      RectWidthStyle rectWidthStyle) {
1669     std::vector<TextBox> results;
1670 #ifdef ENABLE_TEXT_ENHANCE
1671     if (fText.isEmpty() || fState < kShaped) {
1672 #else
1673     if (fText.isEmpty()) {
1674 #endif
1675         if (start == 0 && end > 0) {
1676             // On account of implied "\n" that is always at the end of the text
1677             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
1678 #ifdef ENABLE_TEXT_ENHANCE
1679             results.emplace_back(getEmptyTextRect(rectHeightStyle));
1680 #else
1681             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1682 #endif
1683         }
1684         return results;
1685     }
1686 
1687     this->ensureUTF16Mapping();
1688 
1689     if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
1690         return results;
1691     }
1692 
1693     // Adjust the text to grapheme edges
1694     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
1695     // I don't know why - the solution I have here returns an empty box for every query that
1696     // does not contain an end of a grapheme.
1697     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
1698     // To avoid any problems, I will not allow any selection of a part of a grapheme.
1699     // One flutter test fails because of it but the editing experience is correct
1700     // (although you have to press the cursor many times before it moves to the next grapheme).
1701     TextRange text(fText.size(), fText.size());
1702     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
1703     //  (so we can compare the results). We now include in the selection box only the graphemes
1704     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
1705     if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1706         auto utf8 = fUTF8IndexForUTF16Index[start];
1707         // If start points to a trailing surrogate, skip it
1708         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
1709             utf8 = fUTF8IndexForUTF16Index[start + 1];
1710         }
1711         text.start = this->findNextGraphemeBoundary(utf8);
1712     }
1713     if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1714         auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
1715         text.end = utf8;
1716     }
1717     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
1718     for (auto& line : fLines) {
1719         auto lineText = line.textWithNewlines();
1720 #ifdef ENABLE_TEXT_ENHANCE
1721         lineText = textRangeMergeBtoA(lineText, line.getTextRangeReplacedByEllipsis());
1722 #endif
1723         auto intersect = lineText * text;
1724         if (intersect.empty() && lineText.start != text.start) {
1725             continue;
1726         }
1727 
1728         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
1729     }
1730 /*
1731     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
1732     for (auto& r : results) {
1733         r.rect.fLeft = littleRound(r.rect.fLeft);
1734         r.rect.fRight = littleRound(r.rect.fRight);
1735         r.rect.fTop = littleRound(r.rect.fTop);
1736         r.rect.fBottom = littleRound(r.rect.fBottom);
1737         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
1738     }
1739 */
1740     return results;
1741 }
1742 
1743 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
1744   std::vector<TextBox> boxes;
1745 #ifdef ENABLE_TEXT_ENHANCE
1746   // this method should not be called before kshaped
1747     if (fText.isEmpty() || fState < kShaped) {
1748 #else
1749     if (fText.isEmpty()) {
1750 #endif
1751        return boxes;
1752   }
1753   if (fPlaceholders.size() == 1) {
1754        // We always have one fake placeholder
1755        return boxes;
1756   }
1757   for (auto& line : fLines) {
1758       line.getRectsForPlaceholders(boxes);
1759   }
1760   /*
1761   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
1762   for (auto& r : boxes) {
1763       r.rect.fLeft = littleRound(r.rect.fLeft);
1764       r.rect.fRight = littleRound(r.rect.fRight);
1765       r.rect.fTop = littleRound(r.rect.fTop);
1766       r.rect.fBottom = littleRound(r.rect.fBottom);
1767       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
1768                (r.direction == TextDirection::kLtr ? "left" : "right"));
1769   }
1770   */
1771   return boxes;
1772 }
1773 
1774 // TODO: Optimize (save cluster <-> codepoint connection)
1775 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
1776 
1777     if (fText.isEmpty()) {
1778         return {0, Affinity::kDownstream};
1779     }
1780 
1781     this->ensureUTF16Mapping();
1782 
1783     for (auto& line : fLines) {
1784         // Let's figure out if we can stop looking
1785         auto offsetY = line.offset().fY;
1786         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
1787             // This line is not good enough
1788             continue;
1789         }
1790 
1791         // This is so far the the line vertically closest to our coordinates
1792         // (or the first one, or the only one - all the same)
1793 
1794         auto result = line.getGlyphPositionAtCoordinate(dx);
1795         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
1796         //   result.affinity == Affinity::kUpstream ? "up" : "down");
1797         return result;
1798     }
1799 
1800     return {0, Affinity::kDownstream};
1801 }
1802 
1803 // Finds the first and last glyphs that define a word containing
1804 // the glyph at index offset.
1805 // By "glyph" they mean a character index - indicated by Minikin's code
1806 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
1807 
1808     if (fWords.empty()) {
1809         if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
1810             return {0, 0 };
1811         }
1812     }
1813 
1814     int32_t start = 0;
1815     int32_t end = 0;
1816     for (size_t i = 0; i < fWords.size(); ++i) {
1817         auto word = fWords[i];
1818         if (word <= offset) {
1819             start = word;
1820             end = word;
1821         } else if (word > offset) {
1822             end = word;
1823             break;
1824         }
1825     }
1826 
1827     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
1828     return { SkToU32(start), SkToU32(end) };
1829 }
1830 
1831 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
1832     metrics.clear();
1833     for (auto& line : fLines) {
1834         metrics.emplace_back(line.getMetrics());
1835     }
1836 }
1837 
1838 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
1839     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
1840     auto start = fText.c_str() + textRange.start;
1841     return SkSpan<const char>(start, textRange.width());
1842 }
1843 
1844 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
1845     SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
1846              clusterRange.end <= SkToSizeT(fClusters.size()));
1847     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
1848 }
1849 
1850 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
1851     SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
1852     return fClusters[clusterIndex];
1853 }
1854 
1855 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
1856     auto start = cluster(clusterIndex);
1857     return this->run(start.fRunIndex);
1858 }
1859 
1860 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
1861     SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
1862              blockRange.end <= SkToSizeT(fTextStyles.size()));
1863     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
1864 }
1865 
1866 Block& ParagraphImpl::block(BlockIndex blockIndex) {
1867     SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
1868     return fTextStyles[blockIndex];
1869 }
1870 
1871 void ParagraphImpl::setState(InternalState state) {
1872     if (fState <= state) {
1873         fState = state;
1874         return;
1875     }
1876 
1877     fState = state;
1878     switch (fState) {
1879         case kUnknown:
1880             SkASSERT(false);
1881             /*
1882             // The text is immutable and so are all the text indexing properties
1883             // taken from SkUnicode
1884             fCodeUnitProperties.reset();
1885             fWords.clear();
1886             fBidiRegions.clear();
1887             fUTF8IndexForUTF16Index.reset();
1888             fUTF16IndexForUTF8Index.reset();
1889             */
1890             [[fallthrough]];
1891 
1892         case kIndexed:
1893             fRuns.clear();
1894             fClusters.clear();
1895             [[fallthrough]];
1896 
1897         case kShaped:
1898             fLines.clear();
1899             [[fallthrough]];
1900 
1901         case kLineBroken:
1902             fPicture = nullptr;
1903             [[fallthrough]];
1904 
1905         default:
1906             break;
1907     }
1908 }
1909 
1910 void ParagraphImpl::computeEmptyMetrics() {
1911 
1912     // The empty metrics is used to define the height of the empty lines
1913     // Unfortunately, Flutter has 2 different cases for that:
1914     // 1. An empty line inside the text
1915     // 2. An empty paragraph
1916     // In the first case SkParagraph takes the metrics from the default paragraph style
1917     // In the second case it should take it from the current text style
1918     bool emptyParagraph = fRuns.empty();
1919     TextStyle textStyle = paragraphStyle().getTextStyle();
1920     if (emptyParagraph && !fTextStyles.empty()) {
1921         textStyle = fTextStyles.back().fStyle;
1922     }
1923 
1924     auto typefaces = fontCollection()->findTypefaces(
1925       textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1926     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
1927 
1928 #ifdef ENABLE_TEXT_ENHANCE
1929     RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1930 #else
1931     SkFont font(typeface, textStyle.getFontSize());
1932 #endif
1933     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
1934 
1935     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
1936         textStyle.getHeightOverride()) {
1937 #ifdef ENABLE_TEXT_ENHANCE
1938         const auto intrinsicHeight = fEmptyMetrics.fDescent - fEmptyMetrics.fAscent + fEmptyMetrics.fLeading;
1939 #else
1940         const auto intrinsicHeight = fEmptyMetrics.height();
1941 #endif
1942         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
1943         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
1944             fEmptyMetrics.update(
1945                 fEmptyMetrics.ascent(),
1946                 fEmptyMetrics.descent(),
1947                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
1948         } else {
1949             const auto multiplier = strutHeight / intrinsicHeight;
1950             fEmptyMetrics.update(
1951                 fEmptyMetrics.ascent() * multiplier,
1952                 fEmptyMetrics.descent() * multiplier,
1953                 fEmptyMetrics.leading() * multiplier);
1954         }
1955     }
1956 
1957     if (emptyParagraph) {
1958         // For an empty text we apply both TextHeightBehaviour flags
1959         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
1960         // We have to do it here because we skip wrapping for an empty text
1961         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
1962         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
1963         fEmptyMetrics.update(
1964             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1965             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1966             fEmptyMetrics.leading());
1967     }
1968 
1969     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1970         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1971     }
1972 }
1973 
1974 SkString ParagraphImpl::getEllipsis() const {
1975 
1976     auto ellipsis8 = fParagraphStyle.getEllipsis();
1977     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1978     if (!ellipsis8.isEmpty()) {
1979         return ellipsis8;
1980     } else {
1981         return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1982     }
1983 }
1984 
1985 #ifdef ENABLE_TEXT_ENHANCE
1986 WordBreakType ParagraphImpl::getWordBreakType() const {
1987     return fParagraphStyle.getStrutStyle().getWordBreakType();
1988 }
1989 
1990 LineBreakStrategy ParagraphImpl::getLineBreakStrategy() const {
1991     return fParagraphStyle.getStrutStyle().getLineBreakStrategy();
1992 }
1993 #endif
1994 
1995 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1996 
1997   SkASSERT(from == 0 && to == fText.size());
1998   auto defaultStyle = fParagraphStyle.getTextStyle();
1999   defaultStyle.setFontSize(fontSize);
2000   fParagraphStyle.setTextStyle(defaultStyle);
2001 
2002   for (auto& textStyle : fTextStyles) {
2003     textStyle.fStyle.setFontSize(fontSize);
2004   }
2005 
2006   fState = std::min(fState, kIndexed);
2007   fOldWidth = 0;
2008   fOldHeight = 0;
2009 }
2010 
2011 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
2012     fParagraphStyle.setTextAlign(textAlign);
2013 
2014     if (fState >= kLineBroken) {
2015         fState = kLineBroken;
2016     }
2017 }
2018 
2019 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
2020     SkASSERT(from == 0 && to == fText.size());
2021     auto defaultStyle = fParagraphStyle.getTextStyle();
2022     defaultStyle.setForegroundColor(paint);
2023     fParagraphStyle.setTextStyle(defaultStyle);
2024 
2025     for (auto& textStyle : fTextStyles) {
2026         textStyle.fStyle.setForegroundColor(paint);
2027     }
2028 }
2029 
2030 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
2031     SkASSERT(from == 0 && to == fText.size());
2032     auto defaultStyle = fParagraphStyle.getTextStyle();
2033     defaultStyle.setBackgroundColor(paint);
2034     fParagraphStyle.setTextStyle(defaultStyle);
2035 
2036     for (auto& textStyle : fTextStyles) {
2037         textStyle.fStyle.setBackgroundColor(paint);
2038     }
2039 }
2040 
2041 #ifdef ENABLE_TEXT_ENHANCE
2042 ParagraphPainter::PaintID ParagraphImpl::updateTextStyleColorAndForeground(TextStyle& textStyle, SkColor color)
2043 {
2044     textStyle.setColor(color);
2045     if (textStyle.hasForeground()) {
2046         auto paintOrID = textStyle.getForegroundPaintOrID();
2047         SkPaint* paint = std::get_if<SkPaint>(&paintOrID);
2048         if (paint) {
2049             paint->setColor(color);
2050             textStyle.setForegroundPaint(*paint);
2051         } else {
2052             auto paintID = std::get_if<ParagraphPainter::PaintID>(&paintOrID);
2053             if (paintID) {
2054                 return *paintID;
2055             }
2056         }
2057     }
2058     return INVALID_PAINT_ID;
2059 }
2060 
2061 std::vector<ParagraphPainter::PaintID> ParagraphImpl::updateColor(size_t from, size_t to, SkColor color,
2062     UtfEncodeType encodeType) {
2063     std::vector<ParagraphPainter::PaintID> unresolvedPaintID;
2064     if (from >= to) {
2065         return unresolvedPaintID;
2066     }
2067     this->ensureUTF16Mapping();
2068     if (encodeType == UtfEncodeType::kUtf8) {
2069         from = (from < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[from] : fText.size();
2070         to = (to < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[to] : fText.size();
2071     }
2072     if (from == 0 && to == fText.size()) {
2073         auto defaultStyle = fParagraphStyle.getTextStyle();
2074         auto paintID = updateTextStyleColorAndForeground(defaultStyle, color);
2075         if (paintID != INVALID_PAINT_ID) {
2076             unresolvedPaintID.emplace_back(paintID);
2077         }
2078         fParagraphStyle.setTextStyle(defaultStyle);
2079     }
2080 
2081     for (auto& textStyle : fTextStyles) {
2082         auto& fRange = textStyle.fRange;
2083         if (to < fRange.end) {
2084             break;
2085         }
2086         if (from > fRange.start) {
2087             continue;
2088         }
2089         auto paintID = updateTextStyleColorAndForeground(textStyle.fStyle, color);
2090         if (paintID != INVALID_PAINT_ID) {
2091             unresolvedPaintID.emplace_back(paintID);
2092         }
2093     }
2094     for (auto& line : fLines) {
2095         line.setTextBlobCachePopulated(false);
2096     }
2097     return unresolvedPaintID;
2098 }
2099 
2100 bool ParagraphImpl::isAutoSpaceEnabled() const
2101 {
2102     return paragraphStyle().getEnableAutoSpace() || TextParameter::GetAutoSpacingEnable();
2103 }
2104 
2105 SkScalar ParagraphImpl::clusterUsingAutoSpaceWidth(const Cluster& cluster) const
2106 {
2107     if(!isAutoSpaceEnabled()){
2108         return cluster.width();
2109     }
2110     Run& run = cluster.run();
2111     size_t start = cluster.startPos();
2112     size_t end = cluster.endPos();
2113     float correction = 0.0f;
2114     if (end > start && !run.getAutoSpacings().empty()) {
2115         correction = run.getAutoSpacings()[end - 1].fX - run.getAutoSpacings()[start].fY;
2116     }
2117 
2118     return cluster.width() + std::max(0.0f, correction);
2119 }
2120 
2121 bool ParagraphImpl::preCalculateSingleRunAutoSpaceWidth(SkScalar floorWidth)
2122 {
2123     SkScalar singleRunWidth = fRuns[0].fAdvance.fX;
2124     if (!isAutoSpaceEnabled()) {
2125         return singleRunWidth <= floorWidth - this->detectIndents(0);
2126     }
2127     SkScalar totalFakeSpacing = 0.0f;
2128     ClusterIndex endOfClusters = fClusters.size();
2129     for (size_t cluster = 1; cluster < endOfClusters; ++cluster) {
2130         totalFakeSpacing += (fClusters[cluster].needAutoSpacing())
2131             ? fClusters[cluster - 1].getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
2132     }
2133     singleRunWidth += totalFakeSpacing;
2134     return singleRunWidth <= floorWidth - this->detectIndents(0);
2135 }
2136 
2137 std::vector<TextBlobRecordInfo> ParagraphImpl::getTextBlobRecordInfo()
2138 {
2139     std::vector<TextBlobRecordInfo> textBlobRecordInfos;
2140     for (auto& line : fLines) {
2141         for (auto& block : line.fTextBlobCache) {
2142             TextBlobRecordInfo recordInfo;
2143             recordInfo.fBlob = block.fBlob;
2144             recordInfo.fOffset = block.fOffset;
2145             recordInfo.fPaint = block.fPaint;
2146             textBlobRecordInfos.emplace_back(recordInfo);
2147         }
2148     }
2149     return textBlobRecordInfos;
2150 }
2151 
2152 bool ParagraphImpl::canPaintAllText() const
2153 {
2154     for (auto& line : fLines) {
2155         if (line.ellipsis() != nullptr) {
2156             return false;
2157         }
2158     }
2159     return !fExceededMaxLines;
2160 }
2161 #endif
2162 TArray<TextIndex> ParagraphImpl::countSurroundingGraphemes(TextRange textRange) const {
2163     textRange = textRange.intersection({0, fText.size()});
2164     TArray<TextIndex> graphemes;
2165     if ((fCodeUnitProperties[textRange.start] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2166         // Count the previous partial grapheme
2167         graphemes.emplace_back(textRange.start);
2168     }
2169     for (auto index = textRange.start; index < textRange.end; ++index) {
2170         if ((fCodeUnitProperties[index] & SkUnicode::CodeUnitFlags::kGraphemeStart) != 0) {
2171             graphemes.emplace_back(index);
2172         }
2173     }
2174     return graphemes;
2175 }
2176 
2177 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) const {
2178     while (utf8 > 0 &&
2179           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2180         --utf8;
2181     }
2182     return utf8;
2183 }
2184 
2185 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) const {
2186     while (utf8 < fText.size() &&
2187           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2188         ++utf8;
2189     }
2190     return utf8;
2191 }
2192 
2193 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) const {
2194     while (utf8 < fText.size() &&
2195           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
2196         ++utf8;
2197     }
2198     return utf8;
2199 }
2200 
2201 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) const {
2202     while (utf8 > 0 &&
2203           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
2204         --utf8;
2205     }
2206     return utf8;
2207 }
2208 
2209 void ParagraphImpl::ensureUTF16Mapping() {
2210     fillUTF16MappingOnce([&] {
2211         SkUnicode::extractUtfConversionMapping(
2212                 this->text(),
2213                 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
2214                 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
2215     });
2216 }
2217 
2218 void ParagraphImpl::visit(const Visitor& visitor) {
2219 #ifndef ENABLE_TEXT_ENHANCE
2220     int lineNumber = 0;
2221     for (auto& line : fLines) {
2222         line.ensureTextBlobCachePopulated();
2223         for (auto& rec : line.fTextBlobCache) {
2224             if (rec.fBlob == nullptr) {
2225                 continue;
2226             }
2227             SkTextBlob::Iter iter(*rec.fBlob);
2228             SkTextBlob::Iter::ExperimentalRun run;
2229 
2230             STArray<128, uint32_t> clusterStorage;
2231             const Run* R = rec.fVisitor_Run;
2232             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
2233 
2234             if (R->fClusterStart > 0) {
2235                 int count = R->fClusterIndexes.size();
2236                 clusterStorage.reset(count);
2237                 for (int i = 0; i < count; ++i) {
2238                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
2239                 }
2240                 clusterPtr = &clusterStorage[0];
2241             }
2242             clusterPtr += rec.fVisitor_Pos;
2243 
2244             while (iter.experimentalNext(&run)) {
2245                 const Paragraph::VisitorInfo info = {
2246                     run.font,
2247                     rec.fOffset,
2248                     rec.fClipRect.fRight,
2249                     run.count,
2250                     run.glyphs,
2251                     run.positions,
2252                     clusterPtr,
2253                     0,  // flags
2254                 };
2255                 visitor(lineNumber, &info);
2256                 clusterPtr += run.count;
2257             }
2258         }
2259         visitor(lineNumber, nullptr);   // signal end of line
2260         lineNumber += 1;
2261     }
2262 #endif
2263 }
2264 
2265 #ifdef ENABLE_TEXT_ENHANCE
2266 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
2267     for (size_t i = 0; i < fLines.size(); ++i) {
2268         auto& line = fLines[i];
2269         if (line.text().contains({codeUnitIndex, codeUnitIndex + 1})) {
2270             return i;
2271         }
2272     }
2273     return -1;
2274 }
2275 #else
2276 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
2277     if (codeUnitIndex >= fText.size()) {
2278         return -1;
2279     }
2280     size_t startLine = 0;
2281     size_t endLine = fLines.size() - 1;
2282     if (fLines.empty() || fLines[endLine].textWithNewlines().end <= codeUnitIndex) {
2283         return -1;
2284     }
2285 
2286     while (endLine > startLine) {
2287         // startLine + 1 <= endLine, so we have startLine <= midLine <= endLine - 1.
2288         const size_t midLine = (endLine + startLine) / 2;
2289         const TextRange midLineRange = fLines[midLine].textWithNewlines();
2290         if (codeUnitIndex < midLineRange.start) {
2291             endLine = midLine - 1;
2292         } else if (midLineRange.end <= codeUnitIndex) {
2293             startLine = midLine + 1;
2294         } else {
2295             return midLine;
2296         }
2297     }
2298     SkASSERT(startLine == endLine);
2299     return startLine;
2300 }
2301 #endif
2302 
2303 int ParagraphImpl::getLineNumberAtUTF16Offset(size_t codeUnitIndex) {
2304     this->ensureUTF16Mapping();
2305     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
2306         return -1;
2307     }
2308     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
2309     return getLineNumberAt(utf8);
2310 }
2311 
2312 bool ParagraphImpl::getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const {
2313     if (lineNumber < 0 || lineNumber >= fLines.size()) {
2314         return false;
2315     }
2316     auto& line = fLines[lineNumber];
2317     if (lineMetrics) {
2318         *lineMetrics = line.getMetrics();
2319     }
2320     return true;
2321 }
2322 
2323 TextRange ParagraphImpl::getActualTextRange(int lineNumber, bool includeSpaces) const {
2324     if (lineNumber < 0 || lineNumber >= fLines.size()) {
2325 #ifdef ENABLE_TEXT_ENHANCE
2326         return {0, 0};
2327 #else
2328         return EMPTY_TEXT;
2329 #endif
2330     }
2331     auto& line = fLines[lineNumber];
2332     return includeSpaces ? line.text() : line.trimmedText();
2333 }
2334 
2335 #ifdef ENABLE_TEXT_ENHANCE
2336 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
2337     for (size_t i = 0; i < fLines.size(); ++i) {
2338         auto& line = fLines[i];
2339         if (!line.text().contains({codeUnitIndex, codeUnitIndex})) {
2340             continue;
2341         }
2342         for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
2343             auto& cluster = fClusters[c];
2344             if (cluster.contains(codeUnitIndex)) {
2345                 std::vector<TextBox> boxes;
2346                 line.getRectsForRange(cluster.textRange(),
2347                                       RectHeightStyle::kTight,
2348                                       RectWidthStyle::kTight,
2349                                       boxes);
2350                 if (boxes.size() > 0) {
2351                     if (glyphInfo) {
2352                         *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
2353                     }
2354                     return true;
2355                 }
2356             }
2357         }
2358         return false;
2359     }
2360     return false;
2361 }
2362 #else
2363 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
2364     const int lineNumber = getLineNumberAt(codeUnitIndex);
2365     if (lineNumber == -1) {
2366         return false;
2367     }
2368     auto& line = fLines[lineNumber];
2369     for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
2370         auto& cluster = fClusters[c];
2371         if (cluster.contains(codeUnitIndex)) {
2372             std::vector<TextBox> boxes;
2373             line.getRectsForRange(cluster.textRange(),
2374                                     RectHeightStyle::kTight,
2375                                     RectWidthStyle::kTight,
2376                                     boxes);
2377             if (!boxes.empty()) {
2378                 if (glyphInfo) {
2379                     *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
2380                 }
2381                 return true;
2382             }
2383         }
2384     }
2385     return false;
2386 }
2387 #endif
2388 
2389 #ifdef ENABLE_TEXT_ENHANCE
2390 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
2391                                              SkScalar dy,
2392                                              GlyphClusterInfo* glyphInfo) {
2393     auto res = this->getGlyphPositionAtCoordinate(dx, dy);
2394     auto textIndex = res.position + (res.affinity == Affinity::kDownstream ? 0 : 1);
2395     GlyphClusterInfo gci;
2396     if (this->getGlyphClusterAt(textIndex, glyphInfo ? glyphInfo : &gci)) {
2397         return true;
2398     } else {
2399         return false;
2400     }
2401 }
2402 #else
2403 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
2404                                              SkScalar dy,
2405                                              GlyphClusterInfo* glyphInfo) {
2406     const PositionWithAffinity res = this->getGlyphPositionAtCoordinate(dx, dy);
2407     SkASSERT(res.position != 0 || res.affinity != Affinity::kUpstream);
2408     const size_t utf16Offset = res.position + (res.affinity == Affinity::kDownstream ? 0 : -1);
2409     this->ensureUTF16Mapping();
2410     SkASSERT(utf16Offset < SkToSizeT(fUTF8IndexForUTF16Index.size()));
2411     return this->getGlyphClusterAt(fUTF8IndexForUTF16Index[utf16Offset], glyphInfo);
2412 }
2413 #endif
2414 
2415 bool ParagraphImpl::getGlyphInfoAtUTF16Offset(size_t codeUnitIndex, GlyphInfo* glyphInfo) {
2416     this->ensureUTF16Mapping();
2417     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
2418         return false;
2419     }
2420     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
2421     const int lineNumber = getLineNumberAt(utf8);
2422     if (lineNumber == -1) {
2423         return false;
2424     }
2425     if (glyphInfo == nullptr) {
2426         return true;
2427     }
2428     const TextLine& line = fLines[lineNumber];
2429     const TextIndex startIndex = findPreviousGraphemeBoundary(utf8);
2430     const TextIndex endIndex = findNextGraphemeBoundary(utf8 + 1);
2431     const ClusterIndex glyphClusterIndex = clusterIndex(utf8);
2432     const Cluster& glyphCluster = cluster(glyphClusterIndex);
2433 
2434     // `startIndex` and `endIndex` must be on the same line.
2435     std::vector<TextBox> boxes;
2436     line.getRectsForRange({startIndex, endIndex}, RectHeightStyle::kTight, RectWidthStyle::kTight, boxes);
2437     // TODO: currently placeholders with height=0 and width=0 are ignored so boxes
2438     // can be empty. These placeholders should still be reported for their
2439     // offset information.
2440     if (glyphInfo && !boxes.empty()) {
2441         *glyphInfo = {
2442             boxes[0].rect,
2443             { fUTF16IndexForUTF8Index[startIndex], fUTF16IndexForUTF8Index[endIndex] },
2444             boxes[0].direction,
2445             glyphCluster.run().isEllipsis(),
2446         };
2447     }
2448     return true;
2449 }
2450 
2451 bool ParagraphImpl::getClosestUTF16GlyphInfoAt(SkScalar dx, SkScalar dy, GlyphInfo* glyphInfo) {
2452     const PositionWithAffinity res = this->getGlyphPositionAtCoordinate(dx, dy);
2453     SkASSERT(res.position != 0 || res.affinity != Affinity::kUpstream);
2454     const size_t utf16Offset = res.position + (res.affinity == Affinity::kDownstream ? 0 : -1);
2455     return getGlyphInfoAtUTF16Offset(utf16Offset, glyphInfo);
2456 }
2457 
2458 #ifdef ENABLE_TEXT_ENHANCE
2459 RSFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const
2460 {
2461     for (auto& run : fRuns) {
2462         const auto textRange = run.textRange();
2463         if (textRange.start <= codeUnitIndex && codeUnitIndex < textRange.end) {
2464             return run.font();
2465         }
2466     }
2467     return RSFont();
2468 }
2469 #else
2470 SkFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const {
2471     for (auto& run : fRuns) {
2472         const auto textRange = run.textRange();
2473         if (textRange.start <= codeUnitIndex && codeUnitIndex < textRange.end) {
2474             return run.font();
2475         }
2476     }
2477     return SkFont();
2478 }
2479 #endif
2480 
2481 #ifndef ENABLE_TEXT_ENHANCE
2482 SkFont ParagraphImpl::getFontAtUTF16Offset(size_t codeUnitIndex) {
2483     ensureUTF16Mapping();
2484     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
2485         return SkFont();
2486     }
2487     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
2488     for (auto& run : fRuns) {
2489         const auto textRange = run.textRange();
2490         if (textRange.start <= utf8 && utf8 < textRange.end) {
2491             return run.font();
2492         }
2493     }
2494     return SkFont();
2495 }
2496 #endif
2497 
2498 std::vector<Paragraph::FontInfo> ParagraphImpl::getFonts() const {
2499     std::vector<FontInfo> results;
2500     for (auto& run : fRuns) {
2501         results.emplace_back(run.font(), run.textRange());
2502     }
2503     return results;
2504 }
2505 
2506 #ifdef ENABLE_TEXT_ENHANCE
2507 RSFontMetrics ParagraphImpl::measureText()
2508 {
2509     RSFontMetrics metrics;
2510     if (fRuns.empty()) {
2511         return metrics;
2512     }
2513 
2514     auto& firstFont = const_cast<RSFont&>(fRuns.front().font());
2515     RSRect firstBounds;
2516     auto firstStr = text(fRuns.front().textRange());
2517     firstFont.GetMetrics(&metrics);
2518     auto decompressFont = firstFont;
2519     scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
2520     metricsIncludeFontPadding(&metrics, decompressFont);
2521     firstFont.MeasureText(firstStr.data(), firstStr.size(), RSDrawing::TextEncoding::UTF8, &firstBounds);
2522     fGlyphsBoundsTop = firstBounds.GetTop();
2523     fGlyphsBoundsBottom = firstBounds.GetBottom();
2524     fGlyphsBoundsLeft = firstBounds.GetLeft();
2525     float realWidth = 0;
2526     for (size_t i = 0; i < fRuns.size(); ++i) {
2527         auto run = fRuns[i];
2528         auto& font = const_cast<RSFont&>(run.font());
2529         RSRect bounds;
2530         auto str = text(run.textRange());
2531         auto advance = font.MeasureText(str.data(), str.size(), RSDrawing::TextEncoding::UTF8, &bounds);
2532         realWidth += advance;
2533         if (i == 0) {
2534             realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2535         }
2536         if (i == (fRuns.size() - 1)) {
2537             realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2538         }
2539         fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.GetTop());
2540         fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.GetBottom());
2541     }
2542     fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
2543     return metrics;
2544 }
2545 
2546 std::vector<std::unique_ptr<TextLineBase>> ParagraphImpl::GetTextLines() {
2547     std::vector<std::unique_ptr<TextLineBase>> textLineBases;
2548     for (auto& line: fLines) {
2549         std::unique_ptr<TextLineBaseImpl> textLineBaseImplPtr =
2550             std::make_unique<TextLineBaseImpl>(std::make_unique<TextLine>(std::move(line)));
2551         textLineBases.emplace_back(std::move(textLineBaseImplPtr));
2552     }
2553 
2554     return textLineBases;
2555 }
2556 
2557 size_t ParagraphImpl::prefixByteCountUntilChar(size_t index) {
2558     convertUtf8ToUnicode(fText);
2559     if (fUnicodeIndexForUTF8Index.empty()) {
2560         return std::numeric_limits<size_t>::max();
2561     }
2562     auto it = std::lower_bound(fUnicodeIndexForUTF8Index.begin(), fUnicodeIndexForUTF8Index.end(), index);
2563     if (it != fUnicodeIndexForUTF8Index.end() && *it == index) {
2564         return std::distance(fUnicodeIndexForUTF8Index.begin(), it);
2565     } else {
2566         return std::numeric_limits<size_t>::max();
2567     }
2568 }
2569 
2570 void ParagraphImpl::copyProperties(const ParagraphImpl& source) {
2571     fText = source.fText;
2572     fTextStyles = source.fTextStyles;
2573     fPlaceholders = source.fPlaceholders;
2574     fParagraphStyle = source.fParagraphStyle;
2575     fFontCollection = source.fFontCollection;
2576     fUnicode = source.fUnicode;
2577 
2578     fState = kUnknown;
2579     fUnresolvedGlyphs = 0;
2580     fPicture = nullptr;
2581     fStrutMetrics = false;
2582     fOldWidth = 0;
2583     fOldHeight = 0;
2584     fHasLineBreaks = false;
2585     fHasWhitespacesInside = false;
2586     fTrailingSpaces = 0;
2587 }
2588 
2589 std::unique_ptr<Paragraph> ParagraphImpl::createCroppedCopy(size_t startIndex, size_t count) {
2590     std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2591     paragraph->copyProperties(*this);
2592 
2593     // change range
2594     auto validStart = prefixByteCountUntilChar(startIndex);
2595     if (validStart == std::numeric_limits<size_t>::max()) {
2596         return nullptr;
2597     }
2598     // For example, when the clipped string str1 is "123456789"
2599     // startIndex=2, count=std:: numeric_imits<size_t>:: max(), the resulting string str2 is "3456789".
2600     // When startIndex=3 and count=3, crop the generated string str3 to "456"
2601     TextRange firstDeleteRange(0, validStart);
2602     paragraph->fText.remove(0, validStart);
2603     paragraph->resetTextStyleRange(firstDeleteRange);
2604     paragraph->resetPlaceholderRange(firstDeleteRange);
2605 
2606     if (count != std::numeric_limits<size_t>::max()) {
2607         auto invalidStart = paragraph->prefixByteCountUntilChar(count);
2608         if (invalidStart == std::numeric_limits<size_t>::max()) {
2609             return nullptr;
2610         }
2611         auto invalidEnd = paragraph->fText.size();
2612         TextRange secodeDeleteRange(invalidStart, invalidEnd);
2613         paragraph->fText.remove(invalidStart, invalidEnd - invalidStart);
2614         paragraph->resetTextStyleRange(secodeDeleteRange);
2615         paragraph->resetPlaceholderRange(secodeDeleteRange);
2616     }
2617     return paragraph;
2618 }
2619 
2620 void ParagraphImpl::initUnicodeText() {
2621     this->fUnicodeText = convertUtf8ToUnicode(fText);
2622 }
2623 
2624 // Currently, only support to generate text and text shadow paint regions.
2625 // Can't accurately calculate the paint region of italic fonts(including fake italic).
2626 SkIRect ParagraphImpl::generatePaintRegion(SkScalar x, SkScalar y) {
2627     if (fState < kFormatted) {
2628         TEXT_LOGW("Call generatePaintRegion when paragraph is not formatted");
2629         return SkIRect::MakeXYWH(x, y, 0, 0);
2630     }
2631 
2632     if (fPaintRegion.has_value()) {
2633         return fPaintRegion.value().makeOffset(x, y).roundOut();
2634     }
2635 
2636     fPaintRegion = SkRect::MakeEmpty();
2637     for (auto& line : fLines) {
2638         SkRect linePaintRegion = line.generatePaintRegion(0, 0);
2639         fPaintRegion->join(linePaintRegion);
2640     }
2641     return fPaintRegion.value().makeOffset(x, y).roundOut();
2642 }
2643 
2644 std::unique_ptr<Paragraph> ParagraphImpl::CloneSelf()
2645 {
2646     std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2647 
2648     paragraph->fFontCollection = this->fFontCollection;
2649     paragraph->fParagraphStyle = this->fParagraphStyle;
2650     paragraph->fAlphabeticBaseline = this->fAlphabeticBaseline;
2651     paragraph->fIdeographicBaseline = this->fIdeographicBaseline;
2652     paragraph->fGlyphsBoundsTop = this->fGlyphsBoundsTop;
2653     paragraph->fGlyphsBoundsBottom = this->fGlyphsBoundsBottom;
2654     paragraph->fGlyphsBoundsLeft = this->fGlyphsBoundsLeft;
2655     paragraph->fGlyphsBoundsRight = this->fGlyphsBoundsRight;
2656     paragraph->fHeight = this->fHeight;
2657     paragraph->fWidth = this->fWidth;
2658     paragraph->fMaxIntrinsicWidth = this->fMaxIntrinsicWidth;
2659     paragraph->fMinIntrinsicWidth = this->fMinIntrinsicWidth;
2660     paragraph->fLongestLine = this->fLongestLine;
2661     paragraph->fLongestLineWithIndent = this->fLongestLineWithIndent;
2662     paragraph->fExceededMaxLines = this->fExceededMaxLines;
2663 
2664     paragraph->fLetterSpaceStyles = this->fLetterSpaceStyles;
2665     paragraph->fWordSpaceStyles = this->fWordSpaceStyles;
2666     paragraph->fBackgroundStyles = this->fBackgroundStyles;
2667     paragraph->fForegroundStyles = this->fForegroundStyles;
2668     paragraph->fShadowStyles = this->fShadowStyles;
2669     paragraph->fDecorationStyles = this->fDecorationStyles;
2670     paragraph->fTextStyles = this->fTextStyles;
2671     paragraph->fPlaceholders = this->fPlaceholders;
2672     paragraph->fText = this->fText;
2673 
2674     paragraph->fState = this->fState;
2675     paragraph->fRuns = this->fRuns;
2676     paragraph->fClusters = this->fClusters;
2677     paragraph->fCodeUnitProperties = this->fCodeUnitProperties;
2678     paragraph->fClustersIndexFromCodeUnit = this->fClustersIndexFromCodeUnit;
2679 
2680     paragraph->fWords = this->fWords;
2681     paragraph->fIndents = this->fIndents;
2682     paragraph->fBidiRegions = this->fBidiRegions;
2683 
2684     paragraph->fUTF8IndexForUTF16Index = this->fUTF8IndexForUTF16Index;
2685     paragraph->fUTF16IndexForUTF8Index = this->fUTF16IndexForUTF8Index;
2686     paragraph->fUnresolvedGlyphs = this->fUnresolvedGlyphs;
2687     paragraph->fUnresolvedCodepoints = this->fUnresolvedCodepoints;
2688 
2689     for (auto& line : this->fLines) {
2690         paragraph->fLines.emplace_back(line.CloneSelf());
2691     }
2692 
2693     paragraph->fPicture = this->fPicture;
2694     paragraph->fFontSwitches = this->fFontSwitches;
2695     paragraph->fEmptyMetrics = this->fEmptyMetrics;
2696     paragraph->fStrutMetrics = this->fStrutMetrics;
2697 
2698     paragraph->fOldWidth = this->fOldWidth;
2699     paragraph->fOldHeight = this->fOldHeight;
2700     paragraph->fMaxWidthWithTrailingSpaces = this->fMaxWidthWithTrailingSpaces;
2701 
2702     paragraph->fUnicode = this->fUnicode;
2703     paragraph->fHasLineBreaks = this->fHasLineBreaks;
2704     paragraph->fHasWhitespacesInside = this->fHasWhitespacesInside;
2705     paragraph->fTrailingSpaces = this->fTrailingSpaces;
2706     paragraph->fLineNumber = this->fLineNumber;
2707     paragraph->fEllipsisRange = this->fEllipsisRange;
2708 
2709     for (auto& run : paragraph->fRuns) {
2710         run.setOwner(paragraph.get());
2711     }
2712     for (auto& cluster : paragraph->fClusters) {
2713         cluster.setOwner(paragraph.get());
2714     }
2715     for (auto& line : paragraph->fLines) {
2716         line.setParagraphImpl(paragraph.get());
2717     }
2718     return paragraph;
2719 }
2720 
2721 #endif
2722 
2723 #ifndef ENABLE_TEXT_ENHANCE
2724 void ParagraphImpl::extendedVisit(const ExtendedVisitor& visitor) {
2725     int lineNumber = 0;
2726     for (auto& line : fLines) {
2727         line.iterateThroughVisualRuns(
2728             false,
2729             [&](const Run* run,
2730                 SkScalar runOffsetInLine,
2731                 TextRange textRange,
2732                 SkScalar* runWidthInLine) {
2733                 *runWidthInLine = line.iterateThroughSingleRunByStyles(
2734                 TextLine::TextAdjustment::GlyphCluster,
2735                 run,
2736                 runOffsetInLine,
2737                 textRange,
2738                 StyleType::kNone,
2739                 [&](TextRange textRange,
2740                     const TextStyle& style,
2741                     const TextLine::ClipContext& context) {
2742                     SkScalar correctedBaseline = SkScalarFloorToScalar(
2743                         line.baseline() + style.getBaselineShift() + 0.5);
2744                     SkPoint offset =
2745                         SkPoint::Make(line.offset().fX + context.fTextShift,
2746                                       line.offset().fY + correctedBaseline);
2747                     SkRect rect = context.clip.makeOffset(line.offset());
2748                     AutoSTArray<16, SkRect> glyphBounds;
2749                     glyphBounds.reset(SkToInt(run->size()));
2750                     run->font().getBounds(run->glyphs().data(),
2751                                           SkToInt(run->size()),
2752                                           glyphBounds.data(),
2753                                           nullptr);
2754                     STArray<128, uint32_t> clusterStorage;
2755                     const uint32_t* clusterPtr = run->clusterIndexes().data();
2756                     if (run->fClusterStart > 0) {
2757                         clusterStorage.reset(context.size);
2758                         for (size_t i = 0; i < context.size; ++i) {
2759                           clusterStorage[i] =
2760                               run->fClusterStart + run->fClusterIndexes[i];
2761                         }
2762                         clusterPtr = &clusterStorage[0];
2763                     }
2764                     const Paragraph::ExtendedVisitorInfo info = {
2765                         run->font(),
2766                         offset,
2767                         SkSize::Make(rect.width(), rect.height()),
2768                         SkToS16(context.size),
2769                         &run->glyphs()[context.pos],
2770                         &run->fPositions[context.pos],
2771                         &glyphBounds[context.pos],
2772                         clusterPtr,
2773                         0,  // flags
2774                     };
2775                     visitor(lineNumber, &info);
2776                 });
2777             return true;
2778             });
2779         visitor(lineNumber, nullptr);   // signal end of line
2780         lineNumber += 1;
2781     }
2782 }
2783 
2784 int ParagraphImpl::getPath(int lineNumber, SkPath* dest) {
2785     int notConverted = 0;
2786     auto& line = fLines[lineNumber];
2787     line.iterateThroughVisualRuns(
2788               false,
2789               [&](const Run* run,
2790                   SkScalar runOffsetInLine,
2791                   TextRange textRange,
2792                   SkScalar* runWidthInLine) {
2793           *runWidthInLine = line.iterateThroughSingleRunByStyles(
2794           TextLine::TextAdjustment::GlyphCluster,
2795           run,
2796           runOffsetInLine,
2797           textRange,
2798           StyleType::kNone,
2799           [&](TextRange textRange,
2800               const TextStyle& style,
2801               const TextLine::ClipContext& context) {
2802               const SkFont& font = run->font();
2803               SkScalar correctedBaseline = SkScalarFloorToScalar(
2804                 line.baseline() + style.getBaselineShift() + 0.5);
2805               SkPoint offset =
2806                   SkPoint::Make(line.offset().fX + context.fTextShift,
2807                                 line.offset().fY + correctedBaseline);
2808               SkRect rect = context.clip.makeOffset(offset);
2809               struct Rec {
2810                   SkPath* fPath;
2811                   SkPoint fOffset;
2812                   const SkPoint* fPos;
2813                   int fNotConverted;
2814               } rec =
2815                   {dest, SkPoint::Make(rect.left(), rect.top()),
2816                    &run->positions()[context.pos], 0};
2817               font.getPaths(&run->glyphs()[context.pos], context.size,
2818                     [](const SkPath* path, const SkMatrix& mx, void* ctx) {
2819                         Rec* rec = reinterpret_cast<Rec*>(ctx);
2820                         if (path) {
2821                             SkMatrix total = mx;
2822                             total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
2823                                                 rec->fPos->fY + rec->fOffset.fY);
2824                             rec->fPath->addPath(*path, total);
2825                         } else {
2826                             rec->fNotConverted++;
2827                         }
2828                         rec->fPos += 1; // move to the next glyph's position
2829                     }, &rec);
2830               notConverted += rec.fNotConverted;
2831           });
2832         return true;
2833     });
2834 
2835     return notConverted;
2836 }
2837 #endif
2838 
2839 SkPath Paragraph::GetPath(SkTextBlob* textBlob) {
2840     SkPath path;
2841     SkTextBlobRunIterator iter(textBlob);
2842     while (!iter.done()) {
2843         SkFont font = iter.font();
2844         struct Rec { SkPath* fDst; SkPoint fOffset; const SkPoint* fPos; } rec =
2845             {&path, {textBlob->bounds().left(), textBlob->bounds().top()},
2846              iter.points()};
2847         font.getPaths(iter.glyphs(), iter.glyphCount(),
2848             [](const SkPath* src, const SkMatrix& mx, void* ctx) {
2849                 Rec* rec = (Rec*)ctx;
2850                 if (src) {
2851                     SkMatrix tmp(mx);
2852                     tmp.postTranslate(rec->fPos->fX - rec->fOffset.fX,
2853                                       rec->fPos->fY - rec->fOffset.fY);
2854                     rec->fDst->addPath(*src, tmp);
2855                 }
2856                 rec->fPos += 1;
2857             },
2858             &rec);
2859         iter.next();
2860     }
2861     return path;
2862 }
2863 
2864 bool ParagraphImpl::containsEmoji(SkTextBlob* textBlob) {
2865     bool result = false;
2866     SkTextBlobRunIterator iter(textBlob);
2867     while (!iter.done() && !result) {
2868         // Walk through all the text by codepoints
2869         this->getUnicode()->forEachCodepoint(iter.text(), iter.textSize(),
2870            [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
2871                 if (this->getUnicode()->isEmoji(unichar)) {
2872                     result = true;
2873                 }
2874             });
2875         iter.next();
2876     }
2877     return result;
2878 }
2879 
2880 bool ParagraphImpl::containsColorFontOrBitmap(SkTextBlob* textBlob) {
2881     SkTextBlobRunIterator iter(textBlob);
2882     bool flag = false;
2883     while (!iter.done() && !flag) {
2884         iter.font().getPaths(
2885             (const SkGlyphID*) iter.glyphs(),
2886             iter.glyphCount(),
2887             [](const SkPath* path, const SkMatrix& mx, void* ctx) {
2888                 if (path == nullptr) {
2889                     bool* flag1 = (bool*)ctx;
2890                     *flag1 = true;
2891                 }
2892             }, &flag);
2893         iter.next();
2894     }
2895     return flag;
2896 }
2897 
2898 #ifdef ENABLE_TEXT_ENHANCE
2899 std::string_view ParagraphImpl::GetState() const
2900 {
2901     static std::unordered_map<InternalState, std::string_view> state = {
2902         {kUnknown, "Unknow"},
2903         {kIndexed, "Indexed"},
2904         {kShaped, "Shaped"},
2905         {kLineBroken, "LineBroken"},
2906         {kFormatted, "Formatted"},
2907         {kDrawn, "Drawn"},
2908     };
2909     return state[fState];
2910 }
2911 
2912 std::string ParagraphImpl::GetDumpInfo() const
2913 {
2914     // 由于arkui那边要求dump信息只能一行,这里将应该换行的地方用逗号代替,方便定位时统一用工具替换
2915     std::ostringstream paragraphInfo;
2916     paragraphInfo << "This is paragraph dump info:,";
2917     paragraphInfo << "Text size: " << fText.size() << " fState: " << GetState()
2918                   << " fSkipTextBlobDrawing: " << (fSkipTextBlobDrawing ? "true" : "false") << ",";
2919     uint32_t glyphSize = 0;
2920     uint32_t runIndex = 0;
2921     for (auto& run : fRuns) {
2922         paragraphInfo << "Run[" << runIndex << "]" << " glyph size: " << run.size()
2923                       << " text range: [" << run.textRange().start << "-" << run.textRange().end << "),";
2924         runIndex++;
2925         glyphSize += run.size();
2926     }
2927     uint32_t blockIndex = 0;
2928     for (auto& block : fTextStyles) {
2929         paragraphInfo << "Block[" << blockIndex << "]"
2930                       << " text range[" << block.fRange.start << "-"<< block.fRange.end << ")"
2931                       << " font size: " << block.fStyle.getFontSize()
2932                       << " font color: " << std::hex << block.fStyle.getColor() << std::dec
2933                       << " font height: " << block.fStyle.getHeight()
2934                       << " font weight: " << block.fStyle.getFontStyle().GetWeight()
2935                       << " font width: " << block.fStyle.getFontStyle().GetWidth()
2936                       << " font slant: " << block.fStyle.getFontStyle().GetSlant() << ",";
2937         blockIndex++;
2938     }
2939     paragraphInfo << "Paragraph glyph size: " << glyphSize << ",";
2940     uint32_t lineIndex = 0;
2941     for (auto& line : fLines) {
2942         auto runs = line.getLineAllRuns();
2943         auto runSize = runs.size();
2944         if (runSize !=0 ) {
2945             paragraphInfo << "Line[" << lineIndex << "] run range: [" << runs[0] << "-" << runs[runSize - 1] << "],";
2946         }
2947         lineIndex++;
2948     }
2949     return paragraphInfo.str();
2950 }
2951 #endif
2952 
2953 }  // namespace textlayout
2954 }  // namespace skia
2955