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