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