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