• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 
3 #include "include/core/SkCanvas.h"
4 #include "include/core/SkFontMetrics.h"
5 #include "include/core/SkMatrix.h"
6 #include "include/core/SkPictureRecorder.h"
7 #include "include/core/SkSpan.h"
8 #include "include/core/SkTypeface.h"
9 #include "include/private/SkTFitsIn.h"
10 #include "include/private/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 #ifdef OHOS_SUPPORT
23 #include "utils/text_trace.h"
24 #endif
25 #include "src/utils/SkUTF.h"
26 #include <math.h>
27 #include <algorithm>
28 #include <utility>
29 
30 #ifdef OHOS_SUPPORT
31 #include "log.h"
32 #include "modules/skparagraph/src/TextLineBaseImpl.h"
33 #include "TextParameter.h"
34 #endif
35 
36 namespace skia {
37 namespace textlayout {
38 
39 namespace {
40 constexpr int PARAM_DOUBLE = 2;
41 constexpr ParagraphPainter::PaintID INVALID_PAINT_ID = -1;
42 
littleRound(SkScalar a)43 SkScalar littleRound(SkScalar a) {
44     // This rounding is done to match Flutter tests. Must be removed..
45     auto val = std::fabs(a);
46     if (val < 10000) {
47         return SkScalarRoundToScalar(a * 100.0)/100.0;
48     } else if (val < 100000) {
49         return SkScalarRoundToScalar(a * 10.0)/10.0;
50     } else {
51         return SkScalarFloorToScalar(a);
52     }
53 }
54 }  // namespace
55 
operator *(const TextRange & a,const TextRange & b)56 TextRange operator*(const TextRange& a, const TextRange& b) {
57     if (a.start == b.start && a.end == b.end) return a;
58     auto begin = std::max(a.start, b.start);
59     auto end = std::min(a.end, b.end);
60     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
61 }
62 
textRangeMergeBtoA(const TextRange & a,const TextRange & b)63 TextRange textRangeMergeBtoA(const TextRange& a, const TextRange& b) {
64     if (a.width() <= 0 || b.width() <= 0 || a.end < b.start || a.start > b.end) {
65         return a;
66     }
67 
68     return TextRange(std::min(a.start, b.start), std::max(a.end, b.end));
69 }
70 
convertUtf8ToUnicode(const SkString & utf8)71 std::vector<SkUnichar> ParagraphImpl::convertUtf8ToUnicode(const SkString& utf8)
72 {
73     fUnicodeIndexForUTF8Index.reset();
74     std::vector<SkUnichar> result;
75     auto p = utf8.c_str();
76     auto end = p + utf8.size();
77     while (p < end) {
78         auto tmp = p;
79         auto unichar = SkUTF::NextUTF8(&p, end);
80         for (auto i = 0; i < p - tmp; ++i) {
81             fUnicodeIndexForUTF8Index.emplace_back(result.size());
82         }
83         result.emplace_back(unichar);
84     }
85     fUnicodeIndexForUTF8Index.emplace_back(result.size());
86     return result;
87 }
88 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)89 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
90             : fFontCollection(std::move(fonts))
91             , fParagraphStyle(std::move(style))
92             , fAlphabeticBaseline(0)
93             , fIdeographicBaseline(0)
94             , fHeight(0)
95             , fWidth(0)
96             , fMaxIntrinsicWidth(0)
97             , fMinIntrinsicWidth(0)
98             , fLongestLine(0)
99 #ifdef OHOS_SUPPORT
100             , fLongestLineWithIndent(0)
101 #endif
102             , fExceededMaxLines(0)
103 { }
104 
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)105 ParagraphImpl::ParagraphImpl(const SkString& text,
106                              ParagraphStyle style,
107                              SkTArray<Block, true> blocks,
108                              SkTArray<Placeholder, true> placeholders,
109                              sk_sp<FontCollection> fonts,
110                              std::shared_ptr<SkUnicode> unicode)
111         : Paragraph(std::move(style), std::move(fonts))
112         , fTextStyles(std::move(blocks))
113         , fPlaceholders(std::move(placeholders))
114         , fText(text)
115         , fState(kUnknown)
116         , fUnresolvedGlyphs(0)
117         , fPicture(nullptr)
118         , fStrutMetrics(false)
119         , fOldWidth(0)
120         , fOldHeight(0)
121         , fUnicode(std::move(unicode))
122         , fHasLineBreaks(false)
123         , fHasWhitespacesInside(false)
124         , fTrailingSpaces(0)
125 {
126     SkASSERT(fUnicode);
127 }
128 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)129 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
130                              ParagraphStyle style,
131                              SkTArray<Block, true> blocks,
132                              SkTArray<Placeholder, true> placeholders,
133                              sk_sp<FontCollection> fonts,
134                              std::shared_ptr<SkUnicode> unicode)
135         : ParagraphImpl(SkString(),
136                         std::move(style),
137                         std::move(blocks),
138                         std::move(placeholders),
139                         std::move(fonts),
140                         std::move(unicode))
141 {
142     SkASSERT(fUnicode);
143     fText =  SkUnicode::convertUtf16ToUtf8(utf16text);
144 }
145 
146 ParagraphImpl::~ParagraphImpl() = default;
147 
unresolvedGlyphs()148 int32_t ParagraphImpl::unresolvedGlyphs() {
149     if (fState < kShaped) {
150         return -1;
151     }
152 
153     return fUnresolvedGlyphs;
154 }
155 
156 #ifndef USE_SKIA_TXT
GetLineFontMetrics(const size_t lineNumber,size_t & charNumber,std::vector<SkFontMetrics> & fontMetrics)157 bool ParagraphImpl::GetLineFontMetrics(const size_t lineNumber, size_t& charNumber,
158     std::vector<SkFontMetrics>& fontMetrics) {
159 #else
160 bool ParagraphImpl::GetLineFontMetrics(const size_t lineNumber, size_t& charNumber,
161     std::vector<RSFontMetrics>& fontMetrics) {
162 #endif
163     if (lineNumber > fLines.size() || !lineNumber ||
164         !fLines[lineNumber - 1].getLineAllRuns().size()) {
165         return false;
166     }
167 
168     size_t textRange = 0;
169     size_t lineCharCount = fLines[lineNumber - 1].clusters().end -
170         fLines[lineNumber - 1].clusters().start;
171 
172     for (auto& runIndex : fLines[lineNumber - 1].getLineAllRuns()) {
173         Run& targetRun = this->run(runIndex);
174         size_t runClock = 0;
175         size_t currentRunCharNumber = targetRun.clusterRange().end -
176             targetRun.clusterRange().start;
177         for (;textRange < lineCharCount; textRange++) {
178             if (++runClock > currentRunCharNumber) {
179                 break;
180             }
181 #ifndef USE_SKIA_TXT
182             SkFontMetrics newFontMetrics;
183             targetRun.fFont.getMetrics(&newFontMetrics);
184 #else
185             RSFontMetrics newFontMetrics;
186             targetRun.fFont.GetMetrics(&newFontMetrics);
187 #endif
188 #ifdef OHOS_SUPPORT
189             auto decompressFont = targetRun.fFont;
190             scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
191             metricsIncludeFontPadding(&newFontMetrics, decompressFont);
192 #endif
193             fontMetrics.emplace_back(newFontMetrics);
194         }
195     }
196 
197     charNumber = lineCharCount;
198     return true;
199 }
200 
201 std::unordered_set<SkUnichar> ParagraphImpl::unresolvedCodepoints() {
202     return fUnresolvedCodepoints;
203 }
204 
205 void ParagraphImpl::addUnresolvedCodepoints(TextRange textRange) {
206     fUnicode->forEachCodepoint(
207         &fText[textRange.start], textRange.width(),
208         [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
209             fUnresolvedCodepoints.emplace(unichar);
210         }
211     );
212 }
213 
214 TextRange ParagraphImpl::resetRangeWithDeletedRange(const TextRange& sourceRange,
215     const TextRange& deletedRange, const size_t& ellSize)
216 {
217     if (sourceRange.end <= deletedRange.start) {
218         return sourceRange;
219     }
220     auto changeSize = ellSize - deletedRange.width();
221 
222     if (sourceRange.start >= deletedRange.end) {
223         return TextRange(sourceRange.start + changeSize, sourceRange.end + changeSize);
224     }
225 
226     TextRange target;
227     target.start = sourceRange.start <= deletedRange.start ? sourceRange.start : deletedRange.start + ellSize;
228     target.end = sourceRange.end <= deletedRange.end ? deletedRange.start + ellSize : sourceRange.end + changeSize;
229     return target.start <= target.end ? target : EMPTY_RANGE;
230 }
231 
232 void ParagraphImpl::resetTextStyleRange(const TextRange& deletedRange)
233 {
234     auto tmpTextStyle = fTextStyles;
235     fTextStyles.reset();
236     for (auto fs : tmpTextStyle) {
237         auto newTextRange = resetRangeWithDeletedRange(fs.fRange, deletedRange, this->getEllipsis().size());
238         LOGD("ParagraphImpl::resetTextStyleRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
239             static_cast<unsigned long>(fs.fRange.start), static_cast<unsigned long>(fs.fRange.end),
240             static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
241         if (newTextRange.width() == 0) {
242             continue;
243         }
244         fs.fRange = newTextRange;
245         fTextStyles.emplace_back(fs);
246     }
247 }
248 
249 void ParagraphImpl::resetPlaceholderRange(const TextRange& deletedRange)
250 {
251     // reset fRange && fTextBefore && fBlockBefore
252     auto ellSize = this->getEllipsis().size();
253     auto tmpPlaceholders = fPlaceholders;
254     fPlaceholders.reset();
255     for (auto ph : tmpPlaceholders) {
256         auto newTextRange = resetRangeWithDeletedRange(ph.fRange, deletedRange, ellSize);
257         LOGD("ParagraphImpl::resetPlaceholderRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
258             static_cast<unsigned long>(ph.fRange.start), static_cast<unsigned long>(ph.fRange.end),
259             static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
260         if (newTextRange.empty()) {
261             continue;
262         }
263         ph.fRange = newTextRange;
264         newTextRange = ph.fTextBefore;
265         newTextRange.start = fPlaceholders.empty() ? 0 : fPlaceholders.back().fRange.end;
266         if (newTextRange.end > deletedRange.start) {
267             newTextRange.end = newTextRange.end <= deletedRange.end ?
268                 deletedRange.start + ellSize : newTextRange.end + ellSize - deletedRange.width();
269         }
270         ph.fTextBefore = newTextRange;
271         fPlaceholders.emplace_back(ph);
272     }
273 }
274 
275 #ifdef OHOS_SUPPORT
276 bool ParagraphImpl::middleEllipsisDeal()
277 {
278     if (fRuns.empty()) {
279         return false;
280     }
281     isMiddleEllipsis = false;
282 
283     size_t end = 0;
284     size_t charbegin = 0;
285     size_t charend = 0;
286     if (fRuns.begin()->leftToRight()) {
287         middleEllipsisLtrDeal(end, charbegin, charend);
288     } else {
289         middleEllipsisRtlDeal(end, charbegin, charend);
290     }
291     if (end != 0) {
292         TextRange deletedRange(charbegin, charend);
293         resetTextStyleRange(deletedRange);
294         resetPlaceholderRange(deletedRange);
295         fEllipsisRange = deletedRange;
296     }
297     // end = 0 means the text does not exceed the width limit
298     return end != 0;
299 }
300 
301 void ParagraphImpl::middleEllipsisLtrDeal(size_t& end,
302                                           size_t& charbegin,
303                                           size_t& charend)
304 {
305     const SkString& ell = this->getEllipsis();
306     const char *ellStr = ell.c_str();
307     size_t start = 0;
308     if (ltrTextSize.empty() || ltrTextSize[0].phraseWidth >= fOldMaxWidth) {
309         fText.reset();
310         fText.set(ellStr);
311         end = 1;
312         charend = ell.size();
313     } else {
314         scanTextCutPoint(ltrTextSize, start, end);
315         if (end) {
316             charbegin = ltrTextSize[start].charbegin;
317             charend = ltrTextSize[end].charOver;
318             fText.remove(ltrTextSize[start].charbegin, ltrTextSize[end].charOver - ltrTextSize[start].charbegin);
319             fText.insert(ltrTextSize[start].charbegin, ellStr);
320         }
321     }
322     ltrTextSize.clear();
323 }
324 
325 void ParagraphImpl::middleEllipsisRtlDeal(size_t& end,
326                                           size_t& charbegin,
327                                           size_t& charend)
328 {
329     const SkString& ell = this->getEllipsis();
330     const char *ellStr = ell.c_str();
331     size_t start = 0;
332     scanTextCutPoint(rtlTextSize, start, end);
333     if (start < 1 || end + PARAM_DOUBLE >= rtlTextSize.size()) {
334         start = 0;
335         end = 0;
336     }
337     if (end) {
338         charbegin = rtlTextSize[start - 1].charbegin;
339         charend = rtlTextSize[end + PARAM_DOUBLE].charbegin;
340         fText.remove(rtlTextSize[start - 1].charbegin,
341             rtlTextSize[end + PARAM_DOUBLE].charbegin - rtlTextSize[start - 1].charbegin);
342         fText.insert(rtlTextSize[start - 1].charbegin, ellStr);
343     }
344     rtlTextSize.clear();
345 }
346 #endif
347 
348 SkScalar ParagraphImpl::resetEllipsisWidth(SkScalar ellipsisWidth, size_t& lastRunIndex, const size_t textIndex)
349 {
350     auto targetCluster = cluster(clusterIndex(textIndex));
351     if (lastRunIndex != targetCluster.runIndex()) {
352         TextLine textLine;
353         textLine.setParagraphImpl(this);
354         auto blockRange = findAllBlocks(TextRange(textIndex, textIndex + 1));
355         textLine.setBlockRange(blockRange);
356         const SkString& ellipsis = this->getEllipsis();
357         std::unique_ptr<Run> ellipsisRun;
358         ellipsisRun = textLine.shapeEllipsis(ellipsis, &targetCluster);
359         lastRunIndex = targetCluster.runIndex();
360         ellipsisWidth = ellipsisRun->fAdvanceX();
361         ellipsisRun.reset();
362     }
363     return ellipsisWidth;
364 }
365 
366 void ParagraphImpl::scanRTLTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
367 {
368     size_t lastRunIndex = EMPTY_RUN;
369     auto runTimeEllipsisWidth = resetEllipsisWidth(0, lastRunIndex, 0);
370     float measureWidth = runTimeEllipsisWidth;
371     size_t left = 0;
372     size_t right = rawTextSize.size() - 1;
373     while (left < rawTextSize.size() && measureWidth < fOldMaxWidth && left <= right) {
374         measureWidth += rawTextSize[left++].phraseWidth;
375         if (right > left && measureWidth < fOldMaxWidth) {
376             measureWidth += rawTextSize[right--].phraseWidth;
377         }
378         measureWidth -= runTimeEllipsisWidth;
379         runTimeEllipsisWidth = resetEllipsisWidth(runTimeEllipsisWidth, lastRunIndex, left);
380         measureWidth += runTimeEllipsisWidth;
381     }
382 
383     if (right < left) {
384         right = left;
385     }
386 
387     if (measureWidth >= fOldMaxWidth || fParagraphStyle.getTextOverflower()) {
388         start = left;
389         end = right;
390     } else {
391         start = 0;
392         end = 0;
393     }
394 }
395 
396 void ParagraphImpl::scanLTRTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
397 {
398     size_t lastRunIndex = EMPTY_RUN;
399     auto runTimeEllipsisWidth = resetEllipsisWidth(0, lastRunIndex, 0);
400     float measureWidth = runTimeEllipsisWidth;
401     size_t begin = 0;
402     size_t last = rawTextSize.size() - 1;
403     bool rightExit = false;
404     while (begin < last && !rightExit && measureWidth < fOldMaxWidth) {
405         measureWidth += rawTextSize[begin++].phraseWidth;
406         if (measureWidth > fOldMaxWidth) {
407             --begin;
408             break;
409         }
410         if (last > begin && measureWidth < fOldMaxWidth) {
411             measureWidth += rawTextSize[last--].phraseWidth;
412             if (measureWidth > fOldMaxWidth) {
413                 rightExit = true;
414                 ++last;
415             }
416         }
417         measureWidth -= runTimeEllipsisWidth;
418         runTimeEllipsisWidth = resetEllipsisWidth(runTimeEllipsisWidth, lastRunIndex, begin);
419         measureWidth += runTimeEllipsisWidth;
420     }
421 
422     if (measureWidth >= fOldMaxWidth || fParagraphStyle.getTextOverflower()) {
423         start = begin;
424         end = last;
425     } else {
426         start = 0;
427         end = 0;
428     }
429 }
430 
431 void ParagraphImpl::scanTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
432 {
433     if (allTextWidth <= fOldMaxWidth || !rawTextSize.size()) {
434         allTextWidth = 0;
435         return;
436     }
437 
438     if (fRuns.begin()->leftToRight()) {
439         scanLTRTextCutPoint(rawTextSize, start, end);
440     } else {
441         scanRTLTextCutPoint(rawTextSize, start, end);
442     }
443 }
444 
445 bool ParagraphImpl::shapeForMiddleEllipsis(SkScalar rawWidth)
446 {
447     if (fParagraphStyle.getMaxLines() != 1 || fParagraphStyle.getEllipsisMod() != EllipsisModal::MIDDLE ||
448         !fParagraphStyle.ellipsized()) {
449         return true;
450     }
451     fOldMaxWidth = rawWidth;
452     isMiddleEllipsis = true;
453     allTextWidth = 0;
454     this->computeCodeUnitProperties();
455     this->fRuns.reset();
456     this->fClusters.reset();
457     this->fClustersIndexFromCodeUnit.reset();
458     this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
459     if (!this->shapeTextIntoEndlessLine()) {
460         return false;
461     }
462     return middleEllipsisDeal();
463 }
464 
465 void ParagraphImpl::prepareForMiddleEllipsis(SkScalar rawWidth)
466 {
467     if (fParagraphStyle.getMaxLines() != 1 || fParagraphStyle.getEllipsisMod() != EllipsisModal::MIDDLE ||
468         !fParagraphStyle.ellipsized()) {
469         return;
470     }
471     std::shared_ptr<ParagraphImpl> tmpParagraph = std::make_shared<ParagraphImpl>(fText, fParagraphStyle, fTextStyles,
472         fPlaceholders, fFontCollection, fUnicode);
473     if (tmpParagraph->shapeForMiddleEllipsis(rawWidth)) {
474         fText = tmpParagraph->fText;
475         fTextStyles = tmpParagraph->fTextStyles;
476         fPlaceholders = tmpParagraph->fPlaceholders;
477 #ifdef OHOS_SUPPORT
478         fEllipsisRange = tmpParagraph->fEllipsisRange;
479 #endif
480     }
481 }
482 
483 void ParagraphImpl::layout(SkScalar rawWidth) {
484 #ifdef OHOS_SUPPORT
485     TEXT_TRACE_FUNC();
486 #endif
487     fLineNumber = 1;
488     allTextWidth = 0;
489     fLayoutRawWidth = rawWidth;
490     // TODO: This rounding is done to match Flutter tests. Must be removed...
491     auto floorWidth = rawWidth;
492 
493     if (fParagraphStyle.getMaxLines() == 1 &&
494         fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE) {
495         fOldMaxWidth = rawWidth;
496         isMiddleEllipsis = true;
497     }
498     if (getApplyRoundingHack()) {
499         floorWidth = SkScalarFloorToScalar(floorWidth);
500     }
501 
502 #ifdef OHOS_SUPPORT
503     fPaintRegion.reset();
504     bool isMaxLinesZero = false;
505     if (fParagraphStyle.getMaxLines() == 0) {
506         if (fText.size() != 0) {
507             isMaxLinesZero = true;
508         }
509         fText.reset();
510     }
511 #endif
512 
513     if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
514         fState >= kLineBroken &&
515          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
516         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
517         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
518         fWidth = floorWidth;
519         fState = kShaped;
520     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
521         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
522         fState = kShaped;
523     } else {
524         // Nothing changed case: we can reuse the data from the last layout
525     }
526 
527     this->prepareForMiddleEllipsis(rawWidth);
528     this->fUnicodeText = convertUtf8ToUnicode(fText);
529     auto paragraphCache = fFontCollection->getParagraphCache();
530 
531     if (fState < kShaped) {
532         // Check if we have the text in the cache and don't need to shape it again
533         if (!paragraphCache->findParagraph(this)) {
534             if (fState < kIndexed) {
535                 // This only happens once at the first layout; the text is immutable
536                 // and there is no reason to repeat it
537                 if (this->computeCodeUnitProperties()) {
538                     fState = kIndexed;
539                 }
540             }
541             this->fRuns.reset();
542             this->fClusters.reset();
543             this->fClustersIndexFromCodeUnit.reset();
544             this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
545             if (!this->shapeTextIntoEndlessLine()) {
546                 this->resetContext();
547 
548 #ifdef OHOS_SUPPORT
549                 if (isMaxLinesZero) {
550                     fExceededMaxLines  = true;
551                 }
552 #endif
553                 // TODO: merge the two next calls - they always come together
554                 this->resolveStrut();
555                 this->computeEmptyMetrics();
556                 this->fLines.reset();
557 
558                 // Set the important values that are not zero
559                 fWidth = floorWidth;
560                 fHeight = fEmptyMetrics.height();
561                 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
562                     fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
563                     fHeight = fStrutMetrics.height();
564                 }
565                 if (fParagraphStyle.getMaxLines() == 0) {
566                     fHeight = 0;
567                 }
568                 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
569                 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
570                 fLongestLine = FLT_MIN - FLT_MAX;  // That is what flutter has
571                 fMinIntrinsicWidth = 0;
572                 fMaxIntrinsicWidth = 0;
573                 this->fOldWidth = floorWidth;
574                 this->fOldHeight = this->fHeight;
575 
576                 return;
577             } else if (!(fParagraphStyle.getMaxLines() == 1 &&
578                 fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE)) {
579                 // Add the paragraph to the cache
580                 paragraphCache->updateParagraph(this);
581             }
582         }
583         fState = kShaped;
584     }
585 
586     if (fState == kShaped) {
587         this->resetContext();
588         this->resolveStrut();
589         this->computeEmptyMetrics();
590         this->fLines.reset();
591 #ifdef OHOS_SUPPORT
592         // fast path
593         if (!fHasLineBreaks &&
594             !fHasWhitespacesInside &&
595             fPlaceholders.size() == 1 &&
596             (fRuns.size() == 1 && fRuns[0].fAdvance.fX <= floorWidth - this->detectIndents(0))) {
597             positionShapedTextIntoLine(floorWidth);
598         } else if (!paragraphCache->GetStoredLayout(*this)) {
599             breakShapedTextIntoLines(floorWidth);
600             // text breaking did not go to fast path and we did not have cached layout
601             paragraphCache->SetStoredLayout(*this);
602         }
603 #else
604         this->breakShapedTextIntoLines(floorWidth);
605 #endif
606         fState = kLineBroken;
607     }
608 
609     if (fState == kLineBroken) {
610         // Build the picture lazily not until we actually have to paint (or never)
611         this->resetShifts();
612         this->formatLines(fWidth);
613         fState = kFormatted;
614     }
615 
616     if (fParagraphStyle.getMaxLines() == 0) {
617         fHeight = 0;
618     }
619 
620     this->fOldWidth = floorWidth;
621     this->fOldHeight = this->fHeight;
622 
623     if (getApplyRoundingHack()) {
624         // TODO: This rounding is done to match Flutter tests. Must be removed...
625         fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
626         fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
627     }
628 
629     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
630     if (fParagraphStyle.getMaxLines() == 1 ||
631         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
632         fMinIntrinsicWidth = fMaxIntrinsicWidth;
633     }
634 
635     // TODO: Since min and max are calculated differently it's possible to get a rounding error
636     //  that would make min > max. Sort it out later, make it the same for now
637     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
638         fMaxIntrinsicWidth = fMinIntrinsicWidth;
639     }
640     if (fParagraphStyle.getMaxLines() == 0) {
641         fLineNumber = 0;
642     } else {
643         fLineNumber = std::max(size_t(1), fLines.size());
644     }
645     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
646 }
647 
648 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
649     CanvasParagraphPainter painter(canvas);
650     paint(&painter, x, y);
651 }
652 
653 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
654     for (auto& line : fLines) {
655         line.paint(painter, x, y);
656     }
657 }
658 
659 void ParagraphImpl::paint(ParagraphPainter* painter, RSPath* path, SkScalar hOffset, SkScalar vOffset) {
660     auto& style = fTextStyles[0].fStyle;
661     float align = 0.0f;
662     switch (paragraphStyle().getTextAlign()) {
663         case TextAlign::kCenter:
664             align = -0.5f;
665             break;
666         case TextAlign::kRight:
667             align = -1.0f;
668             break;
669         default:
670             break;
671     }
672     hOffset += align * (fMaxIntrinsicWidth - style.getLetterSpacing() - path->GetLength(false));
673     for (auto& line : fLines) {
674         line.paint(painter, path, hOffset, vOffset);
675     }
676 }
677 
678 #ifdef OHOS_SUPPORT
679 TextRange ParagraphImpl::getEllipsisTextRange() {
680     if (fState < kLineBroken) {
681         return EMPTY_RANGE;
682     }
683     if (!fEllipsisRange.empty()) {
684         return fEllipsisRange;
685     }
686     this->ensureUTF16Mapping();
687     for (const auto& line: fLines) {
688         if (line.getTextRangeReplacedByEllipsis().empty()) {
689             continue;
690         }
691         auto ellipsisClusterRange = line.getTextRangeReplacedByEllipsis();
692         return TextRange(getUTF16Index(ellipsisClusterRange.start),
693                                       getUTF16Index(ellipsisClusterRange.end));
694     }
695     return EMPTY_RANGE;
696 }
697 #endif
698 
699 void ParagraphImpl::resetContext() {
700     fAlphabeticBaseline = 0;
701     fHeight = 0;
702     fWidth = 0;
703     fIdeographicBaseline = 0;
704     fMaxIntrinsicWidth = 0;
705     fMinIntrinsicWidth = 0;
706     fLongestLine = 0;
707 #ifdef OHOS_SUPPORT
708     fLongestLineWithIndent = 0;
709 #endif
710     fMaxWidthWithTrailingSpaces = 0;
711     fExceededMaxLines = false;
712 }
713 
714 // shapeTextIntoEndlessLine is the thing that calls this method
715 bool ParagraphImpl::computeCodeUnitProperties() {
716 #ifdef OHOS_SUPPORT
717     TEXT_TRACE_FUNC();
718 #endif
719     if (nullptr == fUnicode) {
720         return false;
721     }
722 
723     // Get bidi regions
724     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
725                               ? SkUnicode::TextDirection::kLTR
726                               : SkUnicode::TextDirection::kRTL;
727     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
728         return false;
729     }
730 
731     // Collect all spaces and some extra information
732     // (and also substitute \t with a space while we are at it)
733     if (!fUnicode->computeCodeUnitFlags(&fText[0],
734                                         fText.size(),
735 #ifdef OHOS_SUPPORT
736                                         this->paragraphStyle().getReplaceTabCharacters() ||
737                                         (!(this->paragraphStyle().getTextTab().location < 1.0)),
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 (size_t i = 0; i < fCodeUnitProperties.size(); ++i) {
749         auto flags = fCodeUnitProperties[i];
750         if (SkUnicode::isPartOfWhiteSpaceBreak(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::isHardLineBreak(flags)) {
761             fHasLineBreaks = true;
762         }
763     }
764 
765     if (firstWhitespace < fTrailingSpaces) {
766         fHasWhitespacesInside = true;
767     }
768 
769     return true;
770 }
771 
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 OHOS_SUPPORT
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),
819     SkRange<SkUnichar>(0x0041, 0x005A),
820     SkRange<SkUnichar>(0x0061, 0x007A),
821 };
822 
823 constexpr SkUnichar COPYRIGHT_UNICODE = 0x00A9;
824 
825 struct UnicodeIdentifier {
826     static bool cmp(SkRange<SkUnichar> a, SkRange<SkUnichar> b) {
827         return a.start < b.start;
828     }
829     const std::vector<SkRange<SkUnichar>>& fUnicodeSet;
830     explicit UnicodeIdentifier(const std::vector<SkRange<SkUnichar>>& unicodeSet) : fUnicodeSet(unicodeSet) {}
831     bool exist(SkUnichar c) const {
832         if (!TextParameter::GetAutoSpacingEnable()) {
833             return false;
834         }
835         auto pos = std::upper_bound(fUnicodeSet.begin(), fUnicodeSet.end(), SkRange<SkUnichar>(c, c), cmp);
836         if (pos == fUnicodeSet.begin()) {
837             return false;
838         }
839         --pos;
840         return pos->end >= c;
841     }
842 };
843 
844 static const UnicodeIdentifier CJK_IDENTIFIER(CJK_UNICODE_SET);
845 static const UnicodeIdentifier WESTERN_IDENTIFIER(WESTERN_UNICODE_SET);
846 
847 static Cluster::AutoSpacingFlag recognizeUnicodeAutoSpacingFlag(SkUnichar unicode)
848 {
849     if (WESTERN_IDENTIFIER.exist(unicode)) {
850         return Cluster::AutoSpacingFlag::Western;
851     }
852 
853     if (CJK_IDENTIFIER.exist(unicode)) {
854         return Cluster::AutoSpacingFlag::CJK;
855     }
856 
857     if (unicode == COPYRIGHT_UNICODE) {
858         return Cluster::AutoSpacingFlag::Copyright;
859     }
860 
861     return Cluster::AutoSpacingFlag::NoFlag;
862 }
863 #endif
864 Cluster::Cluster(ParagraphImpl* owner,
865                  RunIndex runIndex,
866                  size_t start,
867                  size_t end,
868                  SkSpan<const char> text,
869                  SkScalar width,
870                  SkScalar height)
871         : fOwner(owner)
872         , fRunIndex(runIndex)
873         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
874         , fGraphemeRange(EMPTY_RANGE)
875         , fStart(start)
876         , fEnd(end)
877         , fWidth(width)
878         , fHeight(height)
879         , fHalfLetterSpacing(0.0)
880         , fIsIdeographic(false) {
881     size_t whiteSpacesBreakLen = 0;
882     size_t intraWordBreakLen = 0;
883 
884     const char* ch = text.begin();
885     if (text.end() - ch == 1 && *(unsigned char*)ch <= 0x7F) {
886         // I am not even sure it's worth it if we do not save a unicode call
887         if (is_ascii_7bit_space(*ch)) {
888             ++whiteSpacesBreakLen;
889         }
890 #ifdef OHOS_SUPPORT
891         fIsPunctuation = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kPunctuation);
892         fIsEllipsis = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kEllipsis);
893 #endif
894     } else {
895         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
896             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
897                 ++whiteSpacesBreakLen;
898             }
899             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
900                 ++intraWordBreakLen;
901             }
902             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kIdeographic)) {
903                 fIsIdeographic = true;
904             }
905 #ifdef OHOS_SUPPORT
906             fIsPunctuation = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPunctuation) | fIsPunctuation;
907             fIsEllipsis = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kEllipsis) | fIsEllipsis;
908 #endif
909         }
910     }
911 
912     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
913     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
914     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
915                                                SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
916 #ifdef OHOS_SUPPORT
917     fIsTabulation = fOwner->codeUnitHasProperty(fTextRange.start,
918                                                 SkUnicode::CodeUnitFlags::kTabulation);
919     auto unicodeStart = fOwner->getUnicodeIndex(fTextRange.start);
920     auto unicodeEnd = fOwner->getUnicodeIndex(fTextRange.end);
921     SkUnichar unicode = 0;
922     if (unicodeEnd - unicodeStart == 1 && unicodeStart < fOwner->unicodeText().size()) {
923         unicode = fOwner->unicodeText()[unicodeStart];
924     }
925 
926     auto curAutoSpacingFlag = recognizeUnicodeAutoSpacingFlag(unicode);
927     auto lastAutoSpacingFlag = fOwner->getLastAutoSpacingFlag();
928     fNeedAutoSpacing = curAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag &&
929         curAutoSpacingFlag != lastAutoSpacingFlag && lastAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag;
930     fOwner->setLastAutoSpacingFlag(curAutoSpacingFlag);
931 #endif
932 }
933 
934 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
935     SkASSERT(start <= end);
936     // clip |= end == size();  // Clip at the end of the run?
937     auto correction = 0.0f;
938     if (end > start && !fJustificationShifts.empty()) {
939         // This is not a typo: we are using Point as a pair of SkScalars
940         correction = fJustificationShifts[end - 1].fX -
941                      fJustificationShifts[start].fY;
942     }
943     if (end > start && !fAutoSpacings.empty()) {
944         // This is not a tyopo: we are using Point as a pair of SkScalars
945         correction += fAutoSpacings[end - 1].fX - fAutoSpacings[start].fY;
946     }
947     return posX(end) - posX(start) + correction;
948 }
949 
950 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
951 // the opposite - just to optimize the most common case.
952 void ParagraphImpl::applySpacingAndBuildClusterTable() {
953 
954     // Check all text styles to see what we have to do (if anything)
955     size_t letterSpacingStyles = 0;
956     bool hasWordSpacing = false;
957     for (auto& block : fTextStyles) {
958         if (block.fRange.width() > 0) {
959             if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
960                 ++letterSpacingStyles;
961             }
962             if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
963                 hasWordSpacing = true;
964             }
965         }
966     }
967 
968     if (letterSpacingStyles == 0 && !hasWordSpacing) {
969         // We don't have to do anything about spacing (most common case)
970         this->buildClusterTable();
971         return;
972     }
973 
974     if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
975         fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
976         // We have to letter space the entire paragraph (second most common case)
977         auto& run = fRuns[0];
978         auto& style = fTextStyles[0].fStyle;
979         this->buildClusterTable();
980         SkScalar shift = 0;
981         run.iterateThroughClusters([this, &run, &shift, &style](Cluster* cluster) {
982             run.shift(cluster, shift);
983             shift += run.addSpacesEvenly(style.getLetterSpacing(), cluster);
984         });
985         return;
986     }
987 
988     // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
989     this->buildClusterTable();
990 
991     // Walk through all the clusters in the direction of shaped text
992     // (we have to walk through the styles in the same order, too)
993     SkScalar shift = 0;
994     for (auto& run : fRuns) {
995 
996         // Skip placeholder runs
997         if (run.isPlaceholder()) {
998             continue;
999         }
1000         bool soFarWhitespacesOnly = true;
1001         bool wordSpacingPending = false;
1002         Cluster* lastSpaceCluster = nullptr;
1003         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
1004             // Shift the cluster (shift collected from the previous clusters)
1005             run.shift(cluster, shift);
1006 
1007             // Synchronize styles (one cluster can be covered by few styles)
1008             Block* currentStyle = fTextStyles.begin();
1009             while (!cluster->startsIn(currentStyle->fRange)) {
1010                 currentStyle++;
1011                 SkASSERT(currentStyle != fTextStyles.end());
1012             }
1013 
1014             SkASSERT(!currentStyle->fStyle.isPlaceholder());
1015 
1016             // Process word spacing
1017             if (currentStyle->fStyle.getWordSpacing() != 0) {
1018                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
1019                     if (!soFarWhitespacesOnly) {
1020                         lastSpaceCluster = cluster;
1021                         wordSpacingPending = true;
1022                     }
1023                 } else if (wordSpacingPending) {
1024                     SkScalar spacing = currentStyle->fStyle.getWordSpacing();
1025                     run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
1026                     run.shift(cluster, spacing);
1027                     shift += spacing;
1028                     wordSpacingPending = false;
1029                 }
1030             }
1031             // Process letter spacing
1032             if (currentStyle->fStyle.getLetterSpacing() != 0) {
1033                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
1034             }
1035 
1036             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
1037                 soFarWhitespacesOnly = false;
1038             }
1039         });
1040     }
1041 }
1042 
1043 void ParagraphImpl::middleEllipsisAddText(size_t charStart,
1044                                           size_t charEnd,
1045                                           SkScalar& allTextWidth,
1046                                           SkScalar width,
1047                                           bool isLeftToRight) {
1048     if (isMiddleEllipsis) {
1049         TextCutRecord textCount;
1050         textCount.charbegin = charStart;
1051         textCount.charOver = charEnd;
1052         textCount.phraseWidth = width;
1053         allTextWidth += width;
1054         if (isLeftToRight) {
1055             this->ltrTextSize.emplace_back(textCount);
1056         } else {
1057             this->rtlTextSize.emplace_back(textCount);
1058         }
1059     }
1060 }
1061 
1062 // Clusters in the order of the input text
1063 void ParagraphImpl::buildClusterTable() {
1064     // It's possible that one grapheme includes few runs; we cannot handle it
1065     // so we break graphemes by the runs instead
1066     // It's not the ideal solution and has to be revisited later
1067     size_t cluster_count = 1;
1068     for (auto& run : fRuns) {
1069         cluster_count += run.isPlaceholder() ? 1 : run.size();
1070         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1071         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1072     }
1073     if (!fRuns.empty()) {
1074         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1075         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1076     }
1077     fClusters.reserve_back(fClusters.size() + cluster_count);
1078 
1079     // Walk through all the run in the direction of input text
1080     for (auto& run : fRuns) {
1081         auto runIndex = run.index();
1082         auto runStart = fClusters.size();
1083         if (run.isPlaceholder()) {
1084             // Add info to cluster indexes table (text -> cluster)
1085             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
1086               fClustersIndexFromCodeUnit[i] = fClusters.size();
1087             }
1088             // There are no glyphs but we want to have one cluster
1089             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
1090             fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1091             fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1092         } else {
1093             // Walk through the glyph in the direction of input text
1094             run.iterateThroughClustersInTextOrder([&run, runIndex, this](size_t glyphStart,
1095                                                                    size_t glyphEnd,
1096                                                                    size_t charStart,
1097                                                                    size_t charEnd,
1098                                                                    SkScalar width,
1099                                                                    SkScalar height) {
1100                 SkASSERT(charEnd >= charStart);
1101                 // Add info to cluster indexes table (text -> cluster)
1102                 for (auto i = charStart; i < charEnd; ++i) {
1103                   fClustersIndexFromCodeUnit[i] = fClusters.size();
1104                 }
1105 
1106                 middleEllipsisAddText(charStart, charEnd, allTextWidth, width, run.leftToRight());
1107                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
1108                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
1109                 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1110             });
1111         }
1112         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1113 
1114         run.setClusterRange(runStart, fClusters.size());
1115         fMaxIntrinsicWidth += run.advance().fX;
1116     }
1117     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
1118     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
1119 }
1120 
1121 bool ParagraphImpl::shapeTextIntoEndlessLine() {
1122 #ifdef OHOS_SUPPORT
1123     TEXT_TRACE_FUNC();
1124 #endif
1125     if (fText.size() == 0) {
1126         return false;
1127     }
1128 
1129     fUnresolvedCodepoints.clear();
1130     fFontSwitches.reset();
1131 
1132     OneLineShaper oneLineShaper(this);
1133     auto result = oneLineShaper.shape();
1134     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
1135 
1136     this->applySpacingAndBuildClusterTable();
1137 
1138     return result;
1139 }
1140 
1141 void ParagraphImpl::setIndents(const std::vector<SkScalar>& indents)
1142 {
1143     fIndents = indents;
1144 }
1145 
1146 SkScalar ParagraphImpl::detectIndents(size_t index)
1147 {
1148     SkScalar indent = 0.0;
1149     if (fIndents.size() > 0 && index < fIndents.size()) {
1150         indent = fIndents[index];
1151     } else {
1152         indent = fIndents.size() > 0 ? fIndents.back() : 0.0;
1153     }
1154 
1155     return indent;
1156 }
1157 
1158 #ifdef OHOS_SUPPORT
1159 void ParagraphImpl::positionShapedTextIntoLine(SkScalar maxWidth) {
1160     resetAutoSpacing();
1161     // This is a short version of a line breaking when we know that:
1162     // 1. We have only one line of text
1163     // 2. It's shaped into a single run
1164     // 3. There are no placeholders
1165     // 4. There are no linebreaks (which will format text into multiple lines)
1166     // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1167     // (To think about that, the last condition is not quite right;
1168     // we should calculate minIntrinsicWidth by soft line breaks.
1169     // However, it's how it's done in Flutter now)
1170     auto& run = this->fRuns[0];
1171     auto advance = run.advance();
1172     auto textRange = TextRange(0, this->text().size());
1173     auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1174     InternalLineMetrics metrics(this->strutForceHeight());
1175     metrics.add(&run);
1176     auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
1177     auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
1178     if (disableFirstAscent) {
1179         metrics.fAscent = metrics.fRawAscent;
1180     }
1181     if (disableLastDescent) {
1182         metrics.fDescent = metrics.fRawDescent;
1183     }
1184     if (this->strutEnabled()) {
1185         this->strutMetrics().updateLineMetrics(metrics);
1186     }
1187     ClusterIndex trailingSpaces = fClusters.size();
1188     do {
1189         --trailingSpaces;
1190         auto& cluster = fClusters[trailingSpaces];
1191         if (!cluster.isWhitespaceBreak()) {
1192             ++trailingSpaces;
1193             break;
1194         }
1195         advance.fX -= cluster.width();
1196     } while (trailingSpaces != 0);
1197 
1198     advance.fY = metrics.height();
1199     SkScalar heightWithParagraphSpacing = advance.fY;
1200     if (this->paragraphStyle().getIsEndAddParagraphSpacing() &&
1201         this->paragraphStyle().getParagraphSpacing() > 0) {
1202         heightWithParagraphSpacing += this->paragraphStyle().getParagraphSpacing();
1203     }
1204     auto clusterRange = ClusterRange(0, trailingSpaces);
1205     auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1206     SkScalar offsetX = this->detectIndents(0);
1207     auto& line = this->addLine(SkPoint::Make(offsetX, 0), advance, textExcludingSpaces, textRange, textRange,
1208         clusterRange, clusterRangeWithGhosts, run.advance().x(), metrics);
1209     auto spacing = line.autoSpacing();
1210     auto longestLine = std::max(run.advance().fX, advance.fX) + spacing;
1211     setSize(heightWithParagraphSpacing, maxWidth, longestLine);
1212     setLongestLineWithIndent(longestLine + offsetX);
1213     setIntrinsicSize(run.advance().fX, advance.fX,
1214         fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1215         fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1216         false);
1217 }
1218 
1219 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1220 #ifdef OHOS_SUPPORT
1221     TEXT_TRACE_FUNC();
1222 #endif
1223     resetAutoSpacing();
1224     TextWrapper textWrapper;
1225     textWrapper.breakTextIntoLines(
1226             this,
1227             maxWidth,
1228             [&](TextRange textExcludingSpaces,
1229                 TextRange text,
1230                 TextRange textWithNewlines,
1231                 ClusterRange clusters,
1232                 ClusterRange clustersWithGhosts,
1233                 SkScalar widthWithSpaces,
1234                 size_t startPos,
1235                 size_t endPos,
1236                 SkVector offset,
1237                 SkVector advance,
1238                 InternalLineMetrics metrics,
1239                 bool addEllipsis,
1240                 SkScalar indent,
1241                 SkScalar noIndentWidth) {
1242                 // TODO: Take in account clipped edges
1243                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines,
1244                     clusters, clustersWithGhosts, widthWithSpaces, metrics);
1245                 if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::TAIL) {
1246                     line.createTailEllipsis(noIndentWidth, this->getEllipsis(), true, this->getWordBreakType());
1247                 } else if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD) {
1248                     line.createHeadEllipsis(noIndentWidth, this->getEllipsis(), true);
1249                 }
1250 #ifdef OHOS_SUPPORT
1251                 else if (textWrapper.brokeLineWithHyphen()
1252                          || ((clusters.end == clustersWithGhosts.end) && (clusters.end >= 1)
1253                              && (clusters.end < this->fUnicodeText.size())
1254                              && (this->fUnicodeText[clusters.end - 1] == 0xad))) { // 0xad represents a soft hyphen
1255                     line.setBreakWithHyphen(true);
1256                 }
1257 #endif
1258                 auto spacing = line.autoSpacing();
1259                 auto longestLine = std::max(line.width(), line.widthWithEllipsisSpaces()) + spacing;
1260                 fLongestLine = std::max(fLongestLine, longestLine);
1261                 fLongestLineWithIndent = std::max(fLongestLineWithIndent, longestLine + indent);
1262             });
1263     setSize(textWrapper.height(), maxWidth, fLongestLine);
1264     setIntrinsicSize(textWrapper.maxIntrinsicWidth(), textWrapper.minIntrinsicWidth(),
1265         fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1266         fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1267         textWrapper.exceededMaxLines());
1268 }
1269 #else
1270 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1271 
1272     if (!fHasLineBreaks &&
1273         !fHasWhitespacesInside &&
1274         fPlaceholders.size() == 1 &&
1275         fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
1276         // This is a short version of a line breaking when we know that:
1277         // 1. We have only one line of text
1278         // 2. It's shaped into a single run
1279         // 3. There are no placeholders
1280         // 4. There are no linebreaks (which will format text into multiple lines)
1281         // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1282         // (To think about that, the last condition is not quite right;
1283         // we should calculate minIntrinsicWidth by soft line breaks.
1284         // However, it's how it's done in Flutter now)
1285         auto& run = this->fRuns[0];
1286         auto advance = run.advance();
1287         auto textRange = TextRange(0, this->text().size());
1288         auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1289         InternalLineMetrics metrics(this->strutForceHeight());
1290         metrics.add(&run);
1291         auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
1292                                   TextHeightBehavior::kDisableFirstAscent;
1293         auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
1294                                   TextHeightBehavior::kDisableLastDescent;
1295         if (disableFirstAscent) {
1296             metrics.fAscent = metrics.fRawAscent;
1297         }
1298         if (disableLastDescent) {
1299             metrics.fDescent = metrics.fRawDescent;
1300         }
1301         if (this->strutEnabled()) {
1302             this->strutMetrics().updateLineMetrics(metrics);
1303         }
1304         ClusterIndex trailingSpaces = fClusters.size();
1305         do {
1306             --trailingSpaces;
1307             auto& cluster = fClusters[trailingSpaces];
1308             if (!cluster.isWhitespaceBreak()) {
1309                 ++trailingSpaces;
1310                 break;
1311             }
1312             advance.fX -= cluster.width();
1313         } while (trailingSpaces != 0);
1314 
1315         advance.fY = metrics.height();
1316         auto clusterRange = ClusterRange(0, trailingSpaces);
1317         auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1318         this->addLine(SkPoint::Make(0, 0), advance,
1319                       textExcludingSpaces, textRange, textRange,
1320                       clusterRange, clusterRangeWithGhosts, run.advance().x(),
1321                       metrics);
1322 
1323         fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
1324         fHeight = advance.fY;
1325         fWidth = maxWidth;
1326         fMaxIntrinsicWidth = run.advance().fX;
1327         fMinIntrinsicWidth = advance.fX;
1328         fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1329         fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1330         fExceededMaxLines = false;
1331         return;
1332     }
1333 
1334     TextWrapper textWrapper;
1335     textWrapper.breakTextIntoLines(
1336             this,
1337             maxWidth,
1338             [&](TextRange textExcludingSpaces,
1339                 TextRange text,
1340                 TextRange textWithNewlines,
1341                 ClusterRange clusters,
1342                 ClusterRange clustersWithGhosts,
1343                 SkScalar widthWithSpaces,
1344                 size_t startPos,
1345                 size_t endPos,
1346                 SkVector offset,
1347                 SkVector advance,
1348                 InternalLineMetrics metrics,
1349                 bool addEllipsis) {
1350                 // TODO: Take in account clipped edges
1351                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
1352                 if (addEllipsis) {
1353                     line.createEllipsis(maxWidth, this->getEllipsis(), true);
1354                 }
1355                 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
1356             });
1357 
1358     fHeight = textWrapper.height();
1359     fWidth = maxWidth;
1360     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
1361     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
1362     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1363     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1364     fExceededMaxLines = textWrapper.exceededMaxLines();
1365 }
1366 #endif
1367 
1368 void ParagraphImpl::formatLines(SkScalar maxWidth) {
1369 #ifdef OHOS_SUPPORT
1370     TEXT_TRACE_FUNC();
1371 #endif
1372     auto effectiveAlign = fParagraphStyle.effective_align();
1373     const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
1374         || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
1375 
1376     if (!SkScalarIsFinite(maxWidth) && !isLeftAligned) {
1377         // Special case: clean all text in case of maxWidth == INF & align != left
1378         // We had to go through shaping though because we need all the measurement numbers
1379         fLines.reset();
1380         return;
1381     }
1382 
1383     size_t iLineNumber = 0;
1384     for (auto& line : fLines) {
1385         SkScalar noIndentWidth = maxWidth - detectIndents(iLineNumber++);
1386         if (fParagraphStyle.getTextDirection() == TextDirection::kRtl) {
1387             line.setLineOffsetX(0);
1388         }
1389         line.format(effectiveAlign, noIndentWidth, this->paragraphStyle().getEllipsisMod());
1390 #ifdef OHOS_SUPPORT
1391         line.updateTextLinePaintAttributes();
1392 #endif
1393     }
1394 }
1395 
1396 void ParagraphImpl::resolveStrut() {
1397     auto strutStyle = this->paragraphStyle().getStrutStyle();
1398     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
1399         return;
1400     }
1401 
1402     auto typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle(), std::nullopt);
1403     if (typefaces.empty()) {
1404         SkDEBUGF("Could not resolve strut font\n");
1405         return;
1406     }
1407 
1408 #ifndef USE_SKIA_TXT
1409     SkFont font(typefaces.front(), strutStyle.getFontSize());
1410     SkFontMetrics metrics;
1411 #ifdef OHOS_SUPPORT
1412     SkFont compressFont = font;
1413     scaleFontWithCompressionConfig(compressFont, ScaleOP::COMPRESS);
1414     compressFont.getMetrics(&metrics);
1415     metricsIncludeFontPadding(&metrics, font);
1416 #else
1417     font.getMetrics(&metrics);
1418 #endif
1419 #else
1420     RSFont font(typefaces.front(), strutStyle.getFontSize(), 1, 0);
1421     RSFontMetrics metrics;
1422 #ifdef OHOS_SUPPORT
1423     RSFont compressFont = font;
1424     scaleFontWithCompressionConfig(compressFont, ScaleOP::COMPRESS);
1425     compressFont.GetMetrics(&metrics);
1426     metricsIncludeFontPadding(&metrics, font);
1427 #else
1428     font.GetMetrics(&metrics);
1429 #endif
1430 #endif
1431 
1432     if (strutStyle.getHeightOverride()) {
1433         auto strutHeight = metrics.fDescent - metrics.fAscent;
1434         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
1435         fStrutMetrics = InternalLineMetrics(
1436             (metrics.fAscent / strutHeight) * strutMultiplier,
1437             (metrics.fDescent / strutHeight) * strutMultiplier,
1438                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
1439             metrics.fAscent, metrics.fDescent, metrics.fLeading);
1440     } else {
1441         fStrutMetrics = InternalLineMetrics(
1442                 metrics.fAscent,
1443                 metrics.fDescent,
1444                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
1445     }
1446     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
1447 }
1448 
1449 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
1450     BlockIndex begin = EMPTY_BLOCK;
1451     BlockIndex end = EMPTY_BLOCK;
1452     for (BlockIndex index = 0; index < fTextStyles.size(); ++index) {
1453         auto& block = fTextStyles[index];
1454         if (block.fRange.end <= textRange.start) {
1455             continue;
1456         }
1457         if (block.fRange.start >= textRange.end) {
1458             break;
1459         }
1460         if (begin == EMPTY_BLOCK) {
1461             begin = index;
1462         }
1463         end = index;
1464     }
1465 
1466     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
1467         // It's possible if some text is not covered with any text style
1468         // Not in Flutter but in direct use of SkParagraph
1469         return EMPTY_RANGE;
1470     }
1471 
1472     return { begin, end + 1 };
1473 }
1474 
1475 TextLine& ParagraphImpl::addLine(SkVector offset,
1476                                  SkVector advance,
1477                                  TextRange textExcludingSpaces,
1478                                  TextRange text,
1479                                  TextRange textIncludingNewLines,
1480                                  ClusterRange clusters,
1481                                  ClusterRange clustersWithGhosts,
1482                                  SkScalar widthWithSpaces,
1483                                  InternalLineMetrics sizes) {
1484     // Define a list of styles that covers the line
1485     auto blocks = findAllBlocks(textIncludingNewLines);
1486     return fLines.emplace_back(this, offset, advance, blocks,
1487                                textExcludingSpaces, text, textIncludingNewLines,
1488                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
1489 }
1490 
1491 #ifdef OHOS_SUPPORT
1492 TextBox ParagraphImpl::getEmptyTextRect(RectHeightStyle rectHeightStyle) const
1493 {
1494     // get textStyle to calculate text box when text is empty(reference to ParagraphImpl::computeEmptyMetrics())
1495     bool emptyParagraph = fRuns.empty();
1496     TextStyle textStyle = paragraphStyle().getTextStyle();
1497     if (emptyParagraph && !fTextStyles.empty()) {
1498         textStyle = fTextStyles.back().fStyle;
1499     }
1500 
1501     // calculate text box(reference to TextLine::getRectsForRange(), switch(rectHeightStyle))
1502     TextBox box(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1503     auto verticalShift = fEmptyMetrics.rawAscent() - fEmptyMetrics.ascent();
1504     switch (rectHeightStyle) {
1505         case RectHeightStyle::kTight:
1506             if (textStyle.getHeightOverride() && textStyle.getHeight() > 0) {
1507                 const auto effectiveBaseline = fEmptyMetrics.baseline() + fEmptyMetrics.delta();
1508                 box.rect.fTop = effectiveBaseline + fEmptyMetrics.rawAscent();
1509                 box.rect.fBottom = effectiveBaseline + fEmptyMetrics.rawDescent();
1510             }
1511             return box;
1512         case RectHeightStyle::kMax:
1513             box.rect.fBottom = fHeight;
1514             box.rect.fTop = fEmptyMetrics.delta();
1515             return box;
1516         case RectHeightStyle::kIncludeLineSpacingMiddle:
1517         case RectHeightStyle::kIncludeLineSpacingTop:
1518         case RectHeightStyle::kIncludeLineSpacingBottom:
1519             box.rect.fBottom = fHeight;
1520             box.rect.fTop = fEmptyMetrics.delta() + verticalShift;
1521             return box;
1522         case RectHeightStyle::kStrut:
1523             if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
1524                 fParagraphStyle.getStrutStyle().getFontSize() > 0) {
1525                 auto baseline = fEmptyMetrics.baseline();
1526                 box.rect.fTop = baseline + fStrutMetrics.ascent();
1527                 box.rect.fBottom = baseline + fStrutMetrics.descent();
1528             }
1529             return box;
1530         default:
1531             return box;
1532     }
1533 }
1534 #endif
1535 
1536 // Returns a vector of bounding boxes that enclose all text between
1537 // start and end glyph indexes, including start and excluding end
1538 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
1539                                                      unsigned end,
1540                                                      RectHeightStyle rectHeightStyle,
1541                                                      RectWidthStyle rectWidthStyle) {
1542     std::vector<TextBox> results;
1543     // this method should not be called before kshaped
1544     if (fText.isEmpty() || fState < kShaped) {
1545         if (start == 0 && end > 0) {
1546             // On account of implied "\n" that is always at the end of the text
1547             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
1548 #ifdef OHOS_SUPPORT
1549             results.emplace_back(getEmptyTextRect(rectHeightStyle));
1550 #else
1551             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1552 #endif
1553         }
1554         return results;
1555     }
1556 
1557     this->ensureUTF16Mapping();
1558 
1559     if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
1560         return results;
1561     }
1562 
1563     // Adjust the text to grapheme edges
1564     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
1565     // I don't know why - the solution I have here returns an empty box for every query that
1566     // does not contain an end of a grapheme.
1567     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
1568     // To avoid any problems, I will not allow any selection of a part of a grapheme.
1569     // One flutter test fails because of it but the editing experience is correct
1570     // (although you have to press the cursor many times before it moves to the next grapheme).
1571     TextRange text(fText.size(), fText.size());
1572     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
1573     //  (so we can compare the results). We now include in the selection box only the graphemes
1574     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
1575     if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1576         auto utf8 = fUTF8IndexForUTF16Index[start];
1577         // If start points to a trailing surrogate, skip it
1578         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
1579             utf8 = fUTF8IndexForUTF16Index[start + 1];
1580         }
1581         text.start = this->findNextGraphemeBoundary(utf8);
1582     }
1583     if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1584         auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
1585         text.end = utf8;
1586     }
1587     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
1588     for (auto& line : fLines) {
1589         auto lineText = line.textWithNewlines();
1590         lineText = textRangeMergeBtoA(lineText, line.getTextRangeReplacedByEllipsis());
1591         auto intersect = lineText * text;
1592         if (intersect.empty() && lineText.start != text.start) {
1593             continue;
1594         }
1595 
1596         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
1597     }
1598 /*
1599     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
1600     for (auto& r : results) {
1601         r.rect.fLeft = littleRound(r.rect.fLeft);
1602         r.rect.fRight = littleRound(r.rect.fRight);
1603         r.rect.fTop = littleRound(r.rect.fTop);
1604         r.rect.fBottom = littleRound(r.rect.fBottom);
1605         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
1606     }
1607 */
1608     return results;
1609 }
1610 
1611 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
1612   std::vector<TextBox> boxes;
1613   // this method should not be called before kshaped
1614   if (fText.isEmpty() || fState < kShaped) {
1615        return boxes;
1616   }
1617   if (fPlaceholders.size() == 1) {
1618        // We always have one fake placeholder
1619        return boxes;
1620   }
1621   for (auto& line : fLines) {
1622       line.getRectsForPlaceholders(boxes);
1623   }
1624   /*
1625   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
1626   for (auto& r : boxes) {
1627       r.rect.fLeft = littleRound(r.rect.fLeft);
1628       r.rect.fRight = littleRound(r.rect.fRight);
1629       r.rect.fTop = littleRound(r.rect.fTop);
1630       r.rect.fBottom = littleRound(r.rect.fBottom);
1631       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
1632                (r.direction == TextDirection::kLtr ? "left" : "right"));
1633   }
1634   */
1635   return boxes;
1636 }
1637 
1638 // TODO: Optimize (save cluster <-> codepoint connection)
1639 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
1640 
1641     if (fText.isEmpty()) {
1642         return {0, Affinity::kDownstream};
1643     }
1644 
1645     this->ensureUTF16Mapping();
1646 
1647     for (auto& line : fLines) {
1648         // Let's figure out if we can stop looking
1649         auto offsetY = line.offset().fY;
1650         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
1651             // This line is not good enough
1652             continue;
1653         }
1654 
1655         // This is so far the the line vertically closest to our coordinates
1656         // (or the first one, or the only one - all the same)
1657 
1658         auto result = line.getGlyphPositionAtCoordinate(dx);
1659         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
1660         //   result.affinity == Affinity::kUpstream ? "up" : "down");
1661         return result;
1662     }
1663 
1664     return {0, Affinity::kDownstream};
1665 }
1666 
1667 // Finds the first and last glyphs that define a word containing
1668 // the glyph at index offset.
1669 // By "glyph" they mean a character index - indicated by Minikin's code
1670 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
1671 
1672     if (fWords.empty()) {
1673         if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
1674             return {0, 0 };
1675         }
1676     }
1677 
1678     int32_t start = 0;
1679     int32_t end = 0;
1680     for (size_t i = 0; i < fWords.size(); ++i) {
1681         auto word = fWords[i];
1682         if (word <= offset) {
1683             start = word;
1684             end = word;
1685         } else if (word > offset) {
1686             end = word;
1687             break;
1688         }
1689     }
1690 
1691     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
1692     return { SkToU32(start), SkToU32(end) };
1693 }
1694 
1695 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
1696     metrics.clear();
1697     for (auto& line : fLines) {
1698         metrics.emplace_back(line.getMetrics());
1699     }
1700 }
1701 
1702 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
1703     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
1704     auto start = fText.c_str() + textRange.start;
1705     return SkSpan<const char>(start, textRange.width());
1706 }
1707 
1708 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
1709     SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
1710              clusterRange.end <= SkToSizeT(fClusters.size()));
1711     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
1712 }
1713 
1714 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
1715     SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
1716     return fClusters[clusterIndex];
1717 }
1718 
1719 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
1720     auto start = cluster(clusterIndex);
1721     return this->run(start.fRunIndex);
1722 }
1723 
1724 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
1725     SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
1726              blockRange.end <= SkToSizeT(fTextStyles.size()));
1727     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
1728 }
1729 
1730 Block& ParagraphImpl::block(BlockIndex blockIndex) {
1731     SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
1732     return fTextStyles[blockIndex];
1733 }
1734 
1735 void ParagraphImpl::setState(InternalState state) {
1736     if (fState <= state) {
1737         fState = state;
1738         return;
1739     }
1740 
1741     fState = state;
1742     switch (fState) {
1743         case kUnknown:
1744             SkASSERT(false);
1745             /*
1746             // The text is immutable and so are all the text indexing properties
1747             // taken from SkUnicode
1748             fCodeUnitProperties.reset();
1749             fWords.clear();
1750             fBidiRegions.clear();
1751             fUTF8IndexForUTF16Index.reset();
1752             fUTF16IndexForUTF8Index.reset();
1753             */
1754             [[fallthrough]];
1755 
1756         case kIndexed:
1757             fRuns.reset();
1758             fClusters.reset();
1759             [[fallthrough]];
1760 
1761         case kShaped:
1762             fLines.reset();
1763             [[fallthrough]];
1764 
1765         case kLineBroken:
1766             fPicture = nullptr;
1767             [[fallthrough]];
1768 
1769         default:
1770             break;
1771     }
1772 }
1773 
1774 void ParagraphImpl::computeEmptyMetrics() {
1775 
1776     // The empty metrics is used to define the height of the empty lines
1777     // Unfortunately, Flutter has 2 different cases for that:
1778     // 1. An empty line inside the text
1779     // 2. An empty paragraph
1780     // In the first case SkParagraph takes the metrics from the default paragraph style
1781     // In the second case it should take it from the current text style
1782     bool emptyParagraph = fRuns.empty();
1783     TextStyle textStyle = paragraphStyle().getTextStyle();
1784     if (emptyParagraph && !fTextStyles.empty()) {
1785         textStyle = fTextStyles.back().fStyle;
1786     }
1787 
1788     auto typefaces = fontCollection()->findTypefaces(
1789       textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1790     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
1791 
1792 #ifndef USE_SKIA_TXT
1793     SkFont font(typeface, textStyle.getFontSize());
1794 #else
1795     RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1796 #endif
1797     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
1798 
1799     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
1800         textStyle.getHeightOverride()) {
1801 #ifdef OHOS_SUPPORT
1802         const auto intrinsicHeight = fEmptyMetrics.fDescent - fEmptyMetrics.fAscent + fEmptyMetrics.fLeading;
1803 #else
1804         const auto intrinsicHeight = fEmptyMetrics.height();
1805 #endif
1806         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
1807         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
1808             fEmptyMetrics.update(
1809                 fEmptyMetrics.ascent(),
1810                 fEmptyMetrics.descent(),
1811                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
1812         } else {
1813             const auto multiplier = strutHeight / intrinsicHeight;
1814             fEmptyMetrics.update(
1815                 fEmptyMetrics.ascent() * multiplier,
1816                 fEmptyMetrics.descent() * multiplier,
1817                 fEmptyMetrics.leading() * multiplier);
1818         }
1819     }
1820 
1821     if (emptyParagraph) {
1822         // For an empty text we apply both TextHeightBehaviour flags
1823         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
1824         // We have to do it here because we skip wrapping for an empty text
1825         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
1826         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
1827         fEmptyMetrics.update(
1828             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1829             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1830             fEmptyMetrics.leading());
1831     }
1832 
1833     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1834         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1835     }
1836 }
1837 
1838 SkString ParagraphImpl::getEllipsis() const {
1839 
1840     auto ellipsis8 = fParagraphStyle.getEllipsis();
1841     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1842     if (!ellipsis8.isEmpty()) {
1843         return ellipsis8;
1844     } else {
1845         return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1846     }
1847 }
1848 
1849 WordBreakType ParagraphImpl::getWordBreakType() const {
1850     return fParagraphStyle.getStrutStyle().getWordBreakType();
1851 }
1852 
1853 LineBreakStrategy ParagraphImpl::getLineBreakStrategy() const {
1854     return fParagraphStyle.getStrutStyle().getLineBreakStrategy();
1855 }
1856 
1857 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1858 
1859   SkASSERT(from == 0 && to == fText.size());
1860   auto defaultStyle = fParagraphStyle.getTextStyle();
1861   defaultStyle.setFontSize(fontSize);
1862   fParagraphStyle.setTextStyle(defaultStyle);
1863 
1864   for (auto& textStyle : fTextStyles) {
1865     textStyle.fStyle.setFontSize(fontSize);
1866   }
1867 
1868   fState = std::min(fState, kIndexed);
1869   fOldWidth = 0;
1870   fOldHeight = 0;
1871 }
1872 
1873 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
1874     fParagraphStyle.setTextAlign(textAlign);
1875 
1876     if (fState >= kLineBroken) {
1877         fState = kLineBroken;
1878     }
1879 }
1880 
1881 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
1882     SkASSERT(from == 0 && to == fText.size());
1883     auto defaultStyle = fParagraphStyle.getTextStyle();
1884     defaultStyle.setForegroundColor(paint);
1885     fParagraphStyle.setTextStyle(defaultStyle);
1886 
1887     for (auto& textStyle : fTextStyles) {
1888         textStyle.fStyle.setForegroundColor(paint);
1889     }
1890 }
1891 
1892 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1893     SkASSERT(from == 0 && to == fText.size());
1894     auto defaultStyle = fParagraphStyle.getTextStyle();
1895     defaultStyle.setBackgroundColor(paint);
1896     fParagraphStyle.setTextStyle(defaultStyle);
1897 
1898     for (auto& textStyle : fTextStyles) {
1899         textStyle.fStyle.setBackgroundColor(paint);
1900     }
1901 }
1902 
1903 #ifdef OHOS_SUPPORT
1904 ParagraphPainter::PaintID ParagraphImpl::updateTextStyleColorAndForeground(TextStyle& textStyle, SkColor color)
1905 {
1906     textStyle.setColor(color);
1907     if (textStyle.hasForeground()) {
1908         auto paintOrID = textStyle.getForegroundPaintOrID();
1909         SkPaint* paint = std::get_if<SkPaint>(&paintOrID);
1910         if (paint) {
1911             paint->setColor(color);
1912             textStyle.setForegroundPaint(*paint);
1913         } else {
1914             auto paintID = std::get_if<ParagraphPainter::PaintID>(&paintOrID);
1915             if (paintID) {
1916                 return *paintID;
1917             }
1918         }
1919     }
1920     return INVALID_PAINT_ID;
1921 }
1922 
1923 std::vector<ParagraphPainter::PaintID> ParagraphImpl::updateColor(size_t from, size_t to, SkColor color) {
1924     std::vector<ParagraphPainter::PaintID> unresolvedPaintID;
1925     if (from >= to) {
1926         return unresolvedPaintID;
1927     }
1928     this->ensureUTF16Mapping();
1929     from = (from < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[from] : fText.size();
1930     to = (to < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[to] : fText.size();
1931     if (from == 0 && to == fText.size()) {
1932         auto defaultStyle = fParagraphStyle.getTextStyle();
1933         auto paintID = updateTextStyleColorAndForeground(defaultStyle, color);
1934         if (paintID != INVALID_PAINT_ID) {
1935             unresolvedPaintID.emplace_back(paintID);
1936         }
1937         fParagraphStyle.setTextStyle(defaultStyle);
1938     }
1939 
1940     for (auto& textStyle : fTextStyles) {
1941         auto& fRange = textStyle.fRange;
1942         if (to < fRange.end) {
1943             break;
1944         }
1945         if (from > fRange.start) {
1946             continue;
1947         }
1948         auto paintID = updateTextStyleColorAndForeground(textStyle.fStyle, color);
1949         if (paintID != INVALID_PAINT_ID) {
1950             unresolvedPaintID.emplace_back(paintID);
1951         }
1952     }
1953     for (auto& line : fLines) {
1954         line.setTextBlobCachePopulated(false);
1955     }
1956     return unresolvedPaintID;
1957 }
1958 #endif
1959 
1960 SkTArray<TextIndex> ParagraphImpl::countSurroundingGraphemes(TextRange textRange) const {
1961     textRange = textRange.intersection({0, fText.size()});
1962     SkTArray<TextIndex> graphemes;
1963     if ((fCodeUnitProperties[textRange.start] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1964         // Count the previous partial grapheme
1965         graphemes.emplace_back(textRange.start);
1966     }
1967     for (auto index = textRange.start; index < textRange.end; ++index) {
1968         if ((fCodeUnitProperties[index] & SkUnicode::CodeUnitFlags::kGraphemeStart) != 0) {
1969             graphemes.emplace_back(index);
1970         }
1971     }
1972     return graphemes;
1973 }
1974 
1975 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) const {
1976     while (utf8 > 0 &&
1977           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1978         --utf8;
1979     }
1980     return utf8;
1981 }
1982 
1983 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) const {
1984     while (utf8 < fText.size() &&
1985           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1986         ++utf8;
1987     }
1988     return utf8;
1989 }
1990 
1991 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) const {
1992     while (utf8 < fText.size() &&
1993           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1994         ++utf8;
1995     }
1996     return utf8;
1997 }
1998 
1999 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) const {
2000     while (utf8 > 0 &&
2001           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
2002         --utf8;
2003     }
2004     return utf8;
2005 }
2006 
2007 void ParagraphImpl::ensureUTF16Mapping() {
2008     fillUTF16MappingOnce([&] {
2009         fUnicode->extractUtfConversionMapping(
2010                 this->text(),
2011                 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
2012                 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
2013     });
2014 }
2015 
2016 void ParagraphImpl::visit(const Visitor& visitor) {
2017 #ifndef USE_SKIA_TXT
2018     int lineNumber = 0;
2019     for (auto& line : fLines) {
2020         line.ensureTextBlobCachePopulated();
2021         for (auto& rec : line.fTextBlobCache) {
2022             SkTextBlob::Iter iter(*rec.fBlob);
2023             SkTextBlob::Iter::ExperimentalRun run;
2024 
2025             SkSTArray<128, uint32_t> clusterStorage;
2026             const Run* R = rec.fVisitor_Run;
2027             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
2028 
2029             if (R->fClusterStart > 0) {
2030                 int count = R->fClusterIndexes.size();
2031                 clusterStorage.reset(count);
2032                 for (int i = 0; i < count; ++i) {
2033                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
2034                 }
2035                 clusterPtr = &clusterStorage[0];
2036             }
2037             clusterPtr += rec.fVisitor_Pos;
2038 
2039             while (iter.experimentalNext(&run)) {
2040                 const Paragraph::VisitorInfo info = {
2041                     run.font,
2042                     rec.fOffset,
2043                     rec.fClipRect.fRight,
2044                     run.count,
2045                     run.glyphs,
2046                     run.positions,
2047                     clusterPtr,
2048                     0,  // flags
2049                 };
2050                 visitor(lineNumber, &info);
2051                 clusterPtr += run.count;
2052             }
2053         }
2054         visitor(lineNumber, nullptr);   // signal end of line
2055         lineNumber += 1;
2056     }
2057 #endif
2058 }
2059 
2060 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
2061     for (size_t i = 0; i < fLines.size(); ++i) {
2062         auto& line = fLines[i];
2063         if (line.text().contains({codeUnitIndex, codeUnitIndex + 1})) {
2064             return i;
2065         }
2066     }
2067     return -1;
2068 }
2069 
2070 bool ParagraphImpl::getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const {
2071     if (lineNumber < 0 || static_cast<size_t>(lineNumber) >= fLines.size()) {
2072         return false;
2073     }
2074     auto& line = fLines[lineNumber];
2075     if (lineMetrics) {
2076         *lineMetrics = line.getMetrics();
2077     }
2078     return true;
2079 }
2080 
2081 TextRange ParagraphImpl::getActualTextRange(int lineNumber, bool includeSpaces) const {
2082     if (lineNumber < 0 || static_cast<size_t>(lineNumber) >= fLines.size()) {
2083         return EMPTY_TEXT;
2084     }
2085     auto& line = fLines[lineNumber];
2086     return includeSpaces ? line.text() : line.trimmedText();
2087 }
2088 
2089 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
2090     for (size_t i = 0; i < fLines.size(); ++i) {
2091         auto& line = fLines[i];
2092         if (!line.text().contains({codeUnitIndex, codeUnitIndex})) {
2093             continue;
2094         }
2095         for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
2096             auto& cluster = fClusters[c];
2097             if (cluster.contains(codeUnitIndex)) {
2098                 std::vector<TextBox> boxes;
2099                 line.getRectsForRange(cluster.textRange(),
2100                                       RectHeightStyle::kTight,
2101                                       RectWidthStyle::kTight,
2102                                       boxes);
2103                 if (boxes.size() > 0) {
2104                     if (glyphInfo) {
2105                         *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
2106                     }
2107                     return true;
2108                 }
2109             }
2110         }
2111         return false;
2112     }
2113     return false;
2114 }
2115 
2116 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
2117                                              SkScalar dy,
2118                                              GlyphClusterInfo* glyphInfo) {
2119     auto res = this->getGlyphPositionAtCoordinate(dx, dy);
2120     auto textIndex = res.position + (res.affinity == Affinity::kDownstream ? 0 : 1);
2121     GlyphClusterInfo gci;
2122     if (this->getGlyphClusterAt(textIndex, glyphInfo ? glyphInfo : &gci)) {
2123         return true;
2124     } else {
2125         return false;
2126     }
2127 }
2128 
2129 #ifndef USE_SKIA_TXT
2130 SkFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const {
2131     for (auto& run : fRuns) {
2132         if (run.textRange().contains({codeUnitIndex, codeUnitIndex})) {
2133             return run.font();
2134         }
2135     }
2136     return SkFont();
2137 }
2138 #else
2139 RSFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const
2140 {
2141     for (auto& run : fRuns) {
2142         if (run.textRange().contains({codeUnitIndex, codeUnitIndex})) {
2143             return run.font();
2144         }
2145     }
2146     return RSFont();
2147 }
2148 #endif
2149 
2150 std::vector<Paragraph::FontInfo> ParagraphImpl::getFonts() const {
2151     std::vector<FontInfo> results;
2152     for (auto& run : fRuns) {
2153         results.emplace_back(run.font(), run.textRange());
2154     }
2155     return results;
2156 }
2157 
2158 #ifndef USE_SKIA_TXT
2159 SkFontMetrics ParagraphImpl::measureText() {
2160     SkFontMetrics metrics;
2161     if (fRuns.empty()) {
2162         return metrics;
2163     }
2164 
2165     const auto& firstFont = fRuns.front().font();
2166     SkRect firstBounds;
2167     auto firstStr = text(fRuns.front().textRange());
2168     firstFont.getMetrics(&metrics);
2169 #ifdef OHOS_SUPPORT
2170     auto decompressFont = firstFont;
2171     scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
2172     metricsIncludeFontPadding(&metrics, decompressFont);
2173 #endif
2174     firstFont.measureText(firstStr.data(), firstStr.size(), SkTextEncoding::kUTF8, &firstBounds, nullptr);
2175     fGlyphsBoundsTop = firstBounds.top();
2176     fGlyphsBoundsBottom = firstBounds.bottom();
2177     fGlyphsBoundsLeft = firstBounds.left();
2178     SkScalar realWidth = 0;
2179     for (size_t i = 0; i < fRuns.size(); ++i) {
2180         auto run = fRuns[i];
2181         const auto& font = run.font();
2182         SkRect bounds;
2183         auto str = text(run.textRange());
2184         auto advance = font.measureText(str.data(), str.size(), SkTextEncoding::kUTF8, &bounds, nullptr);
2185         realWidth += advance;
2186         if (i == 0) {
2187             realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
2188         }
2189         if (i == (fRuns.size() - 1)) {
2190             realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
2191         }
2192         fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.top());
2193         fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.bottom());
2194     }
2195     fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
2196     return metrics;
2197 }
2198 #else
2199 RSFontMetrics ParagraphImpl::measureText()
2200 {
2201     RSFontMetrics metrics;
2202     if (fRuns.empty()) {
2203         return metrics;
2204     }
2205 
2206     auto& firstFont = const_cast<RSFont&>(fRuns.front().font());
2207     RSRect firstBounds;
2208     auto firstStr = text(fRuns.front().textRange());
2209     firstFont.GetMetrics(&metrics);
2210 #ifdef OHOS_SUPPORT
2211     auto decompressFont = firstFont;
2212     scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
2213     metricsIncludeFontPadding(&metrics, decompressFont);
2214 #endif
2215     firstFont.MeasureText(firstStr.data(), firstStr.size(), RSDrawing::TextEncoding::UTF8, &firstBounds);
2216     fGlyphsBoundsTop = firstBounds.GetTop();
2217     fGlyphsBoundsBottom = firstBounds.GetBottom();
2218     fGlyphsBoundsLeft = firstBounds.GetLeft();
2219     float realWidth = 0;
2220     for (size_t i = 0; i < fRuns.size(); ++i) {
2221         auto run = fRuns[i];
2222         auto& font = const_cast<RSFont&>(run.font());
2223         RSRect bounds;
2224         auto str = text(run.textRange());
2225         auto advance = font.MeasureText(str.data(), str.size(), RSDrawing::TextEncoding::UTF8, &bounds);
2226         realWidth += advance;
2227         if (i == 0) {
2228             realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2229         }
2230         if (i == (fRuns.size() - 1)) {
2231             realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2232         }
2233         fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.GetTop());
2234         fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.GetBottom());
2235     }
2236     fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
2237     return metrics;
2238 }
2239 #endif
2240 
2241 std::vector<std::unique_ptr<TextLineBase>> ParagraphImpl::GetTextLines() {
2242     std::vector<std::unique_ptr<TextLineBase>> textLineBases;
2243     for (auto& line: fLines) {
2244 #ifdef OHOS_SUPPORT
2245         std::unique_ptr<TextLineBaseImpl> textLineBaseImplPtr =
2246             std::make_unique<TextLineBaseImpl>(std::make_unique<TextLine>(std::move(line)));
2247 #else
2248         std::unique_ptr<TextLineBaseImpl> textLineBaseImplPtr = std::make_unique<TextLineBaseImpl>(&line);
2249 #endif
2250         textLineBases.emplace_back(std::move(textLineBaseImplPtr));
2251     }
2252 
2253     return textLineBases;
2254 }
2255 
2256 #ifdef OHOS_SUPPORT
2257 size_t ParagraphImpl::prefixByteCountUntilChar(size_t index) {
2258     convertUtf8ToUnicode(fText);
2259     if (fUnicodeIndexForUTF8Index.empty()) {
2260         return std::numeric_limits<size_t>::max();
2261     }
2262     auto it = std::lower_bound(fUnicodeIndexForUTF8Index.begin(), fUnicodeIndexForUTF8Index.end(), index);
2263     if (it != fUnicodeIndexForUTF8Index.end() && *it == index) {
2264         return std::distance(fUnicodeIndexForUTF8Index.begin(), it);
2265     } else {
2266         return std::numeric_limits<size_t>::max();
2267     }
2268 }
2269 
2270 void ParagraphImpl::copyProperties(const ParagraphImpl& source) {
2271     fText = source.fText;
2272     fTextStyles = source.fTextStyles;
2273     fPlaceholders = source.fPlaceholders;
2274     fParagraphStyle = source.fParagraphStyle;
2275     fFontCollection = source.fFontCollection;
2276     fUnicode = source.fUnicode;
2277 
2278     fState = kUnknown;
2279     fUnresolvedGlyphs = 0;
2280     fPicture = nullptr;
2281     fStrutMetrics = false;
2282     fOldWidth = 0;
2283     fOldHeight = 0;
2284     fHasLineBreaks = false;
2285     fHasWhitespacesInside = false;
2286     fTrailingSpaces = 0;
2287 }
2288 
2289 std::unique_ptr<Paragraph> ParagraphImpl::createCroppedCopy(size_t startIndex, size_t count) {
2290     std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2291     paragraph->copyProperties(*this);
2292 
2293     // change range
2294     auto validStart = prefixByteCountUntilChar(startIndex);
2295     if (validStart == std::numeric_limits<size_t>::max()) {
2296         return nullptr;
2297     }
2298     // For example, when the clipped string str1 is "123456789"
2299     // startIndex=2, count=std:: numeric_imits<size_t>:: max(), the resulting string str2 is "3456789".
2300     // When startIndex=3 and count=3, crop the generated string str3 to "456"
2301     TextRange firstDeleteRange(0, validStart);
2302     paragraph->fText.remove(0, validStart);
2303     paragraph->resetTextStyleRange(firstDeleteRange);
2304     paragraph->resetPlaceholderRange(firstDeleteRange);
2305 
2306     if (count != std::numeric_limits<size_t>::max()) {
2307         auto invalidStart = paragraph->prefixByteCountUntilChar(count);
2308         if (invalidStart == std::numeric_limits<size_t>::max()) {
2309             return nullptr;
2310         }
2311         auto invalidEnd = paragraph->fText.size();
2312         TextRange secodeDeleteRange(invalidStart, invalidEnd);
2313         paragraph->fText.remove(invalidStart, invalidEnd - invalidStart);
2314         paragraph->resetTextStyleRange(secodeDeleteRange);
2315         paragraph->resetPlaceholderRange(secodeDeleteRange);
2316     }
2317     return paragraph;
2318 }
2319 
2320 void ParagraphImpl::initUnicodeText() {
2321     this->fUnicodeText = convertUtf8ToUnicode(fText);
2322 }
2323 
2324 // Currently, only support to generate text and text shadow paint regions.
2325 // Can't accurately calculate the paint region of italic fonts(including fake italic).
2326 SkIRect ParagraphImpl::generatePaintRegion(SkScalar x, SkScalar y) {
2327     if (fState < kFormatted) {
2328         TEXT_LOGW("Call generatePaintRegion when paragraph is not formatted");
2329         return SkIRect::MakeXYWH(x, y, 0, 0);
2330     }
2331 
2332     if (fPaintRegion.has_value()) {
2333         return fPaintRegion.value().makeOffset(x, y).roundOut();
2334     }
2335 
2336     fPaintRegion = SkRect::MakeEmpty();
2337     for (auto& line : fLines) {
2338         SkRect linePaintRegion = line.generatePaintRegion(0, 0);
2339         fPaintRegion->join(linePaintRegion);
2340     }
2341     return fPaintRegion.value().makeOffset(x, y).roundOut();
2342 }
2343 #endif
2344 
2345 std::unique_ptr<Paragraph> ParagraphImpl::CloneSelf()
2346 {
2347     std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2348 
2349     paragraph->fFontCollection = this->fFontCollection;
2350     paragraph->fParagraphStyle = this->fParagraphStyle;
2351     paragraph->fAlphabeticBaseline = this->fAlphabeticBaseline;
2352     paragraph->fIdeographicBaseline = this->fIdeographicBaseline;
2353     paragraph->fGlyphsBoundsTop = this->fGlyphsBoundsTop;
2354     paragraph->fGlyphsBoundsBottom = this->fGlyphsBoundsBottom;
2355     paragraph->fGlyphsBoundsLeft = this->fGlyphsBoundsLeft;
2356     paragraph->fGlyphsBoundsRight = this->fGlyphsBoundsRight;
2357     paragraph->fHeight = this->fHeight;
2358     paragraph->fWidth = this->fWidth;
2359     paragraph->fMaxIntrinsicWidth = this->fMaxIntrinsicWidth;
2360     paragraph->fMinIntrinsicWidth = this->fMinIntrinsicWidth;
2361     paragraph->fLongestLine = this->fLongestLine;
2362 #ifdef OHOS_SUPPORT
2363     paragraph->fLongestLineWithIndent = this->fLongestLineWithIndent;
2364 #endif
2365     paragraph->fExceededMaxLines = this->fExceededMaxLines;
2366 
2367     paragraph->fLetterSpaceStyles = this->fLetterSpaceStyles;
2368     paragraph->fWordSpaceStyles = this->fWordSpaceStyles;
2369     paragraph->fBackgroundStyles = this->fBackgroundStyles;
2370     paragraph->fForegroundStyles = this->fForegroundStyles;
2371     paragraph->fShadowStyles = this->fShadowStyles;
2372     paragraph->fDecorationStyles = this->fDecorationStyles;
2373     paragraph->fTextStyles = this->fTextStyles;
2374     paragraph->fPlaceholders = this->fPlaceholders;
2375     paragraph->fText = this->fText;
2376 
2377     paragraph->fState = this->fState;
2378     paragraph->fRuns = this->fRuns;
2379     paragraph->fClusters = this->fClusters;
2380     paragraph->fCodeUnitProperties = this->fCodeUnitProperties;
2381     paragraph->fClustersIndexFromCodeUnit = this->fClustersIndexFromCodeUnit;
2382 
2383     paragraph->fWords = this->fWords;
2384     paragraph->fIndents = this->fIndents;
2385     paragraph->rtlTextSize = this->rtlTextSize;
2386     paragraph->ltrTextSize = this->ltrTextSize;
2387     paragraph->fBidiRegions = this->fBidiRegions;
2388 
2389     paragraph->fUTF8IndexForUTF16Index = this->fUTF8IndexForUTF16Index;
2390     paragraph->fUTF16IndexForUTF8Index = this->fUTF16IndexForUTF8Index;
2391     paragraph->fUnresolvedGlyphs = this->fUnresolvedGlyphs;
2392     paragraph->isMiddleEllipsis = this->isMiddleEllipsis;
2393     paragraph->fUnresolvedCodepoints = this->fUnresolvedCodepoints;
2394 
2395     for (auto& line : this->fLines) {
2396         paragraph->fLines.emplace_back(line.CloneSelf());
2397     }
2398 
2399     paragraph->fPicture = this->fPicture;
2400     paragraph->fFontSwitches = this->fFontSwitches;
2401     paragraph->fEmptyMetrics = this->fEmptyMetrics;
2402     paragraph->fStrutMetrics = this->fStrutMetrics;
2403 
2404     paragraph->fOldWidth = this->fOldWidth;
2405     paragraph->fOldHeight = this->fOldHeight;
2406     paragraph->fMaxWidthWithTrailingSpaces = this->fMaxWidthWithTrailingSpaces;
2407     paragraph->fOldMaxWidth = this->fOldMaxWidth;
2408     paragraph->allTextWidth = this->allTextWidth;
2409 
2410     paragraph->fUnicode = this->fUnicode;
2411     paragraph->fHasLineBreaks = this->fHasLineBreaks;
2412     paragraph->fHasWhitespacesInside = this->fHasWhitespacesInside;
2413     paragraph->fTrailingSpaces = this->fTrailingSpaces;
2414     paragraph->fLineNumber = this->fLineNumber;
2415     paragraph->fEllipsisRange = this->fEllipsisRange;
2416 
2417     for (auto& run : paragraph->fRuns) {
2418         run.setOwner(paragraph.get());
2419     }
2420     for (auto& cluster : paragraph->fClusters) {
2421         cluster.setOwner(paragraph.get());
2422     }
2423     for (auto& line : paragraph->fLines) {
2424         line.setParagraphImpl(paragraph.get());
2425     }
2426     return paragraph;
2427 }
2428 
2429 }  // namespace textlayout
2430 }  // namespace skia
2431